Compare commits

...

111 Commits

Author SHA1 Message Date
dsehnal
2784ccf379 3.0.1 2022-01-27 12:15:53 +01:00
dsehnal
0ad1d578fe changelog 2022-01-27 12:13:22 +01:00
David Sehnal
31fd1c9c68 Merge pull request #353 from molstar/drag-tweak
emit drag event whenever started within viewport
2022-01-27 12:12:07 +01:00
David Sehnal
9c961297a2 Merge branch 'master' into drag-tweak 2022-01-27 12:11:59 +01:00
David Sehnal
b920053349 Merge pull request #352 from molstar/var-fixes
Various fixes
2022-01-27 12:09:39 +01:00
David Sehnal
0a5c764e4a Merge pull request #351 from molstar/volume-server-data-fix
Volume server data fix
2022-01-27 11:37:31 +01:00
Alexander Rose
b9a71c83ff emit drag event whenever started within viewport 2022-01-26 21:32:14 -08:00
Alexander Rose
3255f207d0 Merge branch 'master' of https://github.com/molstar/molstar into var-fixes 2022-01-26 20:46:11 -08:00
Alexander Rose
e3b4ca8862 add entity-id and entity-source as carbonColor 2022-01-26 20:42:38 -08:00
Alexander Rose
6810793015 fix marking of InteractionsInterUnitVisual 2022-01-26 20:40:12 -08:00
Alexander Rose
1feb3c2095 fix entity-id coloring broken for non-ihm models 2022-01-26 20:38:46 -08:00
Alexander Rose
f2da6033d0 fix pdbe xray maps url 2022-01-26 20:36:52 -08:00
Alexander Rose
28bc212132 fix marking pass for transparentBackground 2022-01-26 20:35:38 -08:00
dsehnal
1a7c62eec6 remove gl from dependencies & install it on demand instead
- installing gl on M1 Mac was taking several minutes to compile on each update
2022-01-26 23:12:12 +01:00
dsehnal
de67dbacba iso-value adjustment for VolumeServer data in default Viewer 2022-01-26 17:47:23 +01:00
dsehnal
57223a0f9a Fix VolumeServer/query CLI 2022-01-26 16:49:18 +01:00
Alexander Rose
2ad0754b90 3.0.0 2022-01-23 18:12:48 -08:00
Alexander Rose
3ecb3af57b changelog 2022-01-23 18:08:01 -08:00
Alexander Rose
ec4f15f549 improve/fix InteractionsIntraUnitVisual marking 2022-01-23 17:55:51 -08:00
Alexander Rose
2458ea7b92 Merge pull request #349 from molstar/custom-theme-colors
add custom theme colors
2022-01-23 15:13:24 -08:00
Alexander Rose
c5e6bedf11 Merge branch 'master' into custom-theme-colors 2022-01-23 13:54:57 -08:00
dsehnal
8528e5a666 Support/bugfixes for atom_site.pdbx_sifts_xref categories 2022-01-23 20:46:49 +01:00
dsehnal
6ed232b3d9 fix using default values for webgl1/wboit features in the default viewer app 2022-01-23 19:25:09 +01:00
dsehnal
f8aae8cbd1 skip Coarse models in export extension 2022-01-23 16:13:02 +01:00
Alexander Rose
00c2517045 package updates 2022-01-22 14:06:26 -08:00
Alexander Rose
99b043a929 avoid circular dependency 2022-01-22 11:10:41 -08:00
Alexander Rose
5900e27e39 add custom theme colors
- element-symbol
- molecule-type
- residue-name
- secondary-structure
2022-01-22 10:51:41 -08:00
Alexander Rose
1b79d34907 fix marking of carbohydrate visuals 2022-01-22 08:53:47 -08:00
Alexander Rose
fc52e29c92 re-add using _struct_asym cat in getStructAsymMap
- needed for ihm model support
2022-01-22 08:50:29 -08:00
Alexander Rose
df23b3c0fe fix coarse model support in entity-id color theme
- add StructureProperties.coarse.entity_id
2022-01-22 08:48:07 -08:00
David Sehnal
5e25716c98 Merge pull request #334 from molstar/export-extension
Model export extension & related improvements
2022-01-18 13:23:11 +01:00
dsehnal
f70a10bc56 add info about CIF export failure 2022-01-18 13:20:53 +01:00
dsehnal
0ccb045f4e Merge branch 'master' of https://github.com/molstar/molstar into export-extension 2022-01-18 12:30:45 +01:00
Alexander Rose
fa18d0d852 3.0.0-dev.10 2022-01-17 14:08:28 -08:00
Alexander Rose
687c4342fb changelog 2022-01-17 14:03:55 -08:00
Alexander Rose
9459af46b8 Merge pull request #337 from molstar/anim-rock
Add rock animation
2022-01-17 13:38:08 -08:00
Alexander Rose
fc5832747a Merge pull request #345 from molstar/bump-immutable
bump immutable to 4.0
2022-01-17 13:37:19 -08:00
Alexander Rose
01205d244b tweak trackball speed calc 2022-01-17 13:16:19 -08:00
dsehnal
31a555255a bump immutable to 4.0 2022-01-17 17:56:39 +01:00
dsehnal
fbb60c9493 Treat empty string as non-present value in BinaryCIF 2022-01-17 16:21:14 +01:00
dsehnal
9f953ef51c add Model Export overlay 2022-01-17 15:47:56 +01:00
dsehnal
4871f1547c Generate structAsymMap from normalized atomic hierarchy
- Fixes issue with remapped chains
- Requires to parse PRD separately
2022-01-17 14:52:53 +01:00
dsehnal
d6413529f4 PR feedback 2022-01-17 13:58:20 +01:00
dsehnal
724cf5a0da Add integrations section to readme 2022-01-17 12:58:35 +01:00
dsehnal
b6847907ca Add ModelExport to viewer/app.ts 2022-01-17 11:37:49 +01:00
dsehnal
fb54a1aed7 Merge branch 'master' of https://github.com/molstar/molstar into export-extension 2022-01-17 11:37:16 +01:00
Alexander Rose
9815318daf add entity-source option to illustrative coloring 2022-01-16 23:08:43 -08:00
Alexander Rose
bc13b98111 Merge branch 'master' of https://github.com/molstar/molstar into anim-rock 2022-01-16 15:48:33 -08:00
Alexander Rose
238b70c121 simplify rock state/trackball animation
- use sin() instead of smoothstep()
2022-01-16 15:47:25 -08:00
Alexander Rose
dcd23bc0cb improve illustrative style support
- add illustrative representation preset
- add style option to illustrative color theme
2022-01-16 13:13:00 -08:00
Alexander Rose
4694ea85fa support custom colors in molecule-type theme 2022-01-16 13:05:43 -08:00
Alexander Rose
bdb17743d7 update schemas 2022-01-15 19:54:22 -08:00
Alexander Rose
8b76ff2461 update packages 2022-01-15 19:53:04 -08:00
Alexander Rose
ea5421002b add camera rock state animation 2022-01-15 18:59:12 -08:00
Alexander Rose
76ac55917d type, tweaks 2022-01-15 18:45:56 -08:00
Alexander Rose
5cfb2376c4 Merge branch 'master' of https://github.com/molstar/molstar into anim-rock 2022-01-15 15:56:55 -08:00
Alexander Rose
6b9d3fd80e cleaner MembraneOrientationVisuals defaults 2022-01-15 13:52:14 -08:00
dsehnal
a09752b62e changelog and contibutors in package.json 2022-01-14 12:53:51 +01:00
David Sehnal
3134e1d9f9 Merge pull request #341 from molstar/fix-camera-spin-stutter
Pass animation info to state animations
2022-01-14 12:51:51 +01:00
David Sehnal
e94ecf2a0b Merge pull request #314 from ptourlas/feature/formal-charge-labels
Feature/formal charge labels
2022-01-14 12:51:09 +01:00
ptourlas
1bd4d841a1 ADD: Test multiple charge lines in molfiles 2022-01-12 17:01:36 +02:00
ptourlas
8e349f47a5 (author tags) 2022-01-12 16:50:06 +02:00
ptourlas
119c0a4231 ADD: Test formal charge parsing in sdf reader 2022-01-12 16:25:23 +02:00
ptourlas
f009f533e0 TWEAK: Default charges in V3000 sdf files 2022-01-12 15:05:07 +02:00
ptourlas
3ab0c1e509 ADD: Test formal charge parsing in mol reader 2022-01-12 12:42:29 +02:00
dsehnal
e3d264e239 Pass animation info to state animations
+ Fix camera stutter for "camera spin"
2022-01-11 19:38:32 +01:00
dsehnal
9bd60f8e8e Fix getOperatorsForIndex 2022-01-11 17:34:43 +01:00
ptourlas
bcec1d9637 (forgot to cleanup) 2022-01-11 16:41:23 +02:00
ptourlas
1b431b1d20 RFR: Formal charge assignment at model creation 2022-01-11 16:04:46 +02:00
ptourlas
23c2dcdfd4 FIX: Use tokenizer to ensure the loop terminates 2022-01-11 15:01:15 +02:00
ptourlas
dd415bf802 FTR: Support multiple formal charge lines
* Start with two empty arrays for indexes and charges.
* Each `M CHG` line is passed to `handleFormalCharges()` and the
parsed elements are pushed to the arrays.
* The `Column`s are made at the end of this process.
2022-01-11 14:40:57 +02:00
ptourlas
7bc0e9db7c RFR: formalChargeMapper tweaks
* The key is of type number, no need to stringify it.
* Simplified `switch()`.
2022-01-11 00:18:25 +02:00
ptourlas
ca10bb01db Merge branch 'molstar:master' into feature/formal-charge-labels 2022-01-10 23:46:37 +02:00
Alexander Rose
c0f14b7c33 3.0.0-dev.9 2022-01-09 18:24:30 -08:00
Alexander Rose
b096f328fc changelog 2022-01-09 18:19:52 -08:00
Alexander Rose
88dbd43884 re-allow interaction during trackball animation
- was disallowed as a stop-gap measure
- ok after improving temporal multi sampling
2022-01-09 14:39:09 -08:00
Alexander Rose
ade5e4d4b8 re-allow interaction during trackball animation
- was disallowed as a stop-gap measure
- ok after improving temporal multi sampling
2022-01-09 14:30:43 -08:00
ptourlas
cb76b53a1b FTR: Add formal charges during model creation
This implementation takes into account both the property and atom block
cases and makes sure the latter is ignored if the first one is present.
2022-01-10 00:30:25 +02:00
Alexander Rose
b9423f70d4 Merge branch 'master' of https://github.com/molstar/molstar into anim-rock 2022-01-09 14:25:36 -08:00
Alexander Rose
d61e18e6f3 fix mol2 element symbol assignment 2022-01-09 14:04:31 -08:00
Alexander Rose
ca4a725a79 Merge pull request #336 from molstar/bond-dist-id
IndexPairBonds improvements
2022-01-09 13:37:38 -08:00
Alexander Rose
17a18d5fea tweak bond assignment from IndexPairBonds
- fix & clarify logic
2022-01-09 13:25:14 -08:00
Alexander Rose
73be238ac4 rename IndexPairBonds mapping field from id to key 2022-01-09 12:51:26 -08:00
Alexander Rose
796a034fec add rock animation to trackball controls 2022-01-08 17:02:33 -08:00
Alexander Rose
952b320975 Merge branch 'master' of https://github.com/molstar/molstar into bond-dist-id 2022-01-08 13:23:15 -08:00
Alexander Rose
be0f06ff0f fix mol2 crysin support 2022-01-08 13:22:26 -08:00
Alexander Rose
6294ef2db2 fix stats for single element in multi-chain unit
- observe in, e.g., label for water molecule in 3pqr
2022-01-08 12:42:37 -08:00
Alexander Rose
78b5d505bd improve IndexPairBonds
- add id field
- better distance-based assignment
2022-01-08 12:35:40 -08:00
Alexander Rose
22afdffa15 add mol2 symmetry support
- only for spacegroup setting 1
2022-01-08 12:14:14 -08:00
Alexander Rose
d1056eddeb Merge pull request #335 from molstar/standalone-viewer
move Viewer class to separate file
2022-01-08 11:55:53 -08:00
dsehnal
8655f4d85a move viewer app to separate file 2022-01-08 10:31:53 +01:00
ptourlas
f9deb54352 FIX: Found the right tokenizer operations
(at last)
2022-01-08 01:16:59 +02:00
ptourlas
eae3c1b33a FIX: Formal charges are all on the same line
Therefore we have to get the count of charges and iterate based on that.
A handle properties function is added so that new handler can be used based
the property type. Note that this function only returns the charges at
the moment for simplicity. A more general version should return multiple
properties.
2022-01-07 16:14:47 +02:00
ptourlas
5c5f8aa741 FTR:(WIP) Add formal charges during model creation 2022-01-06 16:12:44 +02:00
ptourlas
ba68ac2e32 FTR: Parse formal charges in the atom block 2022-01-06 15:58:49 +02:00
ptourlas
239fef281e Merge branch 'master' into feature/formal-charge-labels 2022-01-06 13:32:05 +02:00
dsehnal
c0880b647f changelog 2022-01-03 13:51:09 +01:00
dsehnal
039dc6a76b changelog 2022-01-03 13:49:59 +01:00
dsehnal
042a7625ad isWithoutOperator tweak 2022-01-03 13:46:13 +01:00
dsehnal
41827c478d open zip files with multiple entries 2022-01-03 13:42:37 +01:00
dsehnal
9a73180c3c support transformed export & structAsymMap parsing fix 2022-01-03 13:15:51 +01:00
dsehnal
333ee85fdb omit suffix for identity assembly operators 2022-01-03 12:52:05 +01:00
dsehnal
fa8ca45b6a do not include assembly categories in export if an operator has been applied 2022-01-03 12:34:37 +01:00
dsehnal
c2bae1aeb7 model export extension 2022-01-03 12:26:36 +01:00
Alexander Rose
ada7a45fe6 add PDBj pdb-provider option 2022-01-01 16:56:23 -08:00
ptourlas
13ea97bd98 Merge branch 'master' into feature/formal-charge-labels 2021-12-27 18:18:27 +02:00
ptourlas
009a17a9ca (forgot the author tag) 2021-12-27 17:56:25 +02:00
ptourlas
ca38d9adb1 (WIP) Formal charges handler implementation 2021-12-27 17:44:59 +02:00
ptourlas
4e5a86e3db (WIP) Add the formal charges handler to sdf parser 2021-12-27 17:37:52 +02:00
ptourlas
a736fe7989 Add formal charge option to atom site 2021-12-15 00:35:34 +02:00
ptourlas
1970b7f249 Spot the formal charge prefix 2021-12-15 00:34:06 +02:00
90 changed files with 5325 additions and 6340 deletions

View File

@@ -6,6 +6,59 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v3.0.1] - 2022-01-27
- Fix marking pass not working with ``transparentBackground``
- Fix pdbe xray maps url not https
- Fix entity-id color theme broken for non-IHM models
- Improve/fix marking of ``InteractionsInterUnitVisual`` (mark when all contact-feature members are given)
- Add missing "entity-id" and "enity-source" options for carbon coloring to "element-symbol" color theme
- Fix VolumeServer/query CLI
- Support automatic iso-value adjustment for VolumeServer data in ``Viewer.loadVolumeFromUrl``
- Emit drag event whenever started within viewport (not only for non-empty loci)
## [v3.0.0] - 2022-01-23
- Assembly handling tweaks:
- Do not include suffix for "identity assembly operators"
- Do not include assembly-related categories to export if the structure was composed from an assembly
- Special case for ``structAsymMap`` if Mol* asym id operator mapping is present
- Support for opening ZIP files with multiple entries
- Add Model Export extension
- Bugfix: Automatically treat empty string as "non-present" value in BinaryCIF writer.
- Fix coarse model support in entity-id color theme
- Fix marking of carbohydrate visuals (whole chain could get marked instead of single residue)
- Add custom colors to "element-symbol", "molecule-type", "residue-name", and "secondary-structure" themes
- Support/bugfixes for ``atom_site.pdbx_sifts_xref`` categories
- Improve/fix marking of ``InteractionsIntraUnitVisual`` (mark when all contact-feature members are given)
## [v3.0.0-dev.10] - 2022-01-17
- Fix ``getOperatorsForIndex``
- Pass animation info (current frame & count) to state animations
- Fix camera stutter for "camera spin" animation
- Add formal charge parsing support for MOL/SDF files (thanks @ptourlas)
- [Breaking] Cleaner looking ``MembraneOrientationVisuals`` defaults
- [Breaking] Add rock animation to trackball controls
- Add ``animate`` to ``TrackballControlsParams``, remove ``spin`` and ``spinSpeed``
- Add ``animate`` to ``SimpleSettingsParams``, remove ``spin``
- Add "camera rock" state animation
- Add support for custom colors to "molecule-type" theme
- [Breaking] Add style parameter to "illustrative" color theme
- Defaults to "entity-id" style instead of "chain-id"
- Add "illustrative" representation preset
## [v3.0.0-dev.9] - 2022-01-09
- Add PDBj as a ``pdb-provider`` option
- Move Viewer APP to a separate file to allow use without importing light theme & index.html
- Add symmetry support for mol2 files (only spacegroup setting 1)
- Fix mol2 files element symbol assignment
- Improve bond assignment from ``IndexPairBonds``
- Add ``key`` field for mapping to source data
- Fix assignment of bonds with unphysical length
- Fix label/stats of single atom selection in multi-chain units
## [v3.0.0-dev.8] - 2021-12-31
- Add ``PluginFeatureDetection`` and disable WBOIT in Safari 15.

View File

