diff --git a/README.md b/README.md index 91d48e5e50e9899cfa1270ff2c5522ed1e40ed4f..966b03ca726183d87db8f3ce6599949094a40ccf 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ Install CIFTools `npm install ciftools -g` cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD + cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/cif-core.ts -p CifCore -aa **GraphQL schemas** diff --git a/data/cif-core.csv b/data/cif-core.csv deleted file mode 100644 index 25ce0320c9e0e7c60a833309094d6a27814b7a4c..0000000000000000000000000000000000000000 --- a/data/cif-core.csv +++ /dev/null @@ -1,60 +0,0 @@ -audit.block_doi - -database_code.depnum_ccdc_archive - -chemical.name_systematic -chemical.name_common -chemical.melting_point - -chemical_formula.moiety -chemical_formula.sum -chemical_formula.weight - -atom_type.symbol -atom_type.description - -atom_type_scat.dispersion_real -atom_type_scat.dispersion_imag -atom_type_scat.source - -space_group.crystal_system -space_group.name_H-M_full -space_group_symop.operation_xyz - -cell.length_a -cell.length_b -cell.length_c -cell.angle_alpha -cell.angle_beta -cell.angle_gamma -cell.volume -cell.formula_units_Z - -atom_site.label -atom_site.type_symbol -atom_site.fract_x -atom_site.fract_y -atom_site.fract_z -atom_site.U_iso_or_equiv -atom_site.adp_type -atom_site.occupancy -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_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 - -geom_bond.atom_site_label_1 -geom_bond.atom_site_label_2 -geom_bond.distance -geom_bond.site_symmetry_2 -geom_bond.publ_flag \ No newline at end of file diff --git a/data/rcsb-graphql/codegen.yml b/data/rcsb-graphql/codegen.yml index d470dd1734e115746ca3d64fd343534388165706..726a47193be3b15ac8085a40842d210863f722c0 100644 --- a/data/rcsb-graphql/codegen.yml +++ b/data/rcsb-graphql/codegen.yml @@ -1,7 +1,7 @@ -schema: http://data-beta.rcsb.org/graphql +schema: https://data-beta.rcsb.org/graphql documents: './src/mol-model-props/rcsb/graphql/symmetry.gql.ts' generates: - './src/mol-model-props/rcsb/graphql/types.d.ts': + './src/mol-model-props/rcsb/graphql/types.ts': plugins: - add: '/* eslint-disable */' - time diff --git a/package-lock.json b/package-lock.json index 68aca65ce3021d1fc472ecb3836f900f775eb067..5cd5ce9e9f5b8b0c6b8e3a51e9cbd0b25dd54a4d 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index 094894bad700aceefd16acaa1e26d35d6a78add6..a376d66ec660ecd0a6d414d448da20af92c989c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "molstar", - "version": "0.5.0-dev.1", + "version": "0.5.1", "description": "A comprehensive macromolecular library.", "homepage": "https://github.com/molstar/molstar#readme", "repository": { @@ -72,9 +72,9 @@ "@graphql-codegen/typescript-graphql-request": "^1.12.2", "@graphql-codegen/typescript-operations": "^1.12.2", "@types/cors": "^2.8.6", - "@typescript-eslint/eslint-plugin": "^2.19.2", - "@typescript-eslint/eslint-plugin-tslint": "^2.19.2", - "@typescript-eslint/parser": "^2.19.2", + "@typescript-eslint/eslint-plugin": "^2.20.0", + "@typescript-eslint/eslint-plugin-tslint": "^2.20.0", + "@typescript-eslint/parser": "^2.20.0", "benchmark": "^2.1.4", "circular-dependency-plugin": "^5.2.0", "concurrently": "^5.1.0", @@ -82,7 +82,7 @@ "css-loader": "^3.4.2", "eslint": "^6.8.0", "extra-watch-webpack-plugin": "^1.0.3", - "file-loader": "^5.0.2", + "file-loader": "^5.1.0", "fs-extra": "^8.1.0", "http-server": "^0.12.1", "jest": "^25.1.0", @@ -95,20 +95,20 @@ "sass-loader": "^8.0.2", "simple-git": "^1.131.0", "style-loader": "^1.1.3", - "ts-jest": "^25.2.0", - "typescript": "^3.7.5", - "webpack": "^4.41.5", - "webpack-cli": "^3.3.10" + "ts-jest": "^25.2.1", + "typescript": "^3.8.2", + "webpack": "^4.41.6", + "webpack-cli": "^3.3.11" }, "dependencies": { "@types/argparse": "^1.0.38", "@types/benchmark": "^1.0.31", "@types/compression": "1.7.0", "@types/express": "^4.17.2", - "@types/jest": "^25.1.2", - "@types/node": "^13.7.0", + "@types/jest": "^25.1.3", + "@types/node": "^13.7.4", "@types/node-fetch": "^2.5.4", - "@types/react": "^16.9.19", + "@types/react": "^16.9.22", "@types/react-dom": "^16.9.5", "@types/swagger-ui-dist": "3.0.5", "argparse": "^1.0.10", diff --git a/src/apps/basic-wrapper/index.ts b/src/apps/basic-wrapper/index.ts index 3f8f15056b1e12358d4562042e570cdb8c45aa3b..79c65460b2a7fb765db156c3f5e0804b2feeb805 100644 --- a/src/apps/basic-wrapper/index.ts +++ b/src/apps/basic-wrapper/index.ts @@ -63,7 +63,7 @@ class BasicWrapper { return parsed .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }) - .apply(StateTransforms.Model.CustomModelProperties, { properties: [StripedResidues.propertyProvider.descriptor.name] }, { ref: 'props', state: { isGhost: false } }) + .apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [StripedResidues.propertyProvider.descriptor.name], properties: {} }, { ref: 'props', state: { isGhost: false } }) .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' }); } diff --git a/src/apps/chem-comp-bond/create-table.ts b/src/apps/chem-comp-bond/create-table.ts index 84091a6d6fc1fe54c9d80585a8a78b80c9a46681..1e26c4935c4e44adf886ccfaee13c736e08ff9f4 100644 --- a/src/apps/chem-comp-bond/create-table.ts +++ b/src/apps/chem-comp-bond/create-table.ts @@ -144,9 +144,9 @@ async function createBonds() { const comp_id: string[] = [] const atom_id_1: string[] = [] const atom_id_2: string[] = [] - const value_order: string[] = [] - const pdbx_aromatic_flag: string[] = [] - const pdbx_stereo_config: string[] = [] + const value_order: typeof mmCIF_chemCompBond_schema['value_order']['T'][] = [] + const pdbx_aromatic_flag: typeof mmCIF_chemCompBond_schema['pdbx_aromatic_flag']['T'][] = [] + const pdbx_stereo_config: typeof mmCIF_chemCompBond_schema['pdbx_stereo_config']['T'][] = [] const molstar_protonation_variant: string[] = [] function addBonds(compId: string, ccb: CCB, protonationVariant: boolean) { diff --git a/src/apps/state-docs/pd-to-md.ts b/src/apps/state-docs/pd-to-md.ts index 578753df00e954937af3058c043e397aff650d43..f0df30519f9e4e18dfd3cd60509d6b45baf244f0 100644 --- a/src/apps/state-docs/pd-to-md.ts +++ b/src/apps/state-docs/pd-to-md.ts @@ -22,6 +22,7 @@ function paramInfo(param: PD.Any, offset: number): string { case 'color-list': return `One of ${oToS(param.options)}`; case 'vec3': return `3D vector [x, y, z]`; case 'file': return `JavaScript File Handle`; + case 'file-list': return `JavaScript FileList Handle`; case 'select': return `One of ${oToS(param.options)}`; case 'text': return 'String'; case 'interval': return `Interval [min, max]`; diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts index 980d525551d478941f88f7101e3158e94f55df51..507ef7a19c151e9e7dfbb02c55540c4fbcfd25b5 100644 --- a/src/apps/structure-info/model.ts +++ b/src/apps/structure-info/model.ts @@ -16,6 +16,8 @@ import { openCif, downloadCif } from './helpers'; import { Vec3 } from '../../mol-math/linear-algebra'; import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif'; import { Sequence } from '../../mol-model/sequence'; +import { ModelSecondaryStructure } from '../../mol-model-formats/structure/property/secondary-structure'; +import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry'; async function downloadFromPdb(pdb: string) { @@ -50,8 +52,10 @@ export function residueLabel(model: Model, rI: number) { export function printSecStructure(model: Model) { console.log('\nSecondary Structure\n============='); const { residues } = model.atomicHierarchy; - const { key, elements } = model.properties.secondaryStructure; + const secondaryStructure = ModelSecondaryStructure.Provider.get(model); + if (!secondaryStructure) return + const { key, elements } = secondaryStructure const count = residues._rowCount; let rI = 0; while (rI < count) { @@ -148,7 +152,7 @@ export function printRings(structure: Structure) { export function printUnits(structure: Structure) { console.log('\nUnits\n============='); - const l = StructureElement.Location.create(); + const l = StructureElement.Location.create(structure); for (const unit of structure.units) { l.unit = unit; @@ -179,7 +183,8 @@ export function printUnits(structure: Structure) { export function printSymmetryInfo(model: Model) { console.log('\nSymmetry Info\n============='); - const { symmetry } = model; + const symmetry = ModelSymmetry.Provider.get(model) + if (!symmetry) return const { size, anglesInRadians } = symmetry.spacegroup.cell; console.log(`Spacegroup: ${symmetry.spacegroup.name} size: ${Vec3.toString(size)} angles: ${Vec3.toString(anglesInRadians)}`); console.log(`Assembly names: ${symmetry.assemblies.map(a => a.id).join(', ')}`); diff --git a/src/apps/viewer/extensions/cellpack/model.ts b/src/apps/viewer/extensions/cellpack/model.ts index 352bd8a7ea68b7739d38bedbfb660f3438ae5a9d..bae20822d53b91a0b15532b84b2d34809b01da64 100644 --- a/src/apps/viewer/extensions/cellpack/model.ts +++ b/src/apps/viewer/extensions/cellpack/model.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -11,7 +11,7 @@ import { ParamDefinition as PD } from '../../../../mol-util/param-definition'; import { Ingredient, CellPacking, Cell } from './data'; import { getFromPdb, getFromCellPackDB } from './util'; import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../../../mol-model/structure'; -import { trajectoryFromMmCIF } from '../../../../mol-model-formats/structure/mmcif'; +import { trajectoryFromMmCIF, MmcifFormat } from '../../../../mol-model-formats/structure/mmcif'; import { trajectoryFromPDB } from '../../../../mol-model-formats/structure/pdb'; import { Mat4, Vec3, Quat } from '../../../../mol-math/linear-algebra'; import { SymmetryOperator } from '../../../../mol-math/geometry'; @@ -28,11 +28,10 @@ import { compile } from '../../../../mol-script/runtime/query/compiler'; import { UniformColorThemeProvider } from '../../../../mol-theme/color/uniform'; import { ThemeRegistryContext } from '../../../../mol-theme/theme'; import { ColorTheme } from '../../../../mol-theme/color'; -import { _parse_mmCif } from '../../../../mol-model-formats/structure/mmcif/parser'; -import { ModelFormat } from '../../../../mol-model-formats/structure/format'; import { CifCategory, CifField } from '../../../../mol-io/reader/cif'; import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif'; import { Column } from '../../../../mol-data/db'; +import { createModels } from '../../../../mol-model-formats/structure/basic/parser'; function getCellPackModelUrl(fileName: string, baseUrl: string) { return `${baseUrl}/results/${fileName}` @@ -124,7 +123,10 @@ function getAssembly(transforms: Mat4[], structure: Structure) { } function getCifCurve(name: string, transforms: Mat4[], model: Model) { - const d = model.sourceData.data.atom_site + if (!MmcifFormat.is(model.sourceData)) throw new Error('mmcif source data needed') + + const { db } = model.sourceData.data + const d = db.atom_site const n = d._rowCount const rowCount = n * transforms.length @@ -201,8 +203,8 @@ function getCifCurve(name: string, transforms: Mat4[], model: Model) { } const categories = { - entity: CifCategory.ofTable('entity', model.sourceData.data.entity), - chem_comp: CifCategory.ofTable('chem_comp', model.sourceData.data.chem_comp), + entity: CifCategory.ofTable('entity', db.entity), + chem_comp: CifCategory.ofTable('chem_comp', db.chem_comp), atom_site: CifCategory.ofFields('atom_site', _atom_site) } @@ -217,8 +219,8 @@ async function getCurve(name: string, transforms: Mat4[], model: Model) { const cif = getCifCurve(name, transforms, model) const curveModelTask = Task.create('Curve Model', async ctx => { - const format = ModelFormat.mmCIF(cif) - const models = await _parse_mmCif(format, ctx) + const format = MmcifFormat.fromFrame(cif) + const models = await createModels(format.data.db, format, ctx) return models[0] }) diff --git a/src/examples/proteopedia-wrapper/coloring.ts b/src/examples/proteopedia-wrapper/coloring.ts index b694129e1dcc3e088dfd65416fa023472f361056..0f7bc4b3a5f423f5ccabcdb4f5cb38ca521508fb 100644 --- a/src/examples/proteopedia-wrapper/coloring.ts +++ b/src/examples/proteopedia-wrapper/coloring.ts @@ -54,7 +54,7 @@ export function createProteopediaCustomTheme(colors: number[]) { const colors = props.colors, colorCount = colors.length, defaultColor = colors[0].color; if (ctx.structure) { - const l = StructureElement.Location.create() + const l = StructureElement.Location.create(ctx.structure) const { models } = ctx.structure const asymIdSerialMap = new Map<string, number>() for (let i = 0, il = models.length; i < il; ++i) { diff --git a/src/examples/proteopedia-wrapper/helpers.ts b/src/examples/proteopedia-wrapper/helpers.ts index d376596d3157a6b9bda160bf5bb9346443dc69ae..8611b4ba02d43c5e12770e9935159e4a7520e08f 100644 --- a/src/examples/proteopedia-wrapper/helpers.ts +++ b/src/examples/proteopedia-wrapper/helpers.ts @@ -9,6 +9,7 @@ import { BuiltInStructureRepresentationsName } from '../../mol-repr/structure/re import { BuiltInColorThemeName } from '../../mol-theme/color'; import { AminoAcidNames } from '../../mol-model/structure/model/types'; import { PluginContext } from '../../mol-plugin/context'; +import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry'; export interface ModelInfo { hetResidues: { name: string, indices: ResidueIndex[] }[], @@ -72,10 +73,11 @@ export namespace ModelInfo { } const preferredAssemblyId = await pref; + const symmetry = ModelSymmetry.Provider.get(model) return { hetResidues: hetResidues, - assemblies: model.symmetry.assemblies.map(a => ({ id: a.id, details: a.details, isPreferred: a.id === preferredAssemblyId })), + assemblies: symmetry ? symmetry.assemblies.map(a => ({ id: a.id, details: a.details, isPreferred: a.id === preferredAssemblyId })) : [], preferredAssemblyId }; } diff --git a/src/examples/proteopedia-wrapper/index.ts b/src/examples/proteopedia-wrapper/index.ts index c83e4d66bb6546817600de648eb6428de0efd004..7254ea8b8727546c2c030bd517a0879cec262d2d 100644 --- a/src/examples/proteopedia-wrapper/index.ts +++ b/src/examples/proteopedia-wrapper/index.ts @@ -94,7 +94,7 @@ class MolStarProteopediaWrapper { const model = this.state.build().to(StateElements.Model); const s = model - .apply(StateTransforms.Model.CustomModelProperties, { properties: [EvolutionaryConservation.propertyProvider.descriptor.name] }, { ref: StateElements.ModelProps, state: { isGhost: false } }) + .apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [EvolutionaryConservation.propertyProvider.descriptor.name], properties: {} }, { ref: StateElements.ModelProps, state: { isGhost: false } }) .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: StateElements.Assembly }); s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: StateElements.Sequence }); diff --git a/src/mol-canvas3d/camera.ts b/src/mol-canvas3d/camera.ts index dc64ccf73d9b178d0cca0bd5a9af3fdcc042a537..37ac7d866989e92b87663c2b3466a63313ab4b76 100644 --- a/src/mol-canvas3d/camera.ts +++ b/src/mol-canvas3d/camera.ts @@ -264,12 +264,13 @@ function updatePers(camera: Camera) { function updateClip(camera: Camera) { const { radiusNear, radiusFar, mode, fog, clipFar } = camera.state - const cDist = Vec3.distance(camera.position, camera.target) - let near = cDist - radiusNear - let far = cDist + (clipFar ? radiusNear : radiusFar) + const normalizedFar = clipFar ? radiusNear : radiusFar + const cameraDistance = Vec3.distance(camera.position, camera.target) + let near = cameraDistance - radiusNear + let far = cameraDistance + normalizedFar const fogNearFactor = -(50 - fog) / 50 - let fogNear = cDist - (radiusNear * fogNearFactor) + let fogNear = cameraDistance - (normalizedFar * fogNearFactor) let fogFar = far if (mode === 'perspective') { diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index cf08c02c979b3ad50860a19b98679de956b62b31..a00217f4b7b7aed0e509e252a7fd5ffcd6483c66 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -2,6 +2,7 @@ * Copyright (c) 2018-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 { BehaviorSubject, Subscription } from 'rxjs'; @@ -37,7 +38,7 @@ import { isDebugMode } from '../mol-util/debug'; export const Canvas3DParams = { cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]), - cameraFog: PD.Numeric(50, { min: 1, max: 100, step: 1 }), + cameraFog: PD.Numeric(50, { min: 0, max: 100, step: 1 }), cameraClipFar: PD.Boolean(true), cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }), transparentBackground: PD.Boolean(false), @@ -56,30 +57,33 @@ export { Canvas3D } interface Canvas3D { readonly webgl: WebGLContext, - add: (repr: Representation.Any) => Promise<void> - remove: (repr: Representation.Any) => Promise<void> - update: (repr?: Representation.Any, keepBoundingSphere?: boolean) => void - clear: () => void - - // draw: (force?: boolean) => void - requestDraw: (force?: boolean) => void - animate: () => void - identify: (x: number, y: number) => PickingId | undefined - mark: (loci: Representation.Loci, action: MarkerAction) => void - getLoci: (pickingId: PickingId) => Representation.Loci + add(repr: Representation.Any): void + remove(repr: Representation.Any): void + /** + * This function must be called if animate() is not set up so that add/remove actions take place. + */ + commit(isSynchronous?: boolean): void + update(repr?: Representation.Any, keepBoundingSphere?: boolean): void + clear(): void + + requestDraw(force?: boolean): void + animate(): void + identify(x: number, y: number): PickingId | undefined + mark(loci: Representation.Loci, action: MarkerAction): void + getLoci(pickingId: PickingId): Representation.Loci readonly didDraw: BehaviorSubject<now.Timestamp> readonly reprCount: BehaviorSubject<number> - handleResize: () => void + handleResize(): void /** Focuses camera on scene's bounding sphere, centered and zoomed. */ - resetCamera: () => void + requestCameraReset(durationMs?: number): void readonly camera: Camera readonly boundingSphere: Readonly<Sphere3D> - downloadScreenshot: () => void - getPixelData: (variant: GraphicsRenderVariant) => PixelData - setProps: (props: Partial<Canvas3DProps>) => void - getImagePass: () => ImagePass + downloadScreenshot(): void + getPixelData(variant: GraphicsRenderVariant): PixelData + setProps(props: Partial<Canvas3DProps>): void + getImagePass(): ImagePass /** Returns a copy of the current Canvas3D instance props */ readonly props: Readonly<Canvas3DProps> @@ -87,7 +91,7 @@ interface Canvas3D { readonly stats: RendererStats readonly interaction: Canvas3dInteractionHelper['events'] - dispose: () => void + dispose(): void } const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now())) @@ -186,6 +190,7 @@ namespace Canvas3D { let drawPending = false let cameraResetRequested = false + let nextCameraResetDuration: number | undefined = void 0 function getLoci(pickingId: PickingId) { let loci: Loci = EmptyLoci @@ -220,7 +225,7 @@ namespace Canvas3D { } function render(force: boolean) { - if (scene.isCommiting || webgl.isContextLost) return false + if (webgl.isContextLost) return false let didRender = false controls.update(currentTime) @@ -262,7 +267,9 @@ namespace Canvas3D { function animate() { currentTime = now(); + commit(); camera.transition.tick(currentTime); + draw(false); if (!camera.transition.inTransition && !webgl.isContextLost) { interactionHelper.tick(currentTime); @@ -274,22 +281,35 @@ namespace Canvas3D { return webgl.isContextLost ? undefined : pickPass.identify(x, y) } - async function commit(renderObjects?: readonly GraphicsRenderObject[]) { - scene.update(renderObjects, false) + function commit(isSynchronous: boolean = false) { + const allCommited = commitScene(isSynchronous); + // Only reset the camera after the full scene has been commited. + if (allCommited) resolveCameraReset(); + } - return runTask(scene.commit()).then(() => { - if (cameraResetRequested && !scene.isCommiting) { - const { center, radius } = scene.boundingSphere - camera.focus(center, radius, radius) - cameraResetRequested = false - } - if (debugHelper.isEnabled) debugHelper.update() - requestDraw(true) - reprCount.next(reprRenderObjects.size) - }) + function resolveCameraReset() { + if (!cameraResetRequested) return; + const { center, radius } = scene.boundingSphere; + const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration + camera.focus(center, radius, radius, duration); + nextCameraResetDuration = void 0; + cameraResetRequested = false; + } + + const sceneCommitTimeoutMs = 250; + function commitScene(isSynchronous: boolean) { + if (!scene.needsCommit) return true; + + if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false; + + if (debugHelper.isEnabled) debugHelper.update(); + reprCount.next(reprRenderObjects.size); + return true; } function add(repr: Representation.Any) { + registerAutoUpdate(repr); + const oldRO = reprRenderObjects.get(repr) const newRO = new Set<GraphicsRenderObject>() repr.renderObjects.forEach(o => newRO.add(o)) @@ -303,7 +323,35 @@ namespace Canvas3D { repr.renderObjects.forEach(o => scene.add(o)) } reprRenderObjects.set(repr, newRO) - return commit(repr.renderObjects) + + scene.update(repr.renderObjects, false) + } + + function remove(repr: Representation.Any) { + unregisterAutoUpdate(repr); + + const renderObjects = reprRenderObjects.get(repr) + if (renderObjects) { + renderObjects.forEach(o => scene.remove(o)) + reprRenderObjects.delete(repr) + scene.update(repr.renderObjects, false, true) + } + } + + function registerAutoUpdate(repr: Representation.Any) { + if (reprUpdatedSubscriptions.has(repr)) return; + + reprUpdatedSubscriptions.set(repr, repr.updated.subscribe(_ => { + if (!repr.state.syncManually) add(repr); + })) + } + + function unregisterAutoUpdate(repr: Representation.Any) { + const updatedSubscription = reprUpdatedSubscriptions.get(repr); + if (updatedSubscription) { + updatedSubscription.unsubscribe(); + reprUpdatedSubscriptions.delete(repr); + } } handleResize() @@ -311,25 +359,9 @@ namespace Canvas3D { return { webgl, - add: (repr: Representation.Any) => { - reprUpdatedSubscriptions.set(repr, repr.updated.subscribe(_ => { - if (!repr.state.syncManually) add(repr) - })) - return add(repr) - }, - remove: (repr: Representation.Any) => { - const updatedSubscription = reprUpdatedSubscriptions.get(repr) - if (updatedSubscription) { - updatedSubscription.unsubscribe() - } - const renderObjects = reprRenderObjects.get(repr) - if (renderObjects) { - renderObjects.forEach(o => scene.remove(o)) - reprRenderObjects.delete(repr) - return commit() - } - return Promise.resolve() - }, + add, + remove, + commit, update: (repr, keepSphere) => { if (repr) { if (!reprRenderObjects.has(repr)) return; @@ -356,14 +388,9 @@ namespace Canvas3D { getLoci, handleResize, - resetCamera: () => { - if (scene.isCommiting) { - cameraResetRequested = true - } else { - const { center, radius } = scene.boundingSphere - camera.focus(center, radius, radius, p.cameraResetDurationMs) - requestDraw(true); - } + requestCameraReset: (durationMs) => { + nextCameraResetDuration = durationMs; + cameraResetRequested = true; }, camera, boundingSphere: scene.boundingSphere, diff --git a/src/mol-canvas3d/helper/bounding-sphere-helper.ts b/src/mol-canvas3d/helper/bounding-sphere-helper.ts index 4d0d18e2c026da2e68c7490c944144ba005974ea..69f69194144483bc2560bfe4d9bafbb56e3eddd6 100644 --- a/src/mol-canvas3d/helper/bounding-sphere-helper.ts +++ b/src/mol-canvas3d/helper/bounding-sphere-helper.ts @@ -82,7 +82,7 @@ export class BoundingSphereHelper { }) this.scene.update(void 0, false) - this.scene.syncCommit() + this.scene.commit() } syncVisibility() { diff --git a/src/mol-data/db/column.ts b/src/mol-data/db/column.ts index 1f25b531ea611511f567c059028320ce2ce7b227..8f242202ca93a747e465b0587c6fa6d8d7f1fee8 100644 --- a/src/mol-data/db/column.ts +++ b/src/mol-data/db/column.ts @@ -37,7 +37,7 @@ namespace Column { export type Coordinate = { '@type': 'coord', T: number } & Base<'float'> export type Tensor = { '@type': 'tensor', T: Tensors.Data, space: Tensors.Space, baseType: Int | Float } & Base<'tensor'> - export type Aliased<T> = { '@type': 'aliased', T: T } & Base<'str' | 'int'> + export type Aliased<T> = { '@type': 'aliased', T: T } & Base<T extends string ? 'str' : 'int'> export type List<T extends number|string> = { '@type': 'list', T: T[], separator: string, itemParse: (x: string) => T } & Base<'list'> export const str: Str = { '@type': 'str', T: '', valueType: 'str' }; @@ -137,6 +137,14 @@ namespace Column { return arrayColumn({ array, schema: Schema.str }); } + export function ofStringAliasArray<T extends string>(array: ArrayLike<T>) { + return arrayColumn<Schema.Aliased<T>>({ array, schema: Schema.Aliased(Schema.str) }); + } + + export function ofStringListArray<T extends string>(array: ArrayLike<T[]>, separator = ',') { + return arrayColumn<Schema.List<T>>({ array, schema: Schema.List<T>(separator, x => x as T) }); + } + export function ofIntTokens(tokens: Tokens) { const { count, data, indices } = tokens return lambdaColumn({ diff --git a/src/mol-data/db/table.ts b/src/mol-data/db/table.ts index 693fe3cd077b9c74434a450e259891ca75cd019c..b202468232b649e0591a39a496fc178bdf5d2f5a 100644 --- a/src/mol-data/db/table.ts +++ b/src/mol-data/db/table.ts @@ -9,7 +9,7 @@ import { sortArray } from '../util/sort' import { StringBuilder } from '../../mol-util'; /** A collection of columns */ -type Table<Schema extends Table.Schema> = { +type Table<Schema extends Table.Schema = any> = { readonly _rowCount: number, readonly _columns: ReadonlyArray<string>, readonly _schema: Schema @@ -21,7 +21,8 @@ namespace Table { export type Columns<S extends Schema> = { [C in keyof S]: Column<S[C]['T']> } export type Row<S extends Schema> = { [C in keyof S]: S[C]['T'] } export type Arrays<S extends Schema> = { [C in keyof S]: ArrayLike<S[C]['T']> } - export type PartialTable<S extends Table.Schema> = { readonly _rowCount: number, readonly _columns: ReadonlyArray<string> } & { [C in keyof S]?: Column<S[C]['T']> } + export type PartialColumns<S extends Schema> = { [C in keyof S]?: Column<S[C]['T']> } + export type PartialTable<S extends Table.Schema> = { readonly _rowCount: number, readonly _columns: ReadonlyArray<string> } & PartialColumns<S> export function is(t: any): t is Table<any> { return t && typeof t._rowCount === 'number' && !!t._columns && !!t._schema; @@ -47,6 +48,19 @@ namespace Table { return { _rowCount, _columns, _schema: schema, ...(columns as any) }; } + export function ofPartialColumns<S extends Schema, R extends Table<S> = Table<S>>(schema: S, partialColumns: PartialColumns<S>, rowCount: number): R { + const ret = Object.create(null); + const columns = Object.keys(schema); + ret._rowCount = rowCount; + ret._columns = columns; + ret._schema = schema; + for (const k of columns) { + if (k in partialColumns) ret[k] = partialColumns[k] + else ret[k] = Column.Undefined(rowCount, schema[k]) + } + return ret; + } + export function ofUndefinedColumns<S extends Schema, R extends Table<S> = Table<S>>(schema: S, rowCount: number): R { const ret = Object.create(null); const columns = Object.keys(schema); @@ -59,7 +73,7 @@ namespace Table { return ret; } - export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, rows: ArrayLike<Partial<Row<S>>>): R { + export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: S, rows: ArrayLike<Partial<Row<S>>>): R { const ret = Object.create(null); const rowCount = rows.length; const columns = Object.keys(schema); @@ -77,14 +91,19 @@ namespace Table { return ret as R; } - export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, arrays: Arrays<S>): R { + export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: S, arrays: Partial<Arrays<S>>): R { const ret = Object.create(null); const columns = Object.keys(schema); - ret._rowCount = arrays[columns[0]].length; + ret._rowCount = 0; ret._columns = columns; ret._schema = schema; for (const k of columns) { - (ret as any)[k] = typeof arrays[k] !== 'undefined' ? Column.ofArray({ array: arrays[k], schema: schema[k] }) : Column.Undefined(ret._rowCount, schema[k]); + if (typeof arrays[k] !== 'undefined') { + (ret as any)[k] = Column.ofArray({ array: arrays[k]!, schema: schema[k] }); + ret._rowCount = arrays[k]?.length; + } else { + (ret as any)[k] = Column.Undefined(ret._rowCount, schema[k]); + } } return ret as R; } @@ -153,7 +172,7 @@ namespace Table { } /** Sort and return a new table */ - export function sort<T extends Table<S>, S extends Schema>(table: T, cmp: (i: number, j: number) => number) { + export function sort<T extends Table>(table: T, cmp: (i: number, j: number) => number) { const indices = new Int32Array(table._rowCount); for (let i = 0, _i = indices.length; i < _i; i++) indices[i] = i; sortArray(indices, (_, i, j) => cmp(i, j)); @@ -177,7 +196,7 @@ namespace Table { return ret; } - export function areEqual<T extends Table<Schema>>(a: T, b: T) { + export function areEqual<T extends Table<any>>(a: T, b: T) { if (a._rowCount !== b._rowCount) return false; if (a._columns.length !== b._columns.length) return false; for (const c of a._columns) { diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index 4f85148b16f86c502ecfe73c3d91b4592aa82b9c..c9fdde6ddda907376edb2dfab6f54cf894516b1d 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -121,7 +121,7 @@ describe('renderer', () => { const points = createPoints() scene.add(points) - await scene.commit().run() + scene.commit() expect(ctx.stats.resourceCounts.attribute).toBe(4); expect(ctx.stats.resourceCounts.texture).toBe(5); expect(ctx.stats.resourceCounts.vertexArray).toBe(5); @@ -129,7 +129,7 @@ describe('renderer', () => { expect(ctx.stats.resourceCounts.shader).toBe(10); scene.remove(points) - await scene.commit().run() + scene.commit() expect(ctx.stats.resourceCounts.attribute).toBe(0); expect(ctx.stats.resourceCounts.texture).toBe(0); expect(ctx.stats.resourceCounts.vertexArray).toBe(0); diff --git a/src/mol-gl/commit-queue.ts b/src/mol-gl/commit-queue.ts new file mode 100644 index 0000000000000000000000000000000000000000..2cfd37d6206dcdde6c600c8d0fe7e074be551d5b --- /dev/null +++ b/src/mol-gl/commit-queue.ts @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { LinkedList } from '../mol-data/generic' +import { GraphicsRenderObject } from './render-object' + +type N = LinkedList.Node<GraphicsRenderObject> + +export class CommitQueue { + private removeList = LinkedList<GraphicsRenderObject>(); + private removeMap = new Map<GraphicsRenderObject, N>(); + private addList = LinkedList<GraphicsRenderObject>(); + private addMap = new Map<GraphicsRenderObject, N>(); + + get isEmpty() { + return this.removeList.count === 0 && this.addList.count === 0; + } + + add(o: GraphicsRenderObject) { + if (this.removeMap.has(o)) { + const a = this.removeMap.get(o)!; + this.removeMap.delete(o); + this.removeList.remove(a); + } + if (this.addMap.has(o)) return; + const b = this.addList.addLast(o); + this.addMap.set(o, b); + } + + remove(o: GraphicsRenderObject) { + if (this.addMap.has(o)) { + const a = this.addMap.get(o)!; + this.addMap.delete(o); + this.addList.remove(a); + } + if (this.removeMap.has(o)) return; + const b = this.removeList.addLast(o); + this.removeMap.set(o, b); + } + + tryGetRemove() { + const o = this.removeList.removeFirst(); + if (o) this.removeMap.delete(o); + return o; + } + + tryGetAdd() { + const o = this.addList.removeFirst(); + if (o) this.addMap.delete(o); + return o; + } +} diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index e47ae7f194970625aa738054fa9569dd7069caa6..ecfbbad30634189da8dcba6c39c2564c37937d4f 100644 --- a/src/mol-gl/scene.ts +++ b/src/mol-gl/scene.ts @@ -1,7 +1,8 @@ /** - * 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 Alexander Rose <alexander.rose@weirdbyte.de> + * @author David Sehnal <david.sehnal@gmail.com> */ import { Renderable } from './renderable' @@ -12,8 +13,9 @@ import { Object3D } from './object3d'; import { Sphere3D } from '../mol-math/geometry'; import { Vec3 } from '../mol-math/linear-algebra'; import { BoundaryHelper } from '../mol-math/geometry/boundary-helper'; -import { RuntimeContext, Task } from '../mol-task'; -import { AsyncQueue } from '../mol-util/async-queue'; +import { CommitQueue } from './commit-queue'; +import { now } from '../mol-util/now'; +import { arraySetRemove } from '../mol-util/array'; const boundaryHelper = new BoundaryHelper(); function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D { @@ -56,13 +58,12 @@ interface Scene extends Object3D { readonly count: number readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>> readonly boundingSphere: Sphere3D - readonly isCommiting: boolean - update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean) => void + update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean, isRemoving?: boolean) => void add: (o: GraphicsRenderObject) => void // Renderable<any> remove: (o: GraphicsRenderObject) => void - syncCommit: () => void - commit: () => Task<void> + commit: (maxTimeMs?: number) => boolean + readonly needsCommit: boolean has: (o: GraphicsRenderObject) => boolean clear: () => void forEach: (callbackFn: (value: Renderable<RenderableValues & BaseValues>, key: GraphicsRenderObject) => void) => void @@ -78,7 +79,7 @@ namespace Scene { const object3d = Object3D.create() - const add = (o: GraphicsRenderObject) => { + function add(o: GraphicsRenderObject) { if (!renderableMap.has(o)) { const renderable = createRenderable(ctx, o) renderables.push(renderable) @@ -91,47 +92,50 @@ namespace Scene { } } - const remove = (o: GraphicsRenderObject) => { + function remove(o: GraphicsRenderObject) { const renderable = renderableMap.get(o) if (renderable) { renderable.dispose() - renderables.splice(renderables.indexOf(renderable), 1) + arraySetRemove(renderables, renderable); renderableMap.delete(o) boundingSphereDirty = true } } - const commitQueue = new AsyncQueue<any>(); - const toAdd: GraphicsRenderObject[] = [] - const toRemove: GraphicsRenderObject[] = [] + const commitBulkSize = 100; + function commit(maxTimeMs: number) { + const start = now(); - type CommitParams = { toAdd: GraphicsRenderObject[], toRemove: GraphicsRenderObject[] } + let i = 0; - const step = 100 - const handle = async (ctx: RuntimeContext, arr: GraphicsRenderObject[], fn: (o: GraphicsRenderObject) => void, message: string) => { - for (let i = 0, il = arr.length; i < il; i += step) { - if (ctx.shouldUpdate) await ctx.update({ message, current: i, max: il }) - for (let j = i, jl = Math.min(i + step, il); j < jl; ++j) { - fn(arr[j]) - } + while (true) { + const o = commitQueue.tryGetRemove(); + if (!o) break; + remove(o); + if (++i % commitBulkSize === 0 && now() - start > maxTimeMs) return false; + } + + while (true) { + const o = commitQueue.tryGetAdd(); + if (!o) break; + add(o); + if (++i % commitBulkSize === 0 && now() - start > maxTimeMs) return false; } - } - const commit = async (ctx: RuntimeContext, p: CommitParams) => { - await handle(ctx, p.toRemove, remove, 'Removing GraphicsRenderObjects') - await handle(ctx, p.toAdd, add, 'Adding GraphicsRenderObjects') - if (ctx.shouldUpdate) await ctx.update({ message: 'Sorting GraphicsRenderObjects' }) renderables.sort(renderableSort) + return true; } + const commitQueue = new CommitQueue(); + return { get view () { return object3d.view }, get position () { return object3d.position }, get direction () { return object3d.direction }, get up () { return object3d.up }, - get isCommiting () { return commitQueue.length > 0 }, + // get isCommiting () { return commitQueue.length > 0 }, - update(objects, keepBoundingSphere) { + update(objects, keepBoundingSphere, isRemoving) { Object3D.update(object3d) if (objects) { for (let i = 0, il = objects.length; i < il; ++i) { @@ -139,44 +143,17 @@ namespace Scene { if (!o) continue; o.update(); } - } else { + } else if (!isRemoving) { for (let i = 0, il = renderables.length; i < il; ++i) { renderables[i].update() } } if (!keepBoundingSphere) boundingSphereDirty = true }, - add: (o: GraphicsRenderObject) => { - toAdd.push(o) - }, - remove: (o: GraphicsRenderObject) => { - toRemove.push(o) - }, - syncCommit: () => { - for (let i = 0, il = toRemove.length; i < il; ++i) remove(toRemove[i]) - toRemove.length = 0 - for (let i = 0, il = toAdd.length; i < il; ++i) add(toAdd[i]) - toAdd.length = 0 - renderables.sort(renderableSort) - }, - commit: () => { - const params = { toAdd: [ ...toAdd ], toRemove: [ ...toRemove ] } - toAdd.length = 0 - toRemove.length = 0 - - return Task.create('Commiting GraphicsRenderObjects', async ctx => { - const removed = await commitQueue.enqueue(params); - if (!removed) return; - - try { - await commit(ctx, params); - } finally { - commitQueue.handled(params); - } - }, () => { - commitQueue.remove(params); - }) - }, + add: (o: GraphicsRenderObject) => commitQueue.add(o), + remove: (o: GraphicsRenderObject) => commitQueue.remove(o), + commit: (maxTime = Number.MAX_VALUE) => commit(maxTime), + get needsCommit() { return !commitQueue.isEmpty; }, has: (o: GraphicsRenderObject) => { return renderableMap.has(o) }, diff --git a/src/mol-io/reader/_spec/cif.spec.ts b/src/mol-io/reader/_spec/cif.spec.ts index f8823d1309adb2141bbedbe99089c3cdd054aa90..8b25cf7b17b3aef5dd5708b24ad913316c01e8d1 100644 --- a/src/mol-io/reader/_spec/cif.spec.ts +++ b/src/mol-io/reader/_spec/cif.spec.ts @@ -8,6 +8,7 @@ import * as Data from '../cif/data-model' import * as Schema from '../cif/schema' import { Column } from '../../../mol-data/db' +import parse from '../cif/text/parser'; const columnData = `123abc d,e,f '4 5 6'`; // 123abc d,e,f '4 5 6' @@ -36,6 +37,24 @@ namespace TestSchema { export const schema = { test } } +test('cif triple quote', async () => { + const data = `data_test +_test.field1 '''123 " '' 1''' +_test.field2 ''' c glide reflection through the plane (x,1/4,z) +chosen as one of the generators of the space group'''`; + + const result = await parse(data).run(); + if (result.isError) { + expect(false).toBe(true); + return; + } + + const cat = result.result.blocks[0].categories['test']; + expect(cat.getField('field1')!.str(0)).toBe(`123 " '' 1`); + expect(cat.getField('field2')!.str(0)).toBe(` c glide reflection through the plane (x,1/4,z) +chosen as one of the generators of the space group`); +}); + describe('schema', () => { const db = Schema.toDatabase(TestSchema.schema, testBlock); it('property access', () => { diff --git a/src/mol-io/reader/cif.ts b/src/mol-io/reader/cif.ts index 436e3137c4e258f1ead1438cc41690af5168784d..63883a2fdf4a219d3f125dff0ece117db244264f 100644 --- a/src/mol-io/reader/cif.ts +++ b/src/mol-io/reader/cif.ts @@ -14,7 +14,7 @@ import { CCD_Schema, CCD_Database } from './cif/schema/ccd' import { BIRD_Schema, BIRD_Database } from './cif/schema/bird' import { dic_Schema, dic_Database } from './cif/schema/dic' import { DensityServer_Data_Schema, DensityServer_Data_Database } from './cif/schema/density-server' -import { cifCore_Database, cifCore_Schema, cifCore_Aliases } from './cif/schema/cif-core' +import { CifCore_Database, CifCore_Schema, CifCore_Aliases } from './cif/schema/cif-core' export const CIF = { parse: (data: string|Uint8Array) => typeof data === 'string' ? parseText(data) : parseBinary(data), @@ -27,7 +27,7 @@ export const CIF = { CCD: (frame: CifFrame) => toDatabase<CCD_Schema, CCD_Database>(CCD_Schema, frame), BIRD: (frame: CifFrame) => toDatabase<BIRD_Schema, BIRD_Database>(BIRD_Schema, frame), dic: (frame: CifFrame) => toDatabase<dic_Schema, dic_Database>(dic_Schema, frame), - cifCore: (frame: CifFrame) => toDatabase<cifCore_Schema, cifCore_Database>(cifCore_Schema, frame, cifCore_Aliases), + cifCore: (frame: CifFrame) => toDatabase<CifCore_Schema, CifCore_Database>(CifCore_Schema, frame, CifCore_Aliases), densityServer: (frame: CifFrame) => toDatabase<DensityServer_Data_Schema, DensityServer_Data_Database>(DensityServer_Data_Schema, frame), } } diff --git a/src/mol-io/reader/cif/schema/bird.ts b/src/mol-io/reader/cif/schema/bird.ts index 6a1912e5f3e55814706997eb11807912dc65abc0..6e87afbd818f490f10cbd99bfa696df40ff45a33 100644 --- a/src/mol-io/reader/cif/schema/bird.ts +++ b/src/mol-io/reader/cif/schema/bird.ts @@ -1,7 +1,7 @@ /** - * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * - * Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.320, IHM 1.05, CARB draft. + * Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft. * * @author molstar/ciftools package */ @@ -78,7 +78,7 @@ export const BIRD_Schema = { /** * Defines how this entity is represented in PDB data files. */ - represent_as: Aliased<'polymer' | 'single molecule'>(str), + represent_as: Aliased<'polymer' | 'single molecule' | 'branched'>(str), /** * For entities represented as single molecules, the identifier * corresponding to the chemical definition for the molecule. diff --git a/src/mol-io/reader/cif/schema/ccd.ts b/src/mol-io/reader/cif/schema/ccd.ts index ba71621291228682e276d0c02d2add2de407f71d..ae08d364df297f9d7e6bacc05f670c8a2f8ea926 100644 --- a/src/mol-io/reader/cif/schema/ccd.ts +++ b/src/mol-io/reader/cif/schema/ccd.ts @@ -1,7 +1,7 @@ /** - * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * - * Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.320, IHM 1.05, CARB draft. + * Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft. * * @author molstar/ciftools package */ diff --git a/src/mol-io/reader/cif/schema/cif-core.ts b/src/mol-io/reader/cif/schema/cif-core.ts index ddde84aaa47f344030ebe90a6d3750ab80acd1ef..e3de62df551ace8ad31174e095b522150402058f 100644 --- a/src/mol-io/reader/cif/schema/cif-core.ts +++ b/src/mol-io/reader/cif/schema/cif-core.ts @@ -1,7 +1,7 @@ /** - * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * - * Code-generated 'cifCore' schema file. Dictionary versions: CifCore 3.0.11. + * Code-generated 'CifCore' schema file. Dictionary versions: CifCore 3.0.11. * * @author molstar/ciftools package */ @@ -14,7 +14,7 @@ const int = Schema.int; const float = Schema.float; const str = Schema.str; -export const cifCore_Schema = { +export const CifCore_Schema = { /** * The CATEGORY of data items used to describe the parameters of * the crystal unit cell and their measurement. @@ -585,7 +585,7 @@ export const cifCore_Schema = { }, } -export const cifCore_Aliases = { +export const CifCore_Aliases = { 'space_group.name_H-M_full': [ 'symmetry_space_group_name_H-M', ], @@ -633,5 +633,5 @@ export const cifCore_Aliases = { ], } -export type cifCore_Schema = typeof cifCore_Schema; -export interface cifCore_Database extends Database<cifCore_Schema> {} \ No newline at end of file +export type CifCore_Schema = typeof CifCore_Schema; +export interface CifCore_Database extends Database<CifCore_Schema> {} \ No newline at end of file diff --git a/src/mol-io/reader/cif/schema/mmcif.ts b/src/mol-io/reader/cif/schema/mmcif.ts index d52d5763ed69b64273f3cbe77d6ded6601f50a88..484c694f06e0a44e735b072a1db878ba761f31de 100644 --- a/src/mol-io/reader/cif/schema/mmcif.ts +++ b/src/mol-io/reader/cif/schema/mmcif.ts @@ -1,7 +1,7 @@ /** - * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * - * Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.320, IHM 1.05, CARB draft. + * Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft. * * @author molstar/ciftools package */ @@ -1880,6 +1880,23 @@ export const mmCIF_Schema = { */ name: str, }, + /** + * PDBX_CHEM_COMP_SYNONYMS holds chemical name and synonym correspondences. + */ + pdbx_chem_comp_synonyms: { + /** + * The synonym of this particular chemical component. + */ + name: str, + /** + * The chemical component for which this synonym applies. + */ + comp_id: str, + /** + * The provenance of this synonym. + */ + provenance: Aliased<'AUTHOR' | 'DRUGBANK' | 'CHEBI' | 'CHEMBL' | 'PDB' | 'PUBCHEM'>(str), + }, /** * Data items in the CHEM_COMP_IDENTIFIER category provide * identifiers for chemical components. @@ -3665,7 +3682,7 @@ export const mmCIF_Schema = { /** * The type of crosslinker used. */ - linker_type: Aliased<'EDC' | 'DSS' | 'EGS' | 'BS3' | 'BS2G' | 'DST' | 'sulfo-SDA' | 'sulfo-SMCC' | 'DSSO' | 'DSG' | 'BSP' | 'BMSO' | 'DHSO' | 'Other'>(str), + linker_type: Aliased<'EDC' | 'DSS' | 'EGS' | 'BS3' | 'BS2G' | 'DST' | 'sulfo-SDA' | 'sulfo-SMCC' | 'DSSO' | 'DSG' | 'BSP' | 'BMSO' | 'DHSO' | 'CYS' | 'Other'>(str), /** * Identifier to the crosslinking dataset. * This data item is a pointer to the _ihm_dataset_list.id in the @@ -4584,23 +4601,6 @@ export const mmCIF_Schema = { */ auth_mon_id: str, }, - /** - * PDBX_CHEM_COMP_SYNONYMS holds chemical name and synonym correspondences. - */ - pdbx_chem_comp_synonyms: { - /** - * The synonym of this particular chemical component. - */ - name: str, - /** - * The chemical component for which this synonym applies. - */ - comp_id: str, - /** - * The provenance of this synonym. - */ - provenance: Aliased<'AUTHOR' | 'DRUGBANK' | 'CHEBI' | 'CHEMBL' | 'PDB' | 'PUBCHEM'>(str), - }, /** * PDBX_CHEM_COMP_RELATED describes the relationship between two chemical components. */ diff --git a/src/mol-io/reader/cif/text/parser.ts b/src/mol-io/reader/cif/text/parser.ts index c593e25ddf4d176b39f2fc289da025058a34687c..693754053e9743d122d4b07f94ef6ffca1067883 100644 --- a/src/mol-io/reader/cif/text/parser.ts +++ b/src/mol-io/reader/cif/text/parser.ts @@ -131,6 +131,28 @@ function eatEscaped(state: TokenizerState, esc: number) { state.tokenEnd = state.position; } +/** + * Eats an escaped value "triple quote" (''') value. + */ +function eatTripleQuote(state: TokenizerState) { + // skip the ''' + state.position += 3; + while (state.position < state.length) { + if (state.data.charCodeAt(state.position) === 39 /* ' */ && isTripleQuoteAtPosition(state)) { + // get rid of the quotes. + state.tokenStart += 3; + state.tokenEnd = state.position; + state.isEscaped = true; + state.position += 3; + return; + } + + ++state.position; + } + + state.tokenEnd = state.position; +} + /** * Eats a multiline token of the form NL;....NL; */ @@ -235,6 +257,18 @@ function skipWhitespace(state: TokenizerState): number { return prev; } + +/** + * Returns true if there are two consecutive ' in +1 and +2 positions. + */ +function isTripleQuoteAtPosition(state: TokenizerState): boolean { + if (state.length - state.position < 2) return false; + if (state.data.charCodeAt(state.position + 1) !== 39) return false; // ' + if (state.data.charCodeAt(state.position + 2) !== 39) return false; // ' + + return true; +} + function isData(state: TokenizerState): boolean { // here we already assume the 5th char is _ and that the length >= 5 @@ -393,8 +427,13 @@ function moveNextInternal(state: TokenizerState) { skipCommentLine(state); state.tokenType = CifTokenType.Comment; break; - case 34: // ", escaped value case 39: // ', escaped value + if (isTripleQuoteAtPosition(state)) { + eatTripleQuote(state); + state.tokenType = CifTokenType.Value; + break; + } + case 34: // ", escaped value eatEscaped(state, c); state.tokenType = CifTokenType.Value; break; diff --git a/src/mol-io/writer/cif/encoder.ts b/src/mol-io/writer/cif/encoder.ts index 9c0b8a743f3377edd90527b13977b95165fbfe03..74079dcfa2a64b80cdb19ca1461643ea8233bfb8 100644 --- a/src/mol-io/writer/cif/encoder.ts +++ b/src/mol-io/writer/cif/encoder.ts @@ -199,7 +199,7 @@ export namespace Category { getFormat(cat, field) { return void 0; } } - export function ofTable(table: Table<Table.Schema>, indices?: ArrayLike<number>): Category.Instance { + export function ofTable(table: Table, indices?: ArrayLike<number>): Category.Instance { if (indices) { return { fields: cifFieldsFromTableSchema(table._schema), diff --git a/src/mol-model-formats/structure/3dg.ts b/src/mol-model-formats/structure/3dg.ts index f72aff712cfc7d96f4bd3d7a33bd2d30d7942f17..74d5eba815f6560164c043e521319fb44401f6f8 100644 --- a/src/mol-model-formats/structure/3dg.ts +++ b/src/mol-model-formats/structure/3dg.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -7,16 +7,15 @@ import { Model } from '../../mol-model/structure/model'; import { Task } from '../../mol-task'; import { ModelFormat } from './format'; -import { _parse_mmCif } from './mmcif/parser'; -import { CifCategory, CifField } from '../../mol-io/reader/cif'; -import { Column } from '../../mol-data/db'; -import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif'; +import { Column, Table } from '../../mol-data/db'; import { EntityBuilder } from './common/entity'; import { File3DG } from '../../mol-io/reader/3dg/parser'; import { fillSerial } from '../../mol-util/array'; import { MoleculeType } from '../../mol-model/structure/model/types'; +import { BasicSchema, createBasic } from './basic/schema'; +import { createModels } from './basic/parser'; -function getCategories(table: File3DG['table']) { +function getBasic(table: File3DG['table']) { const entityIds = new Array<string>(table._rowCount) const entityBuilder = new EntityBuilder() @@ -33,47 +32,52 @@ function getCategories(table: File3DG['table']) { seqIdEnds[i] = seqIdStarts[i] + stride - 1 } - const ihm_sphere_obj_site: CifCategory.SomeFields<mmCIF_Schema['ihm_sphere_obj_site']> = { - id: CifField.ofNumbers(fillSerial(new Uint32Array(table._rowCount))), - entity_id: CifField.ofStrings(entityIds), - seq_id_begin: CifField.ofNumbers(seqIdStarts), - seq_id_end: CifField.ofNumbers(seqIdEnds), - asym_id: CifField.ofColumn(table.chromosome), + const ihm_sphere_obj_site = Table.ofPartialColumns(BasicSchema.ihm_sphere_obj_site, { + id: Column.ofIntArray(fillSerial(new Uint32Array(table._rowCount))), + entity_id: Column.ofStringArray(entityIds), + seq_id_begin: Column.ofIntArray(seqIdStarts), + seq_id_end: Column.ofIntArray(seqIdEnds), + asym_id: table.chromosome, - Cartn_x: CifField.ofNumbers(Column.mapToArray(table.x, x => x * 10, Float32Array)), - Cartn_y: CifField.ofNumbers(Column.mapToArray(table.y, y => y * 10, Float32Array)), - Cartn_z: CifField.ofNumbers(Column.mapToArray(table.z, z => z * 10, Float32Array)), + Cartn_x: Column.ofFloatArray(Column.mapToArray(table.x, x => x * 10, Float32Array)), + Cartn_y: Column.ofFloatArray(Column.mapToArray(table.y, y => y * 10, Float32Array)), + Cartn_z: Column.ofFloatArray(Column.mapToArray(table.z, z => z * 10, Float32Array)), - object_radius: CifField.ofColumn(Column.ofConst(objectRadius, table._rowCount, Column.Schema.float)), - rmsf: CifField.ofColumn(Column.ofConst(0, table._rowCount, Column.Schema.float)), - model_id: CifField.ofColumn(Column.ofConst(1, table._rowCount, Column.Schema.int)), - } + object_radius: Column.ofConst(objectRadius, table._rowCount, Column.Schema.float), + rmsf: Column.ofConst(0, table._rowCount, Column.Schema.float), + model_id: Column.ofConst(1, table._rowCount, Column.Schema.int), + }, table._rowCount) - return { - entity: entityBuilder.getEntityCategory(), - ihm_model_list: CifCategory.ofFields('ihm_model_list', { - model_id: CifField.ofNumbers([1]), - model_name: CifField.ofStrings(['3DG Model']), - }), - ihm_sphere_obj_site: CifCategory.ofFields('ihm_sphere_obj_site', ihm_sphere_obj_site) - } + return createBasic({ + entity: entityBuilder.getEntityTable(), + ihm_model_list: Table.ofPartialColumns(BasicSchema.ihm_model_list, { + model_id: Column.ofIntArray([1]), + model_name: Column.ofStringArray(['3DG Model']), + }, 1), + ihm_sphere_obj_site + }) } -async function mmCifFrom3dg(file3dg: File3DG) { - const categories = getCategories(file3dg.table) +// + +export { Format3dg } - return { - header: '3DG', - categoryNames: Object.keys(categories), - categories - }; +type Format3dg = ModelFormat<File3DG> + +namespace Format3dg { + export function is(x: ModelFormat): x is Format3dg { + return x.kind === '3dg' + } + + export function from3dg(file3dg: File3DG): Format3dg { + return { kind: '3dg', name: '3DG', data: file3dg }; + } } export function trajectoryFrom3DG(file3dg: File3DG): Task<Model.Trajectory> { return Task.create('Parse 3DG', async ctx => { - await ctx.update('Converting to mmCIF'); - const cif = await mmCifFrom3dg(file3dg); - const format = ModelFormat.mmCIF(cif); - return _parse_mmCif(format, ctx); + const format = Format3dg.from3dg(file3dg); + const basic = getBasic(file3dg.table) + return createModels(basic, format, ctx); }) } diff --git a/src/mol-model-formats/structure/mmcif/atomic.ts b/src/mol-model-formats/structure/basic/atomic.ts similarity index 86% rename from src/mol-model-formats/structure/mmcif/atomic.ts rename to src/mol-model-formats/structure/basic/atomic.ts index 81346a2ce981d0643e8d60ed04ed9f729dba9bae..277026770b18b7f084bf6df2c6ef9da23a2234de 100644 --- a/src/mol-model-formats/structure/mmcif/atomic.ts +++ b/src/mol-model-formats/structure/basic/atomic.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-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> @@ -7,7 +7,6 @@ import { Column, Table } from '../../../mol-data/db'; import { Interval, Segmentation } from '../../../mol-data/int'; -import { mmCIF_Database } from '../../../mol-io/reader/cif/schema/mmcif'; import UUID from '../../../mol-util/uuid'; import { ElementIndex } from '../../../mol-model/structure'; import { Model } from '../../../mol-model/structure/model/model'; @@ -16,9 +15,7 @@ import { getAtomicIndex } from '../../../mol-model/structure/model/properties/ut import { ElementSymbol } from '../../../mol-model/structure/model/types'; import { Entities } from '../../../mol-model/structure/model/properties/common'; import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived'; -import { FormatData } from './parser'; - -type AtomSite = mmCIF_Database['atom_site'] +import { AtomSite } from './schema'; function findHierarchyOffsets(atom_site: AtomSite) { if (atom_site._rowCount === 0) return { residues: [], chains: [] }; @@ -93,12 +90,12 @@ function getConformation(atom_site: AtomSite): AtomicConformation { function isHierarchyDataEqual(a: AtomicData, b: AtomicData) { // TODO need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300 - return Table.areEqual(a.chains as Table<ChainsSchema>, b.chains as Table<ChainsSchema>) - && Table.areEqual(a.residues as Table<ResiduesSchema>, b.residues as Table<ResiduesSchema>) - && Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>) + return Table.areEqual(a.chains, b.chains) + && Table.areEqual(a.residues, b.residues) + && Table.areEqual(a.atoms, b.atoms) } -function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, formatData: FormatData, previous?: Model) { +function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) { const hierarchyOffsets = findHierarchyOffsets(atom_site); const hierarchyData = createHierarchyData(atom_site, sourceIndex, hierarchyOffsets); @@ -115,13 +112,13 @@ function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, en } const index = getAtomicIndex(hierarchyData, entities, hierarchySegments); - const derived = getAtomicDerivedData(hierarchyData, index, formatData.chemicalComponentMap); + const derived = getAtomicDerivedData(hierarchyData, index, chemicalComponentMap); const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, index, derived }; return { sameAsPrevious: false, hierarchy }; } -export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, formatData: FormatData, previous?: Model) { - const { sameAsPrevious, hierarchy } = getAtomicHierarchy(atom_site, sourceIndex, entities, formatData, previous) +export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) { + const { sameAsPrevious, hierarchy } = getAtomicHierarchy(atom_site, sourceIndex, entities, chemicalComponentMap, previous) const conformation = getConformation(atom_site) return { sameAsPrevious, hierarchy, conformation }; } \ No newline at end of file diff --git a/src/mol-model-formats/structure/mmcif/ihm.ts b/src/mol-model-formats/structure/basic/coarse.ts similarity index 74% rename from src/mol-model-formats/structure/mmcif/ihm.ts rename to src/mol-model-formats/structure/basic/coarse.ts index 7ce1eb9fd8b03eb2f6709a9cafc8297951c64a26..7cb248ec5f9cdd75753331e3072363e4deb572f4 100644 --- a/src/mol-model-formats/structure/mmcif/ihm.ts +++ b/src/mol-model-formats/structure/basic/coarse.ts @@ -1,10 +1,10 @@ /** - * Copyright (c) 2018 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> */ -import { mmCIF_Database as mmCIF, mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif' import { CoarseHierarchy, CoarseConformation, CoarseElementData, CoarseSphereConformation, CoarseGaussianConformation } from '../../../mol-model/structure/model/properties/coarse' import { Entities } from '../../../mol-model/structure/model/properties/common'; import { Column } from '../../../mol-data/db'; @@ -14,35 +14,36 @@ import { Segmentation, Interval } from '../../../mol-data/int'; import { Mat3, Tensor } from '../../../mol-math/linear-algebra'; import { ElementIndex, ChainIndex } from '../../../mol-model/structure/model/indexing'; import { getCoarseRanges } from '../../../mol-model/structure/model/properties/utils/coarse-ranges'; -import { FormatData } from './parser'; +import { IhmSphereObjSite, IhmGaussianObjSite, AtomSite, BasicSchema } from './schema'; +import { Model } from '../../../mol-model/structure'; -export interface IHMData { +export interface CoarseData { model_id: number, model_name: string, model_group_name: string, entities: Entities, - atom_site: mmCIF['atom_site'], + atom_site: AtomSite, atom_site_sourceIndex: Column<number>, - ihm_sphere_obj_site: mmCIF['ihm_sphere_obj_site'], - ihm_gaussian_obj_site: mmCIF['ihm_gaussian_obj_site'] + ihm_sphere_obj_site: IhmSphereObjSite, + ihm_gaussian_obj_site: IhmGaussianObjSite } -export const EmptyIHMCoarse = { hierarchy: CoarseHierarchy.Empty, conformation: void 0 as any } +export const EmptyCoarse = { hierarchy: CoarseHierarchy.Empty, conformation: void 0 as any } -export function getIHMCoarse(data: IHMData, formatData: FormatData): { hierarchy: CoarseHierarchy, conformation: CoarseConformation } { +export function getCoarse(data: CoarseData, properties: Model['properties']): { hierarchy: CoarseHierarchy, conformation: CoarseConformation } { const { ihm_sphere_obj_site, ihm_gaussian_obj_site } = data; - if (ihm_sphere_obj_site._rowCount === 0 && ihm_gaussian_obj_site._rowCount === 0) return EmptyIHMCoarse; + if (ihm_sphere_obj_site._rowCount === 0 && ihm_gaussian_obj_site._rowCount === 0) return EmptyCoarse; const sphereData = getData(ihm_sphere_obj_site); const sphereConformation = getSphereConformation(ihm_sphere_obj_site); const sphereKeys = getCoarseKeys(sphereData, data.entities); - const sphereRanges = getCoarseRanges(sphereData, formatData.chemicalComponentMap); + const sphereRanges = getCoarseRanges(sphereData, properties.chemicalComponentMap); const gaussianData = getData(ihm_gaussian_obj_site); const gaussianConformation = getGaussianConformation(ihm_gaussian_obj_site); const gaussianKeys = getCoarseKeys(gaussianData, data.entities); - const gaussianRanges = getCoarseRanges(gaussianData, formatData.chemicalComponentMap); + const gaussianRanges = getCoarseRanges(gaussianData, properties.chemicalComponentMap); return { hierarchy: { @@ -58,7 +59,7 @@ export function getIHMCoarse(data: IHMData, formatData: FormatData): { hierarchy }; } -function getSphereConformation(data: mmCIF['ihm_sphere_obj_site']): CoarseSphereConformation { +function getSphereConformation(data: IhmSphereObjSite): CoarseSphereConformation { return { x: data.Cartn_x.toArray({ array: Float32Array }), y: data.Cartn_y.toArray({ array: Float32Array }), @@ -68,8 +69,8 @@ function getSphereConformation(data: mmCIF['ihm_sphere_obj_site']): CoarseSphere }; } -function getGaussianConformation(data: mmCIF['ihm_gaussian_obj_site']): CoarseGaussianConformation { - const matrix_space = mmCIF_Schema.ihm_gaussian_obj_site.covariance_matrix.space; +function getGaussianConformation(data: IhmGaussianObjSite): CoarseGaussianConformation { + const matrix_space = BasicSchema.ihm_gaussian_obj_site.covariance_matrix.space; const covariance_matrix: Mat3[] = []; const { covariance_matrix: cm } = data; @@ -98,7 +99,7 @@ function getSegments(asym_id: Column<string>, seq_id_begin: Column<number>, seq_ } } -function getData(data: mmCIF['ihm_sphere_obj_site'] | mmCIF['ihm_gaussian_obj_site']): CoarseElementData { +function getData(data: IhmSphereObjSite | IhmGaussianObjSite): CoarseElementData { const { entity_id, seq_id_begin, seq_id_end, asym_id } = data; return { count: entity_id.rowCount, entity_id, asym_id, seq_id_begin, seq_id_end, ...getSegments(asym_id, seq_id_begin, seq_id_end) }; } \ No newline at end of file diff --git a/src/mol-model-formats/structure/basic/entities.ts b/src/mol-model-formats/structure/basic/entities.ts new file mode 100644 index 0000000000000000000000000000000000000000..77d1468297bc84d448c8f22e7ca112fe8cb5be7b --- /dev/null +++ b/src/mol-model-formats/structure/basic/entities.ts @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2017-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 { Column, Table } from '../../../mol-data/db'; +import { Entities, EntitySubtype } from '../../../mol-model/structure/model/properties/common'; +import { getEntityType, getEntitySubtype } from '../../../mol-model/structure/model/types'; +import { ElementIndex, EntityIndex } from '../../../mol-model/structure/model'; +import { BasicData, BasicSchema, Entity } from './schema'; + +export function getEntities(data: BasicData): Entities { + let entityData: Entity + + if (!data.entity.id.isDefined) { + const entityIds = new Set<string>() + + const ids: ReturnType<Entity['id']['value']>[] = [] + const types: ReturnType<Entity['type']['value']>[] = [] + + const { label_entity_id, label_comp_id } = data.atom_site; + for (let i = 0 as ElementIndex, il = data.atom_site._rowCount; i < il; i++) { + const entityId = label_entity_id.value(i); + if (!entityIds.has(entityId)) { + ids.push(entityId) + types.push(getEntityType(label_comp_id.value(i))) + entityIds.add(entityId) + } + } + + const { entity_id: sphere_entity_id } = data.ihm_sphere_obj_site; + for (let i = 0 as ElementIndex, il = data.ihm_sphere_obj_site._rowCount; i < il; i++) { + const entityId = sphere_entity_id.value(i); + if (!entityIds.has(entityId)) { + ids.push(entityId) + types.push('polymer') + entityIds.add(entityId) + } + } + + const { entity_id: gaussian_entity_id } = data.ihm_gaussian_obj_site; + for (let i = 0 as ElementIndex, il = data.ihm_gaussian_obj_site._rowCount; i < il; i++) { + const entityId = gaussian_entity_id.value(i); + if (!entityIds.has(entityId)) { + ids.push(entityId) + types.push('polymer') + entityIds.add(entityId) + } + } + + entityData = Table.ofPartialColumns(BasicSchema.entity, { + id: Column.ofArray({ array: ids, schema: BasicSchema.entity.id }), + type: Column.ofArray({ array: types, schema: BasicSchema.entity.type }), + }, ids.length) + } else { + entityData = data.entity; + } + + const getEntityIndex = Column.createIndexer<string, EntityIndex>(entityData.id) + + // + + const subtypes: EntitySubtype[] = new Array(entityData._rowCount) + subtypes.fill('other') + + const entityIds = new Set<string>() + let assignSubtype = false + + if (data.entity_poly && data.entity_poly.type.isDefined) { + const { entity_id, type, _rowCount } = data.entity_poly + for (let i = 0; i < _rowCount; ++i) { + const entityId = entity_id.value(i) + subtypes[getEntityIndex(entityId)] = type.value(i) + entityIds.add(entityId) + } + } else { + assignSubtype = true + } + + if (data.pdbx_entity_branch && data.pdbx_entity_branch.entity_id.isDefined) { + const { entity_id, type, _rowCount } = data.pdbx_entity_branch + for (let i = 0; i < _rowCount; ++i) { + const entityId = entity_id.value(i) + subtypes[getEntityIndex(entityId)] = type.value(i) + entityIds.add(entityId) + } + } else { + assignSubtype = true + } + + if (assignSubtype) { + const chemCompType = new Map<string, string>() + if (data.chem_comp) { + const { id, type } = data.chem_comp; + for (let i = 0, il = data.chem_comp._rowCount; i < il; i++) { + chemCompType.set(id.value(i), type.value(i)) + } + } + + if (data.atom_site) { + const { label_entity_id, label_comp_id } = data.atom_site; + for (let i = 0 as ElementIndex, il = data.atom_site._rowCount; i < il; i++) { + const entityId = label_entity_id.value(i); + if (!entityIds.has(entityId)) { + const compId = label_comp_id.value(i) + const compType = chemCompType.get(compId) || '' + subtypes[getEntityIndex(entityId)] = getEntitySubtype(compId, compType) + entityIds.add(entityId) + } + } + } + // TODO how to handle coarse? + } + + const subtypeColumn = Column.ofArray({ array: subtypes, schema: EntitySubtype }) + + // + + return { data: entityData, subtype: subtypeColumn, getEntityIndex }; +} \ No newline at end of file diff --git a/src/mol-model-formats/structure/basic/parser.ts b/src/mol-model-formats/structure/basic/parser.ts new file mode 100644 index 0000000000000000000000000000000000000000..cf41cd1866c7c82aef5f6b339cdf6d5f28a8e402 --- /dev/null +++ b/src/mol-model-formats/structure/basic/parser.ts @@ -0,0 +1,210 @@ +/** + * Copyright (c) 2017-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 { Column, Table } from '../../../mol-data/db'; +import { RuntimeContext } from '../../../mol-task'; +import UUID from '../../../mol-util/uuid'; +import { Model } from '../../../mol-model/structure/model/model'; +import { Entities } from '../../../mol-model/structure/model/properties/common'; +import { CustomProperties } from '../../../mol-model/structure'; +import { getAtomicHierarchyAndConformation } from './atomic'; +import { getCoarse, EmptyCoarse, CoarseData } from './coarse'; +import { getSequence } from './sequence'; +import { sortAtomSite } from './sort'; +import { ModelFormat } from '../format'; +import { getAtomicRanges } from '../../../mol-model/structure/model/properties/utils/atomic-ranges'; +import { AtomSite, BasicData } from './schema'; +import { getProperties } from './properties'; +import { getEntities } from './entities'; +import { getModelGroupName } from './util'; + +export async function createModels(data: BasicData, format: ModelFormat, ctx: RuntimeContext) { + const properties = getProperties(data) + return data.ihm_model_list._rowCount > 0 + ? await readIntegrative(ctx, data, properties, format) + : await readStandard(ctx, data, properties, format); +} + +/** Standard atomic model */ +function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, properties: Model['properties'], format: ModelFormat, previous?: Model): Model { + + const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, properties.chemicalComponentMap, previous); + const modelNum = atom_site.pdbx_PDB_model_num.value(0) + if (previous && atomic.sameAsPrevious) { + return { + ...previous, + id: UUID.create22(), + modelNum, + atomicConformation: atomic.conformation, + _dynamicPropertyData: Object.create(null) + }; + } + + const coarse = EmptyCoarse; + const sequence = getSequence(data, entities, atomic.hierarchy, coarse.hierarchy, properties.modifiedResidues.parentId) + const atomicRanges = getAtomicRanges(atomic.hierarchy, entities, atomic.conformation, sequence) + + const entry = data.entry.id.valueKind(0) === Column.ValueKind.Present + ? data.entry.id.value(0) : format.name; + + const label: string[] = [] + if (entry) label.push(entry) + if (data.struct.title.valueKind(0) === Column.ValueKind.Present) label.push(data.struct.title.value(0)) + + return { + id: UUID.create22(), + entryId: entry, + label: label.join(' | '), + entry, + sourceData: format, + modelNum, + entities, + sequence, + atomicHierarchy: atomic.hierarchy, + atomicConformation: atomic.conformation, + atomicRanges, + coarseHierarchy: coarse.hierarchy, + coarseConformation: coarse.conformation, + properties, + customProperties: new CustomProperties(), + _staticPropertyData: Object.create(null), + _dynamicPropertyData: Object.create(null) + }; +} + +/** Integrative model with atomic/coarse parts */ +function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Model['properties'], format: ModelFormat): Model { + const atomic = getAtomicHierarchyAndConformation(ihm.atom_site, ihm.atom_site_sourceIndex, ihm.entities, properties.chemicalComponentMap); + const coarse = getCoarse(ihm, properties); + const sequence = getSequence(data, ihm.entities, atomic.hierarchy, coarse.hierarchy, properties.modifiedResidues.parentId) + const atomicRanges = getAtomicRanges(atomic.hierarchy, ihm.entities, atomic.conformation, sequence) + + const entry = data.entry.id.valueKind(0) === Column.ValueKind.Present + ? data.entry.id.value(0) : format.name; + + const label: string[] = [] + if (entry) label.push(entry) + if (data.struct.title.valueKind(0) === Column.ValueKind.Present) label.push(data.struct.title.value(0)) + if (ihm.model_name) label.push(ihm.model_name) + if (ihm.model_group_name) label.push(ihm.model_group_name) + + return { + id: UUID.create22(), + entryId: entry, + label: label.join(' | '), + entry, + sourceData: format, + modelNum: ihm.model_id, + entities: ihm.entities, + sequence, + atomicHierarchy: atomic.hierarchy, + atomicConformation: atomic.conformation, + atomicRanges, + coarseHierarchy: coarse.hierarchy, + coarseConformation: coarse.conformation, + properties, + customProperties: new CustomProperties(), + _staticPropertyData: Object.create(null), + _dynamicPropertyData: Object.create(null) + }; +} + +function findModelEnd(num: Column<number>, startIndex: number) { + const rowCount = num.rowCount; + if (!num.isDefined) return rowCount; + let endIndex = startIndex + 1; + while (endIndex < rowCount && num.areValuesEqual(startIndex, endIndex)) endIndex++; + return endIndex; +} + +async function readStandard(ctx: RuntimeContext, data: BasicData, properties: Model['properties'], format: ModelFormat) { + const models: Model[] = []; + + if (data.atom_site) { + const atomCount = data.atom_site.id.rowCount; + const entities = getEntities(data) + + let modelStart = 0; + while (modelStart < atomCount) { + const modelEnd = findModelEnd(data.atom_site.pdbx_PDB_model_num, modelStart); + const { atom_site, sourceIndex } = await sortAtomSite(ctx, data.atom_site, modelStart, modelEnd); + const model = createStandardModel(data, atom_site, sourceIndex, entities, properties, format, models.length > 0 ? models[models.length - 1] : void 0); + models.push(model); + modelStart = modelEnd; + } + } + return models; +} + +function splitTable<T extends Table<any>>(table: T, col: Column<number>) { + const ret = new Map<number, { table: T, start: number, end: number }>() + const rowCount = table._rowCount; + let modelStart = 0; + while (modelStart < rowCount) { + const modelEnd = findModelEnd(col, modelStart); + const id = col.value(modelStart); + ret.set(id, { + table: Table.window(table, table._schema, modelStart, modelEnd) as T, + start: modelStart, + end: modelEnd + }); + modelStart = modelEnd; + } + return ret; +} + + + +async function readIntegrative(ctx: RuntimeContext, data: BasicData, properties: Model['properties'], format: ModelFormat) { + const entities = getEntities(data) + // when `atom_site.ihm_model_id` is undefined fall back to `atom_site.pdbx_PDB_model_num` + const atom_sites_modelColumn = data.atom_site.ihm_model_id.isDefined + ? data.atom_site.ihm_model_id : data.atom_site.pdbx_PDB_model_num + const atom_sites = splitTable(data.atom_site, atom_sites_modelColumn) + + // TODO: will coarse IHM records require sorting or will we trust it? + // ==> Probably implement a sort as as well and store the sourceIndex same as with atomSite + // If the sorting is implemented, updated mol-model/structure/properties: atom.sourceIndex + const sphere_sites = splitTable(data.ihm_sphere_obj_site, data.ihm_sphere_obj_site.model_id); + const gauss_sites = splitTable(data.ihm_gaussian_obj_site, data.ihm_gaussian_obj_site.model_id); + + const models: Model[] = []; + + if (data.ihm_model_list) { + const { model_id, model_name } = data.ihm_model_list; + for (let i = 0; i < data.ihm_model_list._rowCount; i++) { + const id = model_id.value(i); + + let atom_site, atom_site_sourceIndex; + if (atom_sites.has(id)) { + const e = atom_sites.get(id)!; + // need to sort `data.atom_site` as `e.start` and `e.end` are indices into that + const { atom_site: sorted, sourceIndex } = await sortAtomSite(ctx, data.atom_site, e.start, e.end); + atom_site = sorted; + atom_site_sourceIndex = sourceIndex; + } else { + atom_site = Table.window(data.atom_site, data.atom_site._schema, 0, 0); + atom_site_sourceIndex = Column.ofIntArray([]); + } + + const ihm: CoarseData = { + model_id: id, + model_name: model_name.value(i), + model_group_name: getModelGroupName(id, data), + entities: entities, + atom_site, + atom_site_sourceIndex, + ihm_sphere_obj_site: sphere_sites.has(id) ? sphere_sites.get(id)!.table : Table.window(data.ihm_sphere_obj_site, data.ihm_sphere_obj_site._schema, 0, 0), + ihm_gaussian_obj_site: gauss_sites.has(id) ? gauss_sites.get(id)!.table : Table.window(data.ihm_gaussian_obj_site, data.ihm_gaussian_obj_site._schema, 0, 0) + }; + const model = createIntegrativeModel(data, ihm, properties, format); + models.push(model); + } + } + + return models; +} \ No newline at end of file diff --git a/src/mol-model-formats/structure/basic/properties.ts b/src/mol-model-formats/structure/basic/properties.ts new file mode 100644 index 0000000000000000000000000000000000000000..45ecd6e5d17ee6f0d4e13803c91981b6cf0369b0 --- /dev/null +++ b/src/mol-model-formats/structure/basic/properties.ts @@ -0,0 +1,134 @@ +/** + * Copyright (c) 2017-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 { Model } from '../../../mol-model/structure/model/model'; +import { ChemicalComponent, MissingResidue } from '../../../mol-model/structure/model/properties/common'; +import { getMoleculeType, MoleculeType, getDefaultChemicalComponent } from '../../../mol-model/structure/model/types'; +import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants'; +import { memoize1 } from '../../../mol-util/memoize'; +import { BasicData } from './schema'; +import { Table } from '../../../mol-data/db'; + +function getModifiedResidueNameMap(data: BasicData): Model['properties']['modifiedResidues'] { + const parentId = new Map<string, string>(); + const details = new Map<string, string>(); + + const c = data.pdbx_struct_mod_residue; + const comp_id = c.label_comp_id.isDefined ? c.label_comp_id : c.auth_comp_id; + const parent_id = c.parent_comp_id, details_data = c.details; + + for (let i = 0; i < c._rowCount; i++) { + const id = comp_id.value(i); + parentId.set(id, parent_id.value(i)); + details.set(id, details_data.value(i)); + } + + return { parentId, details }; +} + +function getMissingResidues(data: BasicData): Model['properties']['missingResidues'] { + const map = new Map<string, MissingResidue>(); + const getKey = (model_num: number, asym_id: string, seq_id: number) => { + return `${model_num}|${asym_id}|${seq_id}` + } + + const c = data.pdbx_unobs_or_zero_occ_residues + for (let i = 0, il = c._rowCount; i < il; ++i) { + const key = getKey(c.PDB_model_num.value(i), c.label_asym_id.value(i), c.label_seq_id.value(i)) + map.set(key, { polymer_flag: c.polymer_flag.value(i), occupancy_flag: c.occupancy_flag.value(i) }) + } + + return { + has: (model_num: number, asym_id: string, seq_id: number) => { + return map.has(getKey(model_num, asym_id, seq_id)) + }, + get: (model_num: number, asym_id: string, seq_id: number) => { + return map.get(getKey(model_num, asym_id, seq_id)) + }, + size: map.size + } +} + +function getChemicalComponentMap(data: BasicData): Model['properties']['chemicalComponentMap'] { + const map = new Map<string, ChemicalComponent>(); + + if (data.chem_comp._rowCount > 0) { + const { id } = data.chem_comp + for (let i = 0, il = id.rowCount; i < il; ++i) { + map.set(id.value(i), Table.getRow(data.chem_comp, i)) + } + } else { + const uniqueNames = getUniqueComponentNames(data); + uniqueNames.forEach(n => { + map.set(n, getDefaultChemicalComponent(n)); + }); + } + return map +} + +function getSaccharideComponentMap(data: BasicData): SaccharideComponentMap { + const map = new Map<string, SaccharideComponent>(); + + if (data.pdbx_chem_comp_identifier._rowCount > 0) { + // note that `pdbx_chem_comp_identifier` does not contain + // a 'SNFG CARBOHYDRATE SYMBOL' entry for 'Unknown' saccharide components + // so we always need to check `chem_comp` for those + const { comp_id, type, identifier } = data.pdbx_chem_comp_identifier + for (let i = 0, il = comp_id.rowCount; i < il; ++i) { + if (type.value(i) === 'SNFG CARBOHYDRATE SYMBOL' || + type.value(i) === 'SNFG CARB SYMBOL' // legacy, to be removed from mmCIF dictionary + ) { + const snfgName = identifier.value(i) + const saccharideComp = SaccharidesSnfgMap.get(snfgName) + if (saccharideComp) { + map.set(comp_id.value(i), saccharideComp) + } else { + console.warn(`Unknown SNFG name '${snfgName}'`) + } + } + } + } + + if (data.chem_comp._rowCount > 0) { + const { id, type } = data.chem_comp + for (let i = 0, il = id.rowCount; i < il; ++i) { + const _id = id.value(i) + if (map.has(_id)) continue + const _type = type.value(i) + if (SaccharideCompIdMap.has(_id)) { + map.set(_id, SaccharideCompIdMap.get(_id)!) + } else if (getMoleculeType(_type, _id) === MoleculeType.Saccharide) { + map.set(_id, UnknownSaccharideComponent) + } + } + } else { + const uniqueNames = getUniqueComponentNames(data) + SaccharideCompIdMap.forEach((v, k) => { + if (!map.has(k) && uniqueNames.has(k)) map.set(k, v) + }) + } + return map +} + +const getUniqueComponentNames = memoize1((data: BasicData) => { + const uniqueNames = new Set<string>() + const { label_comp_id, auth_comp_id } = data.atom_site + const comp_id = label_comp_id.isDefined ? label_comp_id : auth_comp_id; + for (let i = 0, il = comp_id.rowCount; i < il; ++i) { + uniqueNames.add(comp_id.value(i)) + } + return uniqueNames +}) + +export function getProperties(data: BasicData): Model['properties'] { + return { + modifiedResidues: getModifiedResidueNameMap(data), + missingResidues: getMissingResidues(data), + chemicalComponentMap: getChemicalComponentMap(data), + saccharideComponentMap: getSaccharideComponentMap(data) + } +} \ No newline at end of file diff --git a/src/mol-model-formats/structure/basic/schema.ts b/src/mol-model-formats/structure/basic/schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..dea7e328e8a9db387542f49ee8c057e7fb9f73aa --- /dev/null +++ b/src/mol-model-formats/structure/basic/schema.ts @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif'; +import { Table } from '../../../mol-data/db'; + +// TODO split into conformation and hierarchy parts +// TODO extract `pdbx_struct_mod_residue` as property? + +export type Entry = Table<mmCIF_Schema['entry']> +export type Struct = Table<mmCIF_Schema['struct']> +export type StructAsym = Table<mmCIF_Schema['struct_asym']> +export type IhmModelList = Table<mmCIF_Schema['ihm_model_list']> +export type IhmModelGroup = Table<mmCIF_Schema['ihm_model_group']> +export type IhmModelGroupLink = Table<mmCIF_Schema['ihm_model_group_link']> +export type Entity = Table<mmCIF_Schema['entity']> +export type EntityPoly = Table<mmCIF_Schema['entity_poly']> +export type EntityPolySeq = Table<mmCIF_Schema['entity_poly_seq']> +export type EntityBranch = Table<mmCIF_Schema['pdbx_entity_branch']> +export type ChemComp = Table<mmCIF_Schema['chem_comp']> +export type ChemCompIdentifier = Table<mmCIF_Schema['pdbx_chem_comp_identifier']> +export type StructModResidue = Table<mmCIF_Schema['pdbx_struct_mod_residue']> +export type AtomSite = Table<mmCIF_Schema['atom_site']> +export type IhmSphereObjSite = Table<mmCIF_Schema['ihm_sphere_obj_site']> +export type IhmGaussianObjSite =Table<mmCIF_Schema['ihm_gaussian_obj_site']> +export type UnobsOrZeroOccResidues =Table<mmCIF_Schema['pdbx_unobs_or_zero_occ_residues']> + +export const BasicSchema = { + entry: mmCIF_Schema.entry, + struct: mmCIF_Schema.struct, + struct_asym: mmCIF_Schema.struct_asym, + ihm_model_list: mmCIF_Schema.ihm_model_list, + ihm_model_group: mmCIF_Schema.ihm_model_group, + ihm_model_group_link: mmCIF_Schema.ihm_model_group_link, + entity: mmCIF_Schema.entity, + entity_poly: mmCIF_Schema.entity_poly, + entity_poly_seq: mmCIF_Schema.entity_poly_seq, + pdbx_entity_branch: mmCIF_Schema.pdbx_entity_branch, + chem_comp: mmCIF_Schema.chem_comp, + pdbx_chem_comp_identifier: mmCIF_Schema.pdbx_chem_comp_identifier, + pdbx_struct_mod_residue: mmCIF_Schema.pdbx_struct_mod_residue, + atom_site: mmCIF_Schema.atom_site, + ihm_sphere_obj_site: mmCIF_Schema.ihm_sphere_obj_site, + ihm_gaussian_obj_site: mmCIF_Schema.ihm_gaussian_obj_site, + pdbx_unobs_or_zero_occ_residues: mmCIF_Schema.pdbx_unobs_or_zero_occ_residues, +} + +export interface BasicData { + entry: Entry + struct: Struct + struct_asym: StructAsym + ihm_model_list: IhmModelList + ihm_model_group: IhmModelGroup + ihm_model_group_link: IhmModelGroupLink + entity: Entity + entity_poly: EntityPoly + entity_poly_seq: EntityPolySeq + pdbx_entity_branch: EntityBranch + chem_comp: ChemComp + pdbx_chem_comp_identifier: ChemCompIdentifier + pdbx_struct_mod_residue: StructModResidue + atom_site: AtomSite + ihm_sphere_obj_site: IhmSphereObjSite + ihm_gaussian_obj_site: IhmGaussianObjSite + pdbx_unobs_or_zero_occ_residues: UnobsOrZeroOccResidues +} + +export function createBasic(data: Partial<BasicData>): BasicData { + const basic = Object.create(null) + for (const name of Object.keys(BasicSchema)) { + if (name in data) { + basic[name] = data[name as keyof typeof BasicSchema] + } else { + basic[name] = Table.ofUndefinedColumns(BasicSchema[name as keyof typeof BasicSchema], 0) + } + } + return basic +} \ No newline at end of file diff --git a/src/mol-model-formats/structure/mmcif/sequence.ts b/src/mol-model-formats/structure/basic/sequence.ts similarity index 77% rename from src/mol-model-formats/structure/mmcif/sequence.ts rename to src/mol-model-formats/structure/basic/sequence.ts index 2b455a2a74c9103ed984be005c0b93a6886c41ec..d73a6f84a50bf48ff02504a0002813012f49d417 100644 --- a/src/mol-model-formats/structure/mmcif/sequence.ts +++ b/src/mol-model-formats/structure/basic/sequence.ts @@ -1,24 +1,24 @@ /** - * 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> */ -import { mmCIF_Database as mmCIF } from '../../../mol-io/reader/cif/schema/mmcif' import StructureSequence from '../../../mol-model/structure/model/properties/sequence' import { Column } from '../../../mol-data/db'; import { AtomicHierarchy } from '../../../mol-model/structure/model/properties/atomic'; import { Entities } from '../../../mol-model/structure/model/properties/common'; import { Sequence } from '../../../mol-model/sequence'; import { CoarseHierarchy } from '../../../mol-model/structure/model/properties/coarse'; +import { BasicData } from './schema'; -export function getSequence(cif: mmCIF, entities: Entities, atomicHierarchy: AtomicHierarchy, coarseHierarchy: CoarseHierarchy, modResMap: ReadonlyMap<string, string>): StructureSequence { - if (!cif.entity_poly_seq._rowCount) { +export function getSequence(data: BasicData, entities: Entities, atomicHierarchy: AtomicHierarchy, coarseHierarchy: CoarseHierarchy, modResMap: ReadonlyMap<string, string>): StructureSequence { + if (!data.entity_poly_seq || !data.entity_poly_seq._rowCount) { return StructureSequence.fromHierarchy(entities, atomicHierarchy, coarseHierarchy, modResMap); } - const { entity_id, num, mon_id } = cif.entity_poly_seq; + const { entity_id, num, mon_id } = data.entity_poly_seq; const byEntityKey: StructureSequence['byEntityKey'] = {}; const sequences: StructureSequence.Entity[] = []; diff --git a/src/mol-model-formats/structure/mmcif/sort.ts b/src/mol-model-formats/structure/basic/sort.ts similarity index 86% rename from src/mol-model-formats/structure/mmcif/sort.ts rename to src/mol-model-formats/structure/basic/sort.ts index a6e450b40076403d40a2fac796ceb0ab7b6233c9..51b939afdad6718564068c2c38047ae8f4c6c9f0 100644 --- a/src/mol-model-formats/structure/mmcif/sort.ts +++ b/src/mol-model-formats/structure/basic/sort.ts @@ -4,12 +4,15 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { mmCIF_Database } from '../../../mol-io/reader/cif/schema/mmcif'; import { createRangeArray, makeBuckets } from '../../../mol-data/util'; import { Column, Table } from '../../../mol-data/db'; import { RuntimeContext } from '../../../mol-task'; +import { AtomSite } from './schema'; -export type SortedAtomSite = mmCIF_Database['atom_site'] & { sourceIndex: Column<number> } +export type SortedAtomSite = { + atom_site: AtomSite + sourceIndex: Column<number> +} function isIdentity(xs: ArrayLike<number>) { for (let i = 0, _i = xs.length; i < _i; i++) { @@ -18,7 +21,7 @@ function isIdentity(xs: ArrayLike<number>) { return true; } -export async function sortAtomSite(ctx: RuntimeContext, atom_site: mmCIF_Database['atom_site'], start: number, end: number) { +export async function sortAtomSite(ctx: RuntimeContext, atom_site: AtomSite, start: number, end: number): Promise<SortedAtomSite> { const indices = createRangeArray(start, end - 1); const { label_entity_id, label_asym_id, label_seq_id } = atom_site; @@ -42,7 +45,7 @@ export async function sortAtomSite(ctx: RuntimeContext, atom_site: mmCIF_Databas } return { - atom_site: Table.view(atom_site, atom_site._schema, indices) as mmCIF_Database['atom_site'], + atom_site: Table.view(atom_site, atom_site._schema, indices) as AtomSite, sourceIndex: Column.ofIntArray(indices) }; } \ No newline at end of file diff --git a/src/mol-model-formats/structure/basic/util.ts b/src/mol-model-formats/structure/basic/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..daf0d3a8a3ebeaddf5e9c373843e602cc46bb2d7 --- /dev/null +++ b/src/mol-model-formats/structure/basic/util.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2017-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 { BasicData } from './schema'; +import { Table } from '../../../mol-data/db'; + +export function getModelGroupName(model_id: number, data: BasicData) { + const { ihm_model_group, ihm_model_group_link } = data; + + const link = Table.pickRow(ihm_model_group_link, i => ihm_model_group_link.model_id.value(i) === model_id) + if (link) { + const group = Table.pickRow(ihm_model_group, i => ihm_model_group.id.value(i) === link.group_id) + if (group) return group.name + } + return '' +} \ No newline at end of file diff --git a/src/mol-model-formats/structure/common/component.ts b/src/mol-model-formats/structure/common/component.ts index 30f201b86c2f344560749e26fdd71ecdbb34f46a..483ac6e95369b33506deee7132a663104a8a7b91 100644 --- a/src/mol-model-formats/structure/common/component.ts +++ b/src/mol-model-formats/structure/common/component.ts @@ -1,15 +1,14 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { _parse_mmCif } from '../mmcif/parser'; -import { CifCategory, CifField } from '../../../mol-io/reader/cif'; import { Table, Column } from '../../../mol-data/db'; import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif'; import { WaterNames } from '../../../mol-model/structure/model/types'; import { SetUtils } from '../../../mol-util/set'; +import { BasicSchema } from '../basic/schema'; type Component = Table.Row<Pick<mmCIF_Schema['chem_comp'], 'id' | 'name' | 'type'>> @@ -137,13 +136,12 @@ export class ComponentBuilder { return this.get(compId)! } - getChemCompCategory() { - const chemComp: CifCategory.SomeFields<mmCIF_Schema['chem_comp']> = { - id: CifField.ofStrings(this.ids), - name: CifField.ofStrings(this.names), - type: CifField.ofStrings(this.types), - } - return CifCategory.ofFields('chem_comp', chemComp) + getChemCompTable() { + return Table.ofPartialColumns(BasicSchema.chem_comp, { + id: Column.ofStringArray(this.ids), + name: Column.ofStringArray(this.names), + type: Column.ofStringAliasArray(this.types), + }, this.ids.length) } setNames(names: [string, string][]) { diff --git a/src/mol-model-formats/structure/common/entity.ts b/src/mol-model-formats/structure/common/entity.ts index d44fefb2f7f9e7d5117333b80731f81d7181c604..3de2bfefd7c42951082f68b0f654d059d2dba716 100644 --- a/src/mol-model-formats/structure/common/entity.ts +++ b/src/mol-model-formats/structure/common/entity.ts @@ -1,20 +1,23 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { CifCategory, CifField } from '../../../mol-io/reader/cif'; import { MoleculeType, isPolymer } from '../../../mol-model/structure/model/types'; -import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif'; +import { Column, Table } from '../../../mol-data/db'; +import { BasicSchema } from '../basic/schema'; export type EntityCompound = { chains: string[], description: string } +// TODO add support for `branched` +type EntityType = 'water' | 'polymer' | 'non-polymer' + export class EntityBuilder { private count = 0 private ids: string[] = [] - private types: string[] = [] - private descriptions: string[] = [] + private types: EntityType[] = [] + private descriptions: string[][] = [] private compoundsMap = new Map<string, string>() private namesMap = new Map<string, string>() @@ -22,11 +25,11 @@ export class EntityBuilder { private chainMap = new Map<string, string>() private waterId?: string - private set(type: string, description: string) { + private set(type: EntityType, description: string) { this.count += 1 this.ids.push(`${this.count}`) this.types.push(type) - this.descriptions.push(description) + this.descriptions.push([description]) } getEntityId(compId: string, moleculeType: MoleculeType, chainId: string): string { @@ -55,13 +58,12 @@ export class EntityBuilder { } } - getEntityCategory() { - const entity: CifCategory.SomeFields<mmCIF_Schema['entity']> = { - id: CifField.ofStrings(this.ids), - type: CifField.ofStrings(this.types), - pdbx_description: CifField.ofStrings(this.descriptions), - } - return CifCategory.ofFields('entity', entity) + getEntityTable() { + return Table.ofPartialColumns(BasicSchema.entity, { + id: Column.ofStringArray(this.ids), + type: Column.ofStringAliasArray(this.types), + pdbx_description: Column.ofStringListArray(this.descriptions), + }, this.count) } setCompounds(compounds: EntityCompound[]) { diff --git a/src/mol-model-formats/structure/common/property.ts b/src/mol-model-formats/structure/common/property.ts new file mode 100644 index 0000000000000000000000000000000000000000..44bae5f2f3e0e1642a01a9fc10b3a302dde39f69 --- /dev/null +++ b/src/mol-model-formats/structure/common/property.ts @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { CustomPropertyDescriptor, Model } from '../../../mol-model/structure'; +import { ModelFormat } from '../format'; + +class FormatRegistry<T> { + map = new Map<ModelFormat['kind'], (model: Model) => T | undefined>() + + add(kind: ModelFormat['kind'], obtain: (model: Model) => T | undefined) { + this.map.set(kind, obtain) + } + + get(kind: ModelFormat['kind']) { + return this.map.get(kind) + } +} + +export { FormatPropertyProvider as FormatPropertyProvider } + +interface FormatPropertyProvider<T> { + readonly descriptor: CustomPropertyDescriptor + readonly formatRegistry: FormatRegistry<T> + get(model: Model): T | undefined + set(model: Model, value: T): void +} + +namespace FormatPropertyProvider { + export function create<T>(descriptor: CustomPropertyDescriptor): FormatPropertyProvider<T> { + const { name } = descriptor + const formatRegistry = new FormatRegistry<T>() + + return { + descriptor, + formatRegistry, + get(model: Model): T | undefined { + if (model._staticPropertyData[name]) return model._staticPropertyData[name] + if (model.customProperties.has(descriptor)) return + + const obtain = formatRegistry.get(model.sourceData.kind) + if (!obtain) return + + model._staticPropertyData[name] = obtain(model) + model.customProperties.add(descriptor) + return model._staticPropertyData[name] + }, + set(model: Model, value: T) { + model._staticPropertyData[name] = value + } + } + } +} \ No newline at end of file diff --git a/src/mol-model-formats/structure/format.ts b/src/mol-model-formats/structure/format.ts index 08d0d3b3ffdb70973b46e3a9fd22c4a75cde6161..a3cb77eb5327320f34972b02f43fc71995851534 100644 --- a/src/mol-model-formats/structure/format.ts +++ b/src/mol-model-formats/structure/format.ts @@ -1,18 +1,8 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { mmCIF_Database } from '../../mol-io/reader/cif/schema/mmcif'; -import { CIF, CifFrame } from '../../mol-io/reader/cif'; - -type ModelFormat = - | ModelFormat.mmCIF - -namespace ModelFormat { - export interface mmCIF { kind: 'mmCIF', data: mmCIF_Database, frame: CifFrame } - export function mmCIF(frame: CifFrame, data?: mmCIF_Database): mmCIF { return { kind: 'mmCIF', data: data || CIF.schema.mmCIF(frame), frame }; } -} - -export { ModelFormat } \ No newline at end of file +export interface ModelFormat<T = unknown> { readonly kind: string, name: string, data: T } \ No newline at end of file diff --git a/src/mol-model-formats/structure/gro.ts b/src/mol-model-formats/structure/gro.ts index 8f141ea1c98305df18e3b54a5dd6ecf64a5b027a..c38beca49164cb7d98d322036b10db9d83a2a316 100644 --- a/src/mol-model-formats/structure/gro.ts +++ b/src/mol-model-formats/structure/gro.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -7,22 +7,21 @@ import { Model } from '../../mol-model/structure/model'; import { Task } from '../../mol-task'; import { ModelFormat } from './format'; -import { _parse_mmCif } from './mmcif/parser'; import { GroFile, GroAtoms } from '../../mol-io/reader/gro/schema'; -import { CifCategory, CifField } from '../../mol-io/reader/cif'; -import { Column } from '../../mol-data/db'; -import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif'; +import { Column, Table } from '../../mol-data/db'; import { guessElementSymbolString } from './util'; import { MoleculeType, getMoleculeType } from '../../mol-model/structure/model/types'; import { ComponentBuilder } from './common/component'; import { getChainId } from './common/util'; import { EntityBuilder } from './common/entity'; +import { BasicData, BasicSchema, createBasic } from './basic/schema'; +import { createModels } from './basic/parser'; // TODO multi model files -function getCategories(atoms: GroAtoms) { - const auth_atom_id = CifField.ofColumn(atoms.atomName) - const auth_comp_id = CifField.ofColumn(atoms.residueName) +function getBasic(atoms: GroAtoms): BasicData { + const auth_atom_id = atoms.atomName + const auth_comp_id = atoms.residueName const entityIds = new Array<string>(atoms.count) const asymIds = new Array<string>(atoms.count) @@ -69,57 +68,57 @@ function getCategories(atoms: GroAtoms) { ids[i] = i } - const auth_asym_id = CifField.ofColumn(Column.ofStringArray(asymIds)) + const auth_asym_id = Column.ofStringArray(asymIds) - const atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = { + const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, { auth_asym_id, auth_atom_id, auth_comp_id, - auth_seq_id: CifField.ofColumn(atoms.residueNumber), - B_iso_or_equiv: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)), - Cartn_x: CifField.ofNumbers(Column.mapToArray(atoms.x, x => x * 10, Float32Array)), - Cartn_y: CifField.ofNumbers(Column.mapToArray(atoms.y, y => y * 10, Float32Array)), - Cartn_z: CifField.ofNumbers(Column.mapToArray(atoms.z, z => z * 10, Float32Array)), - group_PDB: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)), - id: CifField.ofColumn(Column.ofIntArray(ids)), - - label_alt_id: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)), + auth_seq_id: atoms.residueNumber, + Cartn_x: Column.ofFloatArray(Column.mapToArray(atoms.x, x => x * 10, Float32Array)), + Cartn_y: Column.ofFloatArray(Column.mapToArray(atoms.y, y => y * 10, Float32Array)), + Cartn_z: Column.ofFloatArray(Column.mapToArray(atoms.z, z => z * 10, Float32Array)), + id: Column.ofIntArray(ids), label_asym_id: auth_asym_id, label_atom_id: auth_atom_id, label_comp_id: auth_comp_id, - label_seq_id: CifField.ofColumn(Column.ofIntArray(seqIds)), - label_entity_id: CifField.ofColumn(Column.ofStringArray(entityIds)), + label_seq_id: Column.ofIntArray(seqIds), + label_entity_id: Column.ofStringArray(entityIds), - occupancy: CifField.ofColumn(Column.ofConst(1, atoms.count, Column.Schema.float)), - type_symbol: CifField.ofStrings(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))), + occupancy: Column.ofConst(1, atoms.count, Column.Schema.float), + type_symbol: Column.ofStringArray(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))), - pdbx_PDB_ins_code: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)), - pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst('1', atoms.count, Column.Schema.str)), - } + pdbx_PDB_model_num: Column.ofConst(1, atoms.count, Column.Schema.int), + }, atoms.count) - return { - entity: entityBuilder.getEntityCategory(), - chem_comp: componentBuilder.getChemCompCategory(), - atom_site: CifCategory.ofFields('atom_site', atom_site) - } + return createBasic({ + entity: entityBuilder.getEntityTable(), + chem_comp: componentBuilder.getChemCompTable(), + atom_site + }) } -function groToMmCif(gro: GroFile) { - const categories = getCategories(gro.structures[0].atoms) +// + +export { GroFormat } - return { - header: gro.structures[0].header.title, - categoryNames: Object.keys(categories), - categories - }; +type GroFormat = ModelFormat<GroFile> + +namespace GroFormat { + export function is(x: ModelFormat): x is GroFormat { + return x.kind === 'gro' + } + + export function fromGro(gro: GroFile): GroFormat { + return { kind: 'gro', name: gro.structures[0].header.title, data: gro }; + } } export function trajectoryFromGRO(gro: GroFile): Task<Model.Trajectory> { return Task.create('Parse GRO', async ctx => { - await ctx.update('Converting to mmCIF'); - const cif = groToMmCif(gro); - const format = ModelFormat.mmCIF(cif); - return _parse_mmCif(format, ctx); + const format = GroFormat.fromGro(gro); + const basic = getBasic(gro.structures[0].atoms) + return createModels(basic, format, ctx); }) } diff --git a/src/mol-model-formats/structure/mmcif.ts b/src/mol-model-formats/structure/mmcif.ts index ea8c6de0653c21bb9f0542f25e4fa69123be5462..d52f7898ce97b0431e80ea59a6e726efa58e3660 100644 --- a/src/mol-model-formats/structure/mmcif.ts +++ b/src/mol-model-formats/structure/mmcif.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-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> @@ -8,9 +8,90 @@ import { Model } from '../../mol-model/structure/model/model'; import { Task } from '../../mol-task'; import { ModelFormat } from './format'; -import { _parse_mmCif } from './mmcif/parser'; -import { CifFrame } from '../../mol-io/reader/cif'; +import { CifFrame, CIF } from '../../mol-io/reader/cif'; +import { mmCIF_Database, mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif'; +import { createModels } from './basic/parser'; +import { ModelSymmetry } from './property/symmetry'; +import { ModelSecondaryStructure } from './property/secondary-structure'; +import { Table } from '../../mol-data/db'; +import { AtomSiteAnisotrop } from './property/anisotropic'; +import { ComponentBond } from './property/bonds/comp'; +import { StructConn } from './property/bonds/struct_conn'; +import { ModelCrossLinkRestraint } from './property/pair-restraints/cross-links'; + +function modelSymmetryFromMmcif(model: Model) { + if (!MmcifFormat.is(model.sourceData)) return; + return ModelSymmetry.fromData(model.sourceData.data.db) +} +ModelSymmetry.Provider.formatRegistry.add('mmCIF', modelSymmetryFromMmcif) + +function secondaryStructureFromMmcif(model: Model) { + if (!MmcifFormat.is(model.sourceData)) return; + const { struct_conf, struct_sheet_range } = model.sourceData.data.db + return ModelSecondaryStructure.fromStruct(struct_conf, struct_sheet_range, model.atomicHierarchy) +} +ModelSecondaryStructure.Provider.formatRegistry.add('mmCIF', secondaryStructureFromMmcif) + +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) + return { data, elementToAnsiotrop } +} +AtomSiteAnisotrop.Provider.formatRegistry.add('mmCIF', atomSiteAnisotropFromMmcif) + +function componentBondFromMmcif(model: Model) { + if (!MmcifFormat.is(model.sourceData)) return; + const { chem_comp_bond } = model.sourceData.data.db; + if (chem_comp_bond._rowCount === 0) return; + return { + data: chem_comp_bond, + entries: ComponentBond.getEntriesFromChemCompBond(chem_comp_bond) + } +} +ComponentBond.Provider.formatRegistry.add('mmCIF', componentBondFromMmcif) + +function structConnFromMmcif(model: Model) { + if (!MmcifFormat.is(model.sourceData)) return; + const { struct_conn } = model.sourceData.data.db; + if (struct_conn._rowCount === 0) return; + const entries = StructConn.getEntriesFromStructConn(struct_conn, model) + return { + data: struct_conn, + byAtomIndex: StructConn.getAtomIndexFromEntries(entries), + entries, + } +} +StructConn.Provider.formatRegistry.add('mmCIF', structConnFromMmcif) + +function crossLinkRestraintFromMmcif(model: Model) { + if (!MmcifFormat.is(model.sourceData)) return; + const { ihm_cross_link_restraint } = model.sourceData.data.db; + if (ihm_cross_link_restraint._rowCount === 0) return; + return ModelCrossLinkRestraint.fromTable(ihm_cross_link_restraint, model) +} +ModelCrossLinkRestraint.Provider.formatRegistry.add('mmCIF', crossLinkRestraintFromMmcif) + +// + +export { MmcifFormat } + +type MmcifFormat = ModelFormat<MmcifFormat.Data> + +namespace MmcifFormat { + export type Data = { db: mmCIF_Database, frame: CifFrame } + export function is(x: ModelFormat): x is MmcifFormat { + return x.kind === 'mmCIF' + } + + export function fromFrame(frame: CifFrame, db?: mmCIF_Database): MmcifFormat { + if (!db) db = CIF.schema.mmCIF(frame) + return { kind: 'mmCIF', name: db._name, data: { db, frame } }; + } +} export function trajectoryFromMmCIF(frame: CifFrame): Task<Model.Trajectory> { - return Task.create('Create mmCIF Model', ctx => _parse_mmCif(ModelFormat.mmCIF(frame), ctx)); + const format = MmcifFormat.fromFrame(frame) + return Task.create('Create mmCIF Model', ctx => createModels(format.data.db, format, ctx)); } \ No newline at end of file diff --git a/src/mol-model-formats/structure/mmcif/anisotropic.ts b/src/mol-model-formats/structure/mmcif/anisotropic.ts deleted file mode 100644 index b4ae035eb11be2951a3ad627edebb06c66964cf4..0000000000000000000000000000000000000000 --- a/src/mol-model-formats/structure/mmcif/anisotropic.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import { Table } from '../../../mol-data/db'; -import { Model, CustomPropertyDescriptor } from '../../../mol-model/structure'; -import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif'; -import { CifWriter } from '../../../mol-io/writer/cif'; - -export interface AtomSiteAnisotrop { - data: Table<AtomSiteAnisotrop.Schema['atom_site_anisotrop']> - /** maps atom_site-index to atom_site_anisotrop-index */ - elementToAnsiotrop: Int32Array -} - -export namespace AtomSiteAnisotrop { - export function getAtomSiteAnisotrop(model: Model) { - if (model.sourceData.kind !== 'mmCIF') return void 0; - const { atom_site_anisotrop } = model.sourceData.data - return Table.ofColumns(Schema.atom_site_anisotrop, atom_site_anisotrop); - } - - export const PropName = '__AtomSiteAnisotrop__'; - export function get(model: Model): AtomSiteAnisotrop | undefined { - if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName] - if (!model.customProperties.has(Descriptor)) return void 0; - - const data = getAtomSiteAnisotrop(model); - if (!data) return void 0; - - const prop = { data, elementToAnsiotrop: getElementToAnsiotrop(model, data) } - set(model, prop) - - return prop; - } - function set(model: Model, prop: AtomSiteAnisotrop) { - (model._staticPropertyData[PropName] as AtomSiteAnisotrop) = prop; - } - - export const Schema = { atom_site_anisotrop: mmCIF_Schema['atom_site_anisotrop'] }; - export type Schema = typeof Schema - - export const Descriptor: CustomPropertyDescriptor = { - name: 'atom_site_anisotrop', - cifExport: { - prefix: '', - categories: [{ - name: 'atom_site_anisotrop', - instance(ctx) { - const atom_site_anisotrop = getAtomSiteAnisotrop(ctx.firstModel); - if (!atom_site_anisotrop) return CifWriter.Category.Empty; - return CifWriter.Category.ofTable(atom_site_anisotrop); - } - }] - } - }; - - function getElementToAnsiotrop(model: Model, data: Table<Schema['atom_site_anisotrop']>) { - const { atomId } = model.atomicConformation - 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)] - if (ei !== -1) elementToAnsiotrop[ei] = i - } - - return elementToAnsiotrop - } - - export async function attachFromMmCif(model: Model) { - if (model.customProperties.has(Descriptor)) return true; - if (model.sourceData.kind !== 'mmCIF') return false; - const atomSiteAnisotrop = getAtomSiteAnisotrop(model); - if (!atomSiteAnisotrop || atomSiteAnisotrop._rowCount === 0) return false; - - model.customProperties.add(Descriptor); - return true; - } -} \ No newline at end of file diff --git a/src/mol-model-formats/structure/mmcif/bonds.ts b/src/mol-model-formats/structure/mmcif/bonds.ts deleted file mode 100644 index 0f69970236251826f5c529d481726b6d2c82b8fc..0000000000000000000000000000000000000000 --- a/src/mol-model-formats/structure/mmcif/bonds.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Copyright (c) 2017-2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -export * from './bonds/comp' -export * from './bonds/struct_conn' \ No newline at end of file diff --git a/src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts b/src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts deleted file mode 100644 index 8c8edd3857bd15ebaa93119853bcec9dc611fc69..0000000000000000000000000000000000000000 --- a/src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Copyright (c) 2017-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import { Model } from '../../../../mol-model/structure/model/model' -import { Structure } from '../../../../mol-model/structure' -import { BondType } from '../../../../mol-model/structure/model/types' -import { findEntityIdByAsymId, findAtomIndexByLabelName } from '../util' -import { Column } from '../../../../mol-data/db' -import { CustomPropertyDescriptor } from '../../../../mol-model/structure'; -import { mmCIF_Database } from '../../../../mol-io/reader/cif/schema/mmcif'; -import { SortedArray } from '../../../../mol-data/int'; -import { CifWriter } from '../../../../mol-io/writer/cif' -import { ElementIndex, ResidueIndex } from '../../../../mol-model/structure/model/indexing'; -import { getInterBondOrderFromTable } from '../../../../mol-model/structure/model/properties/atomic/bonds'; - -export interface StructConn { - getResidueEntries(residueAIndex: ResidueIndex, residueBIndex: ResidueIndex): ReadonlyArray<StructConn.Entry>, - getAtomEntries(atomIndex: ElementIndex): ReadonlyArray<StructConn.Entry>, - readonly entries: ReadonlyArray<StructConn.Entry> -} - -export namespace StructConn { - export const Descriptor: CustomPropertyDescriptor = { - name: 'struct_conn', - cifExport: { - prefix: '', - categories: [{ - name: 'struct_conn', - instance(ctx) { - const structure = ctx.structures[0], model = structure.model; - const struct_conn = getStructConn(model); - if (!struct_conn) return CifWriter.Category.Empty; - - const strConn = get(model); - if (!strConn || strConn.entries.length === 0) return CifWriter.Category.Empty; - - const foundAtoms = new Set<ElementIndex>(); - const indices: number[] = []; - for (const entry of strConn.entries) { - const { partners } = entry; - let hasAll = true; - for (let i = 0, _i = partners.length; i < _i; i++) { - const atom = partners[i].atomIndex; - if (foundAtoms.has(atom)) continue; - if (hasAtom(structure, atom)) { - foundAtoms.add(atom); - } else { - hasAll = false; - break; - } - } - if (hasAll) { - indices[indices.length] = entry.rowIndex; - } - } - - return CifWriter.Category.ofTable(struct_conn, indices); - } - }] - } - } - - function hasAtom({ units }: Structure, element: ElementIndex) { - for (let i = 0, _i = units.length; i < _i; i++) { - if (SortedArray.indexOf(units[i].elements, element) >= 0) return true; - } - return false; - } - - function _resKey(rA: number, rB: number) { - if (rA < rB) return `${rA}-${rB}`; - return `${rB}-${rA}`; - } - const _emptyEntry: Entry[] = []; - - class StructConnImpl implements StructConn { - private _residuePairIndex: Map<string, StructConn.Entry[]> | undefined = void 0; - private _atomIndex: Map<number, StructConn.Entry[]> | undefined = void 0; - - private getResiduePairIndex() { - if (this._residuePairIndex) return this._residuePairIndex; - this._residuePairIndex = new Map(); - for (const e of this.entries) { - const ps = e.partners; - const l = ps.length; - for (let i = 0; i < l - 1; i++) { - for (let j = i + i; j < l; j++) { - const key = _resKey(ps[i].residueIndex, ps[j].residueIndex); - if (this._residuePairIndex.has(key)) { - this._residuePairIndex.get(key)!.push(e); - } else { - this._residuePairIndex.set(key, [e]); - } - } - } - } - return this._residuePairIndex; - } - - private getAtomIndex() { - if (this._atomIndex) return this._atomIndex; - this._atomIndex = new Map(); - for (const e of this.entries) { - for (const p of e.partners) { - const key = p.atomIndex; - if (this._atomIndex.has(key)) { - this._atomIndex.get(key)!.push(e); - } else { - this._atomIndex.set(key, [e]); - } - } - } - return this._atomIndex; - } - - getResidueEntries(residueAIndex: ResidueIndex, residueBIndex: ResidueIndex): ReadonlyArray<StructConn.Entry> { - return this.getResiduePairIndex().get(_resKey(residueAIndex, residueBIndex)) || _emptyEntry; - } - - getAtomEntries(atomIndex: ElementIndex): ReadonlyArray<StructConn.Entry> { - return this.getAtomIndex().get(atomIndex) || _emptyEntry; - } - - constructor(public entries: StructConn.Entry[]) { - } - } - - export interface Entry { - rowIndex: number, - distance: number, - order: number, - flags: number, - partners: { residueIndex: ResidueIndex, atomIndex: ElementIndex, symmetry: string }[] - } - - export function attachFromMmCif(model: Model): boolean { - if (model.customProperties.has(Descriptor)) return true; - if (model.sourceData.kind !== 'mmCIF') return false; - const { struct_conn } = model.sourceData.data; - if (struct_conn._rowCount === 0) return false; - model.customProperties.add(Descriptor); - model._staticPropertyData.__StructConnData__ = struct_conn; - return true; - } - - function getStructConn(model: Model) { - return model._staticPropertyData.__StructConnData__ as mmCIF_Database['struct_conn']; - } - - export const PropName = '__StructConn__'; - export function get(model: Model): StructConn | undefined { - if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName]; - if (!model.customProperties.has(Descriptor)) return void 0; - - const struct_conn = getStructConn(model); - - const { conn_type_id, pdbx_dist_value, pdbx_value_order } = struct_conn; - const p1 = { - label_asym_id: struct_conn.ptnr1_label_asym_id, - label_seq_id: struct_conn.ptnr1_label_seq_id, - auth_seq_id: struct_conn.ptnr1_auth_seq_id, - label_atom_id: struct_conn.ptnr1_label_atom_id, - label_alt_id: struct_conn.pdbx_ptnr1_label_alt_id, - ins_code: struct_conn.pdbx_ptnr1_PDB_ins_code, - symmetry: struct_conn.ptnr1_symmetry - }; - const p2: typeof p1 = { - label_asym_id: struct_conn.ptnr2_label_asym_id, - label_seq_id: struct_conn.ptnr2_label_seq_id, - auth_seq_id: struct_conn.ptnr2_auth_seq_id, - label_atom_id: struct_conn.ptnr2_label_atom_id, - label_alt_id: struct_conn.pdbx_ptnr2_label_alt_id, - ins_code: struct_conn.pdbx_ptnr2_PDB_ins_code, - symmetry: struct_conn.ptnr2_symmetry - }; - - const _p = (row: number, ps: typeof p1) => { - if (ps.label_asym_id.valueKind(row) !== Column.ValueKind.Present) return void 0; - const asymId = ps.label_asym_id.value(row); - const residueIndex = model.atomicHierarchy.index.findResidue( - findEntityIdByAsymId(model, asymId), - asymId, - ps.auth_seq_id.value(row), - ps.ins_code.value(row) - ); - if (residueIndex < 0) return void 0; - const atomName = ps.label_atom_id.value(row); - // turns out "mismat" records might not have atom name value - if (!atomName) return void 0; - const atomIndex = findAtomIndexByLabelName(model, residueIndex, atomName, ps.label_alt_id.value(row)); - if (atomIndex < 0) return void 0; - return { residueIndex, atomIndex, symmetry: ps.symmetry.value(row) || '1_555' }; - } - - const _ps = (row: number) => { - const ret = []; - let p = _p(row, p1); - if (p) ret.push(p); - p = _p(row, p2); - if (p) ret.push(p); - return ret; - } - - const entries: StructConn.Entry[] = []; - for (let i = 0; i < struct_conn._rowCount; i++) { - const partners = _ps(i); - if (partners.length < 2) continue; - - const type = conn_type_id.value(i) - const orderType = (pdbx_value_order.value(i) || '').toLowerCase(); - let flags = BondType.Flag.None; - let order = 1; - - switch (orderType) { - case 'sing': order = 1; break; - case 'doub': order = 2; break; - case 'trip': order = 3; break; - case 'quad': order = 4; break; - default: - order = getInterBondOrderFromTable( - struct_conn.ptnr1_label_comp_id.value(i), - struct_conn.ptnr1_label_atom_id.value(i), - struct_conn.ptnr2_label_comp_id.value(i), - struct_conn.ptnr2_label_atom_id.value(i) - ) - } - - switch (type) { - case 'covale': - flags = BondType.Flag.Covalent; - break; - case 'disulf': flags = BondType.Flag.Covalent | BondType.Flag.Disulfide; break; - case 'hydrog': - flags = BondType.Flag.HydrogenBond; - break; - case 'metalc': flags = BondType.Flag.MetallicCoordination; break; - } - - entries.push({ rowIndex: i, flags, order, distance: pdbx_dist_value.value(i), partners }); - } - - const ret = new StructConnImpl(entries); - model._staticPropertyData[PropName] = ret; - return ret; - } -} \ No newline at end of file diff --git a/src/mol-model-formats/structure/mmcif/pair-restraint.ts b/src/mol-model-formats/structure/mmcif/pair-restraint.ts deleted file mode 100644 index 40cec49d41786cc3576f29bca18c357e87cb715e..0000000000000000000000000000000000000000 --- a/src/mol-model-formats/structure/mmcif/pair-restraint.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -export * from './pair-restraints/cross-links' -// export * from './pair-restraints/predicted-contacts' \ No newline at end of file diff --git a/src/mol-model-formats/structure/mmcif/parser.ts b/src/mol-model-formats/structure/mmcif/parser.ts deleted file mode 100644 index 8f19909479f858d24406f421f324863c1b10f291..0000000000000000000000000000000000000000 --- a/src/mol-model-formats/structure/mmcif/parser.ts +++ /dev/null @@ -1,529 +0,0 @@ -/** - * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import { Column, Table } from '../../../mol-data/db'; -import { mmCIF_Database, mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif'; -import { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/geometry'; -import { Tensor, Vec3, Mat3 } from '../../../mol-math/linear-algebra'; -import { RuntimeContext } from '../../../mol-task'; -import UUID from '../../../mol-util/uuid'; -import { Model } from '../../../mol-model/structure/model/model'; -import { Entities, ChemicalComponent, MissingResidue, EntitySubtype } from '../../../mol-model/structure/model/properties/common'; -import { CustomProperties } from '../../../mol-model/structure'; -import { ModelSymmetry } from '../../../mol-model/structure/model/properties/symmetry'; -import { createAssemblies } from './assembly'; -import { getAtomicHierarchyAndConformation } from './atomic'; -import { ComponentBond } from './bonds'; -import { getIHMCoarse, EmptyIHMCoarse, IHMData } from './ihm'; -import { getSecondaryStructure } from './secondary-structure'; -import { getSequence } from './sequence'; -import { sortAtomSite } from './sort'; -import { StructConn } from './bonds/struct_conn'; -import { getMoleculeType, MoleculeType, getEntityType, getEntitySubtype, getDefaultChemicalComponent } from '../../../mol-model/structure/model/types'; -import { ModelFormat } from '../format'; -import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants'; -import mmCIF_Format = ModelFormat.mmCIF -import { memoize1 } from '../../../mol-util/memoize'; -import { ElementIndex, EntityIndex } from '../../../mol-model/structure/model'; -import { AtomSiteAnisotrop } from './anisotropic'; -import { getAtomicRanges } from '../../../mol-model/structure/model/properties/utils/atomic-ranges'; - -export async function _parse_mmCif(format: mmCIF_Format, ctx: RuntimeContext) { - const formatData = getFormatData(format) - const isIHM = format.data.ihm_model_list._rowCount > 0; - return isIHM ? await readIHM(ctx, format, formatData) : await readStandard(ctx, format, formatData); -} - -type AtomSite = mmCIF_Database['atom_site'] - -function getSymmetry(format: mmCIF_Format): ModelSymmetry { - const assemblies = createAssemblies(format); - const spacegroup = getSpacegroup(format); - const isNonStandardCrytalFrame = checkNonStandardCrystalFrame(format, spacegroup); - return { assemblies, spacegroup, isNonStandardCrytalFrame, ncsOperators: getNcsOperators(format) }; -} - -function checkNonStandardCrystalFrame(format: mmCIF_Format, spacegroup: Spacegroup) { - const { atom_sites } = format.data; - if (atom_sites._rowCount === 0) return false; - // TODO: parse atom_sites transform and check if it corresponds to the toFractional matrix - return false; -} - -function getSpacegroupNameOrNumber(symmetry: mmCIF_Format['data']['symmetry']) { - const groupNumber = symmetry['Int_Tables_number'].value(0); - const groupName = symmetry['space_group_name_H-M'].value(0); - if (!symmetry['Int_Tables_number'].isDefined) return groupName - if (!symmetry['space_group_name_H-M'].isDefined) return groupNumber - return groupName -} - -function getSpacegroup(format: mmCIF_Format): Spacegroup { - const { symmetry, cell } = format.data; - if (symmetry._rowCount === 0 || cell._rowCount === 0) return Spacegroup.ZeroP1; - const nameOrNumber = getSpacegroupNameOrNumber(symmetry) - 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.create(spaceCell); -} - -function getNcsOperators(format: mmCIF_Format) { - const { struct_ncs_oper } = format.data; - if (struct_ncs_oper._rowCount === 0) return void 0; - const { id, matrix, vector } = struct_ncs_oper; - - const matrixSpace = mmCIF_Schema.struct_ncs_oper.matrix.space, vectorSpace = mmCIF_Schema.struct_ncs_oper.vector.space; - - const opers: SymmetryOperator[] = []; - for (let i = 0; i < struct_ncs_oper._rowCount; i++) { - const m = Tensor.toMat3(Mat3(), matrixSpace, matrix.value(i)); - const v = Tensor.toVec3(Vec3(), vectorSpace, vector.value(i)); - if (!SymmetryOperator.checkIfRotationAndTranslation(m, v)) continue; - // ignore non-identity 'given' NCS operators - if (struct_ncs_oper.code.value(i) === 'given' && !Mat3.isIdentity(m) && !Vec3.isZero(v)) continue; - const ncsId = id.value(i) - opers[opers.length] = SymmetryOperator.ofRotationAndOffset(`ncs_${ncsId}`, m, v, ncsId); - } - return opers; -} - -function getModifiedResidueNameMap(format: mmCIF_Format): Model['properties']['modifiedResidues'] { - const data = format.data.pdbx_struct_mod_residue; - const parentId = new Map<string, string>(); - const details = new Map<string, string>(); - const comp_id = data.label_comp_id.isDefined ? data.label_comp_id : data.auth_comp_id; - const parent_id = data.parent_comp_id, details_data = data.details; - - for (let i = 0; i < data._rowCount; i++) { - const id = comp_id.value(i); - parentId.set(id, parent_id.value(i)); - details.set(id, details_data.value(i)); - } - - return { parentId, details }; -} - -function getMissingResidues(format: mmCIF_Format): Model['properties']['missingResidues'] { - const map = new Map<string, MissingResidue>(); - const c = format.data.pdbx_unobs_or_zero_occ_residues - - const getKey = (model_num: number, asym_id: string, seq_id: number) => { - return `${model_num}|${asym_id}|${seq_id}` - } - - for (let i = 0, il = c._rowCount; i < il; ++i) { - const key = getKey(c.PDB_model_num.value(i), c.label_asym_id.value(i), c.label_seq_id.value(i)) - map.set(key, { polymer_flag: c.polymer_flag.value(i), occupancy_flag: c.occupancy_flag.value(i) }) - } - - return { - has: (model_num: number, asym_id: string, seq_id: number) => { - return map.has(getKey(model_num, asym_id, seq_id)) - }, - get: (model_num: number, asym_id: string, seq_id: number) => { - return map.get(getKey(model_num, asym_id, seq_id)) - }, - size: map.size - } -} - -function getChemicalComponentMap(format: mmCIF_Format): Model['properties']['chemicalComponentMap'] { - const map = new Map<string, ChemicalComponent>(); - const { chem_comp } = format.data - - if (chem_comp._rowCount > 0) { - const { id } = chem_comp - for (let i = 0, il = id.rowCount; i < il; ++i) { - map.set(id.value(i), Table.getRow(chem_comp, i)) - } - } else { - const uniqueNames = getUniqueComponentNames(format); - uniqueNames.forEach(n => { - map.set(n, getDefaultChemicalComponent(n)); - }); - } - return map -} - -function getSaccharideComponentMap(format: mmCIF_Format): SaccharideComponentMap { - const map = new Map<string, SaccharideComponent>(); - - if (format.data.pdbx_chem_comp_identifier._rowCount > 0) { - // note that `pdbx_chem_comp_identifier` does not contain - // a 'SNFG CARBOHYDRATE SYMBOL' entry for 'Unknown' saccharide components - // so we always need to check `chem_comp` for those - const { comp_id, type, identifier } = format.data.pdbx_chem_comp_identifier - for (let i = 0, il = comp_id.rowCount; i < il; ++i) { - if (type.value(i) === 'SNFG CARBOHYDRATE SYMBOL' || - type.value(i) === 'SNFG CARB SYMBOL' // legacy, to be removed from mmCIF dictionary - ) { - const snfgName = identifier.value(i) - const saccharideComp = SaccharidesSnfgMap.get(snfgName) - if (saccharideComp) { - map.set(comp_id.value(i), saccharideComp) - } else { - console.warn(`Unknown SNFG name '${snfgName}'`) - } - } - } - } - - if (format.data.chem_comp._rowCount > 0) { - const { id, type } = format.data.chem_comp - for (let i = 0, il = id.rowCount; i < il; ++i) { - const _id = id.value(i) - if (map.has(_id)) continue - const _type = type.value(i) - if (SaccharideCompIdMap.has(_id)) { - map.set(_id, SaccharideCompIdMap.get(_id)!) - } else if (getMoleculeType(_type, _id) === MoleculeType.Saccharide) { - map.set(_id, UnknownSaccharideComponent) - } - } - } else { - const uniqueNames = getUniqueComponentNames(format) - SaccharideCompIdMap.forEach((v, k) => { - if (!map.has(k) && uniqueNames.has(k)) map.set(k, v) - }) - } - return map -} - -const getUniqueComponentNames = memoize1((format: mmCIF_Format) => { - const uniqueNames = new Set<string>() - const data = format.data.atom_site - const comp_id = data.label_comp_id.isDefined ? data.label_comp_id : data.auth_comp_id; - for (let i = 0, il = comp_id.rowCount; i < il; ++i) { - uniqueNames.add(comp_id.value(i)) - } - return uniqueNames -}) - -export interface FormatData { - modifiedResidues: Model['properties']['modifiedResidues'] - missingResidues: Model['properties']['missingResidues'] - chemicalComponentMap: Model['properties']['chemicalComponentMap'] - saccharideComponentMap: Model['properties']['saccharideComponentMap'] -} - -function getFormatData(format: mmCIF_Format): FormatData { - return { - modifiedResidues: getModifiedResidueNameMap(format), - missingResidues: getMissingResidues(format), - chemicalComponentMap: getChemicalComponentMap(format), - saccharideComponentMap: getSaccharideComponentMap(format) - } -} - -function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, formatData: FormatData, previous?: Model): Model { - const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, formatData, previous); - const modelNum = atom_site.pdbx_PDB_model_num.value(0) - if (previous && atomic.sameAsPrevious) { - return { - ...previous, - id: UUID.create22(), - modelNum, - atomicConformation: atomic.conformation, - _dynamicPropertyData: Object.create(null) - }; - } - - const coarse = EmptyIHMCoarse; - const sequence = getSequence(format.data, entities, atomic.hierarchy, coarse.hierarchy, formatData.modifiedResidues.parentId) - const atomicRanges = getAtomicRanges(atomic.hierarchy, entities, atomic.conformation, sequence) - - const entry = format.data.entry.id.valueKind(0) === Column.ValueKind.Present - ? format.data.entry.id.value(0) - : format.data._name; - - const label: string[] = [] - if (entry) label.push(entry) - if (format.data.struct.title.valueKind(0) === Column.ValueKind.Present) label.push(format.data.struct.title.value(0)) - - return { - id: UUID.create22(), - entryId: entry, - label: label.join(' | '), - entry, - sourceData: format, - modelNum, - entities, - symmetry: getSymmetry(format), - sequence, - atomicHierarchy: atomic.hierarchy, - atomicConformation: atomic.conformation, - atomicRanges, - coarseHierarchy: coarse.hierarchy, - coarseConformation: coarse.conformation, - properties: { - secondaryStructure: getSecondaryStructure(format.data, atomic.hierarchy), - ...formatData - }, - customProperties: new CustomProperties(), - _staticPropertyData: Object.create(null), - _dynamicPropertyData: Object.create(null) - }; -} - -function createModelIHM(format: mmCIF_Format, data: IHMData, formatData: FormatData): Model { - const atomic = getAtomicHierarchyAndConformation(data.atom_site, data.atom_site_sourceIndex, data.entities, formatData); - const coarse = getIHMCoarse(data, formatData); - const sequence = getSequence(format.data, data.entities, atomic.hierarchy, coarse.hierarchy, formatData.modifiedResidues.parentId) - const atomicRanges = getAtomicRanges(atomic.hierarchy, data.entities, atomic.conformation, sequence) - - const entry = format.data.entry.id.valueKind(0) === Column.ValueKind.Present - ? format.data.entry.id.value(0) - : format.data._name; - - const label: string[] = [] - if (entry) label.push(entry) - if (format.data.struct.title.valueKind(0) === Column.ValueKind.Present) label.push(format.data.struct.title.value(0)) - if (data.model_group_name) label.push(data.model_name) - if (data.model_group_name) label.push(data.model_group_name) - - return { - id: UUID.create22(), - entryId: entry, - label: label.join(' | '), - entry, - sourceData: format, - modelNum: data.model_id, - entities: data.entities, - symmetry: getSymmetry(format), - sequence, - atomicHierarchy: atomic.hierarchy, - atomicConformation: atomic.conformation, - atomicRanges, - coarseHierarchy: coarse.hierarchy, - coarseConformation: coarse.conformation, - properties: { - secondaryStructure: getSecondaryStructure(format.data, atomic.hierarchy), - ...formatData - }, - customProperties: new CustomProperties(), - _staticPropertyData: Object.create(null), - _dynamicPropertyData: Object.create(null) - }; -} - -function attachProps(model: Model) { - ComponentBond.attachFromMmCif(model); - StructConn.attachFromMmCif(model); - AtomSiteAnisotrop.attachFromMmCif(model); -} - -function findModelEnd(num: Column<number>, startIndex: number) { - const rowCount = num.rowCount; - if (!num.isDefined) return rowCount; - let endIndex = startIndex + 1; - while (endIndex < rowCount && num.areValuesEqual(startIndex, endIndex)) endIndex++; - return endIndex; -} - -function getEntities(format: mmCIF_Format): Entities { - let entityData: Table<mmCIF_Schema['entity']> - - if (!format.data.entity.id.isDefined) { - const entityIds = new Set<string>() - - const ids: mmCIF_Schema['entity']['id']['T'][] = [] - const types: mmCIF_Schema['entity']['type']['T'][] = [] - - const { label_entity_id, label_comp_id } = format.data.atom_site; - for (let i = 0 as ElementIndex, il = format.data.atom_site._rowCount; i < il; i++) { - const entityId = label_entity_id.value(i); - if (!entityIds.has(entityId)) { - ids.push(entityId) - types.push(getEntityType(label_comp_id.value(i))) - entityIds.add(entityId) - } - } - - const { entity_id: sphere_entity_id } = format.data.ihm_sphere_obj_site; - for (let i = 0 as ElementIndex, il = format.data.ihm_sphere_obj_site._rowCount; i < il; i++) { - const entityId = sphere_entity_id.value(i); - if (!entityIds.has(entityId)) { - ids.push(entityId) - types.push('polymer') - entityIds.add(entityId) - } - } - - const { entity_id: gaussian_entity_id } = format.data.ihm_gaussian_obj_site; - for (let i = 0 as ElementIndex, il = format.data.ihm_gaussian_obj_site._rowCount; i < il; i++) { - const entityId = gaussian_entity_id.value(i); - if (!entityIds.has(entityId)) { - ids.push(entityId) - types.push('polymer') - entityIds.add(entityId) - } - } - - entityData = Table.ofColumns(mmCIF_Schema.entity, { - ...format.data.entity, - id: Column.ofArray({ array: ids, schema: mmCIF_Schema.entity.id }), - type: Column.ofArray({ array: types, schema: mmCIF_Schema.entity.type }), - }) - } else { - entityData = format.data.entity; - } - - const getEntityIndex = Column.createIndexer<string, EntityIndex>(entityData.id) - - // - - const subtypes: EntitySubtype[] = new Array(entityData._rowCount) - subtypes.fill('other') - - const entityIds = new Set<string>() - let assignSubtype = false - - if (format.data.entity_poly.entity_id.isDefined) { - const { entity_id, type, _rowCount } = format.data.entity_poly - for (let i = 0; i < _rowCount; ++i) { - const entityId = entity_id.value(i) - subtypes[getEntityIndex(entityId)] = type.value(i) - entityIds.add(entityId) - } - } else { - assignSubtype = true - } - - if (format.data.pdbx_entity_branch.entity_id.isDefined) { - const { entity_id, type, _rowCount } = format.data.pdbx_entity_branch - for (let i = 0; i < _rowCount; ++i) { - const entityId = entity_id.value(i) - subtypes[getEntityIndex(entityId)] = type.value(i) - entityIds.add(entityId) - } - } else { - assignSubtype = true - } - - if (assignSubtype) { - const chemCompType = new Map<string, string>() - const { id, type } = format.data.chem_comp; - for (let i = 0, il = format.data.chem_comp._rowCount; i < il; i++) { - chemCompType.set(id.value(i), type.value(i)) - } - - const { label_entity_id, label_comp_id } = format.data.atom_site; - for (let i = 0 as ElementIndex, il = format.data.atom_site._rowCount; i < il; i++) { - const entityId = label_entity_id.value(i); - if (!entityIds.has(entityId)) { - const compId = label_comp_id.value(i) - const compType = chemCompType.get(compId) || '' - subtypes[getEntityIndex(entityId)] = getEntitySubtype(compId, compType) - entityIds.add(entityId) - } - } - // TODO how to handle coarse? - } - - const subtypeColumn = Column.ofArray({ array: subtypes, schema: EntitySubtype }) - - // - - return { data: entityData, subtype: subtypeColumn, getEntityIndex }; -} - -async function readStandard(ctx: RuntimeContext, format: mmCIF_Format, formatData: FormatData) { - const atomCount = format.data.atom_site._rowCount; - const entities = getEntities(format) - - const models: Model[] = []; - let modelStart = 0; - while (modelStart < atomCount) { - const modelEnd = findModelEnd(format.data.atom_site.pdbx_PDB_model_num, modelStart); - const { atom_site, sourceIndex } = await sortAtomSite(ctx, format.data.atom_site, modelStart, modelEnd); - const model = createStandardModel(format, atom_site, sourceIndex, entities, formatData, models.length > 0 ? models[models.length - 1] : void 0); - attachProps(model); - models.push(model); - modelStart = modelEnd; - } - return models; -} - -function splitTable<T extends Table<any>>(table: T, col: Column<number>) { - const ret = new Map<number, { table: T, start: number, end: number }>() - const rowCount = table._rowCount; - let modelStart = 0; - while (modelStart < rowCount) { - const modelEnd = findModelEnd(col, modelStart); - const id = col.value(modelStart); - ret.set(id, { - table: Table.window(table, table._schema, modelStart, modelEnd) as T, - start: modelStart, - end: modelEnd - }); - modelStart = modelEnd; - } - return ret; -} - -function getModelGroupName(model_id: number, format: mmCIF_Format) { - const { ihm_model_group, ihm_model_group_link } = format.data; - - const link = Table.pickRow(ihm_model_group_link, i => ihm_model_group_link.model_id.value(i) === model_id) - if (link) { - const group = Table.pickRow(ihm_model_group, i => ihm_model_group.id.value(i) === link.group_id) - if (group) return group.name - } - return '' -} - -async function readIHM(ctx: RuntimeContext, format: mmCIF_Format, formatData: FormatData) { - // when `atom_site.ihm_model_id` is undefined fall back to `atom_site.pdbx_PDB_model_num` - const atom_sites_modelColumn = format.data.atom_site.ihm_model_id.isDefined ? format.data.atom_site.ihm_model_id : format.data.atom_site.pdbx_PDB_model_num - - const { ihm_model_list } = format.data; - const entities = getEntities(format) - - const atom_sites = splitTable(format.data.atom_site, atom_sites_modelColumn); - // TODO: will coarse IHM records require sorting or will we trust it? - // ==> Probably implement a sort as as well and store the sourceIndex same as with atomSite - // If the sorting is implemented, updated mol-model/structure/properties: atom.sourceIndex - const sphere_sites = splitTable(format.data.ihm_sphere_obj_site, format.data.ihm_sphere_obj_site.model_id); - const gauss_sites = splitTable(format.data.ihm_gaussian_obj_site, format.data.ihm_gaussian_obj_site.model_id); - - const models: Model[] = []; - - const { model_id, model_name } = ihm_model_list; - for (let i = 0; i < ihm_model_list._rowCount; i++) { - const id = model_id.value(i); - - let atom_site, atom_site_sourceIndex; - if (atom_sites.has(id)) { - const e = atom_sites.get(id)!; - // need to sort `format.data.atom_site` as `e.start` and `e.end` are indices into that - const { atom_site: sorted, sourceIndex } = await sortAtomSite(ctx, format.data.atom_site, e.start, e.end); - atom_site = sorted; - atom_site_sourceIndex = sourceIndex; - } else { - atom_site = Table.window(format.data.atom_site, format.data.atom_site._schema, 0, 0); - atom_site_sourceIndex = Column.ofIntArray([]); - } - - const data: IHMData = { - model_id: id, - model_name: model_name.value(i), - model_group_name: getModelGroupName(id, format), - entities: entities, - atom_site, - atom_site_sourceIndex, - ihm_sphere_obj_site: sphere_sites.has(id) ? sphere_sites.get(id)!.table : Table.window(format.data.ihm_sphere_obj_site, format.data.ihm_sphere_obj_site._schema, 0, 0), - ihm_gaussian_obj_site: gauss_sites.has(id) ? gauss_sites.get(id)!.table : Table.window(format.data.ihm_gaussian_obj_site, format.data.ihm_gaussian_obj_site._schema, 0, 0) - }; - const model = createModelIHM(format, data, formatData); - attachProps(model); - models.push(model); - } - - return models; -} \ No newline at end of file diff --git a/src/mol-model-formats/structure/mmcif/util.ts b/src/mol-model-formats/structure/mmcif/util.ts deleted file mode 100644 index 71d7ac58cc5810b6fda966b26b4966e4a01ee93f..0000000000000000000000000000000000000000 --- a/src/mol-model-formats/structure/mmcif/util.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import { Model } from '../../../mol-model/structure/model' -import { ElementIndex } from '../../../mol-model/structure/model/indexing'; - -export function findEntityIdByAsymId(model: Model, asymId: string) { - if (model.sourceData.kind !== 'mmCIF') return '' - const { struct_asym } = model.sourceData.data - for (let i = 0, n = struct_asym._rowCount; i < n; ++i) { - if (struct_asym.id.value(i) === asymId) return struct_asym.entity_id.value(i) - } - return '' -} - -export function findAtomIndexByLabelName(model: Model, residueIndex: number, atomName: string, altLoc: string | null): ElementIndex { - const { offsets } = model.atomicHierarchy.residueAtomSegments; - const { label_atom_id, label_alt_id } = model.atomicHierarchy.atoms; - for (let i = offsets[residueIndex], n = offsets[residueIndex + 1]; i < n; ++i) { - if (label_atom_id.value(i) === atomName && (!altLoc || label_alt_id.value(i) === altLoc)) return i as ElementIndex; - } - return -1 as ElementIndex; -} \ No newline at end of file diff --git a/src/mol-model-formats/structure/pdb.ts b/src/mol-model-formats/structure/pdb.ts index 525ec39cccfa76c405fbb2d44b231b213ab0a946..c77392ec26435bf213bec83521a7f74f95836c17 100644 --- a/src/mol-model-formats/structure/pdb.ts +++ b/src/mol-model-formats/structure/pdb.ts @@ -1,21 +1,22 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * 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> */ import { PdbFile } from '../../mol-io/reader/pdb/schema'; import { pdbToMmCif } from './pdb/to-cif'; import { Model } from '../../mol-model/structure/model'; import { Task } from '../../mol-task'; -import { ModelFormat } from './format'; -import { _parse_mmCif } from './mmcif/parser'; +import { MmcifFormat } from './mmcif'; +import { createModels } from './basic/parser'; export function trajectoryFromPDB(pdb: PdbFile): Task<Model.Trajectory> { return Task.create('Parse PDB', async ctx => { await ctx.update('Converting to mmCIF'); const cif = await pdbToMmCif(pdb); - const format = ModelFormat.mmCIF(cif); - return _parse_mmCif(format, ctx); + const format = MmcifFormat.fromFrame(cif) + return createModels(format.data.db, format, ctx) }) } diff --git a/src/mol-model-formats/structure/pdb/to-cif.ts b/src/mol-model-formats/structure/pdb/to-cif.ts index cf967d4359bbb2a6437f4973a3eb3a20069646b8..05e0f8592689d7a48a0d44b3cac74e06f83284d5 100644 --- a/src/mol-model-formats/structure/pdb/to-cif.ts +++ b/src/mol-model-formats/structure/pdb/to-cif.ts @@ -162,8 +162,8 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> { } const categories = { - entity: entityBuilder.getEntityCategory(), - chem_comp: componentBuilder.getChemCompCategory(), + entity: entityBuilder.getEntityTable(), + chem_comp: componentBuilder.getChemCompTable(), atom_site: CifCategory.ofFields('atom_site', getAtomSite(atomSite)), atom_site_anisotrop: CifCategory.ofFields('atom_site_anisotrop', getAnisotropic(anisotropic)) } as any; diff --git a/src/mol-model-formats/structure/property/anisotropic.ts b/src/mol-model-formats/structure/property/anisotropic.ts new file mode 100644 index 0000000000000000000000000000000000000000..156fcb8926083d77d75dd277a22e48621530d972 --- /dev/null +++ b/src/mol-model-formats/structure/property/anisotropic.ts @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Table } from '../../../mol-data/db'; +import { Model, 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'; + +export { AtomSiteAnisotrop } + +type Anisotrop = Table<mmCIF_Schema['atom_site_anisotrop']> + +interface AtomSiteAnisotrop { + data: Anisotrop + /** maps atom_site-index to atom_site_anisotrop-index */ + elementToAnsiotrop: Int32Array +} + +namespace AtomSiteAnisotrop { + export const Descriptor: CustomPropertyDescriptor = { + name: 'atom_site_anisotrop', + cifExport: { + prefix: '', + categories: [{ + name: 'atom_site_anisotrop', + instance(ctx) { + const p = Provider.get(ctx.firstModel); + if (!p) return CifWriter.Category.Empty; + // TODO filter to write only data for elements that exist in model + return CifWriter.Category.ofTable(p.data); + } + }] + } + }; + + export const Provider = FormatPropertyProvider.create<AtomSiteAnisotrop>(Descriptor) + + export function getElementToAnsiotrop(model: Model, data: Anisotrop) { + const { atomId } = model.atomicConformation + 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)] + if (ei !== -1) elementToAnsiotrop[ei] = i + } + + return elementToAnsiotrop + } +} \ No newline at end of file diff --git a/src/mol-model-formats/structure/mmcif/assembly.ts b/src/mol-model-formats/structure/property/assembly.ts similarity index 84% rename from src/mol-model-formats/structure/mmcif/assembly.ts rename to src/mol-model-formats/structure/property/assembly.ts index 46720eb26c5bd1560c93ef14b83e696fc359540e..f253cf1edfb65483a01a8ef7ec1733b589bc0644 100644 --- a/src/mol-model-formats/structure/mmcif/assembly.ts +++ b/src/mol-model-formats/structure/property/assembly.ts @@ -9,17 +9,20 @@ import { SymmetryOperator } from '../../../mol-math/geometry/symmetry-operator' import { Assembly, OperatorGroup, OperatorGroups } from '../../../mol-model/structure/model/properties/symmetry' import { Queries as Q } from '../../../mol-model/structure' import { StructureProperties } from '../../../mol-model/structure'; -import { ModelFormat } from '../format'; -import mmCIF_Format = ModelFormat.mmCIF +import { Table } from '../../../mol-data/db'; +import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif'; -export function createAssemblies(format: mmCIF_Format): ReadonlyArray<Assembly> { - const { pdbx_struct_assembly } = format.data; +type StructAssembly = Table<mmCIF_Schema['pdbx_struct_assembly']> +type StructAssemblyGen = Table<mmCIF_Schema['pdbx_struct_assembly_gen']> +type StructOperList = Table<mmCIF_Schema['pdbx_struct_oper_list']> + +export function createAssemblies(pdbx_struct_assembly: StructAssembly, pdbx_struct_assembly_gen: StructAssemblyGen, pdbx_struct_oper_list: StructOperList): ReadonlyArray<Assembly> { if (!pdbx_struct_assembly._rowCount) return []; - const matrices = getMatrices(format); + const matrices = getMatrices(pdbx_struct_oper_list); const assemblies: Assembly[] = []; for (let i = 0; i < pdbx_struct_assembly._rowCount; i++) { - assemblies[assemblies.length] = createAssembly(format, i, matrices); + assemblies[assemblies.length] = createAssembly(pdbx_struct_assembly, pdbx_struct_assembly_gen, i, matrices); } return assemblies; } @@ -27,9 +30,7 @@ export function createAssemblies(format: mmCIF_Format): ReadonlyArray<Assembly> type Matrices = Map<string, Mat4> type Generator = { assemblyId: string, expression: string, asymIds: string[] } -function createAssembly(format: mmCIF_Format, index: number, matrices: Matrices): Assembly { - const { pdbx_struct_assembly, pdbx_struct_assembly_gen } = format.data; - +function createAssembly(pdbx_struct_assembly: StructAssembly, pdbx_struct_assembly_gen: StructAssemblyGen, index: number, matrices: Matrices): Assembly { const id = pdbx_struct_assembly.id.value(index); const details = pdbx_struct_assembly.details.value(index); const generators: Generator[] = []; @@ -70,8 +71,7 @@ function operatorGroupsProvider(generators: Generator[], matrices: Matrices): () } } -function getMatrices({ data }: mmCIF_Format): Matrices { - const { pdbx_struct_oper_list } = data; +function getMatrices(pdbx_struct_oper_list: StructOperList): Matrices { const { id, matrix, vector, _schema } = pdbx_struct_oper_list; const matrices = new Map<string, Mat4>(); diff --git a/src/mol-model-formats/structure/mmcif/bonds/comp.ts b/src/mol-model-formats/structure/property/bonds/comp.ts similarity index 51% rename from src/mol-model-formats/structure/mmcif/bonds/comp.ts rename to src/mol-model-formats/structure/property/bonds/comp.ts index 4ae1bfc330dff8550ebaf441fc0d78ff351622c3..21eafaa7f9f979a8aee88031e8d66a047a201015 100644 --- a/src/mol-model-formats/structure/mmcif/bonds/comp.ts +++ b/src/mol-model-formats/structure/property/bonds/comp.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-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> @@ -8,12 +8,14 @@ import { Model } from '../../../../mol-model/structure/model/model' import { BondType } from '../../../../mol-model/structure/model/types' import { CustomPropertyDescriptor } from '../../../../mol-model/structure'; -import { mmCIF_Database, mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif'; +import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif'; import { CifWriter } from '../../../../mol-io/writer/cif' import { Table } from '../../../../mol-data/db'; +import { FormatPropertyProvider } from '../../common/property'; export interface ComponentBond { - entries: Map<string, ComponentBond.Entry> + readonly data: Table<mmCIF_Schema['chem_comp_bond']> + readonly entries: ReadonlyMap<string, ComponentBond.Entry> } export namespace ComponentBond { @@ -24,7 +26,9 @@ export namespace ComponentBond { categories: [{ name: 'chem_comp_bond', instance(ctx) { - const chem_comp_bond = getChemCompBond(ctx.firstModel); + const p = Provider.get(ctx.firstModel); + if (!p) return CifWriter.Category.Empty; + const chem_comp_bond = p.data; if (!chem_comp_bond) return CifWriter.Category.Empty; const comp_names = ctx.structures[0].uniqueResidueNames; @@ -40,73 +44,27 @@ export namespace ComponentBond { } } - export function attachFromMmCif(model: Model): boolean { - if (model.customProperties.has(Descriptor)) return true; - if (model.sourceData.kind !== 'mmCIF') return false; - const { chem_comp_bond } = model.sourceData.data; - if (chem_comp_bond._rowCount === 0) return false; + export const Provider = FormatPropertyProvider.create<ComponentBond>(Descriptor) - model.customProperties.add(Descriptor); - model._staticPropertyData.__ComponentBondData__ = chem_comp_bond; - return true; - } - - export function attachFromExternalData(model: Model, table: mmCIF_Database['chem_comp_bond'], force = false) { - if (!force && model.customProperties.has(Descriptor)) return true; - if (model._staticPropertyData.__ComponentBondData__) delete model._staticPropertyData.__ComponentBondData__; - const chem_comp_bond = chemCompBondFromTable(model, table); - if (chem_comp_bond._rowCount === 0) return false; - - model.customProperties.add(Descriptor); - model._staticPropertyData.__ComponentBondData__ = chem_comp_bond; - return true; - } - - function chemCompBondFromTable(model: Model, table: mmCIF_Database['chem_comp_bond']): mmCIF_Database['chem_comp_bond'] { + export function chemCompBondFromTable(model: Model, table: Table<mmCIF_Schema['chem_comp_bond']>): Table<mmCIF_Schema['chem_comp_bond']> { return Table.pick(table, mmCIF_Schema.chem_comp_bond, (i: number) => { return model.properties.chemicalComponentMap.has(table.comp_id.value(i)) }) } - export class ComponentBondImpl implements ComponentBond { - entries: Map<string, ComponentBond.Entry> = new Map(); + export function getEntriesFromChemCompBond(data: Table<mmCIF_Schema['chem_comp_bond']>) { + const entries: Map<string, Entry> = new Map(); - addEntry(id: string) { + function addEntry(id: string) { let e = new Entry(id); - this.entries.set(id, e); + entries.set(id, e); return e; } - } - export class Entry { - map: Map<string, Map<string, { order: number, flags: number }>> = new Map(); - - add(a: string, b: string, order: number, flags: number, swap = true) { - let e = this.map.get(a); - if (e !== void 0) { - let f = e.get(b); - if (f === void 0) { - e.set(b, { order, flags }); - } - } else { - let map = new Map<string, { order: number, flags: number }>(); - map.set(b, { order, flags }); - this.map.set(a, map); - } + const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount } = data; - if (swap) this.add(b, a, order, flags, false); - } - - constructor(public id: string) { - } - } - - export function parseChemCompBond(data: mmCIF_Database['chem_comp_bond']): ComponentBond { - const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount: rowCount } = data; - - const compBond = new ComponentBondImpl(); - let entry = compBond.addEntry(comp_id.value(0)!); - for (let i = 0; i < rowCount; i++) { + let entry = addEntry(comp_id.value(0)!); + for (let i = 0; i < _rowCount; i++) { const id = comp_id.value(i)!; const nameA = atom_id_1.value(i)!; const nameB = atom_id_2.value(i)!; @@ -114,7 +72,7 @@ export namespace ComponentBond { const aromatic = pdbx_aromatic_flag.value(i) === 'Y'; if (entry.id !== id) { - entry = compBond.addEntry(id); + entry = addEntry(id); } let flags: number = BondType.Flag.Covalent; @@ -132,23 +90,28 @@ export namespace ComponentBond { entry.add(nameA, nameB, ord, flags); } - return compBond; + return entries } - function getChemCompBond(model: Model) { - return model._staticPropertyData.__ComponentBondData__ as mmCIF_Database['chem_comp_bond']; - } + export class Entry { + readonly map: Map<string, Map<string, { order: number, flags: number }>> = new Map(); - export const PropName = '__ComponentBond__'; - export function get(model: Model): ComponentBond | undefined { - if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName]; - if (!model.customProperties.has(Descriptor)) return void 0; + add(a: string, b: string, order: number, flags: number, swap = true) { + let e = this.map.get(a); + if (e !== void 0) { + let f = e.get(b); + if (f === void 0) { + e.set(b, { order, flags }); + } + } else { + let map = new Map<string, { order: number, flags: number }>(); + map.set(b, { order, flags }); + this.map.set(a, map); + } - const chem_comp_bond = getChemCompBond(model); - if (!chem_comp_bond) return void 0; + if (swap) this.add(b, a, order, flags, false); + } - const chemComp = parseChemCompBond(chem_comp_bond); - model._staticPropertyData[PropName] = chemComp; - return chemComp; + constructor(public readonly id: string) { } } } \ No newline at end of file diff --git a/src/mol-model-formats/structure/mmcif/bonds/index-pair.ts b/src/mol-model-formats/structure/property/bonds/index-pair.ts similarity index 54% rename from src/mol-model-formats/structure/mmcif/bonds/index-pair.ts rename to src/mol-model-formats/structure/property/bonds/index-pair.ts index 22af2913d7f403ee6a42fcf01b2408f933eff851..8cc29e892471b5d9847dffb075c82efbf1d52d9d 100644 --- a/src/mol-model-formats/structure/mmcif/bonds/index-pair.ts +++ b/src/mol-model-formats/structure/property/bonds/index-pair.ts @@ -1,13 +1,13 @@ /** - * Copyright (c) 2019 Mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 Mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Model } from '../../../../mol-model/structure/model/model' import { CustomPropertyDescriptor } from '../../../../mol-model/structure'; 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> }> @@ -27,6 +27,8 @@ export namespace IndexPairBonds { name: 'index_pair_bonds', } + export const Provider = FormatPropertyProvider.create<IndexPairBonds>(Descriptor) + export type Data = { pairs: { indexA: Column<number>, @@ -36,33 +38,11 @@ export namespace IndexPairBonds { count: number } - export function attachFromData(model: Model, data: Data): boolean { - if (model.customProperties.has(Descriptor)) return true; - - model.customProperties.add(Descriptor); - model._staticPropertyData.__IndexPairBondsData__ = data; - return true; - } - - function getIndexPairBonds(model: Model) { - return model._staticPropertyData.__IndexPairBondsData__ as Data; - } - - export const PropName = '__IndexPairBonds__'; - export function get(model: Model): IndexPairBonds | undefined { - if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName]; - if (!model.customProperties.has(Descriptor)) return void 0; - - const data = getIndexPairBonds(model); - if (!data) return void 0; + export function fromData(data: Data) { const { pairs, count } = data - const indexA = pairs.indexA.toArray() const indexB = pairs.indexB.toArray() const order = pairs.order.toArray() - - const indexPairBonds = getGraph(indexA, indexB, order, count); - model._staticPropertyData[PropName] = indexPairBonds; - return indexPairBonds; + return getGraph(indexA, indexB, order, count); } } \ No newline at end of file diff --git a/src/mol-model-formats/structure/property/bonds/struct_conn.ts b/src/mol-model-formats/structure/property/bonds/struct_conn.ts new file mode 100644 index 0000000000000000000000000000000000000000..4462ebe20da53b2081ef4ec4961a3e9f98832728 --- /dev/null +++ b/src/mol-model-formats/structure/property/bonds/struct_conn.ts @@ -0,0 +1,168 @@ +/** + * Copyright (c) 2017-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 { Model } from '../../../../mol-model/structure/model/model' +import { Structure } from '../../../../mol-model/structure' +import { BondType } from '../../../../mol-model/structure/model/types' +import { Column, Table } from '../../../../mol-data/db' +import { CustomPropertyDescriptor } from '../../../../mol-model/structure'; +import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif'; +import { SortedArray } from '../../../../mol-data/int'; +import { CifWriter } from '../../../../mol-io/writer/cif' +import { ElementIndex, ResidueIndex } from '../../../../mol-model/structure/model/indexing'; +import { getInterBondOrderFromTable } from '../../../../mol-model/structure/model/properties/atomic/bonds'; +import { FormatPropertyProvider } from '../../common/property'; + +export interface StructConn { + readonly data: Table<mmCIF_Schema['struct_conn']> + readonly byAtomIndex: Map<ElementIndex, ReadonlyArray<StructConn.Entry>> + readonly entries: ReadonlyArray<StructConn.Entry> +} + +export namespace StructConn { + export const Descriptor: CustomPropertyDescriptor = { + name: 'struct_conn', + cifExport: { + prefix: '', + categories: [{ + name: 'struct_conn', + instance(ctx) { + const p = Provider.get(ctx.firstModel); + if (!p || p.entries.length === 0) return CifWriter.Category.Empty; + + const structure = ctx.structures[0] + + const indices: number[] = []; + for (const e of p.entries) { + if (hasAtom(structure, e.partnerA.atomIndex) && + hasAtom(structure, e.partnerB.atomIndex)) { + indices[indices.length] = e.rowIndex; + } + } + + return CifWriter.Category.ofTable(p.data, indices); + } + }] + } + } + + export const Provider = FormatPropertyProvider.create<StructConn>(Descriptor) + + function hasAtom({ units }: Structure, element: ElementIndex) { + for (let i = 0, _i = units.length; i < _i; i++) { + if (SortedArray.indexOf(units[i].elements, element) >= 0) return true; + } + return false; + } + + export function getAtomIndexFromEntries(entries: StructConn['entries']) { + const m = new Map(); + for (const e of entries) { + const { partnerA: { atomIndex: iA }, partnerB: { atomIndex: iB } } = e; + if (m.has(iA)) m.get(iA)!.push(e); + else m.set(iA, [e]); + + if (m.has(iB)) m.get(iB)!.push(e); + else m.set(iB, [e]); + } + return m; + } + + export interface Entry { + rowIndex: number, + distance: number, + order: number, + flags: number, + partnerA: { residueIndex: ResidueIndex, atomIndex: ElementIndex, symmetry: string }, + partnerB: { residueIndex: ResidueIndex, atomIndex: ElementIndex, symmetry: string } + } + + export function getEntriesFromStructConn(struct_conn: Table<mmCIF_Schema['struct_conn']>, model: Model): StructConn['entries'] { + const { conn_type_id, pdbx_dist_value, pdbx_value_order } = struct_conn; + const p1 = { + label_asym_id: struct_conn.ptnr1_label_asym_id, + label_seq_id: struct_conn.ptnr1_label_seq_id, + auth_seq_id: struct_conn.ptnr1_auth_seq_id, + label_atom_id: struct_conn.ptnr1_label_atom_id, + label_alt_id: struct_conn.pdbx_ptnr1_label_alt_id, + ins_code: struct_conn.pdbx_ptnr1_PDB_ins_code, + symmetry: struct_conn.ptnr1_symmetry + }; + const p2: typeof p1 = { + label_asym_id: struct_conn.ptnr2_label_asym_id, + label_seq_id: struct_conn.ptnr2_label_seq_id, + auth_seq_id: struct_conn.ptnr2_auth_seq_id, + label_atom_id: struct_conn.ptnr2_label_atom_id, + label_alt_id: struct_conn.pdbx_ptnr2_label_alt_id, + ins_code: struct_conn.pdbx_ptnr2_PDB_ins_code, + symmetry: struct_conn.ptnr2_symmetry + }; + + const _p = (row: number, ps: typeof p1) => { + if (ps.label_asym_id.valueKind(row) !== Column.ValueKind.Present) return void 0; + const asymId = ps.label_asym_id.value(row); + const entityIndex = model.atomicHierarchy.index.findEntity(asymId); + if (entityIndex < 0) return void 0; + const residueIndex = model.atomicHierarchy.index.findResidue( + model.entities.data.id.value(entityIndex), + asymId, + ps.auth_seq_id.value(row), + ps.ins_code.value(row) + ); + if (residueIndex < 0) return void 0; + const atomName = ps.label_atom_id.value(row); + // turns out "mismat" records might not have atom name value + if (!atomName) return void 0; + const atomIndex = model.atomicHierarchy.index.findAtomOnResidue(residueIndex, atomName, ps.label_alt_id.value(row)); + if (atomIndex < 0) return void 0; + return { residueIndex, atomIndex, symmetry: ps.symmetry.value(row) }; + } + + const entries: StructConn.Entry[] = []; + for (let i = 0; i < struct_conn._rowCount; i++) { + const partnerA = _p(i, p1) + const partnerB = _p(i, p2) + if (partnerA === undefined || partnerB === undefined) continue; + + const type = conn_type_id.value(i) + const orderType = (pdbx_value_order.value(i) || '').toLowerCase(); + let flags = BondType.Flag.None; + let order = 1; + + switch (orderType) { + case 'sing': order = 1; break; + case 'doub': order = 2; break; + case 'trip': order = 3; break; + case 'quad': order = 4; break; + default: + order = getInterBondOrderFromTable( + struct_conn.ptnr1_label_comp_id.value(i), + struct_conn.ptnr1_label_atom_id.value(i), + struct_conn.ptnr2_label_comp_id.value(i), + struct_conn.ptnr2_label_atom_id.value(i) + ) + } + + switch (type) { + case 'covale': + flags = BondType.Flag.Covalent; + break; + case 'disulf': flags = BondType.Flag.Covalent | BondType.Flag.Disulfide; break; + case 'hydrog': + flags = BondType.Flag.HydrogenBond; + break; + case 'metalc': flags = BondType.Flag.MetallicCoordination; break; + } + + entries.push({ + rowIndex: i, flags, order, distance: pdbx_dist_value.value(i), partnerA, partnerB + }); + } + + return entries; + } +} \ No newline at end of file diff --git a/src/mol-model-formats/structure/mmcif/pair-restraints/cross-links.ts b/src/mol-model-formats/structure/property/pair-restraints/cross-links.ts similarity index 57% rename from src/mol-model-formats/structure/mmcif/pair-restraints/cross-links.ts rename to src/mol-model-formats/structure/property/pair-restraints/cross-links.ts index 62305d3e942cefc6f3cbef76c598aba74de7caf4..484645a3a921c46bc2aa3f63aa26766043b3569d 100644 --- a/src/mol-model-formats/structure/mmcif/pair-restraints/cross-links.ts +++ b/src/mol-model-formats/structure/property/pair-restraints/cross-links.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 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 Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -7,43 +7,39 @@ import { Model } from '../../../../mol-model/structure/model/model' import { Table } from '../../../../mol-data/db' import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif'; -import { findAtomIndexByLabelName } from '../util'; -import { Unit } from '../../../../mol-model/structure'; +import { Unit, CustomPropertyDescriptor } from '../../../../mol-model/structure'; import { ElementIndex } from '../../../../mol-model/structure/model/indexing'; +import { FormatPropertyProvider } from '../../common/property'; -function findAtomIndex(model: Model, entityId: string, asymId: string, seqId: number, atomId: string) { - if (!model.atomicHierarchy.atoms.auth_atom_id.isDefined) return -1 - const residueIndex = model.atomicHierarchy.index.findResidue(entityId, asymId, seqId) - if (residueIndex < 0) return -1 - return findAtomIndexByLabelName(model, residueIndex, atomId, '') as ElementIndex -} +export { ModelCrossLinkRestraint } -export interface IHMCrossLinkRestraint { +interface ModelCrossLinkRestraint { getIndicesByElement: (element: ElementIndex, kind: Unit.Kind) => number[] data: Table<mmCIF_Schema['ihm_cross_link_restraint']> } -export namespace IHMCrossLinkRestraint { - export const PropName = '__CrossLinkRestraint__'; - export function fromModel(model: Model): IHMCrossLinkRestraint | undefined { - if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName] +namespace ModelCrossLinkRestraint { + export const Descriptor: CustomPropertyDescriptor = { + name: 'ihm_cross_link_restraint', + // TODO cifExport + } + + export const Provider = FormatPropertyProvider.create<ModelCrossLinkRestraint>(Descriptor) - if (model.sourceData.kind !== 'mmCIF') return - const { ihm_cross_link_restraint } = model.sourceData.data; - if (!ihm_cross_link_restraint._rowCount) return + export function fromTable(table: Table<mmCIF_Schema['ihm_cross_link_restraint']>, model: Model): ModelCrossLinkRestraint { const p1 = { - entity_id: ihm_cross_link_restraint.entity_id_1, - asym_id: ihm_cross_link_restraint.asym_id_1, - seq_id: ihm_cross_link_restraint.seq_id_1, - atom_id: ihm_cross_link_restraint.atom_id_1, + entity_id: table.entity_id_1, + asym_id: table.asym_id_1, + seq_id: table.seq_id_1, + atom_id: table.atom_id_1, } const p2: typeof p1 = { - entity_id: ihm_cross_link_restraint.entity_id_2, - asym_id: ihm_cross_link_restraint.asym_id_2, - seq_id: ihm_cross_link_restraint.seq_id_2, - atom_id: ihm_cross_link_restraint.atom_id_2, + entity_id: table.entity_id_2, + asym_id: table.asym_id_2, + seq_id: table.seq_id_2, + atom_id: table.atom_id_2, } function _add(map: Map<ElementIndex, number[]>, element: ElementIndex, row: number) { @@ -57,8 +53,13 @@ export namespace IHMCrossLinkRestraint { const asymId = ps.asym_id.value(row) const seqId = ps.seq_id.value(row) - if (ihm_cross_link_restraint.model_granularity.value(row) === 'by-atom') { - const atomicElement = findAtomIndex(model, entityId, asymId, seqId, ps.atom_id.value(row)) + if (table.model_granularity.value(row) === 'by-atom') { + const atomicElement = model.atomicHierarchy.index.findAtom({ + auth_seq_id: seqId, + label_asym_id: asymId, + label_atom_id: ps.atom_id.value(row), + label_entity_id: entityId, + }) if (atomicElement >= 0) _add(atomicElementMap, atomicElement as ElementIndex, row) } else if (model.coarseHierarchy.isDefined) { const sphereElement = model.coarseHierarchy.spheres.findSequenceKey(entityId, asymId, seqId) @@ -88,20 +89,18 @@ export namespace IHMCrossLinkRestraint { const emptyIndexArray: number[] = []; - for (let i = 0; i < ihm_cross_link_restraint._rowCount; ++i) { + for (let i = 0; i < table._rowCount; ++i) { add(i, p1) add(i, p2) } - const crossLinkRestraint = { + return { getIndicesByElement: (element: ElementIndex, kind: Unit.Kind) => { const map = getMapByKind(kind) const idx = map.get(element) return idx !== undefined ? idx : emptyIndexArray }, - data: ihm_cross_link_restraint + data: table } - model._staticPropertyData[PropName] = crossLinkRestraint - return crossLinkRestraint } } \ No newline at end of file diff --git a/src/mol-model-formats/structure/mmcif/pair-restraints/predicted-contacts.ts b/src/mol-model-formats/structure/property/pair-restraints/predicted-contacts.ts similarity index 100% rename from src/mol-model-formats/structure/mmcif/pair-restraints/predicted-contacts.ts rename to src/mol-model-formats/structure/property/pair-restraints/predicted-contacts.ts diff --git a/src/mol-model-formats/structure/mmcif/secondary-structure.ts b/src/mol-model-formats/structure/property/secondary-structure.ts similarity index 77% rename from src/mol-model-formats/structure/mmcif/secondary-structure.ts rename to src/mol-model-formats/structure/property/secondary-structure.ts index 73c454a8dea08a44966f700330153503568a1043..d1be73086b1cff12372e6ad2406aa78118fa593e 100644 --- a/src/mol-model-formats/structure/mmcif/secondary-structure.ts +++ b/src/mol-model-formats/structure/property/secondary-structure.ts @@ -1,36 +1,51 @@ /** - * 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> */ -import { mmCIF_Database as mmCIF, mmCIF_Database } from '../../../mol-io/reader/cif/schema/mmcif' +import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif' import { SecondaryStructureType } from '../../../mol-model/structure/model/types'; import { AtomicHierarchy } from '../../../mol-model/structure/model/properties/atomic'; import { SecondaryStructure } from '../../../mol-model/structure/model/properties/seconday-structure'; -import { Column } from '../../../mol-data/db'; +import { Column, Table } from '../../../mol-data/db'; import { ChainIndex, ResidueIndex } from '../../../mol-model/structure/model/indexing'; +import { FormatPropertyProvider } from '../common/property'; +import { CustomPropertyDescriptor } from '../../../mol-model/structure'; -export function getSecondaryStructure(data: mmCIF_Database, hierarchy: AtomicHierarchy): SecondaryStructure { - const map: SecondaryStructureMap = new Map(); - const elements: SecondaryStructure.Element[] = [{ kind: 'none' }]; - addHelices(data.struct_conf, map, elements); - // must add Helices 1st because of 'key' value assignment. - addSheets(data.struct_sheet_range, map, data.struct_conf._rowCount, elements); +export { ModelSecondaryStructure } - const n = hierarchy.residues._rowCount - const getIndex = (rI: ResidueIndex) => rI +type StructConf = Table<mmCIF_Schema['struct_conf']> +type StructSheetRange = Table<mmCIF_Schema['struct_sheet_range']> - const secStruct: SecondaryStructureData = { - type: new Int32Array(n) as any, - key: new Int32Array(n) as any, - elements +namespace ModelSecondaryStructure { + export const Descriptor: CustomPropertyDescriptor = { + name: 'model_secondary_structure', }; - if (map.size > 0) assignSecondaryStructureRanges(hierarchy, map, secStruct); - return SecondaryStructure(secStruct.type, secStruct.key, secStruct.elements, getIndex); + export const Provider = FormatPropertyProvider.create<SecondaryStructure>(Descriptor) + + export function fromStruct(conf: StructConf, sheetRange: StructSheetRange, hierarchy: AtomicHierarchy): SecondaryStructure { + const map: SecondaryStructureMap = new Map(); + const elements: SecondaryStructure.Element[] = [{ kind: 'none' }]; + addHelices(conf, map, elements); + // must add Helices 1st because of 'key' value assignment. + addSheets(sheetRange, map, conf._rowCount, elements); + + const n = hierarchy.residues._rowCount + const getIndex = (rI: ResidueIndex) => rI + + const secStruct: SecondaryStructureData = { + type: new Int32Array(n) as any, + key: new Int32Array(n) as any, + elements + }; + + if (map.size > 0) assignSecondaryStructureRanges(hierarchy, map, secStruct); + return SecondaryStructure(secStruct.type, secStruct.key, secStruct.elements, getIndex); + } } type SecondaryStructureEntry = { @@ -44,7 +59,7 @@ type SecondaryStructureEntry = { type SecondaryStructureMap = Map<string, Map<number, SecondaryStructureEntry[]>> type SecondaryStructureData = { type: SecondaryStructureType[], key: number[], elements: SecondaryStructure.Element[] } -function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap, elements: SecondaryStructure.Element[]) { +function addHelices(cat: StructConf, map: SecondaryStructureMap, elements: SecondaryStructure.Element[]) { if (!cat._rowCount) return; const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat; @@ -90,7 +105,7 @@ function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap, eleme } } -function addSheets(cat: mmCIF['struct_sheet_range'], map: SecondaryStructureMap, sheetCount: number, elements: SecondaryStructure.Element[]) { +function addSheets(cat: StructSheetRange, map: SecondaryStructureMap, sheetCount: number, elements: SecondaryStructure.Element[]) { if (!cat._rowCount) return; const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat; diff --git a/src/mol-model-formats/structure/property/symmetry.ts b/src/mol-model-formats/structure/property/symmetry.ts new file mode 100644 index 0000000000000000000000000000000000000000..487509249aefa9567915ca4b994aa715cf4e7f6d --- /dev/null +++ b/src/mol-model-formats/structure/property/symmetry.ts @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2017-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 { 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 { createAssemblies } from './assembly'; +import { CustomPropertyDescriptor } from '../../../mol-model/structure'; +import { FormatPropertyProvider } from '../common/property'; +import { Table } from '../../../mol-data/db'; + +export { ModelSymmetry } + +namespace ModelSymmetry { + export const Descriptor: CustomPropertyDescriptor = { + name: 'model_symmetry', + }; + + export const Provider = FormatPropertyProvider.create<_ModelSymmetry>(Descriptor) + + type Data = { + symmetry: Table<mmCIF_Schema['symmetry']> + cell: Table<mmCIF_Schema['cell']> + struct_ncs_oper: Table<mmCIF_Schema['struct_ncs_oper']> + atom_sites: Table<mmCIF_Schema['atom_sites']> + pdbx_struct_assembly: Table<mmCIF_Schema['pdbx_struct_assembly']> + pdbx_struct_assembly_gen: Table<mmCIF_Schema['pdbx_struct_assembly_gen']> + pdbx_struct_oper_list: Table<mmCIF_Schema['pdbx_struct_oper_list']> + } + + export function fromData(data: Data): _ModelSymmetry { + 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); + return { assemblies, spacegroup, isNonStandardCrytalFrame, ncsOperators: getNcsOperators(data.struct_ncs_oper) }; + } +} + +function checkNonStandardCrystalFrame(atom_sites: Table<mmCIF_Schema['atom_sites']>, spacegroup: Spacegroup) { + if (atom_sites._rowCount === 0) return false; + // TODO: parse atom_sites transform and check if it corresponds to the toFractional matrix + return false; +} + +function getSpacegroupNameOrNumber(symmetry: Table<mmCIF_Schema['symmetry']>) { + const groupNumber = symmetry['Int_Tables_number'].value(0); + const groupName = symmetry['space_group_name_H-M'].value(0); + if (!symmetry['Int_Tables_number'].isDefined) return groupName + if (!symmetry['space_group_name_H-M'].isDefined) return groupNumber + return groupName +} + +function getSpacegroup(symmetry: Table<mmCIF_Schema['symmetry']>, cell: Table<mmCIF_Schema['cell']>): Spacegroup { + if (symmetry._rowCount === 0 || cell._rowCount === 0) return Spacegroup.ZeroP1; + const nameOrNumber = getSpacegroupNameOrNumber(symmetry) + 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.create(spaceCell); +} + +function getNcsOperators(struct_ncs_oper: Table<mmCIF_Schema['struct_ncs_oper']>) { + if (struct_ncs_oper._rowCount === 0) return void 0; + const { id, matrix, vector } = struct_ncs_oper; + + const matrixSpace = mmCIF_Schema.struct_ncs_oper.matrix.space, vectorSpace = mmCIF_Schema.struct_ncs_oper.vector.space; + + const opers: SymmetryOperator[] = []; + for (let i = 0; i < struct_ncs_oper._rowCount; i++) { + const m = Tensor.toMat3(Mat3(), matrixSpace, matrix.value(i)); + const v = Tensor.toVec3(Vec3(), vectorSpace, vector.value(i)); + if (!SymmetryOperator.checkIfRotationAndTranslation(m, v)) continue; + // ignore non-identity 'given' NCS operators + if (struct_ncs_oper.code.value(i) === 'given' && !Mat3.isIdentity(m) && !Vec3.isZero(v)) continue; + const ncsId = id.value(i) + opers[opers.length] = SymmetryOperator.ofRotationAndOffset(`ncs_${ncsId}`, m, v, ncsId); + } + return opers; +} \ No newline at end of file diff --git a/src/mol-model-formats/structure/psf.ts b/src/mol-model-formats/structure/psf.ts index ab4eae3e12a7c9c2a7d441f554fbe9d9b80dc59d..88e5a53d64a8e3f6dfdf798b15f8e61f580dccbf 100644 --- a/src/mol-model-formats/structure/psf.ts +++ b/src/mol-model-formats/structure/psf.ts @@ -1,26 +1,24 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { PsfFile } from '../../mol-io/reader/psf/parser'; -import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif'; -import { Column } from '../../mol-data/db'; +import { Column, Table } from '../../mol-data/db'; import { EntityBuilder } from './common/entity'; import { ComponentBuilder } from './common/component'; -import { CifCategory, CifField } from '../../mol-io/reader/cif'; import { guessElementSymbolString } from './util'; import { MoleculeType, getMoleculeType } from '../../mol-model/structure/model/types'; import { getChainId } from './common/util'; import { Task } from '../../mol-task'; import { ModelFormat } from './format'; import { Topology } from '../../mol-model/structure/topology/topology'; +import { createBasic, BasicSchema } from './basic/schema'; -// TODO: shares most of the code with ./gro.ts#getCategories -function getCategories(atoms: PsfFile['atoms']) { - const auth_atom_id = CifField.ofColumn(atoms.atomName) - const auth_comp_id = CifField.ofColumn(atoms.residueName) +function getBasic(atoms: PsfFile['atoms']) { + const auth_atom_id = atoms.atomName + const auth_comp_id = atoms.residueName const entityIds = new Array<string>(atoms.count) const asymIds = new Array<string>(atoms.count) @@ -62,57 +60,54 @@ function getCategories(atoms: PsfFile['atoms']) { ids[i] = i } - const auth_asym_id = CifField.ofColumn(Column.ofStringArray(asymIds)) + const auth_asym_id = Column.ofStringArray(asymIds) - const atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = { + const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, { auth_asym_id, auth_atom_id, auth_comp_id, - auth_seq_id: CifField.ofColumn(atoms.residueId), - B_iso_or_equiv: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)), - Cartn_x: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)), - Cartn_y: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)), - Cartn_z: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)), - group_PDB: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)), - id: CifField.ofColumn(Column.ofIntArray(ids)), - - label_alt_id: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)), + auth_seq_id: atoms.residueId, + id: Column.ofIntArray(ids), label_asym_id: auth_asym_id, label_atom_id: auth_atom_id, label_comp_id: auth_comp_id, - label_seq_id: CifField.ofColumn(Column.ofIntArray(seqIds)), - label_entity_id: CifField.ofColumn(Column.ofStringArray(entityIds)), + label_seq_id: Column.ofIntArray(seqIds), + label_entity_id: Column.ofStringArray(entityIds), - occupancy: CifField.ofColumn(Column.ofConst(1, atoms.count, Column.Schema.float)), - type_symbol: CifField.ofStrings(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))), + occupancy: Column.ofConst(1, atoms.count, Column.Schema.float), + type_symbol: Column.ofStringArray(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))), - pdbx_PDB_ins_code: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)), - pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst('1', atoms.count, Column.Schema.str)), - } + pdbx_PDB_model_num: Column.ofConst(1, atoms.count, Column.Schema.int), + }, atoms.count) - return { - entity: entityBuilder.getEntityCategory(), - chem_comp: componentBuilder.getChemCompCategory(), - atom_site: CifCategory.ofFields('atom_site', atom_site) - } + return createBasic({ + entity: entityBuilder.getEntityTable(), + chem_comp: componentBuilder.getChemCompTable(), + atom_site + }) } -function psfToMmCif(psf: PsfFile) { - const categories = getCategories(psf.atoms) +// + +export { PsfFormat } - return { - header: psf.id, - categoryNames: Object.keys(categories), - categories - }; +type PsfFormat = ModelFormat<PsfFile> + +namespace PsfFormat { + export function is(x: ModelFormat): x is PsfFormat { + return x.kind === 'psf' + } + + export function fromPsf(psf: PsfFile): PsfFormat { + return { kind: 'psf', name: psf.id, data: psf }; + } } export function topologyFromPsf(psf: PsfFile): Task<Topology> { return Task.create('Parse PSF', async ctx => { - const label = psf.id - const cif = psfToMmCif(psf); - const format = ModelFormat.mmCIF(cif); + const format = PsfFormat.fromPsf(psf); + const basic = getBasic(psf.atoms) const { atomIdA, atomIdB } = psf.bonds @@ -130,6 +125,6 @@ export function topologyFromPsf(psf: PsfFile): Task<Topology> { order: Column.ofConst(1, psf.bonds.count, Column.Schema.int) } - return Topology.create(label, format, bonds) + return Topology.create(psf.id, basic, bonds, format) }) } \ No newline at end of file diff --git a/src/mol-model-props/common/custom-property.ts b/src/mol-model-props/common/custom-property.ts index f8bc99821856b6d095ca2f86d0dcc0aabeeeaf46..24dbbd7a9adb19634b9a79184ca97814aa152687 100644 --- a/src/mol-model-props/common/custom-property.ts +++ b/src/mol-model-props/common/custom-property.ts @@ -41,21 +41,32 @@ namespace CustomProperty { /** Get params for all applicable property providers */ getParams(data?: Data) { - const params: PD.Params = {} + const propertiesParams: PD.Params = {} + const autoAttachOptions: [string, string][] = [] + const autoAttachDefault: string[] = [] if (data) { - const values = this.providers.values(); + const values = this.providers.values(); while (true) { const v = values.next() if (v.done) break + const provider = v.value if (!provider.isApplicable(data)) continue - params[provider.descriptor.name] = PD.Group({ - autoAttach: PD.Boolean(this.defaultAutoAttachValues.get(provider.descriptor.name)!), - ...provider.getParams(data), - }, { label: v.value.label }) + + autoAttachOptions.push([provider.descriptor.name, provider.label]) + if (this.defaultAutoAttachValues.get(provider.descriptor.name)) { + autoAttachDefault.push(provider.descriptor.name) + } + + propertiesParams[provider.descriptor.name] = PD.Group({ + ...provider.getParams(data) + }, { label: provider.label }) } } - return params + return { + autoAttach: PD.MultiSelect(autoAttachDefault, autoAttachOptions), + properties: PD.Group(propertiesParams, { isFlat: true }) + } } setDefaultAutoAttach(name: string, value: boolean) { diff --git a/src/mol-model-props/common/wrapper.ts b/src/mol-model-props/common/wrapper.ts index 21c57e124e18ad5ab3193eb9a67774cc8a0abd51..7f3523515dbfc88602fe798e05c7a6c79328b8f0 100644 --- a/src/mol-model-props/common/wrapper.ts +++ b/src/mol-model-props/common/wrapper.ts @@ -7,6 +7,7 @@ import { CifWriter } from '../../mol-io/writer/cif'; import { Model } from '../../mol-model/structure'; import { dateToUtcString } from '../../mol-util/date'; +import { MmcifFormat } from '../../mol-model-formats/structure/mmcif'; interface PropertyWrapper<Data> { info: PropertyWrapper.Info, @@ -40,11 +41,11 @@ namespace PropertyWrapper { ]; export function tryGetInfoFromCif(categoryName: string, model: Model): Info | undefined { - if (model.sourceData.kind !== 'mmCIF' || !model.sourceData.frame.categoryNames.includes(categoryName)) { + if (!MmcifFormat.is(model.sourceData) || !model.sourceData.data.frame.categoryNames.includes(categoryName)) { return; } - const timestampField = model.sourceData.frame.categories[categoryName].getField('updated_datetime_utc'); + const timestampField = model.sourceData.data.frame.categories[categoryName].getField('updated_datetime_utc'); if (!timestampField || timestampField.rowCount === 0) return; return { timestamp_utc: timestampField.str(0) || dateToUtcString(new Date()) }; diff --git a/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/area.ts b/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/area.ts index 5097af98adc5d27078e239c9aa1cb1e580bd6aaf..fd93549b530d56b99679fc68644cab989513a5c6 100644 --- a/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/area.ts +++ b/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/area.ts @@ -31,10 +31,11 @@ export async function computeArea(runtime: RuntimeContext, ctx: ShrakeRupleyCont const aPos = Vec3(); const bPos = Vec3(); const testPoint = Vec3(); -const aLoc = StructureElement.Location.create() -const bLoc = StructureElement.Location.create() +const aLoc = StructureElement.Location.create(void 0 as any) +const bLoc = StructureElement.Location.create(void 0 as any) function setLocation(l: StructureElement.Location, structure: Structure, serialIndex: number) { + l.structure = structure; l.unit = structure.units[structure.serialMapping.unitIndices[serialIndex]] l.element = structure.serialMapping.elementIndices[serialIndex] return l @@ -63,7 +64,7 @@ function computeRange(ctx: ShrakeRupleyContext, begin: number, end: number) { const neighbors = []; // TODO reuse for (let iI = 0; iI < count; ++iI) { const bUnit = units[iI] - StructureElement.Location.set(bLoc, bUnit, bUnit.elements[indices[iI]]) + StructureElement.Location.set(bLoc, ctx.structure, bUnit, bUnit.elements[indices[iI]]) const bI = cumulativeUnitElementCount[unitIndexMap.get(bUnit.id)] + indices[iI] const radius2 = VdWLookup[atomRadiusType[bI]]; diff --git a/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/radii.ts b/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/radii.ts index 32520eda0599b1296a6edab429419d020a05a9ab..cc9ac3f5a0ad31f4d8f69b7769c9622636bbf0c7 100644 --- a/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/radii.ts +++ b/src/mol-model-props/computed/accessible-surface-area/shrake-rupley/radii.ts @@ -12,7 +12,7 @@ import { VdwRadius } from '../../../../mol-model/structure/model/properties/atom import { StructureElement, StructureProperties } from '../../../../mol-model/structure/structure'; import { getElementMoleculeType } from '../../../../mol-model/structure/util'; -const l = StructureElement.Location.create() +const l = StructureElement.Location.create(void 0) export function assignRadiusForHeavyAtoms(ctx: ShrakeRupleyContext) { const { label_comp_id, key } = StructureProperties.residue @@ -23,6 +23,7 @@ export function assignRadiusForHeavyAtoms(ctx: ShrakeRupleyContext) { let residueIdx = 0 let serialResidueIdx = -1 + l.structure = structure; for (let i = 0, m = 0, il = structure.units.length; i < il; ++i) { const unit = structure.units[i] const { elements } = unit diff --git a/src/mol-model-props/computed/interactions/interactions.ts b/src/mol-model-props/computed/interactions/interactions.ts index c7e1e409bdf03c41522bd470ca22bf5c163bf416..881163733e3549460322b933d2297b734621be20 100644 --- a/src/mol-model-props/computed/interactions/interactions.ts +++ b/src/mol-model-props/computed/interactions/interactions.ts @@ -39,6 +39,7 @@ interface Interactions { namespace Interactions { export interface Element { + structure: Structure, unitA: Unit /** Index into features of unitA */ indexA: Features.FeatureIndex @@ -48,8 +49,9 @@ namespace Interactions { } export interface Location extends DataLocation<Interactions, Element> {} - export function Location(interactions: Interactions, unitA?: Unit, indexA?: Features.FeatureIndex, unitB?: Unit, indexB?: Features.FeatureIndex): Location { - return DataLocation('interactions', interactions, { unitA: unitA as any, indexA: indexA as any, unitB: unitB as any, indexB: indexB as any }); + export function Location(interactions: Interactions, structure: Structure, unitA?: Unit, indexA?: Features.FeatureIndex, unitB?: Unit, indexB?: Features.FeatureIndex): Location { + return DataLocation('interactions', interactions, + { structure: structure as any, unitA: unitA as any, indexA: indexA as any, unitB: unitB as any, indexB: indexB as any }); } export function isLocation(x: any): x is Location { @@ -87,7 +89,9 @@ namespace Interactions { export interface Loci extends DataLoci<StructureInteractions, Element> { } export function Loci(structure: Structure, interactions: Interactions, elements: ReadonlyArray<Element>): Loci { - return DataLoci('interactions', { structure, interactions }, elements, (boundingSphere) => getBoundingSphere(interactions, elements, boundingSphere), () => getLabel(interactions, elements)); + return DataLoci('interactions', { structure, interactions }, elements, + (boundingSphere) => getBoundingSphere(interactions, elements, boundingSphere), + () => getLabel(structure, interactions, elements)); } export function isLoci(x: any): x is Loci { @@ -103,7 +107,7 @@ namespace Interactions { }, boundingSphere) } - export function getLabel(interactions: Interactions, elements: ReadonlyArray<Element>) { + export function getLabel(structure: Structure, interactions: Interactions, elements: ReadonlyArray<Element>) { const element = elements[0] if (element === undefined) return '' const { unitA, indexA, unitB, indexB } = element @@ -116,7 +120,7 @@ namespace Interactions { } return [ _label(interactions, element), - bondLabel(Bond.Location(unitA, mA[oA[indexA]], unitB, mB[oB[indexB]]), options) + bondLabel(Bond.Location(structure, unitA, mA[oA[indexA]], structure, unitB, mB[oB[indexB]]), options) ].join('</br>') } } diff --git a/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts b/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts index 8717cb985abc479070db186c5859edb34faca488..a13a95720688fd97ff2f042fa9ad144fb688971e 100644 --- a/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts +++ b/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts @@ -21,7 +21,7 @@ import { InteractionsProvider } from '../interactions'; import { LocationIterator } from '../../../mol-geo/util/location-iterator'; import { InteractionFlag } from '../interactions/common'; -const tmpLoc = StructureElement.Location.create() +const tmpLoc = StructureElement.Location.create(void 0) function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InteractionsInterUnitParams>, mesh?: Mesh) { if (!structure.hasAtomic) return Mesh.createEmpty(mesh) @@ -49,6 +49,7 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S radius: (edgeIndex: number) => { const b = edges[edgeIndex] const fA = unitsFeatures.get(b.unitA.id) + tmpLoc.structure = structure; tmpLoc.unit = b.unitA tmpLoc.element = b.unitA.elements[fA.members[fA.offsets[b.indexA]]] const sizeA = theme.size.size(tmpLoc) @@ -100,8 +101,8 @@ function getInteractionLoci(pickingId: PickingId, structure: Structure, id: numb const interactions = InteractionsProvider.get(structure).value! const c = interactions.contacts.edges[groupId] return Interactions.Loci(structure, interactions, [ - { unitA: c.unitA, indexA: c.indexA, unitB: c.unitB, indexB: c.indexB }, - { unitA: c.unitB, indexA: c.indexB, unitB: c.unitA, indexB: c.indexA }, + { structure, unitA: c.unitA, indexA: c.indexA, unitB: c.unitB, indexB: c.indexB }, + { structure, unitA: c.unitB, indexA: c.indexB, unitB: c.unitA, indexB: c.indexA }, ]) } return EmptyLoci @@ -130,7 +131,7 @@ function createInteractionsIterator(structure: Structure): LocationIterator { const { contacts } = interactions const groupCount = contacts.edgeCount const instanceCount = 1 - const location = Interactions.Location(interactions) + const location = Interactions.Location(interactions, structure) const { element } = location const getLocation = (groupIndex: number) => { const c = contacts.edges[groupIndex] diff --git a/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts b/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts index 6626f248950e8526023b5aaff2ef1bc10ae73d55..66a03f6ac7ee24d281c2251051e40bbf4e9a22ba 100644 --- a/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts +++ b/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts @@ -24,7 +24,7 @@ import { InteractionFlag } from '../interactions/common'; async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<InteractionsIntraUnitParams>, mesh?: Mesh) { if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh) - const location = StructureElement.Location.create(unit) + const location = StructureElement.Location.create(structure, unit) const interactions = InteractionsProvider.get(structure).value! const features = interactions.unitsFeatures.get(unit.id) @@ -97,8 +97,8 @@ function getInteractionLoci(pickingId: PickingId, structureGroup: StructureGroup const interactions = InteractionsProvider.get(structure).value! const { a, b } = interactions.unitsContacts.get(unit.id) return Interactions.Loci(structure, interactions, [ - { unitA: unit, indexA: a[groupId], unitB: unit, indexB: b[groupId] }, - { unitA: unit, indexA: b[groupId], unitB: unit, indexB: a[groupId] }, + { structure, unitA: unit, indexA: a[groupId], unitB: unit, indexB: b[groupId] }, + { structure, unitA: unit, indexA: b[groupId], unitB: unit, indexB: a[groupId] }, ]) } return EmptyLoci @@ -134,7 +134,7 @@ function createInteractionsIterator(structureGroup: StructureGroup): LocationIte const contacts = interactions.unitsContacts.get(unit.id) const groupCount = contacts.edgeCount * 2 const instanceCount = group.units.length - const location = Interactions.Location(interactions) + const location = Interactions.Location(interactions, structure) const { element } = location const getLocation = (groupIndex: number, instanceIndex: number) => { const instanceUnit = group.units[instanceIndex] diff --git a/src/mol-model-props/computed/secondary-structure.ts b/src/mol-model-props/computed/secondary-structure.ts index 7b875d34705a5c92c46020ec173b3ffdc526f5df..d80c58b383b9a8725daed90eb9632203986e2f17 100644 --- a/src/mol-model-props/computed/secondary-structure.ts +++ b/src/mol-model-props/computed/secondary-structure.ts @@ -11,6 +11,8 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { Unit } from '../../mol-model/structure/structure'; import { CustomStructureProperty } from '../common/custom-structure-property'; import { CustomProperty } from '../common/custom-property'; +import { ModelSecondaryStructure } from '../../mol-model-formats/structure/property/secondary-structure'; +import { MmcifFormat } from '../../mol-model-formats/structure/mmcif'; function getSecondaryStructureParams(data?: Structure) { let defaultType = 'mmcif' as 'mmcif' | 'dssp' @@ -18,10 +20,10 @@ function getSecondaryStructureParams(data?: Structure) { defaultType = 'dssp' for (let i = 0, il = data.models.length; i < il; ++i) { const m = data.models[i] - if (m.sourceData.kind === 'mmCIF') { - if (data.model.sourceData.data.struct_conf.id.isDefined || - data.model.sourceData.data.struct_sheet_range.id.isDefined || - data.model.sourceData.data.database_2.database_id.isDefined + if (MmcifFormat.is(m.sourceData)) { + if (m.sourceData.data.db.struct_conf.id.isDefined || + m.sourceData.data.db.struct_sheet_range.id.isDefined || + m.sourceData.data.db.database_2.database_id.isDefined ) { // if there is any secondary structure definition given or if there is // an archival model, don't calculate dssp by default @@ -83,7 +85,10 @@ async function computeMmcif(structure: Structure): Promise<SecondaryStructureVal for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) { const u = structure.unitSymmetryGroups[i].units[0] if (Unit.isAtomic(u)) { - map.set(u.invariantId, u.model.properties.secondaryStructure) + const secondaryStructure = ModelSecondaryStructure.Provider.get(u.model) + if (secondaryStructure) { + map.set(u.invariantId, secondaryStructure) + } } } return map diff --git a/src/mol-model-props/pdbe/preferred-assembly.ts b/src/mol-model-props/pdbe/preferred-assembly.ts index 9be69ce401bd1bbc4fd2847f97f3af0e7cd321c4..ebc7fb081c24c9e50396450f84618f0a06a09b99 100644 --- a/src/mol-model-props/pdbe/preferred-assembly.ts +++ b/src/mol-model-props/pdbe/preferred-assembly.ts @@ -8,13 +8,15 @@ import { Column, Table } from '../../mol-data/db'; import { toTable } from '../../mol-io/reader/cif/schema'; import { CifWriter } from '../../mol-io/writer/cif'; import { Model, CustomPropertyDescriptor } from '../../mol-model/structure'; +import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry'; +import { MmcifFormat } from '../../mol-model-formats/structure/mmcif'; export namespace PDBePreferredAssembly { export type Property = string export function getFirstFromModel(model: Model): Property { - const asm = model.symmetry.assemblies; - return asm.length ? asm[0].id : ''; + const symmetry = ModelSymmetry.Provider.get(model) + return symmetry?.assemblies.length ? symmetry.assemblies[0].id : ''; } export function get(model: Model): Property { @@ -46,8 +48,8 @@ export namespace PDBePreferredAssembly { }); function fromCifData(model: Model): string | undefined { - if (model.sourceData.kind !== 'mmCIF') return void 0; - const cat = model.sourceData.frame.categories.pdbe_preferred_assembly; + if (!MmcifFormat.is(model.sourceData)) return void 0; + const cat = model.sourceData.data.frame.categories.pdbe_preferred_assembly; if (!cat) return void 0; return toTable(Schema.pdbe_preferred_assembly, cat).assembly_id.value(0) || getFirstFromModel(model); } diff --git a/src/mol-model-props/pdbe/struct-ref-domain.ts b/src/mol-model-props/pdbe/struct-ref-domain.ts index 54a31131e8e6ecd75134ea49a17227dbda57b06b..db37fee7e8412b3d06e7f5e587aea9822483ebe9 100644 --- a/src/mol-model-props/pdbe/struct-ref-domain.ts +++ b/src/mol-model-props/pdbe/struct-ref-domain.ts @@ -9,6 +9,7 @@ import { toTable } from '../../mol-io/reader/cif/schema'; import { CifWriter } from '../../mol-io/writer/cif'; import { Model, CustomPropertyDescriptor } from '../../mol-model/structure'; import { PropertyWrapper } from '../common/wrapper'; +import { MmcifFormat } from '../../mol-model-formats/structure/mmcif'; export namespace PDBeStructRefDomain { export type Property = PropertyWrapper<Table<Schema['pdbe_struct_ref_domain']> | undefined> @@ -57,8 +58,8 @@ export namespace PDBeStructRefDomain { }); function fromCifData(model: Model): Property['data'] { - if (model.sourceData.kind !== 'mmCIF') return void 0; - const cat = model.sourceData.frame.categories.pdbe_struct_ref_domain; + if (!MmcifFormat.is(model.sourceData)) return void 0; + const cat = model.sourceData.data.frame.categories.pdbe_struct_ref_domain; if (!cat) return void 0; return toTable(Schema.pdbe_struct_ref_domain, cat); } diff --git a/src/mol-model-props/pdbe/structure-quality-report.ts b/src/mol-model-props/pdbe/structure-quality-report.ts index b956250aadcfc613462e4e2b0ce67de22cefc3da..e153de014ba3749c37f0700edb55b9f0a01698b3 100644 --- a/src/mol-model-props/pdbe/structure-quality-report.ts +++ b/src/mol-model-props/pdbe/structure-quality-report.ts @@ -11,7 +11,7 @@ import { mmCIF_residueId_schema } from '../../mol-io/reader/cif/schema/mmcif-ext import { CifWriter } from '../../mol-io/writer/cif'; import { Model, CustomPropertyDescriptor, ResidueIndex, Unit, IndexedCustomProperty } from '../../mol-model/structure'; import { residueIdFields } from '../../mol-model/structure/export/categories/atom_site'; -import { StructureElement, CifExportContext } from '../../mol-model/structure/structure'; +import { StructureElement, CifExportContext, Structure } from '../../mol-model/structure/structure'; import { CustomPropSymbol } from '../../mol-script/language/symbol'; import Type from '../../mol-script/language/type'; import { QuerySymbolRuntime } from '../../mol-script/runtime/query/compiler'; @@ -19,10 +19,15 @@ import { PropertyWrapper } from '../common/wrapper'; import { CustomModelProperty } from '../common/custom-model-property'; import { ParamDefinition as PD } from '../../mol-util/param-definition' import { CustomProperty } from '../common/custom-property'; +import { arraySetAdd } from '../../mol-util/array'; +import { MmcifFormat } from '../../mol-model-formats/structure/mmcif'; export { StructureQualityReport } -type StructureQualityReport = PropertyWrapper<IndexedCustomProperty.Residue<string[]> | undefined> +type StructureQualityReport = PropertyWrapper<{ + issues: IndexedCustomProperty.Residue<string[]>, + issueTypes: string[] +}| undefined> namespace StructureQualityReport { export const DefaultServerUrl = 'https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/' @@ -33,8 +38,8 @@ namespace StructureQualityReport { export function isApplicable(model?: Model): boolean { return ( !!model && - model.sourceData.kind === 'mmCIF' && - (model.sourceData.data.database_2.database_id.isDefined || + MmcifFormat.is(model.sourceData) && + (model.sourceData.data.db.database_2.database_id.isDefined || model.entryId.length === 4) ) } @@ -88,14 +93,21 @@ namespace StructureQualityReport { const prop = StructureQualityReportProvider.get(e.unit.model).value; if (!prop || !prop.data) return _emptyArray; const rI = e.unit.residueIndex[e.element]; - return prop.data.has(rI) ? prop.data.get(rI)! : _emptyArray; + return prop.data.issues.has(rI) ? prop.data.issues.get(rI)! : _emptyArray; + } + + export function getIssueTypes(structure?: Structure) { + if (!structure) return _emptyArray; + const prop = StructureQualityReportProvider.get(structure.models[0]).value; + if (!prop || !prop.data) return _emptyArray; + return prop.data.issueTypes; } function getCifData(model: Model) { - if (model.sourceData.kind !== 'mmCIF') throw new Error('Data format must be mmCIF.'); + if (!MmcifFormat.is(model.sourceData)) throw new Error('Data format must be mmCIF.'); return { - residues: toTable(Schema.pdbe_structure_quality_report_issues, model.sourceData.frame.categories.pdbe_structure_quality_report_issues), - groups: toTable(Schema.pdbe_structure_quality_report_issue_types, model.sourceData.frame.categories.pdbe_structure_quality_report_issue_types), + residues: toTable(Schema.pdbe_structure_quality_report_issues, model.sourceData.data.frame.categories.pdbe_structure_quality_report_issues), + groups: toTable(Schema.pdbe_structure_quality_report_issue_types, model.sourceData.data.frame.categories.pdbe_structure_quality_report_issue_types), } } } @@ -174,7 +186,7 @@ function createExportContext(ctx: CifExportContext): ReportExportContext { if (prop) info = prop.info; if (!prop || !prop.data) continue; - const { elements, property } = prop.data.getElements(s); + const { elements, property } = prop.data.issues.getElements(s); if (elements.length === 0) continue; const elementGroupId: number[] = []; @@ -205,6 +217,8 @@ function createIssueMapFromJson(modelData: Model, data: any): StructureQualityRe const ret = new Map<ResidueIndex, string[]>(); if (!data.molecules) return; + const issueTypes: string[] = []; + for (const entity of data.molecules) { const entity_id = entity.entity_id.toString(); for (const chain of entity.chains) { @@ -217,12 +231,19 @@ function createIssueMapFromJson(modelData: Model, data: any): StructureQualityRe const auth_seq_id = residue.author_residue_number, ins_code = residue.author_insertion_code || ''; const idx = modelData.atomicHierarchy.index.findResidue(entity_id, asym_id, auth_seq_id, ins_code); ret.set(idx, residue.outlier_types); + + for (const t of residue.outlier_types) { + arraySetAdd(issueTypes, t); + } } } } } - return IndexedCustomProperty.fromResidueMap(ret); + return { + issues: IndexedCustomProperty.fromResidueMap(ret), + issueTypes + }; } function createIssueMapFromCif(modelData: Model, @@ -240,7 +261,17 @@ function createIssueMapFromCif(modelData: Model, ret.set(idx, groups.get(issue_type_group_id.value(i))!); } - return IndexedCustomProperty.fromResidueMap(ret); + const issueTypes: string[] = []; + groups.forEach(issues => { + for (const t of issues) { + arraySetAdd(issueTypes, t); + } + }) + + return { + issues: IndexedCustomProperty.fromResidueMap(ret), + issueTypes + }; } function parseIssueTypes(groupData: Table<typeof StructureQualityReport.Schema.pdbe_structure_quality_report_issue_types>): Map<number, string[]> { diff --git a/src/mol-model-props/pdbe/themes/structure-quality-report.ts b/src/mol-model-props/pdbe/themes/structure-quality-report.ts index f84a18744b05dc1ca7056b5a02d67999ef2ff035..e3296bab946d7dc478f50b5b20c3cfe05a533ef4 100644 --- a/src/mol-model-props/pdbe/themes/structure-quality-report.ts +++ b/src/mol-model-props/pdbe/themes/structure-quality-report.ts @@ -11,6 +11,8 @@ import { ColorTheme, LocationColor } from '../../../mol-theme/color'; import { ThemeDataContext } from '../../../mol-theme/theme'; import { Color } from '../../../mol-util/color'; import { TableLegend } from '../../../mol-util/legend'; +import { ParamDefinition as PD } from '../../../mol-util/param-definition'; +import { CustomProperty } from '../../common/custom-property'; const ValidationColors = [ Color.fromRgb(170, 170, 170), // not applicable @@ -28,16 +30,38 @@ const ValidationColorTable: [string, Color][] = [ ['Not Applicable', ValidationColors[9]] ] -export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: {}): ColorTheme<{}> { +export const StructureQualityReportColorThemeParams = { + type: PD.MappedStatic('issue-count', { + 'issue-count': PD.Group({}), + 'specific-issue': PD.Group({ + kind: PD.Text() + }) + }) +}; + +type Params = typeof StructureQualityReportColorThemeParams + +export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: PD.Values<Params>): ColorTheme<Params> { let color: LocationColor if (ctx.structure && !ctx.structure.isEmpty && ctx.structure.models[0].customProperties.has(StructureQualityReportProvider.descriptor)) { const getIssues = StructureQualityReport.getIssues; - color = (location: Location) => { - if (StructureElement.Location.is(location)) { - return ValidationColors[Math.min(3, getIssues(location).length) + 1]; + + if (props.type.name === 'issue-count') { + color = (location: Location) => { + if (StructureElement.Location.is(location)) { + return ValidationColors[Math.min(3, getIssues(location).length) + 1]; + } + return ValidationColors[0]; + } + } else { + const issue = props.type.params.kind; + color = (location: Location) => { + if (StructureElement.Location.is(location) && getIssues(location).indexOf(issue) >= 0) { + return ValidationColors[4]; + } + return ValidationColors[0]; } - return ValidationColors[0]; } } else { color = () => ValidationColors[0]; @@ -48,7 +72,36 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: { granularity: 'group', color: color, props: props, - description: 'Assigns residue colors according to the number of issues in the PDBe Validation Report.', + description: 'Assigns residue colors according to the number of issues or a specific issue in the PDBe Validation Report.', legend: TableLegend(ValidationColorTable) } +} + +export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Params> = { + label: 'PDBe Structure Quality Report', + factory: StructureQualityReportColorTheme, + getParams: ctx => { + const issueTypes = StructureQualityReport.getIssueTypes(ctx.structure); + if (issueTypes.length === 0) { + return { + type: PD.MappedStatic('issue-count', { + 'issue-count': PD.Group({}) + }) + }; + } + + return { + type: PD.MappedStatic('issue-count', { + 'issue-count': PD.Group({}), + 'specific-issue': PD.Group({ + kind: PD.Select(issueTypes[0], PD.arrayToOptions(issueTypes)) + }, { isFlat: true }) + }) + }; + }, + defaultValues: PD.getDefaultValues(StructureQualityReportColorThemeParams), + isApplicable: (ctx: ThemeDataContext) => StructureQualityReport.isApplicable(ctx.structure?.models[0]), + ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => { + return data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve() + } } \ No newline at end of file diff --git a/src/mol-model-props/rcsb/assembly-symmetry.ts b/src/mol-model-props/rcsb/assembly-symmetry.ts index e4c8c07e2f00ac16de5477a40106ff5a921479ab..6dce3e871e3ebd414256ecc7fb2af5825fe524f6 100644 --- a/src/mol-model-props/rcsb/assembly-symmetry.ts +++ b/src/mol-model-props/rcsb/assembly-symmetry.ts @@ -14,6 +14,7 @@ import { GraphQLClient } from '../../mol-util/graphql-client'; import { CustomProperty } from '../common/custom-property'; import { NonNullableArray } from '../../mol-util/type-helpers'; import { CustomStructureProperty } from '../common/custom-structure-property'; +import { MmcifFormat } from '../../mol-model-formats/structure/mmcif'; const BiologicalAssemblyNames = new Set([ 'author_and_software_defined_assembly', @@ -25,15 +26,15 @@ const BiologicalAssemblyNames = new Set([ ]) export namespace AssemblySymmetry { - export const DefaultServerUrl = 'http://data-beta.rcsb.org/graphql' + export const DefaultServerUrl = 'https://data-beta.rcsb.org/graphql' export function isApplicable(structure?: Structure): boolean { // check if structure is from pdb entry - if (!structure || structure.models.length !== 1 || structure.models[0].sourceData.kind !== 'mmCIF' || (!structure.models[0].sourceData.data.database_2.database_id.isDefined && + if (!structure || structure.models.length !== 1 || !MmcifFormat.is(structure.models[0].sourceData) || (!structure.models[0].sourceData.data.db.database_2.database_id.isDefined && structure.models[0].entryId.length !== 4)) return false // check if assembly is 'biological' - const mmcif = structure.models[0].sourceData.data + const mmcif = structure.models[0].sourceData.data.db if (!mmcif.pdbx_struct_assembly.details.isDefined) return false const id = structure.units[0].conformation.operator.assembly.id const indices = Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id) diff --git a/src/mol-model-props/rcsb/graphql/types.d.ts b/src/mol-model-props/rcsb/graphql/types.ts similarity index 99% rename from src/mol-model-props/rcsb/graphql/types.d.ts rename to src/mol-model-props/rcsb/graphql/types.ts index 0af39affaf13fc554cf4dfdcfe12c551a42902ad..ac02ff026d544af2de62eed61b9aa8a58a3f52e3 100644 --- a/src/mol-model-props/rcsb/graphql/types.d.ts +++ b/src/mol-model-props/rcsb/graphql/types.ts @@ -1,7 +1,7 @@ /* eslint-disable */ export type Maybe<T> = T | null; -// Generated in 2020-02-07T10:59:45-08:00 +// Generated in 2020-02-21T15:58:06-08:00 /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { @@ -14,7 +14,6 @@ export type Scalars = { UNREPRESENTABLE: any, }; - export type AuditAuthor = { readonly identifier_ORCID?: Maybe<Scalars['String']>, readonly name?: Maybe<Scalars['String']>, @@ -1750,6 +1749,7 @@ export type RcsbEntryContainerIdentifiers = { readonly emdb_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>, readonly entity_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>, readonly entry_id: Scalars['String'], + readonly model_ids?: Maybe<ReadonlyArray<Maybe<Scalars['Int']>>>, readonly non_polymer_entity_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>, readonly polymer_entity_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>, readonly pubmed_id?: Maybe<Scalars['Int']>, @@ -1921,7 +1921,6 @@ export type RcsbNonpolymerInstanceAnnotationAnnotationLineage = { export type RcsbNonpolymerInstanceFeature = { readonly assignment_version?: Maybe<Scalars['String']>, - readonly auth_seq_id?: Maybe<Scalars['String']>, readonly comp_id?: Maybe<Scalars['String']>, readonly description?: Maybe<Scalars['String']>, readonly feature_id?: Maybe<Scalars['String']>, @@ -1942,6 +1941,7 @@ export type RcsbNonpolymerInstanceFeatureFeatureValue = { }; export type RcsbNonpolymerInstanceFeatureSummary = { + readonly comp_id?: Maybe<Scalars['String']>, readonly count?: Maybe<Scalars['Int']>, readonly maximum_length?: Maybe<Scalars['Int']>, readonly maximum_value?: Maybe<Scalars['Float']>, diff --git a/src/mol-model-props/rcsb/representations/validation-report-clashes.ts b/src/mol-model-props/rcsb/representations/validation-report-clashes.ts index 136aea063f014e6d3debddb152a2e1baf0a5b6d0..be69dbcda1645c51fda6b31ec0768f2cc15460c4 100644 --- a/src/mol-model-props/rcsb/representations/validation-report-clashes.ts +++ b/src/mol-model-props/rcsb/representations/validation-report-clashes.ts @@ -91,7 +91,7 @@ function getIntraClashBoundingSphere(unit: Unit.Atomic, clashes: IntraUnitClashe }, boundingSphere) } -function getIntraClashLabel(unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) { +function getIntraClashLabel(structure: Structure, unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) { const idx = elements[0] if (idx === undefined) return '' const { edgeProps: { id, magnitude, distance } } = clashes @@ -100,12 +100,14 @@ function getIntraClashLabel(unit: Unit.Atomic, clashes: IntraUnitClashes, elemen return [ `Clash id: ${id[idx]} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`, - bondLabel(Bond.Location(unit, clashes.a[idx], unit, clashes.b[idx])) + bondLabel(Bond.Location(structure, unit, clashes.a[idx], structure, unit, clashes.b[idx])) ].join('</br>') } -function IntraClashLoci(unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) { - return DataLoci('intra-clashes', { unit, clashes }, elements, (boundingSphere: Sphere3D) => getIntraClashBoundingSphere(unit, clashes, elements, boundingSphere), () => getIntraClashLabel(unit, clashes, elements)) +function IntraClashLoci(structure: Structure, unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) { + return DataLoci('intra-clashes', { unit, clashes }, elements, + (boundingSphere: Sphere3D) => getIntraClashBoundingSphere(unit, clashes, elements, boundingSphere), + () => getIntraClashLabel(structure, unit, clashes, elements)) } function getIntraClashLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) { @@ -115,7 +117,7 @@ function getIntraClashLoci(pickingId: PickingId, structureGroup: StructureGroup, const unit = group.units[instanceId] if (Unit.isAtomic(unit)) { const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id) - return IntraClashLoci(unit, clashes, [groupId]) + return IntraClashLoci(structure, unit, clashes, [groupId]) } } return EmptyLoci @@ -134,7 +136,7 @@ function createIntraClashIterator(structureGroup: StructureGroup): LocationItera const { a } = clashes const groupCount = clashes.edgeCount * 2 const instanceCount = group.units.length - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit @@ -203,7 +205,7 @@ function getInterClashBoundingSphere(clashes: InterUnitClashes, elements: number }, boundingSphere) } -function getInterClashLabel(clashes: InterUnitClashes, elements: number[]) { +function getInterClashLabel(structure: Structure, clashes: InterUnitClashes, elements: number[]) { const idx = elements[0] if (idx === undefined) return '' const c = clashes.edges[idx] @@ -212,19 +214,21 @@ function getInterClashLabel(clashes: InterUnitClashes, elements: number[]) { return [ `Clash id: ${c.props.id} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`, - bondLabel(Bond.Location(c.unitA, c.indexA, c.unitB, c.indexB)) + bondLabel(Bond.Location(structure, c.unitA, c.indexA, structure, c.unitB, c.indexB)) ].join('</br>') } -function InterClashLoci(clashes: InterUnitClashes, elements: number[]) { - return DataLoci('inter-clashes', clashes, elements, (boundingSphere: Sphere3D) => getInterClashBoundingSphere(clashes, elements, boundingSphere), () => getInterClashLabel(clashes, elements)) +function InterClashLoci(structure: Structure, clashes: InterUnitClashes, elements: number[]) { + return DataLoci('inter-clashes', clashes, elements, + (boundingSphere: Sphere3D) => getInterClashBoundingSphere(clashes, elements, boundingSphere), + () => getInterClashLabel(structure, clashes, elements)) } function getInterClashLoci(pickingId: PickingId, structure: Structure, id: number) { const { objectId, groupId } = pickingId if (id === objectId) { const clashes = ClashesProvider.get(structure).value!.interUnit - return InterClashLoci(clashes, [groupId]) + return InterClashLoci(structure, clashes, [groupId]) } return EmptyLoci } @@ -239,7 +243,7 @@ function createInterClashIterator(structure: Structure): LocationIterator { const clashes = ClashesProvider.get(structure).value!.interUnit const groupCount = clashes.edgeCount const instanceCount = 1 - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const getLocation = (groupIndex: number) => { const clash = clashes.edges[groupIndex] location.unit = clash.unitA diff --git a/src/mol-model-props/rcsb/themes/geometry-quality.ts b/src/mol-model-props/rcsb/themes/geometry-quality.ts index 201d8fabf73f9b997620431889d104c532cd39da..1f39b8de4b19e47b1765c0c85ae93cbe7ab9c64f 100644 --- a/src/mol-model-props/rcsb/themes/geometry-quality.ts +++ b/src/mol-model-props/rcsb/themes/geometry-quality.ts @@ -14,6 +14,7 @@ import { CustomProperty } from '../../common/custom-property'; import { ValidationReportProvider, ValidationReport } from '../validation-report'; import { TableLegend } from '../../../mol-util/legend'; import { PolymerType } from '../../../mol-model/structure/model/types'; +import { SetUtils } from '../../../mol-util/set'; const DefaultColor = Color(0x909090) @@ -23,13 +24,28 @@ const TwoIssuesColor = Color(0xf46d43) const ThreeOrMoreIssuesColor = Color(0xa50026) const ColorLegend = TableLegend([ + ['Data unavailable', DefaultColor], ['No issues', NoIssuesColor], ['One issue', OneIssueColor], ['Two issues', TwoIssuesColor], - ['Three or more issues', ThreeOrMoreIssuesColor] + ['Three or more issues', ThreeOrMoreIssuesColor], ]) -export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: {}): ColorTheme<{}> { +export function getGeometricQualityColorThemeParams(ctx: ThemeDataContext) { + const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]).value + const options: [string, string][] = [] + if (validationReport) { + const kinds = new Set<string>() + validationReport.geometryIssues.forEach(v => v.forEach(k => kinds.add(k))) + kinds.forEach(k => options.push([k, k])) + } + return { + ignore: PD.MultiSelect([] as string[], options) + } +} +export type GeometricQualityColorThemeParams = ReturnType<typeof getGeometricQualityColorThemeParams> + +export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Values<GeometricQualityColorThemeParams>): ColorTheme<GeometricQualityColorThemeParams> { let color: LocationColor = () => DefaultColor const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]) @@ -42,19 +58,26 @@ export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: {}): Col const { geometryIssues, clashes, bondOutliers, angleOutliers } = value const residueIndex = model.atomicHierarchy.residueAtomSegments.index const { polymerType } = model.atomicHierarchy.derived.residue + const ignore = new Set(props.ignore) + color = (location: Location): Color => { if (StructureElement.Location.is(location) && location.unit.model === model) { const { element } = location const rI = residueIndex[element] - let value = geometryIssues.get(rI)?.size - if (value !== undefined && polymerType[rI] === PolymerType.NA) { - value = 0 - if (clashes.getVertexEdgeCount(element) > 0) value += 1 - if (bondOutliers.index.has(element)) value += 1 - if (angleOutliers.index.has(element)) value += 1 + + const value = geometryIssues.get(rI) + if (value === undefined) return DefaultColor + + let count = SetUtils.differenceSize(value, ignore) + + if (count > 0 && polymerType[rI] === PolymerType.NA) { + count = 0 + if (!ignore.has('clash') && clashes.getVertexEdgeCount(element) > 0) count += 1 + if (!ignore.has('mog-bond-outlier') && bondOutliers.index.has(element)) count += 1 + if (!ignore.has('mog-angle-outlier') && angleOutliers.index.has(element)) count += 1 } - switch (value) { + switch (count) { case undefined: return DefaultColor case 0: return NoIssuesColor case 1: return OneIssueColor @@ -72,16 +95,16 @@ export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: {}): Col color, props, contextHash, - description: 'Assigns residue colors according to the number of geometry issues.', + description: 'Assigns residue colors according to the number of (filtered) geometry issues.', legend: ColorLegend } } -export const GeometryQualityColorThemeProvider: ColorTheme.Provider<{}> = { +export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQualityColorThemeParams> = { label: 'RCSB Geometry Quality', factory: GeometryQualityColorTheme, - getParams: () => ({}), - defaultValues: PD.getDefaultValues({}), + getParams: getGeometricQualityColorThemeParams, + defaultValues: PD.getDefaultValues(getGeometricQualityColorThemeParams({})), isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]), ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => { return data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve() diff --git a/src/mol-model-props/rcsb/validation-report.ts b/src/mol-model-props/rcsb/validation-report.ts index c2a2b054f8dab317d739a391fef2823b56658b0f..ada844656b5fd832507d19efae31e330cbe3d98c 100644 --- a/src/mol-model-props/rcsb/validation-report.ts +++ b/src/mol-model-props/rcsb/validation-report.ts @@ -18,6 +18,7 @@ import { IntMap, SortedArray } from '../../mol-data/int'; import { arrayMax } from '../../mol-util/array'; import { equalEps } from '../../mol-math/linear-algebra/3d/common'; import { Vec3 } from '../../mol-math/linear-algebra'; +import { MmcifFormat } from '../../mol-model-formats/structure/mmcif'; export { ValidationReport } @@ -90,8 +91,8 @@ namespace ValidationReport { export function isApplicable(model?: Model): boolean { return ( !!model && - model.sourceData.kind === 'mmCIF' && - (model.sourceData.data.database_2.database_id.isDefined || + MmcifFormat.is(model.sourceData) && + (model.sourceData.data.db.database_2.database_id.isDefined || model.entryId.length === 4) ) } diff --git a/src/mol-model/structure/export/categories/modified-residues.ts b/src/mol-model/structure/export/categories/modified-residues.ts index e19e33636c6c1b7531fbd22876517e517e18686a..51726e8c6b2a1df02b78335e1b9087f7c01b3a33 100644 --- a/src/mol-model/structure/export/categories/modified-residues.ts +++ b/src/mol-model/structure/export/categories/modified-residues.ts @@ -35,7 +35,7 @@ function getModifiedResidues({ structures }: CifExportContext): StructureElement const ret = []; const prop = P.residue.label_comp_id; - const loc = StructureElement.Location.create(); + const loc = StructureElement.Location.create(structure); for (const unit of structure.units) { if (!Unit.isAtomic(unit) || !unit.conformation.operator.isIdentity) continue; const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements); @@ -45,7 +45,7 @@ function getModifiedResidues({ structures }: CifExportContext): StructureElement loc.element = unit.elements[seg.start]; const name = prop(loc); if (map.has(name)) { - ret[ret.length] = StructureElement.Location.create(loc.unit, loc.element); + ret[ret.length] = StructureElement.Location.clone(loc); } } } diff --git a/src/mol-model/structure/export/categories/secondary-structure.ts b/src/mol-model/structure/export/categories/secondary-structure.ts index a5bc27ce4b6d9cf74350fd1a705eeef8bcc4c0ff..21d9c13488ac510ddcf6bb5cac5c0c39e378db9b 100644 --- a/src/mol-model/structure/export/categories/secondary-structure.ts +++ b/src/mol-model/structure/export/categories/secondary-structure.ts @@ -13,6 +13,7 @@ import CifField = CifWriter.Field import CifCategory = CifWriter.Category import { Column } from '../../../../mol-data/db'; import { residueIdFields } from './atom_site'; +import { ModelSecondaryStructure } from '../../../../mol-model-formats/structure/property/secondary-structure'; export const _struct_conf: CifCategory<CifExportContext> = { name: 'struct_conf', @@ -70,11 +71,14 @@ interface SSElement<T extends SecondaryStructure.Element> { function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContext, kind: SecondaryStructure.Element['kind']) { // TODO: encode secondary structure for different models? - const { key, elements } = ctx.structures[0].model.properties.secondaryStructure; + const secondaryStructure = ModelSecondaryStructure.Provider.get(ctx.firstModel) + if (!secondaryStructure) return [] as SSElement<T>[] + const { key, elements } = secondaryStructure; const ssElements: SSElement<any>[] = []; - for (const unit of ctx.structures[0].units) { + const structure = ctx.structures[0]; + for (const unit of structure.units) { // currently can only support this for "identity" operators. if (!Unit.isAtomic(unit) || !unit.conformation.operator.isIdentity) continue; @@ -100,8 +104,8 @@ function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContex if (startIdx !== key[current.index]) { move = false; ssElements[ssElements.length] = { - start: StructureElement.Location.create(unit, segs.offsets[start]), - end: StructureElement.Location.create(unit, segs.offsets[prev]), + start: StructureElement.Location.create(structure, unit, segs.offsets[start]), + end: StructureElement.Location.create(structure, unit, segs.offsets[prev]), length: prev - start + 1, element } diff --git a/src/mol-model/structure/export/categories/utils.ts b/src/mol-model/structure/export/categories/utils.ts index 091e92172dcbfeefa10bb7e069c717103474a05a..4f500efbf56d9dfe93322614ca74b4b1d1958e47 100644 --- a/src/mol-model/structure/export/categories/utils.ts +++ b/src/mol-model/structure/export/categories/utils.ts @@ -13,10 +13,11 @@ import { UniqueArray } from '../../../../mol-data/generic'; import { sortArray } from '../../../../mol-data/util'; import { CifWriter } from '../../../../mol-io/writer/cif'; import { CifExportContext } from '../mmcif'; +import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif'; export function getModelMmCifCategory<K extends keyof mmCIF_Schema>(model: Model, name: K): mmCIF_Database[K] | undefined { - if (model.sourceData.kind !== 'mmCIF') return; - return model.sourceData.data[name]; + if (!MmcifFormat.is(model.sourceData)) return; + return model.sourceData.data.db[name]; } export function getUniqueResidueNamesFromStructures(structures: Structure[]) { @@ -50,9 +51,9 @@ export function copy_mmCif_category(name: keyof mmCIF_Schema, condition?: (struc if (condition && !condition(structures[0])) return CifWriter.Category.Empty; const model = structures[0].model; - if (model.sourceData.kind !== 'mmCIF') return CifWriter.Category.Empty; + if (!MmcifFormat.is(model.sourceData)) return CifWriter.Category.Empty; - const table = model.sourceData.data[name]; + const table = model.sourceData.data.db[name]; if (!table || !table._rowCount) return CifWriter.Category.Empty; return CifWriter.Category.ofTable(table); } diff --git a/src/mol-model/structure/model.ts b/src/mol-model/structure/model.ts index 8a1dccfdebe0bda698574a261203ab9741156036..a1cb4a8bd81a7af47e32eafbc8236632cc61c6d9 100644 --- a/src/mol-model/structure/model.ts +++ b/src/mol-model/structure/model.ts @@ -6,9 +6,9 @@ import { Model } from './model/model' import * as Types from './model/types' -import { ModelSymmetry } from './model/properties/symmetry' +import { Symmetry } from './model/properties/symmetry' import StructureSequence from './model/properties/sequence' export * from './model/properties/custom/indexed' export * from './model/indexing' -export { Model, Types, ModelSymmetry, StructureSequence } \ No newline at end of file +export { Model, Types, Symmetry, StructureSequence } \ No newline at end of file diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index 74cd85af6bf0d1b6652f4e390b654cbef7c67fc2..04dd2a0dd482c28713a5bb4c68686bf244752153 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-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> @@ -8,11 +8,9 @@ import UUID from '../../../mol-util/uuid'; import StructureSequence from './properties/sequence'; import { AtomicHierarchy, AtomicConformation, AtomicRanges } from './properties/atomic'; -import { ModelSymmetry } from './properties/symmetry'; import { CoarseHierarchy, CoarseConformation } from './properties/coarse'; import { Entities, ChemicalComponentMap, MissingResidues } from './properties/common'; import { CustomProperties } from '../common/custom-property'; -import { SecondaryStructure } from './properties/seconday-structure'; import { SaccharideComponentMap } from '../structure/carbohydrates/constants'; import { ModelFormat } from '../../../mol-model-formats/structure/format'; import { calcModelCenter } from './util'; @@ -20,9 +18,9 @@ import { Vec3 } from '../../../mol-math/linear-algebra'; import { Mutable } from '../../../mol-util/type-helpers'; import { Coordinates } from '../coordinates'; import { Topology } from '../topology'; -import { _parse_mmCif } from '../../../mol-model-formats/structure/mmcif/parser'; import { Task } from '../../../mol-task'; -import { IndexPairBonds } from '../../../mol-model-formats/structure/mmcif/bonds/index-pair'; +import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair'; +import { createModels } from '../../../mol-model-formats/structure/basic/parser'; /** * Interface to the "source data" of the molecule. @@ -47,7 +45,6 @@ export interface Model extends Readonly<{ sourceData: ModelFormat, - symmetry: ModelSymmetry, entities: Entities, sequence: StructureSequence, @@ -56,8 +53,6 @@ export interface Model extends Readonly<{ atomicRanges: AtomicRanges, properties: { - /** secondary structure provided by the input file */ - readonly secondaryStructure: SecondaryStructure, /** maps modified residue name to its parent */ readonly modifiedResidues: Readonly<{ parentId: ReadonlyMap<string, string>, @@ -87,7 +82,6 @@ export interface Model extends Readonly<{ } { } export namespace Model { - // TODO: is this enough? export type Trajectory = ReadonlyArray<Model> export function trajectoryFromModelAndCoordinates(model: Model, coordinates: Coordinates): Trajectory { @@ -113,12 +107,13 @@ export namespace Model { export function trajectoryFromTopologyAndCoordinates(topology: Topology, coordinates: Coordinates): Task<Trajectory> { return Task.create('Create Trajectory', async ctx => { - const model = (await _parse_mmCif(topology.format, ctx))[0]; + const model = (await createModels(topology.basic, topology.sourceData, ctx))[0]; if (!model) throw new Error('found no model') const trajectory = trajectoryFromModelAndCoordinates(model, coordinates) const bondData = { pairs: topology.bonds, count: model.atomicHierarchy.atoms._rowCount } + const indexPairBonds = IndexPairBonds.fromData(bondData) for (const m of trajectory) { - IndexPairBonds.attachFromData(m, bondData) + IndexPairBonds.Provider.set(m, indexPairBonds) } return trajectory }) diff --git a/src/mol-model/structure/model/properties/atomic/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts index 5b17b8b4f2e137161191553075bf4b8c41d4ff6a..0f1bee9cd65d589251a2a74fa83f5f4266841d1a 100644 --- a/src/mol-model/structure/model/properties/atomic/hierarchy.ts +++ b/src/mol-model/structure/model/properties/atomic/hierarchy.ts @@ -50,7 +50,7 @@ export const AtomsSchema = { }; export type AtomsSchema = typeof AtomsSchema -export interface Atoms extends Table<AtomsSchema> { } +export type Atoms = Table<AtomsSchema> export const ResiduesSchema = { /** @@ -83,7 +83,7 @@ export const ResiduesSchema = { pdbx_PDB_ins_code: mmCIF.atom_site.pdbx_PDB_ins_code, }; export type ResiduesSchema = typeof ResiduesSchema -export interface Residues extends Table<ResiduesSchema> { } +export type Residues = Table<ResiduesSchema> export const ChainsSchema = { /** @@ -102,7 +102,7 @@ export const ChainsSchema = { label_entity_id: mmCIF.atom_site.label_entity_id } export type ChainsSchema = typeof ChainsSchema -export interface Chains extends Table<ChainsSchema> { } +export type Chains = Table<ChainsSchema> export interface AtomicData { atoms: Atoms, @@ -139,6 +139,8 @@ export interface AtomicSegments { export interface AtomicIndex { /** @returns index or -1 if not present. */ getEntityFromChain(cI: ChainIndex): EntityIndex, + /** @returns index or -1 if not present. */ + findEntity(label_asym_id: string): EntityIndex /** * Find chain using label_ mmCIF properties diff --git a/src/mol-model/structure/model/properties/computed.ts b/src/mol-model/structure/model/properties/computed.ts deleted file mode 100644 index 39896495fbaa30bcf65f3575e6664b7517a9af68..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/model/properties/computed.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -// TODO: stuff like "residue type/flags", isRingAtom, rings, bonds??, wrap these in a computation? -// secondary structure is also a computed property - -// import { SecondaryStructureType } from '../constants' - - -// interface SecondaryStructure { -// ofResidue: ArrayLike<SecondaryStructureType>, -// // atom segmentation?? -// segments: Segmentation -// } - -// interface Conformation { -// positions: Conformation, -// secondaryStructure: SecondaryStructure -// } \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/custom/indexed.ts b/src/mol-model/structure/model/properties/custom/indexed.ts index 4e8aa7ef39895112531941eee44bbab9b52cd24f..de239f33f5ed8a6708ce12a8d670e87ad45ce97e 100644 --- a/src/mol-model/structure/model/properties/custom/indexed.ts +++ b/src/mol-model/structure/model/properties/custom/indexed.ts @@ -109,7 +109,7 @@ class SegmentedMappedIndexedCustomProperty<Idx extends IndexedCustomProperty.Ind const seg = chains.move(); if (!this.has(seg.index) || seenIndices.has(seg.index)) continue; seenIndices.add(seg.index); - loci[loci.length] = StructureElement.Location.create(unit, unit.elements[seg.start]); + loci[loci.length] = StructureElement.Location.create(structure, unit, unit.elements[seg.start]); } } @@ -154,7 +154,7 @@ class ElementMappedCustomProperty<T = any> implements IndexedCustomProperty<Elem const e = elements[i]; if (!this.has(e) || seenIndices.has(e)) continue; seenIndices.add(elements[i]); - loci[loci.length] = StructureElement.Location.create(unit, e); + loci[loci.length] = StructureElement.Location.create(structure, unit, e); } } @@ -202,7 +202,7 @@ class EntityMappedCustomProperty<T = any> implements IndexedCustomProperty<Entit const eI = index.getEntityFromChain(seg.index); if (!this.has(eI) || seenIndices.has(eI)) continue; seenIndices.add(eI); - loci[loci.length] = StructureElement.Location.create(unit, unit.elements[seg.start]); + loci[loci.length] = StructureElement.Location.create(structure, unit, unit.elements[seg.start]); } } diff --git a/src/mol-model/structure/model/properties/symmetry.ts b/src/mol-model/structure/model/properties/symmetry.ts index 38b9e5e12642ac80ad7c8e5ae1daa661bd6334a9..a25aa43a4d55541e6e9f40fffd990b4b71a938ea 100644 --- a/src/mol-model/structure/model/properties/symmetry.ts +++ b/src/mol-model/structure/model/properties/symmetry.ts @@ -10,6 +10,7 @@ import { StructureQuery } from '../../query' import { Model } from '../../model' import { Spacegroup } from '../../../../mol-math/geometry'; import { Vec3 } from '../../../../mol-math/linear-algebra'; +import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry'; /** Determine an atom set and a list of operators that should be applied to that set */ export interface OperatorGroup { @@ -42,7 +43,7 @@ export namespace Assembly { } } -interface ModelSymmetry { +interface Symmetry { readonly assemblies: ReadonlyArray<Assembly>, readonly spacegroup: Spacegroup, readonly isNonStandardCrytalFrame: boolean, @@ -58,13 +59,14 @@ interface ModelSymmetry { } } -namespace ModelSymmetry { - export const Default: ModelSymmetry = { assemblies: [], spacegroup: Spacegroup.ZeroP1, isNonStandardCrytalFrame: false }; +namespace Symmetry { + export const Default: Symmetry = { assemblies: [], spacegroup: Spacegroup.ZeroP1, isNonStandardCrytalFrame: false }; export function findAssembly(model: Model, id: string): Assembly | undefined { const _id = id.toLocaleLowerCase(); - return arrayFind(model.symmetry.assemblies, a => a.id.toLowerCase() === _id); + const symmetry = ModelSymmetry.Provider.get(model) + return symmetry ? arrayFind(symmetry.assemblies, a => a.id.toLowerCase() === _id) : undefined; } } -export { ModelSymmetry } \ No newline at end of file +export { Symmetry } \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/utils/atomic-index.ts b/src/mol-model/structure/model/properties/utils/atomic-index.ts index f35949c1cd71eb26c540f809e2e22ffc6219caec..b16cbb5289ee2b1dfead9b7a32b2fdb2453a0f6b 100644 --- a/src/mol-model/structure/model/properties/utils/atomic-index.ts +++ b/src/mol-model/structure/model/properties/utils/atomic-index.ts @@ -51,7 +51,9 @@ interface Mapping { chain_index_label_seq_id: Map<ChainIndex, Map<string | number, ResidueIndex>>, auth_asym_id_auth_seq_id: Map<string, Map<number, ChainIndex>>, - chain_index_auth_seq_id: Map<ChainIndex, Map<string | number, ResidueIndex>> + chain_index_auth_seq_id: Map<ChainIndex, Map<string | number, ResidueIndex>>, + + label_asym_id: Map<string, EntityIndex>, } function createMapping(entities: Entities, data: AtomicData, segments: AtomicSegments): Mapping { @@ -67,6 +69,7 @@ function createMapping(entities: Entities, data: AtomicData, segments: AtomicSeg chain_index_label_seq_id: new Map(), auth_asym_id_auth_seq_id: new Map(), chain_index_auth_seq_id: new Map(), + label_asym_id: new Map(), }; } @@ -79,6 +82,11 @@ class Index implements AtomicIndex { return this.map.chain_index_entity_index[cI]; } + findEntity(label_asym_id: string): EntityIndex { + const entityIndex = this.map.label_asym_id.get(label_asym_id) + return entityIndex !== undefined ? entityIndex : -1 as EntityIndex + } + findChainLabel(key: AtomicIndex.ChainLabelKey): ChainIndex { const eI = this.entityIndex(key.label_entity_id); if (eI < 0 || !this.map.entity_index_label_asym_id.has(eI)) return -1 as ChainIndex; @@ -216,7 +224,9 @@ export function getAtomicIndex(data: AtomicData, entities: Entities, segments: A map.auth_asym_id_auth_seq_id.set(authAsymId, auth_asym_id_auth_seq_id) } - updateMapMapIndex(map.entity_index_label_asym_id, entityIndex, label_asym_id.value(chainIndex), chainIndex); + const labelAsymId = label_asym_id.value(chainIndex) + if (!map.label_asym_id.has(labelAsymId)) map.label_asym_id.set(labelAsymId, entityIndex); + updateMapMapIndex(map.entity_index_label_asym_id, entityIndex, labelAsymId, chainIndex); const chain_index_label_seq_id = new Map<string | number, ResidueIndex>(); const chain_index_auth_seq_id = new Map<string | number, ResidueIndex>(); diff --git a/src/mol-model/structure/model/types.ts b/src/mol-model/structure/model/types.ts index d9f083413db7634beb079693f418b50d18ebeaa9..95a1e8e41d6b9688c79b8316186748eb08501001 100644 --- a/src/mol-model/structure/model/types.ts +++ b/src/mol-model/structure/model/types.ts @@ -221,6 +221,7 @@ export const WaterNames = new Set([ export const AminoAcidNamesL = new Set([ 'HIS', 'ARG', 'LYS', 'ILE', 'PHE', 'LEU', 'TRP', 'ALA', 'MET', 'PRO', 'CYS', 'ASN', 'VAL', 'GLY', 'SER', 'GLN', 'TYR', 'ASP', 'GLU', 'THR', 'SEC', 'PYL', + 'UNK' // unknown amino acid from CCD ]) export const AminoAcidNamesD = new Set([ 'DAL', // D-ALANINE @@ -247,8 +248,14 @@ export const AminoAcidNamesD = new Set([ ]) export const AminoAcidNames = SetUtils.unionMany(AminoAcidNamesL, AminoAcidNamesD) -export const RnaBaseNames = new Set([ 'A', 'C', 'T', 'G', 'I', 'U' ]) -export const DnaBaseNames = new Set([ 'DA', 'DC', 'DT', 'DG', 'DI', 'DU' ]) +export const RnaBaseNames = new Set([ + 'A', 'C', 'T', 'G', 'I', 'U', + 'N' // unknown RNA base from CCD +]) +export const DnaBaseNames = new Set([ + 'DA', 'DC', 'DT', 'DG', 'DI', 'DU', + 'DN' // unknown DNA base from CCD +]) export const PeptideBaseNames = new Set([ 'APN', 'CPN', 'TPN', 'GPN' ]) export const PurineBaseNames = new Set([ 'A', 'G', 'DA', 'DG', 'DI', 'APN', 'GPN' ]) export const PyrimidineBaseNames = new Set([ 'C', 'T', 'U', 'DC', 'DT', 'DU', 'CPN', 'TPN' ]) diff --git a/src/mol-model/structure/query/context.ts b/src/mol-model/structure/query/context.ts index d73e9f1a4026f1a7062c87ab65a36f6c97ce8cc1..43b2538b98ba727032ab231948c203283e123f61 100644 --- a/src/mol-model/structure/query/context.ts +++ b/src/mol-model/structure/query/context.ts @@ -6,13 +6,12 @@ import { Structure, StructureElement, Unit } from '../structure'; import { now } from '../../../mol-util/now'; -import { ElementIndex } from '../model'; import { BondType } from '../model/types'; import { StructureSelection } from './selection'; import { defaultBondTest } from './queries/internal'; export interface QueryContextView { - readonly element: StructureElement.Location; + readonly element: Readonly<StructureElement.Location>; readonly currentStructure: Structure; } @@ -28,7 +27,7 @@ export class QueryContext implements QueryContextView { readonly inputStructure: Structure; /** Current element */ - readonly element = StructureElement.Location.create(); + readonly element: StructureElement.Location = StructureElement.Location.create(void 0); currentStructure: Structure = void 0 as any; /** Current bond between atoms */ @@ -37,14 +36,9 @@ export class QueryContext implements QueryContextView { /** Supply this from the outside. Used by the internal.generator.current symbol */ currentSelection: StructureSelection | undefined = void 0; - setElement(unit: Unit, e: ElementIndex) { - this.element.unit = unit; - this.element.element = e; - } - pushCurrentElement(): StructureElement.Location { this.currentElementStack[this.currentElementStack.length] = this.element; - (this.element as StructureElement.Location) = StructureElement.Location.create(); + (this.element as StructureElement.Location) = StructureElement.Location.create(void 0); return this.element; } @@ -112,16 +106,21 @@ export interface QueryContextOptions { export interface QueryPredicate { (ctx: QueryContext): boolean } export interface QueryFn<T = any> { (ctx: QueryContext): T } -export class QueryContextBondInfo<U extends Unit = Unit> { - a: StructureElement.Location<U> = StructureElement.Location.create(); +class QueryContextBondInfo<U extends Unit = Unit> { + a: StructureElement.Location<U> = StructureElement.Location.create(void 0); aIndex: StructureElement.UnitIndex = 0 as StructureElement.UnitIndex; - b: StructureElement.Location<U> = StructureElement.Location.create(); + b: StructureElement.Location<U> = StructureElement.Location.create(void 0); bIndex: StructureElement.UnitIndex = 0 as StructureElement.UnitIndex; type: BondType = BondType.Flag.None; order: number = 0; private testFn: QueryPredicate = defaultBondTest; + setStructure(s: Structure) { + this.a.structure = s; + this.b.structure = s; + } + setTestFn(fn?: QueryPredicate) { this.testFn = fn || defaultBondTest; } @@ -136,6 +135,10 @@ export class QueryContextBondInfo<U extends Unit = Unit> { } private swap() { + // const sA = this.a.structure; + // this.a.structure = this.b.structure; + // this.b.structure = sA; + const idxA = this.aIndex; this.aIndex = this.bIndex; this.bIndex = idxA; diff --git a/src/mol-model/structure/query/queries/filters.ts b/src/mol-model/structure/query/queries/filters.ts index aff78800bc31595d98513243e04d6771a132d0c6..3b689246c33f2db5e22062ab22e426d75c4a7737 100644 --- a/src/mol-model/structure/query/queries/filters.ts +++ b/src/mol-model/structure/query/queries/filters.ts @@ -56,6 +56,7 @@ export function getCurrentStructureProperties(ctx: QueryContext, props: UnitType const { units } = ctx.currentStructure; const l = ctx.pushCurrentElement(); + l.structure = ctx.currentStructure; for (const unit of units) { l.unit = unit; const elements = unit.elements; @@ -239,6 +240,9 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) { const atomicBond = queryCtx.atomicBond; const interBonds = input.interUnitBonds; + + atomicBond.setStructure(input); + for (const unit of structure.units) { if (!Unit.isAtomic(unit)) continue; @@ -248,8 +252,6 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) { const bondedUnits = interBonds.getConnectedUnits(unit); const buCount = bondedUnits.length; - atomicBond.a.unit = inputUnit; - const srcElements = unit.elements; const inputElements = inputUnit.elements; diff --git a/src/mol-model/structure/query/queries/generators.ts b/src/mol-model/structure/query/queries/generators.ts index 646f67dfc7f60cba98cf1facc4314fa58a014c48..afac8b988674b8fe329eab0bf1fca5f2a5ea83e9 100644 --- a/src/mol-model/structure/query/queries/generators.ts +++ b/src/mol-model/structure/query/queries/generators.ts @@ -64,6 +64,7 @@ function atomGroupsLinear(atomTest: QueryPredicate): StructureQuery { const l = ctx.pushCurrentElement(); const builder = inputStructure.subsetBuilder(true); + l.structure = inputStructure; for (const unit of units) { l.unit = unit; const elements = unit.elements; @@ -89,6 +90,7 @@ function atomGroupsSegmented({ unitTest, entityTest, chainTest, residueTest, ato const l = ctx.pushCurrentElement(); const builder = inputStructure.subsetBuilder(true); + l.structure = inputStructure; for (const unit of units) { l.unit = unit; if (!unitTest(ctx)) continue; @@ -158,6 +160,7 @@ function atomGroupsGrouped({ unitTest, entityTest, chainTest, residueTest, atomT const l = ctx.pushCurrentElement(); const builder = new LinearGroupingBuilder(inputStructure); + l.structure = inputStructure; for (const unit of units) { l.unit = unit; if (!unitTest(ctx)) continue; @@ -295,6 +298,8 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery { const atomicBond = ctx.atomicBond; atomicBond.setTestFn(bondTest); + atomicBond.setStructure(structure); + // Process intra unit bonds for (const unit of structure.units) { if (unit.kind !== Unit.Kind.Atomic) continue; diff --git a/src/mol-model/structure/query/queries/internal.ts b/src/mol-model/structure/query/queries/internal.ts index c0258e58d901e769fe51dd787c9b9625b5da0fd0..68ce9e555818ad41443e00864fef4dd21670f8ab 100644 --- a/src/mol-model/structure/query/queries/internal.ts +++ b/src/mol-model/structure/query/queries/internal.ts @@ -22,7 +22,7 @@ export function defaultBondTest(ctx: QueryContext) { export function atomicSequence(): StructureQuery { return function query_atomicSequence(ctx) { const { inputStructure } = ctx; - const l = StructureElement.Location.create(); + const l = StructureElement.Location.create(inputStructure); const units: Unit[] = []; for (const unit of inputStructure.units) { @@ -50,7 +50,7 @@ export function atomicSequence(): StructureQuery { export function water(): StructureQuery { return function query_water(ctx) { const { inputStructure } = ctx; - const l = StructureElement.Location.create(); + const l = StructureElement.Location.create(inputStructure); const units: Unit[] = []; for (const unit of inputStructure.units) { @@ -69,7 +69,7 @@ export function water(): StructureQuery { export function atomicHet(): StructureQuery { return function query_atomicHet(ctx) { const { inputStructure } = ctx; - const l = StructureElement.Location.create(); + const l = StructureElement.Location.create(inputStructure); const units: Unit[] = []; for (const unit of inputStructure.units) { diff --git a/src/mol-model/structure/query/queries/modifiers.ts b/src/mol-model/structure/query/queries/modifiers.ts index 8f38b20430e72dc66118da04056af4639d2f69aa..6c7fd7319cbf17b7bbc3d34cb9cc0e7e532b4a24 100644 --- a/src/mol-model/structure/query/queries/modifiers.ts +++ b/src/mol-model/structure/query/queries/modifiers.ts @@ -94,6 +94,7 @@ function getIncludeSurroundingsWithRadius(ctx: QueryContext, source: Structure, const { elementRadius, elementRadiusClosure, sourceMaxRadius, radius } = params; ctx.pushCurrentElement(); + ctx.element.structure = structure; for (const unit of structure.units) { ctx.element.unit = unit; const { x, y, z } = unit.conformation; @@ -115,6 +116,7 @@ function getIncludeSurroundingsWithRadius(ctx: QueryContext, source: Structure, function createElementRadiusFn(ctx: QueryContext, eRadius: QueryFn<number>): StructureElement.Property<number> { return e => { + ctx.element.structure = e.structure; ctx.element.unit = e.unit; ctx.element.element = e.element; return eRadius(ctx); @@ -123,6 +125,7 @@ function createElementRadiusFn(ctx: QueryContext, eRadius: QueryFn<number>): Str function findStructureRadius(ctx: QueryContext, eRadius: QueryFn<number>) { let r = 0; + ctx.element.structure = ctx.inputStructure; for (const unit of ctx.inputStructure.units) { ctx.element.unit = unit; const elements = unit.elements; @@ -252,6 +255,7 @@ export function expandProperty(query: StructureQuery, property: QueryFn): Struct const builders: StructureSubsetBuilder[] = []; ctx.pushCurrentElement(); StructureSelection.forEach(src, (s, sI) => { + ctx.element.structure = s; for (const unit of s.units) { ctx.element.unit = unit; const elements = unit.elements; @@ -272,6 +276,7 @@ export function expandProperty(query: StructureQuery, property: QueryFn): Struct if (sI % 10 === 0) ctx.throwIfTimedOut(); }); + ctx.element.structure = ctx.inputStructure; for (const unit of ctx.inputStructure.units) { ctx.element.unit = unit; const elements = unit.elements; @@ -355,6 +360,8 @@ function expandConnected(ctx: QueryContext, structure: Structure) { const inputUnitA = inputStructure.unitMap.get(unit.id) as Unit.Atomic; const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order } } = inputUnitA.bonds; + atomicBond.setStructure(inputStructure); + // Process intra unit bonds atomicBond.a.unit = inputUnitA; atomicBond.b.unit = inputUnitA; diff --git a/src/mol-model/structure/query/utils/builders.ts b/src/mol-model/structure/query/utils/builders.ts index c7d0c6ceeff8abcecbc2aa94e138a1df320e19c7..add4742dd95b13e7fbaeca60c12be03c8264a60c 100644 --- a/src/mol-model/structure/query/utils/builders.ts +++ b/src/mol-model/structure/query/utils/builders.ts @@ -56,7 +56,7 @@ export class LinearGroupingBuilder { private singletonSelection(): StructureSelection { const builder = this.source.subsetBuilder(true); - const loc = StructureElement.Location.create(); + const loc = StructureElement.Location.create(this.source); for (let i = 0, _i = this.builders.length; i < _i; i++) { this.builders[i].setSingletonLocation(loc); builder.addToUnit(loc.unit.id, loc.element); diff --git a/src/mol-model/structure/query/utils/structure-distance.ts b/src/mol-model/structure/query/utils/structure-distance.ts index f05ca7cb1fe2e87e464fa281ed68f97c5020ee0a..bdea7bfe1089c874d003b9786ae161b5673fafab 100644 --- a/src/mol-model/structure/query/utils/structure-distance.ts +++ b/src/mol-model/structure/query/utils/structure-distance.ts @@ -61,12 +61,14 @@ namespace MinMaxDist { const { units } = a; let withinRange = false; + ctx.element.structure = a; for (let i = 0, _i = units.length; i < _i; i++) { const unit = units[i]; const { elements, conformation: { position } } = unit; + ctx.element.unit = unit; for (let i = 0, _i = elements.length; i < _i; i++) { const e = elements[i]; - ctx.setElement(unit, e); + ctx.element.element = e; const tp = toPoint(ctx, b, position(e, distPivot), elementRadius(ctx), minDist, maxDist, elementRadius); if (tp === Result.BelowMin) return Result.BelowMin; if (tp === Result.WithinMax) withinRange = true; @@ -102,12 +104,14 @@ namespace MaxRadiusDist { if (a.elementCount === 0 || b.elementCount === 0) return 0; const { units } = a; + ctx.element.structure = a; for (let i = 0, _i = units.length; i < _i; i++) { const unit = units[i]; + ctx.element.unit = unit; const { elements, conformation: { position } } = unit; for (let i = 0, _i = elements.length; i < _i; i++) { const e = elements[i]; - ctx.setElement(unit, e); + ctx.element.element = e; if (toPoint(ctx, b, position(e, distPivot), elementRadius(ctx), maxDist, elementRadius)) return true; } } diff --git a/src/mol-model/structure/structure/element/location.ts b/src/mol-model/structure/structure/element/location.ts index c0d745b23633d308a425b7c8312ca9bfa541f6da..ec5f970e4a17f64c08a5d6775cb68505260b3c30 100644 --- a/src/mol-model/structure/structure/element/location.ts +++ b/src/mol-model/structure/structure/element/location.ts @@ -8,22 +8,34 @@ import { ElementIndex } from '../../model'; import Unit from '../unit'; import { Vec3 } from '../../../../mol-math/linear-algebra'; +import Structure from '../structure'; export { Location } interface Location<U = Unit> { readonly kind: 'element-location', + structure: Structure, unit: U, /** Index into element (atomic/coarse) properties of unit.model */ element: ElementIndex } namespace Location { - export function create<U extends Unit>(unit?: U, element?: ElementIndex): Location<U> { - return { kind: 'element-location', unit: unit!, element: element || (0 as ElementIndex) }; + export function create<U extends Unit>(structure: Structure | undefined, unit?: U, element?: ElementIndex): Location<U> { + return { + kind: 'element-location', + structure: structure as any, + unit: unit as any, + element: element || (0 as ElementIndex) + }; } - export function set(a: Location, unit?: Unit, element?: ElementIndex): Location { + export function clone<U extends Unit>(l: Location<U>): Location<U> { + return create(l.structure, l.unit, l.element); + } + + export function set(a: Location, structure?: Structure, unit?: Unit, element?: ElementIndex): Location { + if (structure) a.structure = structure; if (unit) a.unit = unit if (element !== undefined) a.element = element return a; diff --git a/src/mol-model/structure/structure/element/loci.ts b/src/mol-model/structure/structure/element/loci.ts index 4523f631ae8f423215188430a64f006a2d1968dd..76746ddb8b49af4dcda61173166e9c1c6d6df908 100644 --- a/src/mol-model/structure/structure/element/loci.ts +++ b/src/mol-model/structure/structure/element/loci.ts @@ -90,11 +90,12 @@ export namespace Loci { const unit = loci.elements[0].unit; const element = unit.elements[OrderedSet.getAt(loci.elements[0].indices, 0)]; if (e) { + e.structure = loci.structure; e.unit = loci.elements[0].unit; e.element = element; return e; } - return Location.create(unit, element); + return Location.create(loci.structure, unit, element); } export function toStructure(loci: Loci): Structure { @@ -365,7 +366,7 @@ export namespace Loci { export function extendToWholeEntities(loci: Loci): Loci { const elements: Loci['elements'][0][] = [] - const l = Location.create() + const l = Location.create(loci.structure) const entities = new Set<string>() const { units } = loci.structure diff --git a/src/mol-model/structure/structure/element/stats.ts b/src/mol-model/structure/structure/element/stats.ts index fea5bc4198831c24457a31530aa641b1d9b44f7c..ac9f6e2854b4946c60c278af1a5a93e45338108d 100644 --- a/src/mol-model/structure/structure/element/stats.ts +++ b/src/mol-model/structure/structure/element/stats.ts @@ -10,6 +10,7 @@ import Unit from '../unit'; import { Loci } from './loci'; import { Location } from './location'; import { ChainIndex } from '../../model/indexing'; +import Structure from '../structure'; export interface Stats { elementCount: number @@ -37,12 +38,12 @@ export namespace Stats { unitCount: 0, structureCount: 0, - firstElementLoc: Location.create(), - firstConformationLoc: Location.create(), - firstResidueLoc: Location.create(), - firstChainLoc: Location.create(), - firstUnitLoc: Location.create(), - firstStructureLoc: Location.create(), + firstElementLoc: Location.create(void 0), + firstConformationLoc: Location.create(void 0), + firstResidueLoc: Location.create(void 0), + firstChainLoc: Location.create(void 0), + firstUnitLoc: Location.create(void 0), + firstStructureLoc: Location.create(void 0), } } @@ -51,7 +52,7 @@ export namespace Stats { map.set(key, count + inc) } - function handleElement(stats: Stats, element: Loci['elements'][0]) { + function handleElement(stats: Stats, structure: Structure, element: Loci['elements'][0]) { const { indices, unit } = element const { elements } = unit const size = OrderedSet.size(indices) @@ -60,19 +61,19 @@ export namespace Stats { const residueAltIdCounts = new Map<string, number>() if (size > 0) { - Location.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)]) + Location.set(stats.firstElementLoc, structure, unit, elements[OrderedSet.start(indices)]) } // count single element unit as unit not element if (size === elements.length) { stats.unitCount += 1 if (stats.unitCount === 1) { - Location.set(stats.firstUnitLoc, unit, elements[OrderedSet.start(indices)]) + Location.set(stats.firstUnitLoc, structure, unit, elements[OrderedSet.start(indices)]) } } else if (size === 1) { stats.elementCount += 1 if (stats.elementCount === 1) { - Location.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)]) + Location.set(stats.firstElementLoc, structure, unit, elements[OrderedSet.start(indices)]) } } else { if (Unit.isAtomic(unit)) { @@ -99,7 +100,7 @@ export namespace Stats { // full residue stats.residueCount += 1 if (stats.residueCount === 1) { - Location.set(stats.firstResidueLoc, unit, offsets[rI]) + Location.set(stats.firstResidueLoc, structure, unit, offsets[rI]) } } else { // partial residue @@ -116,7 +117,7 @@ export namespace Stats { if (stats.conformationCount === 1) { for (let l = offsets[rI], _l = offsets[rI + 1]; l < _l; ++l) { if (k === label_alt_id.value(l)) { - Location.set(stats.firstConformationLoc, unit, l) + Location.set(stats.firstConformationLoc, structure, unit, l) break } } @@ -131,13 +132,13 @@ export namespace Stats { } else { stats.elementCount += size if (stats.elementCount === 1) { - Location.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)]) + Location.set(stats.firstElementLoc, structure, unit, elements[OrderedSet.start(indices)]) } } } } - function handleUnitChainsSimple(stats: Stats, element: Loci['elements'][0]) { + function handleUnitChainsSimple(stats: Stats, structure: Structure, element: Loci['elements'][0]) { const { indices, unit } = element; const size = OrderedSet.size(indices); if (size === 0) return; @@ -148,7 +149,7 @@ export namespace Stats { if (size === elements.length) { stats.chainCount += 1; if (stats.chainCount === 1) { - Location.set(stats.firstChainLoc, unit, elements[OrderedSet.start(indices)]); + Location.set(stats.firstChainLoc, structure, unit, elements[OrderedSet.start(indices)]); } } return; @@ -186,13 +187,13 @@ export namespace Stats { // full chain stats.chainCount += 1; if (stats.chainCount === 1) { - Location.set(stats.firstChainLoc, unit, offsets[cI]); + Location.set(stats.firstChainLoc, structure, unit, offsets[cI]); } } } } - function handleUnitChainsPartitioned(stats: Stats, lociElements: Loci['elements'], start: number, end: number) { + function handleUnitChainsPartitioned(stats: Stats, structure: Structure, lociElements: Loci['elements'], start: number, end: number) { let element = lociElements[start]; // all the elements have the same model since they are part of the same group so this is ok. @@ -273,7 +274,7 @@ export namespace Stats { const eI = elements[OrderedSet.getAt(indices, i)]; const cI = index[eI]; if (cI === firstCI) { - Location.set(stats.firstChainLoc, unit, eI); + Location.set(stats.firstChainLoc, structure, unit, eI); return; } } @@ -289,13 +290,13 @@ export namespace Stats { stats.structureCount += 1 if (stats.structureCount === 1) { const { unit, indices } = loci.elements[0] - Location.set(stats.firstStructureLoc, unit, unit.elements[OrderedSet.min(indices)]) + Location.set(stats.firstStructureLoc, loci.structure, unit, unit.elements[OrderedSet.min(indices)]) } } else { for (const e of loci.elements) { - handleElement(stats, e) + handleElement(stats, loci.structure, e) if (!Unit.Traits.is(e.unit.traits, Unit.Trait.Partitioned)) { - handleUnitChainsSimple(stats, e); + handleUnitChainsSimple(stats, loci.structure, e); } else { hasPartitions = true; } @@ -314,9 +315,9 @@ export namespace Stats { const end = i; i--; if (end - start === 1) { - handleUnitChainsSimple(stats, e); + handleUnitChainsSimple(stats, loci.structure, e); } else { - handleUnitChainsPartitioned(stats, loci.elements, start, end); + handleUnitChainsPartitioned(stats, loci.structure, loci.elements, start, end); } } } diff --git a/src/mol-model/structure/structure/properties.ts b/src/mol-model/structure/structure/properties.ts index 1b11bc22c9f8c627dd19c34f62f520a70eaade1d..48ab835a516d3c50613ad4e5004389db0f6d53a8 100644 --- a/src/mol-model/structure/structure/properties.ts +++ b/src/mol-model/structure/structure/properties.ts @@ -7,6 +7,8 @@ import StructureElement from './element' import Unit from './unit' import { VdwRadius } from '../model/properties/atomic'; +import { ModelSecondaryStructure } from '../../../mol-model-formats/structure/property/secondary-structure'; +import { SecondaryStructureType } from '../model/types'; function p<T>(p: StructureElement.Property<T>) { return p; } @@ -60,7 +62,7 @@ function _compId(l: StructureElement.Location) { function compId(l: StructureElement.Location) { if (!Unit.isAtomic(l.unit)) notAtomic() if (!hasMicroheterogeneity(l)) return _compId(l) - return l.unit.model.sourceData.data.atom_site.label_comp_id.value(l.element) + return l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element]) } function seqId(l: StructureElement.Location) { @@ -103,8 +105,20 @@ const residue = { isNonStandard: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.mon_nstd_flag[0] !== 'y'), hasMicroheterogeneity: p(hasMicroheterogeneity), microheterogeneityCompIds: p(microheterogeneityCompIds), - secondary_structure_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.type[l.unit.residueIndex[l.element]]), - secondary_structure_key: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.key[l.unit.residueIndex[l.element]]), + // TODO implement as symbol in SecondaryStructureProvider (not ModelSecondaryStructure.Provider) + secondary_structure_type: p(l => { + if (!Unit.isAtomic(l.unit)) notAtomic() + const secondaryStructure = ModelSecondaryStructure.Provider.get(l.unit.model) + if (secondaryStructure) return secondaryStructure.type[l.unit.residueIndex[l.element]] + else return SecondaryStructureType.Flag.NA + }), + // TODO implement as symbol in SecondaryStructureProvider (not ModelSecondaryStructure.Provider) + secondary_structure_key: p(l => { + if (!Unit.isAtomic(l.unit)) notAtomic() + const secondaryStructure = ModelSecondaryStructure.Provider.get(l.unit.model) + if (secondaryStructure) return secondaryStructure.key[l.unit.residueIndex[l.element]] + else return -1 + }), chem_comp_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.type), } diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index aae04aa3c2f8fb64156168329e1069e68a3a1af8..c03a2a97b212e350f5bcf609fc421b98bb2036d1 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -401,7 +401,7 @@ function getModels(s: Structure) { function getUniqueResidueNames(s: Structure) { const prop = StructureProperties.residue.label_comp_id; const names = new Set<string>(); - const loc = StructureElement.Location.create(); + const loc = StructureElement.Location.create(s); for (const unit of s.units) { // TODO: support coarse unit? if (!Unit.isAtomic(unit)) continue; @@ -418,7 +418,7 @@ function getUniqueResidueNames(s: Structure) { function getEntityIndices(structure: Structure): ReadonlyArray<EntityIndex> { const { units } = structure; - const l = StructureElement.Location.create(); + const l = StructureElement.Location.create(structure); const keys = UniqueArray.create<number, EntityIndex>(); for (const unit of units) { @@ -871,7 +871,7 @@ namespace Structure { } export class ElementLocationIterator implements Iterator<StructureElement.Location> { - private current = StructureElement.Location.create(); + private current: StructureElement.Location; private unitIndex = 0; private elements: StructureElement.Set; private maxIdx = 0; @@ -905,6 +905,7 @@ namespace Structure { } constructor(private structure: Structure) { + this.current = StructureElement.Location.create(structure); this.hasNext = structure.elementCount > 0; if (this.hasNext) { this.elements = structure.units[0].elements; diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts index 6568f9cb4f41af97513ea1d51376dd958f85f628..e6c5bae4673f2379bfaeb1b2296f905b74613b70 100644 --- a/src/mol-model/structure/structure/symmetry.ts +++ b/src/mol-model/structure/structure/symmetry.ts @@ -10,10 +10,11 @@ import { EquivalenceClasses } from '../../../mol-data/util'; import { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/geometry'; import { Vec3, Mat4 } from '../../../mol-math/linear-algebra'; import { RuntimeContext, Task } from '../../../mol-task'; -import { ModelSymmetry, Model } from '../model'; +import { Symmetry, Model } from '../model'; import { QueryContext, StructureSelection } from '../query'; import Structure from './structure'; import Unit from './unit'; +import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry'; namespace StructureSymmetry { export function buildAssembly(structure: Structure, asmName: string) { @@ -21,7 +22,7 @@ namespace StructureSymmetry { const models = structure.models; if (models.length !== 1) throw new Error('Can only build assemblies from structures based on 1 model.'); - const assembly = ModelSymmetry.findAssembly(models[0], asmName); + const assembly = Symmetry.findAssembly(models[0], asmName); if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`); const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] }) @@ -95,7 +96,7 @@ namespace StructureSymmetry { } } -function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3, modelCenter: Vec3) { +function getOperators(symmetry: Symmetry, ijkMin: Vec3, ijkMax: Vec3, modelCenter: Vec3) { const { spacegroup, ncsOperators } = symmetry; const ncsCount = (ncsOperators && ncsOperators.length) || 0 const operators: SymmetryOperator[] = []; @@ -135,7 +136,7 @@ function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3, model return operators; } -function getOperatorsCached333(symmetry: ModelSymmetry, ref: Vec3) { +function getOperatorsCached333(symmetry: Symmetry, ref: Vec3) { if (symmetry._operators_333 && Vec3.equals(ref, symmetry._operators_333.ref)) { return symmetry._operators_333.operators; } @@ -161,7 +162,10 @@ async function _buildNCS(ctx: RuntimeContext, structure: Structure) { const models = structure.models; if (models.length !== 1) throw new Error('Can only build NCS from structures based on 1 model.'); - const operators = models[0].symmetry.ncsOperators; + const symmetry = ModelSymmetry.Provider.get(models[0]) + if (!symmetry) return structure + + const operators = symmetry.ncsOperators; if (!operators || !operators.length) return structure; return assembleOperators(structure, operators); } @@ -170,11 +174,14 @@ async function findSymmetryRange(ctx: RuntimeContext, structure: Structure, ijkM const models = structure.models; if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.'); - const { spacegroup } = models[0].symmetry; + const symmetry = ModelSymmetry.Provider.get(models[0]) + if (!symmetry) return structure + + const { spacegroup } = symmetry; if (SpacegroupCell.isZero(spacegroup.cell)) return structure; const modelCenter = Model.getCenter(models[0]) - const operators = getOperators(models[0].symmetry, ijkMin, ijkMax, modelCenter); + const operators = getOperators(symmetry, ijkMin, ijkMax, modelCenter); return assembleOperators(structure, operators); } @@ -182,7 +189,9 @@ async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius const models = structure.models; if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.'); - const symmetry = models[0].symmetry; + const symmetry = ModelSymmetry.Provider.get(models[0]) + if (!symmetry) return structure + const { spacegroup } = symmetry; if (SpacegroupCell.isZero(spacegroup.cell)) return structure; diff --git a/src/mol-model/structure/structure/unit/bonds.ts b/src/mol-model/structure/structure/unit/bonds.ts index 698677153f4ef231f917ec2d3627ccec99c1da3d..2cc0c7b298e04d449f22f65a800c6441a6dcd520 100644 --- a/src/mol-model/structure/structure/unit/bonds.ts +++ b/src/mol-model/structure/structure/unit/bonds.ts @@ -19,16 +19,28 @@ export * from './bonds/inter-compute' namespace Bond { export interface Location<U extends Unit = Unit> { readonly kind: 'bond-location', + + aStructure: Structure, aUnit: U, /** Index into aUnit.elements */ aIndex: StructureElement.UnitIndex, + + bStructure: Structure, bUnit: U, /** Index into bUnit.elements */ bIndex: StructureElement.UnitIndex, } - export function Location(aUnit?: Unit, aIndex?: StructureElement.UnitIndex, bUnit?: Unit, bIndex?: StructureElement.UnitIndex): Location { - return { kind: 'bond-location', aUnit: aUnit as any, aIndex: aIndex as any, bUnit: bUnit as any, bIndex: bIndex as any }; + export function Location(aStructure?: Structure, aUnit?: Unit, aIndex?: StructureElement.UnitIndex, bStructure?: Structure, bUnit?: Unit, bIndex?: StructureElement.UnitIndex): Location { + return { + kind: 'bond-location', + aStructure: aStructure as any, + aUnit: aUnit as any, + aIndex: aIndex as any, + bStructure: bStructure as any, + bUnit: bUnit as any, + bIndex: bIndex as any + }; } export function isLocation(x: any): x is Location { @@ -37,6 +49,7 @@ namespace Bond { export function areLocationsEqual(locA: Location, locB: Location) { return ( + locA.aStructure.label === locB.aStructure.label && locA.bStructure.label === locB.bStructure.label && locA.aIndex === locB.aIndex && locA.bIndex === locB.bIndex && locA.aUnit.id === locB.aUnit.id && locA.bUnit.id === locB.bUnit.id ) @@ -86,7 +99,7 @@ namespace Bond { const indexB = SortedArray.indexOf(unitB.elements, elementB) as StructureElement.UnitIndex | -1 if (indexB === -1) return - bonds.push(Location(unitA, indexA, unitB, indexB)) + bonds.push(Location(loci.structure, unitA, indexA, loci.structure, unitB, indexB)) }); return Loci(structure, bonds); 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 2b3d68e415233d0ef4177355f1ed9f2d07aa4f52..053bf322f1b7efa5a96cf9463ae25980555df551 100644 --- a/src/mol-model/structure/structure/unit/bonds/inter-compute.ts +++ b/src/mol-model/structure/structure/unit/bonds/inter-compute.ts @@ -13,11 +13,11 @@ import { InterUnitBonds, InterUnitEdgeProps } from './data'; import { SortedArray } from '../../../../../mol-data/int'; import { Vec3, Mat4 } from '../../../../../mol-math/linear-algebra'; import StructureElement from '../../element'; -import { StructConn } from '../../../../../mol-model-formats/structure/mmcif/bonds'; import { ElementIndex } from '../../../model/indexing'; import { getInterBondOrderFromTable } from '../../../model/properties/atomic/bonds'; -import { IndexPairBonds } from '../../../../../mol-model-formats/structure/mmcif/bonds/index-pair'; +import { IndexPairBonds } from '../../../../../mol-model-formats/structure/property/bonds/index-pair'; import { InterUnitGraph } from '../../../../../mol-math/graph/inter-unit-graph'; +import { StructConn } from '../../../../../mol-model-formats/structure/property/bonds/struct_conn'; const MAX_RADIUS = 4; @@ -46,8 +46,8 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput const { occupancy: occupancyB } = unitB.model.atomicConformation; const { lookup3d } = unitB; - const structConn = unitA.model === unitB.model && unitA.model.sourceData.kind === 'mmCIF' ? StructConn.get(unitA.model) : void 0; - const indexPairs = unitA.model === unitB.model ? IndexPairBonds.get(unitA.model) : void 0; + const structConn = unitA.model === unitB.model && StructConn.Provider.get(unitA.model) + const indexPairs = unitA.model === unitB.model && IndexPairBonds.Provider.get(unitA.model) // the lookup queries need to happen in the "unitB space". // that means imageA = inverseOperB(operA(aI)) @@ -75,18 +75,20 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput continue // assume `indexPairs` supplies all bonds } - const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI); + const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.byAtomIndex.get(aI); if (structConnEntries && structConnEntries.length) { let added = false; for (const se of structConnEntries) { - for (const p of se.partners) { - const _bI = SortedArray.indexOf(unitB.elements, p.atomIndex) as StructureElement.UnitIndex; - if (_bI < 0 || _aI === _bI) continue; - // check if the bond is within MAX_RADIUS for this pair of units - if (getDistance(unitA, aI, unitB, p.atomIndex) > MAX_RADIUS) continue; - builder.add(_aI, _bI, { order: se.order, flag: se.flags }); - added = true; - } + const { partnerA, partnerB } = se + const p = partnerA.atomIndex === aI ? partnerB : partnerA + const _bI = SortedArray.indexOf(unitB.elements, p.atomIndex) as StructureElement.UnitIndex; + if (_bI < 0) continue; + + // check if the bond is within MAX_RADIUS for this pair of units + if (getDistance(unitA, aI, unitB, p.atomIndex) > MAX_RADIUS) continue; + + builder.add(_aI, _bI, { order: se.order, flag: se.flags }); + added = true; } // assume, for an atom, that if any inter unit bond is given // all are given and thus we don't need to compute any other 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 10327a44a84dbe743128edecb7501666093dd5e4..1ba15206b2a9a0e6ddad536477760e9b792140ca 100644 --- a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts +++ b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-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,10 +11,11 @@ import Unit from '../../unit' import { IntAdjacencyGraph } from '../../../../../mol-math/graph'; import { BondComputationProps, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, getElementPairThreshold, DefaultBondComputationProps } from './common'; import { SortedArray } from '../../../../../mol-data/int'; -import { StructConn, ComponentBond } from '../../../../../mol-model-formats/structure/mmcif/bonds'; import { getIntraBondOrderFromTable } from '../../../model/properties/atomic/bonds'; import StructureElement from '../../element'; -import { IndexPairBonds } from '../../../../../mol-model-formats/structure/mmcif/bonds/index-pair'; +import { IndexPairBonds } from '../../../../../mol-model-formats/structure/property/bonds/index-pair'; +import { ComponentBond } from '../../../../../mol-model-formats/structure/property/bonds/comp'; +import { StructConn } from '../../../../../mol-model-formats/structure/property/bonds/struct_conn'; function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], atomCount: number): IntraUnitBonds { const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB); @@ -29,6 +30,8 @@ function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.U return builder.createGraph({ flags, order }); } +const __structConnAdded = new Set<StructureElement.UnitIndex>(); + function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBonds { const MAX_RADIUS = 4; @@ -39,9 +42,9 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni const { label_comp_id } = unit.model.atomicHierarchy.residues; const query3d = unit.lookup3d; - const structConn = unit.model.sourceData.kind === 'mmCIF' ? StructConn.get(unit.model) : void 0; - const component = unit.model.sourceData.kind === 'mmCIF' ? ComponentBond.get(unit.model) : void 0; - const indexPairs = IndexPairBonds.get(unit.model) + const structConn = StructConn.Provider.get(unit.model) + const component = ComponentBond.Provider.get(unit.model) + const indexPairs = IndexPairBonds.Provider.get(unit.model) const atomA: StructureElement.UnitIndex[] = []; const atomB: StructureElement.UnitIndex[] = []; @@ -51,6 +54,8 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni let lastResidue = -1; let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0; + const structConnAdded = __structConnAdded; + for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) { const aI = atoms[_aI]; @@ -66,19 +71,26 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni continue // assume `indexPairs` supplies all bonds } - const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI); - const structConnAdded = new Set<StructureElement.UnitIndex>() + const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.byAtomIndex.get(aI); + let hasStructConn = false; if (structConnEntries) { for (const se of structConnEntries) { - for (const p of se.partners) { - const _bI = SortedArray.indexOf(unit.elements, p.atomIndex) as StructureElement.UnitIndex; - if (_bI < 0 || _aI === _bI) continue; - atomA[atomA.length] = _aI; - atomB[atomB.length] = _bI; - flags[flags.length] = se.flags; - order[order.length] = se.order; - structConnAdded.add(_bI) - } + const { partnerA, partnerB } = se + // symmetry must be the same for intra-unit bonds + if (partnerA.symmetry !== partnerB.symmetry) continue + + const p = partnerA.atomIndex === aI ? partnerB : partnerA + const _bI = SortedArray.indexOf(unit.elements, p.atomIndex) as StructureElement.UnitIndex; + if (_bI < 0) continue; + + atomA[atomA.length] = _aI; + atomB[atomB.length] = _bI; + flags[flags.length] = se.flags; + order[order.length] = se.order; + + if (!hasStructConn) structConnAdded.clear(); + hasStructConn = true; + structConnAdded.add(_bI); } } @@ -106,7 +118,7 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni for (let ni = 0; ni < count; ni++) { const _bI = indices[ni]; - if (structConnAdded.has(_bI)) continue; + if (hasStructConn && structConnAdded.has(_bI)) continue; const bI = atoms[_bI]; if (bI <= aI) continue; diff --git a/src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts b/src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts index 2cddb0e9c5cfd3ce0d972f1ba3f681c91b993d1e..0b98c288cd7e44f5409655d1919e232a726fdb0b 100644 --- a/src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts +++ b/src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts @@ -8,9 +8,9 @@ import Unit from '../../unit'; import Structure from '../../structure'; import { PairRestraints, CrossLinkRestraint } from './data'; import { StructureElement } from '../../../structure'; -import { IHMCrossLinkRestraint } from '../../../../../mol-model-formats/structure/mmcif/pair-restraint'; +import { ModelCrossLinkRestraint } from '../../../../../mol-model-formats/structure/property/pair-restraints/cross-links'; -function _addRestraints(map: Map<number, number>, unit: Unit, restraints: IHMCrossLinkRestraint) { +function _addRestraints(map: Map<number, number>, unit: Unit, restraints: ModelCrossLinkRestraint) { const { elements } = unit; const elementCount = elements.length; const kind = unit.kind @@ -25,7 +25,7 @@ function extractInter(pairs: CrossLinkRestraint[], unitA: Unit, unitB: Unit) { if (unitA.model !== unitB.model) return if (unitA.model.sourceData.kind !== 'mmCIF') return - const restraints = IHMCrossLinkRestraint.fromModel(unitA.model) + const restraints = ModelCrossLinkRestraint.Provider.get(unitA.model) if (!restraints) return const rA = new Map<number, StructureElement.UnitIndex>(); @@ -47,7 +47,7 @@ function extractInter(pairs: CrossLinkRestraint[], unitA: Unit, unitB: Unit) { function extractIntra(pairs: CrossLinkRestraint[], unit: Unit) { if (unit.model.sourceData.kind !== 'mmCIF') return - const restraints = IHMCrossLinkRestraint.fromModel(unit.model) + const restraints = ModelCrossLinkRestraint.Provider.get(unit.model) if (!restraints) return const { elements } = unit; @@ -75,7 +75,7 @@ function extractIntra(pairs: CrossLinkRestraint[], unit: Unit) { }) } -function createCrossLinkRestraint(unitA: Unit, indexA: StructureElement.UnitIndex, unitB: Unit, indexB: StructureElement.UnitIndex, restraints: IHMCrossLinkRestraint, row: number): CrossLinkRestraint { +function createCrossLinkRestraint(unitA: Unit, indexA: StructureElement.UnitIndex, unitB: Unit, indexB: StructureElement.UnitIndex, restraints: ModelCrossLinkRestraint, row: number): CrossLinkRestraint { return { unitA, indexA, unitB, indexB, @@ -89,7 +89,7 @@ function createCrossLinkRestraint(unitA: Unit, indexA: StructureElement.UnitInde function extractCrossLinkRestraints(structure: Structure): PairRestraints<CrossLinkRestraint> { const pairs: CrossLinkRestraint[] = [] - if (!structure.models.some(m => IHMCrossLinkRestraint.fromModel(m))) { + if (!structure.models.some(m => ModelCrossLinkRestraint.Provider.get(m))) { return new PairRestraints(pairs) } diff --git a/src/mol-model/structure/structure/util/lookup3d.ts b/src/mol-model/structure/structure/util/lookup3d.ts index c10e03095960cdbd6e8044158a7802c700419d6e..e3ac9d7dbc713d32db378d5e11ac223c350b28d4 100644 --- a/src/mol-model/structure/structure/util/lookup3d.ts +++ b/src/mol-model/structure/structure/util/lookup3d.ts @@ -100,7 +100,7 @@ export class StructureLookup3D { const closeUnits = this.unitLookup.find(x, y, z, radius); if (closeUnits.count === 0) return; - const se = StructureElement.Location.create(); + const se = StructureElement.Location.create(this.structure); const queryRadius = pivotR + maxRadius + radius; for (let t = 0, _t = closeUnits.count; t < _t; t++) { diff --git a/src/mol-model/structure/structure/util/unit-transforms.ts b/src/mol-model/structure/structure/util/unit-transforms.ts index 9a7be99d81c87d0dd07069336db9b7cda6782416..f2de9309b2d0d65654b581b2d3e772dcc1d4c609 100644 --- a/src/mol-model/structure/structure/util/unit-transforms.ts +++ b/src/mol-model/structure/structure/util/unit-transforms.ts @@ -18,8 +18,10 @@ export class StructureUnitTransforms { private unitOffsetMap = IntMap.Mutable<number>(); private groupIndexMap = IntMap.Mutable<number>(); private size: number; - + private _isIdentity: boolean | undefined = undefined; + + version = 0; constructor(readonly structure: Structure) { this.unitTransforms = new Float32Array(structure.units.length * 16) @@ -39,6 +41,7 @@ export class StructureUnitTransforms { } reset() { + this.version = 0; fillIdentityTransform(this.unitTransforms, this.size); this._isIdentity = true } @@ -58,6 +61,7 @@ export class StructureUnitTransforms { } setTransform(matrix: Mat4, unit: Unit) { + this.version = (this.version + 1) % 0x7fffffff; Mat4.toArray(matrix, this.unitTransforms, this.unitOffsetMap.get(unit.id)) this._isIdentity = undefined } diff --git a/src/mol-model/structure/topology/topology.ts b/src/mol-model/structure/topology/topology.ts index d5e6dbac27cde544ce95dc6784bbb880a2eeab67..6ef843f72f937e518baaa489571ca361759bc377 100644 --- a/src/mol-model/structure/topology/topology.ts +++ b/src/mol-model/structure/topology/topology.ts @@ -1,11 +1,12 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { UUID } from '../../../mol-util'; import { Column } from '../../../mol-data/db'; +import { BasicData } from '../../../mol-model-formats/structure/basic/schema'; import { ModelFormat } from '../../../mol-model-formats/structure/format'; export { Topology } @@ -14,7 +15,8 @@ interface Topology { readonly id: UUID readonly label: string - readonly format: ModelFormat + readonly basic: BasicData + readonly sourceData: ModelFormat readonly bonds: { readonly indexA: Column<number>, @@ -47,11 +49,12 @@ interface Topology { } namespace Topology { - export function create(label: string, format: ModelFormat, bonds: Topology['bonds']): Topology { + export function create(label: string, basic: BasicData, bonds: Topology['bonds'], format: ModelFormat): Topology { return { id: UUID.create22(), label, - format, + basic, + sourceData: format, bonds } } diff --git a/src/mol-plugin-ui/controls/parameters.tsx b/src/mol-plugin-ui/controls/parameters.tsx index a942c12439df0d7fdcc595e26d000c8ea361e3fc..49363d5cd68a1fa21a03855f4ffe426dd887a1a4 100644 --- a/src/mol-plugin-ui/controls/parameters.tsx +++ b/src/mol-plugin-ui/controls/parameters.tsx @@ -59,6 +59,7 @@ function controlFor(param: PD.Any): ParamControl | undefined { case 'color-list': return ColorListControl; case 'vec3': return Vec3Control; case 'file': return FileControl; + case 'file-list': return FileListControl; case 'select': return SelectControl; case 'text': return TextControl; case 'interval': return typeof param.min !== 'undefined' && typeof param.max !== 'undefined' @@ -436,7 +437,6 @@ export class Vec3Control extends React.PureComponent<ParamProps<PD.Vec3>, { isEx } } - export class FileControl extends React.PureComponent<ParamProps<PD.FileParam>> { change(value: File) { this.props.onChange({ name: this.props.name, param: this.props.param, value }); @@ -449,13 +449,40 @@ export class FileControl extends React.PureComponent<ParamProps<PD.FileParam>> { render() { const value = this.props.value; - // return <input disabled={this.props.isDisabled} value={void 0} type='file' multiple={false} /> return <div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file' style={{ marginTop: '1px' }}> {value ? value.name : 'Select a file...'} <input disabled={this.props.isDisabled} onChange={this.onChangeFile} type='file' multiple={false} accept={this.props.param.accept} /> </div> } } +export class FileListControl extends React.PureComponent<ParamProps<PD.FileListParam>> { + change(value: FileList) { + this.props.onChange({ name: this.props.name, param: this.props.param, value }); + } + + onChangeFileList = (e: React.ChangeEvent<HTMLInputElement>) => { + this.change(e.target.files!); + } + + render() { + const value = this.props.value; + + const names: string[] = [] + if (value) { + for (let i = 0, il = value.length; i < il; ++i) { + names.push(value[i].name) + } + } + const label = names.length === 0 + ? 'Select files...' : names.length === 1 + ? names[0] : `${names.length} files selected` + + return <div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file' style={{ marginTop: '1px' }}> + {label} <input disabled={this.props.isDisabled} onChange={this.onChangeFileList} type='file' multiple={true} accept={this.props.param.accept} /> + </div> + } +} + export class MultiSelectControl extends React.PureComponent<ParamProps<PD.MultiSelect<any>>, { isExpanded: boolean }> { state = { isExpanded: false } diff --git a/src/mol-plugin-ui/sequence.tsx b/src/mol-plugin-ui/sequence.tsx index 1ab7f214fdab49c0aa8b6955fa40b0ba797c2fd9..f89f119ee02447590f1c133c4654f8fad497bba9 100644 --- a/src/mol-plugin-ui/sequence.tsx +++ b/src/mol-plugin-ui/sequence.tsx @@ -39,13 +39,13 @@ function splitModelEntityId(modelEntityId: string) { function getSequenceWrapper(state: SequenceViewState, structureSelection: StructureElementSelectionManager): SequenceWrapper.Any | string { const { structure, modelEntityId, chainGroupId, operatorKey } = state - const l = StructureElement.Location.create() + const l = StructureElement.Location.create(structure) const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId) const units: Unit[] = [] for (const unit of structure.units) { - StructureElement.Location.set(l, unit, unit.elements[0]) + StructureElement.Location.set(l, structure, unit, unit.elements[0]) if (structure.getModelIndex(unit.model) !== modelIdx) continue if (SP.entity.id(l) !== entityId) continue if (unit.chainGroupId !== chainGroupId) continue @@ -60,7 +60,7 @@ function getSequenceWrapper(state: SequenceViewState, structureSelection: Struct let sw: SequenceWrapper<any> if (unit.polymerElements.length) { - const l = StructureElement.Location.create(unit, unit.elements[0]) + const l = StructureElement.Location.create(structure, unit, unit.elements[0]) const entitySeq = unit.model.sequence.byEntityKey[SP.entity.key(l)] // check if entity sequence is available if (entitySeq && entitySeq.sequence.length <= MaxDisplaySequenceLength) { @@ -92,11 +92,11 @@ function getSequenceWrapper(state: SequenceViewState, structureSelection: Struct function getModelEntityOptions(structure: Structure) { const options: [string, string][] = [] - const l = StructureElement.Location.create() + const l = StructureElement.Location.create(structure) const seen = new Set<string>() for (const unit of structure.units) { - StructureElement.Location.set(l, unit, unit.elements[0]) + StructureElement.Location.set(l, structure, unit, unit.elements[0]) const id = SP.entity.id(l) const modelIdx = structure.getModelIndex(unit.model) const key = `${modelIdx}|${id}` @@ -121,12 +121,12 @@ function getModelEntityOptions(structure: Structure) { function getChainOptions(structure: Structure, modelEntityId: string) { const options: [number, string][] = [] - const l = StructureElement.Location.create() + const l = StructureElement.Location.create(structure) const seen = new Set<number>() const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId) for (const unit of structure.units) { - StructureElement.Location.set(l, unit, unit.elements[0]) + StructureElement.Location.set(l, structure, unit, unit.elements[0]) if (structure.getModelIndex(unit.model) !== modelIdx) continue if (SP.entity.id(l) !== entityId) continue @@ -147,12 +147,12 @@ function getChainOptions(structure: Structure, modelEntityId: string) { function getOperatorOptions(structure: Structure, modelEntityId: string, chainGroupId: number) { const options: [string, string][] = [] - const l = StructureElement.Location.create() + const l = StructureElement.Location.create(structure) const seen = new Set<string>() const [ modelIdx, entityId ] = splitModelEntityId(modelEntityId) for (const unit of structure.units) { - StructureElement.Location.set(l, unit, unit.elements[0]) + StructureElement.Location.set(l, structure, unit, unit.elements[0]) if (structure.getModelIndex(unit.model) !== modelIdx) continue if (SP.entity.id(l) !== entityId) continue if (unit.chainGroupId !== chainGroupId) continue diff --git a/src/mol-plugin-ui/sequence/chain.ts b/src/mol-plugin-ui/sequence/chain.ts index b3e53700e3263f3dd3ed84a57a46962300ba4999..e15f691ec47f4f00861965eaaa6816e579f1c1b5 100644 --- a/src/mol-plugin-ui/sequence/chain.ts +++ b/src/mol-plugin-ui/sequence/chain.ts @@ -54,14 +54,14 @@ export class ChainSequenceWrapper extends SequenceWrapper<StructureUnit> { let residueCount = 0 let elementCount = 0 const counts: string[] = [] - const l = StructureElement.Location.create() + const l = StructureElement.Location.create(data.structure) const unitIndices = new Map<number, Interval<StructureElement.UnitIndex>>() const lociElements: StructureElement.Loci['elements'][0][] = [] for (let i = 0, il = data.units.length; i < il; ++i) { const unit = data.units[i] - StructureElement.Location.set(l, unit, unit.elements[0]) + StructureElement.Location.set(l, data.structure, unit, unit.elements[0]) const entitySeq = unit.model.sequence.byEntityKey[StructureProperties.entity.key(l)] if (entitySeq) residueCount += entitySeq.sequence.length elementCount += unit.elements.length diff --git a/src/mol-plugin-ui/sequence/polymer.ts b/src/mol-plugin-ui/sequence/polymer.ts index 1402a8888735197aeb3f8d9b972a826a58142838..2073dd4bb284c9d726457bac65348a3adb86bb2c 100644 --- a/src/mol-plugin-ui/sequence/polymer.ts +++ b/src/mol-plugin-ui/sequence/polymer.ts @@ -66,7 +66,7 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> { } constructor(data: StructureUnit) { - const l = StructureElement.Location.create(data.units[0], data.units[0].elements[0]) + const l = StructureElement.Location.create(data.structure, data.units[0], data.units[0].elements[0]) const entitySeq = data.units[0].model.sequence.byEntityKey[SP.entity.key(l)] const length = entitySeq.sequence.length diff --git a/src/mol-plugin-ui/sequence/sequence.tsx b/src/mol-plugin-ui/sequence/sequence.tsx index f1ef4b5ffbf1ac949c9b014479c349e5ae725ba2..3ff18fdcefbbfa270195d2cc2cb1a4059e0ca239 100644 --- a/src/mol-plugin-ui/sequence/sequence.tsx +++ b/src/mol-plugin-ui/sequence/sequence.tsx @@ -176,7 +176,7 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> { return classList.join(' ') } - private location = StructureElement.Location.create(); + private location = StructureElement.Location.create(void 0); private getSequenceNumber(seqIdx: number) { let seqNum = '' const loci = this.props.sequenceWrapper.getLoci(seqIdx) diff --git a/src/mol-plugin-ui/viewport/simple-settings.tsx b/src/mol-plugin-ui/viewport/simple-settings.tsx index 699c7c484d49c87f962ce8c3c57540b2c920ef65..387e0154319e1b650725ccdc27eeaecbc10c2596 100644 --- a/src/mol-plugin-ui/viewport/simple-settings.tsx +++ b/src/mol-plugin-ui/viewport/simple-settings.tsx @@ -21,7 +21,7 @@ const SimpleSettingsParams = { 'transparent': PD.EmptyGroup(), 'opaque': PD.Group({ color: PD.Color(Color(0xFCFBF9), { description: 'Custom background color' }) }, { isFlat: true }) }, { description: 'Background of the 3D canvas' }), - renderStyle: PD.Select('glossy', [['toon', 'Toon'], ['matte', 'Matte'], ['glossy', 'Glossy'], ['metallic', 'Metallic']], { description: 'Style in which the 3D scene is rendered' }), + renderStyle: PD.Select('glossy', [['flat', 'Flat'], ['matte', 'Matte'], ['glossy', 'Glossy'], ['metallic', 'Metallic']], { description: 'Style in which the 3D scene is rendered' }), occlusion: PD.Boolean(false, { description: 'Darken occluded crevices with the ambient occlusion effect' }), outline: PD.Boolean(false, { description: 'Draw outline around 3D objects' }), fog: PD.Boolean(false, { description: 'Show fog in the distance' }), @@ -49,7 +49,7 @@ export class SimpleSettingsControl extends PluginUIComponent { if (!this.plugin.canvas3d) return; const renderer = this.plugin.canvas3d.props.renderer; - if (p.value === 'toon') { + if (p.value === 'flat') { PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, lightIntensity: 0, ambientIntensity: 1, roughness: 0.4, metalness: 0 } } }); @@ -80,7 +80,7 @@ export class SimpleSettingsControl extends PluginUIComponent { } }); } else if (p.name === 'fog') {; PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { - cameraFog: p.value ? 50 : 1, + cameraFog: p.value ? 50 : 0, } }); } else if (p.name === 'clipFar') {; PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { @@ -97,7 +97,7 @@ export class SimpleSettingsControl extends PluginUIComponent { if (renderer) { if (renderer.lightIntensity === 0 && renderer.ambientIntensity === 1 && renderer.roughness === 0.4 && renderer.metalness === 0) { - renderStyle = 'toon' + renderStyle = 'flat' } else if (renderer.lightIntensity === 0.6 && renderer.ambientIntensity === 0.4) { if (renderer.roughness === 1 && renderer.metalness === 0) { renderStyle = 'matte' diff --git a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts index 2c35e078ea35a00d2b138ed09085be460b7ab257..69742b2992896625fd383526de1a7e8183c90520 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts @@ -6,13 +6,11 @@ import { OrderedSet } from '../../../../../mol-data/int'; import { StructureQualityReport, StructureQualityReportProvider } from '../../../../../mol-model-props/pdbe/structure-quality-report'; -import { StructureQualityReportColorTheme } from '../../../../../mol-model-props/pdbe/themes/structure-quality-report'; +import { StructureQualityReportColorThemeProvider } from '../../../../../mol-model-props/pdbe/themes/structure-quality-report'; import { Loci } from '../../../../../mol-model/loci'; import { StructureElement } from '../../../../../mol-model/structure'; import { ParamDefinition as PD } from '../../../../../mol-util/param-definition'; import { PluginBehavior } from '../../../behavior'; -import { ThemeDataContext } from '../../../../../mol-theme/theme'; -import { CustomProperty } from '../../../../../mol-model-props/common/custom-property'; export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({ name: 'pdbe-structure-quality-report-prop', @@ -32,7 +30,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo const u = e.unit; if (!u.model.customProperties.has(StructureQualityReportProvider.descriptor)) return void 0; - const se = StructureElement.Location.create(u, u.elements[OrderedSet.getAt(e.indices, 0)]); + const se = StructureElement.Location.create(loci.structure, u, u.elements[OrderedSet.getAt(e.indices, 0)]); const issues = StructureQualityReport.getIssues(se); if (issues.length === 0) return 'PDBe Validation: No Issues'; return `PDBe Validation: ${issues.join(', ')}`; @@ -45,16 +43,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo this.ctx.customModelProperties.register(this.provider, false); this.ctx.lociLabels.addProvider(this.labelPDBeValidation); - this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add('pdbe-structure-quality-report', { - label: 'PDBe Structure Quality Report', - factory: StructureQualityReportColorTheme, - getParams: () => ({}), - defaultValues: {}, - isApplicable: (ctx: ThemeDataContext) => StructureQualityReport.isApplicable(ctx.structure?.models[0]), - ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => { - return data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve() - } - }) + this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add('pdbe-structure-quality-report', StructureQualityReportColorThemeProvider) } update(p: { autoAttach: boolean, showTooltip: boolean }) { diff --git a/src/mol-plugin/behavior/dynamic/representation.ts b/src/mol-plugin/behavior/dynamic/representation.ts index 9f3a48c3ae6a0795b89798f77d1685aca4d19c54..e945aeab6b78486381c78f4802c5f1a95e117b8e 100644 --- a/src/mol-plugin/behavior/dynamic/representation.ts +++ b/src/mol-plugin/behavior/dynamic/representation.ts @@ -45,7 +45,7 @@ export const HighlightLoci = PluginBehavior.create({ } register() { this.subscribeObservable(this.ctx.behaviors.interaction.hover, ({ current, buttons, modifiers }) => { - if (!this.ctx.canvas3d) return + if (!this.ctx.canvas3d || this.ctx.isBusy) return let matched = false if (Binding.match(this.params.bindings.hoverHighlightOnly, buttons, modifiers)) { @@ -132,7 +132,7 @@ export const SelectLoci = PluginBehavior.create({ }) this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, button, modifiers }) => { - if (!this.ctx.canvas3d) return + if (!this.ctx.canvas3d || this.ctx.isBusy) return; // only trigger the 1st action that matches for (const [binding, action, condition] of actions) { diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/util.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/util.ts index ea703fe3691773819183ba7f215374de547d1af7..0f1d5ecca749050a9ef7e3f7e0aa388d53e21dd5 100644 --- a/src/mol-plugin/behavior/dynamic/volume-streaming/util.ts +++ b/src/mol-plugin/behavior/dynamic/volume-streaming/util.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * 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> @@ -9,30 +9,31 @@ import { Structure, Model } from '../../../../mol-model/structure'; import { VolumeServerInfo } from './model'; import { PluginContext } from '../../../../mol-plugin/context'; import { RuntimeContext } from '../../../../mol-task'; +import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif'; export function getStreamingMethod(s?: Structure, defaultKind: VolumeServerInfo.Kind = 'x-ray'): VolumeServerInfo.Kind { if (!s) return defaultKind; const model = s.models[0]; - if (model.sourceData.kind !== 'mmCIF') return defaultKind; + if (!MmcifFormat.is(model.sourceData)) return defaultKind; - const { data } = model.sourceData; + const { db } = model.sourceData.data; // prefer EMDB entries over structure-factors (SF) e.g. for 'ELECTRON CRYSTALLOGRAPHY' entries // like 6axz or 6kj3 for which EMDB entries are available but map calculation from SF is hard - for (let i = 0, il = data.pdbx_database_related._rowCount; i < il; ++i) { - if (data.pdbx_database_related.db_name.value(i).toUpperCase() === 'EMDB') { + for (let i = 0, il = db.pdbx_database_related._rowCount; i < il; ++i) { + if (db.pdbx_database_related.db_name.value(i).toUpperCase() === 'EMDB') { return 'em' } } - if (data.pdbx_database_status.status_code_sf.isDefined && data.pdbx_database_status.status_code_sf.value(0) === 'REL') { + if (db.pdbx_database_status.status_code_sf.isDefined && db.pdbx_database_status.status_code_sf.value(0) === 'REL') { return 'x-ray' } // fallbacks - for (let i = 0; i < data.exptl.method.rowCount; i++) { - const v = data.exptl.method.value(i).toUpperCase(); + for (let i = 0; i < db.exptl.method.rowCount; i++) { + const v = db.exptl.method.value(i).toUpperCase(); if (v.indexOf('MICROSCOPY') >= 0) return 'em'; } return defaultKind; @@ -41,9 +42,9 @@ export function getStreamingMethod(s?: Structure, defaultKind: VolumeServerInfo. /** Returns EMD ID when available, otherwise falls back to PDB ID */ export function getEmIds(model: Model): string[] { const ids: string[] = [] - if (model.sourceData.kind !== 'mmCIF') return [ model.entryId ] + if (!MmcifFormat.is(model.sourceData)) return [ model.entryId ] - const { db_id, db_name, content_type } = model.sourceData.data.pdbx_database_related + const { db_id, db_name, content_type } = model.sourceData.data.db.pdbx_database_related if (!db_name.isDefined) return [ model.entryId ] for (let i = 0, il = db_name.rowCount; i < il; ++i) { diff --git a/src/mol-plugin/behavior/static/camera.ts b/src/mol-plugin/behavior/static/camera.ts index 0fc627654b28bab00b8a4c7b1cfafdcc114c6f70..32971d8edc6ae922e406d40e21293f20c74290a0 100644 --- a/src/mol-plugin/behavior/static/camera.ts +++ b/src/mol-plugin/behavior/static/camera.ts @@ -16,7 +16,7 @@ export function registerDefault(ctx: PluginContext) { export function Reset(ctx: PluginContext) { PluginCommands.Camera.Reset.subscribe(ctx, () => { - ctx.canvas3d?.resetCamera(); + ctx.canvas3d?.requestCameraReset(); }) } diff --git a/src/mol-plugin/behavior/static/representation.ts b/src/mol-plugin/behavior/static/representation.ts index 8cdbe6ad31c5e7fc7e2b02dc6060a070e0e81885..4bc083eda3a22c68e928bb84fa804efd53a08c81 100644 --- a/src/mol-plugin/behavior/static/representation.ts +++ b/src/mol-plugin/behavior/static/representation.ts @@ -20,7 +20,7 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) { ctx.events.canvas3d.initialized.subscribe(() => { ctx.canvas3d?.reprCount.subscribe(v => { - if (reprCount === 0) ctx.canvas3d?.resetCamera(); + if (reprCount === 0) ctx.canvas3d?.requestCameraReset(); reprCount = v; }); }) diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 591d9763e840dc14302bc7ed3bf9c1d0da2e16db..802a6ad2c8aa0697b026055c97056bdf04209d00 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -192,6 +192,11 @@ export class PluginContext { */ readonly fetch = ajaxGet + /** return true is animating or updating */ + get isBusy() { + return this.behaviors.state.isAnimating.value || this.behaviors.state.isUpdating.value; + } + runTask<T>(task: Task<T>) { return this.tasks.run(task); } diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 51a4ee96fc85fecbf830c482b3d4c0cf108e71d9..9c43953cd18827be185aeac38a5988635e214bc9 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -25,7 +25,7 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Action(StateActions.Structure.DownloadStructure), PluginSpec.Action(StateActions.Structure.AddTrajectory), PluginSpec.Action(StateActions.Volume.DownloadDensity), - PluginSpec.Action(StateActions.DataFormat.OpenFile), + PluginSpec.Action(StateActions.DataFormat.OpenFiles), PluginSpec.Action(StateActions.Structure.Create3DRepresentationPreset), PluginSpec.Action(StateActions.Structure.Remove3DRepresentationPreset), PluginSpec.Action(StateActions.Structure.EnableModelCustomProps), diff --git a/src/mol-plugin/state/actions/data-format.ts b/src/mol-plugin/state/actions/data-format.ts index 75ae09650b465dcce5012b5bbac8cd058b7a3c5b..a759f94eb6f85cbffa670868f29e62318eb5ab2b 100644 --- a/src/mol-plugin/state/actions/data-format.ts +++ b/src/mol-plugin/state/actions/data-format.ts @@ -119,36 +119,35 @@ export interface DataFormatProvider<D extends PluginStateObject.Data.Binary | Pl // -export const OpenFile = StateAction.build({ - display: { name: 'Open File', description: 'Load a file and optionally create its default visuals' }, +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 { - file: PD.File({ accept: Array.from(extensions.values()).map(e => `.${e}`).join(',') + ',.gz,.zip' }), + 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 }, ctx: PluginContext) => Task.create('Open File', async taskCtx => { - const info = getFileInfo(params.file) - const data = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: ctx.dataFormat.registry.binaryExtensions.has(info.ext) }); - const dataStateObject = await state.updateTree(data).runInContext(taskCtx); - - // Alternative for more complex states where the builder is not a simple StateBuilder.To<>: - /* - const dataRef = dataTree.ref; - await state.updateTree(dataTree).runInContext(taskCtx); - const dataCell = state.select(dataRef)[0]; - */ - - // const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) }); - - const provider = params.format === 'auto' ? ctx.dataFormat.registry.auto(info, dataStateObject) : ctx.dataFormat.registry.get(params.format) - const b = state.build().to(data.ref); - const options = { visuals: params.visuals } - // need to await the 2nd update the so that the enclosing Task finishes after the update is done. - await provider.getDefaultBuilder(ctx, b, options, state).runInContext(taskCtx) +})(({ params, state }, ctx: PluginContext) => Task.create('Open Files', async taskCtx => { + for (let i = 0, il = params.files.length; i < il; ++i) { + try { + const file = params.files[i] + const info = getFileInfo(file) + const isBinary = ctx.dataFormat.registry.binaryExtensions.has(info.ext) + const data = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file, isBinary }); + const dataStateObject = await state.updateTree(data).runInContext(taskCtx); + const provider = params.format === 'auto' + ? ctx.dataFormat.registry.auto(info, dataStateObject) + : ctx.dataFormat.registry.get(params.format) + const b = state.build().to(data.ref); + // need to await so that the enclosing Task finishes after the update is done. + await provider.getDefaultBuilder(ctx, b, { visuals: params.visuals }, state).runInContext(taskCtx) + } catch (e) { + ctx.log.error(e) + } + } })); // diff --git a/src/mol-plugin/state/representation/model.ts b/src/mol-plugin/state/representation/model.ts index 143d8ce61c0504cbb6153ed239bd62f4ce5ed3e5..47abb3649f33ac19334f3edca6fddc52675692c9 100644 --- a/src/mol-plugin/state/representation/model.ts +++ b/src/mol-plugin/state/representation/model.ts @@ -11,13 +11,16 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { Vec3 } from '../../../mol-math/linear-algebra'; import { RuntimeContext } from '../../../mol-task'; import { PluginContext } from '../../context'; -import { Assembly, ModelSymmetry } from '../../../mol-model/structure/model/properties/symmetry'; +import { Assembly, Symmetry } from '../../../mol-model/structure/model/properties/symmetry'; import { PluginStateObject as SO } from '../objects'; +import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry'; export namespace ModelStructureRepresentation { export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates') { - const assemblyIds = model ? model.symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]) : []; - const showSymm = !model ? true : !SpacegroupCell.isZero(model.symmetry.spacegroup.cell); + const symmetry = model && ModelSymmetry.Provider.get(model) + + const assemblyIds = symmetry ? symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]) : []; + const showSymm = !symmetry ? true : !SpacegroupCell.isZero(symmetry.spacegroup.cell); const modes = { deposited: PD.EmptyGroup(), @@ -58,17 +61,19 @@ export namespace ModelStructureRepresentation { async function buildAssembly(plugin: PluginContext, ctx: RuntimeContext, model: Model, id?: string) { let asm: Assembly | undefined = void 0; + const symmetry = ModelSymmetry.Provider.get(model) + // if no id is specified, use the 1st assembly. - if (!id && model.symmetry.assemblies.length !== 0) { - id = model.symmetry.assemblies[0].id; + if (!id && symmetry && symmetry.assemblies.length !== 0) { + id = symmetry.assemblies[0].id; } - if (model.symmetry.assemblies.length === 0) { + if (!symmetry || symmetry.assemblies.length === 0) { if (id !== 'deposited') { plugin.log.warn(`Model '${model.entryId}' has no assembly, returning deposited structure.`); } } else { - asm = ModelSymmetry.findAssembly(model, id || ''); + asm = Symmetry.findAssembly(model, id || ''); if (!asm) { plugin.log.warn(`Model '${model.entryId}' has no assembly called '${id}', returning deposited structure.`); } diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index 77a364905ab46d5199b7455d6d0942c4477dd3d1..d593a97b01f8ae8f2aa79c5d5577ee468e0eee94 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.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> @@ -32,6 +32,7 @@ import { parseDcd } from '../../../mol-io/reader/dcd/parser'; import { coordinatesFromDcd } from '../../../mol-model-formats/structure/dcd'; import { topologyFromPsf } from '../../../mol-model-formats/structure/psf'; import { deepEqual } from '../../../mol-util'; +import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry'; export { CoordinatesFromDcd }; export { TopologyFromPsf }; @@ -300,8 +301,8 @@ const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({ if (!a) { return { id: PD.Optional(PD.Text('', { label: 'Assembly Id', description: 'Assembly Id. Value \'deposited\' can be used to specify deposited asymmetric unit.' })) }; } - const model = a.data; - const ids = model.symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]); + const assemblies = ModelSymmetry.Provider.get(a.data)?.assemblies || [] + const ids = assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]); ids.push(['deposited', 'Deposited']); return { id: PD.Optional(PD.Select(ids[0][0], ids, { label: 'Asm Id', description: 'Assembly Id' })) @@ -713,12 +714,13 @@ const CustomModelProperties = PluginStateTransform.BuiltIn({ }); } }); -async function attachModelProps(model: Model, ctx: PluginContext, taskCtx: RuntimeContext, params: PD.Values<PD.Params>) { +async function attachModelProps(model: Model, ctx: PluginContext, taskCtx: RuntimeContext, params: ReturnType<CustomModelProperties['createDefaultParams']>) { const propertyCtx = { runtime: taskCtx, fetch: ctx.fetch } - for (const name of Object.keys(params)) { + const { autoAttach, properties } = params + for (const name of Object.keys(properties)) { const property = ctx.customModelProperties.get(name) const props = params[name as keyof typeof params] - if (props.autoAttach) { + if (autoAttach.includes(name)) { try { await property.attach(propertyCtx, model, props) } catch (e) { @@ -747,12 +749,13 @@ const CustomStructureProperties = PluginStateTransform.BuiltIn({ }); } }); -async function attachStructureProps(structure: Structure, ctx: PluginContext, taskCtx: RuntimeContext, params: PD.Values<PD.Params>) { +async function attachStructureProps(structure: Structure, ctx: PluginContext, taskCtx: RuntimeContext, params: ReturnType<CustomStructureProperties['createDefaultParams']>) { const propertyCtx = { runtime: taskCtx, fetch: ctx.fetch } - for (const name of Object.keys(params)) { + const { autoAttach, properties } = params + for (const name of Object.keys(properties)) { const property = ctx.customStructureProperties.get(name) const props = params[name as keyof typeof params] - if (props.autoAttach) { + if (autoAttach.includes(name)) { try { await property.attach(propertyCtx, structure, props) } catch (e) { diff --git a/src/mol-plugin/state/transforms/representation.ts b/src/mol-plugin/state/transforms/representation.ts index f4c9956b90c2ce42c13e091a49f5ab83a3a73c2a..611d19c55790844b5bd6f68bb60c8121f857f134 100644 --- a/src/mol-plugin/state/transforms/representation.ts +++ b/src/mol-plugin/state/transforms/representation.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> @@ -13,7 +13,7 @@ import { BuiltInStructureRepresentationsName } from '../../../mol-repr/structure import { StructureParams } from '../../../mol-repr/structure/representation'; import { BuiltInVolumeRepresentationsName } from '../../../mol-repr/volume/registry'; import { VolumeParams } from '../../../mol-repr/volume/representation'; -import { StateTransformer } from '../../../mol-state'; +import { StateTransformer, StateObject } from '../../../mol-state'; import { Task } from '../../../mol-task'; import { BuiltInColorThemeName, ColorTheme, BuiltInColorThemes } from '../../../mol-theme/color'; import { BuiltInSizeThemeName, SizeTheme } from '../../../mol-theme/size'; @@ -36,6 +36,7 @@ import { LabelParams, LabelRepresentation } from '../../../mol-repr/shape/loci/l import { OrientationRepresentation, OrientationParams } from '../../../mol-repr/shape/loci/orientation'; import { AngleParams, AngleRepresentation } from '../../../mol-repr/shape/loci/angle'; import { DihedralParams, DihedralRepresentation } from '../../../mol-repr/shape/loci/dihedral'; +import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry'; export { StructureRepresentation3D } export { StructureRepresentation3DHelpers } @@ -649,13 +650,16 @@ const ModelUnitcell3D = PluginStateTransform.BuiltIn({ ...UnitcellParams, } })({ + isApplicable: a => !!ModelSymmetry.Provider.get(a.data), canAutoUpdate({ oldParams, newParams }) { return true; }, apply({ a, params }) { return Task.create('Model Unitcell', async ctx => { + const symmetry = ModelSymmetry.Provider.get(a.data) + if (!symmetry) return StateObject.Null const repr = await getUnitcellRepresentation(ctx, a.data, params); - return new SO.Shape.Representation3D({ repr, source: a }, { label: `Unitcell`, description: a.data.symmetry.spacegroup.name }); + return new SO.Shape.Representation3D({ repr, source: a }, { label: `Unitcell`, description: symmetry.spacegroup.name }); }); }, update({ a, b, newParams }) { diff --git a/src/mol-plugin/util/model-unitcell.ts b/src/mol-plugin/util/model-unitcell.ts index def1a033b60a822e853d63967424ae78d81ff547..7ea961e9dbd12bd53edf1a804038ecc9e4ec99ba 100644 --- a/src/mol-plugin/util/model-unitcell.ts +++ b/src/mol-plugin/util/model-unitcell.ts @@ -1,10 +1,10 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Model, ModelSymmetry } from '../../mol-model/structure'; +import { Model, Symmetry } from '../../mol-model/structure'; import { ShapeRepresentation } from '../../mol-repr/shape/representation'; import { Shape } from '../../mol-model/shape'; import { ColorNames } from '../../mol-util/color/names'; @@ -16,6 +16,7 @@ import { BoxCage } from '../../mol-geo/primitive/box'; import { Mat4, Vec3 } from '../../mol-math/linear-algebra'; import { transformCage, cloneCage } from '../../mol-geo/primitive/cage'; import { radToDeg } from '../../mol-math/misc'; +import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry'; const translate05 = Mat4.fromTranslation(Mat4(), Vec3.create(0.5, 0.5, 0.5)) const unitCage = transformCage(cloneCage(BoxCage()), translate05) @@ -24,7 +25,7 @@ const tmpRef = Vec3() const tmpTranslate = Mat4() interface UnitcellData { - symmetry: ModelSymmetry + symmetry: Symmetry ref: Vec3 } @@ -54,11 +55,14 @@ function getUnitcellMesh(data: UnitcellData, props: UnitcellProps, mesh?: Mesh) export async function getUnitcellRepresentation(ctx: RuntimeContext, model: Model, params: UnitcellProps, prev?: ShapeRepresentation<UnitcellData, Mesh, Mesh.Params>) { const repr = prev || ShapeRepresentation(getUnitcellShape, Mesh.Utils); - const data = { - symmetry: model.symmetry, - ref: Vec3.transformMat4(Vec3(), Model.getCenter(model), model.symmetry.spacegroup.cell.toFractional) + const symmetry = ModelSymmetry.Provider.get(model) + if (symmetry) { + const data = { + symmetry, + ref: Vec3.transformMat4(Vec3(), Model.getCenter(model), symmetry.spacegroup.cell.toFractional) + } + await repr.createOrUpdate(params, data).runInContext(ctx); } - await repr.createOrUpdate(params, data).runInContext(ctx); return repr; } diff --git a/src/mol-repr/structure/representation.ts b/src/mol-repr/structure/representation.ts index a5cc1224e74b2591708ef7346c3502d4f6c162fb..a01d70ab45d288d9054a15a7832c37eae3170bb6 100644 --- a/src/mol-repr/structure/representation.ts +++ b/src/mol-repr/structure/representation.ts @@ -19,13 +19,15 @@ import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh'; import { Text } from '../../mol-geo/geometry/text/text'; export interface StructureRepresentationState extends Representation.State { - unitTransforms: StructureUnitTransforms | null + unitTransforms: StructureUnitTransforms | null, + unitTransformsVersion: number } export const StructureRepresentationStateBuilder: Representation.StateBuilder<StructureRepresentationState> = { create: () => { return { ...Representation.createState(), - unitTransforms: null + unitTransforms: null, + unitTransformsVersion: -1 } }, update: (state: StructureRepresentationState, update: Partial<StructureRepresentationState>) => { diff --git a/src/mol-repr/structure/representation/ellipsoid.ts b/src/mol-repr/structure/representation/ellipsoid.ts index 2fd4c05a3485e76df1baa23178c4cc5156ba8b70..9d8f9cf10bedc2f8764f4c40b33ad0520b3d9e2d 100644 --- a/src/mol-repr/structure/representation/ellipsoid.ts +++ b/src/mol-repr/structure/representation/ellipsoid.ts @@ -11,7 +11,7 @@ import { Structure } from '../../../mol-model/structure'; import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation'; import { EllipsoidMeshParams, EllipsoidMeshVisual } from '../visual/ellipsoid-mesh'; import { UnitKind, UnitKindOptions } from '../../../mol-repr/structure/visual/util/common'; -import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/mmcif/anisotropic'; +import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/property/anisotropic'; import { IntraUnitBondParams, IntraUnitBondVisual } from '../visual/bond-intra-unit-cylinder'; import { InterUnitBondParams, InterUnitBondVisual } from '../visual/bond-inter-unit-cylinder'; diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts index 478f1762aa1e76be703e10be9e9ebab241c30674..8ce3568ece2c05d3b2d1bd1aed2866229ae09fd7 100644 --- a/src/mol-repr/structure/units-representation.ts +++ b/src/mol-repr/structure/units-representation.ts @@ -18,10 +18,11 @@ import { Theme } from '../../mol-theme/theme'; import { Task } from '../../mol-task'; import { PickingId } from '../../mol-geo/geometry/picking'; import { Loci, EmptyLoci, isEmptyLoci, isEveryLoci, isDataLoci } from '../../mol-model/loci'; -import { MarkerAction, MarkerActions } from '../../mol-util/marker-action'; +import { MarkerAction, MarkerActions, applyMarkerAction } from '../../mol-util/marker-action'; import { Overpaint } from '../../mol-theme/overpaint'; import { Transparency } from '../../mol-theme/transparency'; import { Mat4, EPSILON } from '../../mol-math/linear-algebra'; +import { Interval } from '../../mol-data/int'; export const UnitsParams = { ...StructureParams, @@ -88,6 +89,13 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R if (promise) await promise visuals.set(group.hashCode, { visual, group }) oldVisuals.delete(group.hashCode) + + // Remove highlight + // TODO: remove selection too?? + if (visual.renderObject) { + const arr = visual.renderObject.values.tMarker.ref.value.array; + applyMarkerAction(arr, Interval.ofBounds(0, arr.length), MarkerAction.RemoveHighlight); + } } else { // console.log(label, 'not found visualGroup to reuse, creating new') // newGroups.push(group) @@ -223,9 +231,13 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: R if (transparency !== undefined && !Transparency.areEqual(transparency, _state.transparency)) { newState.transparency = transparency } - if (transform !== undefined && !Mat4.areEqual(transform, _state.transform, EPSILON)) { newState.transform = transform + if (transform !== undefined && !Mat4.areEqual(transform, _state.transform, EPSILON)) { + newState.transform = transform + } + if (unitTransforms !== _state.unitTransforms || unitTransforms?.version !== state.unitTransformsVersion) { + newState.unitTransforms = unitTransforms + _state.unitTransformsVersion = unitTransforms ? unitTransforms?.version : -1 } - if (unitTransforms !== _state.unitTransforms) newState.unitTransforms = unitTransforms if (syncManually !== _state.syncManually) newState.syncManually = syncManually if (markerActions !== _state.markerActions) newState.markerActions = markerActions diff --git a/src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts b/src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts index 4a998a09ae0661a1682ecd8e570546a25570d4aa..6ab12e6477d19023cef0e4d2351736dbbc8dae51 100644 --- a/src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts +++ b/src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts @@ -33,7 +33,7 @@ function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, inde } const tmpRef = Vec3() -const tmpLoc = StructureElement.Location.create() +const tmpLoc = StructureElement.Location.create(void 0) function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitBondParams>, mesh?: Mesh) { const bonds = structure.interUnitBonds @@ -90,6 +90,7 @@ function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structur }, radius: (edgeIndex: number) => { const b = edges[edgeIndex] + tmpLoc.structure = structure tmpLoc.unit = b.unitA tmpLoc.element = b.unitA.elements[b.indexA] const sizeA = theme.size.size(tmpLoc) @@ -142,12 +143,12 @@ function getBondLoci(pickingId: PickingId, structure: Structure, id: number) { const bond = structure.interUnitBonds.edges[groupId] return Bond.Loci(structure, [ Bond.Location( - bond.unitA, bond.indexA as StructureElement.UnitIndex, - bond.unitB, bond.indexB as StructureElement.UnitIndex + structure, bond.unitA, bond.indexA as StructureElement.UnitIndex, + structure, bond.unitB, bond.indexB as StructureElement.UnitIndex ), Bond.Location( - bond.unitB, bond.indexB as StructureElement.UnitIndex, - bond.unitA, bond.indexA as StructureElement.UnitIndex + structure, bond.unitB, bond.indexB as StructureElement.UnitIndex, + structure, bond.unitA, bond.indexA as StructureElement.UnitIndex ) ]) } diff --git a/src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts b/src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts index 622603ae1daf724817766cb85ded366dcf604898..7283e794ab2381f67cf335efab01a61af03dbf52 100644 --- a/src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts +++ b/src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts @@ -25,7 +25,7 @@ import { ignoreBondType, BondCylinderParams, BondIterator } from './util/bond'; function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondParams>, mesh?: Mesh) { if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh) - const location = StructureElement.Location.create(unit) + const location = StructureElement.Location.create(structure, unit) const elements = unit.elements; const bonds = unit.bonds @@ -135,12 +135,12 @@ function getBondLoci(pickingId: PickingId, structureGroup: StructureGroup, id: n if (Unit.isAtomic(unit)) { return Bond.Loci(structure, [ Bond.Location( - unit, unit.bonds.a[groupId] as StructureElement.UnitIndex, - unit, unit.bonds.b[groupId] as StructureElement.UnitIndex + structure, unit, unit.bonds.a[groupId] as StructureElement.UnitIndex, + structure, unit, unit.bonds.b[groupId] as StructureElement.UnitIndex ), Bond.Location( - unit, unit.bonds.b[groupId] as StructureElement.UnitIndex, - unit, unit.bonds.a[groupId] as StructureElement.UnitIndex + structure, unit, unit.bonds.b[groupId] as StructureElement.UnitIndex, + structure, unit, unit.bonds.a[groupId] as StructureElement.UnitIndex ) ]) } diff --git a/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts b/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts index 3fd7397e53e585a6a7b00196fe3c26c2a8b78e32..0379023e9fdd0dcab6580e3d723dda028ad17fea 100644 --- a/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts +++ b/src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts @@ -24,7 +24,7 @@ function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure: Struc const { links, elements } = structure.carbohydrates const { linkSizeFactor } = props - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const builderProps = { linkCount: links.length, @@ -74,7 +74,7 @@ function CarbohydrateLinkIterator(structure: Structure): LocationIterator { const { elements, links } = structure.carbohydrates const groupCount = links.length const instanceCount = 1 - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const getLocation = (groupIndex: number) => { const link = links[groupIndex] const carbA = elements[link.carbohydrateIndexA] diff --git a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts index a266989792f17ada44312b13b21dc0f0f107df60..39ab381b91a15002eb6187e22dafa319d0fc98b2 100644 --- a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts @@ -53,7 +53,7 @@ function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Structure, const carbohydrates = structure.carbohydrates const n = carbohydrates.elements.length - const l = StructureElement.Location.create() + const l = StructureElement.Location.create(structure) for (let i = 0; i < n; ++i) { const c = carbohydrates.elements[i]; @@ -187,7 +187,7 @@ function CarbohydrateElementIterator(structure: Structure): LocationIterator { const carbElements = structure.carbohydrates.elements const groupCount = carbElements.length * 2 const instanceCount = 1 - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) function getLocation (groupIndex: number, instanceIndex: number) { const carb = carbElements[Math.floor(groupIndex / 2)] const ring = carb.unit.rings.all[carb.ringIndex] diff --git a/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts index bc661bde59560ffa46768ddb7d5888f57cf95071..1b176e4b3065c257d7b6760ada6e503fd2707837 100644 --- a/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts +++ b/src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts @@ -25,7 +25,7 @@ function createCarbohydrateTerminalLinkCylinderMesh(ctx: VisualContext, structur const { terminalLinks, elements } = structure.carbohydrates const { terminalLinkSizeFactor } = props - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const builderProps = { linkCount: terminalLinks.length, @@ -91,7 +91,7 @@ function CarbohydrateTerminalLinkIterator(structure: Structure): LocationIterato const { elements, terminalLinks } = structure.carbohydrates const groupCount = terminalLinks.length const instanceCount = 1 - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const getLocation = (groupIndex: number) => { const terminalLink = terminalLinks[groupIndex] if (terminalLink.fromCarbohydrate) { diff --git a/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts b/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts index 06e78634061a48264db601159b9d76e7d7f29c2f..db047214479a432ea8d0e84dc57c3f5d9b3fd0d1 100644 --- a/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts +++ b/src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts @@ -24,7 +24,7 @@ function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Str if (!crossLinks.count) return Mesh.createEmpty(mesh) const { sizeFactor } = props - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const builderProps = { linkCount: crossLinks.count, @@ -76,8 +76,10 @@ function CrossLinkRestraintIterator(structure: Structure): LocationIterator { const location = Bond.Location() const getLocation = (groupIndex: number) => { const pair = pairs[groupIndex] + location.aStructure = structure location.aUnit = pair.unitA location.aIndex = pair.indexA + location.bStructure = structure location.bUnit = pair.unitB location.bIndex = pair.indexB return location @@ -91,8 +93,8 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) { const pair = structure.crossLinkRestraints.pairs[groupId] if (pair) { return Bond.Loci(structure, [ - Bond.Location(pair.unitA, pair.indexA, pair.unitB, pair.indexB), - Bond.Location(pair.unitB, pair.indexB, pair.unitA, pair.indexA) + Bond.Location(structure, pair.unitA, pair.indexA, structure, pair.unitB, pair.indexB), + Bond.Location(structure, pair.unitB, pair.indexB, structure, pair.unitA, pair.indexA) ]) } } diff --git a/src/mol-repr/structure/visual/ellipsoid-mesh.ts b/src/mol-repr/structure/visual/ellipsoid-mesh.ts index e812c2098799de3123955c0afc733417532b7b22..1496651807b0186b284d40b29c466cc986db747a 100644 --- a/src/mol-repr/structure/visual/ellipsoid-mesh.ts +++ b/src/mol-repr/structure/visual/ellipsoid-mesh.ts @@ -17,7 +17,7 @@ import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder'; import { Vec3, Mat3, Tensor, EPSILON } from '../../../mol-math/linear-algebra'; import { isHydrogen } from '../../../mol-repr/structure/visual/util/common'; import { addEllipsoid } from '../../../mol-geo/geometry/mesh/builder/ellipsoid'; -import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/mmcif/anisotropic' +import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/property/anisotropic' import { equalEps } from '../../../mol-math/linear-algebra/3d/common'; import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere'; @@ -62,7 +62,7 @@ export function createEllipsoidMesh(ctx: VisualContext, unit: Unit, structure: S const vertexCount = elementCount * sphereVertexCount(detail) const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh) - const atomSiteAnisotrop = AtomSiteAnisotrop.get(model) + const atomSiteAnisotrop = AtomSiteAnisotrop.Provider.get(model) if (!atomSiteAnisotrop) return Mesh.createEmpty(mesh) const v = Vec3() @@ -74,7 +74,7 @@ export function createEllipsoidMesh(ctx: VisualContext, unit: Unit, structure: S const { U } = data const space = data._schema.U.space const pos = unit.conformation.invariantPosition - const l = StructureElement.Location.create() + const l = StructureElement.Location.create(structure) l.unit = unit for (let i = 0; i < elementCount; i++) { diff --git a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts index 22c939e38816cee1b2c91518aa31ab56586be6b9..d52d00a8b31ab34fcec773514dc74f94bca0159c 100644 --- a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts +++ b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts @@ -38,7 +38,7 @@ export function getGaussianSurfaceVisual(webgl?: WebGLContext) { async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> { const { smoothness } = props - const { transform, field, idField } = await computeUnitGaussianDensity(unit, props, ctx.webgl).runInContext(ctx.runtime) + const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime) const params = { isoLevel: Math.exp(-smoothness), @@ -119,7 +119,7 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit, if (!ctx.webgl) throw new Error('webgl context required to create gaussian surface texture-mesh') const isoLevel = Math.exp(-props.smoothness) - const densityTextureData = await computeUnitGaussianDensityTexture2d(unit, props, ctx.webgl).runInContext(ctx.runtime) + const densityTextureData = await computeUnitGaussianDensityTexture2d(structure, unit, props, ctx.webgl).runInContext(ctx.runtime) // console.log(densityTextureData) // console.log('vertexGroupTexture', readTexture(ctx.webgl, densityTextureData.texture)) // ctx.webgl.waitForGpuCommandsCompleteSync() diff --git a/src/mol-repr/structure/visual/gaussian-surface-wireframe.ts b/src/mol-repr/structure/visual/gaussian-surface-wireframe.ts index 04bd8c7f54c4beba04875370728705120547926c..c0410641e47220cfb54ed4a9d584c0462c2a119d 100644 --- a/src/mol-repr/structure/visual/gaussian-surface-wireframe.ts +++ b/src/mol-repr/structure/visual/gaussian-surface-wireframe.ts @@ -17,7 +17,7 @@ import { VisualUpdateState } from '../../util'; async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, lines?: Lines): Promise<Lines> { const { smoothness } = props - const { transform, field, idField } = await computeUnitGaussianDensity(unit, props, ctx.webgl).runInContext(ctx.runtime) + const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime) const params = { isoLevel: Math.exp(-smoothness), diff --git a/src/mol-repr/structure/visual/label-text.ts b/src/mol-repr/structure/visual/label-text.ts index a2511e993ed5c9a4cf9c2651c4d9c89d0ee8cdca..696f64fb3db59946bd23df3d226afe56eaf77ac8 100644 --- a/src/mol-repr/structure/visual/label-text.ts +++ b/src/mol-repr/structure/visual/label-text.ts @@ -67,8 +67,7 @@ const tmpVec = Vec3(); const boundaryHelper = new BoundaryHelper(); function createChainText(ctx: VisualContext, structure: Structure, theme: Theme, props: LabelTextProps, text?: Text): Text { - - const l = StructureElement.Location.create(); + const l = StructureElement.Location.create(structure); const { units, serialMapping } = structure; const { auth_asym_id, label_asym_id } = StructureProperties.chain; const { cumulativeUnitElementCount } = serialMapping @@ -93,8 +92,7 @@ function createChainText(ctx: VisualContext, structure: Structure, theme: Theme, } function createResidueText(ctx: VisualContext, structure: Structure, theme: Theme, props: LabelTextProps, text?: Text): Text { - - const l = StructureElement.Location.create(); + const l = StructureElement.Location.create(structure); const { units, serialMapping } = structure; const { auth_seq_id, label_comp_id } = StructureProperties.residue; const { cumulativeUnitElementCount } = serialMapping @@ -145,8 +143,7 @@ function createResidueText(ctx: VisualContext, structure: Structure, theme: Them } function createElementText(ctx: VisualContext, structure: Structure, theme: Theme, props: LabelTextProps, text?: Text): Text { - - const l = StructureElement.Location.create(); + const l = StructureElement.Location.create(structure); const { units, serialMapping } = structure; const { label_atom_id, label_alt_id } = StructureProperties.atom; const { cumulativeUnitElementCount } = serialMapping diff --git a/src/mol-repr/structure/visual/molecular-surface-mesh.ts b/src/mol-repr/structure/visual/molecular-surface-mesh.ts index 0971b4a11ff202d6e421abb96ba75c438de39271..ac17068a8d4371e7331f440719234ae3fbf3c4b3 100644 --- a/src/mol-repr/structure/visual/molecular-surface-mesh.ts +++ b/src/mol-repr/structure/visual/molecular-surface-mesh.ts @@ -27,7 +27,7 @@ export type MolecularSurfaceMeshParams = typeof MolecularSurfaceMeshParams async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceProps, mesh?: Mesh): Promise<Mesh> { - const { transform, field, idField } = await computeUnitMolecularSurface(unit, props).runInContext(ctx.runtime) + const { transform, field, idField } = await computeUnitMolecularSurface(structure, unit, props).runInContext(ctx.runtime) const params = { isoLevel: props.probeRadius, scalarField: field, diff --git a/src/mol-repr/structure/visual/molecular-surface-wireframe.ts b/src/mol-repr/structure/visual/molecular-surface-wireframe.ts index b7b809b61b2a9c31154e8028a0ead2657e0d58c8..964857397e3458cdc7ec5196ffe7c07679157f24 100644 --- a/src/mol-repr/structure/visual/molecular-surface-wireframe.ts +++ b/src/mol-repr/structure/visual/molecular-surface-wireframe.ts @@ -28,7 +28,7 @@ export type MolecularSurfaceWireframeParams = typeof MolecularSurfaceWireframePa async function createMolecularSurfaceWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceProps, lines?: Lines): Promise<Lines> { - const { transform, field, idField } = await computeUnitMolecularSurface(unit, props).runInContext(ctx.runtime) + const { transform, field, idField } = await computeUnitMolecularSurface(structure, unit, props).runInContext(ctx.runtime) const params = { isoLevel: props.probeRadius, scalarField: field, diff --git a/src/mol-repr/structure/visual/orientation-ellipsoid-mesh.ts b/src/mol-repr/structure/visual/orientation-ellipsoid-mesh.ts index df4ffead22d6a9600eace70d6a45b056f5564e94..fadec4c0f7b0b20c89da7429ce89ba536bc93285 100644 --- a/src/mol-repr/structure/visual/orientation-ellipsoid-mesh.ts +++ b/src/mol-repr/structure/visual/orientation-ellipsoid-mesh.ts @@ -88,10 +88,10 @@ export function createOrientationEllipsoidMesh(ctx: VisualContext, unit: Unit, s // function UnitIterator(structureGroup: StructureGroup): LocationIterator { - const { group } = structureGroup + const { group, structure } = structureGroup const groupCount = 1 const instanceCount = group.units.length - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit diff --git a/src/mol-repr/structure/visual/polymer-backbone-cylinder.ts b/src/mol-repr/structure/visual/polymer-backbone-cylinder.ts index 73da6a17de41b4753927ac618c336d294b2cfd00..84d8ac7e3d6481e69357bb80f8106574351c0d86 100644 --- a/src/mol-repr/structure/visual/polymer-backbone-cylinder.ts +++ b/src/mol-repr/structure/visual/polymer-backbone-cylinder.ts @@ -42,7 +42,7 @@ function createPolymerBackboneCylinderMesh(ctx: VisualContext, unit: Unit, struc const pB = Vec3.zero() const cylinderProps: CylinderProps = { radiusTop: 1, radiusBottom: 1, radialSegments } - const polymerBackboneIt = PolymerBackboneIterator(unit) + const polymerBackboneIt = PolymerBackboneIterator(structure, unit) while (polymerBackboneIt.hasNext) { const { centerA, centerB } = polymerBackboneIt.move() pos(centerA.element, pA) diff --git a/src/mol-repr/structure/visual/polymer-gap-cylinder.ts b/src/mol-repr/structure/visual/polymer-gap-cylinder.ts index 074c190839214a03c04bcb88b18773a520c5304c..78b7aba9e0fe950b95b8226053a20767d1188b4d 100644 --- a/src/mol-repr/structure/visual/polymer-gap-cylinder.ts +++ b/src/mol-repr/structure/visual/polymer-gap-cylinder.ts @@ -48,7 +48,7 @@ function createPolymerGapCylinderMesh(ctx: VisualContext, unit: Unit, structure: } let i = 0 - const polymerGapIt = PolymerGapIterator(unit) + const polymerGapIt = PolymerGapIterator(structure, unit) while (polymerGapIt.hasNext) { const { centerA, centerB } = polymerGapIt.move() if (centerA.element === centerB.element) { diff --git a/src/mol-repr/structure/visual/util/bond.ts b/src/mol-repr/structure/visual/util/bond.ts index b21b11ccb50d6b7824d58e26329ada568fd29a13..1b05d5e840b9fcd865fd146157d940e909e29828 100644 --- a/src/mol-repr/structure/visual/util/bond.ts +++ b/src/mol-repr/structure/visual/util/bond.ts @@ -25,11 +25,11 @@ export function ignoreBondType(include: BondType.Flag, exclude: BondType.Flag, f export namespace BondIterator { export function fromGroup(structureGroup: StructureGroup): LocationIterator { - const { group } = structureGroup + const { group, structure } = structureGroup const unit = group.units[0] const groupCount = Unit.isAtomic(unit) ? unit.bonds.edgeCount * 2 : 0 const instanceCount = group.units.length - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit @@ -42,7 +42,7 @@ export namespace BondIterator { export function fromStructure(structure: Structure): LocationIterator { const groupCount = structure.interUnitBonds.edgeCount const instanceCount = 1 - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const getLocation = (groupIndex: number) => { const bond = structure.interUnitBonds.edges[groupIndex] location.unit = bond.unitA diff --git a/src/mol-repr/structure/visual/util/common.ts b/src/mol-repr/structure/visual/util/common.ts index eeee2ceea5f5a6dca2dc20c2073f1430602b1c04..855b37efd01fff324aa46681098c72e0249028ea 100644 --- a/src/mol-repr/structure/visual/util/common.ts +++ b/src/mol-repr/structure/visual/util/common.ts @@ -105,7 +105,7 @@ export function getConformation(unit: Unit) { } } -export function getUnitConformationAndRadius(unit: Unit, ignoreHydrogens = false) { +export function getUnitConformationAndRadius(structure: Structure, unit: Unit, ignoreHydrogens = false) { const conformation = getConformation(unit) const { elements } = unit @@ -135,7 +135,7 @@ export function getUnitConformationAndRadius(unit: Unit, ignoreHydrogens = false id } - const l = StructureElement.Location.create(unit) + const l = StructureElement.Location.create(structure, unit) const sizeTheme = PhysicalSizeTheme({}, {}) const radius = (index: number) => { l.element = index as ElementIndex @@ -146,7 +146,7 @@ export function getUnitConformationAndRadius(unit: Unit, ignoreHydrogens = false } export function getStructureConformationAndRadius(structure: Structure, ignoreHydrogens = false) { - const l = StructureElement.Location.create() + const l = StructureElement.Location.create(structure) const sizeTheme = PhysicalSizeTheme({}, {}) let xs: ArrayLike<number> diff --git a/src/mol-repr/structure/visual/util/element.ts b/src/mol-repr/structure/visual/util/element.ts index 82bdedfb1a94ed641157765f537c7650c7efff44..daa69d87e0d1d8a8e51fa3fac80c0e9fff83308c 100644 --- a/src/mol-repr/structure/visual/util/element.ts +++ b/src/mol-repr/structure/visual/util/element.ts @@ -38,7 +38,7 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur const v = Vec3.zero() const pos = unit.conformation.invariantPosition - const l = StructureElement.Location.create() + const l = StructureElement.Location.create(structure) l.unit = unit for (let i = 0; i < elementCount; i++) { @@ -159,10 +159,10 @@ export function getSerialElementLoci(pickingId: PickingId, structure: Structure, export namespace ElementIterator { export function fromGroup(structureGroup: StructureGroup): LocationIterator { - const { group } = structureGroup + const { group, structure } = structureGroup const groupCount = group.elements.length const instanceCount = group.units.length - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit @@ -177,7 +177,7 @@ export namespace ElementIterator { const groupCount = elementCount const instanceCount = 1 const { unitIndices, elementIndices } = structure.serialMapping - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const getLocation = (groupIndex: number) => { location.unit = units[unitIndices[groupIndex]] location.element = elementIndices[groupIndex] diff --git a/src/mol-repr/structure/visual/util/gaussian.ts b/src/mol-repr/structure/visual/util/gaussian.ts index fb850975919ee193613eb1527a5e5e324eb05d7b..b836de2c6f443a7dd974df803564ba842cbebe64 100644 --- a/src/mol-repr/structure/visual/util/gaussian.ts +++ b/src/mol-repr/structure/visual/util/gaussian.ts @@ -34,22 +34,22 @@ export type GaussianDensityTextureProps = typeof DefaultGaussianDensityTexturePr // -export function computeUnitGaussianDensity(unit: Unit, props: GaussianDensityProps, webgl?: WebGLContext) { - const { position, radius } = getUnitConformationAndRadius(unit, props.ignoreHydrogens) +export function computeUnitGaussianDensity(structure: Structure, unit: Unit, props: GaussianDensityProps, webgl?: WebGLContext) { + const { position, radius } = getUnitConformationAndRadius(structure, unit, props.ignoreHydrogens) return Task.create('Gaussian Density', async ctx => { return await GaussianDensity(ctx, position, unit.lookup3d.boundary.box, radius, props, webgl); }); } -export function computeUnitGaussianDensityTexture(unit: Unit, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) { - const { position, radius } = getUnitConformationAndRadius(unit, props.ignoreHydrogens) +export function computeUnitGaussianDensityTexture(structure: Structure, unit: Unit, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) { + const { position, radius } = getUnitConformationAndRadius(structure, unit, props.ignoreHydrogens) return Task.create('Gaussian Density', async ctx => { return GaussianDensityTexture(webgl, position, unit.lookup3d.boundary.box, radius, props, texture); }); } -export function computeUnitGaussianDensityTexture2d(unit: Unit, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) { - const { position, radius } = getUnitConformationAndRadius(unit, props.ignoreHydrogens) +export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) { + const { position, radius } = getUnitConformationAndRadius(structure, unit, props.ignoreHydrogens) return Task.create('Gaussian Density', async ctx => { return GaussianDensityTexture2d(webgl, position, unit.lookup3d.boundary.box, radius, props, texture); }); diff --git a/src/mol-repr/structure/visual/util/molecular-surface.ts b/src/mol-repr/structure/visual/util/molecular-surface.ts index 5d3fd7491289151b590c1d4f8a8c681cc2926f27..550e99fadee2694046d461363a96e096d7dce2a0 100644 --- a/src/mol-repr/structure/visual/util/molecular-surface.ts +++ b/src/mol-repr/structure/visual/util/molecular-surface.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit } from '../../../../mol-model/structure'; +import { Unit, Structure } from '../../../../mol-model/structure'; import { Task, RuntimeContext } from '../../../../mol-task'; import { getUnitConformationAndRadius } from './common'; import { PositionData, DensityData } from '../../../../mol-math/geometry'; @@ -15,9 +15,9 @@ export type MolecularSurfaceProps = MolecularSurfaceCalculationProps & { ignoreHydrogens: boolean } -function getPositionDataAndMaxRadius(unit: Unit, props: MolecularSurfaceProps) { +function getPositionDataAndMaxRadius(structure: Structure, unit: Unit, props: MolecularSurfaceProps) { const { probeRadius, ignoreHydrogens } = props - const { position, radius } = getUnitConformationAndRadius(unit, ignoreHydrogens) + const { position, radius } = getUnitConformationAndRadius(structure, unit, ignoreHydrogens) const { indices } = position const n = OrderedSet.size(indices) const radii = new Float32Array(OrderedSet.end(indices)) @@ -33,8 +33,8 @@ function getPositionDataAndMaxRadius(unit: Unit, props: MolecularSurfaceProps) { return { position: { ...position, radius: radii }, maxRadius } } -export function computeUnitMolecularSurface(unit: Unit, props: MolecularSurfaceProps) { - const { position, maxRadius } = getPositionDataAndMaxRadius(unit, props) +export function computeUnitMolecularSurface(structure: Structure, unit: Unit, props: MolecularSurfaceProps) { + const { position, maxRadius } = getPositionDataAndMaxRadius(structure, unit, props) return Task.create('Molecular Surface', async ctx => { return await MolecularSurface(ctx, position, maxRadius, props); }); diff --git a/src/mol-repr/structure/visual/util/nucleotide.ts b/src/mol-repr/structure/visual/util/nucleotide.ts index 41c744a168f3667f074ec9154a8ff66ffd35f6c6..6b54f3bb1648f8ea6a004502689eed80ec00a373 100644 --- a/src/mol-repr/structure/visual/util/nucleotide.ts +++ b/src/mol-repr/structure/visual/util/nucleotide.ts @@ -15,12 +15,12 @@ import { eachAtomicUnitTracedElement } from './polymer'; export namespace NucleotideLocationIterator { export function fromGroup(structureGroup: StructureGroup): LocationIterator { - const { group } = structureGroup + const { group, structure } = structureGroup const u = group.units[0] const nucleotideElementIndices = Unit.isAtomic(u) ? u.nucleotideElements : [] const groupCount = nucleotideElementIndices.length const instanceCount = group.units.length - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit diff --git a/src/mol-repr/structure/visual/util/polymer.ts b/src/mol-repr/structure/visual/util/polymer.ts index d40bdbebb4d11ca1bcc521a3e9b1067520827c51..dc868b4587a62883b04b11d9c4c54e8b1c02da2d 100644 --- a/src/mol-repr/structure/visual/util/polymer.ts +++ b/src/mol-repr/structure/visual/util/polymer.ts @@ -43,11 +43,11 @@ export function getGapRanges(unit: Unit): SortedRanges<ElementIndex> { export namespace PolymerLocationIterator { export function fromGroup(structureGroup: StructureGroup): LocationIterator { - const { group } = structureGroup + const { group, structure } = structureGroup const polymerElements = group.units[0].polymerElements const groupCount = polymerElements.length const instanceCount = group.units.length - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit @@ -60,11 +60,11 @@ export namespace PolymerLocationIterator { export namespace PolymerGapLocationIterator { export function fromGroup(structureGroup: StructureGroup): LocationIterator { - const { group } = structureGroup + const { group, structure } = structureGroup const gapElements = group.units[0].gapElements const groupCount = gapElements.length const instanceCount = group.units.length - const location = StructureElement.Location.create() + const location = StructureElement.Location.create(structure) const getLocation = (groupIndex: number, instanceIndex: number) => { const unit = group.units[instanceIndex] location.unit = unit @@ -214,8 +214,8 @@ export function getPolymerGapElementLoci(pickingId: PickingId, structureGroup: S const unitIndexB = OrderedSet.indexOf(unit.elements, unit.gapElements[groupId % 2 ? groupId - 1 : groupId + 1]) as StructureElement.UnitIndex if (unitIndexA !== -1 && unitIndexB !== -1) { return Bond.Loci(structure, [ - Bond.Location(unit, unitIndexA, unit, unitIndexB), - Bond.Location(unit, unitIndexB, unit, unitIndexA) + Bond.Location(structure, unit, unitIndexA, structure, unit, unitIndexB), + Bond.Location(structure, unit, unitIndexB, structure, unit, unitIndexA) ]) } } diff --git a/src/mol-repr/structure/visual/util/polymer/backbone-iterator.ts b/src/mol-repr/structure/visual/util/polymer/backbone-iterator.ts index 82155734d9d95f0cbe9ab739ddd7989e4a50a83a..c8bb7f68173506b6879a8a197e076833e08c88d4 100644 --- a/src/mol-repr/structure/visual/util/polymer/backbone-iterator.ts +++ b/src/mol-repr/structure/visual/util/polymer/backbone-iterator.ts @@ -4,19 +4,19 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit, StructureElement, ElementIndex, ResidueIndex } from '../../../../../mol-model/structure'; +import { Unit, Structure, StructureElement, ElementIndex, ResidueIndex } from '../../../../../mol-model/structure'; import { Segmentation } from '../../../../../mol-data/int'; import Iterator from '../../../../../mol-data/iterator'; import SortedRanges from '../../../../../mol-data/int/sorted-ranges'; import { getPolymerRanges } from '../polymer'; /** Iterates over consecutive pairs of residues/coarse elements in polymers */ -export function PolymerBackboneIterator(unit: Unit): Iterator<PolymerBackbonePair> { +export function PolymerBackboneIterator(structure: Structure, unit: Unit): Iterator<PolymerBackbonePair> { switch (unit.kind) { - case Unit.Kind.Atomic: return new AtomicPolymerBackboneIterator(unit) + case Unit.Kind.Atomic: return new AtomicPolymerBackboneIterator(structure, unit) case Unit.Kind.Spheres: case Unit.Kind.Gaussians: - return new CoarsePolymerBackboneIterator(unit) + return new CoarsePolymerBackboneIterator(structure, unit) } } @@ -25,10 +25,10 @@ interface PolymerBackbonePair { centerB: StructureElement.Location } -function createPolymerBackbonePair (unit: Unit) { +function createPolymerBackbonePair (structure: Structure, unit: Unit) { return { - centerA: StructureElement.Location.create(unit), - centerB: StructureElement.Location.create(unit), + centerA: StructureElement.Location.create(structure, unit), + centerB: StructureElement.Location.create(structure, unit), } } @@ -80,11 +80,11 @@ export class AtomicPolymerBackboneIterator implements Iterator<PolymerBackbonePa return this.value; } - constructor(private unit: Unit.Atomic) { + constructor(structure: Structure, private unit: Unit.Atomic) { this.traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex as ArrayLike<ElementIndex> // can assume it won't be -1 for polymer residues this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements) this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements) - this.value = createPolymerBackbonePair(unit) + this.value = createPolymerBackbonePair(structure, unit) this.hasNext = this.residueIt.hasNext && this.polymerIt.hasNext } } @@ -126,9 +126,9 @@ export class CoarsePolymerBackboneIterator implements Iterator<PolymerBackbonePa return this.value; } - constructor(private unit: Unit.Spheres | Unit.Gaussians) { + constructor(structure: Structure, private unit: Unit.Spheres | Unit.Gaussians) { this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements); - this.value = createPolymerBackbonePair(unit) + this.value = createPolymerBackbonePair(structure, unit) this.hasNext = this.polymerIt.hasNext } } \ No newline at end of file diff --git a/src/mol-repr/structure/visual/util/polymer/gap-iterator.ts b/src/mol-repr/structure/visual/util/polymer/gap-iterator.ts index 5b8208f57bc07ebae58b2ea0ab4610d89279ad2a..0da71df3904149025a867a7a0421dc3ffb2f7152 100644 --- a/src/mol-repr/structure/visual/util/polymer/gap-iterator.ts +++ b/src/mol-repr/structure/visual/util/polymer/gap-iterator.ts @@ -4,18 +4,18 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Unit, StructureElement, ElementIndex, ResidueIndex } from '../../../../../mol-model/structure'; +import { Unit, StructureElement, ElementIndex, ResidueIndex, Structure } from '../../../../../mol-model/structure'; import Iterator from '../../../../../mol-data/iterator'; import SortedRanges from '../../../../../mol-data/int/sorted-ranges'; import { getGapRanges } from '../polymer'; /** Iterates over gaps, i.e. the stem residues/coarse elements adjacent to gaps */ -export function PolymerGapIterator(unit: Unit): Iterator<PolymerGapPair> { +export function PolymerGapIterator(structure: Structure, unit: Unit): Iterator<PolymerGapPair> { switch (unit.kind) { - case Unit.Kind.Atomic: return new AtomicPolymerGapIterator(unit) + case Unit.Kind.Atomic: return new AtomicPolymerGapIterator(structure, unit) case Unit.Kind.Spheres: case Unit.Kind.Gaussians: - return new CoarsePolymerGapIterator(unit) + return new CoarsePolymerGapIterator(structure, unit) } } @@ -24,10 +24,10 @@ interface PolymerGapPair { centerB: StructureElement.Location } -function createPolymerGapPair (unit: Unit) { +function createPolymerGapPair (structure: Structure, unit: Unit) { return { - centerA: StructureElement.Location.create(unit), - centerB: StructureElement.Location.create(unit), + centerA: StructureElement.Location.create(structure, unit), + centerB: StructureElement.Location.create(structure, unit), } } @@ -46,10 +46,10 @@ export class AtomicPolymerGapIterator implements Iterator<PolymerGapPair> { return this.value; } - constructor(private unit: Unit.Atomic) { + constructor(structure: Structure, private unit: Unit.Atomic) { this.traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex as ArrayLike<ElementIndex> // can assume it won't be -1 for polymer residues this.gapIt = SortedRanges.transientSegments(getGapRanges(unit), unit.elements); - this.value = createPolymerGapPair(unit) + this.value = createPolymerGapPair(structure, unit) this.hasNext = this.gapIt.hasNext } } @@ -67,9 +67,9 @@ export class CoarsePolymerGapIterator implements Iterator<PolymerGapPair> { return this.value; } - constructor(private unit: Unit.Spheres | Unit.Gaussians) { + constructor(structure: Structure, private unit: Unit.Spheres | Unit.Gaussians) { this.gapIt = SortedRanges.transientSegments(getGapRanges(unit), unit.elements); - this.value = createPolymerGapPair(unit) + this.value = createPolymerGapPair(structure, unit) this.hasNext = this.gapIt.hasNext } } diff --git a/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts b/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts index f0b405a0fcb31bc15362cb015cb36f965606858a..2631d7588926625f383c0ade64cd016cbbc1f7af 100644 --- a/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts +++ b/src/mol-repr/structure/visual/util/polymer/trace-iterator.ts @@ -47,11 +47,11 @@ interface PolymerTraceElement { const SecStrucTypeNA = SecondaryStructureType.create(SecondaryStructureType.Flag.NA) -function createPolymerTraceElement (unit: Unit): PolymerTraceElement { +function createPolymerTraceElement (structure: Structure, unit: Unit): PolymerTraceElement { return { - center: StructureElement.Location.create(unit), - centerPrev: StructureElement.Location.create(unit), - centerNext: StructureElement.Location.create(unit), + center: StructureElement.Location.create(structure, unit), + centerPrev: StructureElement.Location.create(structure, unit), + centerNext: StructureElement.Location.create(structure, unit), first: false, last: false, initial: false, final: false, secStrucFirst: false, secStrucLast: false, @@ -302,7 +302,7 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement> this.cyclicPolymerMap = unit.model.atomicRanges.cyclicPolymerMap this.polymerIt = SortedRanges.transientSegments(this.polymerRanges, unit.elements) this.residueIt = Segmentation.transientSegments(this.residueAtomSegments, unit.elements); - this.value = createPolymerTraceElement(unit) + this.value = createPolymerTraceElement(structure, unit) this.hasNext = this.residueIt.hasNext && this.polymerIt.hasNext const secondaryStructure = SecondaryStructureProvider.get(structure).value?.get(unit.invariantId) @@ -397,7 +397,7 @@ export class CoarsePolymerTraceIterator implements Iterator<PolymerTraceElement> constructor(private unit: Unit.Spheres | Unit.Gaussians, structure: Structure) { this.polymerIt = SortedRanges.transientSegments(getPolymerRanges(unit), unit.elements); - this.value = createPolymerTraceElement(unit) + this.value = createPolymerTraceElement(structure, unit) Vec3.set(this.value.d12, 1, 0, 0) Vec3.set(this.value.d23, 1, 0, 0) switch (unit.kind) { diff --git a/src/mol-theme/color/chain-id.ts b/src/mol-theme/color/chain-id.ts index ef8b1f00daef96e0c5d5fbfe09587e178566f86b..16c072ff40491cae1f2c181df6d4eef3194fe1fa 100644 --- a/src/mol-theme/color/chain-id.ts +++ b/src/mol-theme/color/chain-id.ts @@ -81,7 +81,7 @@ export function ChainIdColorTheme(ctx: ThemeDataContext, props: PD.Values<ChainI let legend: ScaleLegend | TableLegend | undefined if (ctx.structure) { - const l = StructureElement.Location.create() + const l = StructureElement.Location.create(ctx.structure) const asymIdSerialMap = getAsymIdSerialMap(ctx.structure.root) const labelTable = Array.from(asymIdSerialMap.keys()) diff --git a/src/mol-theme/color/entity-source.ts b/src/mol-theme/color/entity-source.ts index c38d9f14a2776eaf61953b61bc99816856bed500..a8ac312bfe1693d8be1637d780f1709702303b5d 100644 --- a/src/mol-theme/color/entity-source.ts +++ b/src/mol-theme/color/entity-source.ts @@ -16,6 +16,7 @@ import { getPaletteParams, getPalette } from '../../mol-util/color/palette'; import { TableLegend, ScaleLegend } from '../../mol-util/legend'; import { isInteger } from '../../mol-util/number'; import { ColorLists } from '../../mol-util/color/lists'; +import { MmcifFormat } from '../../mol-model-formats/structure/mmcif'; const DefaultList = 'dark-2' const DefaultColor = Color(0xFAFAFA) @@ -100,8 +101,8 @@ function getMaps(models: ReadonlyArray<Model>) { for (let i = 0, il = models.length; i <il; ++i) { const m = models[i] - if (m.sourceData.kind !== 'mmCIF') continue - const { entity_src_gen, entity_src_nat, pdbx_entity_src_syn } = m.sourceData.data + if (!MmcifFormat.is(m.sourceData)) continue + const { entity_src_gen, entity_src_nat, pdbx_entity_src_syn } = m.sourceData.data.db addSrc(seqToSrcByModelEntity, srcKeySerialMap, i, m, entity_src_gen, entity_src_gen.pdbx_gene_src_scientific_name, entity_src_gen.plasmid_name, entity_src_gen.pdbx_gene_src_gene) addSrc(seqToSrcByModelEntity, srcKeySerialMap, i, m, entity_src_nat, entity_src_nat.pdbx_organism_scientific, entity_src_nat.pdbx_plasmid_name) addSrc(seqToSrcByModelEntity, srcKeySerialMap, i, m, pdbx_entity_src_syn, pdbx_entity_src_syn.organism_scientific) @@ -125,7 +126,7 @@ export function EntitySourceColorTheme(ctx: ThemeDataContext, props: PD.Values<E let legend: ScaleLegend | TableLegend | undefined if (ctx.structure) { - const l = StructureElement.Location.create() + const l = StructureElement.Location.create(ctx.structure) const { models } = ctx.structure.root const { seqToSrcByModelEntity, srcKeySerialMap } = getMaps(models) diff --git a/src/mol-theme/color/polymer-id.ts b/src/mol-theme/color/polymer-id.ts index 18f0e76541605b271f10de8c4bd6bd096ba69aa4..46955e905c48bad6d951683869e8c9f3fb5f94ee 100644 --- a/src/mol-theme/color/polymer-id.ts +++ b/src/mol-theme/color/polymer-id.ts @@ -90,7 +90,7 @@ export function PolymerIdColorTheme(ctx: ThemeDataContext, props: PD.Values<Poly let legend: ScaleLegend | TableLegend | undefined if (ctx.structure) { - const l = StructureElement.Location.create() + const l = StructureElement.Location.create(ctx.structure) const polymerAsymIdSerialMap = getPolymerAsymIdSerialMap(ctx.structure.root) const labelTable = Array.from(polymerAsymIdSerialMap.keys()) diff --git a/src/mol-theme/color/uncertainty.ts b/src/mol-theme/color/uncertainty.ts index b715369f10fb8db503a5c89c3745a07b8382e44d..d7511cb0533f6554bf50d9d92797ef1b0df223f5 100644 --- a/src/mol-theme/color/uncertainty.ts +++ b/src/mol-theme/color/uncertainty.ts @@ -67,5 +67,5 @@ export const UncertaintyColorThemeProvider: ColorTheme.Provider<UncertaintyColor factory: UncertaintyColorTheme, getParams: getUncertaintyColorThemeParams, defaultValues: PD.getDefaultValues(UncertaintyColorThemeParams), - isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => m.atomicConformation.B_iso_or_equiv.isDefined || m.sourceData.data.ihm_sphere_obj_site.rmsf.isDefined) + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => m.atomicConformation.B_iso_or_equiv.isDefined || m.coarseHierarchy.isDefined) } \ No newline at end of file diff --git a/src/mol-theme/label.ts b/src/mol-theme/label.ts index cc58137b48249a7806c84a2d0ee367569d1e2fdd..a903862f28ade44b66a725717f671d1ff53a2f4c 100644 --- a/src/mol-theme/label.ts +++ b/src/mol-theme/label.ts @@ -112,8 +112,8 @@ function _structureElementStatsLabel(stats: StructureElement.Stats, countsOnly = export function bondLabel(bond: Bond.Location, options: Partial<LabelOptions> = {}): string { const o = { ...DefaultLabelOptions, ...options } - const locA = StructureElement.Location.create(bond.aUnit, bond.aUnit.elements[bond.aIndex]) - const locB = StructureElement.Location.create(bond.bUnit, bond.bUnit.elements[bond.bIndex]) + const locA = StructureElement.Location.create(bond.aStructure, bond.aUnit, bond.aUnit.elements[bond.aIndex]) + const locB = StructureElement.Location.create(bond.bStructure, bond.bUnit, bond.bUnit.elements[bond.bIndex]) const labelA = _elementLabel(locA, o.granularity, o.hidePrefix) const labelB = _elementLabel(locB, o.granularity, o.hidePrefix) let offset = 0 @@ -137,7 +137,7 @@ export function bundleLabel(bundle: Loci.Bundle<any>, options: Partial<LabelOpti const o = { ...DefaultLabelOptions, ...options } const locations = (bundle.loci as StructureElement.Loci[]).map(l => { const { unit, indices } = l.elements[0] - return StructureElement.Location.create(unit, unit.elements[OrderedSet.start(indices)]) + return StructureElement.Location.create(l.structure, unit, unit.elements[OrderedSet.start(indices)]) }) const labels = locations.map(l => _elementLabel(l, o.granularity, o.hidePrefix)) diff --git a/src/mol-theme/size/uncertainty.ts b/src/mol-theme/size/uncertainty.ts index 78e4dc41c8d915d8904b8c3c4d6bb47696dfe122..5f9da065b4b8331ac620db16e6e4b806446ac176 100644 --- a/src/mol-theme/size/uncertainty.ts +++ b/src/mol-theme/size/uncertainty.ts @@ -57,5 +57,5 @@ export const UncertaintySizeThemeProvider: SizeTheme.Provider<UncertaintySizeThe factory: UncertaintySizeTheme, getParams: getUncertaintySizeThemeParams, defaultValues: PD.getDefaultValues(UncertaintySizeThemeParams), - isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => m.atomicConformation.B_iso_or_equiv.isDefined || m.sourceData.data.ihm_sphere_obj_site.rmsf.isDefined) + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => m.atomicConformation.B_iso_or_equiv.isDefined || m.coarseHierarchy.isDefined) } \ No newline at end of file diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index 4ba2901d7190e73786769c9d9e8de16e175e834e..edcf9300c4635fb1ac00869978d8f9457f58906c 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -119,15 +119,25 @@ export namespace ParamDefinition { } export interface FileParam extends Base<File> { - type: 'file', + type: 'file' accept?: string } - export function File(info?: Info & { accept?: string }): FileParam { + export function File(info?: Info & { accept?: string, multiple?: boolean }): FileParam { const ret = setInfo<FileParam>({ type: 'file', defaultValue: void 0 as any }, info); if (info && info.accept) ret.accept = info.accept; return ret; } + export interface FileListParam extends Base<FileList> { + 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); + if (info && info.accept) ret.accept = info.accept; + return ret; + } + export interface Range { /** If given treat as a range. */ min?: number @@ -256,7 +266,7 @@ export namespace ParamDefinition { } export type Any = - | Value<any> | Select<any> | MultiSelect<any> | BooleanParam | Text | Color | Vec3 | Numeric | FileParam | Interval | LineGraph + | Value<any> | Select<any> | MultiSelect<any> | BooleanParam | Text | Color | Vec3 | Numeric | FileParam | FileListParam | Interval | LineGraph | ColorList<any> | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | Script | ObjectList export type Params = { [k: string]: Any } diff --git a/src/mol-util/set.ts b/src/mol-util/set.ts index 25114f787f1df08d836f969726272cc95d4f13e0..af02d56a3cab789b73612da9c24a48d43c4c461f 100644 --- a/src/mol-util/set.ts +++ b/src/mol-util/set.ts @@ -67,6 +67,14 @@ export namespace SetUtils { return flag; } + export function intersectionSize<T>(setA: ReadonlySet<T>, setB: ReadonlySet<T>): number { + let count = 0 + setB.forEach(elem => { + if (setA.has(elem)) count += 1; + }) + return count; + } + /** Create set containing elements of set a that are not in set b. */ export function difference<T>(setA: ReadonlySet<T>, setB: ReadonlySet<T>): Set<T> { const difference = new Set(setA); @@ -74,6 +82,15 @@ export namespace SetUtils { return difference; } + /** Number of elements that are in set a but not in set b. */ + export function differenceSize<T>(setA: ReadonlySet<T>, setB: ReadonlySet<T>): number { + let count = setA.size; + setA.forEach(elem => { + if (setB.has(elem)) count -= 1 + }) + return count; + } + /** Test if set a and b contain the same elements. */ export function areEqual<T>(setA: ReadonlySet<T>, setB: ReadonlySet<T>) { if (setA.size !== setB.size) return false diff --git a/src/mol-util/uuid.ts b/src/mol-util/uuid.ts index c5f5b508dbe5f70aba21116b8e4ea6da1356fce8..541489a5f5f57d439bff87bdee88381c9eb5cc6e 100644 --- a/src/mol-util/uuid.ts +++ b/src/mol-util/uuid.ts @@ -6,13 +6,14 @@ import { now } from '../mol-util/now'; +/** A UUID, either standard 36 characters or 22 characters base64 encoded. */ type UUID = string & { '@type': 'uuid' } namespace UUID { const _btoa = typeof btoa !== 'undefined' ? btoa : (s: string) => Buffer.from(s).toString('base64') const chars: string[] = []; - /** Creates 22 characted "base64" UUID */ + /** Creates a 22 characters 'base64' encoded UUID */ export function create22(): UUID { let d = (+new Date()) + now(); for (let i = 0; i < 16; i++) { diff --git a/src/perf-tests/lookup3d.ts b/src/perf-tests/lookup3d.ts index 8df122b3e8f708a35d22e50f4ddf8ce927e34a9d..c4f7c19b319e039d08a2220ef925fc05b1cab758 100644 --- a/src/perf-tests/lookup3d.ts +++ b/src/perf-tests/lookup3d.ts @@ -7,7 +7,7 @@ import { Structure } from '../mol-model/structure' import { GridLookup3D } from '../mol-math/geometry'; // import { sortArray } from 'mol-data/util'; import { OrderedSet } from '../mol-data/int'; -import { trajectoryFromMmCIF } from '../mol-model-formats/structure/mmcif'; +import { trajectoryFromMmCIF, MmcifFormat } from '../mol-model-formats/structure/mmcif'; require('util.promisify').shim(); const readFileAsync = util.promisify(fs.readFile); @@ -35,14 +35,14 @@ export async function readCIF(path: string) { const models = await trajectoryFromMmCIF(parsed.result.blocks[0]).run(); const structures = models.map(Structure.ofModel); - return { mmcif: models[0].sourceData.data, models, structures }; + return { mmcif: models[0].sourceData.data as MmcifFormat.Data, models, structures }; } export async function test() { const { mmcif, structures } = await readCIF('e:/test/quick/1tqn_updated.cif'); - const lookup = GridLookup3D({ x: mmcif.atom_site.Cartn_x.toArray(), y: mmcif.atom_site.Cartn_y.toArray(), z: mmcif.atom_site.Cartn_z.toArray(), - indices: OrderedSet.ofBounds(0, mmcif.atom_site._rowCount), + const lookup = GridLookup3D({ x: mmcif.db.atom_site.Cartn_x.toArray(), y: mmcif.db.atom_site.Cartn_y.toArray(), z: mmcif.db.atom_site.Cartn_z.toArray(), + indices: OrderedSet.ofBounds(0, mmcif.db.atom_site._rowCount), // radius: [1, 1, 1, 1] // indices: [1] }); diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts index 8f7d1ac4e6cd3db684566a39004a2e485c5ed0c5..989e29c7f4df5bb9cef0ab3567fafd6d5390c99c 100644 --- a/src/perf-tests/structure.ts +++ b/src/perf-tests/structure.ts @@ -16,7 +16,7 @@ import { Structure, Model, Queries as Q, StructureElement, StructureSelection, S import to_mmCIF from '../mol-model/structure/export/mmcif' import { Vec3 } from '../mol-math/linear-algebra'; -import { trajectoryFromMmCIF } from '../mol-model-formats/structure/mmcif'; +import { trajectoryFromMmCIF, MmcifFormat } from '../mol-model-formats/structure/mmcif'; // import { printUnits } from '../apps/structure-info/model'; // import { EquivalenceClasses } from '../mol-data/util'; @@ -106,8 +106,8 @@ export async function getBcif(pdbId: string) { export namespace PropertyAccess { function baseline(model: Model) { - if (model.sourceData.kind !== 'mmCIF') throw new Error('Model must be mmCIF'); - const atom_site = model.sourceData.data.atom_site; + if (!MmcifFormat.is(model.sourceData)) throw new Error('Model must be mmCIF'); + const atom_site = model.sourceData.data.db.atom_site; const id = atom_site.id.value; let s = 0; for (let i = 0, _i = atom_site._rowCount; i < _i; i++) { @@ -117,7 +117,7 @@ export namespace PropertyAccess { } function sumProperty(structure: Structure, p: StructureElement.Property<number>) { - const l = StructureElement.Location.create(); + const l = StructureElement.Location.create(structure); let s = 0; for (const unit of structure.units) { diff --git a/src/servers/model/properties/providers/wwpdb.ts b/src/servers/model/properties/providers/wwpdb.ts index 773bbbff39ca5f6df1ba867b8680e31b8bbf4152..c35f991be4998bd07dfcabaee8434f8b2ae3d483 100644 --- a/src/servers/model/properties/providers/wwpdb.ts +++ b/src/servers/model/properties/providers/wwpdb.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -9,15 +9,17 @@ import * as util from 'util' import { AttachModelProperty } from '../../property-provider'; import { CIF } from '../../../../mol-io/reader/cif'; import { getParam } from '../../../common/util'; -import { ComponentBond } from '../../../../mol-model-formats/structure/mmcif/bonds'; import { mmCIF_Database, mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif'; +import { ComponentBond } from '../../../../mol-model-formats/structure/property/bonds/comp'; require('util.promisify').shim() const readFile = util.promisify(fs.readFile) export const wwPDB_chemCompBond: AttachModelProperty = async ({ model, params }) => { const table = await getChemCompBondTable(getTablePath(params)) - return ComponentBond.attachFromExternalData(model, table, true) + const data = ComponentBond.chemCompBondFromTable(model, table) + const entries = ComponentBond.getEntriesFromChemCompBond(data) + return ComponentBond.Provider.set(model, { entries, data }) } async function read(path: string) { diff --git a/src/tests/browser/marching-cubes.ts b/src/tests/browser/marching-cubes.ts index 4f8e8335b34dfd2ae04c720e74c64a49e43b667c..66fa5d3fe59d5d021fc6cacee278e8507d783288 100644 --- a/src/tests/browser/marching-cubes.ts +++ b/src/tests/browser/marching-cubes.ts @@ -114,7 +114,7 @@ async function init() { const mcIsoSurfaceRepr = Representation.fromRenderObject('texture-mesh', mcIsoSurfaceRenderObject) canvas3d.add(mcIsoSurfaceRepr) - canvas3d.resetCamera() + canvas3d.requestCameraReset() // @@ -141,7 +141,7 @@ async function init() { const meshRepr = Representation.fromRenderObject('mesh', meshRenderObject) canvas3d.add(meshRepr) - canvas3d.resetCamera() + canvas3d.requestCameraReset() } init() \ No newline at end of file diff --git a/src/tests/browser/render-lines.ts b/src/tests/browser/render-lines.ts index 4cecd427cb4e85e4b061bafd95ab948ad6c99408..d782b92aa7540d53169f57741fcebf8b7767ca3a 100644 --- a/src/tests/browser/render-lines.ts +++ b/src/tests/browser/render-lines.ts @@ -41,4 +41,4 @@ function linesRepr() { } canvas3d.add(linesRepr()) -canvas3d.resetCamera() \ No newline at end of file +canvas3d.requestCameraReset() \ No newline at end of file diff --git a/src/tests/browser/render-mesh.ts b/src/tests/browser/render-mesh.ts index ca5ee131a4db0b7c7875d1a419bea8a099420bb2..f5358bf9f6d54ff5d208a854a8b5dd34a8939cd2 100644 --- a/src/tests/browser/render-mesh.ts +++ b/src/tests/browser/render-mesh.ts @@ -47,4 +47,4 @@ function meshRepr() { } canvas3d.add(meshRepr()) -canvas3d.resetCamera() \ No newline at end of file +canvas3d.requestCameraReset() \ No newline at end of file diff --git a/src/tests/browser/render-shape.ts b/src/tests/browser/render-shape.ts index ac2153ce5d55f3069be39ccb5cb08700833dc2d0..cf7b748b6eeaa7d18605fc207aed8c578611909e 100644 --- a/src/tests/browser/render-shape.ts +++ b/src/tests/browser/render-shape.ts @@ -112,7 +112,7 @@ export async function init() { // Create shape from myData and add to canvas3d await repr.createOrUpdate({}, myData).run((p: Progress) => console.log(Progress.format(p))) canvas3d.add(repr) - canvas3d.resetCamera() + canvas3d.requestCameraReset() // Change color after 1s setTimeout(async () => { diff --git a/src/tests/browser/render-spheres.ts b/src/tests/browser/render-spheres.ts index 020b5795906cc42f4c06b8790aebc4571dabe337..094e48585b429ddcee036a1ad64fe65b1239532b 100644 --- a/src/tests/browser/render-spheres.ts +++ b/src/tests/browser/render-spheres.ts @@ -40,4 +40,4 @@ function spheresRepr() { } canvas3d.add(spheresRepr()) -canvas3d.resetCamera() \ No newline at end of file +canvas3d.requestCameraReset() \ No newline at end of file diff --git a/src/tests/browser/render-structure.ts b/src/tests/browser/render-structure.ts index 36e284c5448ed8f3743d3d182ba2ababade6fc24..e19dbb44e12c0505c21816c5173bd05893396f3c 100644 --- a/src/tests/browser/render-structure.ts +++ b/src/tests/browser/render-structure.ts @@ -195,7 +195,7 @@ async function init() { if (show.ballAndStick) canvas3d.add(ballAndStickRepr) if (show.molecularSurface) canvas3d.add(molecularSurfaceRepr) if (show.gaussianSurface) canvas3d.add(gaussianSurfaceRepr) - canvas3d.resetCamera() + canvas3d.requestCameraReset() // canvas3d.setProps({ trackball: { ...canvas3d.props.trackball, spin: true } }) } diff --git a/src/tests/browser/render-text.ts b/src/tests/browser/render-text.ts index 692e1e4a28a299bfbf5143df4ccbd22448106936..0c11067311cc7733651a443cf00cb2921df955ed 100644 --- a/src/tests/browser/render-text.ts +++ b/src/tests/browser/render-text.ts @@ -74,4 +74,4 @@ function spheresRepr() { canvas3d.add(textRepr()) canvas3d.add(spheresRepr()) -canvas3d.resetCamera() \ No newline at end of file +canvas3d.requestCameraReset() \ No newline at end of file