@@ -11,6 +11,13 @@ When using Mol*, please cite:
David Sehnal, Sebastian Bittrich, Mandar Deshpande, Radka Svobodová, Karel Berka, Václav Bazgier, Sameer Velankar, Stephen K Burley, Jaroslav Koča, Alexander S Rose: [Mol* Viewer: modern web app for 3D visualization and analysis of large biomolecular structures](https://doi.org/10.1093/nar/gkab314), *Nucleic Acids Research*, 2021; https://doi.org/10.1093/nar/gkab314.
### Protein Data Bank Integrations
- The [pdbe-molstar](https://github.com/molstar/pdbe-molstar) library is the Mol* implementation used by EMBL-EBI data resources such as [PDBe](https://pdbe.org/), [PDBe-KB](https://pdbe-kb.org/) and [AlphaFold DB](https://alphafold.ebi.ac.uk/). This implementation can be used as a JS plugin and a Web component and supports property/attribute-based easy customisation. It provides helper methods to facilitate programmatic interactions between the web application and the 3D viewer. It also provides a superposition view for overlaying all the observed ligand molecules on representative protein conformations.
- [rcsb-molstar](https://github.com/molstar/rcsb-molstar) is the Mol* plugin used by [RCSB PDB](https://www.rcsb.org). The project provides additional presets for the visualization of structure alignments and structure motifs such as ligand binding sites. Furthermore, [rcsb-molstar](https://github.com/molstar/rcsb-molstar) allows to interactively add or hide of (parts of) chains, as seen in the [3D Protein Feature View](https://www.rcsb.org/3d-sequence/4hhb).
## Project Structure Overview
The core of Mol* consists of these modules (see under `src/`):

8538
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "3.0.0-dev.8",
"version": "3.0.1",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -13,7 +13,7 @@
"scripts": {
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"test": "npm run lint && jest",
"test": "npm install --no-save \"gl@^5.0.0\" && npm run lint && jest",
"jest": "jest",
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
"clean": "node ./scripts/clean.js",
@@ -86,45 +86,46 @@
"Áron Samuel Kovács <aron.kovacs@mail.muni.cz>",
"Ludovic Autin <autin@scripps.edu>",
"Michal Malý <michal.maly@ibt.cas.cz>",
"Jiří Černý <jiri.cerny@ibt.cas.cz>"
"Jiří Černý <jiri.cerny@ibt.cas.cz>",
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>"
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^3.1.0",
"@graphql-codegen/cli": "^2.3.0",
"@graphql-codegen/time": "^3.1.0",
"@graphql-codegen/typescript": "^2.4.1",
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.0",
"@graphql-codegen/typescript-graphql-request": "^4.3.1",
"@graphql-codegen/typescript-operations": "^2.2.1",
"@graphql-codegen/add": "^3.1.1",
"@graphql-codegen/cli": "^2.4.0",
"@graphql-codegen/time": "^3.1.1",
"@graphql-codegen/typescript": "^2.4.2",
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.1",
"@graphql-codegen/typescript-graphql-request": "^4.3.3",
"@graphql-codegen/typescript-operations": "^2.2.2",
"@types/cors": "^2.8.12",
"@types/gl": "^4.1.0",
"@types/jest": "^27.0.3",
"@typescript-eslint/eslint-plugin": "^5.5.0",
"@typescript-eslint/parser": "^5.5.0",
"@types/jest": "^27.4.0",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"benchmark": "^2.1.4",
"concurrently": "^6.4.0",
"cpx2": "^4.0.0",
"concurrently": "^7.0.0",
"cpx2": "^4.1.2",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.5.1",
"eslint": "^8.3.0",
"eslint": "^8.7.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^10.0.0",
"graphql": "^15.7.2",
"http-server": "^14.0.0",
"jest": "^27.3.1",
"mini-css-extract-plugin": "^2.4.5",
"graphql": "^16.2.0",
"http-server": "^14.1.0",
"jest": "^27.4.7",
"mini-css-extract-plugin": "^2.5.2",
"path-browserify": "^1.0.1",
"raw-loader": "^4.0.2",
"sass": "^1.43.5",
"sass-loader": "^12.3.0",
"simple-git": "^2.47.0",
"sass": "^1.49.0",
"sass-loader": "^12.4.0",
"simple-git": "^2.48.0",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.1",
"ts-jest": "^27.0.7",
"typescript": "^4.5.2",
"webpack": "^5.64.4",
"ts-jest": "^27.1.3",
"typescript": "^4.5.5",
"webpack": "^5.67.0",
"webpack-cli": "^4.9.1"
},
"dependencies": {
@@ -132,22 +133,22 @@
"@types/benchmark": "^2.1.1",
"@types/compression": "1.7.2",
"@types/express": "^4.17.13",
"@types/node": "^16.11.10",
"@types/node": "^16.11.21",
"@types/node-fetch": "^2.5.12",
"@types/react": "^17.0.37",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@types/swagger-ui-dist": "3.30.1",
"argparse": "^2.0.1",
"body-parser": "^1.19.0",
"body-parser": "^1.19.1",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.17.1",
"express": "^4.17.2",
"h264-mp4-encoder": "^1.0.12",
"immer": "^9.0.7",
"immutable": "^3.8.2",
"node-fetch": "^2.6.2",
"rxjs": "^7.4.0",
"swagger-ui-dist": "^4.1.1",
"immer": "^9.0.12",
"immutable": "^4.0.0",
"node-fetch": "^2.6.7",
"rxjs": "^7.5.2",
"swagger-ui-dist": "^4.2.1",
"tslib": "^2.3.1",
"util.promisify": "^1.1.1",
"xhr2": "^0.2.1"
@@ -155,8 +156,5 @@
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"optionalDependencies": {
"gl": "^4.9.2"
}
}

491
src/apps/viewer/app.ts Normal file
View File

@@ -0,0 +1,491 @@
/**
* Copyright (c) 2018-2022 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 { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
import { CellPack } from '../../extensions/cellpack';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { GeometryExport } from '../../extensions/geo-export';
import { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
import { ModelExport } from '../../extensions/model-export';
import { Mp4Export } from '../../extensions/mp4-export';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { Volume } from '../../mol-model/volume';
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset';
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
import { BuildInStructureFormat } from '../../mol-plugin-state/formats/structure';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model';
import { createPluginUI } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { PluginSpec } from '../../mol-plugin/spec';
import { PluginState } from '../../mol-plugin/state';
import { StateObjectRef, StateObjectSelector } from '../../mol-state';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import '../../mol-util/polyfill';
import { ObjectKeys } from '../../mol-util/type-helpers';
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
const CustomFormats = [
['g3d', G3dProvider] as const
];
const Extensions = {
'cellpack': PluginSpec.Behavior(CellPack),
'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids),
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
'g3d': PluginSpec.Behavior(G3DFormat),
'model-export': PluginSpec.Behavior(ModelExport),
'mp4-export': PluginSpec.Behavior(Mp4Export),
'geo-export': PluginSpec.Behavior(GeometryExport),
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
};
const DefaultViewerOptions = {
customFormats: CustomFormats as [string, DataFormatProvider][],
extensions: ObjectKeys(Extensions),
layoutIsExpanded: true,
layoutShowControls: true,
layoutShowRemoteState: true,
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
layoutShowSequence: true,
layoutShowLog: true,
layoutShowLeftPanel: true,
collapseLeftPanel: false,
collapseRightPanel: false,
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
pixelScale: PluginConfig.General.PixelScale.defaultValue,
pickScale: PluginConfig.General.PickScale.defaultValue,
pickPadding: PluginConfig.General.PickPadding.defaultValue,
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
};
type ViewerOptions = typeof DefaultViewerOptions;
export class Viewer {
constructor(public plugin: PluginUIContext) {
}
static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
const definedOptions = {} as any;
// filter for defined properies only so the default values
// are property applied
for (const p of Object.keys(options) as (keyof ViewerOptions)[]) {
if (options[p] !== void 0) definedOptions[p] = options[p];
}
const o: ViewerOptions = { ...DefaultViewerOptions, ...definedOptions };
const defaultSpec = DefaultPluginUISpec();
const spec: PluginUISpec = {
actions: defaultSpec.actions,
behaviors: [
...defaultSpec.behaviors,
...o.extensions.map(e => Extensions[e]),
],
animations: [...defaultSpec.animations || []],
customParamEditors: defaultSpec.customParamEditors,
customFormats: o?.customFormats,
layout: {
initial: {
isExpanded: o.layoutIsExpanded,
showControls: o.layoutShowControls,
controlsDisplay: o.layoutControlsDisplay,
regionState: {
bottom: 'full',
left: o.collapseLeftPanel ? 'collapsed' : 'full',
right: o.collapseRightPanel ? 'hidden' : 'full',
top: 'full',
}
},
},
components: {
...defaultSpec.components,
controls: {
...defaultSpec.components?.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
},
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
},
config: [
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
[PluginConfig.General.PixelScale, o.pixelScale],
[PluginConfig.General.PickScale, o.pickScale],
[PluginConfig.General.PickPadding, o.pickPadding],
[PluginConfig.General.EnableWboit, o.enableWboit],
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
[PluginConfig.State.DefaultServer, o.pluginStateServer],
[PluginConfig.State.CurrentServer, o.pluginStateServer],
[PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
[PluginConfig.VolumeStreaming.Enabled, !o.volumeStreamingDisabled],
[PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
[PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
]
};
const element = typeof elementOrId === 'string'
? document.getElementById(elementOrId)
: elementOrId;
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
const plugin = await createPluginUI(element, spec, {
onBeforeUIRender: plugin => {
// the preset needs to be added before the UI renders otherwise
// "Download Structure" wont be able to pick it up
plugin.builders.structure.representation.registerPreset(ViewerAutoPreset);
}
});
return new Viewer(plugin);
}
setRemoteSnapshot(id: string) {
const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`;
return PluginCommands.State.Snapshots.Fetch(this.plugin, { url });
}
loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) {
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
}
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'url',
params: {
url: Asset.Url(url),
format: format as any,
isBinary,
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
}
}
}));
}
async loadAllModelsOrAssemblyFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
const plugin = this.plugin;
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'all-models', { useDefaultIfSingleModel: true, representationPresetParams: options?.representationParams });
}
async loadStructureFromData(data: string | number[], format: BuiltInTrajectoryFormat, options?: { dataLabel?: string }) {
const _data = await this.plugin.builders.data.rawData({ data, label: options?.dataLabel });
const trajectory = await this.plugin.builders.structure.parseTrajectory(_data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
}
loadPdb(pdb: string, options?: LoadStructureOptions) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!;
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'pdb' as const,
params: {
provider: {
id: pdb,
server: {
name: provider,
params: PdbDownloadProvider[provider].defaultValue as any
}
},
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
}
}
}));
}
loadPdbDev(pdbDev: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'pdb-dev' as const,
params: {
provider: {
id: pdbDev,
encoding: 'bcif',
},
options: params.source.params.options,
}
}
}));
}
loadEmdb(emdb: string, options?: { detail?: number }) {
const provider = this.plugin.config.get(PluginConfig.Download.DefaultEmdbProvider)!;
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, {
source: {
name: 'pdb-emd-ds' as const,
params: {
provider: {
id: emdb,
server: provider,
},
detail: options?.detail ?? 3,
}
}
}));
}
loadAlphaFoldDb(afdb: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'alphafolddb' as const,
params: {
id: afdb,
options: {
...params.source.params.options,
representation: 'preset-structure-representation-ma-quality-assessment-plddt'
},
}
}
}));
}
loadModelArchive(id: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'modelarchive' as const,
params: {
id,
options: params.source.params.options,
}
}
}));
}
/**
* @example Load X-ray density from volume server
viewer.loadVolumeFromUrl({
url: 'https://www.ebi.ac.uk/pdbe/densities/x-ray/1tqn/cell?detail=3',
format: 'dscif',
isBinary: true
}, [{
type: 'relative',
value: 1.5,
color: 0x3362B2
}, {
type: 'relative',
value: 3,
color: 0x33BB33,
volumeIndex: 1
}, {
type: 'relative',
value: -3,
color: 0xBB3333,
volumeIndex: 1
}], {
entryId: ['2FO-FC', 'FO-FC'],
isLazy: true
});
* *********************
* @example Load EM density from volume server
viewer.loadVolumeFromUrl({
url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
format: 'dscif',
isBinary: true
}, [{
type: 'relative',
value: 1,
color: 0x3377aa
}], {
entryId: 'EMD-30210',
isLazy: true
});
*/
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string | string[], isLazy?: boolean }) {
const plugin = this.plugin;
if (!plugin.dataFormats.get(format)) {
throw new Error(`Unknown density format: ${format}`);
}
if (options?.isLazy) {
const update = this.plugin.build();
update.toRoot().apply(StateTransforms.Data.LazyVolume, {
url,
format,
entryId: options?.entryId,
isBinary,
isovalues: isovalues.map(v => ({ alpha: 1, volumeIndex: 0, ...v }))
});
return update.commit();
}
return plugin.dataTransaction(async () => {
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId });
const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
const repr = plugin.build();
for (const iso of isovalues) {
const volume: StateObjectSelector<PluginStateObject.Volume.Data> = parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume;
const volumeData = volume.cell!.obj!.data;
repr
.to(volume)
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
type: 'isosurface',
typeParams: { alpha: iso.alpha ?? 1, isoValue: Volume.adjustedIsoValue(volumeData, iso.value, iso.type) },
color: 'uniform',
colorParams: { value: iso.color }
}));
}
await repr.commit();
});
}
/**
* @example
* viewer.loadTrajectory({
* model: { kind: 'model-url', url: 'villin.gro', format: 'gro' },
* coordinates: { kind: 'coordinates-url', url: 'villin.xtc', format: 'xtc', isBinary: true },
* preset: 'all-models' // or 'default'
* });
*/
async loadTrajectory(params: LoadTrajectoryParams) {
const plugin = this.plugin;
let model: StateObjectSelector, coords: StateObjectSelector;
if (params.model.kind === 'model-data' || params.model.kind === 'model-url') {
const data = params.model.kind === 'model-data'
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
const trajectory = await plugin.builders.structure.parseTrajectory(data, params.model.format ?? 'mmcif');
model = await plugin.builders.structure.createModel(trajectory);
} else {
const data = params.model.kind === 'topology-data'
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
const provider = plugin.dataFormats.get(params.model.format);
model = await provider!.parse(plugin, data);
}
{
const data = params.coordinates.kind === 'coordinates-data'
? await plugin.builders.data.rawData({ data: params.coordinates.data, label: params.coordinatesLabel })
: await plugin.builders.data.download({ url: params.coordinates.url, isBinary: params.coordinates.isBinary, label: params.coordinatesLabel });
const provider = plugin.dataFormats.get(params.coordinates.format);
coords = await provider!.parse(plugin, data);
}
const trajectory = await plugin.build().toRoot()
.apply(TrajectoryFromModelAndCoordinates, {
modelRef: model.ref,
coordinatesRef: coords.ref
}, { dependsOn: [model.ref, coords.ref] })
.commit();
const preset = await plugin.builders.structure.hierarchy.applyPreset(trajectory, params.preset ?? 'default');
return { model, coords, preset };
}
handleResize() {
this.plugin.layout.events.updated.next(void 0);
}
}
export interface LoadStructureOptions {
representationParams?: StructureRepresentationPresetProvider.CommonParams
}
export interface VolumeIsovalueInfo {
type: 'absolute' | 'relative',
value: number,
color: Color,
alpha?: number,
volumeIndex?: number
}
export interface LoadTrajectoryParams {
model: { kind: 'model-url', url: string, format?: BuiltInTrajectoryFormat /* mmcif */, isBinary?: boolean }
| { kind: 'model-data', data: string | number[] | ArrayBuffer | Uint8Array, format?: BuiltInTrajectoryFormat /* mmcif */ }
| { kind: 'topology-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
| { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
modelLabel?: string,
coordinates: { kind: 'coordinates-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
coordinatesLabel?: string,
preset?: keyof PresetTrajectoryHierarchy
}
export const ViewerAutoPreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-viewer-auto',
display: {
name: 'Automatic (w/ Annotation)', group: 'Annotation',
description: 'Show standard automatic representation but colored by quality assessment (if available in the model).'
},
isApplicable(a) {
return (
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')) ||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))
);
},
params: () => StructureRepresentationPresetProvider.CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const structure = structureCell?.obj?.data;
if (!structureCell || !structure) return {};
if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'))) {
return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin);
} else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) {
return await QualityAssessmentQmeanPreset.apply(ref, params, plugin);
} else {
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
}
}
});

View File

@@ -57,7 +57,7 @@
var pickScale = getParam('pick-scale', '[^&]+').trim();
var pickPadding = getParam('pick-padding', '[^&]+').trim();
var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1';
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1' || void 0;
molstar.Viewer.create('app', {
layoutShowControls: !hideControls,
@@ -71,7 +71,7 @@
pixelScale: parseFloat(pixelScale) || 1,
pickScale: parseFloat(pickScale) || 0.25,
pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
enableWboit: !disableWboit,
enableWboit: disableWboit ? true : void 0, // use default value if disable-wboit is not set
preferWebgl1: preferWebgl1,
}).then(viewer => {
var snapshotId = getParam('snapshot-id', '[^&]+').trim();

View File

@@ -1,484 +1,12 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 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 { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
import { CellPack } from '../../extensions/cellpack';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { GeometryExport } from '../../extensions/geo-export';
import { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
import { Mp4Export } from '../../extensions/mp4-export';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset';
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
import { BuildInStructureFormat } from '../../mol-plugin-state/formats/structure';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model';
import { createPluginUI } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { PluginSpec } from '../../mol-plugin/spec';
import { PluginState } from '../../mol-plugin/state';
import { StateObjectRef, StateObjectSelector } from '../../mol-state';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import '../../mol-util/polyfill';
import { ObjectKeys } from '../../mol-util/type-helpers';
import './embedded.html';
import './favicon.ico';
import './index.html';
require('mol-plugin-ui/skin/light.scss');
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
const CustomFormats = [
['g3d', G3dProvider] as const
];
const Extensions = {
'cellpack': PluginSpec.Behavior(CellPack),
'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids),
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
'g3d': PluginSpec.Behavior(G3DFormat),
'mp4-export': PluginSpec.Behavior(Mp4Export),
'geo-export': PluginSpec.Behavior(GeometryExport),
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
};
const DefaultViewerOptions = {
customFormats: CustomFormats as [string, DataFormatProvider][],
extensions: ObjectKeys(Extensions),
layoutIsExpanded: true,
layoutShowControls: true,
layoutShowRemoteState: true,
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
layoutShowSequence: true,
layoutShowLog: true,
layoutShowLeftPanel: true,
collapseLeftPanel: false,
collapseRightPanel: false,
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
pixelScale: PluginConfig.General.PixelScale.defaultValue,
pickScale: PluginConfig.General.PickScale.defaultValue,
pickPadding: PluginConfig.General.PickPadding.defaultValue,
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
};
type ViewerOptions = typeof DefaultViewerOptions;
export class Viewer {
constructor(public plugin: PluginUIContext) {
}
static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
const o = { ...DefaultViewerOptions, ...options };
const defaultSpec = DefaultPluginUISpec();
const spec: PluginUISpec = {
actions: defaultSpec.actions,
behaviors: [
...defaultSpec.behaviors,
...o.extensions.map(e => Extensions[e]),
],
animations: [...defaultSpec.animations || []],
customParamEditors: defaultSpec.customParamEditors,
customFormats: o?.customFormats,
layout: {
initial: {
isExpanded: o.layoutIsExpanded,
showControls: o.layoutShowControls,
controlsDisplay: o.layoutControlsDisplay,
regionState: {
bottom: 'full',
left: o.collapseLeftPanel ? 'collapsed' : 'full',
right: o.collapseRightPanel ? 'hidden' : 'full',
top: 'full',
}
},
},
components: {
...defaultSpec.components,
controls: {
...defaultSpec.components?.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
},
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
},
config: [
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
[PluginConfig.General.PixelScale, o.pixelScale],
[PluginConfig.General.PickScale, o.pickScale],
[PluginConfig.General.PickPadding, o.pickPadding],
[PluginConfig.General.EnableWboit, o.enableWboit],
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
[PluginConfig.State.DefaultServer, o.pluginStateServer],
[PluginConfig.State.CurrentServer, o.pluginStateServer],
[PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
[PluginConfig.VolumeStreaming.Enabled, !o.volumeStreamingDisabled],
[PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
[PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
]
};
const element = typeof elementOrId === 'string'
? document.getElementById(elementOrId)
: elementOrId;
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
const plugin = await createPluginUI(element, spec, {
onBeforeUIRender: plugin => {
// the preset needs to be added before the UI renders otherwise
// "Download Structure" wont be able to pick it up
plugin.builders.structure.representation.registerPreset(ViewerAutoPreset);
}
});
return new Viewer(plugin);
}
setRemoteSnapshot(id: string) {
const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`;
return PluginCommands.State.Snapshots.Fetch(this.plugin, { url });
}
loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) {
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
}
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'url',
params: {
url: Asset.Url(url),
format: format as any,
isBinary,
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
}
}
}));
}
async loadAllModelsOrAssemblyFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
const plugin = this.plugin;
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'all-models', { useDefaultIfSingleModel: true, representationPresetParams: options?.representationParams });
}
async loadStructureFromData(data: string | number[], format: BuiltInTrajectoryFormat, options?: { dataLabel?: string }) {
const _data = await this.plugin.builders.data.rawData({ data, label: options?.dataLabel });
const trajectory = await this.plugin.builders.structure.parseTrajectory(_data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
}
loadPdb(pdb: string, options?: LoadStructureOptions) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!;
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'pdb' as const,
params: {
provider: {
id: pdb,
server: {
name: provider,
params: PdbDownloadProvider[provider].defaultValue as any
}
},
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
}
}
}));
}
loadPdbDev(pdbDev: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'pdb-dev' as const,
params: {
provider: {
id: pdbDev,
encoding: 'bcif',
},
options: params.source.params.options,
}
}
}));
}
loadEmdb(emdb: string, options?: { detail?: number }) {
const provider = this.plugin.config.get(PluginConfig.Download.DefaultEmdbProvider)!;
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, {
source: {
name: 'pdb-emd-ds' as const,
params: {
provider: {
id: emdb,
server: provider,
},
detail: options?.detail ?? 3,
}
}
}));
}
loadAlphaFoldDb(afdb: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'alphafolddb' as const,
params: {
id: afdb,
options: {
...params.source.params.options,
representation: 'preset-structure-representation-ma-quality-assessment-plddt'
},
}
}
}));
}
loadModelArchive(id: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'modelarchive' as const,
params: {
id,
options: params.source.params.options,
}
}
}));
}
/**
* @example Load X-ray density from volume server
viewer.loadVolumeFromUrl({
url: 'https://www.ebi.ac.uk/pdbe/densities/x-ray/1tqn/cell?detail=3',
format: 'dscif',
isBinary: true
}, [{
type: 'relative',
value: 1.5,
color: 0x3362B2
}, {
type: 'relative',
value: 3,
color: 0x33BB33,
volumeIndex: 1
}, {
type: 'relative',
value: -3,
color: 0xBB3333,
volumeIndex: 1
}], {
entryId: ['2FO-FC', 'FO-FC'],
isLazy: true
});
* *********************
* @example Load EM density from volume server
viewer.loadVolumeFromUrl({
url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
format: 'dscif',
isBinary: true
}, [{
type: 'relative',
value: 1,
color: 0x3377aa
}], {
entryId: 'EMD-30210',
isLazy: true
});
*/
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string | string[], isLazy?: boolean }) {
const plugin = this.plugin;
if (!plugin.dataFormats.get(format)) {
throw new Error(`Unknown density format: ${format}`);
}
if (options?.isLazy) {
const update = this.plugin.build();
update.toRoot().apply(StateTransforms.Data.LazyVolume, {
url,
format,
entryId: options?.entryId,
isBinary,
isovalues: isovalues.map(v => ({ alpha: 1, volumeIndex: 0, ...v }))
});
return update.commit();
}
return plugin.dataTransaction(async () => {
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId });
const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
const repr = plugin.build();
for (const iso of isovalues) {
repr
.to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume)
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
type: 'isosurface',
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
color: 'uniform',
colorParams: { value: iso.color }
}));
}
await repr.commit();
});
}
/**
* @example
* viewer.loadTrajectory({
* model: { kind: 'model-url', url: 'villin.gro', format: 'gro' },
* coordinates: { kind: 'coordinates-url', url: 'villin.xtc', format: 'xtc', isBinary: true },
* preset: 'all-models' // or 'default'
* });
*/
async loadTrajectory(params: LoadTrajectoryParams) {
const plugin = this.plugin;
let model: StateObjectSelector, coords: StateObjectSelector;
if (params.model.kind === 'model-data' || params.model.kind === 'model-url') {
const data = params.model.kind === 'model-data'
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
const trajectory = await plugin.builders.structure.parseTrajectory(data, params.model.format ?? 'mmcif');
model = await plugin.builders.structure.createModel(trajectory);
} else {
const data = params.model.kind === 'topology-data'
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
const provider = plugin.dataFormats.get(params.model.format);
model = await provider!.parse(plugin, data);
}
{
const data = params.coordinates.kind === 'coordinates-data'
? await plugin.builders.data.rawData({ data: params.coordinates.data, label: params.coordinatesLabel })
: await plugin.builders.data.download({ url: params.coordinates.url, isBinary: params.coordinates.isBinary, label: params.coordinatesLabel });
const provider = plugin.dataFormats.get(params.coordinates.format);
coords = await provider!.parse(plugin, data);
}
const trajectory = await plugin.build().toRoot()
.apply(TrajectoryFromModelAndCoordinates, {
modelRef: model.ref,
coordinatesRef: coords.ref
}, { dependsOn: [model.ref, coords.ref] })
.commit();
const preset = await plugin.builders.structure.hierarchy.applyPreset(trajectory, params.preset ?? 'default');
return { model, coords, preset };
}
handleResize() {
this.plugin.layout.events.updated.next(void 0);
}
}
export interface LoadStructureOptions {
representationParams?: StructureRepresentationPresetProvider.CommonParams
}
export interface VolumeIsovalueInfo {
type: 'absolute' | 'relative',
value: number,
color: Color,
alpha?: number,
volumeIndex?: number
}
export interface LoadTrajectoryParams {
model: { kind: 'model-url', url: string, format?: BuiltInTrajectoryFormat /* mmcif */, isBinary?: boolean }
| { kind: 'model-data', data: string | number[] | ArrayBuffer | Uint8Array, format?: BuiltInTrajectoryFormat /* mmcif */ }
| { kind: 'topology-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
| { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
modelLabel?: string,
coordinates: { kind: 'coordinates-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
coordinatesLabel?: string,
preset?: keyof PresetTrajectoryHierarchy
}
export const ViewerAutoPreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-viewer-auto',
display: {
name: 'Automatic (w/ Annotation)', group: 'Annotation',
description: 'Show standard automatic representation but colored by quality assessment (if available in the model).'
},
isApplicable(a) {
return (
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')) ||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))
);
},
params: () => StructureRepresentationPresetProvider.CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const structure = structureCell?.obj?.data;
if (!structureCell || !structure) return {};
if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'))) {
return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin);
} else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) {
return await QualityAssessmentQmeanPreset.apply(ref, params, plugin);
} else {
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
}
}
});
export * from './app';

View File

@@ -74,12 +74,20 @@ class BasicWrapper {
toggleSpin() {
if (!this.plugin.canvas3d) return;
const trackball = this.plugin.canvas3d.props.trackball;
PluginCommands.Canvas3D.SetSettings(this.plugin, {
settings: props => {
props.trackball.spin = !props.trackball.spin;
settings: {
trackball: {
...trackball,
animate: trackball.animate.name === 'spin'
? { name: 'off', params: {} }
: { name: 'spin', params: { speed: 1 } }
}
}
});
if (!this.plugin.canvas3d.props.trackball.spin) PluginCommands.Camera.Reset(this.plugin, {});
if (this.plugin.canvas3d.props.trackball.animate.name !== 'spin') {
PluginCommands.Camera.Reset(this.plugin, {});
}
}
private animateModelIndexTargetFps() {

View File

@@ -256,7 +256,16 @@ class MolStarProteopediaWrapper {
toggleSpin() {
if (!this.plugin.canvas3d) return;
const trackball = this.plugin.canvas3d.props.trackball;
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
PluginCommands.Canvas3D.SetSettings(this.plugin, {
settings: {
trackball: {
...trackball,
animate: trackball.animate.name === 'spin'
? { name: 'off', params: {} }
: { name: 'spin', params: { speed: 1 } }
}
}
});
}
viewport = {

View File

@@ -43,9 +43,9 @@ export type BilayerPlanesProps = PD.Values<BilayerPlanesParams>
const BilayerRimsParams = {
...Lines.Params,
...SharedParams,
lineSizeAttenuation: PD.Boolean(true),
linesSize: PD.Numeric(0.3, { min: 0.01, max: 50, step: 0.01 }),
dashedLines: PD.Boolean(true),
lineSizeAttenuation: PD.Boolean(false),
linesSize: PD.Numeric(0.5, { min: 0.01, max: 50, step: 0.01 }),
dashedLines: PD.Boolean(false),
};
export type BilayerRimsParams = typeof BilayerRimsParams
export type BilayerRimsProps = PD.Values<BilayerRimsParams>

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { utf8ByteCount, utf8Write } from '../../mol-io/common/utf8';
import { to_mmCIF, Unit } from '../../mol-model/structure';
import { PluginContext } from '../../mol-plugin/context';
import { Task } from '../../mol-task';
import { getFormattedTime } from '../../mol-util/date';
import { download } from '../../mol-util/download';
import { zip } from '../../mol-util/zip/zip';
export async function exportHierarchy(plugin: PluginContext, options?: { format?: 'cif' | 'bcif' }) {
try {
await plugin.runTask(_exportHierarchy(plugin, options), { useOverlay: true });
} catch (e) {
console.error(e);
plugin.log.error(`Model export failed. See console for details.`);
}
}
function _exportHierarchy(plugin: PluginContext, options?: { format?: 'cif' | 'bcif' }) {
return Task.create('Export', async ctx => {
await ctx.update({ message: 'Exporting...', isIndeterminate: true, canAbort: false });
const format = options?.format ?? 'cif';
const { structures } = plugin.managers.structure.hierarchy.current;
const files: [name: string, data: string | Uint8Array][] = [];
const entryMap = new Map<string, number>();
for (const _s of structures) {
const s = _s.transform?.cell.obj?.data ?? _s.cell.obj?.data;
if (!s) continue;
if (s.models.length > 1) {
plugin.log.warn(`[Export] Skipping ${_s.cell.obj?.label}: Multimodel exports not supported.`);
continue;
}
if (s.units.some(u => !Unit.isAtomic(u))) {
plugin.log.warn(`[Export] Skipping ${_s.cell.obj?.label}: Non-atomic model exports not supported.`);
continue;
}
const name = entryMap.has(s.model.entryId)
? `${s.model.entryId}_${entryMap.get(s.model.entryId)! + 1}.${format}`
: `${s.model.entryId}.${format}`;
entryMap.set(s.model.entryId, (entryMap.get(s.model.entryId) ?? 0) + 1);
await ctx.update({ message: `Exporting ${s.model.entryId}...`, isIndeterminate: true, canAbort: false });
if (s.elementCount > 100000) {
// Give UI chance to update, only needed for larger structures.
await new Promise(res => setTimeout(res, 50));
}
try {
files.push([name, to_mmCIF(s.model.entryId, s, format === 'bcif', { copyAllCategories: true })]);
} catch (e) {
if (format === 'cif' && s.elementCount > 2000000) {
plugin.log.warn(`[Export] The structure might be too big to be exported as Text CIF, consider using the BinaryCIF format instead.`);
}
throw e;
}
}
if (files.length === 1) {
download(new Blob([files[0][1]]), files[0][0]);
} else if (files.length > 1) {
const zipData: Record<string, Uint8Array> = {};
for (const [fn, data] of files) {
if (data instanceof Uint8Array) {
zipData[fn] = data;
} else {
const bytes = new Uint8Array(utf8ByteCount(data));
utf8Write(bytes, 0, data);
zipData[fn] = bytes;
}
}
await ctx.update({ message: `Compressing Data...`, isIndeterminate: true, canAbort: false });
const buffer = await zip(ctx, zipData);
download(new Blob([new Uint8Array(buffer, 0, buffer.byteLength)]), `structures_${getFormattedTime()}.zip`);
}
plugin.log.info(`[Export] Done.`);
});
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
import { ModelExportUI } from './ui';
export const ModelExport = PluginBehavior.create<{}>({
name: 'extension-model-export',
category: 'misc',
display: {
name: 'Model Export'
},
ctor: class extends PluginBehavior.Handler<{}> {
register(): void {
this.ctx.customStructureControls.set('model-export', ModelExportUI as any);
}
update() {
return false;
}
unregister() {
this.ctx.customStructureControls.delete('model-export');
}
},
params: () => ({})
});

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { useState } from 'react';
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
import { Button } from '../../mol-plugin-ui/controls/common';
import { GetAppSvg } from '../../mol-plugin-ui/controls/icons';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
import { PluginContext } from '../../mol-plugin/context';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { exportHierarchy } from './export';
export class ModelExportUI extends CollapsableControls<{}, {}> {
protected defaultState(): CollapsableState {
return {
header: 'Export Models',
isCollapsed: true,
brand: { accent: 'cyan', svg: GetAppSvg }
};
}
protected renderControls(): JSX.Element | null {
return <ExportControls plugin={this.plugin} />;
}
}
const Params = {
format: PD.Select<'cif' | 'bcif'>('cif', [['cif', 'mmCIF'], ['bcif', 'Binary mmCIF']])
};
const DefaultParams = PD.getDefaultValues(Params);
function ExportControls({ plugin }: { plugin: PluginContext }) {
const [params, setParams] = useState(DefaultParams);
const [exporting, setExporting] = useState(false);
useBehavior(plugin.managers.structure.hierarchy.behaviors.selection); // triggers UI update
const isBusy = useBehavior(plugin.behaviors.state.isBusy);
const hierarchy = plugin.managers.structure.hierarchy.current;
let label: string = 'Nothing to Export';
if (hierarchy.structures.length === 1) {
label = 'Export';
} if (hierarchy.structures.length > 1) {
label = 'Export (as ZIP)';
}
const onExport = async () => {
setExporting(true);
try {
await exportHierarchy(plugin, { format: params.format });
} finally {
setExporting(false);
}
};
return <>
<ParameterControls params={Params} values={params} onChangeValues={setParams} isDisabled={isBusy || exporting} />
<Button
onClick={onExport}
style={{ marginTop: 1 }}
disabled={isBusy || hierarchy.structures.length === 0 || exporting}
commit={hierarchy.structures.length ? 'on' : 'off'}
>
{label}
</Button>
</>;
}

View File

@@ -73,7 +73,7 @@ export async function encodeMp4Animation<A extends PluginStateAnimation>(plugin:
await plugin.managers.animation.play(params.animation.definition, params.animation.params);
stoppedAnimation = false;
for (let i = 0; i <= N; i++) {
await loop.tick(i * dt, { isSynchronous: true, manualDraw: true });
await loop.tick(i * dt, { isSynchronous: true, animation: { currentFrame: i, frameCount: N }, manualDraw: true });
const image = params.pass.getImageData(width, height, normalizedViewport);
encoder.addFrameRgba(image.data);

View File

@@ -4,7 +4,7 @@ export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
// Generated on 2021-11-25T14:34:23-08:00
// Generated on 2022-01-15T19:52:34-08:00
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
@@ -11113,11 +11113,11 @@ export type RcsbUniprotAlignments = {
export type RcsbUniprotAlignmentsCoreEntityAlignments = {
/** Aligned region */
readonly aligned_regions?: Maybe<ReadonlyArray<Maybe<CoreEntityAlignmentsAlignedRegions>>>;
readonly aligned_regions: ReadonlyArray<Maybe<CoreEntityAlignmentsAlignedRegions>>;
/** core_entity identifiers */
readonly core_entity_identifiers?: Maybe<CoreEntityAlignmentsCoreEntityIdentifiers>;
/** Alignment scores */
readonly scores?: Maybe<CoreEntityAlignmentsScores>;
readonly scores: CoreEntityAlignmentsScores;
};
export type RcsbUniprotAnnotation = {

View File

@@ -404,7 +404,7 @@ namespace Canvas3D {
const ctx = { renderer, camera: cam, scene, helper };
if (MultiSamplePass.isEnabled(p.multiSample)) {
const forceOn = !cameraChanged && allowMulti && !controls.props.spin;
const forceOn = !cameraChanged && allowMulti && !controls.isAnimating;
multiSampleHelper.render(ctx, p, true, forceOn);
} else {
passes.draw.render(ctx, p, true);
@@ -444,7 +444,7 @@ namespace Canvas3D {
}
draw();
if (!camera.transition.inTransition && !controls.props.spin && !webgl.isContextLost) {
if (!camera.transition.inTransition && !webgl.isContextLost) {
interactionHelper.tick(currentTime);
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 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>
@@ -13,7 +13,7 @@ import { Viewport } from '../camera/util';
import { InputObserver, DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys, GestureInput } from '../../mol-util/input/input-observer';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Camera } from '../camera';
import { absMax } from '../../mol-math/misc';
import { absMax, degToRad } from '../../mol-math/misc';
import { Binding } from '../../mol-util/binding';
const B = ButtonsType;
@@ -40,8 +40,16 @@ export const TrackballControlsParams = {
zoomSpeed: PD.Numeric(7.0, { min: 1, max: 15, step: 1 }),
panSpeed: PD.Numeric(1.0, { min: 0.1, max: 5, step: 0.1 }),
spin: PD.Boolean(false, { description: 'Spin the 3D scene around the x-axis in view space' }),
spinSpeed: PD.Numeric(1, { min: -20, max: 20, step: 1 }),
animate: PD.MappedStatic('off', {
off: PD.EmptyGroup(),
spin: PD.Group({
speed: PD.Numeric(1, { min: -20, max: 20, step: 1 }),
}, { description: 'Spin the 3D scene around the x-axis in view space' }),
rock: PD.Group({
speed: PD.Numeric(0.3, { min: -5, max: 5, step: 0.1 }),
angle: PD.Numeric(10, { min: 0, max: 90, step: 1 }, { description: 'How many degrees to rotate in each direction.' }),
}, { description: 'Rock the 3D scene around the x-axis in view space' })
}),
staticMoving: PD.Boolean(true, { isHidden: true }),
dynamicDampingFactor: PD.Numeric(0.2, {}, { isHidden: true }),
@@ -72,7 +80,8 @@ export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams>
export { TrackballControls };
interface TrackballControls {
viewport: Viewport
readonly viewport: Viewport
readonly isAnimating: boolean
readonly props: Readonly<TrackballControlsProps>
setProps: (props: Partial<TrackballControlsProps>) => void
@@ -144,6 +153,11 @@ namespace TrackballControls {
);
}
function getRotateFactor() {
const aspectRatio = input.width / input.height;
return p.rotateSpeed * input.pixelRatio * aspectRatio;
}
const rotAxis = Vec3();
const rotQuat = Quat();
const rotEyeDir = Vec3();
@@ -156,8 +170,7 @@ namespace TrackballControls {
const dy = _rotCurr[1] - _rotPrev[1];
Vec3.set(rotMoveDir, dx, dy, 0);
const aspectRatio = input.width / input.height;
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed * input.pixelRatio * aspectRatio;
const angle = Vec3.magnitude(rotMoveDir) * getRotateFactor();
if (angle) {
Vec3.sub(_eye, camera.position, camera.target);
@@ -306,7 +319,10 @@ namespace TrackballControls {
/** Update the object's position, direction and up vectors */
function update(t: number) {
if (lastUpdated === t) return;
if (p.spin && lastUpdated > 0) spin(t - lastUpdated);
if (lastUpdated > 0) {
if (p.animate.name === 'spin') spin(t - lastUpdated);
else if (p.animate.name === 'rock') rock(t - lastUpdated);
}
Vec3.sub(_eye, camera.position, camera.target);
@@ -345,6 +361,7 @@ namespace TrackballControls {
if (!isStart && !_isInteracting) return;
_isInteracting = true;
resetRock(); // start rocking from the center after interactions
const dragRotate = Binding.match(p.bindings.dragRotate, buttons, modifiers);
const dragRotateZ = Binding.match(p.bindings.dragRotateZ, buttons, modifiers);
@@ -434,11 +451,34 @@ namespace TrackballControls {
const _spinSpeed = Vec2.create(0.005, 0);
function spin(deltaT: number) {
if (p.spinSpeed === 0) return;
if (p.animate.name !== 'spin' || p.animate.params.speed === 0 || _isInteracting) return;
const frameSpeed = (p.spinSpeed || 0) / 1000;
const frameSpeed = p.animate.params.speed / 1000;
_spinSpeed[0] = 60 * Math.min(Math.abs(deltaT), 1000 / 8) / 1000 * frameSpeed;
if (!_isInteracting) Vec2.add(_rotCurr, _rotPrev, _spinSpeed);
Vec2.add(_rotCurr, _rotPrev, _spinSpeed);
}
let _rockPhase = 0;
const _rockSpeed = Vec2.create(0.005, 0);
function rock(deltaT: number) {
if (p.animate.name !== 'rock' || p.animate.params.speed === 0 || _isInteracting) return;
const dt = deltaT / 1000 * p.animate.params.speed;
const maxAngle = degToRad(p.animate.params.angle) / getRotateFactor();
const angleA = Math.sin(_rockPhase * Math.PI * 2) * maxAngle;
const angleB = Math.sin((_rockPhase + dt) * Math.PI * 2) * maxAngle;
_rockSpeed[0] = angleB - angleA;
Vec2.add(_rotCurr, _rotPrev, _rockSpeed);
_rockPhase += dt;
if (_rockPhase >= 1) {
_rockPhase = 0;
}
}
function resetRock() {
_rockPhase = 0;
}
function start(t: number) {
@@ -448,9 +488,13 @@ namespace TrackballControls {
return {
viewport,
get isAnimating() { return p.animate.name !== 'off'; },
get props() { return p as Readonly<TrackballControlsProps>; },
setProps: (props: Partial<TrackballControlsProps>) => {
if (props.animate?.name === 'rock' && p.animate.name !== 'rock') {
resetRock(); // start rocking from the center
}
Object.assign(p, props);
},

View File

@@ -71,7 +71,7 @@ export class Canvas3dInteractionHelper {
const xyChanged = this.startX !== this.endX || this.startY !== this.endY;
if (e === InputEvent.Drag) {
if (xyChanged && !Representation.Loci.isEmpty(this.prevLoci)) {
if (xyChanged && !this.outsideViewport(this.startX, this.startY)) {
this.events.drag.next({ current: this.prevLoci, buttons: this.buttons, button: this.button, modifiers: this.modifiers, pageStart: Vec2.create(this.startX, this.startY), pageEnd: Vec2.create(this.endX, this.endY) });
this.startX = this.endX;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -310,12 +310,12 @@ export class DrawPass {
const markingDepthTest = props.marking.ghostEdgeStrength < 1;
if (markingDepthTest) {
this.marking.depthTarget.bind();
renderer.clear(false);
renderer.clear(false, true);
renderer.renderMarkingDepth(scene.primitives, camera, null);
}
this.marking.maskTarget.bind();
renderer.clear(false);
renderer.clear(false, true);
renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
this.marking.update(props.marking);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -53,7 +53,7 @@ interface Renderer {
readonly stats: RendererStats
readonly props: Readonly<RendererProps>
clear: (toBackgroundColor: boolean) => void
clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => void
clearDepth: () => void
update: (camera: ICamera) => void
@@ -523,13 +523,13 @@ namespace Renderer {
};
return {
clear: (toBackgroundColor: boolean) => {
clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => {
state.enable(gl.SCISSOR_TEST);
state.enable(gl.DEPTH_TEST);
state.colorMask(true, true, true, true);
state.depthMask(true);
if (transparentBackground) {
if (transparentBackground && !ignoreTransparentBackground) {
state.clearColor(0, 0, 0, 0);
} else if (toBackgroundColor) {
state.clearColor(bgColor[0], bgColor[1], bgColor[2], 1);

View File

@@ -1,5 +1,12 @@
/**
* Copyright (c) 2019-2022 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>
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
*/
import { parseMol } from '../mol/parser';
import { parseMol, formalChargeMapper } from '../mol/parser';
const MolString = `2244
-OEChem-04072009073D
@@ -49,6 +56,48 @@ const MolString = `2244
13 20 1 0 0 0 0
M END`;
const MolStringWithAtomBlockCharge = `
Ketcher 1 72215442D 1 1.00000 0.00000 0
4 3 0 0 0 0 999 V2000
0.0000 0.0000 0.0000 C 0 1 0 0 0 0 0 0 0 0 0 0
0.8660 0.5000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
-0.8660 0.5000 0.0000 S 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 -1.0000 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
1 4 2 0 0 0 0
3 1 1 0 0 0 0
2 1 1 0 0 0 0
M END`;
const MolStringWithPropertyBlockCharge = `
Ketcher 1 72215442D 1 1.00000 0.00000 0
4 3 0 0 0 0 999 V2000
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.8660 0.5000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
-0.8660 0.5000 0.0000 S 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 -1.0000 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
1 4 2 0 0 0 0
3 1 1 0 0 0 0
2 1 1 0 0 0 0
M CHG 3 2 -1 3 1 4 1
M END`;
const MolStringWithMultipleChargeLines = `
Ketcher 1 72215442D 1 1.00000 0.00000 0
4 3 0 0 0 0 999 V2000
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.8660 0.5000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
-0.8660 0.5000 0.0000 S 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 -1.0000 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
1 4 2 0 0 0 0
3 1 1 0 0 0 0
2 1 1 0 0 0 0
M CHG 1 2 -1
M CHG 2 3 1 4 1
M END`;
describe('mol reader', () => {
it('basic', async () => {
const parsed = await parseMol(MolString).run();
@@ -70,4 +119,63 @@ describe('mol reader', () => {
expect(bonds.atomIdxB.value(20)).toBe(20);
expect(bonds.order.value(20)).toBe(1);
});
it('property block charges', async () => {
const parsed = await parseMol(MolStringWithPropertyBlockCharge).run();
if (parsed.isError) {
throw new Error(parsed.message);
}
const { formalCharges } = parsed.result;
expect(formalCharges.atomIdx.rowCount).toBe(3);
expect(formalCharges.charge.rowCount).toBe(3);
expect(formalCharges.atomIdx.value(0)).toBe(2);
expect(formalCharges.atomIdx.value(1)).toBe(3);
expect(formalCharges.charge.value(0)).toBe(-1);
expect(formalCharges.charge.value(1)).toBe(1);
});
it('multiple charge lines', async () => {
const parsed = await parseMol(MolStringWithMultipleChargeLines).run();
if (parsed.isError) {
throw new Error(parsed.message);
}
const { formalCharges } = parsed.result;
expect(formalCharges.atomIdx.rowCount).toBe(3);
expect(formalCharges.charge.rowCount).toBe(3);
expect(formalCharges.atomIdx.value(0)).toBe(2);
expect(formalCharges.atomIdx.value(1)).toBe(3);
expect(formalCharges.charge.value(0)).toBe(-1);
expect(formalCharges.charge.value(1)).toBe(1);
});
it('atom block charge mapping', async () => {
expect(formalChargeMapper(7)).toBe(-3);
expect(formalChargeMapper(6)).toBe(-2);
expect(formalChargeMapper(5)).toBe(-1);
expect(formalChargeMapper(0)).toBe(0);
expect(formalChargeMapper(3)).toBe(1);
expect(formalChargeMapper(2)).toBe(2);
expect(formalChargeMapper(1)).toBe(3);
expect(formalChargeMapper(4)).toBe(0);
});
it('atom block charges', async () => {
const parsed = await parseMol(MolStringWithAtomBlockCharge).run();
if (parsed.isError) {
throw new Error(parsed.message);
}
const { atoms, formalCharges } = parsed.result;
/* No property block charges */
expect(formalCharges.atomIdx.rowCount).toBe(0);
expect(formalCharges.charge.rowCount).toBe(0);
expect(atoms.formal_charge.value(0)).toBe(1);
expect(atoms.formal_charge.value(1)).toBe(0);
expect(atoms.formal_charge.value(2)).toBe(0);
expect(atoms.formal_charge.value(3)).toBe(0);
});
});

View File

@@ -244,6 +244,84 @@ GASTEIGER
25 13 23 1
26 13 24 1`;
const Mol2StringCrysin = `@<TRIPOS>MOLECULE
1144204
12 11 2 0 0
SMALL
USER_CHARGES
****
Generated from the CSD
@<TRIPOS>ATOM
1 Cl1 0.0925 3.6184 1.9845 Cl 1 RES1 -1.0000
2 C1 -4.7391 0.3350 0.4215 C.ar 2 RES2 0.0000
3 C2 -3.4121 0.2604 0.9351 C.ar 2 RES2 0.0000
4 C3 -2.9169 1.2555 1.7726 C.ar 2 RES2 0.0000
5 C4 -3.7118 2.3440 2.1099 C.ar 2 RES2 0.0000
6 C5 -5.0314 2.4052 1.6209 C.ar 2 RES2 0.0000
7 C6 -5.5372 1.4057 0.7962 C.ar 2 RES2 0.0000
8 C7 -6.9925 1.4547 0.3334 C.3 2 RES2 0.0000
9 C8 -7.8537 0.5554 1.1859 C.3 2 RES2 0.0000
10 N1 -9.3089 0.7134 0.8192 N.3 2 RES2 1.0000
11 O1 -2.6613 -0.8147 0.5707 O.3 2 RES2 0.0000
12 O2 -1.6204 1.0919 2.2584 O.3 2 RES2 0.0000
@<TRIPOS>BOND
1 2 3 ar
2 3 4 ar
3 4 5 ar
4 5 6 ar
5 6 7 ar
6 7 2 ar
7 8 7 1
8 9 8 1
9 10 9 1
10 11 3 1
11 12 4 1
@<TRIPOS>SUBSTRUCTURE
1 RES1 1 GROUP 0 **** **** 0
2 RES2 2 GROUP 0 **** **** 0
@<TRIPOS>CRYSIN
10.5150 11.1300 7.9380 90.0000 90.0000 90.0000 29 5
@<TRIPOS>MOLECULE
1144204
12 11 2 0 0
SMALL
USER_CHARGES
****
Generated from the CSD
@<TRIPOS>ATOM
1 Cl1 0.0925 3.6184 1.9845 Cl 1 RES1 -1.0000
2 C1 -4.7391 0.3350 0.4215 C.ar 2 RES2 0.0000
3 C2 -3.4121 0.2604 0.9351 C.ar 2 RES2 0.0000
4 C3 -2.9169 1.2555 1.7726 C.ar 2 RES2 0.0000
5 C4 -3.7118 2.3440 2.1099 C.ar 2 RES2 0.0000
6 C5 -5.0314 2.4052 1.6209 C.ar 2 RES2 0.0000
7 C6 -5.5372 1.4057 0.7962 C.ar 2 RES2 0.0000
8 C7 -6.9925 1.4547 0.3334 C.3 2 RES2 0.0000
9 C8 -7.8537 0.5554 1.1859 C.3 2 RES2 0.0000
10 N1 -9.3089 0.7134 0.8192 N.3 2 RES2 1.0000
11 O1 -2.6613 -0.8147 0.5707 O.3 2 RES2 0.0000
12 O2 -1.6204 1.0919 2.2584 O.3 2 RES2 0.0000
@<TRIPOS>BOND
1 2 3 ar
2 3 4 ar
3 4 5 ar
4 5 6 ar
5 6 7 ar
6 7 2 ar
7 8 7 1
8 9 8 1
9 10 9 1
10 11 3 1
11 12 4 1
@<TRIPOS>SUBSTRUCTURE
1 RES1 1 GROUP 0 **** **** 0
2 RES2 2 GROUP 0 **** **** 0
@<TRIPOS>CRYSIN
10.5150 11.1300 7.9380 90.0000 90.0000 90.0000 29 5
`;
describe('mol2 reader', () => {
it('basic', async () => {
const parsed = await parseMol2(Mol2String, '').run();
@@ -397,4 +475,29 @@ describe('mol2 reader', () => {
// optional bond fields
expect(bonds.status_bits.value(0)).toBe('');
});
it('crysin', async () => {
const parsed = await parseMol2(Mol2StringCrysin, '').run();
if (parsed.isError) {
throw new Error(parsed.message);
}
const mol2File = parsed.result;
// number of structures
expect(mol2File.structures.length).toBe(2);
// crysin fields
for (const data of mol2File.structures) {
expect(data.crysin).toEqual({
a: 10.5150,
b: 11.1300,
c: 7.9380,
alpha: 90.0,
beta: 90.0,
gamma: 90.0,
spaceGroup: 29,
setting: 5
});
}
});
});

View File

@@ -1,3 +1,10 @@
/**
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
* @author David Sehnal <david.sehnal@gmail.com>
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
*/
import { parseSdf } from '../sdf/parser';
@@ -458,6 +465,38 @@ describe('sdf reader', () => {
expect(compound3.dataItems.data.value(21)).toBe('2\n5\n10');
});
it('charge parsing in V2000', async () => {
const parsed = await parseSdf(SdfString).run();
if (parsed.isError) {
throw new Error(parsed.message);
}
const compound1 = parsed.result.compounds[0];
const compound2 = parsed.result.compounds[1];
const compound3 = parsed.result.compounds[2];
const formalCharges1 = {
atomIdx: compound1.molFile.formalCharges.atomIdx,
charge: compound1.molFile.formalCharges.charge
};
const formalCharges2 = {
atomIdx: compound2.molFile.formalCharges.atomIdx,
charge: compound2.molFile.formalCharges.charge
};
const formalCharges3 = {
atomIdx: compound3.molFile.formalCharges.atomIdx,
charge: compound3.molFile.formalCharges.charge
};
expect(formalCharges1.atomIdx.rowCount).toBe(3);
expect(formalCharges2.atomIdx.rowCount).toBe(3);
expect(formalCharges3.atomIdx.rowCount).toBe(0);
expect(formalCharges1.charge.rowCount === formalCharges1.atomIdx.rowCount).toBe(true);
expect(formalCharges2.charge.rowCount === formalCharges2.atomIdx.rowCount).toBe(true);
expect(formalCharges3.charge.rowCount === formalCharges3.atomIdx.rowCount).toBe(true);
});
it('v3000', async () => {
const parsed = await parseSdf(V3000SdfString).run();
if (parsed.isError) {
@@ -486,6 +525,11 @@ describe('sdf reader', () => {
expect(compound1.molFile.bonds.atomIdxB.value(10)).toBe(9);
expect(compound1.molFile.bonds.order.value(10)).toBe(2);
expect(compound1.molFile.formalCharges.atomIdx.rowCount).toBe(13);
for (let i = 0; i < compound1.molFile.atoms.count; i++) {
expect(compound1.molFile.formalCharges.charge.value(i)).toBe(0);
}
expect(compound1.dataItems.dataHeader.rowCount).toBe(2);
expect(compound1.dataItems.data.rowCount).toBe(2);

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.354, IHM 1.17, MA 1.3.3.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.354, IHM 1.17, MA 1.3.3.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.353, IHM 1.17, MA 1.3.3.
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.354, IHM 1.17, MA 1.3.3.
*
* @author molstar/ciftools package
*/
@@ -682,7 +682,7 @@ export const mmCIF_Schema = {
/**
* An abbreviation that identifies the database.
*/
database_id: Aliased<'CAS' | 'CSD' | 'EMDB' | 'ICSD' | 'MDF' | 'NDB' | 'NBS' | 'PDB' | 'PDF' | 'RCSB' | 'EBI' | 'PDBE' | 'BMRB' | 'WWPDB' | 'PDB_ACC'>(str),
database_id: Aliased<'AFDB' | 'CAS' | 'CSD' | 'EMDB' | 'ICSD' | 'MA' | 'MDF' | 'MODBASE' | 'NDB' | 'NBS' | 'PDB' | 'PDF' | 'RCSB' | 'SMR' | 'EBI' | 'PDBE' | 'BMRB' | 'WWPDB' | 'PDB_ACC'>(str),
/**
* The code assigned by the database identified in
* _database_2.database_id.

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
*/
import { Column } from '../../../mol-data/db';
@@ -10,6 +11,7 @@ import { TokenColumnProvider as TokenColumn } from '../common/text/column/token'
import { TokenBuilder, Tokenizer } from '../common/text/tokenizer';
import { ReaderResult as Result } from '../result';
/** Subset of the MolFile V2000 format */
export interface MolFile {
readonly title: string,
@@ -20,7 +22,8 @@ export interface MolFile {
readonly x: Column<number>,
readonly y: Column<number>,
readonly z: Column<number>,
readonly type_symbol: Column<string>
readonly type_symbol: Column<string>,
readonly formal_charge: Column<number>
},
readonly bonds: {
readonly count: number
@@ -28,6 +31,57 @@ export interface MolFile {
readonly atomIdxB: Column<number>,
readonly order: Column<number>
}
readonly formalCharges: {
readonly atomIdx: Column<number>;
readonly charge: Column<number>;
}
}
/*
The atom lines in a .mol file have the following structure:
xxxxx.xxxxyyyyy.yyyyzzzzz.zzzz aaaddcccssshhhbbbvvvHHHrrriiimmmnnneee
---------------------------------------------------------------------
Below is a breakdown of each component and its start/end indices:
xxxxx.xxxx (X COORDINATE, 1-10)
yyyyy.yyyy (Y COORDINATE, 10-20)
zzzzz.zzzz (Z COORDINATE, 20-30)
_ (30 IS EMPTY)
aaa (ATOM SYMBOL, 31-34)
dd (MASS DIFF, 34-36)
ccc (FORMAL CHARGE, 36-39)
sss (ATOM STEREO PARITY, 39-42)
hhh (HYDROGEN COUNT+1, 42-45)
bbb (STEREO CARE BOX, 45-48)
vvv (VALENCE, 48-51)
HHH (H0 DESIGNATOR, 51-54)
rrr (UNUSED, 54-57)
iii (UNUSED, 57-60)
mmm (ATOM-ATOM MAPPING NUMBER, 60-63)
nnn (INVERSION/RETENTION FLAG, 63-66)
eee (EXACT CHANGE FLAG, 66-69)
*/
/**
* @param key - The value found at the atom block.
* @returns The actual formal charge based on the mapping.
*/
export function formalChargeMapper(key: number) {
switch (key) {
case 7: return -3;
case 6: return -2;
case 5: return -1;
case 0: return 0;
case 3: return 1;
case 2: return 2;
case 1: return 3;
case 4: return 0;
default:
console.error(`Value ${key} is outside the 0-7 range, defaulting to 0.`);
return 0;
}
}
export function handleAtoms(tokenizer: Tokenizer, count: number): MolFile['atoms'] {
@@ -35,6 +89,7 @@ export function handleAtoms(tokenizer: Tokenizer, count: number): MolFile['atoms
const y = TokenBuilder.create(tokenizer.data, count * 2);
const z = TokenBuilder.create(tokenizer.data, count * 2);
const type_symbol = TokenBuilder.create(tokenizer.data, count * 2);
const formal_charge = TokenBuilder.create(tokenizer.data, count * 2);
for (let i = 0; i < count; ++i) {
Tokenizer.markLine(tokenizer);
@@ -47,6 +102,8 @@ export function handleAtoms(tokenizer: Tokenizer, count: number): MolFile['atoms
TokenBuilder.addUnchecked(z, tokenizer.tokenStart, tokenizer.tokenEnd);
Tokenizer.trim(tokenizer, s + 31, s + 34);
TokenBuilder.addUnchecked(type_symbol, tokenizer.tokenStart, tokenizer.tokenEnd);
Tokenizer.trim(tokenizer, s + 36, s + 39);
TokenBuilder.addUnchecked(formal_charge, tokenizer.tokenStart, tokenizer.tokenEnd);
tokenizer.position = position;
}
@@ -55,7 +112,8 @@ export function handleAtoms(tokenizer: Tokenizer, count: number): MolFile['atoms
x: TokenColumn(x)(Column.Schema.float),
y: TokenColumn(y)(Column.Schema.float),
z: TokenColumn(z)(Column.Schema.float),
type_symbol: TokenColumn(type_symbol)(Column.Schema.str)
type_symbol: TokenColumn(type_symbol)(Column.Schema.str),
formal_charge: TokenColumn(formal_charge)(Column.Schema.int)
};
}
@@ -84,6 +142,76 @@ export function handleBonds(tokenizer: Tokenizer, count: number): MolFile['bonds
};
}
interface FormalChargesRawData {
atomIdx: Array<number>;
charge: Array<number>;
}
export function handleFormalCharges(tokenizer: Tokenizer, lineStart: number, formalCharges: FormalChargesRawData) {
Tokenizer.trim(tokenizer, lineStart + 6, lineStart + 9);
const numOfCharges = parseInt(Tokenizer.getTokenString(tokenizer));
for (let i = 0; i < numOfCharges; ++i) {
/*
M CHG 3 1 -1 2 0 2 -1
| | | | |
| | | | |__charge2 (etc.)
| | | |
| | | |__atomIdx2
| | |
| | |__charge1
| |
| |__atomIdx1 (cursor at position 12)
|
|___numOfCharges
*/
const offset = 9 + (i * 8);
Tokenizer.trim(tokenizer, lineStart + offset, lineStart + offset + 4);
const _atomIdx = Tokenizer.getTokenString(tokenizer);
formalCharges.atomIdx.push(+_atomIdx);
Tokenizer.trim(tokenizer, lineStart + offset + 4, lineStart + offset + 8);
const _charge = Tokenizer.getTokenString(tokenizer);
formalCharges.charge.push(+_charge);
}
/* Once the line is read, move to the next one. */
Tokenizer.eatLine(tokenizer);
}
/** Call an appropriate handler based on the property type.
* (For now it only calls the formal charge handler, additional handlers can
* be added for other properties.)
*/
export function handlePropertiesBlock(tokenizer: Tokenizer): MolFile['formalCharges'] {
const _atomIdx: Array<number> = [];
const _charge: Array<number> = [];
const _formalCharges: FormalChargesRawData = { atomIdx: _atomIdx, charge: _charge };
while (tokenizer.position < tokenizer.length) {
const { position: s } = tokenizer;
Tokenizer.trim(tokenizer, s + 3, s + 6);
const propertyType = Tokenizer.getTokenString(tokenizer);
if (propertyType === 'END') break;
Tokenizer.eatLine(tokenizer);
switch (propertyType) {
case 'CHG':
handleFormalCharges(tokenizer, s, _formalCharges);
break;
default:
break;
}
}
const formalCharges: MolFile['formalCharges'] = {
atomIdx: Column.ofIntArray(_formalCharges.atomIdx),
charge: Column.ofIntArray(_formalCharges.charge)
};
return formalCharges;
}
function parseInternal(data: string): Result<MolFile> {
const tokenizer = Tokenizer(data);
@@ -98,12 +226,15 @@ function parseInternal(data: string): Result<MolFile> {
const atoms = handleAtoms(tokenizer, atomCount);
const bonds = handleBonds(tokenizer, bondCount);
const formalCharges = handlePropertiesBlock(tokenizer);
const result: MolFile = {
title,
program,
comment,
atoms,
bonds
bonds,
formalCharges,
};
return Result.success(result);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Zepei Xu <xuzepei19950617@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -259,6 +259,36 @@ async function handleBonds(state: State): Promise<Schema.Mol2Bonds> {
return ret;
}
function handleCrysin(state: State) {
const { tokenizer } = state;
while (tokenizer.position < tokenizer.data.length) {
const l = getTokenString(tokenizer);
if (l === '@<TRIPOS>MOLECULE') {
return;
} else if (l === '@<TRIPOS>CRYSIN') {
break;
} else {
markLine(tokenizer);
}
}
if (tokenizer.position >= tokenizer.data.length) return;
markLine(tokenizer);
const values = getTokenString(tokenizer).trim().split(reWhitespace);
return {
a: parseFloat(values[0]),
b: parseFloat(values[1]),
c: parseFloat(values[2]),
alpha: parseFloat(values[3]),
beta: parseFloat(values[4]),
gamma: parseFloat(values[5]),
spaceGroup: parseInt(values[6], 10),
setting: parseInt(values[7], 10),
};
}
async function parseInternal(ctx: RuntimeContext, data: string, name: string): Promise<Result<Schema.Mol2File>> {
const tokenizer = Tokenizer(data);
@@ -269,7 +299,8 @@ async function parseInternal(ctx: RuntimeContext, data: string, name: string): P
handleMolecule(state);
const atoms = await handleAtoms(state);
const bonds = await handleBonds(state);
structures.push({ molecule: state.molecule, atoms, bonds });
const crysin = handleCrysin(state);
structures.push({ molecule: state.molecule, atoms, bonds, crysin });
skipWhitespace(tokenizer);
while (getTokenString(tokenizer) !== '@<TRIPOS>MOLECULE' && tokenizer.position < tokenizer.data.length) {
markLine(tokenizer);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -11,6 +11,7 @@ import { Column } from '../../../mol-data/db';
// @<TRIPOS>MOLECULE
// @<TRIPOS>ATOM
// @<TRIPOS>BOND
// @<TRIPOS>CRYSIN
//
// note that the format is not a fixed column format but white space separated
@@ -56,10 +57,22 @@ export interface Mol2Bonds {
status_bits: Column<string>
}
export interface Mol2Crysin {
a: number
b: number
c: number
alpha: number
beta: number
gamma: number
spaceGroup: number
setting: number
}
export interface Mol2Structure {
molecule: Readonly<Mol2Molecule>,
atoms: Readonly<Mol2Atoms>,
bonds: Readonly<Mol2Bonds>
crysin?: Readonly<Mol2Crysin>
}
export interface Mol2File {

View File

@@ -1,3 +1,10 @@
/**
* Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Jason Pattle <jpattle@exscientia.co.uk>
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
*/
import { Column } from '../../../mol-data/db';
import { MolFile } from '../mol/parser';
import { Tokenizer, TokenBuilder, Tokens } from '../common/text/tokenizer';
@@ -61,6 +68,9 @@ export function handleAtomsV3(
y: TokenColumn(y)(Column.Schema.float),
z: TokenColumn(z)(Column.Schema.float),
type_symbol: TokenColumn(type_symbol)(Column.Schema.str),
/* No support for formal charge parsing in V3000 molfiles at the moment,
so all charges default to 0.*/
formal_charge: Column.ofConst(0, atomCount, Column.Schema.int)
};
}

View File

@@ -1,12 +1,14 @@
/**
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Jason Pattle <jpattle@exscientia.co.uk>
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
*/
import { Column } from '../../../mol-data/db';
import { MolFile, handleAtoms, handleBonds } from '../mol/parser';
import { MolFile, handleAtoms, handleBonds, handlePropertiesBlock } from '../mol/parser';
import { Task } from '../../../mol-task';
import { ReaderResult as Result } from '../result';
import { Tokenizer, TokenBuilder } from '../common/text/tokenizer';
@@ -29,6 +31,7 @@ export interface SdfFile {
const delimiter = '$$$$';
function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, data: Column<string> } {
const dataHeader = TokenBuilder.create(tokenizer.data, 32);
const data = TokenBuilder.create(tokenizer.data, 32);
@@ -93,12 +96,20 @@ function handleMolFile(tokenizer: Tokenizer) {
return;
}
/* No support for formal charge parsing in V3000 molfiles at the moment,
so all charges default to 0.*/
const nullFormalCharges: MolFile['formalCharges'] = {
atomIdx: Column.ofConst(0, atomCount, Column.Schema.int),
charge: Column.ofConst(0, atomCount, Column.Schema.int)
};
const atoms = molIsV3 ? handleAtomsV3(tokenizer, atomCount) : handleAtoms(tokenizer, atomCount);
const bonds = molIsV3 ? handleBondsV3(tokenizer, bondCount) : handleBonds(tokenizer, bondCount);
const formalCharges = molIsV3 ? nullFormalCharges : handlePropertiesBlock(tokenizer);
const dataItems = handleDataItems(tokenizer);
return {
molFile: { title, program, comment, atoms, bonds },
molFile: { title, program, comment, atoms, bonds, formalCharges },
dataItems
};
}

View File

@@ -191,8 +191,14 @@ function getFieldData(field: Field<any, any>, arrayCtor: ArrayCtor<string | numb
array[offset] = '';
allPresent = false;
} else {
mask[offset] = Column.ValueKind.Present;
array[offset] = getter(key, d, offset);
const value = getter(key, d, offset);
if (typeof value === 'string' && !value) {
mask[offset] = Column.ValueKind.NotPresent;
allPresent = false;
} else {
mask[offset] = Column.ValueKind.Present;
}
array[offset] = value;
}
offset++;
}

View File

@@ -52,24 +52,30 @@ namespace SymmetryOperator {
export const RotationTranslationEpsilon = 0.005;
export type CreateInfo = { assembly?: SymmetryOperator['assembly'], ncsId?: number, hkl?: Vec3, spgrOp?: number }
export function create(name: string, matrix: Mat4, info?: CreateInfo): SymmetryOperator {
export function create(name: string, matrix: Mat4, info?: CreateInfo | SymmetryOperator): SymmetryOperator {
let { assembly, ncsId, hkl, spgrOp } = info || { };
const _hkl = hkl ? Vec3.clone(hkl) : Vec3();
spgrOp = defaults(spgrOp, -1);
ncsId = ncsId || -1;
const suffix = getSuffix(info);
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId, suffix };
const isIdentity = Mat4.isIdentity(matrix);
const suffix = getSuffix(info, isIdentity);
if (isIdentity) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId, suffix };
if (!Mat4.isRotationAndTranslation(matrix, RotationTranslationEpsilon)) {
console.warn(`Symmetry operator (${name}) should be a composition of rotation and translation.`);
}
return { name, assembly, matrix, inverse: Mat4.invert(Mat4(), matrix), isIdentity: false, hkl: _hkl, spgrOp, ncsId, suffix };
}
function getSuffix(info?: CreateInfo) {
function isSymmetryOperator(x: any): x is SymmetryOperator {
return !!x && !!x.matrix && !!x.inverse && typeof x.name === 'string';
}
function getSuffix(info?: CreateInfo | SymmetryOperator, isIdentity?: boolean) {
if (!info) return '';
if (info.assembly) {
return `_${info.assembly.operId}`;
if (isSymmetryOperator(info)) return info.suffix;
return isIdentity ? '' : `_${info.assembly.operId}`;
}
if (typeof info.spgrOp !== 'undefined' && typeof info.hkl !== 'undefined' && info.spgrOp !== -1) {

View File

@@ -7,21 +7,21 @@
import { Column, Table } from '../../../mol-data/db';
import { Interval, Segmentation } from '../../../mol-data/int';
import { UUID } from '../../../mol-util/uuid';
import { ElementIndex, ChainIndex } from '../../../mol-model/structure';
import { toDatabase } from '../../../mol-io/reader/cif/schema';
import { SymmetryOperator } from '../../../mol-math/geometry';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { ChainIndex, ElementIndex } from '../../../mol-model/structure';
import { AtomSiteOperatorMappingSchema } from '../../../mol-model/structure/export/categories/atom_site_operator_mapping';
import { Model } from '../../../mol-model/structure/model/model';
import { AtomicConformation, AtomicData, AtomicHierarchy, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../../../mol-model/structure/model/properties/atomic';
import { getAtomicIndex } from '../../../mol-model/structure/model/properties/utils/atomic-index';
import { ElementSymbol } from '../../../mol-model/structure/model/types';
import { Entities } from '../../../mol-model/structure/model/properties/common';
import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived';
import { AtomSite } from './schema';
import { getAtomicIndex } from '../../../mol-model/structure/model/properties/utils/atomic-index';
import { ElementSymbol } from '../../../mol-model/structure/model/types';
import { UUID } from '../../../mol-util/uuid';
import { ModelFormat } from '../../format';
import { SymmetryOperator } from '../../../mol-math/geometry';
import { MmcifFormat } from '../mmcif';
import { AtomSiteOperatorMappingSchema } from '../../../mol-model/structure/export/categories/atom_site_operator_mapping';
import { toDatabase } from '../../../mol-io/reader/cif/schema';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { AtomSite } from './schema';
function findHierarchyOffsets(atom_site: AtomSite) {
if (atom_site._rowCount === 0) return { residues: [], chains: [] };

View File

@@ -30,7 +30,7 @@ export interface CoarseData {
export const EmptyCoarse = { hierarchy: CoarseHierarchy.Empty, conformation: void 0 as any };
export function getCoarse(data: CoarseData, properties: Model['properties']): { hierarchy: CoarseHierarchy, conformation: CoarseConformation } {
export function getCoarse(data: CoarseData, chemicalComponentMap: Model['properties']['chemicalComponentMap']): { 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 EmptyCoarse;
@@ -38,12 +38,12 @@ export function getCoarse(data: CoarseData, properties: Model['properties']): {
const sphereData = getData(ihm_sphere_obj_site);
const sphereConformation = getSphereConformation(ihm_sphere_obj_site);
const sphereKeys = getCoarseKeys(sphereData, data.entities);
const sphereRanges = getCoarseRanges(sphereData, properties.chemicalComponentMap);
const sphereRanges = getCoarseRanges(sphereData, 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, properties.chemicalComponentMap);
const gaussianRanges = getCoarseRanges(gaussianData, chemicalComponentMap);
return {
hierarchy: {

View File

@@ -11,7 +11,7 @@ import { getEntityType, getEntitySubtype } from '../../../mol-model/structure/mo
import { ElementIndex, EntityIndex, Model } from '../../../mol-model/structure/model';
import { BasicData, BasicSchema, Entity } from './schema';
export function getEntities(data: BasicData, properties: Model['properties']): Entities {
export function getEntityData(data: BasicData): Entities {
let entityData: Entity;
if (!data.entity.id.isDefined) {
@@ -121,28 +121,32 @@ export function getEntities(data: BasicData, properties: Model['properties']): E
const subtypeColumn = Column.ofArray({ array: subtypes, schema: EntitySubtype });
//
const prdIds: string[] = new Array(entityData._rowCount);
prdIds.fill('');
if (data.pdbx_molecule && data.pdbx_molecule.prd_id.isDefined) {
const { asym_id, prd_id, _rowCount } = data.pdbx_molecule;
for (let i = 0; i < _rowCount; ++i) {
const asymId = asym_id.value(i);
const entityId = properties.structAsymMap.get(asymId)?.entity_id;
if (entityId !== undefined) {
prdIds[getEntityIndex(entityId)] = prd_id.value(i);
}
}
}
const prdIdColumn = Column.ofArray({ array: prdIds, schema: Column.Schema.str });
return {
data: entityData,
subtype: subtypeColumn,
prd_id: prdIdColumn,
getEntityIndex
};
}
export function getEntitiesWithPRD(data: BasicData, entities: Entities, structAsymMap: Model['properties']['structAsymMap']): Entities {
if (!data.pdbx_molecule || !data.pdbx_molecule.prd_id.isDefined) {
return entities;
}
const prdIds: string[] = new Array(entities.data._rowCount);
prdIds.fill('');
const { asym_id, prd_id, _rowCount } = data.pdbx_molecule;
for (let i = 0; i < _rowCount; ++i) {
const asymId = asym_id.value(i);
const entityId = structAsymMap.get(asymId)?.entity_id;
if (entityId !== undefined) {
prdIds[entities.getEntityIndex(entityId)] = prd_id.value(i);
}
}
const prdIdColumn = Column.ofArray({ array: prdIds, schema: Column.Schema.str });
return {
...entities,
prd_id: prdIdColumn
};
}

View File

@@ -18,13 +18,13 @@ 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 { getChemicalComponentMap, getMissingResidues, getSaccharideComponentMap, getStructAsymMap } from './properties';
import { getEntitiesWithPRD, getEntityData } from './entities';
import { getModelGroupName } from './util';
import { ArrayTrajectory } from '../../../mol-model/structure/trajectory';
export async function createModels(data: BasicData, format: ModelFormat, ctx: RuntimeContext) {
const properties = getProperties(data);
const properties = getCommonProperties(data, format);
const models = data.ihm_model_list._rowCount > 0
? await readIntegrative(ctx, data, properties, format)
: await readStandard(ctx, data, properties, format);
@@ -36,9 +36,18 @@ export async function createModels(data: BasicData, format: ModelFormat, ctx: Ru
return new ArrayTrajectory(models);
}
/** Standard atomic model */
function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, properties: Model['properties'], format: ModelFormat, previous?: Model): Model {
type CommonProperties = Omit<Model['properties'], 'structAsymMap'>
function getCommonProperties(data: BasicData, format: ModelFormat): CommonProperties {
return {
missingResidues: getMissingResidues(data),
chemicalComponentMap: getChemicalComponentMap(data),
saccharideComponentMap: getSaccharideComponentMap(data)
};
}
/** Standard atomic model */
function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, properties: CommonProperties, format: ModelFormat, previous?: Model): Model {
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, properties.chemicalComponentMap, format, previous);
const modelNum = atom_site.pdbx_PDB_model_num.value(0);
if (previous && atomic.sameAsPrevious) {
@@ -54,6 +63,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
const coarse = EmptyCoarse;
const sequence = getSequence(data, entities, atomic.hierarchy, coarse.hierarchy);
const atomicRanges = getAtomicRanges(atomic.hierarchy, entities, atomic.conformation, sequence);
const structAsymMap = getStructAsymMap(atomic.hierarchy);
const entry = data.entry.id.valueKind(0) === Column.ValueKind.Present
? data.entry.id.value(0) : format.name;
@@ -70,7 +80,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
sourceData: format,
modelNum,
parent: undefined,
entities,
entities: getEntitiesWithPRD(data, entities, structAsymMap),
sequence,
atomicHierarchy: atomic.hierarchy,
atomicConformation: atomic.conformation,
@@ -78,7 +88,10 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
coarseHierarchy: coarse.hierarchy,
coarseConformation: coarse.conformation,
properties,
properties: {
...properties,
structAsymMap
},
customProperties: new CustomProperties(),
_staticPropertyData: Object.create(null),
_dynamicPropertyData: Object.create(null)
@@ -86,9 +99,9 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
}
/** Integrative model with atomic/coarse parts */
function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Model['properties'], format: ModelFormat): Model {
function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: CommonProperties, format: ModelFormat): Model {
const atomic = getAtomicHierarchyAndConformation(ihm.atom_site, ihm.atom_site_sourceIndex, ihm.entities, properties.chemicalComponentMap, format);
const coarse = getCoarse(ihm, properties);
const coarse = getCoarse(ihm, properties.chemicalComponentMap);
const sequence = getSequence(data, ihm.entities, atomic.hierarchy, coarse.hierarchy);
const atomicRanges = getAtomicRanges(atomic.hierarchy, ihm.entities, atomic.conformation, sequence);
@@ -101,6 +114,8 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
if (ihm.model_name) label.push(ihm.model_name);
if (ihm.model_group_name) label.push(ihm.model_group_name);
const structAsymMap = getStructAsymMap(atomic.hierarchy, data);
return {
id: UUID.create22(),
entryId: entry,
@@ -109,7 +124,7 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
sourceData: format,
modelNum: ihm.model_id,
parent: undefined,
entities: ihm.entities,
entities: getEntitiesWithPRD(data, ihm.entities, structAsymMap),
sequence,
atomicHierarchy: atomic.hierarchy,
atomicConformation: atomic.conformation,
@@ -117,7 +132,10 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
coarseHierarchy: coarse.hierarchy,
coarseConformation: coarse.conformation,
properties,
properties: {
...properties,
structAsymMap
},
customProperties: new CustomProperties(),
_staticPropertyData: Object.create(null),
_dynamicPropertyData: Object.create(null)
@@ -132,12 +150,12 @@ function findModelEnd(num: Column<number>, startIndex: number) {
return endIndex;
}
async function readStandard(ctx: RuntimeContext, data: BasicData, properties: Model['properties'], format: ModelFormat) {
async function readStandard(ctx: RuntimeContext, data: BasicData, properties: CommonProperties, format: ModelFormat) {
const models: Model[] = [];
if (data.atom_site) {
const atomCount = data.atom_site.id.rowCount;
const entities = getEntities(data, properties);
const entities = getEntityData(data);
let modelStart = 0;
while (modelStart < atomCount) {
@@ -170,8 +188,8 @@ function splitTable<T extends Table<any>>(table: T, col: Column<number>) {
async function readIntegrative(ctx: RuntimeContext, data: BasicData, properties: Model['properties'], format: ModelFormat) {
const entities = getEntities(data, properties);
async function readIntegrative(ctx: RuntimeContext, data: BasicData, properties: CommonProperties, format: ModelFormat) {
const entities = getEntityData(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;
@@ -206,7 +224,7 @@ async function readIntegrative(ctx: RuntimeContext, data: BasicData, properties:
model_id: id,
model_name: model_name.value(i),
model_group_name: getModelGroupName(id, data),
entities: 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),

View File

@@ -5,15 +5,16 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Table } from '../../../mol-data/db';
import { Model } from '../../../mol-model/structure/model/model';
import { AtomicHierarchy } from '../../../mol-model/structure/model/properties/atomic';
import { ChemicalComponent, MissingResidue, StructAsym } 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 { getDefaultChemicalComponent, getMoleculeType, MoleculeType } from '../../../mol-model/structure/model/types';
import { SaccharideCompIdMap, SaccharideComponent, SaccharideComponentMap, SaccharidesSnfgMap, 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 getMissingResidues(data: BasicData): Model['properties']['missingResidues'] {
export 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}`;
@@ -36,7 +37,7 @@ function getMissingResidues(data: BasicData): Model['properties']['missingResidu
};
}
function getChemicalComponentMap(data: BasicData): Model['properties']['chemicalComponentMap'] {
export function getChemicalComponentMap(data: BasicData): Model['properties']['chemicalComponentMap'] {
const map = new Map<string, ChemicalComponent>();
if (data.chem_comp._rowCount > 0) {
@@ -53,7 +54,7 @@ function getChemicalComponentMap(data: BasicData): Model['properties']['chemical
return map;
}
function getSaccharideComponentMap(data: BasicData): SaccharideComponentMap {
export function getSaccharideComponentMap(data: BasicData): SaccharideComponentMap {
const map = new Map<string, SaccharideComponent>();
if (data.pdbx_chem_comp_identifier._rowCount > 0) {
@@ -108,22 +109,18 @@ const getUniqueComponentNames = memoize1((data: BasicData) => {
});
function getStructAsymMap(data: BasicData): Model['properties']['structAsymMap'] {
export function getStructAsymMap(atomic: AtomicHierarchy, data?: BasicData): Model['properties']['structAsymMap'] {
const map = new Map<string, StructAsym>();
const { label_asym_id, auth_asym_id, label_entity_id } = data.atom_site;
for (let i = 0, il = label_asym_id.rowCount; i < il; ++i) {
const { auth_asym_id, label_asym_id, label_entity_id } = atomic.chains;
for (let i = 0, _i = atomic.chains._rowCount; i < _i; i ++) {
const id = label_asym_id.value(i);
if (!map.has(id)) {
map.set(id, {
id,
auth_id: auth_asym_id.value(i),
entity_id: label_entity_id.value(i)
});
}
map.set(id, { id, auth_id: auth_asym_id.value(i), entity_id: label_entity_id.value(i) });
}
if (data.struct_asym._rowCount > 0) {
// to get asym mapping for coarse/ihm data
if (data?.struct_asym._rowCount) {
const { id, entity_id } = data.struct_asym;
for (let i = 0, il = id.rowCount; i < il; ++i) {
const _id = id.value(i);
@@ -136,14 +133,6 @@ function getStructAsymMap(data: BasicData): Model['properties']['structAsymMap']
}
}
}
return map;
}
export function getProperties(data: BasicData): Model['properties'] {
return {
missingResidues: getMissingResidues(data),
chemicalComponentMap: getChemicalComponentMap(data),
saccharideComponentMap: getSaccharideComponentMap(data),
structAsymMap: getStructAsymMap(data)
};
return map;
}

View File

@@ -1,12 +1,13 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 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>
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
*/
import { Column, Table } from '../../mol-data/db';
import { MolFile } from '../../mol-io/reader/mol/parser';
import { MolFile, formalChargeMapper } from '../../mol-io/reader/mol/parser';
import { MoleculeType } from '../../mol-model/structure/model/types';
import { RuntimeContext, Task } from '../../mol-task';
import { createModels } from './basic/parser';
@@ -18,13 +19,24 @@ import { IndexPairBonds } from './property/bonds/index-pair';
import { Trajectory } from '../../mol-model/structure';
export async function getMolModels(mol: MolFile, format: ModelFormat<any> | undefined, ctx: RuntimeContext) {
const { atoms, bonds } = mol;
const { atoms, bonds, formalCharges } = mol;
const MOL = Column.ofConst('MOL', mol.atoms.count, Column.Schema.str);
const A = Column.ofConst('A', mol.atoms.count, Column.Schema.str);
const type_symbol = Column.asArrayColumn(atoms.type_symbol);
const seq_id = Column.ofConst(1, atoms.count, Column.Schema.int);
const computedFormalCharges = new Int32Array(mol.atoms.count);
if (formalCharges.atomIdx.rowCount > 0) {
for (let i = 0; i < formalCharges.atomIdx.rowCount; i++) {
computedFormalCharges[formalCharges.atomIdx.value(i) - 1] = formalCharges.charge.value(i);
}
} else {
for (let i = 0; i < mol.atoms.count; i++) {
computedFormalCharges[i] = formalChargeMapper(atoms.formal_charge.value(i));
}
}
const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
auth_asym_id: A,
auth_atom_id: type_symbol,
@@ -45,6 +57,7 @@ export async function getMolModels(mol: MolFile, format: ModelFormat<any> | unde
type_symbol,
pdbx_PDB_model_num: Column.ofConst(1, atoms.count, Column.Schema.int),
pdbx_formal_charge: Column.ofIntArray(computedFormalCharges)
}, atoms.count);
const entityBuilder = new EntityBuilder();

View File

@@ -1,11 +1,11 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Column, Table } from '../../mol-data/db';
import { Model } from '../../mol-model/structure/model';
import { Model, Symmetry } from '../../mol-model/structure/model';
import { BondType, MoleculeType } from '../../mol-model/structure/model/types';
import { RuntimeContext, Task } from '../../mol-task';
import { createModels } from './basic/parser';
@@ -14,22 +14,34 @@ import { ComponentBuilder } from './common/component';
import { EntityBuilder } from './common/entity';
import { ModelFormat } from '../format';
import { IndexPairBonds } from './property/bonds/index-pair';
import { Mol2File } from '../../mol-io/reader/mol2/schema';
import { Mol2Crysin, Mol2File } from '../../mol-io/reader/mol2/schema';
import { AtomPartialCharge } from './property/partial-charge';
import { Trajectory, ArrayTrajectory } from '../../mol-model/structure';
import { guessElementSymbolString } from './util';
import { ModelSymmetry } from './property/symmetry';
import { Spacegroup, SpacegroupCell } from '../../mol-math/geometry';
import { Vec3 } from '../../mol-math/linear-algebra';
async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
const models: Model[] = [];
for (let i = 0, il = mol2.structures.length; i < il; ++i) {
const { atoms, bonds, molecule } = mol2.structures[i];
const { molecule, atoms, bonds, crysin } = mol2.structures[i];
const A = Column.ofConst('A', atoms.count, Column.Schema.str);
const type_symbol = new Array<string>(atoms.count);
let hasAtomType = false;
for (let i = 0; i < atoms.count; ++i) {
type_symbol[i] = guessElementSymbolString(atoms.atom_name.value(i));
if (atoms.atom_type.value(i).includes('.')) {
hasAtomType = true;
break;
}
}
for (let i = 0; i < atoms.count; ++i) {
type_symbol[i] = hasAtomType
? atoms.atom_type.value(i).split('.')[0].toUpperCase()
: guessElementSymbolString(atoms.atom_name.value(i));
}
const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
@@ -74,6 +86,7 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
if (_models.frameCount > 0) {
const indexA = Column.ofIntArray(Column.mapToArray(bonds.origin_atom_id, x => x - 1, Int32Array));
const indexB = Column.ofIntArray(Column.mapToArray(bonds.target_atom_id, x => x - 1, Int32Array));
const key = bonds.bond_id;
const order = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => {
switch (x) {
case 'ar': // aromatic
@@ -100,7 +113,7 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
return BondType.Flag.Covalent;
}
}, Int8Array));
const pairBonds = IndexPairBonds.fromData({ pairs: { indexA, indexB, order, flag }, count: atoms.count });
const pairBonds = IndexPairBonds.fromData({ pairs: { key, indexA, indexB, order, flag }, count: atoms.count });
const first = _models.representative;
IndexPairBonds.Provider.set(first, pairBonds);
@@ -110,6 +123,11 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
type: molecule.charge_type
});
if (crysin) {
const symmetry = getSymmetry(crysin);
if (symmetry) ModelSymmetry.Provider.set(first, symmetry);
}
models.push(first);
}
}
@@ -117,6 +135,24 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
return new ArrayTrajectory(models);
}
function getSymmetry(crysin: Mol2Crysin): Symmetry | undefined {
// TODO handle `crysin.setting`
if (crysin.setting !== 1) return;
const spaceCell = SpacegroupCell.create(
crysin.spaceGroup,
Vec3.create(crysin.a, crysin.b, crysin.c),
Vec3.scale(Vec3(), Vec3.create(crysin.alpha, crysin.beta, crysin.gamma), Math.PI / 180)
);
return {
spacegroup: Spacegroup.create(spaceCell),
assemblies: [],
isNonStandardCrystalFrame: false,
ncsOperators: []
};
}
//
export { Mol2Format };

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -10,9 +10,9 @@ import { Column } from '../../../../mol-data/db';
import { FormatPropertyProvider } from '../../common/property';
import { BondType } from '../../../../mol-model/structure/model/types';
import { ElementIndex } from '../../../../mol-model/structure';
import { DefaultBondMaxRadius } from '../../../../mol-model/structure/structure/unit/bonds/common';
export type IndexPairsProps = {
readonly key: ArrayLike<number>
readonly order: ArrayLike<number>
readonly distance: ArrayLike<number>
readonly flag: ArrayLike<BondType.Flag>
@@ -22,17 +22,19 @@ export type IndexPairBonds = { bonds: IndexPairs, maxDistance: number }
function getGraph(indexA: ArrayLike<ElementIndex>, indexB: ArrayLike<ElementIndex>, props: Partial<IndexPairsProps>, count: number): IndexPairs {
const builder = new IntAdjacencyGraph.EdgeBuilder(count, indexA, indexB);
const key = new Int32Array(builder.slotCount);
const order = new Int8Array(builder.slotCount);
const distance = new Array(builder.slotCount);
const flag = new Array(builder.slotCount);
for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
builder.addNextEdge();
builder.assignProperty(key, props.key ? props.key[i] : -1);
builder.assignProperty(order, props.order ? props.order[i] : 1);
builder.assignProperty(distance, props.distance ? props.distance[i] : -1);
builder.assignProperty(flag, props.flag ? props.flag[i] : BondType.Flag.Covalent);
}
return builder.createGraph({ order, distance, flag });
return builder.createGraph({ key, order, distance, flag });
}
export namespace IndexPairBonds {
@@ -45,15 +47,33 @@ export namespace IndexPairBonds {
export type Data = {
pairs: {
indexA: Column<number>,
indexB: Column<number>
indexB: Column<number>,
key?: Column<number>,
order?: Column<number>,
/**
* Useful for bonds in periodic cells. That is, only bonds within the given
* distance are added. This allows for bond between periodic image but
* avoids unwanted bonds with wrong distances. If negative, test using the
* `maxDistance` option from `Props`.
*/
distance?: Column<number>,
flag?: Column<BondType.Flag>,
},
count: number
}
export const DefaultProps = { maxDistance: DefaultBondMaxRadius };
export const DefaultProps = {
/**
* If negative, test using element-based threshold, otherwise distance in Angstrom.
*
* This option exists to handle bonds in periodic cells. For systems that are
* made from beads (as opposed to atomic elements), set to a specific distance.
*
* Note that `Data` has a `distance` field which allows specifying a distance
* for each bond individually which takes precedence over this option.
*/
maxDistance: -1
};
export type Props = typeof DefaultProps
export function fromData(data: Data, props: Partial<Props> = {}): IndexPairBonds {
@@ -61,11 +81,12 @@ export namespace IndexPairBonds {
const { pairs, count } = data;
const indexA = pairs.indexA.toArray() as ArrayLike<ElementIndex>;
const indexB = pairs.indexB.toArray() as ArrayLike<ElementIndex>;
const key = pairs.key && pairs.key.toArray();
const order = pairs.order && pairs.order.toArray();
const distance = pairs.distance && pairs.distance.toArray();
const flag = pairs.flag && pairs.flag.toArray();
return {
bonds: getGraph(indexA, indexB, { order, distance, flag }, count),
bonds: getGraph(indexA, indexB, { key, order, distance, flag }, count),
maxDistance: p.maxDistance
};
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -98,29 +98,20 @@ class InteractionsInterContacts extends InterUnitGraph<number, Features.FeatureI
constructor(map: Map<number, InterUnitGraph.UnitPairEdges<number, Features.FeatureIndex, InteractionsInterContacts.Props>[]>, unitsFeatures: IntMap<Features>) {
super(map);
let count = 0;
const elementKeyIndex = new Map<string, number[]>();
const add = (index: StructureElement.UnitIndex, unitId: number) => {
const vertexKey = this.getElementKey(index, unitId);
const e = elementKeyIndex.get(vertexKey);
if (e === undefined) elementKeyIndex.set(vertexKey, [count]);
else e.push(count);
};
this.map.forEach(pairEdgesArray => {
pairEdgesArray.forEach(({ unitA, connectedIndices }) => {
connectedIndices.forEach(indexA => {
const { offsets: offsetsA, members: membersA } = unitsFeatures.get(unitA);
for (let j = offsetsA[indexA], jl = offsetsA[indexA + 1]; j < jl; ++j) {
add(membersA[j], unitA);
}
count += 1;
});
});
});
this.elementKeyIndex = elementKeyIndex;
this.elementKeyIndex = new Map<string, number[]>();
for (let i = 0, il = this.edges.length; i < il; ++i) {
const { unitA, indexA } = this.edges[i];
const { offsets, members } = unitsFeatures.get(unitA);
for (let j = offsets[indexA], jl = offsets[indexA + 1]; j < jl; ++j) {
const vertexKey = this.getElementKey(members[j], unitA);
const e = this.elementKeyIndex.get(vertexKey);
if (e === undefined) {
this.elementKeyIndex.set(vertexKey, [i]);
} else {
e.push(i);
}
}
}
}
}
namespace InteractionsInterContacts {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -74,10 +74,11 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
if (!childUnitA) return true;
const unitA = structure.unitMap.get(b.unitA);
const fA = unitsFeatures.get(b.unitA);
// TODO: check all members
const eA = unitA.elements[fA.members[fA.offsets[b.indexA]]];
if (!SortedArray.has(childUnitA.elements, eA)) return true;
const { offsets, members } = unitsFeatures.get(b.unitA);
for (let i = offsets[b.indexA], il = offsets[b.indexA + 1]; i < il; ++i) {
const eA = unitA.elements[members[i]];
if (!SortedArray.has(childUnitA.elements, eA)) return true;
}
}
return false;
@@ -144,6 +145,9 @@ function getInteractionLoci(pickingId: PickingId, structure: Structure, id: numb
return EmptyLoci;
}
const __unitMap = new Map<number, OrderedSet<StructureElement.UnitIndex>>();
const __contactIndicesSet = new Set<number>();
function eachInteraction(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean, isMarking: boolean) {
let changed = false;
if (Interactions.isLoci(loci)) {
@@ -162,21 +166,48 @@ function eachInteraction(loci: Loci, structure: Structure, apply: (interval: Int
if (!Structure.areEquivalent(loci.structure, structure)) return false;
if (isMarking && loci.elements.length === 1) return false; // only a single unit
const contacts = InteractionsProvider.get(structure).value?.contacts;
if (!contacts) return false;
const interactions = InteractionsProvider.get(structure).value;
if (!interactions) return false;
const { contacts, unitsFeatures } = interactions;
for (const e of loci.elements) __unitMap.set(e.unit.id, e.indices);
// TODO when isMarking, all elements of contact features need to be in the loci
for (const e of loci.elements) {
const { unit } = e;
if (!Unit.isAtomic(unit)) continue;
if (isMarking && OrderedSet.size(e.indices) === 1) continue;
OrderedSet.forEach(e.indices, v => {
for (const idx of contacts.getContactIndicesForElement(v, unit)) {
if (apply(Interval.ofSingleton(idx))) changed = true;
__contactIndicesSet.add(idx);
}
});
}
__contactIndicesSet.forEach(i => {
if (isMarking) {
const { indexA, unitA, indexB, unitB } = contacts.edges[i];
const indicesA = __unitMap.get(unitA);
const indicesB = __unitMap.get(unitB);
if (!indicesA || !indicesB) return;
const { offsets: offsetsA, members: membersA } = unitsFeatures.get(unitA);
for (let j = offsetsA[indexA], jl = offsetsA[indexA + 1]; j < jl; ++j) {
if (!OrderedSet.has(indicesA, membersA[j])) return;
}
const { offsets: offsetsB, members: membersB } = unitsFeatures.get(unitB);
for (let j = offsetsB[indexB], jl = offsetsB[indexB + 1]; j < jl; ++j) {
if (!OrderedSet.has(indicesB, membersB[j])) return;
}
}
if (apply(Interval.ofSingleton(i))) changed = true;
});
__unitMap.clear();
__contactIndicesSet.clear();
}
return changed;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -56,11 +56,19 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
const sizeB = theme.size.size(location);
return Math.min(sizeA, sizeB) * sizeFactor;
},
ignore: (edgeIndex: number) => (
flag[edgeIndex] === InteractionFlag.Filtered ||
// TODO: check all members
(!!childUnit && !SortedArray.has(childUnit.elements, unit.elements[members[offsets[a[edgeIndex]]]]))
)
ignore: (edgeIndex: number) => {
if (flag[edgeIndex] === InteractionFlag.Filtered) return true;
if (childUnit) {
const f = a[edgeIndex];
for (let i = offsets[f], jl = offsets[f + 1]; i < jl; ++i) {
const e = unit.elements[members[offsets[i]]];
if (!SortedArray.has(childUnit.elements, e)) return true;
}
}
return false;
}
};
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
@@ -123,6 +131,8 @@ function getInteractionLoci(pickingId: PickingId, structureGroup: StructureGroup
return EmptyLoci;
}
const __contactIndicesSet = new Set<number>();
function eachInteraction(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean, isMarking: boolean) {
let changed = false;
if (Interactions.isLoci(loci)) {
@@ -156,21 +166,37 @@ function eachInteraction(loci: Loci, structureGroup: StructureGroup, apply: (int
const { offset } = contacts;
const { offsets: fOffsets, indices: fIndices } = features.elementsIndex;
const { members, offsets } = features;
// TODO: when isMarking, all elements of contact features need to be in the loci
for (const e of loci.elements) {
const unitIdx = group.unitIndexMap.get(e.unit.id);
if (unitIdx !== undefined) continue;
if (isMarking && OrderedSet.size(e.indices) === 1) continue;
if (unitIdx === undefined) continue;
OrderedSet.forEach(e.indices, v => {
for (let i = fOffsets[v], il = fOffsets[v + 1]; i < il; ++i) {
const fI = fIndices[i];
for (let j = offset[fI], jl = offset[fI + 1]; j < jl; ++j) {
if (apply(Interval.ofSingleton(unitIdx * groupCount + j))) changed = true;
__contactIndicesSet.add(j);
}
}
});
__contactIndicesSet.forEach(i => {
if (isMarking) {
const fA = contacts.a[i];
for (let j = offsets[fA], jl = offsets[fA + 1]; j < jl; ++j) {
if (!OrderedSet.has(e.indices, members[j])) return;
}
const fB = contacts.b[i];
for (let j = offsets[fB], jl = offsets[fB + 1]; j < jl; ++j) {
if (!OrderedSet.has(e.indices, members[j])) return;
}
}
if (apply(Interval.ofSingleton(unitIdx * groupCount + i))) changed = true;
});
__contactIndicesSet.clear();
}
}
return changed;

View File

@@ -29,7 +29,7 @@ namespace BestDatabaseSequenceMapping {
type: 'static',
defaultParams: {},
getParams: () => ({}),
isApplicable: (data: Model) => MmcifFormat.is(data.sourceData) && data.sourceData.data.frame.categories?.atom_site?.fieldNames.indexOf('db_name') >= 0,
isApplicable: (data: Model) => MmcifFormat.is(data.sourceData) && data.sourceData.data.frame.categories?.atom_site?.fieldNames.indexOf('pdbx_sifts_xref_db_name') >= 0,
obtain: async (ctx, data) => {
return { value: fromCif(data) };
}
@@ -40,7 +40,7 @@ namespace BestDatabaseSequenceMapping {
const data = Provider.get(model).value;
if (!data) return '';
const eI = loc.unit.elements[loc.element];
const rI = model.atomicHierarchy.residueAtomSegments.offsets[eI];
const rI = model.atomicHierarchy.residueAtomSegments.index[eI];
return data.accession[rI];
}
@@ -49,7 +49,7 @@ namespace BestDatabaseSequenceMapping {
const data = Provider.get(model).value;
if (!data) return;
const eI = loc.unit.elements[loc.element];
const rI = model.atomicHierarchy.residueAtomSegments.offsets[eI];
const rI = model.atomicHierarchy.residueAtomSegments.index[eI];
const dbName = data.dbName[rI];
if (!dbName) return;
return `${dbName} ${data.accession[rI]} ${data.num[rI]} ${data.residue[rI]}`;
@@ -59,10 +59,10 @@ namespace BestDatabaseSequenceMapping {
if (!MmcifFormat.is(model.sourceData)) return;
const { atom_site } = model.sourceData.data.frame.categories;
const db_name = atom_site.getField('db_name');
const db_acc = atom_site.getField('db_acc');
const db_num = atom_site.getField('db_num');
const db_res = atom_site.getField('db_res');
const db_name = atom_site.getField('pdbx_sifts_xref_db_name');
const db_acc = atom_site.getField('pdbx_sifts_xref_db_acc');
const db_num = atom_site.getField('pdbx_sifts_xref_db_num');
const db_res = atom_site.getField('pdbx_sifts_xref_db_res');
if (!db_name || !db_acc || !db_num || !db_res) return;
@@ -77,17 +77,17 @@ namespace BestDatabaseSequenceMapping {
const row = atomSourceIndex.value(residueOffsets[i]);
if (db_name.valueKind(row) !== Column.ValueKind.Present) {
dbName[row] = '';
accession[row] = '';
num[row] = 0;
residue[row] = '';
dbName[i] = '';
accession[i] = '';
num[i] = 0;
residue[i] = '';
continue;
}
dbName[row] = db_name.str(row);
accession[row] = db_acc.str(row);
num[row] = db_num.int(row);
residue[row] = db_res.str(row);
dbName[i] = db_name.str(row);
accession[i] = db_acc.str(row);
num[i] = db_num.int(row);
residue[i] = db_res.str(row);
}
return { dbName, accession, num, residue };

View File

@@ -52,6 +52,10 @@ function isWithoutSymmetry(structure: Structure) {
return structure.units.every(u => u.conformation.operator.isIdentity);
}
function isWithoutOperator(structure: Structure) {
return isWithoutSymmetry(structure) && structure.units.every(u => !u.conformation.operator.assembly && !u.conformation.operator.suffix);
}
const Categories = [
// Basics
copy_mmCif_category('entry'),
@@ -63,9 +67,9 @@ const Categories = [
copy_mmCif_category('symmetry', isWithoutSymmetry),
// Assemblies
copy_mmCif_category('pdbx_struct_assembly', isWithoutSymmetry),
copy_mmCif_category('pdbx_struct_assembly_gen', isWithoutSymmetry),
copy_mmCif_category('pdbx_struct_oper_list', isWithoutSymmetry),
copy_mmCif_category('pdbx_struct_assembly', isWithoutOperator),
copy_mmCif_category('pdbx_struct_assembly_gen', isWithoutOperator),
copy_mmCif_category('pdbx_struct_oper_list', isWithoutOperator),
// Secondary structure
_struct_conf,
@@ -250,10 +254,10 @@ function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExp
}
function to_mmCIF(name: string, structure: Structure, asBinary = false) {
function to_mmCIF(name: string, structure: Structure, asBinary = false, params?: encode_mmCIF_categories_Params) {
const enc = CifWriter.createEncoder({ binary: asBinary });
enc.startDataBlock(name);
encode_mmCIF_categories(enc, structure);
encode_mmCIF_categories(enc, structure, params);
return enc.getData();
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 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>
@@ -63,6 +63,28 @@ export interface CoarseHierarchy {
gaussians: CoarseElements
}
const EmptyCoarseElements: CoarseElements = {
chainKey: [],
entityKey: [],
findSequenceKey: () => -1 as ElementIndex,
findChainKey: () => -1 as ChainIndex,
getEntityFromChain: () => -1 as EntityIndex,
count: 0,
entity_id: Column.Undefined(0, Column.Schema.str),
asym_id: Column.Undefined(0, Column.Schema.str),
seq_id_begin: Column.Undefined(0, Column.Schema.int),
seq_id_end: Column.Undefined(0, Column.Schema.int),
chainElementSegments: Segmentation.create([]),
polymerRanges: SortedRanges.ofSortedRanges([]),
gapRanges: SortedRanges.ofSortedRanges([]),
};
export namespace CoarseHierarchy {
export const Empty: CoarseHierarchy = { isDefined: false } as any;
export const Empty: CoarseHierarchy = {
isDefined: false,
spheres: EmptyCoarseElements,
gaussians: EmptyCoarseElements
};
}

View File

@@ -22,7 +22,7 @@ export const EntitySubtype = Column.Schema.Aliased<EntitySubtype>(Column.Schema.
export interface Entities {
data: mmCIF_Database['entity'],
subtype: Column<EntitySubtype>,
prd_id: Column<string>,
prd_id?: Column<string>,
getEntityIndex(id: string): EntityIndex
}

View File

@@ -72,6 +72,7 @@ export namespace Stats {
}
} else if (size === 1) {
if (Unit.Traits.is(unit.traits, Unit.Trait.MultiChain)) {
// handled in `handleUnitChainsSimple`
return;
} else {
stats.elementCount += 1;
@@ -193,6 +194,12 @@ export namespace Stats {
if (stats.chainCount === 1) {
Location.set(stats.firstChainLoc, structure, unit, offsets[cI]);
}
} else if (size === 1) {
// need to handle here, skipped in `handleElement`
stats.elementCount += 1;
if (stats.elementCount === 1) {
Location.set(stats.firstElementLoc, structure, unit, eI);
}
}
}
}

View File

@@ -126,6 +126,7 @@ const coarse = {
z: atom.z,
asym_id: p(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.asym_id.value(l.element)),
entity_id: p(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.entity_id.value(l.element)),
seq_id_begin: p(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_begin.value(l.element)),
seq_id_end: p(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_end.value(l.element)),
@@ -162,7 +163,7 @@ const entity = {
pdbx_ec: p(l => l.unit.model.entities.data.pdbx_ec.value(eK(l))),
subtype: p(l => l.unit.model.entities.subtype.value(eK(l))),
prd_id: p(l => l.unit.model.entities.prd_id.value(eK(l))),
prd_id: p(l => l.unit.model.entities.prd_id?.value(eK(l)) ?? ''),
};
const _emptyList: any[] = [];

View File

@@ -184,16 +184,13 @@ function getOperatorsForRange(symmetry: Symmetry, ijkMin: Vec3, ijkMax: Vec3, mo
operators[0] = Spacegroup.getSymmetryOperator(spacegroup, 0, 0, 0, 0);
}
const { toFractional } = spacegroup.cell;
const ref = Vec3.transformMat4(Vec3(), modelCenter, toFractional);
for (let op = 0; op < spacegroup.operators.length; op++) {
for (let i = ijkMin[0]; i <= ijkMax[0]; i++) {
for (let j = ijkMin[1]; j <= ijkMax[1]; j++) {
for (let k = ijkMin[2]; k <= ijkMax[2]; k++) {
// check if we have added identity as the 1st operator.
if (!ncsCount && op === 0 && i === 0 && j === 0 && k === 0) continue;
operators.push(...getOperatorsForIndex(symmetry, op, i, j, k, ref));
operators.push(...getOperatorsForIndex(symmetry, op, i, j, k, modelCenter));
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 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>
@@ -110,6 +110,15 @@ export function getElementThreshold(i: number) {
return r;
}
export function getPairingThreshold(elementIndexA: number, elementIndexB: number, thresholdA: number, thresholdB: number) {
const thresholdAB = getElementPairThreshold(elementIndexA, elementIndexB);
return thresholdAB > 0
? thresholdAB
: elementIndexB < 0
? thresholdA
: (thresholdA + thresholdB) / 1.95; // not sure if avg or min but max is too big
}
const H_ID = __ElementIndex['H']!;
export function isHydrogen(i: number) {
return i === H_ID;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 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,7 +8,7 @@
import { BondType, MoleculeType } from '../../../model/types';
import { Structure } from '../../structure';
import { Unit } from '../../unit';
import { getElementIdx, getElementPairThreshold, getElementThreshold, isHydrogen, BondComputationProps, MetalsSet, DefaultBondComputationProps } from './common';
import { getElementIdx, getElementThreshold, isHydrogen, BondComputationProps, MetalsSet, DefaultBondComputationProps, getPairingThreshold } from './common';
import { InterUnitBonds, InterUnitEdgeProps } from './data';
import { SortedArray } from '../../../../../mol-data/int';
import { Vec3, Mat4 } from '../../../../../mol-math/linear-algebra';
@@ -82,11 +82,31 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
const _bI = SortedArray.indexOf(unitB.elements, bI) as StructureElement.UnitIndex;
if (_bI < 0) continue;
if (type_symbolA.value(aI) === 'H' && type_symbolB.value(bI) === 'H') continue;
const aeI = getElementIdx(type_symbolA.value(aI));
const beI = getElementIdx(type_symbolA.value(bI));
const d = distance[i];
const dist = getDistance(unitA, aI, unitB, bI);
if ((d !== -1 && equalEps(dist, d, 0.5)) || dist < maxDistance) {
let add = false;
if (d >= 0) {
add = equalEps(dist, d, 0.3);
} else if (maxDistance >= 0) {
add = dist < maxDistance;
} else {
const pairingThreshold = getPairingThreshold(
aeI, beI, getElementThreshold(aeI), getElementThreshold(beI)
);
add = dist < pairingThreshold;
if (isHydrogen(aeI) && isHydrogen(beI)) {
// TODO handle molecular hydrogen
add = false;
}
}
if (add) {
builder.add(_aI, _bI, { order: order[i], flag: flag[i] });
}
}
@@ -155,13 +175,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
const dist = Math.sqrt(squaredDistances[ni]);
if (dist === 0) continue;
const thresholdAB = getElementPairThreshold(aeI, beI);
const pairingThreshold = thresholdAB > 0
? thresholdAB
: beI < 0
? thresholdA
: (thresholdA + getElementThreshold(beI)) / 1.95; // not sure if avg or min but max is too big
const pairingThreshold = getPairingThreshold(aeI, beI, thresholdA, getElementThreshold(beI));
if (dist <= pairingThreshold) {
const atomIdB = label_atom_idB.value(bI);
const compIdB = label_comp_idB.value(residueIndexB[bI]);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 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,7 +9,7 @@ import { BondType } from '../../../model/types';
import { IntraUnitBonds } from './data';
import { Unit } from '../../unit';
import { IntAdjacencyGraph } from '../../../../../mol-math/graph';
import { BondComputationProps, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, getElementPairThreshold, DefaultBondComputationProps } from './common';
import { BondComputationProps, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, DefaultBondComputationProps, getPairingThreshold } from './common';
import { SortedArray } from '../../../../../mol-data/int';
import { getIntraBondOrderFromTable } from '../../../model/properties/atomic/bonds';
import { StructureElement } from '../../element';
@@ -62,7 +62,8 @@ function findIndexPairBonds(unit: Unit.Atomic) {
for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) {
const aI = atoms[_aI];
const isHa = type_symbol.value(aI) === 'H';
const aeI = getElementIdx(type_symbol.value(aI));
const isHa = isHydrogen(aeI);
const srcA = sourceIndex.value(aI);
@@ -72,11 +73,30 @@ function findIndexPairBonds(unit: Unit.Atomic) {
const _bI = SortedArray.indexOf(unit.elements, bI) as StructureElement.UnitIndex;
if (_bI < 0) continue;
if (isHa && type_symbol.value(bI) === 'H') continue;
const beI = getElementIdx(type_symbol.value(bI));
const d = distance[i];
const dist = getDistance(unit, aI, bI);
if ((d !== -1 && equalEps(dist, d, 0.5)) || dist < maxDistance) {
let add = false;
if (d >= 0) {
add = equalEps(dist, d, 0.3);
} else if (maxDistance >= 0) {
add = dist < maxDistance;
} else {
const pairingThreshold = getPairingThreshold(
aeI, beI, getElementThreshold(aeI), getElementThreshold(beI)
);
add = dist < pairingThreshold;
if (isHa && isHydrogen(beI)) {
// TODO handle molecular hydrogen
add = false;
}
}
if (add) {
atomA[atomA.length] = _aI;
atomB[atomB.length] = _bI;
orders[orders.length] = order[i];
@@ -214,13 +234,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
const dist = Math.sqrt(squaredDistances[ni]);
if (dist === 0) continue;
const thresholdAB = getElementPairThreshold(aeI, beI);
const pairingThreshold = thresholdAB > 0
? thresholdAB
: beI < 0
? thresholdA
: (thresholdA + getElementThreshold(beI)) / 1.95; // not sure if avg or min but max is too big
const pairingThreshold = getPairingThreshold(aeI, beI, thresholdA, getElementThreshold(beI));
if (dist <= pairingThreshold) {
atomA[atomA.length] = _aI;
atomB[atomB.length] = _bI;

View File

@@ -5,6 +5,7 @@
*/
import { Segmentation } from '../../../../mol-data/int';
import { Mat4 } from '../../../../mol-math/linear-algebra';
import { MinimizeRmsd } from '../../../../mol-math/linear-algebra/3d/minimize-rmsd';
import { BestDatabaseSequenceMapping } from '../../../../mol-model-props/sequence/best-database-mapping';
import { ElementIndex } from '../../model/indexing';
@@ -33,6 +34,7 @@ export function alignAndSuperposeWithBestDatabaseMapping(structures: Structure[]
for (const p of pairs) {
const [a, b] = getPositionTables(index, p.i, p.j, p.count);
const transform = MinimizeRmsd.compute({ a, b });
console.log(Mat4.makeTable(transform.bTransform), transform.rmsd);
ret.push({ transform, pivot: p.i, other: p.j });
}
@@ -51,6 +53,8 @@ function getPositionTables(index: IndexEntry[], pivot: number, other: number, N:
const l = Math.min(a[2] - a[1], b[2] - b[1]);
// TODO: allow to use just backbone atoms?
// TODO: check if residue types match?
for (let i = 0; i < l; i++) {
let eI = (a[1] + i) as ElementIndex;
xs.x[o] = a[0].conformation.x(eI);
@@ -122,7 +126,6 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
if (unit.kind !== Unit.Kind.Atomic) continue;
const { elements, model } = unit;
const { offsets: residueOffset } = model.atomicHierarchy.residueAtomSegments;
const map = BestDatabaseSequenceMapping.Provider.get(model).value;
if (!map) return;
@@ -137,23 +140,27 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
residuesIt.setSegment(chainSegment);
while (residuesIt.hasNext) {
const residueSegment = residuesIt.move();
const eI = elements[residueSegment.start];
const rI = residueOffset[eI];
const rI = residueSegment.index;
if (!dbName[rI]) continue;
const start = elements[residueSegment.start];
const end = elements[residueSegment.end - 1] + 1 as ElementIndex;
const key = `${dbName[rI]}-${accession[rI]}-${num[rI]}`;
if (!index.has(key)) {
index.set(key, { key, pivots: { [sI]: [unit, eI, elements[residueSegment.end]] } });
index.set(key, { key, pivots: { [sI]: [unit, start, end] } });
} else {
const entry = index.get(key)!;
if (!entry.pivots[sI]) {
entry.pivots[sI] = [unit, eI, elements[residueSegment.end]];
entry.pivots[sI] = [unit, start, end];
}
}
}
}
}
console.log(index);
}

View File

@@ -15,6 +15,7 @@ import { ModelFormat } from '../../mol-model-formats/format';
import { CustomProperties } from '../custom-property';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { toPrecision } from '../../mol-util/number';
import { DscifFormat } from '../../mol-model-formats/volume/density-server';
export interface Volume {
readonly label?: string
@@ -84,6 +85,23 @@ export namespace Volume {
}
}
// Converts iso value to relative if using downsample VolumeServer data
export function adjustedIsoValue(volume: Volume, value: number, kind: 'absolute' | 'relative') {
if (kind === 'relative') return IsoValue.relative(value);
const absolute = IsoValue.absolute(value);
if (DscifFormat.is(volume.sourceData)) {
const stats = {
min: volume.sourceData.data.volume_data_3d_info.min_source.value(0),
max: volume.sourceData.data.volume_data_3d_info.max_source.value(0),
mean: volume.sourceData.data.volume_data_3d_info.mean_source.value(0),
sigma: volume.sourceData.data.volume_data_3d_info.sigma_source.value(0),
};
return Volume.IsoValue.toRelative(absolute, stats);
}
return absolute;
}
const defaultStats: Grid['stats'] = { min: -1, max: 1, mean: 0, sigma: 0.1 };
export function createIsoValueParam(defaultValue: Volume.IsoValue, stats?: Grid['stats']) {
const sts = stats || defaultStats;

View File

@@ -7,8 +7,10 @@
import { PluginContext } from '../../mol-plugin/context';
import { StateAction } from '../../mol-state';
import { Task } from '../../mol-task';
import { Asset } from '../../mol-util/assets';
import { getFileInfo } from '../../mol-util/file-info';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { unzip } from '../../mol-util/zip/zip';
import { PluginStateObject } from '../objects';
export const OpenFiles = StateAction.build({
@@ -33,24 +35,37 @@ export const OpenFiles = StateAction.build({
plugin.log.error('No file(s) selected');
return;
}
const processFile = async (file: Asset.File) => {
const info = getFileInfo(file.file!);
const isBinary = plugin.dataFormats.binaryExtensions.has(info.ext);
const { data } = await plugin.builders.data.readFile({ file, isBinary });
const provider = params.format.name === 'auto'
? plugin.dataFormats.auto(info, data.cell?.obj!)
: plugin.dataFormats.get(params.format.params);
if (!provider) {
plugin.log.warn(`OpenFiles: could not find data provider for '${info.name}.${info.ext}'`);
return;
}
// need to await so that the enclosing Task finishes after the update is done.
const parsed = await provider.parse(plugin, data);
if (params.visuals) {
await provider.visuals?.(plugin, parsed);
}
};
for (const file of params.files) {
try {
const info = getFileInfo(file.file!);
const isBinary = plugin.dataFormats.binaryExtensions.has(info.ext);
const { data } = await plugin.builders.data.readFile({ file, isBinary });
const provider = params.format.name === 'auto'
? plugin.dataFormats.auto(info, data.cell?.obj!)
: plugin.dataFormats.get(params.format.params);
if (!provider) {
plugin.log.warn(`OpenFiles: could not find data provider for '${info.name}.${info.ext}'`);
continue;
}
// need to await so that the enclosing Task finishes after the update is done.
const parsed = await provider.parse(plugin, data);
if (params.visuals) {
await provider.visuals?.(plugin, parsed);
if (file.file && file.name.toLowerCase().endsWith('.zip')) {
const zippedFiles = await unzip(taskCtx, await file.file.arrayBuffer());
for (const [fn, filedata] of Object.entries(zippedFiles)) {
const asset = Asset.File(new File([filedata as Uint8Array], fn));
await processFile(asset);
}
} else {
await processFile(file);
}
} catch (e) {
console.error(e);

View File

@@ -19,6 +19,7 @@ import { CustomModelProperties, CustomStructureProperties, TrajectoryFromModelAn
import { Asset } from '../../mol-util/assets';
import { PluginConfig } from '../../mol-plugin/config';
import { getFileInfo } from '../../mol-util/file-info';
import { assertUnreachable } from '../../mol-util/type-helpers';
const DownloadModelRepresentationOptions = (plugin: PluginContext) => {
const representationDefault = plugin.config.get(PluginConfig.Structure.DefaultRepresentationPreset) || PresetStructureRepresentations.auto.id;
@@ -39,6 +40,7 @@ export const PdbDownloadProvider = {
'pdbe': PD.Group({
variant: PD.Select('updated-bcif', [['updated-bcif', 'Updated (bcif)'], ['updated', 'Updated'], ['archival', 'Archival']] as ['updated' | 'updtaed-bcif' | 'archival', string][]),
}, { label: 'PDBe', isFlat: true }),
'pdbj': PD.EmptyGroup({ label: 'PDBj' }),
};
export type PdbDownloadProvider = keyof typeof PdbDownloadProvider;
@@ -104,15 +106,14 @@ const DownloadStructure = StateAction.build({
format = src.params.format;
break;
case 'pdb':
downloadParams = await (src.params.provider.server.name === 'pdbe'
? src.params.provider.server.params.variant === 'updated'
? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}_updated.cif`, id => `PDBe: ${id} (updated cif)`, false)
: src.params.provider.server.params.variant === 'updated-bcif'
? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/entry-files/download/${id.toLowerCase()}.bcif`, id => `PDBe: ${id} (updated cif)`, true)
: getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}.cif`, id => `PDBe: ${id} (cif)`, false)
: src.params.provider.server.params.encoding === 'cif'
? getDownloadParams(src.params.provider.id, id => `https://files.rcsb.org/download/${id.toUpperCase()}.cif`, id => `RCSB: ${id} (cif)`, false)
: getDownloadParams(src.params.provider.id, id => `https://models.rcsb.org/${id.toUpperCase()}.bcif`, id => `RCSB: ${id} (bcif)`, true)
downloadParams = await (
src.params.provider.server.name === 'pdbe'
? getPdbeDownloadParams(src)
: src.params.provider.server.name === 'pdbj'
? getPdbjDownloadParams(src)
: src.params.provider.server.name === 'rcsb'
? getRcsbDownloadParams(src)
: assertUnreachable(src as never)
);
asTrajectory = !!src.params.options.asTrajectory;
break;
@@ -205,6 +206,27 @@ async function getDownloadParams(src: string, url: (id: string) => string | Prom
return ret;
}
async function getPdbeDownloadParams(src: ReturnType<DownloadStructure['createDefaultParams']>['source']) {
if (src.name !== 'pdb' || src.params.provider.server.name !== 'pdbe') throw new Error('expected pdbe');
return src.params.provider.server.params.variant === 'updated'
? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}_updated.cif`, id => `PDBe: ${id} (updated cif)`, false)
: src.params.provider.server.params.variant === 'updated-bcif'
? getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/entry-files/download/${id.toLowerCase()}.bcif`, id => `PDBe: ${id} (updated cif)`, true)
: getDownloadParams(src.params.provider.id, id => `https://www.ebi.ac.uk/pdbe/static/entry/${id.toLowerCase()}.cif`, id => `PDBe: ${id} (cif)`, false);
}
async function getPdbjDownloadParams(src: ReturnType<DownloadStructure['createDefaultParams']>['source']) {
if (src.name !== 'pdb' || src.params.provider.server.name !== 'pdbj') throw new Error('expected pdbj');
return getDownloadParams(src.params.provider.id, id => `https://data.pdbjbk1.pdbj.org/pub/pdb/data/structures/divided/mmCIF/${id.toLowerCase().substring(1, 3)}/${id.toLowerCase()}.cif`, id => `PDBj: ${id} (cif)`, false);
}
async function getRcsbDownloadParams(src: ReturnType<DownloadStructure['createDefaultParams']>['source']) {
if (src.name !== 'pdb' || src.params.provider.server.name !== 'rcsb') throw new Error('expected rcsb');
return src.params.provider.server.params.encoding === 'cif'
? getDownloadParams(src.params.provider.id, id => `https://files.rcsb.org/download/${id.toUpperCase()}.cif`, id => `RCSB PDB: ${id} (cif)`, false)
: getDownloadParams(src.params.provider.id, id => `https://models.rcsb.org/${id.toUpperCase()}.bcif`, id => `RCSB PDB: ${id} (bcif)`, true);
}
export const UpdateTrajectory = StateAction.build({
display: { name: 'Update Trajectory' },
params: {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 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>
@@ -75,8 +75,8 @@ const DownloadDensity = StateAction.build({
case 'pdb-xray':
downloadParams = src.params.provider.server === 'pdbe' ? {
url: Asset.Url(src.params.type === '2fofc'
? `http://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.provider.id.toLowerCase()}.ccp4`
: `http://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.provider.id.toLowerCase()}_diff.ccp4`),
? `https://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.provider.id.toLowerCase()}.ccp4`
: `https://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.provider.id.toLowerCase()}_diff.ccp4`),
isBinary: true,
label: `PDBe X-ray map: ${src.params.provider.id}`
} : {

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Camera } from '../../../mol-canvas3d/camera';
import { clamp } from '../../../mol-math/interpolate';
import { Quat } from '../../../mol-math/linear-algebra/3d/quat';
import { Vec3 } from '../../../mol-math/linear-algebra/3d/vec3';
import { degToRad } from '../../../mol-math/misc';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { PluginStateAnimation } from '../model';
const _dir = Vec3(), _axis = Vec3(), _rot = Quat();
type State = { snapshot: Camera.Snapshot };
export const AnimateCameraRock = PluginStateAnimation.create({
name: 'built-in.animate-camera-rock',
display: { name: 'Camera Rock', description: 'Rock the 3D scene around the x-axis in view space' },
isExportable: true,
params: () => ({
durationInMs: PD.Numeric(4000, { min: 100, max: 20000, step: 100 }),
speed: PD.Numeric(1, { min: 1, max: 10, step: 1 }, { description: 'How many times to rock from side to side.' }),
angle: PD.Numeric(10, { min: 0, max: 180, step: 1 }, { description: 'How many degrees to rotate in each direction.' }),
}),
initialState: (p, ctx) => ({ snapshot: ctx.canvas3d!.camera.getSnapshot() }) as State,
getDuration: p => ({ kind: 'fixed', durationMs: p.durationInMs }),
teardown: (_, state: State, ctx) => {
ctx.canvas3d?.requestCameraReset({ snapshot: state.snapshot, durationMs: 0 });
},
async apply(animState: State, t, ctx) {
if (t.current === 0) {
return { kind: 'next', state: animState };
}
const snapshot = animState.snapshot;
if (snapshot.radiusMax < 0.0001) {
return { kind: 'finished' };
}
const phase = t.animation
? t.animation?.currentFrame / (t.animation.frameCount + 1)
: clamp(t.current / ctx.params.durationInMs, 0, 1);
const angle = Math.sin(phase * ctx.params.speed * Math.PI * 2) * degToRad(ctx.params.angle);
Vec3.sub(_dir, snapshot.position, snapshot.target);
Vec3.normalize(_axis, snapshot.up);
Quat.setAxisAngle(_rot, _axis, angle);
Vec3.transformQuat(_dir, _dir, _rot);
const position = Vec3.add(Vec3(), snapshot.target, _dir);
ctx.plugin.canvas3d?.requestCameraReset({ snapshot: { ...snapshot, position }, durationMs: 0 });
if (phase >= 0.99999) {
return { kind: 'finished' };
}
return { kind: 'next', state: animState };
}
});

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -17,11 +17,11 @@ type State = { snapshot: Camera.Snapshot };
export const AnimateCameraSpin = PluginStateAnimation.create({
name: 'built-in.animate-camera-spin',
display: { name: 'Camera Spin' },
display: { name: 'Camera Spin', description: 'Spin the 3D scene around the x-axis in view space' },
isExportable: true,
params: () => ({
durationInMs: PD.Numeric(4000, { min: 100, max: 20000, step: 100 }),
speed: PD.Numeric(1, { min: 1, max: 10, step: 1 }, { description: 'How many times to spin in the specified dutation.' }),
speed: PD.Numeric(1, { min: 1, max: 10, step: 1 }, { description: 'How many times to spin in the specified duration.' }),
direction: PD.Select<'cw' | 'ccw'>('cw', [['cw', 'Clockwise'], ['ccw', 'Counter Clockwise']], { cycle: true })
}),
initialState: (_, ctx) => ({ snapshot: ctx.canvas3d?.camera.getSnapshot()! }) as State,
@@ -39,13 +39,9 @@ export const AnimateCameraSpin = PluginStateAnimation.create({
return { kind: 'finished' };
}
const phase = clamp(t.current / ctx.params.durationInMs, 0, 1);
if (phase >= 0.99999) {
ctx.plugin.canvas3d?.requestCameraReset({ snapshot, durationMs: 0 });
return { kind: 'finished' };
}
const phase = t.animation
? t.animation?.currentFrame / (t.animation.frameCount + 1)
: clamp(t.current / ctx.params.durationInMs, 0, 1);
const angle = 2 * Math.PI * phase * ctx.params.speed * (ctx.params.direction === 'ccw' ? -1 : 1);
Vec3.sub(_dir, snapshot.position, snapshot.target);
@@ -55,6 +51,10 @@ export const AnimateCameraSpin = PluginStateAnimation.create({
const position = Vec3.add(Vec3(), snapshot.target, _dir);
ctx.plugin.canvas3d?.requestCameraReset({ snapshot: { ...snapshot, position }, durationMs: 0 });
if (phase >= 0.99999) {
return { kind: 'finished' };
}
return { kind: 'next', state: animState };
}
});

View File

@@ -51,7 +51,8 @@ namespace PluginStateAnimation {
export interface Time {
lastApplied: number,
current: number
current: number,
animation?: { currentFrame: number, frameCount: number }
}
export type ApplyResult<S> = { kind: 'finished' } | { kind: 'skip' } | { kind: 'next', state: S }

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 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>
@@ -24,6 +24,8 @@ import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bo
import { StructConn } from '../../../mol-model-formats/structure/property/bonds/struct_conn';
import { StructureRepresentationRegistry } from '../../../mol-repr/structure/registry';
import { assertUnreachable } from '../../../mol-util/type-helpers';
import { Color } from '../../../mol-util/color';
import { PostprocessingParams } from '../../../mol-canvas3d/passes/postprocessing';
export interface StructureRepresentationPresetProvider<P = any, S extends _Result = _Result> extends PresetProvider<PluginStateObject.Molecule.Structure, P, S> { }
export function StructureRepresentationPresetProvider<P, S extends _Result>(repr: StructureRepresentationPresetProvider<P, S>) { return repr; }
@@ -98,6 +100,15 @@ type CommonParams = StructureRepresentationPresetProvider.CommonParams
const reprBuilder = StructureRepresentationPresetProvider.reprBuilder;
const updateFocusRepr = StructureRepresentationPresetProvider.updateFocusRepr;
function resetPostprocessingProps(plugin: PluginContext) {
if (plugin.canvas3d) {
const p = PD.getDefaultValues(PostprocessingParams);
plugin.canvas3d.setProps({
postprocessing: { outline: p.outline, occlusion: p.occlusion }
});
}
}
const auto = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-auto',
display: {
@@ -137,6 +148,7 @@ const empty = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-empty',
display: { name: 'Empty', description: 'Removes all existing representations.' },
async apply(ref, params, plugin) {
resetPostprocessingProps(plugin);
return { };
}
});
@@ -191,6 +203,8 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
await update.commit({ revertOnError: false });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
resetPostprocessingProps(plugin);
return { components, representations };
}
});
@@ -230,6 +244,8 @@ const proteinAndNucleic = StructureRepresentationPresetProvider({
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
resetPostprocessingProps(plugin);
return { components, representations };
}
});
@@ -282,6 +298,8 @@ const coarseSurface = StructureRepresentationPresetProvider({
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
resetPostprocessingProps(plugin);
return { components, representations };
}
});
@@ -315,6 +333,8 @@ const polymerCartoon = StructureRepresentationPresetProvider({
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
resetPostprocessingProps(plugin);
return { components, representations };
}
});
@@ -381,6 +401,56 @@ const atomicDetail = StructureRepresentationPresetProvider({
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name ?? color, params.theme?.focus?.params ?? colorParams);
resetPostprocessingProps(plugin);
return { components, representations };
}
});
const illustrative = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-illustrative',
display: {
name: 'Illustrative', group: 'Miscellaneous',
description: '...'
},
params: () => ({
...CommonParams,
showCarbohydrateSymbol: PD.Boolean(false)
}),
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const components = {
all: await presetStaticComponent(plugin, structureCell, 'all'),
branched: undefined
};
const structure = structureCell.obj!.data;
const { update, builder, typeParams, color } = reprBuilder(plugin, params, structure);
const representations = {
all: builder.buildRepresentation(update, components.all, { type: 'spacefill', typeParams: { ...typeParams, ignoreLight: true }, color: 'illustrative' }, { tag: 'all' }),
};
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name ?? color, params.theme?.focus?.params);
if (plugin.canvas3d) {
plugin.canvas3d.setProps({
postprocessing: {
outline: {
name: 'on',
params: { scale: 1, color: Color(0x000000), threshold: 0.25 }
},
occlusion: {
name: 'on',
params: { bias: 0.9, blurKernelSize: 15, radius: 5, samples: 32 }
},
}
});
}
return { components, representations };
}
});
@@ -400,6 +470,7 @@ export const PresetStructureRepresentations = {
'polymer-cartoon': polymerCartoon,
'polymer-and-ligand': polymerAndLigand,
'protein-and-nucleic': proteinAndNucleic,
'coarse-surface': coarseSurface
'coarse-surface': coarseSurface,
'illustrative': illustrative,
};
export type PresetStructureRepresentations = typeof PresetStructureRepresentations;

View File

@@ -18,7 +18,6 @@ import { objectForEach } from '../../mol-util/object';
import { RecommendedIsoValue } from '../../mol-model-formats/volume/property';
import { getContourLevelEmdb } from '../../mol-plugin/behavior/dynamic/volume-streaming/util';
import { Task } from '../../mol-task';
import { DscifFormat } from '../../mol-model-formats/volume/density-server';
export const VolumeFormatCategory = 'Volume';
type Params = { entryId?: string };
@@ -42,19 +41,9 @@ async function tryObtainRecommendedIsoValue(plugin: PluginContext, volume?: Volu
function tryGetRecomendedIsoValue(volume: Volume) {
const recommendedIsoValue = RecommendedIsoValue.Provider.get(volume);
if (!recommendedIsoValue) return;
if (recommendedIsoValue.kind === 'relative') return recommendedIsoValue;
let stats = volume.grid.stats;
if (DscifFormat.is(volume.sourceData)) {
stats = {
min: volume.sourceData.data.volume_data_3d_info.min_source.value(0),
max: volume.sourceData.data.volume_data_3d_info.max_source.value(0),
mean: volume.sourceData.data.volume_data_3d_info.mean_source.value(0),
sigma: volume.sourceData.data.volume_data_3d_info.sigma_source.value(0),
};
}
return Volume.IsoValue.toRelative(recommendedIsoValue, stats);
return Volume.adjustedIsoValue(volume, recommendedIsoValue.absoluteValue, 'absolute');
}
async function defaultVisuals(plugin: PluginContext, data: { volume: StateObjectSelector<PluginStateObject.Volume.Data> }) {

View File

@@ -99,12 +99,12 @@ class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationMana
await this.start();
}
async tick(t: number, isSynchronous?: boolean) {
async tick(t: number, isSynchronous?: boolean, animation?: PluginAnimationManager.AnimationInfo) {
this.currentTime = t;
if (this.isStopped) return;
if (isSynchronous) {
await this.applyFrame();
if (isSynchronous || animation) {
await this.applyFrame(animation);
} else {
this.applyAsync();
}
@@ -165,12 +165,12 @@ class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationMana
}
}
private async applyFrame() {
private async applyFrame(animation?: PluginAnimationManager.AnimationInfo) {
const t = this.currentTime;
if (this._current.startedTime < 0) this._current.startedTime = t;
const newState = await this._current.anim.apply(
this._current.state,
{ lastApplied: this._current.lastTime, current: t - this._current.startedTime },
{ lastApplied: this._current.lastTime, current: t - this._current.startedTime, animation },
{ params: this._current.paramValues, plugin: this.context });
if (newState.kind === 'finished') {
@@ -228,6 +228,11 @@ class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationMana
}
namespace PluginAnimationManager {
export interface AnimationInfo {
currentFrame: number,
frameCount: number
}
export interface Current {
anim: PluginStateAnimation
params: PD.Params,

View File

@@ -84,7 +84,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
const from = this.getIndex(e);
let to = (from + dir) % len;
if (to < 0) to += len;
const f = this.state.entries.get(to);
const f = this.state.entries.get(to)!;
const entries = this.state.entries.asMutable();
entries.set(to, e);
@@ -115,7 +115,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
if (!id) {
if (len === 0) return void 0;
const idx = dir === -1 ? len - 1 : 0;
return this.state.entries.get(idx).snapshot.id;
return this.state.entries.get(idx)!.snapshot.id;
}
const e = this.getEntry(id);
@@ -126,7 +126,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
idx = (idx + dir) % len;
if (idx < 0) idx += len;
return this.state.entries.get(idx).snapshot.id;
return this.state.entries.get(idx)!.snapshot.id;
}
async setStateSnapshot(snapshot: PluginStateSnapshotManager.StateSnapshot): Promise<PluginState.Snapshot | undefined> {

View File

@@ -918,7 +918,7 @@ async function attachModelProps(model: Model, ctx: PluginContext, taskCtx: Runti
const propertyCtx = { runtime: taskCtx, assetManager: ctx.managers.asset };
const { autoAttach, properties } = params;
for (const name of Object.keys(properties)) {
const property = ctx.customModelProperties.get(name);
const property = ctx.customModelProperties.get(name)!;
const props = properties[name];
if (autoAttach.includes(name) || property.isHidden) {
try {
@@ -973,7 +973,7 @@ async function attachStructureProps(structure: Structure, ctx: PluginContext, ta
const propertyCtx = { runtime: taskCtx, assetManager: ctx.managers.asset };
const { autoAttach, properties } = params;
for (const name of Object.keys(properties)) {
const property = ctx.customStructureProperties.get(name);
const property = ctx.customStructureProperties.get(name)!;
const props = properties[name];
if (autoAttach.includes(name) || property.isHidden) {
try {

View File

@@ -125,7 +125,7 @@ export class StateSnapshotViewportControls extends PluginUIComponent<{}, { isBus
} else if (e.keyCode === 38 || e.key === 'ArrowUp') {
if (snapshots.state.isPlaying) snapshots.stop();
if (snapshots.state.entries.size === 0) return;
const e = snapshots.state.entries.get(0);
const e = snapshots.state.entries.get(0)!;
this.update(e.snapshot.id);
} else if (e.keyCode === 39 || e.key === 'ArrowRight') {
if (snapshots.state.isPlaying) snapshots.stop();
@@ -133,7 +133,7 @@ export class StateSnapshotViewportControls extends PluginUIComponent<{}, { isBus
} else if (e.keyCode === 40 || e.key === 'ArrowDown') {
if (snapshots.state.isPlaying) snapshots.stop();
if (snapshots.state.entries.size === 0) return;
const e = snapshots.state.entries.get(snapshots.state.entries.size - 1);
const e = snapshots.state.entries.get(snapshots.state.entries.size - 1)!;
this.update(e.snapshot.id);
}
};

View File

@@ -185,9 +185,9 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
rmsd += xform.transform.rmsd;
}
rmsd /= transforms.length - 1;
rmsd /= Math.max(transforms.length - 1, 1);
this.plugin.log.info(`Superposed ${input.length} structures with avg. RMSD ${rmsd.toFixed(2)}.`);
this.plugin.log.info(`Superposed ${input.length} structures with avg. RMSD ${rmsd.toFixed(2)} Å.`);
await new Promise(res => requestAnimationFrame(res));
PluginCommands.Camera.Reset(this.plugin);
};

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 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>
@@ -46,10 +46,7 @@ const LayoutOptions = {
type LayoutOptions = keyof typeof LayoutOptions
const SimpleSettingsParams = {
spin: PD.Group({
spin: Canvas3DParams.trackball.params.spin,
speed: Canvas3DParams.trackball.params.spinSpeed
}, { pivot: 'spin' }),
animate: Canvas3DParams.trackball.params.animate,
camera: Canvas3DParams.camera,
background: PD.Group({
color: PD.Color(Color(0xFCFBF9), { label: 'Background', description: 'Custom background color' }),
@@ -96,7 +93,7 @@ const SimpleSettingsMapping = ParamMapping({
return {
layout: props.layout,
spin: { spin: !!canvas.trackball.spin, speed: canvas.trackball.spinSpeed },
animate: canvas.trackball.animate,
camera: canvas.camera,
background: {
color: renderer.backgroundColor,
@@ -114,8 +111,7 @@ const SimpleSettingsMapping = ParamMapping({
},
update(s, props) {
const canvas = props.canvas as Mutable<Canvas3DProps>;
canvas.trackball.spin = s.spin.spin;
canvas.trackball.spinSpeed = s.spin.speed;
canvas.trackball.animate = s.animate;
canvas.camera = s.camera;
canvas.transparentBackground = s.background.transparent;
canvas.renderer.backgroundColor = s.background.color;

View File

@@ -6,6 +6,7 @@
import { PluginContext } from './context';
import { now } from '../mol-util/now';
import { PluginAnimationManager } from '../mol-plugin-state/manager/animation';
export class PluginAnimationLoop {
private currentFrame: any = void 0;
@@ -15,8 +16,8 @@ export class PluginAnimationLoop {
return this._isAnimating;
}
async tick(t: number, options?: { isSynchronous?: boolean, manualDraw?: boolean }) {
await this.plugin.managers.animation.tick(t, options?.isSynchronous);
async tick(t: number, options?: { isSynchronous?: boolean, manualDraw?: boolean, animation?: PluginAnimationManager.AnimationInfo }) {
await this.plugin.managers.animation.tick(t, options?.isSynchronous, options?.animation);
this.plugin.canvas3d?.tick(t as now.Timestamp, options);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 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>
@@ -23,6 +23,7 @@ import { StateTransforms } from '../mol-plugin-state/transforms';
import { BoxifyVolumeStreaming, CreateVolumeStreamingBehavior, InitVolumeStreaming } from '../mol-plugin/behavior/dynamic/volume-streaming/transformers';
import { AnimateStateInterpolation } from '../mol-plugin-state/animation/built-in/state-interpolation';
import { AnimateStructureSpin } from '../mol-plugin-state/animation/built-in/spin-structure';
import { AnimateCameraRock } from '../mol-plugin-state/animation/built-in/camera-rock';
export { PluginSpec };
@@ -131,6 +132,7 @@ export const DefaultPluginSpec = (): PluginSpec => ({
animations: [
AnimateModelIndex,
AnimateCameraSpin,
AnimateCameraRock,
AnimateStateSnapshots,
AnimateAssemblyUnwind,
AnimateStructureSpin,

View File

@@ -72,7 +72,7 @@ export class PluginToastManager extends StatefulPluginComponent<{
if (delay < 0) delay = 500;
return <number><any>setTimeout(() => {
const e = this.state.entries.get(id);
const e = this.state.entries.get(id)!;
e.timeout = void 0;
this.hide(e);
}, delay);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -101,6 +101,8 @@ function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
return EmptyLoci;
}
const __linkIndicesSet = new Set<number>();
function eachCarbohydrateLink(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
let changed = false;
if (!StructureElement.Loci.is(loci)) return false;
@@ -110,11 +112,14 @@ function eachCarbohydrateLink(loci: Loci, structure: Structure, apply: (interval
for (const { unit, indices } of loci.elements) {
if (!Unit.isAtomic(unit)) continue;
__linkIndicesSet.clear();
OrderedSet.forEach(indices, v => {
// TODO avoid duplicate calls to apply
const linkIndices = getLinkIndices(unit, unit.elements[v]);
for (let i = 0, il = linkIndices.length; i < il; ++i) {
if (apply(Interval.ofSingleton(linkIndices[i]))) changed = true;
if (!__linkIndicesSet.has(linkIndices[i])) {
__linkIndicesSet.add(linkIndices[i]);
if (apply(Interval.ofSingleton(linkIndices[i]))) changed = true;
}
}
});
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -29,8 +29,8 @@ import { getAltResidueLociFromId } from './util/common';
import { BaseGeometry } from '../../../mol-geo/geometry/base';
const t = Mat4.identity();
const sVec = Vec3.zero();
const pd = Vec3.zero();
const sVec = Vec3();
const pd = Vec3();
const SideFactor = 2 * 0.806; // 0.806 == Math.cos(Math.PI / 4)
@@ -212,6 +212,8 @@ function getCarbohydrateLoci(pickingId: PickingId, structure: Structure, id: num
return EmptyLoci;
}
const __elementIndicesSet = new Set<number>();
/** For each carbohydrate (usually a monosaccharide) when all its residue's elements are in a loci. */
function eachCarbohydrate(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
const { getElementIndices } = structure.carbohydrates;
@@ -222,11 +224,14 @@ function eachCarbohydrate(loci: Loci, structure: Structure, apply: (interval: In
for (const { unit, indices } of loci.elements) {
if (!Unit.isAtomic(unit)) continue;
__elementIndicesSet.clear();
OrderedSet.forEach(indices, v => {
// TODO avoid duplicate calls to apply
const elementIndices = getElementIndices(unit, unit.elements[v]);
for (let i = 0, il = elementIndices.length; i < il; ++i) {
if (apply(Interval.ofSingleton(elementIndices[i] * 2))) changed = true;
if (!__elementIndicesSet.has(elementIndices[i])) {
__elementIndicesSet.add(elementIndices[i]);
if (apply(Interval.ofSingleton(elementIndices[i] * 2))) changed = true;
}
}
});
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -123,6 +123,8 @@ function getTerminalLinkLoci(pickingId: PickingId, structure: Structure, id: num
return EmptyLoci;
}
const __linkIndicesSet = new Set<number>();
function eachTerminalLink(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
let changed = false;
if (!StructureElement.Loci.is(loci)) return false;
@@ -132,11 +134,14 @@ function eachTerminalLink(loci: Loci, structure: Structure, apply: (interval: In
for (const { unit, indices } of loci.elements) {
if (!Unit.isAtomic(unit)) continue;
__linkIndicesSet.clear();
OrderedSet.forEach(indices, v => {
// TODO avoid duplicate calls to apply
const linkIndices = getTerminalLinkIndices(unit, unit.elements[v]);
for (let i = 0, il = linkIndices.length; i < il; ++i) {
if (apply(Interval.ofSingleton(linkIndices[i]))) changed = true;
if (!__linkIndicesSet.has(linkIndices[i])) {
__linkIndicesSet.add(linkIndices[i]);
if (apply(Interval.ofSingleton(linkIndices[i]))) changed = true;
}
}
});
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -224,6 +224,8 @@ export function getInterBondLoci(pickingId: PickingId, structure: Structure, id:
return EmptyLoci;
}
const __unitMap = new Map<number, OrderedSet<StructureElement.UnitIndex>>();
export function eachInterBond(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean, isMarking: boolean) {
let changed = false;
if (Bond.isLoci(loci)) {
@@ -238,14 +240,13 @@ export function eachInterBond(loci: Loci, structure: Structure, apply: (interval
if (!Structure.areEquivalent(loci.structure, structure)) return false;
if (isMarking && loci.elements.length === 1) return false; // only a single unit
const map = new Map<number, OrderedSet<StructureElement.UnitIndex>>();
for (const e of loci.elements) map.set(e.unit.id, e.indices);
for (const e of loci.elements) __unitMap.set(e.unit.id, e.indices);
for (const e of loci.elements) {
const { unit } = e;
if (!Unit.isAtomic(unit)) continue;
structure.interUnitBonds.getConnectedUnits(unit.id).forEach(b => {
const otherLociIndices = map.get(b.unitB);
const otherLociIndices = __unitMap.get(b.unitB);
if (!isMarking || otherLociIndices) {
OrderedSet.forEach(e.indices, v => {
if (!b.connectedIndices.includes(v)) return;
@@ -259,6 +260,8 @@ export function eachInterBond(loci: Loci, structure: Structure, apply: (interval
}
});
}
__unitMap.clear();
}
return changed;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -88,6 +88,7 @@ namespace Visual {
const currentStatus = markerStatus.ref.value as MarkerInfo['status'];
if (!isEveryLoci(loci)) {
// assume that all interval are non-overlapping
let intervalSize = 0;
lociApply(loci, interval => {
intervalSize += Interval.size(interval);

View File

@@ -34,19 +34,38 @@ namespace StateTree {
readonly forEach: OrderedSet<Ref>['forEach'],
readonly map: OrderedSet<Ref>['map'],
toArray(): Ref[],
first(): Ref
first(): Ref,
asMutable(): MutableChildSet
}
export interface MutableChildSet extends ChildSet {
add(ref: Ref): MutableChildSet,
remove(ref: Ref): MutableChildSet,
asImmutable(): ChildSet
}
interface _Map<T> {
readonly size: number,
has(ref: Ref): boolean,
get(ref: Ref): T
get(ref: Ref): T,
asImmutable(): _Map<T>,
asMutable(): MutableMap<T>
}
export interface MutableMap<T> extends _Map<T> {
set(ref: Ref, value: T): MutableMap<T>,
delete(ref: Ref): MutableMap<T>
}
export interface Transforms extends _Map<StateTransform> {}
export interface Children extends _Map<ChildSet> { }
export interface Dependencies extends _Map<ChildSet> { }
export interface MutableTransforms extends MutableMap<StateTransform> {}
export interface MutableChildren extends MutableMap<MutableChildSet> { }
export interface MutableDependencies extends MutableMap<MutableChildSet> { }
class Impl implements StateTree {
get root() { return this.transforms.get(StateTransform.RootRef)!; }
@@ -63,7 +82,10 @@ namespace StateTree {
*/
export function createEmpty(customRoot?: StateTransform): StateTree {
const root = customRoot || StateTransform.createRoot();
return create(ImmutableMap([[root.ref, root]]), ImmutableMap([[root.ref, OrderedSet()]]), ImmutableMap());
return create(
ImmutableMap([[root.ref, root] as [Ref, StateTransform]]) as Transforms,
ImmutableMap([[root.ref, OrderedSet()] as [Ref, ChildSet]]) as Children,
ImmutableMap() as Dependencies);
}
export function create(nodes: Transforms, children: Children, dependencies: Dependencies): StateTree {
@@ -148,13 +170,13 @@ namespace StateTree {
children.set(transform.ref, OrderedSet<Ref>().asMutable());
}
if (transform.ref !== transform.parent) children.get(transform.parent).add(transform.ref);
if (transform.ref !== transform.parent) children.get(transform.parent)!.add(transform.ref);
}
const dependent = new Set<Ref>();
for (const t of data.transforms) {
const ref = t.ref;
children.set(ref, children.get(ref).asImmutable());
children.set(ref, children.get(ref)!.asImmutable());
if (!t.dependsOn) continue;
@@ -163,16 +185,16 @@ namespace StateTree {
if (!dependencies.has(d)) {
dependencies.set(d, OrderedSet<Ref>([ref]).asMutable());
} else {
dependencies.get(d).add(ref);
dependencies.get(d)!.add(ref);
}
}
}
dependent.forEach(d => {
dependencies.set(d, dependencies.get(d).asImmutable());
dependencies.set(d, dependencies.get(d)!.asImmutable());
});
return create(nodes.asImmutable(), children.asImmutable(), dependencies.asImmutable());
return create(nodes.asImmutable() as Transforms, children.asImmutable() as Children, dependencies.asImmutable() as Dependencies);
}
export function dump(tree: StateTree) {

View File

@@ -13,16 +13,16 @@ import { arrayEqual } from '../../mol-util/array';
export { TransientTree };
class TransientTree implements StateTree {
transforms = this.tree.transforms as ImmutableMap<StateTransform.Ref, StateTransform>;
children = this.tree.children as ImmutableMap<StateTransform.Ref, OrderedSet<StateTransform.Ref>>;
dependencies = this.tree.dependencies as ImmutableMap<StateTransform.Ref, OrderedSet<StateTransform.Ref>>;
transforms = this.tree.transforms as StateTree.MutableTransforms;
children = this.tree.children as StateTree.MutableChildren;
dependencies = this.tree.dependencies as StateTree.MutableDependencies;
private changedNodes = false;
private changedChildren = false;
private changedDependencies = false;
private _childMutations: Map<StateTransform.Ref, OrderedSet<StateTransform.Ref>> | undefined = void 0;
private _dependencyMutations: Map<StateTransform.Ref, OrderedSet<StateTransform.Ref>> | undefined = void 0;
private _dependencyMutations: Map<StateTransform.Ref, StateTree.MutableChildSet> | undefined = void 0;
private _stateUpdates: Set<StateTransform.Ref> | undefined = void 0;
private get childMutations() {
@@ -99,12 +99,11 @@ class TransientTree implements StateTree {
}
private mutateDependency(parent: StateTransform.Ref, child: StateTransform.Ref, action: 'add' | 'remove') {
let set = this.dependencyMutations.get(parent);
let set: StateTree.MutableChildSet | undefined = this.dependencyMutations.get(parent);
if (!set) {
const src = this.dependencies.get(parent);
if (!src && action === 'remove') return;
this.changeDependencies();
set = src ? src.asMutable() : OrderedSet<string>().asMutable();
this.dependencyMutations.set(parent, set);
@@ -275,7 +274,7 @@ class TransientTree implements StateTree {
asImmutable() {
if (!this.changedNodes && !this.changedChildren && !this._childMutations) return this.tree;
if (this._childMutations) this._childMutations.forEach(fixChildMutations, this.children);
if (this._dependencyMutations) this._dependencyMutations.forEach(fixDependencyMutations, this.dependencies);
if (this._dependencyMutations) this._dependencyMutations.forEach(fixDependencyMutations as any, this.dependencies);
return StateTree.create(
this.changedNodes ? this.transforms.asImmutable() : this.transforms,
this.changedChildren ? this.children.asImmutable() : this.children,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -13,8 +13,12 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ThemeDataContext } from '../theme';
import { TableLegend } from '../../mol-util/legend';
import { getAdjustedColorMap } from '../../mol-util/color/color';
import { getColorMapParams } from '../../mol-util/color/params';
import { ChainIdColorTheme, ChainIdColorThemeParams } from './chain-id';
import { OperatorNameColorThemeParams, OperatorNameColorTheme } from './operator-name';
import { EntityIdColorTheme, EntityIdColorThemeParams } from './entity-id';
import { assertUnreachable } from '../../mol-util/type-helpers';
import { EntitySourceColorTheme, EntitySourceColorThemeParams } from './entity-source';
// from Jmol http://jmol.sourceforge.net/jscolors/ (or 0xFFFFFF)
export const ElementSymbolColors = ColorMap({
@@ -25,16 +29,20 @@ export type ElementSymbolColors = typeof ElementSymbolColors
const DefaultElementSymbolColor = Color(0xFFFFFF);
const Description = 'Assigns a color to every atom according to its chemical element.';
// TODO generalise `carbonColor` param to all themes?
export const ElementSymbolColorThemeParams = {
carbonColor: PD.MappedStatic('chain-id', {
'chain-id': PD.Group({ ...ChainIdColorThemeParams }),
'operator-name': PD.Group({ ...OperatorNameColorThemeParams }),
'element-symbol': PD.Group({})
'chain-id': PD.Group(ChainIdColorThemeParams),
'entity-id': PD.Group(EntityIdColorThemeParams),
'entity-source': PD.Group(EntitySourceColorThemeParams),
'operator-name': PD.Group(OperatorNameColorThemeParams),
'element-symbol': PD.EmptyGroup()
}, { description: 'Use chain-id coloring for carbon atoms.' }),
saturation: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
lightness: PD.Numeric(0.2, { min: -6, max: 6, step: 0.1 })
lightness: PD.Numeric(0.2, { min: -6, max: 6, step: 0.1 }),
colors: PD.MappedStatic('default', {
'default': PD.EmptyGroup(),
'custom': PD.Group(getColorMapParams(ElementSymbolColors))
})
};
export type ElementSymbolColorThemeParams = typeof ElementSymbolColorThemeParams
export function getElementSymbolColorThemeParams(ctx: ThemeDataContext) {
@@ -47,13 +55,16 @@ export function elementSymbolColor(colorMap: ElementSymbolColors, element: Eleme
}
export function ElementSymbolColorTheme(ctx: ThemeDataContext, props: PD.Values<ElementSymbolColorThemeParams>): ColorTheme<ElementSymbolColorThemeParams> {
const colorMap = getAdjustedColorMap(ElementSymbolColors, props.saturation, props.lightness);
const colorMap = getAdjustedColorMap(props.colors.name === 'default' ? ElementSymbolColors : props.colors.params, props.saturation, props.lightness);
const carbonColor = props.carbonColor.name === 'chain-id'
? ChainIdColorTheme(ctx, props.carbonColor.params).color
: props.carbonColor.name === 'operator-name'
? OperatorNameColorTheme(ctx, props.carbonColor.params).color
: undefined;
const pcc = props.carbonColor;
const carbonColor =
pcc.name === 'chain-id' ? ChainIdColorTheme(ctx, pcc.params).color :
pcc.name === 'entity-id' ? EntityIdColorTheme(ctx, pcc.params).color :
pcc.name === 'entity-source' ? EntitySourceColorTheme(ctx, pcc.params).color :
pcc.name === 'operator-name' ? OperatorNameColorTheme(ctx, pcc.params).color :
pcc.name === 'element-symbol' ? undefined :
assertUnreachable(pcc);
function elementColor(element: ElementSymbol, location: Location) {
return (carbonColor && element === 'C')
@@ -86,8 +97,8 @@ export function ElementSymbolColorTheme(ctx: ThemeDataContext, props: PD.Values<
color,
props,
description: Description,
legend: TableLegend(Object.keys(ElementSymbolColors).map(name => {
return [name, (ElementSymbolColors as any)[name] as Color] as [string, Color];
legend: TableLegend(Object.keys(colorMap).map(name => {
return [name, (colorMap as any)[name] as Color] as [string, Color];
}))
};
}

View File

@@ -1,10 +1,10 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { StructureProperties, StructureElement, Bond, Structure } from '../../mol-model/structure';
import { StructureProperties, StructureElement, Bond, Structure, Unit } from '../../mol-model/structure';
import { Color } from '../../mol-util/color';
import { Location } from '../../mol-model/location';
import { ColorTheme, LocationColor } from '../color';
@@ -38,10 +38,33 @@ function getEntityIdSerialMap(structure: Structure) {
const k = key(label_entity_id.value(j), i);
if (!map.has(k)) map.set(k, map.size);
}
const { coarseHierarchy } = structure.models[i];
if (coarseHierarchy.isDefined) {
const { entity_id: spheres_entity_id } = coarseHierarchy.spheres;
for (let j = 0, jl = spheres_entity_id.rowCount; j < jl; ++j) {
const k = key(spheres_entity_id.value(j), i);
if (!map.has(k)) map.set(k, map.size);
}
const { entity_id: gaussians_entity_id } = coarseHierarchy.gaussians;
for (let j = 0, jl = gaussians_entity_id.rowCount; j < jl; ++j) {
const k = key(gaussians_entity_id.value(j), i);
if (!map.has(k)) map.set(k, map.size);
}
}
}
return map;
}
function getEntityId(location: StructureElement.Location): string {
switch (location.unit.kind) {
case Unit.Kind.Atomic:
return StructureProperties.chain.label_entity_id(location);
case Unit.Kind.Spheres:
case Unit.Kind.Gaussians:
return StructureProperties.coarse.entity_id(location);
}
}
export function EntityIdColorTheme(ctx: ThemeDataContext, props: PD.Values<EntityIdColorThemeParams>): ColorTheme<EntityIdColorThemeParams> {
let color: LocationColor;
let legend: ScaleLegend | TableLegend | undefined;
@@ -59,16 +82,16 @@ export function EntityIdColorTheme(ctx: ThemeDataContext, props: PD.Values<Entit
color = (location: Location): Color => {
let serial: number | undefined = undefined;
if (StructureElement.Location.is(location)) {
const atomId = StructureProperties.chain.label_entity_id(location);
const entityId = getEntityId(location);
const modelIndex = location.structure.models.indexOf(location.unit.model);
const k = key(atomId, modelIndex);
const k = key(entityId, modelIndex);
serial = entityIdSerialMap.get(k);
} else if (Bond.isLocation(location)) {
l.unit = location.aUnit;
l.element = location.aUnit.elements[location.aIndex];
const atomId = StructureProperties.chain.label_entity_id(l);
const entityId = getEntityId(l);
const modelIndex = l.structure.models.indexOf(l.unit.model);
const k = key(atomId, modelIndex);
const k = key(entityId, modelIndex);
serial = entityIdSerialMap.get(k);
}
return serial === undefined ? DefaultColor : palette.color(serial);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -12,24 +12,42 @@ import { ColorTheme } from '../color';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ThemeDataContext } from '../theme';
import { ChainIdColorTheme, ChainIdColorThemeParams } from './chain-id';
import { UniformColorTheme, UniformColorThemeParams } from './uniform';
import { assertUnreachable } from '../../mol-util/type-helpers';
import { EntityIdColorTheme, EntityIdColorThemeParams } from './entity-id';
import { MoleculeTypeColorTheme, MoleculeTypeColorThemeParams } from './molecule-type';
import { EntitySourceColorTheme, EntitySourceColorThemeParams } from './entity-source';
const DefaultIllustrativeColor = Color(0xEEEEEE);
const Description = `Assigns an illustrative color that gives every chain a unique color with lighter carbons (inspired by David Goodsell's Molecule of the Month style).`;
const Description = `Assigns an illustrative color that gives every chain a color based on the choosen style but with lighter carbons (inspired by David Goodsell's Molecule of the Month style).`;
export const IllustrativeColorThemeParams = {
...ChainIdColorThemeParams,
style: PD.MappedStatic('entity-id', {
uniform: PD.Group(UniformColorThemeParams),
'chain-id': PD.Group(ChainIdColorThemeParams),
'entity-id': PD.Group(EntityIdColorThemeParams),
'entity-source': PD.Group(EntitySourceColorThemeParams),
'molecule-type': PD.Group(MoleculeTypeColorThemeParams),
}),
carbonLightness: PD.Numeric(0.8, { min: -6, max: 6, step: 0.1 })
};
export type IllustrativeColorThemeParams = typeof IllustrativeColorThemeParams
export function getIllustrativeColorThemeParams(ctx: ThemeDataContext) {
return IllustrativeColorThemeParams; // TODO return copy
const params = PD.clone(IllustrativeColorThemeParams);
return params;
}
export function IllustrativeColorTheme(ctx: ThemeDataContext, props: PD.Values<IllustrativeColorThemeParams>): ColorTheme<IllustrativeColorThemeParams> {
const { color: chainIdColor, legend } = ChainIdColorTheme(ctx, props);
const { color: styleColor, legend } =
props.style.name === 'uniform' ? UniformColorTheme(ctx, props.style.params) :
props.style.name === 'chain-id' ? ChainIdColorTheme(ctx, props.style.params) :
props.style.name === 'entity-id' ? EntityIdColorTheme(ctx, props.style.params) :
props.style.name === 'entity-source' ? EntitySourceColorTheme(ctx, props.style.params) :
props.style.name === 'molecule-type' ? MoleculeTypeColorTheme(ctx, props.style.params) :
assertUnreachable(props.style);
function illustrativeColor(location: Location, typeSymbol: ElementSymbol) {
const baseColor = chainIdColor(location, false);
const baseColor = styleColor(location, false);
return typeSymbol === 'C' ? Color.lighten(baseColor, props.carbonLightness) : baseColor;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -14,6 +14,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ThemeDataContext } from '../theme';
import { TableLegend } from '../../mol-util/legend';
import { getAdjustedColorMap } from '../../mol-util/color/color';
import { getColorMapParams } from '../../mol-util/color/params';
export const MoleculeTypeColors = ColorMap({
water: 0x386cb0,
@@ -31,7 +32,11 @@ const Description = 'Assigns a color based on the molecule type of a residue.';
export const MoleculeTypeColorThemeParams = {
saturation: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 })
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
colors: PD.MappedStatic('default', {
'default': PD.EmptyGroup(),
'custom': PD.Group(getColorMapParams(MoleculeTypeColors))
})
};
export type MoleculeTypeColorThemeParams = typeof MoleculeTypeColorThemeParams
export function getMoleculeTypeColorThemeParams(ctx: ThemeDataContext) {
@@ -53,7 +58,7 @@ export function moleculeTypeColor(colorMap: MoleculeTypeColors, unit: Unit, elem
}
export function MoleculeTypeColorTheme(ctx: ThemeDataContext, props: PD.Values<MoleculeTypeColorThemeParams>): ColorTheme<MoleculeTypeColorThemeParams> {
const colorMap = getAdjustedColorMap(MoleculeTypeColors, props.saturation, props.lightness);
const colorMap = getAdjustedColorMap(props.colors.name === 'default' ? MoleculeTypeColors : props.colors.params, props.saturation, props.lightness);
function color(location: Location): Color {
if (StructureElement.Location.is(location)) {
@@ -70,8 +75,8 @@ export function MoleculeTypeColorTheme(ctx: ThemeDataContext, props: PD.Values<M
color,
props,
description: Description,
legend: TableLegend(Object.keys(MoleculeTypeColors).map(name => {
return [name, (MoleculeTypeColors as any)[name] as Color] as [string, Color];
legend: TableLegend(Object.keys(colorMap).map(name => {
return [name, (colorMap as any)[name] as Color] as [string, Color];
}).concat([['Other/unknown', DefaultMoleculeTypeColor]]))
};
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -12,6 +12,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ThemeDataContext } from '../theme';
import { TableLegend } from '../../mol-util/legend';
import { getAdjustedColorMap } from '../../mol-util/color/color';
import { getColorMapParams } from '../../mol-util/color/params';
// protein colors from Jmol http://jmol.sourceforge.net/jscolors/
export const ResidueNameColors = ColorMap({
@@ -66,7 +67,11 @@ const Description = 'Assigns a color to every residue according to its name.';
export const ResidueNameColorThemeParams = {
saturation: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
lightness: PD.Numeric(1, { min: -6, max: 6, step: 0.1 })
lightness: PD.Numeric(1, { min: -6, max: 6, step: 0.1 }),
colors: PD.MappedStatic('default', {
'default': PD.EmptyGroup(),
'custom': PD.Group(getColorMapParams(ResidueNameColors))
})
};
export type ResidueNameColorThemeParams = typeof ResidueNameColorThemeParams
export function getResidueNameColorThemeParams(ctx: ThemeDataContext) {
@@ -93,7 +98,7 @@ export function residueNameColor(colorMap: ResidueNameColors, residueName: strin
}
export function ResidueNameColorTheme(ctx: ThemeDataContext, props: PD.Values<ResidueNameColorThemeParams>): ColorTheme<ResidueNameColorThemeParams> {
const colorMap = getAdjustedColorMap(ResidueNameColors, props.saturation, props.lightness);
const colorMap = getAdjustedColorMap(props.colors.name === 'default' ? ResidueNameColors : props.colors.params, props.saturation, props.lightness);
function color(location: Location): Color {
if (StructureElement.Location.is(location)) {
@@ -123,8 +128,8 @@ export function ResidueNameColorTheme(ctx: ThemeDataContext, props: PD.Values<Re
color,
props,
description: Description,
legend: TableLegend(Object.keys(ResidueNameColors).map(name => {
return [name, (ResidueNameColors as any)[name] as Color] as [string, Color];
legend: TableLegend(Object.keys(colorMap).map(name => {
return [name, (colorMap as any)[name] as Color] as [string, Color];
}).concat([['Unknown', DefaultResidueNameColor]]))
};
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -15,6 +15,7 @@ import { ThemeDataContext } from '../theme';
import { TableLegend } from '../../mol-util/legend';
import { SecondaryStructureProvider, SecondaryStructureValue } from '../../mol-model-props/computed/secondary-structure';
import { getAdjustedColorMap } from '../../mol-util/color/color';
import { getColorMapParams } from '../../mol-util/color/params';
import { CustomProperty } from '../../mol-model-props/common/custom-property';
import { hash2 } from '../../mol-data/util';
@@ -41,7 +42,11 @@ const Description = 'Assigns a color based on the type of secondary structure an
export const SecondaryStructureColorThemeParams = {
saturation: PD.Numeric(-1, { min: -6, max: 6, step: 0.1 }),
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 })
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
colors: PD.MappedStatic('default', {
'default': PD.EmptyGroup(),
'custom': PD.Group(getColorMapParams(SecondaryStructureColors))
})
};
export type SecondaryStructureColorThemeParams = typeof SecondaryStructureColorThemeParams
export function getSecondaryStructureColorThemeParams(ctx: ThemeDataContext) {
@@ -88,7 +93,7 @@ export function SecondaryStructureColorTheme(ctx: ThemeDataContext, props: PD.Va
const computedSecondaryStructure = ctx.structure && SecondaryStructureProvider.get(ctx.structure);
const contextHash = computedSecondaryStructure ? hash2(computedSecondaryStructure.id, computedSecondaryStructure.version) : -1;
const colorMap = getAdjustedColorMap(SecondaryStructureColors, props.saturation, props.lightness);
const colorMap = getAdjustedColorMap(props.colors.name === 'default' ? SecondaryStructureColors : props.colors.params, props.saturation, props.lightness);
function color(location: Location): Color {
if (StructureElement.Location.is(location)) {
@@ -107,8 +112,8 @@ export function SecondaryStructureColorTheme(ctx: ThemeDataContext, props: PD.Va
props,
contextHash,
description: Description,
legend: TableLegend(Object.keys(SecondaryStructureColors).map(name => {
return [name, (SecondaryStructureColors as any)[name] as Color] as [string, Color];
legend: TableLegend(Object.keys(colorMap).map(name => {
return [name, (colorMap as any)[name] as Color] as [string, Color];
}).concat([['Other', DefaultSecondaryStructureColor]]))
};
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { objectForEach } from '../object';
import { ColorMap } from './color';
export function getColorMapParams<T extends { [k: string]: number }>(map: ColorMap<T>) {
const colors: Record<string, PD.Color> = {};
objectForEach(map, (_, k) => {
colors[k] = PD.Color(map[k]);
});
return colors as { [k in keyof T]: PD.Color };
}

View File

@@ -197,13 +197,13 @@ export function configureLocal() {
description: VOLUME_SERVER_HEADER
});
parser.add_argument('--jobs', { help: `Path to a JSON file with job specification.`, required: false });
parser.add_argument('--jobsTemplate', { help: 'Print example template for jobs.json and exit.', required: false, nargs: 0 });
parser.add_argument('--jobsTemplate', { help: 'Print example template for jobs.json and exit.', required: false, action: 'store_true' });
addJsonConfigArgs(parser);
addLimitsArgs(parser);
const config = parser.parse_args() as LimitsConfig & ServerJsonConfig;
if (config.cfgTemplate !== null) {
if (config.cfgTemplate) {
console.log(JSON.stringify(DefaultLimitsConfig, null, 2));
process.exit(0);
}
@@ -216,7 +216,7 @@ export function configureLocal() {
setLimitsConfig(cfg);
}
if (config.printCfg !== null) {
if (config.printCfg) {
console.log(JSON.stringify(LimitsConfig, null, 2));
process.exit(0);
}

View File

@@ -14,7 +14,7 @@ import * as LocalApi from './server/local-api';
const config = configureLocal();
if (config.jobsTemplate !== null) {
if (config.jobsTemplate) {
const exampleJobs: LocalApi.JobEntry[] = [{
source: {
filename: `g:/test/mdb/xray-1tqn.mdb`,