mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 05:44:23 +08:00
Compare commits
195 Commits
v3.0.0-dev
...
v3.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a83afa8c1 | ||
|
|
8891fa328b | ||
|
|
a23c06c456 | ||
|
|
34e87121e1 | ||
|
|
6e5c20f442 | ||
|
|
8ac3bec451 | ||
|
|
5128d0f405 | ||
|
|
6ae2121391 | ||
|
|
749e0c5a47 | ||
|
|
7b55ef85e1 | ||
|
|
23ec35d1f9 | ||
|
|
ff089c2b9f | ||
|
|
1ab088718a | ||
|
|
0cb2e5857a | ||
|
|
7c5ae5d7ee | ||
|
|
6e2665d98d | ||
|
|
b3b4692237 | ||
|
|
55ff1d4999 | ||
|
|
511c839237 | ||
|
|
384cd6e5d9 | ||
|
|
6fd9dcc72e | ||
|
|
12ca06fe91 | ||
|
|
652f6c651b | ||
|
|
7dd808a772 | ||
|
|
945e55f8a7 | ||
|
|
866a30abe5 | ||
|
|
f0e33e1e4e | ||
|
|
8723ca38b4 | ||
|
|
efffca0026 | ||
|
|
6c5eb3035f | ||
|
|
0bf385f2ca | ||
|
|
9d5f51f513 | ||
|
|
a1448131d8 | ||
|
|
664cacc7ac | ||
|
|
1aec37dd05 | ||
|
|
3ff2c0840e | ||
|
|
5ca3c3ac52 | ||
|
|
714ee50965 | ||
|
|
ae1df3c5aa | ||
|
|
28afb39550 | ||
|
|
3466a8a024 | ||
|
|
90db3321f5 | ||
|
|
bf4e5ed7c2 | ||
|
|
d3b2c20c26 | ||
|
|
c3afabb4b1 | ||
|
|
18cc9790d1 | ||
|
|
d5ed3aa674 | ||
|
|
cfb9c9acfe | ||
|
|
67feef0b1d | ||
|
|
e0192ab5aa | ||
|
|
eae7c11c55 | ||
|
|
b8251e1ade | ||
|
|
2ff2b9f348 | ||
|
|
164e3f3343 | ||
|
|
4901a1bd87 | ||
|
|
cd194cca65 | ||
|
|
1748efbc18 | ||
|
|
4d60b40403 | ||
|
|
6e5a41879f | ||
|
|
c5f9eb54da | ||
|
|
aebbfeb061 | ||
|
|
a0a5a6b578 | ||
|
|
6bdafb85d7 | ||
|
|
0dd7debf5d | ||
|
|
962b9ee7af | ||
|
|
15bcc5df88 | ||
|
|
8495d834c8 | ||
|
|
7282399709 | ||
|
|
780bdd6e7e | ||
|
|
ad08e7c67f | ||
|
|
ff089964ca | ||
|
|
990191529a | ||
|
|
ce1bec12b4 | ||
|
|
703ea9af53 | ||
|
|
df0227ae1e | ||
|
|
486d12b6ac | ||
|
|
9c18375ab4 | ||
|
|
e1708aed68 | ||
|
|
a42d778b84 | ||
|
|
7692b59c7c | ||
|
|
82de9b36b3 | ||
|
|
49b3c8f65f | ||
|
|
90ad32d936 | ||
|
|
2509e91f1a | ||
|
|
80dc2219e4 | ||
|
|
931cb0fa7d | ||
|
|
4383f2ea90 | ||
|
|
add79dc242 | ||
|
|
b6e142f04c | ||
|
|
105f6c3041 | ||
|
|
4babbb65c1 | ||
|
|
878159f7ed | ||
|
|
c320386019 | ||
|
|
1e7a0159f0 | ||
|
|
bbf4f1d1d3 | ||
|
|
8cd1c69c76 | ||
|
|
2372a878ac | ||
|
|
9bec644997 | ||
|
|
650d38dff8 | ||
|
|
097277e397 | ||
|
|
82a4d5eedf | ||
|
|
2a00248812 | ||
|
|
9841c773cb | ||
|
|
b21a78ad14 | ||
|
|
7329fa597d | ||
|
|
f1d8f0ecb4 | ||
|
|
1d127f2364 | ||
|
|
b244405cc3 | ||
|
|
38adfe0ca6 | ||
|
|
6f0d798847 | ||
|
|
6e58bfd2b0 | ||
|
|
bc2e8d8ac4 | ||
|
|
7be654d47f | ||
|
|
bfe46e3604 | ||
|
|
008b597fc5 | ||
|
|
c4b4f2e3b1 | ||
|
|
76ee97301b | ||
|
|
289dc09eae | ||
|
|
f23f84f0f3 | ||
|
|
62259f3295 | ||
|
|
854a430a12 | ||
|
|
d27cdb5637 | ||
|
|
7d12d9ee90 | ||
|
|
d70a4ff347 | ||
|
|
49541558d1 | ||
|
|
7b00a1227c | ||
|
|
7800603c81 | ||
|
|
fca00c8116 | ||
|
|
00fa549e44 | ||
|
|
36181b6b87 | ||
|
|
88a95162e9 | ||
|
|
9c1d59a2c8 | ||
|
|
fdd894956a | ||
|
|
eb6dc0859d | ||
|
|
b99026bba2 | ||
|
|
6fa50eb8d5 | ||
|
|
e5046f15a9 | ||
|
|
09f1c066a0 | ||
|
|
ed2f0b34c9 | ||
|
|
c7f75861de | ||
|
|
ccd04dbc9d | ||
|
|
e71f8d2c10 | ||
|
|
d9b4c60239 | ||
|
|
103c1fca21 | ||
|
|
49559bf5fb | ||
|
|
26dceabf83 | ||
|
|
abe506182e | ||
|
|
582a0e2a38 | ||
|
|
2784ccf379 | ||
|
|
0ad1d578fe | ||
|
|
31fd1c9c68 | ||
|
|
9c961297a2 | ||
|
|
b920053349 | ||
|
|
0a5c764e4a | ||
|
|
b9a71c83ff | ||
|
|
3255f207d0 | ||
|
|
e3b4ca8862 | ||
|
|
6810793015 | ||
|
|
1feb3c2095 | ||
|
|
f2da6033d0 | ||
|
|
28bc212132 | ||
|
|
1a7c62eec6 | ||
|
|
de67dbacba | ||
|
|
57223a0f9a | ||
|
|
2ad0754b90 | ||
|
|
3ecb3af57b | ||
|
|
ec4f15f549 | ||
|
|
2458ea7b92 | ||
|
|
c5e6bedf11 | ||
|
|
8528e5a666 | ||
|
|
6ed232b3d9 | ||
|
|
f8aae8cbd1 | ||
|
|
00c2517045 | ||
|
|
99b043a929 | ||
|
|
5900e27e39 | ||
|
|
1b79d34907 | ||
|
|
fc52e29c92 | ||
|
|
df23b3c0fe | ||
|
|
5e25716c98 | ||
|
|
f70a10bc56 | ||
|
|
0ccb045f4e | ||
|
|
fbb60c9493 | ||
|
|
9f953ef51c | ||
|
|
4871f1547c | ||
|
|
d6413529f4 | ||
|
|
b6847907ca | ||
|
|
fb54a1aed7 | ||
|
|
c0880b647f | ||
|
|
039dc6a76b | ||
|
|
042a7625ad | ||
|
|
41827c478d | ||
|
|
9a73180c3c | ||
|
|
333ee85fdb | ||
|
|
fa8ca45b6a | ||
|
|
c2bae1aeb7 |
104
CHANGELOG.md
104
CHANGELOG.md
@@ -6,6 +6,108 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
## [v3.5.0] - 2022-03-25
|
||||
|
||||
- Fix issues with bounding-sphere & color-smoothing (mostly for small geometries)
|
||||
- Support BCIF => CIF conversion in ``cif2bcif`` CLI tool
|
||||
|
||||
## [v3.4.0] - 2022-03-13
|
||||
|
||||
- Fix handling of mmcif with empty ``label_*`` fields
|
||||
- Improve saccharide detection (compare against list from CCD)
|
||||
- Fix legend label of hydrophobicity color theme
|
||||
- Add ``LoadTrajectory`` action
|
||||
- Add ``CustomImportControls`` to left panel
|
||||
- Add Zenodo import extension (load structures, trajectories, volumes, and zip files)
|
||||
- Fix loading of some compressed files within sessions
|
||||
- Fix wrong element assignment for atoms with Charmm ion names
|
||||
- Fix handling of empty symmetry cell data
|
||||
- Add support for ``trr`` and ``nctraj`` coordinates files
|
||||
- Add support for ``prmtop`` and ``top`` topology files
|
||||
|
||||
## [v3.3.1] - 2022-02-27
|
||||
|
||||
- Fix issue with unit boundary reuse (do at visual level instead)
|
||||
- Add option to ignore ions for inter-unit bond computation
|
||||
|
||||
## [v3.3.0] - 2022-02-27
|
||||
|
||||
- Fix parsing contour-level from emdb v3 header files
|
||||
- Fix invalid CSS (#376)
|
||||
- Fix "texture not renderable" & "texture not bound" warnings (#319)
|
||||
- Fix visual for bonds between two aromatic rings
|
||||
- Fix visual for delocalized bonds (parsed from mmcif and mol2)
|
||||
- Fix ring computation algorithm
|
||||
- Add ``UnitResonance`` property with info about delocalized triplets
|
||||
- Resolve marking in main renderer loop to improve overall performance
|
||||
- Use ``throttleTime`` instead of ``debounceTime`` in sequence viewer for better responsiveness
|
||||
- Change line geometry default ``scaleFactor`` to 2 (3 is too big after fixing line rendering)
|
||||
- Trajectory animation performance improvements
|
||||
- Reuse ``Model.CoarseGrained`` for coordinate trajectories
|
||||
- Avoid calculating ``InterUnitBonds`` when ``Structure.parent`` ones are empty
|
||||
- Reuse unit boundary if sphere has not changed too much
|
||||
- Don't show 'inter-bond' and 'element-cross' visuals in line representations of polymerAndLigand preset
|
||||
- Fix additional mononucleotides detected as polymer components
|
||||
- Fix and improve ``canRemap`` handling in ``IntraUnitBonds``
|
||||
- Reuse occlusion for secondary passes during multi-sampling
|
||||
- Check if marking passes are needed before doing them
|
||||
- Add ``resolutionScale`` parameter to allow trading quality of occlusion for performance
|
||||
|
||||
## [v3.2.0] - 2022-02-17
|
||||
|
||||
- Rename "best database mapping" to "SIFTS Mapping"
|
||||
- Add schema and export support for ``atom_site.pdbx_sifts_xref_*`` fields
|
||||
- Add schema export support for ``atom_site.pdbx_label_index`` field
|
||||
- Add `traceOnly` parameter to chain/UniProt-based structure alignment
|
||||
- Store ``IndexPairBonds`` as a dynamic property.
|
||||
|
||||
## [v3.1.0] - 2022-02-06
|
||||
|
||||
- Fix ``xrayShaded`` & ``ignoreLight`` params not working at the same time
|
||||
- Add ``ignoreLight`` to component params
|
||||
- Tweaks for cleaner default representation style
|
||||
- Cartoon: use ``nucleotide-ring`` instead of ``nucleotide-block``
|
||||
- Focus: use ``xrayShaded`` instead of opacity; adjust target size; don't show non-covalent interactions twice
|
||||
- Fix representation preset side effects (changing post-processing parameters, see #363)
|
||||
- Add Quick Styles panel (default, illustrative, stylized)
|
||||
- Fix exported structure missing secondary-structure categories (#364)
|
||||
- Fix volume streaming error message: distinguish between missing data and server error (#364)
|
||||
|
||||
## [v3.0.2] - 2022-01-30
|
||||
|
||||
- Fix color smoothing of elongated structures (by fixing ``Sphere.expand`` for spheres with highly directional extrema)
|
||||
- Fix entity label not displayed when multiple instances of the same entity are highlighted
|
||||
- Fix empty elements created in ``StructureElement.Loci.extendToAllInstances``
|
||||
- Measurement options tweaks (allow larger ``textSize``; make ``customText`` essential)
|
||||
- Fix visual visibility sync edge case when changing state snapshots
|
||||
|
||||
## [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``
|
||||
@@ -19,7 +121,7 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
- 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 instad of "chain-id"
|
||||
- Defaults to "entity-id" style instead of "chain-id"
|
||||
- Add "illustrative" representation preset
|
||||
|
||||
## [v3.0.0-dev.9] - 2022-01-09
|
||||
|
||||
72
CITATION.cff
Normal file
72
CITATION.cff
Normal file
@@ -0,0 +1,72 @@
|
||||
cff-version: 1.2.0
|
||||
title: >-
|
||||
Mol* library
|
||||
message: >-
|
||||
Please cite this software using the metadata from
|
||||
'preferred-citation'.
|
||||
authors:
|
||||
- given-names: Alexander S
|
||||
family-names: Rose
|
||||
orcid: 'https://orcid.org/0000-0002-0893-5551'
|
||||
- given-names: David
|
||||
family-names: Sehnal
|
||||
orcid: 'https://orcid.org/0000-0002-0682-3089'
|
||||
- given-names: Sebastian
|
||||
family-names: Bittrich
|
||||
orcid: 'https://orcid.org/0000-0003-3576-0387'
|
||||
- given-names: Áron Samuel
|
||||
family-names: Kovács
|
||||
- given-names: Ludovic
|
||||
family-names: Autin
|
||||
orcid: 'https://orcid.org/0000-0002-2197-191X'
|
||||
- given-names: Michal
|
||||
family-names: Malý
|
||||
- given-names: Jiří
|
||||
family-names: Černý
|
||||
- given-names: Panagiotis
|
||||
family-names: Tourlas
|
||||
type: software
|
||||
doi: 10.5281/zenodo.3947306
|
||||
preferred-citation:
|
||||
authors:
|
||||
- given-names: David
|
||||
family-names: Sehnal
|
||||
orcid: 'https://orcid.org/0000-0002-0682-3089'
|
||||
- given-names: Sebastian
|
||||
family-names: Bittrich
|
||||
orcid: 'https://orcid.org/0000-0003-3576-0387'
|
||||
- given-names: Mandar
|
||||
family-names: Deshpande
|
||||
orcid: 'https://orcid.org/0000-0002-9043-7665'
|
||||
- given-names: Radka
|
||||
family-names: Svobodová
|
||||
orcid: 'https://orcid.org/0000-0002-3840-8760'
|
||||
- given-names: Karel
|
||||
family-names: Berka
|
||||
orcid: 'https://orcid.org/0000-0001-9472-2589'
|
||||
- given-names: Václav
|
||||
family-names: Bazgier
|
||||
orcid: 'https://orcid.org/0000-0003-3393-3010'
|
||||
- given-names: Sameer
|
||||
family-names: Velankar
|
||||
orcid: 'https://orcid.org/0000-0002-8439-5964'
|
||||
- given-names: Stephen K
|
||||
family-names: Burley
|
||||
orcid: 'https://orcid.org/0000-0002-2487-9713'
|
||||
- given-names: Jaroslav
|
||||
family-names: Koča
|
||||
orcid: 'https://orcid.org/0000-0002-2780-4901'
|
||||
- given-names: Alexander S
|
||||
family-names: Rose
|
||||
orcid: 'https://orcid.org/0000-0002-0893-5551'
|
||||
title: >-
|
||||
Mol* Viewer: modern web app for 3D visualization
|
||||
and analysis of large biomolecular structures
|
||||
type: article
|
||||
doi: 10.1093/nar/gkab314
|
||||
journal: "Nucleic Acids Research"
|
||||
issue: W1
|
||||
volume: 49
|
||||
year: 2021
|
||||
month: 7
|
||||
pages: "W431–W437"
|
||||
10
README.md
10
README.md
@@ -120,6 +120,9 @@ and navigate to `build/viewer`
|
||||
|
||||
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-ions.js src/mol-model/structure/model/types/ions.ts
|
||||
|
||||
**Saccharide names**
|
||||
|
||||
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-saccharides.js src/mol-model/structure/model/types/saccharides.ts
|
||||
|
||||
**GraphQL schemas**
|
||||
|
||||
@@ -138,7 +141,7 @@ and navigate to `build/viewer`
|
||||
|
||||
export NODE_PATH="lib"; node build/state-docs
|
||||
|
||||
**Convert any CIF to BinaryCIF**
|
||||
**Convert any CIF to BinaryCIF (or vice versa)**
|
||||
|
||||
node lib/commonjs/servers/model/preprocess -i file.cif -ob file.bcif
|
||||
|
||||
@@ -148,6 +151,11 @@ Or
|
||||
|
||||
node lib/commonjs/cli/cif2bcif
|
||||
|
||||
E.g.
|
||||
|
||||
node lib/commonjs/cli/cif2bcif src.cif out.bcif.gz
|
||||
node lib/commonjs/cli/cif2bcif src.bcif.gz out.cif
|
||||
|
||||
## Development
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -24,6 +24,11 @@ atom_site.auth_asym_id
|
||||
atom_site.auth_seq_id
|
||||
atom_site.pdbx_PDB_model_num
|
||||
atom_site.ihm_model_id
|
||||
atom_site.pdbx_label_index
|
||||
atom_site.pdbx_sifts_xref_db_name
|
||||
atom_site.pdbx_sifts_xref_db_acc
|
||||
atom_site.pdbx_sifts_xref_db_num
|
||||
atom_site.pdbx_sifts_xref_db_res
|
||||
|
||||
atom_site_anisotrop.id
|
||||
atom_site_anisotrop.U
|
||||
|
||||
|
@@ -34,6 +34,14 @@
|
||||
* ACE (many, e.g. 5AGU, 1E1X)
|
||||
* ACY in 7ABY
|
||||
* NH2 (many, e.g. 6Y13)
|
||||
* Ligands with many rings
|
||||
* STU (e.g. 1U59) - many fused rings
|
||||
* HT (e.g. 127D) - rings connected by a single bond
|
||||
* J2C (e.g. 7EFJ) - rings connected by a single atom
|
||||
* RBF (e.g. 7QF2) - three linearly fused rings
|
||||
* TA1 (e.g. 1JFF) - many fused rings (incl. a 8-member rings)
|
||||
* BPA (e.g. 1JDG) - many fused rings
|
||||
* CLR (e.g. 3GKI) - four fused rings
|
||||
|
||||
Assembly symmetries
|
||||
* 5M30 (Assembly 1, C3 local and pseudo)
|
||||
|
||||
6738
package-lock.json
generated
6738
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
69
package.json
69
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.0.0-dev.10",
|
||||
"version": "3.5.0",
|
||||
"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",
|
||||
@@ -92,72 +92,69 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^3.1.1",
|
||||
"@graphql-codegen/cli": "^2.3.1",
|
||||
"@graphql-codegen/cli": "^2.6.2",
|
||||
"@graphql-codegen/time": "^3.1.1",
|
||||
"@graphql-codegen/typescript": "^2.4.2",
|
||||
"@graphql-codegen/typescript": "^2.4.7",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.3.3",
|
||||
"@graphql-codegen/typescript-operations": "^2.2.2",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.4.2",
|
||||
"@graphql-codegen/typescript-operations": "^2.3.4",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/gl": "^4.1.0",
|
||||
"@types/jest": "^27.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||
"@typescript-eslint/parser": "^5.9.1",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.14.0",
|
||||
"@typescript-eslint/parser": "^5.14.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^7.0.0",
|
||||
"cpx2": "^4.1.2",
|
||||
"cpx2": "^4.2.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.5.1",
|
||||
"eslint": "^8.7.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"eslint": "^8.11.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"graphql": "^16.2.0",
|
||||
"fs-extra": "^10.0.1",
|
||||
"graphql": "^16.3.0",
|
||||
"http-server": "^14.1.0",
|
||||
"jest": "^27.4.7",
|
||||
"mini-css-extract-plugin": "~2.4.7",
|
||||
"jest": "^27.5.1",
|
||||
"mini-css-extract-plugin": "^2.6.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass": "^1.48.0",
|
||||
"sass-loader": "^12.4.0",
|
||||
"simple-git": "^2.48.0",
|
||||
"sass": "^1.49.9",
|
||||
"sass-loader": "^12.6.0",
|
||||
"simple-git": "^3.3.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^27.1.3",
|
||||
"typescript": "^4.5.4",
|
||||
"webpack": "^5.66.0",
|
||||
"webpack-cli": "^4.9.1"
|
||||
"typescript": "^4.6.2",
|
||||
"webpack": "^5.70.0",
|
||||
"webpack-cli": "^4.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^2.0.10",
|
||||
"@types/benchmark": "^2.1.1",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^16.11.19",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/node": "^16.11.26",
|
||||
"@types/node-fetch": "^2.6.1",
|
||||
"@types/react": "^17.0.40",
|
||||
"@types/react-dom": "^17.0.13",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"argparse": "^2.0.1",
|
||||
"body-parser": "^1.19.1",
|
||||
"body-parser": "^1.19.2",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.2",
|
||||
"express": "^4.17.3",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^9.0.12",
|
||||
"immutable": "^4.0.0",
|
||||
"node-fetch": "^2.6.2",
|
||||
"rxjs": "^7.5.2",
|
||||
"swagger-ui-dist": "^4.1.3",
|
||||
"node-fetch": "^2.6.7",
|
||||
"rxjs": "^7.5.5",
|
||||
"swagger-ui-dist": "^4.6.2",
|
||||
"tslib": "^2.3.1",
|
||||
"util.promisify": "^1.1.1",
|
||||
"xhr2": "^0.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"gl": "^5.0.0"
|
||||
"react": "^17.0.2 || ^16.14.0",
|
||||
"react-dom": "^17.0.2 || ^16.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +44,11 @@ function occlusionStyle(plugin: PluginContext) {
|
||||
postprocessing: {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
occlusion: { name: 'on', params: {
|
||||
samples: 64,
|
||||
radius: 8,
|
||||
bias: 1.0,
|
||||
blurKernelSize: 13
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
radius: 5,
|
||||
samples: 32,
|
||||
resolutionScale: 1
|
||||
} },
|
||||
outline: { name: 'on', params: {
|
||||
scale: 1.0,
|
||||
|
||||
@@ -13,15 +13,19 @@ 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 { ZenodoImport } from '../../extensions/zenodo';
|
||||
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 { BuiltInTopologyFormat } from '../../mol-plugin-state/formats/topology';
|
||||
import { BuiltInCoordinatesFormat } from '../../mol-plugin-state/formats/coordinates';
|
||||
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';
|
||||
@@ -57,9 +61,11 @@ const Extensions = {
|
||||
'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),
|
||||
'zenodo-import': PluginSpec.Behavior(ZenodoImport),
|
||||
};
|
||||
|
||||
const DefaultViewerOptions = {
|
||||
@@ -99,7 +105,14 @@ export class Viewer {
|
||||
}
|
||||
|
||||
static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
|
||||
const o = { ...DefaultViewerOptions, ...options };
|
||||
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 = {
|
||||
@@ -357,11 +370,13 @@ export class Viewer {
|
||||
|
||||
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(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume)
|
||||
.to(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 } },
|
||||
typeParams: { alpha: iso.alpha ?? 1, isoValue: Volume.adjustedIsoValue(volumeData, iso.value, iso.type) },
|
||||
color: 'uniform',
|
||||
colorParams: { value: iso.color }
|
||||
}));
|
||||
@@ -441,11 +456,11 @@ export interface VolumeIsovalueInfo {
|
||||
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 },
|
||||
| { kind: 'topology-url', url: string, format: BuiltInTopologyFormat, isBinary?: boolean }
|
||||
| { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuiltInTopologyFormat },
|
||||
modelLabel?: string,
|
||||
coordinates: { kind: 'coordinates-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
|
||||
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
|
||||
coordinates: { kind: 'coordinates-url', url: string, format: BuiltInCoordinatesFormat, isBinary?: boolean }
|
||||
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuiltInCoordinatesFormat },
|
||||
coordinatesLabel?: string,
|
||||
preset?: keyof PresetTrajectoryHierarchy
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -9,4 +9,4 @@ import './embedded.html';
|
||||
import './favicon.ico';
|
||||
import './index.html';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
export * from './app';
|
||||
export * from './app';
|
||||
|
||||
77
src/cli/chem-comp-dict/create-saccharides.ts
Normal file
77
src/cli/chem-comp-dict/create-saccharides.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as argparse from 'argparse';
|
||||
import * as path from 'path';
|
||||
import util from 'util';
|
||||
import fs from 'fs';
|
||||
require('util.promisify').shim();
|
||||
const writeFile = util.promisify(fs.writeFile);
|
||||
|
||||
import { DatabaseCollection } from '../../mol-data/db';
|
||||
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
|
||||
import { ensureDataAvailable, readCCD } from './util';
|
||||
|
||||
function extractSaccharideNames(ccd: DatabaseCollection<CCD_Schema>) {
|
||||
const saccharideNames: string[] = [];
|
||||
for (const k in ccd) {
|
||||
const { chem_comp } = ccd[k];
|
||||
const type = chem_comp.type.value(0).toUpperCase();
|
||||
if (type.includes('SACCHARIDE')) {
|
||||
saccharideNames.push(chem_comp.id.value(0));
|
||||
}
|
||||
}
|
||||
// these are extra saccharides that don't have SACCHARIDE in their type
|
||||
saccharideNames.push(
|
||||
'UMQ', // UNDECYL-MALTOSIDE, via GlyFinder
|
||||
'SQD', // SULFOQUINOVOSYLDIACYLGLYCEROL, via GlyFinder
|
||||
);
|
||||
return saccharideNames;
|
||||
}
|
||||
|
||||
function writeSaccharideNamesFile(filePath: string, ionNames: string[]) {
|
||||
const output = `/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated ion names params file. Names extracted from CCD components.
|
||||
*
|
||||
* @author molstar/cli/chem-comp-dict/create-saccharides
|
||||
*/
|
||||
|
||||
export const SaccharideNames = new Set(${JSON.stringify(ionNames).replace(/"/g, "'").replace(/,/g, ', ')});
|
||||
`;
|
||||
writeFile(filePath, output);
|
||||
}
|
||||
|
||||
async function run(out: string, forceDownload = false) {
|
||||
await ensureDataAvailable(forceDownload);
|
||||
const ccd = await readCCD();
|
||||
const saccharideNames = extractSaccharideNames(ccd);
|
||||
if (!fs.existsSync(path.dirname(out))) {
|
||||
fs.mkdirSync(path.dirname(out));
|
||||
}
|
||||
writeSaccharideNamesFile(out, saccharideNames);
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
add_help: true,
|
||||
description: 'Extract and save SaccharideNames from CCD.'
|
||||
});
|
||||
parser.add_argument('out', {
|
||||
help: 'Generated file output path.'
|
||||
});
|
||||
parser.add_argument('--forceDownload', '-f', {
|
||||
action: 'store_true',
|
||||
help: 'Force download of CCD and PVCD.'
|
||||
});
|
||||
interface Args {
|
||||
out: string,
|
||||
forceDownload?: boolean,
|
||||
}
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
run(args.out, args.forceDownload);
|
||||
@@ -71,7 +71,7 @@ function classify(name: string, field: CifField): CifWriter.Field {
|
||||
}
|
||||
|
||||
export function convert(path: string, asText = false, hints?: EncodingStrategyHint[], filter?: string) {
|
||||
return Task.create<Uint8Array>('BinaryCIF', async ctx => {
|
||||
return Task.create<Uint8Array>('Convert CIF', async ctx => {
|
||||
const encodingProvider: BinaryEncodingProvider = hints
|
||||
? CifWriter.createEncodingProviderFromJsonConfig(hints)
|
||||
: { get: (c, f) => void 0 };
|
||||
|
||||
@@ -18,7 +18,7 @@ async function process(srcPath: string, outPath: string, configPath?: string, fi
|
||||
const config = configPath ? JSON.parse(fs.readFileSync(configPath, 'utf8')) : void 0;
|
||||
const filter = filterPath ? fs.readFileSync(filterPath, 'utf8') : void 0;
|
||||
|
||||
const res = await convert(srcPath, false, config, filter);
|
||||
const res = await convert(srcPath, srcPath.toLowerCase().indexOf('.bcif') > 0, config, filter);
|
||||
await write(outPath, res);
|
||||
}
|
||||
|
||||
@@ -38,13 +38,13 @@ function run(args: Args) {
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
add_help: true,
|
||||
description: 'Convert any CIF file to a BCIF file'
|
||||
description: 'Convert any BCIF file to a CIF file or vice versa'
|
||||
});
|
||||
parser.add_argument('src', {
|
||||
help: 'Source CIF path'
|
||||
help: 'Source file path'
|
||||
});
|
||||
parser.add_argument('out', {
|
||||
help: 'Output BCIF path'
|
||||
help: 'Output file path'
|
||||
});
|
||||
parser.add_argument('-c', '--config', {
|
||||
help: 'Optional encoding strategy/precision config path',
|
||||
|
||||
@@ -24,7 +24,7 @@ const Canvas3DPresets = {
|
||||
illustrative: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
|
||||
outline: { name: 'on', params: { scale: 1, threshold: 0.33, color: Color(0x000000) } }
|
||||
},
|
||||
renderer: {
|
||||
@@ -36,7 +36,7 @@ const Canvas3DPresets = {
|
||||
occlusion: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
|
||||
outline: { name: 'off', params: {} }
|
||||
},
|
||||
renderer: {
|
||||
|
||||
@@ -32,6 +32,7 @@ import { Color } from '../../mol-util/color';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
import { readFromFile } from '../../mol-util/data-source';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { createBasic } from '../../mol-model-formats/structure/basic/schema';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/results/${fileName}`;
|
||||
@@ -310,7 +311,8 @@ async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredi
|
||||
const cif = getCifCurve(name, transforms, model);
|
||||
const curveModelTask = Task.create('Curve Model', async ctx => {
|
||||
const format = MmcifFormat.fromFrame(cif);
|
||||
const models = await createModels(format.data.db, format, ctx);
|
||||
const basic = createBasic(format.data.db, true);
|
||||
const models = await createModels(basic, format, ctx);
|
||||
return models.representative;
|
||||
});
|
||||
|
||||
|
||||
@@ -11,149 +11,248 @@ import { Location } from '../../../mol-model/location';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { ColorTheme } from '../../../mol-theme/color';
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { Color, ColorMap } from '../../../mol-util/color';
|
||||
import { getColorMapParams } from '../../../mol-util/color/params';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { TableLegend } from '../../../mol-util/legend';
|
||||
import { iterableToArray } from '../../../mol-data/util';
|
||||
import { ObjectKeys } from '../../../mol-util/type-helpers';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
const Description = 'Assigns colors to confal pyramids';
|
||||
|
||||
const DefaultClassColors = {
|
||||
A: 0xFFC1C1,
|
||||
B: 0xC8CFFF,
|
||||
BII: 0x0059DA,
|
||||
miB: 0x3BE8FB,
|
||||
Z: 0x01F60E,
|
||||
IC: 0xFA5CFB,
|
||||
OPN: 0xE90000,
|
||||
SYN: 0xFFFF01,
|
||||
N: 0xF2F2F2,
|
||||
};
|
||||
const ErrorColor = Color(0xFFA10A);
|
||||
|
||||
type ConformerClasses = 'A' | 'B' | 'BII' | 'miB' | 'Z' | 'IC' | 'OPN' | 'SYN' | 'N';
|
||||
const PyramidsColors = ColorMap({
|
||||
NANT_Upr: DefaultClassColors.N,
|
||||
NANT_Lwr: DefaultClassColors.N,
|
||||
AA00_Upr: DefaultClassColors.A,
|
||||
AA00_Lwr: DefaultClassColors.A,
|
||||
AA02_Upr: DefaultClassColors.A,
|
||||
AA02_Lwr: DefaultClassColors.A,
|
||||
AA03_Upr: DefaultClassColors.A,
|
||||
AA03_Lwr: DefaultClassColors.A,
|
||||
AA04_Upr: DefaultClassColors.A,
|
||||
AA04_Lwr: DefaultClassColors.A,
|
||||
AA08_Upr: DefaultClassColors.A,
|
||||
AA08_Lwr: DefaultClassColors.A,
|
||||
AA09_Upr: DefaultClassColors.A,
|
||||
AA09_Lwr: DefaultClassColors.A,
|
||||
AA01_Upr: DefaultClassColors.A,
|
||||
AA01_Lwr: DefaultClassColors.A,
|
||||
AA05_Upr: DefaultClassColors.A,
|
||||
AA05_Lwr: DefaultClassColors.A,
|
||||
AA06_Upr: DefaultClassColors.A,
|
||||
AA06_Lwr: DefaultClassColors.A,
|
||||
AA10_Upr: DefaultClassColors.A,
|
||||
AA10_Lwr: DefaultClassColors.A,
|
||||
AA11_Upr: DefaultClassColors.A,
|
||||
AA11_Lwr: DefaultClassColors.A,
|
||||
AA07_Upr: DefaultClassColors.A,
|
||||
AA07_Lwr: DefaultClassColors.A,
|
||||
AA12_Upr: DefaultClassColors.A,
|
||||
AA12_Lwr: DefaultClassColors.A,
|
||||
AA13_Upr: DefaultClassColors.A,
|
||||
AA13_Lwr: DefaultClassColors.A,
|
||||
AB01_Upr: DefaultClassColors.A,
|
||||
AB01_Lwr: DefaultClassColors.B,
|
||||
AB02_Upr: DefaultClassColors.A,
|
||||
AB02_Lwr: DefaultClassColors.B,
|
||||
AB03_Upr: DefaultClassColors.A,
|
||||
AB03_Lwr: DefaultClassColors.B,
|
||||
AB04_Upr: DefaultClassColors.A,
|
||||
AB04_Lwr: DefaultClassColors.B,
|
||||
AB05_Upr: DefaultClassColors.A,
|
||||
AB05_Lwr: DefaultClassColors.B,
|
||||
BA01_Upr: DefaultClassColors.B,
|
||||
BA01_Lwr: DefaultClassColors.A,
|
||||
BA05_Upr: DefaultClassColors.B,
|
||||
BA05_Lwr: DefaultClassColors.A,
|
||||
BA09_Upr: DefaultClassColors.B,
|
||||
BA09_Lwr: DefaultClassColors.A,
|
||||
BA08_Upr: DefaultClassColors.BII,
|
||||
BA08_Lwr: DefaultClassColors.A,
|
||||
BA10_Upr: DefaultClassColors.B,
|
||||
BA10_Lwr: DefaultClassColors.A,
|
||||
BA13_Upr: DefaultClassColors.BII,
|
||||
BA13_Lwr: DefaultClassColors.A,
|
||||
BA16_Upr: DefaultClassColors.BII,
|
||||
BA16_Lwr: DefaultClassColors.A,
|
||||
BA17_Upr: DefaultClassColors.BII,
|
||||
BA17_Lwr: DefaultClassColors.A,
|
||||
BB00_Upr: DefaultClassColors.B,
|
||||
BB00_Lwr: DefaultClassColors.B,
|
||||
BB01_Upr: DefaultClassColors.B,
|
||||
BB01_Lwr: DefaultClassColors.B,
|
||||
BB17_Upr: DefaultClassColors.B,
|
||||
BB17_Lwr: DefaultClassColors.B,
|
||||
BB02_Upr: DefaultClassColors.B,
|
||||
BB02_Lwr: DefaultClassColors.B,
|
||||
BB03_Upr: DefaultClassColors.B,
|
||||
BB03_Lwr: DefaultClassColors.B,
|
||||
BB11_Upr: DefaultClassColors.B,
|
||||
BB11_Lwr: DefaultClassColors.B,
|
||||
BB16_Upr: DefaultClassColors.B,
|
||||
BB16_Lwr: DefaultClassColors.B,
|
||||
BB04_Upr: DefaultClassColors.B,
|
||||
BB04_Lwr: DefaultClassColors.BII,
|
||||
BB05_Upr: DefaultClassColors.B,
|
||||
BB05_Lwr: DefaultClassColors.BII,
|
||||
BB07_Upr: DefaultClassColors.BII,
|
||||
BB07_Lwr: DefaultClassColors.BII,
|
||||
BB08_Upr: DefaultClassColors.BII,
|
||||
BB08_Lwr: DefaultClassColors.BII,
|
||||
BB10_Upr: DefaultClassColors.miB,
|
||||
BB10_Lwr: DefaultClassColors.miB,
|
||||
BB12_Upr: DefaultClassColors.miB,
|
||||
BB12_Lwr: DefaultClassColors.miB,
|
||||
BB13_Upr: DefaultClassColors.miB,
|
||||
BB13_Lwr: DefaultClassColors.miB,
|
||||
BB14_Upr: DefaultClassColors.miB,
|
||||
BB14_Lwr: DefaultClassColors.miB,
|
||||
BB15_Upr: DefaultClassColors.miB,
|
||||
BB15_Lwr: DefaultClassColors.miB,
|
||||
BB20_Upr: DefaultClassColors.miB,
|
||||
BB20_Lwr: DefaultClassColors.miB,
|
||||
IC01_Upr: DefaultClassColors.IC,
|
||||
IC01_Lwr: DefaultClassColors.IC,
|
||||
IC02_Upr: DefaultClassColors.IC,
|
||||
IC02_Lwr: DefaultClassColors.IC,
|
||||
IC03_Upr: DefaultClassColors.IC,
|
||||
IC03_Lwr: DefaultClassColors.IC,
|
||||
IC04_Upr: DefaultClassColors.IC,
|
||||
IC04_Lwr: DefaultClassColors.IC,
|
||||
IC05_Upr: DefaultClassColors.IC,
|
||||
IC05_Lwr: DefaultClassColors.IC,
|
||||
IC06_Upr: DefaultClassColors.IC,
|
||||
IC06_Lwr: DefaultClassColors.IC,
|
||||
IC07_Upr: DefaultClassColors.IC,
|
||||
IC07_Lwr: DefaultClassColors.IC,
|
||||
OP01_Upr: DefaultClassColors.OPN,
|
||||
OP01_Lwr: DefaultClassColors.OPN,
|
||||
OP02_Upr: DefaultClassColors.OPN,
|
||||
OP02_Lwr: DefaultClassColors.OPN,
|
||||
OP03_Upr: DefaultClassColors.OPN,
|
||||
OP03_Lwr: DefaultClassColors.OPN,
|
||||
OP04_Upr: DefaultClassColors.OPN,
|
||||
OP04_Lwr: DefaultClassColors.OPN,
|
||||
OP05_Upr: DefaultClassColors.OPN,
|
||||
OP05_Lwr: DefaultClassColors.OPN,
|
||||
OP06_Upr: DefaultClassColors.OPN,
|
||||
OP06_Lwr: DefaultClassColors.OPN,
|
||||
OP07_Upr: DefaultClassColors.OPN,
|
||||
OP07_Lwr: DefaultClassColors.OPN,
|
||||
OP08_Upr: DefaultClassColors.OPN,
|
||||
OP08_Lwr: DefaultClassColors.OPN,
|
||||
OP09_Upr: DefaultClassColors.OPN,
|
||||
OP09_Lwr: DefaultClassColors.OPN,
|
||||
OP10_Upr: DefaultClassColors.OPN,
|
||||
OP10_Lwr: DefaultClassColors.OPN,
|
||||
OP11_Upr: DefaultClassColors.OPN,
|
||||
OP11_Lwr: DefaultClassColors.OPN,
|
||||
OP12_Upr: DefaultClassColors.OPN,
|
||||
OP12_Lwr: DefaultClassColors.OPN,
|
||||
OP13_Upr: DefaultClassColors.OPN,
|
||||
OP13_Lwr: DefaultClassColors.OPN,
|
||||
OP14_Upr: DefaultClassColors.OPN,
|
||||
OP14_Lwr: DefaultClassColors.OPN,
|
||||
OP15_Upr: DefaultClassColors.OPN,
|
||||
OP15_Lwr: DefaultClassColors.OPN,
|
||||
OP16_Upr: DefaultClassColors.OPN,
|
||||
OP16_Lwr: DefaultClassColors.OPN,
|
||||
OP17_Upr: DefaultClassColors.OPN,
|
||||
OP17_Lwr: DefaultClassColors.OPN,
|
||||
OP18_Upr: DefaultClassColors.OPN,
|
||||
OP18_Lwr: DefaultClassColors.OPN,
|
||||
OP19_Upr: DefaultClassColors.OPN,
|
||||
OP19_Lwr: DefaultClassColors.OPN,
|
||||
OP20_Upr: DefaultClassColors.OPN,
|
||||
OP20_Lwr: DefaultClassColors.OPN,
|
||||
OP21_Upr: DefaultClassColors.OPN,
|
||||
OP21_Lwr: DefaultClassColors.OPN,
|
||||
OP22_Upr: DefaultClassColors.OPN,
|
||||
OP22_Lwr: DefaultClassColors.OPN,
|
||||
OP23_Upr: DefaultClassColors.OPN,
|
||||
OP23_Lwr: DefaultClassColors.OPN,
|
||||
OP24_Upr: DefaultClassColors.OPN,
|
||||
OP24_Lwr: DefaultClassColors.OPN,
|
||||
OP25_Upr: DefaultClassColors.OPN,
|
||||
OP25_Lwr: DefaultClassColors.OPN,
|
||||
OP26_Upr: DefaultClassColors.OPN,
|
||||
OP26_Lwr: DefaultClassColors.OPN,
|
||||
OP27_Upr: DefaultClassColors.OPN,
|
||||
OP27_Lwr: DefaultClassColors.OPN,
|
||||
OP28_Upr: DefaultClassColors.OPN,
|
||||
OP28_Lwr: DefaultClassColors.OPN,
|
||||
OP29_Upr: DefaultClassColors.OPN,
|
||||
OP29_Lwr: DefaultClassColors.OPN,
|
||||
OP30_Upr: DefaultClassColors.OPN,
|
||||
OP30_Lwr: DefaultClassColors.OPN,
|
||||
OP31_Upr: DefaultClassColors.OPN,
|
||||
OP31_Lwr: DefaultClassColors.OPN,
|
||||
OPS1_Upr: DefaultClassColors.OPN,
|
||||
OPS1_Lwr: DefaultClassColors.OPN,
|
||||
OP1S_Upr: DefaultClassColors.OPN,
|
||||
OP1S_Lwr: DefaultClassColors.SYN,
|
||||
AAS1_Upr: DefaultClassColors.SYN,
|
||||
AAS1_Lwr: DefaultClassColors.A,
|
||||
AB1S_Upr: DefaultClassColors.A,
|
||||
AB1S_Lwr: DefaultClassColors.SYN,
|
||||
AB2S_Upr: DefaultClassColors.A,
|
||||
AB2S_Lwr: DefaultClassColors.SYN,
|
||||
BB1S_Upr: DefaultClassColors.B,
|
||||
BB1S_Lwr: DefaultClassColors.SYN,
|
||||
BB2S_Upr: DefaultClassColors.B,
|
||||
BB2S_Lwr: DefaultClassColors.SYN,
|
||||
BBS1_Upr: DefaultClassColors.SYN,
|
||||
BBS1_Lwr: DefaultClassColors.B,
|
||||
ZZ01_Upr: DefaultClassColors.Z,
|
||||
ZZ01_Lwr: DefaultClassColors.Z,
|
||||
ZZ02_Upr: DefaultClassColors.Z,
|
||||
ZZ02_Lwr: DefaultClassColors.Z,
|
||||
ZZ1S_Upr: DefaultClassColors.Z,
|
||||
ZZ1S_Lwr: DefaultClassColors.SYN,
|
||||
ZZ2S_Upr: DefaultClassColors.Z,
|
||||
ZZ2S_Lwr: DefaultClassColors.SYN,
|
||||
ZZS1_Upr: DefaultClassColors.SYN,
|
||||
ZZS1_Lwr: DefaultClassColors.Z,
|
||||
ZZS2_Upr: DefaultClassColors.SYN,
|
||||
ZZS2_Lwr: DefaultClassColors.Z,
|
||||
});
|
||||
type PyramidsColors = typeof PyramidsColors;
|
||||
|
||||
const ColorMapping: ReadonlyMap<ConformerClasses, Color> = new Map([
|
||||
['A', Color(0xFFC1C1)],
|
||||
['B', Color(0xC8CFFF)],
|
||||
['BII', Color(0x0059DA)],
|
||||
['miB', Color(0x3BE8FB)],
|
||||
['Z', Color(0x01F60E)],
|
||||
['IC', Color(0xFA5CFB)],
|
||||
['OPN', Color(0xE90000)],
|
||||
['SYN', Color(0xFFFF01)],
|
||||
['N', Color(0xF2F2F2)],
|
||||
]);
|
||||
export const ConfalPyramidsColorThemeParams = {
|
||||
colors: PD.MappedStatic('default', {
|
||||
'default': PD.EmptyGroup(),
|
||||
'custom': PD.Group(getColorMapParams(PyramidsColors))
|
||||
}),
|
||||
};
|
||||
export type ConfalPyramidsColorThemeParams = typeof ConfalPyramidsColorThemeParams;
|
||||
|
||||
const NtCToClasses: ReadonlyMap<string, [ConformerClasses, ConformerClasses]> = new Map([
|
||||
['NANT', ['N', 'N']],
|
||||
['AA00', ['A', 'A']],
|
||||
['AA02', ['A', 'A']],
|
||||
['AA03', ['A', 'A']],
|
||||
['AA04', ['A', 'A']],
|
||||
['AA08', ['A', 'A']],
|
||||
['AA09', ['A', 'A']],
|
||||
['AA01', ['A', 'A']],
|
||||
['AA05', ['A', 'A']],
|
||||
['AA06', ['A', 'A']],
|
||||
['AA10', ['A', 'A']],
|
||||
['AA11', ['A', 'A']],
|
||||
['AA07', ['A', 'A']],
|
||||
['AA12', ['A', 'A']],
|
||||
['AA13', ['A', 'A']],
|
||||
['AB01', ['A', 'B']],
|
||||
['AB02', ['A', 'B']],
|
||||
['AB03', ['A', 'B']],
|
||||
['AB04', ['A', 'B']],
|
||||
['AB05', ['A', 'B']],
|
||||
['BA01', ['B', 'A']],
|
||||
['BA05', ['B', 'A']],
|
||||
['BA09', ['B', 'A']],
|
||||
['BA08', ['BII', 'A']],
|
||||
['BA10', ['B', 'A']],
|
||||
['BA13', ['BII', 'A']],
|
||||
['BA16', ['BII', 'A']],
|
||||
['BA17', ['BII', 'A']],
|
||||
['BB00', ['B', 'B']],
|
||||
['BB01', ['B', 'B']],
|
||||
['BB17', ['B', 'B']],
|
||||
['BB02', ['B', 'B']],
|
||||
['BB03', ['B', 'B']],
|
||||
['BB11', ['B', 'B']],
|
||||
['BB16', ['B', 'B']],
|
||||
['BB04', ['B', 'BII']],
|
||||
['BB05', ['B', 'BII']],
|
||||
['BB07', ['BII', 'BII']],
|
||||
['BB08', ['BII', 'BII']],
|
||||
['BB10', ['miB', 'miB']],
|
||||
['BB12', ['miB', 'miB']],
|
||||
['BB13', ['miB', 'miB']],
|
||||
['BB14', ['miB', 'miB']],
|
||||
['BB15', ['miB', 'miB']],
|
||||
['BB20', ['miB', 'miB']],
|
||||
['IC01', ['IC', 'IC']],
|
||||
['IC02', ['IC', 'IC']],
|
||||
['IC03', ['IC', 'IC']],
|
||||
['IC04', ['IC', 'IC']],
|
||||
['IC05', ['IC', 'IC']],
|
||||
['IC06', ['IC', 'IC']],
|
||||
['IC07', ['IC', 'IC']],
|
||||
['OP01', ['OPN', 'OPN']],
|
||||
['OP02', ['OPN', 'OPN']],
|
||||
['OP03', ['OPN', 'OPN']],
|
||||
['OP04', ['OPN', 'OPN']],
|
||||
['OP05', ['OPN', 'OPN']],
|
||||
['OP06', ['OPN', 'OPN']],
|
||||
['OP07', ['OPN', 'OPN']],
|
||||
['OP08', ['OPN', 'OPN']],
|
||||
['OP09', ['OPN', 'OPN']],
|
||||
['OP10', ['OPN', 'OPN']],
|
||||
['OP11', ['OPN', 'OPN']],
|
||||
['OP12', ['OPN', 'OPN']],
|
||||
['OP13', ['OPN', 'OPN']],
|
||||
['OP14', ['OPN', 'OPN']],
|
||||
['OP15', ['OPN', 'OPN']],
|
||||
['OP16', ['OPN', 'OPN']],
|
||||
['OP17', ['OPN', 'OPN']],
|
||||
['OP18', ['OPN', 'OPN']],
|
||||
['OP19', ['OPN', 'OPN']],
|
||||
['OP20', ['OPN', 'OPN']],
|
||||
['OP21', ['OPN', 'OPN']],
|
||||
['OP22', ['OPN', 'OPN']],
|
||||
['OP23', ['OPN', 'OPN']],
|
||||
['OP24', ['OPN', 'OPN']],
|
||||
['OP25', ['OPN', 'OPN']],
|
||||
['OP26', ['OPN', 'OPN']],
|
||||
['OP27', ['OPN', 'OPN']],
|
||||
['OP28', ['OPN', 'OPN']],
|
||||
['OP29', ['OPN', 'OPN']],
|
||||
['OP30', ['OPN', 'OPN']],
|
||||
['OP31', ['OPN', 'OPN']],
|
||||
['OPS1', ['OPN', 'OPN']],
|
||||
['OP1S', ['OPN', 'SYN']],
|
||||
['AAS1', ['SYN', 'A']],
|
||||
['AB1S', ['A', 'SYN']],
|
||||
['AB2S', ['A', 'SYN']],
|
||||
['BB1S', ['B', 'SYN']],
|
||||
['BB2S', ['B', 'SYN']],
|
||||
['BBS1', ['SYN', 'B']],
|
||||
['ZZ01', ['Z', 'Z']],
|
||||
['ZZ02', ['Z', 'Z']],
|
||||
['ZZ1S', ['Z', 'SYN']],
|
||||
['ZZ2S', ['Z', 'SYN']],
|
||||
['ZZS1', ['SYN', 'Z']],
|
||||
['ZZS2', ['SYN', 'Z']],
|
||||
]);
|
||||
|
||||
function getConformerColor(ntc: string, useLower: boolean): Color {
|
||||
const item = NtCToClasses.get(ntc);
|
||||
if (!item) return ErrorColor;
|
||||
return ColorMapping.get(useLower ? item[1] : item[0]) ?? ErrorColor;
|
||||
}
|
||||
|
||||
export const ConfalPyramidsColorThemeParams = {};
|
||||
export type ConfalPyramidsColorThemeParams = typeof ConfalPyramidsColorThemeParams
|
||||
export function getConfalPyramidsColorThemeParams(ctx: ThemeDataContext) {
|
||||
return ConfalPyramidsColorThemeParams; // TODO return copy
|
||||
return PD.clone(ConfalPyramidsColorThemeParams);
|
||||
}
|
||||
|
||||
export function ConfalPyramidsColorTheme(ctx: ThemeDataContext, props: PD.Values<ConfalPyramidsColorThemeParams>): ColorTheme<ConfalPyramidsColorThemeParams> {
|
||||
const colorMap = props.colors.name === 'default' ? PyramidsColors : props.colors.params;
|
||||
|
||||
function color(location: Location, isSecondary: boolean): Color {
|
||||
if (CPT.isLocation(location)) {
|
||||
const { pyramid, isLower } = location.data;
|
||||
return getConformerColor(pyramid.NtC, isLower);
|
||||
const key = pyramid.NtC + `_${isLower ? 'Lwr' : 'Upr'}` as keyof PyramidsColors;
|
||||
return colorMap[key] ?? ErrorColor;
|
||||
}
|
||||
|
||||
return DefaultColor;
|
||||
return ErrorColor;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -162,12 +261,7 @@ export function ConfalPyramidsColorTheme(ctx: ThemeDataContext, props: PD.Values
|
||||
color,
|
||||
props,
|
||||
description: Description,
|
||||
legend: TableLegend(iterableToArray(ColorMapping.entries()).map(([conformer, color]) => {
|
||||
return [conformer, color] as [string, Color];
|
||||
}).concat([
|
||||
['Error', ErrorColor],
|
||||
['Unknown', DefaultColor]
|
||||
]))
|
||||
legend: TableLegend(ObjectKeys(colorMap).map(k => [k.replace('_', ' '), colorMap[k]] as [string, Color]).concat([['Error', ErrorColor]])),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -123,10 +123,8 @@ function createPyramidsFromCif(model: Model,
|
||||
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
const model_num = PDB_model_number.value(i);
|
||||
if (model_num !== model.modelNum) {
|
||||
if (model_num !== model.modelNum)
|
||||
hasMultipleModels = true;
|
||||
continue; // We are only interested in data for the current model
|
||||
}
|
||||
|
||||
const { _NtC, _confal_score } = getNtCAndConfalScore(id.value(i), i, stepsSummary);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { ConfalPyramidsProvider } from './property';
|
||||
import { ConfalPyramidsTypes as CPT } from './types';
|
||||
import { OrderedSet, Segmentation } from '../../../mol-data/int';
|
||||
import { Segmentation } from '../../../mol-data/int';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { ChainIndex, ElementIndex, ResidueIndex, Structure, StructureElement, StructureProperties, Unit } from '../../../mol-model/structure';
|
||||
|
||||
@@ -63,15 +63,12 @@ export namespace ConfalPyramidsUtil {
|
||||
return prop.data.hasMultipleModels;
|
||||
}
|
||||
|
||||
function getPossibleAltIdsIndices(eIFirst: ElementIndex, eILast: ElementIndex, structure: Structure, unit: Unit.Atomic): string[] {
|
||||
const loc = StructureElement.Location.create(structure, unit, -1 as ElementIndex);
|
||||
|
||||
const uIFirst = OrderedSet.indexOf(unit.elements, eIFirst);
|
||||
const uILast = OrderedSet.indexOf(unit.elements, eILast);
|
||||
|
||||
function getPossibleAltIds(residue: Residue, structure: Structure, unit: Unit.Atomic): string[] {
|
||||
const possibleAltIds: string[] = [];
|
||||
for (let uI = uIFirst; uI <= uILast; uI++) {
|
||||
loc.element = unit.elements[uI];
|
||||
|
||||
const loc = StructureElement.Location.create(structure, unit, -1 as ElementIndex);
|
||||
for (let rI = residue.start; rI <= residue.end - 1; rI++) {
|
||||
loc.element = unit.elements[rI];
|
||||
const altId = StructureProperties.atom.label_alt_id(loc);
|
||||
if (altId !== '' && !possibleAltIds.includes(altId)) possibleAltIds.push(altId);
|
||||
}
|
||||
@@ -79,10 +76,6 @@ export namespace ConfalPyramidsUtil {
|
||||
return possibleAltIds;
|
||||
}
|
||||
|
||||
function getPossibleAltIdsResidue(residue: Residue, structure: Structure, unit: Unit.Atomic): string[] {
|
||||
return getPossibleAltIdsIndices(unit.elements[residue.start], unit.elements[residue.end - 1], structure, unit);
|
||||
}
|
||||
|
||||
class Utility {
|
||||
protected getPyramidByName(name: string): { pyramid: CPT.Pyramid | undefined, index: number } {
|
||||
const index = this.data.names.get(name);
|
||||
@@ -122,19 +115,22 @@ export namespace ConfalPyramidsUtil {
|
||||
|
||||
export class UnitWalker extends Utility {
|
||||
private getAtomIndices(names: string[], residue: Residue): ElementIndex[] {
|
||||
let rI = residue.start;
|
||||
const rILast = residue.end - 1;
|
||||
const indices: ElementIndex[] = [];
|
||||
|
||||
for (; rI !== rILast; rI++) {
|
||||
const eI = this.unit.elements[rI];
|
||||
const loc = StructureElement.Location.create(this.structure, this.unit, eI);
|
||||
const loc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex);
|
||||
for (let rI = residue.start; rI <= residue.end - 1; rI++) {
|
||||
loc.element = this.unit.elements[rI];
|
||||
const thisName = StructureProperties.atom.label_atom_id(loc);
|
||||
if (names.includes(thisName)) indices.push(eI);
|
||||
if (names.includes(thisName)) indices.push(loc.element);
|
||||
}
|
||||
|
||||
if (indices.length === 0)
|
||||
throw new Error(`Element ${name} not found on residue ${residue.index}`);
|
||||
if (indices.length === 0) {
|
||||
let namesStr = '';
|
||||
for (const n of names)
|
||||
namesStr += `${n} `;
|
||||
|
||||
throw new Error(`Element [${namesStr}] not found on residue ${residue.index}`);
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
@@ -257,12 +253,12 @@ export namespace ConfalPyramidsUtil {
|
||||
}
|
||||
|
||||
private step(residue: Residue): { firstAtoms: FirstResidueAtoms[], secondAtoms: SecondResidueAtoms[] } {
|
||||
const firstPossibleAltIds = getPossibleAltIdsResidue(residue, this.structure, this.unit);
|
||||
const firstPossibleAltIds = getPossibleAltIds(residue, this.structure, this.unit);
|
||||
const firstAtoms = this.processFirstResidue(residue, firstPossibleAltIds);
|
||||
|
||||
residue = this.residueIt.move();
|
||||
|
||||
const secondPossibleAltIds = getPossibleAltIdsResidue(residue, this.structure, this.unit);
|
||||
const secondPossibleAltIds = getPossibleAltIds(residue, this.structure, this.unit);
|
||||
const secondAtoms = this.processSecondResidue(residue, secondPossibleAltIds);
|
||||
|
||||
return { firstAtoms, secondAtoms };
|
||||
|
||||
87
src/extensions/model-export/export.ts
Normal file
87
src/extensions/model-export/export.ts
Normal 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.`);
|
||||
});
|
||||
}
|
||||
30
src/extensions/model-export/index.ts
Normal file
30
src/extensions/model-export/index.ts
Normal 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: () => ({})
|
||||
});
|
||||
69
src/extensions/model-export/ui.tsx
Normal file
69
src/extensions/model-export/ui.tsx
Normal 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>
|
||||
</>;
|
||||
}
|
||||
@@ -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 2022-01-15T19:52:34-08:00
|
||||
// Generated on 2022-02-27T12:49:36-08:00
|
||||
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
@@ -477,6 +477,8 @@ export type ClustersMembers = {
|
||||
export type CoreAssembly = {
|
||||
/** Get PDB entry that includes this assembly. */
|
||||
readonly entry?: Maybe<CoreEntry>;
|
||||
/** Get all pairwise polymer interfaces for this PDB assembly. */
|
||||
readonly interfaces?: Maybe<ReadonlyArray<Maybe<CoreInterface>>>;
|
||||
readonly pdbx_struct_assembly?: Maybe<PdbxStructAssembly>;
|
||||
readonly pdbx_struct_assembly_auth_evidence?: Maybe<ReadonlyArray<Maybe<PdbxStructAssemblyAuthEvidence>>>;
|
||||
readonly pdbx_struct_assembly_gen?: Maybe<ReadonlyArray<Maybe<PdbxStructAssemblyGen>>>;
|
||||
@@ -724,6 +726,16 @@ export type CoreEntry = {
|
||||
readonly symmetry?: Maybe<Symmetry>;
|
||||
};
|
||||
|
||||
export type CoreInterface = {
|
||||
readonly rcsb_id: Scalars['String'];
|
||||
readonly rcsb_interface_container_identifiers: RcsbInterfaceContainerIdentifiers;
|
||||
readonly rcsb_interface_info?: Maybe<RcsbInterfaceInfo>;
|
||||
/** List of operations for each interface partner. */
|
||||
readonly rcsb_interface_operator: ReadonlyArray<Maybe<ReadonlyArray<Maybe<ReadonlyArray<Maybe<Scalars['String']>>>>>>;
|
||||
readonly rcsb_interface_partner: ReadonlyArray<Maybe<RcsbInterfacePartner>>;
|
||||
readonly rcsb_latest_revision?: Maybe<RcsbLatestRevision>;
|
||||
};
|
||||
|
||||
export type CoreNonpolymerEntity = {
|
||||
/** Get PDB entry that contains this non-polymer entity. */
|
||||
readonly entry?: Maybe<CoreEntry>;
|
||||
@@ -3186,6 +3198,28 @@ export type GeneName = {
|
||||
readonly value?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type InterfacePartnerFeatureAdditionalProperties = {
|
||||
/**
|
||||
* The additional property name.
|
||||
*
|
||||
* Allowable values:
|
||||
* TO_BE_DEFINED
|
||||
*
|
||||
*/
|
||||
readonly name?: Maybe<Scalars['String']>;
|
||||
/** The value(s) of the additional property. */
|
||||
readonly values?: Maybe<ReadonlyArray<Maybe<Scalars['ObjectScalar']>>>;
|
||||
};
|
||||
|
||||
export type InterfacePartnerFeatureFeaturePositions = {
|
||||
/** An identifier for the monomer at which this segment of the feature begins. */
|
||||
readonly beg_seq_id: Scalars['Int'];
|
||||
/** An identifier for the monomer at which this segment of the feature ends. */
|
||||
readonly end_seq_id?: Maybe<Scalars['Int']>;
|
||||
/** The value(s) of the feature over the monomer segment. */
|
||||
readonly values?: Maybe<ReadonlyArray<Maybe<Scalars['Float']>>>;
|
||||
};
|
||||
|
||||
export type PdbxAuditRevisionCategory = {
|
||||
/**
|
||||
* The category updated in the pdbx_audit_revision_category record.
|
||||
@@ -6745,6 +6779,10 @@ export type Query = {
|
||||
readonly entries?: Maybe<ReadonlyArray<Maybe<CoreEntry>>>;
|
||||
/** Get PDB entry given the PDB id. */
|
||||
readonly entry?: Maybe<CoreEntry>;
|
||||
/** Get a pairwise polymeric interface given the PDB ID, ASSEMBLY ID and INTERFACE ID. */
|
||||
readonly interface?: Maybe<CoreInterface>;
|
||||
/** Get a list of pairwise polymeric interfaces given a list of INTERFACE IDs. Here INTERFACE ID is a compound identifier that includes entry_id, assembly_id and interface_id e.g. 1XXX-1.1. */
|
||||
readonly interfaces?: Maybe<ReadonlyArray<Maybe<CoreInterface>>>;
|
||||
/** Get a list of PDB non-polymer entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. */
|
||||
readonly nonpolymer_entities?: Maybe<ReadonlyArray<Maybe<CoreNonpolymerEntity>>>;
|
||||
/** Get a PDB non-polymer entity, given the PDB ID and ENTITY ID. Here ENTITY ID is a '1', '2', '3', etc. */
|
||||
@@ -6831,6 +6869,20 @@ export type QueryEntryArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** Query root */
|
||||
export type QueryInterfaceArgs = {
|
||||
assembly_id: Scalars['String'];
|
||||
entry_id: Scalars['String'];
|
||||
interface_id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
/** Query root */
|
||||
export type QueryInterfacesArgs = {
|
||||
interface_ids: ReadonlyArray<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
/** Query root */
|
||||
export type QueryNonpolymer_EntitiesArgs = {
|
||||
entity_ids: ReadonlyArray<Scalars['String']>;
|
||||
@@ -6952,6 +7004,8 @@ export type RcsbAssemblyContainerIdentifiers = {
|
||||
readonly assembly_id: Scalars['String'];
|
||||
/** Entry identifier for the container. */
|
||||
readonly entry_id: Scalars['String'];
|
||||
/** List of binary interface Ids within the assembly (it points to interface id collection). */
|
||||
readonly interface_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>;
|
||||
/**
|
||||
* A unique identifier for each object in this assembly container formed by
|
||||
* a dash separated concatenation of entry and assembly identifiers.
|
||||
@@ -7010,6 +7064,24 @@ export type RcsbAssemblyInfo = {
|
||||
* This is the total count of non-polymer entity instances generated in the assembly coordinate data.
|
||||
*/
|
||||
readonly nonpolymer_entity_instance_count?: Maybe<Scalars['Int']>;
|
||||
/** Number of heterologous (both binding sites are different) interface entities */
|
||||
readonly num_heterologous_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of heteromeric (both partners are different polymeric entities) interface entities */
|
||||
readonly num_heteromeric_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of homomeric (both partners are the same polymeric entity) interface entities */
|
||||
readonly num_homomeric_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of polymer-polymer interface entities, grouping equivalent interfaces at the entity level (i.e. same entity_ids on either side, with similar but not identical binding sites) */
|
||||
readonly num_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of geometrically equivalent (i.e. same asym_ids on either side) polymer-polymer interfaces in the assembly */
|
||||
readonly num_interfaces?: Maybe<Scalars['Int']>;
|
||||
/** Number of isologous (both binding sites are same, i.e. interface is symmetric) interface entities */
|
||||
readonly num_isologous_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of nucleic acid-nucleic acid interface entities */
|
||||
readonly num_na_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of protein-nucleic acid interface entities */
|
||||
readonly num_prot_na_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of protein-protein interface entities */
|
||||
readonly num_protein_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** The assembly non-hydrogen polymer entity atomic coordinate count. */
|
||||
readonly polymer_atom_count?: Maybe<Scalars['Int']>;
|
||||
/**
|
||||
@@ -7085,6 +7157,10 @@ export type RcsbAssemblyInfo = {
|
||||
* This is the total count of solvent entity instances generated in the assembly coordinate data.
|
||||
*/
|
||||
readonly solvent_entity_instance_count?: Maybe<Scalars['Int']>;
|
||||
/** Total buried surface area calculated as the sum of buried surface areas over all interfaces */
|
||||
readonly total_assembly_buried_surface_area?: Maybe<Scalars['Float']>;
|
||||
/** Total number of interfacing residues in the assembly, calculated as the sum of interfacing residues over all interfaces */
|
||||
readonly total_number_interface_residues?: Maybe<Scalars['Int']>;
|
||||
/**
|
||||
* The number of unmodeled polymer monomers in the assembly coordinate data. This is
|
||||
* the total count of monomers with unreported coordinate data for all polymer
|
||||
@@ -8817,6 +8893,99 @@ export type RcsbGenomicLineage = {
|
||||
readonly name?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type RcsbInterfaceContainerIdentifiers = {
|
||||
/** This item references an assembly in pdbx_struct_assembly */
|
||||
readonly assembly_id: Scalars['String'];
|
||||
/** Entry identifier for the container. */
|
||||
readonly entry_id: Scalars['String'];
|
||||
/**
|
||||
* Identifier for NCS-equivalent interfaces within the assembly (same entity_ids on both sides)
|
||||
*
|
||||
* Examples:
|
||||
* 1, 2
|
||||
*
|
||||
*/
|
||||
readonly interface_entity_id?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* Identifier for the geometrically equivalent (same asym_ids on either side) interfaces within the assembly
|
||||
*
|
||||
* Examples:
|
||||
* 1, 2
|
||||
*
|
||||
*/
|
||||
readonly interface_id: Scalars['String'];
|
||||
/**
|
||||
* Unique identifier for the document
|
||||
*
|
||||
* Examples:
|
||||
* 2UZI-1.A.B?1
|
||||
*
|
||||
*/
|
||||
readonly rcsb_id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type RcsbInterfaceInfo = {
|
||||
/** Total interface buried surface area */
|
||||
readonly interface_area?: Maybe<Scalars['Float']>;
|
||||
/** Allowable values: homo, hetero. */
|
||||
readonly interface_character?: Maybe<Scalars['String']>;
|
||||
/** Number of core interface residues, defined as those that bury >90% accessible surface area with respect to the unbound state */
|
||||
readonly num_core_interface_residues?: Maybe<Scalars['Int']>;
|
||||
/** Number of interface residues, defined as those with burial fraction > 0 */
|
||||
readonly num_interface_residues?: Maybe<Scalars['Int']>;
|
||||
/** Allowable values: Nucleic acid (only), Protein (only), Protein/NA. */
|
||||
readonly polymer_composition?: Maybe<Scalars['String']>;
|
||||
/** The Jaccard score (intersection over union) of interface contacts in homomeric interfaces, comparing contact sets left-right vs right-left. High values indicate isologous (symmetric) interfaces, with value=1 if perfectly symmetric (e.g. crystallographic symmetry) */
|
||||
readonly self_jaccard_contact_score?: Maybe<Scalars['Float']>;
|
||||
};
|
||||
|
||||
export type RcsbInterfacePartner = {
|
||||
readonly interface_partner_feature?: Maybe<ReadonlyArray<Maybe<RcsbInterfacePartnerInterfacePartnerFeature>>>;
|
||||
readonly interface_partner_identifier?: Maybe<RcsbInterfacePartnerInterfacePartnerIdentifier>;
|
||||
};
|
||||
|
||||
export type RcsbInterfacePartnerInterfacePartnerFeature = {
|
||||
readonly additional_properties?: Maybe<ReadonlyArray<Maybe<InterfacePartnerFeatureAdditionalProperties>>>;
|
||||
/**
|
||||
* Identifies the version of the feature assignment.
|
||||
*
|
||||
* Examples:
|
||||
* V4_0_2
|
||||
*
|
||||
*/
|
||||
readonly assignment_version?: Maybe<Scalars['String']>;
|
||||
/** A description for the feature. */
|
||||
readonly description?: Maybe<Scalars['String']>;
|
||||
/** An identifier for the feature. */
|
||||
readonly feature_id?: Maybe<Scalars['String']>;
|
||||
readonly feature_positions?: Maybe<ReadonlyArray<Maybe<InterfacePartnerFeatureFeaturePositions>>>;
|
||||
/** A name for the feature. */
|
||||
readonly name?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* Code identifying the individual, organization or program that assigned the feature.
|
||||
*
|
||||
* Examples:
|
||||
* NACCESS
|
||||
*
|
||||
*/
|
||||
readonly provenance_source?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* A type or category of the feature.
|
||||
*
|
||||
* Allowable values:
|
||||
* ASA_UNBOUND, ASA_BOUND
|
||||
*
|
||||
*/
|
||||
readonly type?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type RcsbInterfacePartnerInterfacePartnerIdentifier = {
|
||||
/** Instance identifier for this container. */
|
||||
readonly asym_id: Scalars['String'];
|
||||
/** Polymer entity identifier for the container. */
|
||||
readonly entity_id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type RcsbLatestRevision = {
|
||||
/** The major version number of the latest revision. */
|
||||
readonly major_revision?: Maybe<Scalars['Int']>;
|
||||
@@ -10263,7 +10432,7 @@ export type RcsbPolymerInstanceFeature = {
|
||||
* A type or category of the feature.
|
||||
*
|
||||
* Allowable values:
|
||||
* ANGLE_OUTLIER, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SCOP, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ
|
||||
* ANGLE_OUTLIER, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SCOP, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ, ASA
|
||||
*
|
||||
*/
|
||||
readonly type?: Maybe<Scalars['String']>;
|
||||
@@ -13145,4 +13314,4 @@ export type AssemblySymmetryQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type AssemblySymmetryQuery = { readonly assembly?: { readonly rcsb_struct_symmetry?: ReadonlyArray<{ readonly kind: string, readonly oligomeric_state: string, readonly stoichiometry: ReadonlyArray<string | null | undefined>, readonly symbol: string, readonly type: string, readonly clusters: ReadonlyArray<{ readonly avg_rmsd?: number | null | undefined, readonly members: ReadonlyArray<{ readonly asym_id: string, readonly pdbx_struct_oper_list_ids?: ReadonlyArray<string | null | undefined> | null | undefined } | null | undefined> } | null | undefined>, readonly rotation_axes?: ReadonlyArray<{ readonly order?: number | null | undefined, readonly start: ReadonlyArray<number | null | undefined>, readonly end: ReadonlyArray<number | null | undefined> } | null | undefined> | null | undefined } | null | undefined> | null | undefined } | null | undefined };
|
||||
export type AssemblySymmetryQuery = { readonly assembly?: { readonly rcsb_struct_symmetry?: ReadonlyArray<{ readonly kind: string, readonly oligomeric_state: string, readonly stoichiometry: ReadonlyArray<string | null>, readonly symbol: string, readonly type: string, readonly clusters: ReadonlyArray<{ readonly avg_rmsd?: number | null, readonly members: ReadonlyArray<{ readonly asym_id: string, readonly pdbx_struct_oper_list_ids?: ReadonlyArray<string | null> | null } | null> } | null>, readonly rotation_axes?: ReadonlyArray<{ readonly order?: number | null, readonly start: ReadonlyArray<number | null>, readonly end: ReadonlyArray<number | null> } | null> | null } | null> | null } | null };
|
||||
|
||||
@@ -55,7 +55,16 @@ function createIntraUnitClashCylinderMesh(ctx: VisualContext, unit: Unit, struct
|
||||
radius: (edgeIndex: number) => magnitude[edgeIndex] * sizeFactor,
|
||||
};
|
||||
|
||||
return createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
|
||||
if (boundingSphere) {
|
||||
m.setBoundingSphere(boundingSphere);
|
||||
} else if (m.triangleCount > 0) {
|
||||
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * sizeFactor);
|
||||
m.setBoundingSphere(sphere);
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
export const IntraUnitClashParams = {
|
||||
@@ -169,7 +178,16 @@ function createInterUnitClashCylinderMesh(ctx: VisualContext, structure: Structu
|
||||
radius: (edgeIndex: number) => edges[edgeIndex].props.magnitude * sizeFactor
|
||||
};
|
||||
|
||||
return createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
|
||||
if (boundingSphere) {
|
||||
m.setBoundingSphere(boundingSphere);
|
||||
} else {
|
||||
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * sizeFactor);
|
||||
m.setBoundingSphere(sphere);
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
export const InterUnitClashParams = {
|
||||
|
||||
30
src/extensions/zenodo/index.ts
Normal file
30
src/extensions/zenodo/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
|
||||
import { ZenodoImportUI } from './ui';
|
||||
|
||||
export const ZenodoImport = PluginBehavior.create<{ }>({
|
||||
name: 'extension-zenodo-import',
|
||||
category: 'misc',
|
||||
display: {
|
||||
name: 'Zenodo Export'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ }> {
|
||||
register(): void {
|
||||
this.ctx.customImportControls.set('zenodo-import', ZenodoImportUI as any);
|
||||
}
|
||||
|
||||
update() {
|
||||
return false;
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.ctx.customImportControls.delete('zenodo-import');
|
||||
}
|
||||
},
|
||||
params: () => ({ })
|
||||
});
|
||||
302
src/extensions/zenodo/ui.tsx
Normal file
302
src/extensions/zenodo/ui.tsx
Normal file
@@ -0,0 +1,302 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { DownloadFile } from '../../mol-plugin-state/actions/file';
|
||||
import { DownloadStructure, LoadTrajectory } from '../../mol-plugin-state/actions/structure';
|
||||
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
|
||||
import { CoordinatesFormatCategory } from '../../mol-plugin-state/formats/coordinates';
|
||||
import { TopologyFormatCategory } from '../../mol-plugin-state/formats/topology';
|
||||
import { TrajectoryFormatCategory } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { VolumeFormatCategory } from '../../mol-plugin-state/formats/volume';
|
||||
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
|
||||
import { Button } from '../../mol-plugin-ui/controls/common';
|
||||
import { OpenInBrowserSvg } from '../../mol-plugin-ui/controls/icons';
|
||||
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { formatBytes } from '../../mol-util';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
type ZenodoFile = {
|
||||
bucket: string
|
||||
checksum: string
|
||||
key: string
|
||||
links: {
|
||||
[key: string]: string
|
||||
self: string
|
||||
}
|
||||
size: number
|
||||
type: string
|
||||
}
|
||||
|
||||
type ZenodoRecord = {
|
||||
id: number
|
||||
conceptdoi: string
|
||||
conceptrecid: string
|
||||
created: string
|
||||
doi: string
|
||||
files: ZenodoFile[]
|
||||
revision: number
|
||||
updated: string
|
||||
metadata: {
|
||||
title: string
|
||||
}
|
||||
}
|
||||
|
||||
interface State {
|
||||
busy?: boolean
|
||||
recordValues: PD.Values<typeof ZenodoImportParams>
|
||||
importValues?: PD.Values<ImportParams>
|
||||
importParams?: ImportParams
|
||||
record?: ZenodoRecord
|
||||
files?: ZenodoFile[]
|
||||
}
|
||||
|
||||
const ZenodoImportParams = {
|
||||
record: PD.Text('', { description: 'Zenodo ID.' })
|
||||
};
|
||||
|
||||
function createImportParams(files: ZenodoFile[], plugin: PluginContext) {
|
||||
const modelOpts: [string, string][] = [];
|
||||
const topologyOpts: [string, string][] = [];
|
||||
const coordinatesOpts: [string, string][] = [];
|
||||
const volumeOpts: [string, string][] = [];
|
||||
const compressedOpts: [string, string][] = [];
|
||||
|
||||
const structureExts = new Map<string, { format: string, isBinary: boolean }>();
|
||||
const coordinatesExts = new Map<string, { format: string, isBinary: boolean }>();
|
||||
const topologyExts = new Map<string, { format: string, isBinary: boolean }>();
|
||||
const volumeExts = new Map<string, { format: string, isBinary: boolean }>();
|
||||
|
||||
for (const { provider: { category, binaryExtensions, stringExtensions }, name } of plugin.dataFormats.list) {
|
||||
if (category === TrajectoryFormatCategory) {
|
||||
if (binaryExtensions) for (const e of binaryExtensions) structureExts.set(e, { format: name, isBinary: true });
|
||||
if (stringExtensions) for (const e of stringExtensions) structureExts.set(e, { format: name, isBinary: false });
|
||||
} else if (category === VolumeFormatCategory) {
|
||||
if (binaryExtensions) for (const e of binaryExtensions) volumeExts.set(e, { format: name, isBinary: true });
|
||||
if (stringExtensions) for (const e of stringExtensions) volumeExts.set(e, { format: name, isBinary: false });
|
||||
} else if (category === CoordinatesFormatCategory) {
|
||||
if (binaryExtensions) for (const e of binaryExtensions) coordinatesExts.set(e, { format: name, isBinary: true });
|
||||
if (stringExtensions) for (const e of stringExtensions) coordinatesExts.set(e, { format: name, isBinary: false });
|
||||
} else if (category === TopologyFormatCategory) {
|
||||
if (binaryExtensions) for (const e of binaryExtensions) topologyExts.set(e, { format: name, isBinary: true });
|
||||
if (stringExtensions) for (const e of stringExtensions) topologyExts.set(e, { format: name, isBinary: false });
|
||||
}
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
const label = `${file.key} (${formatBytes(file.size)})`;
|
||||
if (structureExts.has(file.type)) {
|
||||
const { format, isBinary } = structureExts.get(file.type)!;
|
||||
modelOpts.push([`${file.links.self}|${format}|${isBinary}`, label]);
|
||||
topologyOpts.push([`${file.links.self}|${format}|${isBinary}`, label]);
|
||||
} else if (volumeExts.has(file.type)) {
|
||||
const { format, isBinary } = volumeExts.get(file.type)!;
|
||||
volumeOpts.push([`${file.links.self}|${format}|${isBinary}`, label]);
|
||||
} else if (topologyExts.has(file.type)) {
|
||||
const { format, isBinary } = topologyExts.get(file.type)!;
|
||||
topologyOpts.push([`${file.links.self}|${format}|${isBinary}`, label]);
|
||||
} else if (coordinatesExts.has(file.type)) {
|
||||
const { format, isBinary } = coordinatesExts.get(file.type)!;
|
||||
coordinatesOpts.push([`${file.links.self}|${format}|${isBinary}`, label]);
|
||||
} else if (file.type === 'zip') {
|
||||
compressedOpts.push([`${file.links.self}|${file.type}|true`, label]);
|
||||
}
|
||||
}
|
||||
|
||||
const params: PD.Params = {};
|
||||
let defaultType = '';
|
||||
|
||||
if (modelOpts.length) {
|
||||
defaultType = 'structure';
|
||||
params.structure = PD.Select(modelOpts[0][0], modelOpts);
|
||||
}
|
||||
|
||||
if (topologyOpts.length && coordinatesOpts.length) {
|
||||
if (!defaultType) defaultType = 'trajectory';
|
||||
params.trajectory = PD.Group({
|
||||
topology: PD.Select(topologyOpts[0][0], topologyOpts),
|
||||
coordinates: PD.Select(coordinatesOpts[0][0], coordinatesOpts),
|
||||
}, { isFlat: true });
|
||||
}
|
||||
|
||||
if (volumeOpts.length) {
|
||||
if (!defaultType) defaultType = 'volume';
|
||||
params.volume = PD.Select(volumeOpts[0][0], volumeOpts);
|
||||
}
|
||||
|
||||
if (compressedOpts.length) {
|
||||
if (!defaultType) defaultType = 'compressed';
|
||||
params.compressed = PD.Select(compressedOpts[0][0], compressedOpts);
|
||||
}
|
||||
|
||||
return {
|
||||
type: PD.MappedStatic(defaultType, Object.keys(params).length ? params : { '': PD.EmptyGroup() })
|
||||
};
|
||||
}
|
||||
type ImportParams = ReturnType<typeof createImportParams>
|
||||
|
||||
export class ZenodoImportUI extends CollapsableControls<{}, State> {
|
||||
protected defaultState(): State & CollapsableState {
|
||||
return {
|
||||
header: 'Zenodo Import',
|
||||
isCollapsed: true,
|
||||
brand: { accent: 'cyan', svg: OpenInBrowserSvg },
|
||||
recordValues: PD.getDefaultValues(ZenodoImportParams),
|
||||
importValues: undefined,
|
||||
importParams: undefined,
|
||||
record: undefined,
|
||||
files: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private recordParamsOnChange = (values: any) => {
|
||||
this.setState({ recordValues: values });
|
||||
};
|
||||
|
||||
private importParamsOnChange = (values: any) => {
|
||||
this.setState({ importValues: values });
|
||||
};
|
||||
|
||||
private loadRecord = async () => {
|
||||
try {
|
||||
this.setState({ busy: true });
|
||||
const record: ZenodoRecord = await this.plugin.runTask(this.plugin.fetch({ url: `https://zenodo.org/api/records/${this.state.recordValues.record}`, type: 'json' }));
|
||||
const importParams = createImportParams(record.files, this.plugin);
|
||||
this.setState({
|
||||
record,
|
||||
files: record.files,
|
||||
busy: false,
|
||||
importValues: PD.getDefaultValues(importParams),
|
||||
importParams
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.plugin.log.error(`Failed to load Zenodo record '${this.state.recordValues.record}'`);
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
};
|
||||
|
||||
private loadFile = async (values: PD.Values<ImportParams>) => {
|
||||
try {
|
||||
this.setState({ busy: true });
|
||||
|
||||
const t = values.type;
|
||||
if (t.name === 'structure') {
|
||||
const defaultParams = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
|
||||
const [url, format, isBinary] = t.params.split('|');
|
||||
|
||||
await this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'url',
|
||||
params: {
|
||||
url,
|
||||
format: format as any,
|
||||
isBinary: isBinary === 'true',
|
||||
options: defaultParams.source.params.options,
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else if (t.name === 'trajectory') {
|
||||
const [topologyUrl, topologyFormat, topologyIsBinary] = t.params.topology.split('|');
|
||||
const [coordinatesUrl, coordinatesFormat, coordinatesIsBinary] = t.params.coordinates.split('|');
|
||||
|
||||
await this.plugin.runTask(this.plugin.state.data.applyAction(LoadTrajectory, {
|
||||
source: {
|
||||
name: 'url',
|
||||
params: {
|
||||
model: {
|
||||
url: topologyUrl,
|
||||
format: topologyFormat as any,
|
||||
isBinary: topologyIsBinary === 'true',
|
||||
},
|
||||
coordinates: {
|
||||
url: coordinatesUrl,
|
||||
format: coordinatesFormat as any,
|
||||
isBinary: coordinatesIsBinary === 'true',
|
||||
},
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else if (t.name === 'volume') {
|
||||
const [url, format, isBinary] = t.params.split('|');
|
||||
|
||||
await this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, {
|
||||
source: {
|
||||
name: 'url',
|
||||
params: {
|
||||
url,
|
||||
format: format as any,
|
||||
isBinary: isBinary === 'true',
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else if (t.name === 'compressed') {
|
||||
const [url, format, isBinary] = t.params.split('|');
|
||||
|
||||
await this.plugin.runTask(this.plugin.state.data.applyAction(DownloadFile, {
|
||||
url,
|
||||
format: format as any,
|
||||
isBinary: isBinary === 'true',
|
||||
visuals: true
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.plugin.log.error(`Failed to load Zenodo file`);
|
||||
} finally {
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
};
|
||||
|
||||
private clearRecord = () => {
|
||||
this.setState({
|
||||
importValues: undefined,
|
||||
importParams: undefined,
|
||||
record: undefined,
|
||||
files: undefined
|
||||
});
|
||||
};
|
||||
|
||||
private renderLoadRecord() {
|
||||
return <div style={{ marginBottom: 10 }}>
|
||||
<ParameterControls params={ZenodoImportParams} values={this.state.recordValues} onChangeValues={this.recordParamsOnChange} isDisabled={this.state.busy} />
|
||||
<Button onClick={this.loadRecord} style={{ marginTop: 1 }} disabled={this.state.busy || !this.state.recordValues.record}>
|
||||
Load Record
|
||||
</Button>
|
||||
</div>;
|
||||
}
|
||||
|
||||
private renderRecordInfo(record: ZenodoRecord) {
|
||||
return <div style={{ marginBottom: 10 }}>
|
||||
<div className='msp-help-text'>
|
||||
<div>Record {`${record.id}`}: <i>{`${record.metadata.title}`}</i></div>
|
||||
</div>
|
||||
<Button onClick={this.clearRecord} style={{ marginTop: 1 }} disabled={this.state.busy}>
|
||||
Clear
|
||||
</Button>
|
||||
</div>;
|
||||
}
|
||||
|
||||
private renderImportFile(params: ImportParams, values: PD.Values<ImportParams>) {
|
||||
return values.type.name ? <div style={{ marginBottom: 10 }}>
|
||||
<ParameterControls params={params} values={this.state.importValues} onChangeValues={this.importParamsOnChange} isDisabled={this.state.busy} />
|
||||
<Button onClick={() => this.loadFile(values)} style={{ marginTop: 1 }} disabled={this.state.busy}>
|
||||
Import File
|
||||
</Button>
|
||||
</div> : <div className='msp-help-text' style={{ marginBottom: 10 }}>
|
||||
<div>No supported files</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
protected renderControls(): JSX.Element | null {
|
||||
return <>
|
||||
{!this.state.record ? this.renderLoadRecord() : null}
|
||||
{this.state.record ? this.renderRecordInfo(this.state.record) : null}
|
||||
{this.state.importParams && this.state.importValues ? this.renderImportFile(this.state.importParams, this.state.importValues) : null}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { Viewport, cameraProject, cameraUnproject } from './camera/util';
|
||||
import { CameraTransitionManager } from './camera/transition';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Scene } from '../mol-gl/scene';
|
||||
import { assertUnreachable } from '../mol-util/type-helpers';
|
||||
|
||||
export { ICamera, Camera };
|
||||
|
||||
@@ -84,7 +85,7 @@ class Camera implements ICamera {
|
||||
switch (this.state.mode) {
|
||||
case 'orthographic': updateOrtho(this); break;
|
||||
case 'perspective': updatePers(this); break;
|
||||
default: throw new Error('unknown camera mode');
|
||||
default: assertUnreachable(this.state.mode);
|
||||
}
|
||||
|
||||
const changed = !Mat4.areEqual(this.projection, this.prevProjection, EPSILON) || !Mat4.areEqual(this.view, this.prevView, EPSILON);
|
||||
|
||||
@@ -236,7 +236,7 @@ interface Canvas3D {
|
||||
/** Sets drawPaused = false without starting the built in animation loop */
|
||||
resume(): void
|
||||
identify(x: number, y: number): PickData | undefined
|
||||
mark(loci: Representation.Loci, action: MarkerAction, noDraw?: boolean): void
|
||||
mark(loci: Representation.Loci, action: MarkerAction): void
|
||||
getLoci(pickingId: PickingId | undefined): Representation.Loci
|
||||
|
||||
notifyDidDraw: boolean,
|
||||
@@ -345,7 +345,30 @@ namespace Canvas3D {
|
||||
return { loci, repr };
|
||||
}
|
||||
|
||||
function mark(reprLoci: Representation.Loci, action: MarkerAction, noDraw = false) {
|
||||
let markBuffer: [reprLoci: Representation.Loci, action: MarkerAction][] = [];
|
||||
|
||||
function mark(reprLoci: Representation.Loci, action: MarkerAction) {
|
||||
// NOTE: might try to optimize a case with opposite actions for the
|
||||
// same loci. Tho this might end up being more expensive (and error prone)
|
||||
// then just applying everything "naively".
|
||||
markBuffer.push([reprLoci, action]);
|
||||
}
|
||||
|
||||
function resolveMarking() {
|
||||
let changed = false;
|
||||
for (const [r, l] of markBuffer) {
|
||||
changed = applyMark(r, l) || changed;
|
||||
}
|
||||
markBuffer = [];
|
||||
if (changed) {
|
||||
scene.update(void 0, true);
|
||||
helper.handle.scene.update(void 0, true);
|
||||
helper.camera.scene.update(void 0, true);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function applyMark(reprLoci: Representation.Loci, action: MarkerAction) {
|
||||
const { repr, loci } = reprLoci;
|
||||
let changed = false;
|
||||
if (repr) {
|
||||
@@ -355,24 +378,10 @@ namespace Canvas3D {
|
||||
changed = helper.camera.mark(loci, action) || changed;
|
||||
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
|
||||
}
|
||||
if (changed) {
|
||||
if (noDraw) {
|
||||
// Even with `noDraw` make sure changes will be rendered.
|
||||
// Note that with this calling mark (with or without `noDraw`) multiple times
|
||||
// during a JS event loop iteration will only result in a single render call.
|
||||
forceNextRender = true;
|
||||
} else {
|
||||
scene.update(void 0, true);
|
||||
helper.handle.scene.update(void 0, true);
|
||||
helper.camera.scene.update(void 0, true);
|
||||
const prevPickDirty = pickHelper.dirty;
|
||||
draw({ force: true, allowMulti: true });
|
||||
pickHelper.dirty = prevPickDirty; // marking does not change picking buffers
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function render(force: boolean, allowMulti: boolean) {
|
||||
function render(force: boolean) {
|
||||
if (webgl.isContextLost) return false;
|
||||
|
||||
let resized = false;
|
||||
@@ -386,6 +395,8 @@ namespace Canvas3D {
|
||||
y > gl.drawingBufferHeight || y + height < 0
|
||||
) return false;
|
||||
|
||||
const markingUpdated = resolveMarking();
|
||||
|
||||
let didRender = false;
|
||||
controls.update(currentTime);
|
||||
const cameraChanged = camera.update();
|
||||
@@ -393,9 +404,9 @@ namespace Canvas3D {
|
||||
const shouldRender = force || cameraChanged || resized || forceNextRender;
|
||||
forceNextRender = false;
|
||||
|
||||
const multiSampleChanged = multiSampleHelper.update(shouldRender, p.multiSample);
|
||||
const multiSampleChanged = multiSampleHelper.update(markingUpdated || shouldRender, p.multiSample);
|
||||
|
||||
if (shouldRender || multiSampleChanged) {
|
||||
if (shouldRender || multiSampleChanged || markingUpdated) {
|
||||
let cam: Camera | StereoCamera = camera;
|
||||
if (p.camera.stereo.name === 'on') {
|
||||
stereoCamera.update();
|
||||
@@ -404,12 +415,13 @@ namespace Canvas3D {
|
||||
|
||||
const ctx = { renderer, camera: cam, scene, helper };
|
||||
if (MultiSamplePass.isEnabled(p.multiSample)) {
|
||||
const forceOn = !cameraChanged && allowMulti && !controls.isAnimating;
|
||||
const forceOn = !cameraChanged && markingUpdated && !controls.isAnimating;
|
||||
multiSampleHelper.render(ctx, p, true, forceOn);
|
||||
} else {
|
||||
passes.draw.render(ctx, p, true);
|
||||
}
|
||||
pickHelper.dirty = true;
|
||||
// if only marking has updated, do not set the flag to dirty
|
||||
pickHelper.dirty = pickHelper.dirty || shouldRender;
|
||||
didRender = true;
|
||||
}
|
||||
|
||||
@@ -421,9 +433,9 @@ namespace Canvas3D {
|
||||
let currentTime = 0;
|
||||
let drawPaused = false;
|
||||
|
||||
function draw(options?: { force?: boolean, allowMulti?: boolean }) {
|
||||
function draw(options?: { force?: boolean }) {
|
||||
if (drawPaused) return;
|
||||
if (render(!!options?.force, !!options?.allowMulti) && notifyDidDraw) {
|
||||
if (render(!!options?.force) && notifyDidDraw) {
|
||||
didDraw.next(now() - startTime as now.Timestamp);
|
||||
}
|
||||
}
|
||||
@@ -831,6 +843,8 @@ namespace Canvas3D {
|
||||
dispose: () => {
|
||||
contextRestoredSub.unsubscribe();
|
||||
|
||||
markBuffer = [];
|
||||
|
||||
scene.clear();
|
||||
helper.debug.clear();
|
||||
controls.dispose();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -307,19 +307,22 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
if (markingEnabled) {
|
||||
const markingDepthTest = props.marking.ghostEdgeStrength < 1;
|
||||
if (markingDepthTest) {
|
||||
this.marking.depthTarget.bind();
|
||||
renderer.clear(false);
|
||||
renderer.renderMarkingDepth(scene.primitives, camera, null);
|
||||
const markerAverage = scene.getMarkerAverage();
|
||||
if (markerAverage > 0) {
|
||||
const markingDepthTest = props.marking.ghostEdgeStrength < 1;
|
||||
if (markingDepthTest && markerAverage !== 1) {
|
||||
this.marking.depthTarget.bind();
|
||||
renderer.clear(false, true);
|
||||
renderer.renderMarkingDepth(scene.primitives, camera, null);
|
||||
}
|
||||
|
||||
this.marking.maskTarget.bind();
|
||||
renderer.clear(false, true);
|
||||
renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
|
||||
|
||||
this.marking.update(props.marking);
|
||||
this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget);
|
||||
}
|
||||
|
||||
this.marking.maskTarget.bind();
|
||||
renderer.clear(false);
|
||||
renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
|
||||
|
||||
this.marking.update(props.marking);
|
||||
this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget);
|
||||
}
|
||||
|
||||
if (helper.debug.isEnabled) {
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -157,6 +157,14 @@ export class MultiSamplePass {
|
||||
ValueCell.update(compose.values.uWeight, sampleWeight);
|
||||
|
||||
// render scene
|
||||
if (i === 0) {
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
} else {
|
||||
drawPass.postprocessing.setOcclusionOffset(
|
||||
offset[0] / width,
|
||||
offset[1] / height
|
||||
);
|
||||
}
|
||||
drawPass.render(ctx, props, false);
|
||||
|
||||
// compose rendered scene with compose target
|
||||
@@ -175,6 +183,8 @@ export class MultiSamplePass {
|
||||
compose.render();
|
||||
}
|
||||
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
|
||||
ValueCell.update(compose.values.uWeight, 1.0);
|
||||
ValueCell.update(compose.values.tColor, composeTarget.texture);
|
||||
compose.update();
|
||||
@@ -236,6 +246,14 @@ export class MultiSamplePass {
|
||||
camera.update();
|
||||
|
||||
// render scene
|
||||
if (sampleIndex === 0) {
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
} else {
|
||||
drawPass.postprocessing.setOcclusionOffset(
|
||||
offset[0] / width,
|
||||
offset[1] / height
|
||||
);
|
||||
}
|
||||
drawPass.render(ctx, props, false);
|
||||
|
||||
// compose rendered scene with compose target
|
||||
@@ -258,6 +276,8 @@ export class MultiSamplePass {
|
||||
}
|
||||
}
|
||||
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
|
||||
this.bindOutputTarget(toDrawingBuffer);
|
||||
gl.viewport(x, y, width, height);
|
||||
gl.scissor(x, y, width, height);
|
||||
@@ -291,23 +311,23 @@ const JitterVectors = [
|
||||
[0, 0]
|
||||
],
|
||||
[
|
||||
[4, 4], [-4, -4]
|
||||
[0, 0], [-4, -4]
|
||||
],
|
||||
[
|
||||
[-2, -6], [6, -2], [-6, 2], [2, 6]
|
||||
[0, 0], [6, -2], [-6, 2], [2, 6]
|
||||
],
|
||||
[
|
||||
[1, -3], [-1, 3], [5, 1], [-3, -5],
|
||||
[0, 0], [-1, 3], [5, 1], [-3, -5],
|
||||
[-5, 5], [-7, -1], [3, 7], [7, -7]
|
||||
],
|
||||
[
|
||||
[1, 1], [-1, -3], [-3, 2], [4, -1],
|
||||
[0, 0], [-1, -3], [-3, 2], [4, -1],
|
||||
[-5, -2], [2, 5], [5, 3], [3, -5],
|
||||
[-2, 6], [0, -7], [-4, -6], [-6, 4],
|
||||
[-8, 0], [7, -4], [6, 7], [-7, -8]
|
||||
],
|
||||
[
|
||||
[-4, -7], [-7, -5], [-3, -5], [-5, -4],
|
||||
[0, 0], [-7, -5], [-3, -5], [-5, -4],
|
||||
[-1, -4], [-2, -2], [-6, -1], [-4, 0],
|
||||
[-7, 1], [-1, 2], [-6, 3], [-3, 3],
|
||||
[-7, 6], [-3, 6], [-5, 7], [-1, 7],
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
|
||||
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
|
||||
import { CopyRenderable, createCopyRenderable, QuadSchema, QuadValues } from '../../mol-gl/compute/util';
|
||||
import { TextureSpec, Values, UniformSpec, DefineSpec } from '../../mol-gl/renderable/schema';
|
||||
import { ShaderCode } from '../../mol-gl/shader-code';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
@@ -199,6 +199,7 @@ const PostprocessingSchema = {
|
||||
uMaxPossibleViewZDiff: UniformSpec('f'),
|
||||
|
||||
dOcclusionEnable: DefineSpec('boolean'),
|
||||
uOcclusionOffset: UniformSpec('v2'),
|
||||
|
||||
dOutlineEnable: DefineSpec('boolean'),
|
||||
dOutlineScale: DefineSpec('number'),
|
||||
@@ -227,6 +228,7 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
|
||||
uMaxPossibleViewZDiff: ValueCell.create(0.5),
|
||||
|
||||
dOcclusionEnable: ValueCell.create(true),
|
||||
uOcclusionOffset: ValueCell.create(Vec2.create(0, 0)),
|
||||
|
||||
dOutlineEnable: ValueCell.create(false),
|
||||
dOutlineScale: ValueCell.create(1),
|
||||
@@ -244,9 +246,10 @@ export const PostprocessingParams = {
|
||||
occlusion: PD.MappedStatic('on', {
|
||||
on: PD.Group({
|
||||
samples: PD.Numeric(32, { min: 1, max: 256, step: 1 }),
|
||||
radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final radius is 2^x.' }),
|
||||
radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final occlusion radius is 2^x' }),
|
||||
bias: PD.Numeric(0.8, { min: 0, max: 3, step: 0.1 }),
|
||||
blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
|
||||
resolutionScale: PD.Numeric(1, { min: 0.1, max: 1, step: 0.05 }, { description: 'Adjust resolution of occlusion calculation' }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
|
||||
@@ -281,6 +284,9 @@ export class PostprocessingPass {
|
||||
private readonly ssaoBlurFirstPassFramebuffer: Framebuffer;
|
||||
private readonly ssaoBlurSecondPassFramebuffer: Framebuffer;
|
||||
|
||||
private readonly downsampledDepthTarget: RenderTarget;
|
||||
private readonly downsampleDepthRenderable: CopyRenderable;
|
||||
|
||||
private readonly ssaoDepthTexture: Texture;
|
||||
private readonly ssaoDepthBlurProxyTexture: Texture;
|
||||
|
||||
@@ -290,24 +296,25 @@ export class PostprocessingPass {
|
||||
|
||||
private nSamples: number;
|
||||
private blurKernelSize: number;
|
||||
private downsampleFactor: number;
|
||||
|
||||
private readonly renderable: PostprocessingRenderable;
|
||||
|
||||
private ssaoScale: number;
|
||||
private calcSsaoScale() {
|
||||
// downscale ssao for high pixel-ratios
|
||||
return Math.min(1, 1 / this.webgl.pixelRatio);
|
||||
return Math.min(1, 1 / this.webgl.pixelRatio) * this.downsampleFactor;
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, drawPass: DrawPass) {
|
||||
this.ssaoScale = this.calcSsaoScale();
|
||||
|
||||
constructor(private webgl: WebGLContext, private drawPass: DrawPass) {
|
||||
const { colorTarget, depthTexture } = drawPass;
|
||||
const width = colorTarget.getWidth();
|
||||
const height = colorTarget.getHeight();
|
||||
|
||||
this.nSamples = 1;
|
||||
this.blurKernelSize = 1;
|
||||
this.downsampleFactor = 1;
|
||||
this.ssaoScale = this.calcSsaoScale();
|
||||
|
||||
// needs to be linear for anti-aliasing pass
|
||||
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
|
||||
@@ -332,17 +339,20 @@ export class PostprocessingPass {
|
||||
const sw = Math.floor(width * this.ssaoScale);
|
||||
const sh = Math.floor(height * this.ssaoScale);
|
||||
|
||||
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
this.downsampledDepthTarget = webgl.createRenderTarget(sw, sh, false, 'uint8', 'linear');
|
||||
this.downsampleDepthRenderable = createCopyRenderable(webgl, depthTexture);
|
||||
|
||||
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
|
||||
|
||||
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
|
||||
|
||||
this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
|
||||
|
||||
this.ssaoRenderable = getSsaoRenderable(webgl, depthTexture);
|
||||
this.ssaoRenderable = getSsaoRenderable(webgl, this.downsampleFactor === 1 ? depthTexture : this.downsampledDepthTarget.texture);
|
||||
this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
|
||||
this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
|
||||
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, this.outlinesTarget.texture, this.ssaoDepthTexture);
|
||||
@@ -359,11 +369,13 @@ export class PostprocessingPass {
|
||||
const sh = Math.floor(height * this.ssaoScale);
|
||||
this.target.setSize(width, height);
|
||||
this.outlinesTarget.setSize(width, height);
|
||||
this.downsampledDepthTarget.setSize(sw, sh);
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.downsampleDepthRenderable.values.uTexSize, Vec2.set(this.downsampleDepthRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
@@ -434,6 +446,30 @@ export class PostprocessingPass {
|
||||
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
|
||||
}
|
||||
|
||||
if (this.downsampleFactor !== props.occlusion.params.resolutionScale) {
|
||||
needsUpdateSsao = true;
|
||||
|
||||
this.downsampleFactor = props.occlusion.params.resolutionScale;
|
||||
this.ssaoScale = this.calcSsaoScale();
|
||||
|
||||
const sw = Math.floor(w * this.ssaoScale);
|
||||
const sh = Math.floor(h * this.ssaoScale);
|
||||
|
||||
this.downsampledDepthTarget.setSize(sw, sh);
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
|
||||
if (this.ssaoScale === 1) {
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepth, this.drawPass.depthTexture);
|
||||
} else {
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepth, this.downsampledDepthTarget.texture);
|
||||
}
|
||||
|
||||
ValueCell.update(this.downsampleDepthRenderable.values.uTexSize, Vec2.set(this.downsampleDepthRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
}
|
||||
}
|
||||
|
||||
if (props.outline.name === 'on') {
|
||||
@@ -494,6 +530,13 @@ export class PostprocessingPass {
|
||||
gl.scissor(x, y, width, height);
|
||||
}
|
||||
|
||||
private occlusionOffset: [x: number, y: number] = [0, 0];
|
||||
setOcclusionOffset(x: number, y: number) {
|
||||
this.occlusionOffset[0] = x;
|
||||
this.occlusionOffset[1] = y;
|
||||
ValueCell.update(this.renderable.values.uOcclusionOffset, Vec2.set(this.renderable.values.uOcclusionOffset.ref.value, x, y));
|
||||
}
|
||||
|
||||
render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
|
||||
this.updateState(camera, transparentBackground, backgroundColor, props);
|
||||
|
||||
@@ -502,14 +545,13 @@ export class PostprocessingPass {
|
||||
this.outlinesRenderable.render();
|
||||
}
|
||||
|
||||
if (props.occlusion.name === 'on') {
|
||||
const { x, y, width, height } = camera.viewport;
|
||||
const sx = Math.floor(x * this.ssaoScale);
|
||||
const sy = Math.floor(y * this.ssaoScale);
|
||||
const sw = Math.ceil(width * this.ssaoScale);
|
||||
const sh = Math.ceil(height * this.ssaoScale);
|
||||
this.webgl.gl.viewport(sx, sy, sw, sh);
|
||||
this.webgl.gl.scissor(sx, sy, sw, sh);
|
||||
// don't render occlusion if offset is given,
|
||||
// which will reuse the existing occlusion
|
||||
if (props.occlusion.name === 'on' && this.occlusionOffset[0] === 0 && this.occlusionOffset[1] === 0) {
|
||||
if (this.ssaoScale < 1) {
|
||||
this.downsampledDepthTarget.bind();
|
||||
this.downsampleDepthRenderable.render();
|
||||
}
|
||||
|
||||
this.ssaoFramebuffer.bind();
|
||||
this.ssaoRenderable.render();
|
||||
@@ -519,9 +561,6 @@ export class PostprocessingPass {
|
||||
|
||||
this.ssaoBlurSecondPassFramebuffer.bind();
|
||||
this.ssaoBlurSecondPassRenderable.render();
|
||||
|
||||
this.webgl.gl.viewport(x, y, width, height);
|
||||
this.webgl.gl.scissor(x, y, width, height);
|
||||
}
|
||||
|
||||
if (toDrawingBuffer) {
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -165,7 +165,7 @@ export namespace Lines {
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
sizeFactor: PD.Numeric(3, { min: 0, max: 10, step: 0.1 }),
|
||||
sizeFactor: PD.Numeric(2, { min: 0, max: 10, step: 0.1 }),
|
||||
lineSizeAttenuation: PD.Boolean(false),
|
||||
};
|
||||
export type Params = typeof Params
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
@@ -34,13 +34,15 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
|
||||
|
||||
const isInstanceType = colorType.endsWith('Instance');
|
||||
const box = Box3D.fromSphere3D(Box3D(), isInstanceType ? input.boundingSphere : input.invariantBoundingSphere);
|
||||
const pad = 1 + resolution;
|
||||
const expandedBox = Box3D.expand(Box3D(), box, Vec3.create(pad, pad, pad));
|
||||
|
||||
const scaleFactor = 1 / resolution;
|
||||
const scaledBox = Box3D.scale(Box3D(), box, scaleFactor);
|
||||
const scaledBox = Box3D.scale(Box3D(), expandedBox, scaleFactor);
|
||||
const gridDim = Box3D.size(Vec3(), scaledBox);
|
||||
Vec3.ceil(gridDim, gridDim);
|
||||
Vec3.add(gridDim, gridDim, Vec3.create(2, 2, 2));
|
||||
const { min } = box;
|
||||
const { min } = expandedBox;
|
||||
|
||||
const [xn, yn] = gridDim;
|
||||
const { width, height } = getVolumeTexture2dLayout(gridDim);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { ChunkedArray } from '../../../mol-data/util';
|
||||
import { Text } from './text';
|
||||
import { getFontAtlas } from './font-atlas';
|
||||
import { assertUnreachable } from '../../../mol-util/type-helpers';
|
||||
|
||||
const quadIndices = new Uint16Array([
|
||||
0, 1, 2,
|
||||
@@ -237,7 +238,7 @@ export namespace TextBuilder {
|
||||
yBaseCenter = yTop;
|
||||
break;
|
||||
default:
|
||||
throw new Error('unsupported attachment');
|
||||
assertUnreachable(attachment);
|
||||
}
|
||||
caAdd2(mappings, xTip, yTip); // tip
|
||||
caAdd2(mappings, xBaseA, yBaseA); // base A
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
@@ -259,13 +259,15 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
|
||||
|
||||
const isInstanceType = input.colorType.endsWith('Instance');
|
||||
const box = Box3D.fromSphere3D(Box3D(), isInstanceType ? input.boundingSphere : input.invariantBoundingSphere);
|
||||
const pad = 1 + resolution;
|
||||
const expandedBox = Box3D.expand(Box3D(), box, Vec3.create(pad, pad, pad));
|
||||
|
||||
const scaleFactor = 1 / resolution;
|
||||
const scaledBox = Box3D.scale(Box3D(), box, scaleFactor);
|
||||
const scaledBox = Box3D.scale(Box3D(), expandedBox, scaleFactor);
|
||||
const gridDim = Box3D.size(Vec3(), scaledBox);
|
||||
Vec3.ceil(gridDim, gridDim);
|
||||
Vec3.add(gridDim, gridDim, Vec3.create(2, 2, 2));
|
||||
const { min } = box;
|
||||
const { min } = expandedBox;
|
||||
|
||||
const [dx, dy, dz] = gridDim;
|
||||
const { texDimX: width, texDimY: height, texCols } = getTexture2dSize(gridDim);
|
||||
@@ -308,7 +310,7 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
|
||||
accumulateTexture.attachFramebuffer(framebuffer, 0);
|
||||
countTexture.attachFramebuffer(framebuffer, 1);
|
||||
|
||||
const accumulateRenderable = getAccumulateRenderable(webgl, input, box, resolution, stride);
|
||||
const accumulateRenderable = getAccumulateRenderable(webgl, input, expandedBox, resolution, stride);
|
||||
state.currentRenderItemId = -1;
|
||||
|
||||
framebuffer.bind();
|
||||
|
||||
@@ -31,7 +31,7 @@ describe('renderer', () => {
|
||||
expect(ctx.gl.drawingBufferHeight).toBe(32);
|
||||
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(1);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(0);
|
||||
@@ -52,7 +52,7 @@ describe('renderer', () => {
|
||||
scene.add(points);
|
||||
scene.commit();
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(8);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(9);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 5 : 0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(5);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(10);
|
||||
@@ -60,7 +60,7 @@ describe('renderer', () => {
|
||||
scene.remove(points);
|
||||
scene.commit();
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(1);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(5);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(10);
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -16,7 +16,7 @@ import { GlobalUniformValues } from './renderable/schema';
|
||||
import { GraphicsRenderVariant } from './webgl/render-item';
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
import { degToRad } from '../mol-math/misc';
|
||||
import { createNullTexture, Texture, Textures } from './webgl/texture';
|
||||
import { Texture, Textures } from './webgl/texture';
|
||||
import { arrayMapUpsert } from '../mol-util/array';
|
||||
import { clamp } from '../mol-math/interpolate';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -146,9 +146,9 @@ namespace Renderer {
|
||||
|
||||
let transparentBackground = false;
|
||||
|
||||
const nullDepthTexture = createNullTexture(gl);
|
||||
const emptyDepthTexture = ctx.resources.texture('image-depth', 'depth', 'ushort', 'nearest');
|
||||
const sharedTexturesList: Textures = [
|
||||
['tDepth', nullDepthTexture]
|
||||
['tDepth', emptyDepthTexture]
|
||||
];
|
||||
|
||||
const view = Mat4();
|
||||
@@ -309,7 +309,7 @@ namespace Renderer {
|
||||
};
|
||||
|
||||
const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderWboit: boolean, markingDepthTest: boolean) => {
|
||||
arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || nullDepthTexture);
|
||||
arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || emptyDepthTexture);
|
||||
|
||||
ValueCell.update(globalUniforms.uModel, group.view);
|
||||
ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, group.view, camera.view));
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -79,6 +79,7 @@ interface Scene extends Object3D {
|
||||
has: (o: GraphicsRenderObject) => boolean
|
||||
clear: () => void
|
||||
forEach: (callbackFn: (value: GraphicsRenderable, key: GraphicsRenderObject) => void) => void
|
||||
getMarkerAverage: () => number
|
||||
}
|
||||
|
||||
namespace Scene {
|
||||
@@ -243,7 +244,18 @@ namespace Scene {
|
||||
visibleHash = computeVisibleHash();
|
||||
}
|
||||
return boundingSphereVisible;
|
||||
}
|
||||
},
|
||||
getMarkerAverage() {
|
||||
if (primitives.length === 0 && volumes.length === 0) return 0;
|
||||
let markerAverage = 0;
|
||||
for (let i = 0, il = primitives.length; i < il; ++i) {
|
||||
markerAverage += primitives[i].values.markerAverage.ref.value;
|
||||
}
|
||||
for (let i = 0, il = volumes.length; i < il; ++i) {
|
||||
markerAverage += volumes[i].values.markerAverage.ref.value;
|
||||
}
|
||||
return markerAverage / (primitives.length + volumes.length);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@ export const DirectVolumeShaderCode = ShaderCode('direct-volume', directVolume_v
|
||||
|
||||
import { image_vert } from './shader/image.vert';
|
||||
import { image_frag } from './shader/image.frag';
|
||||
import { assertUnreachable } from '../mol-util/type-helpers';
|
||||
export const ImageShaderCode = ShaderCode('image', image_vert, image_frag, { drawBuffers: 'optional' }, {}, ignoreDefineUnlit);
|
||||
|
||||
//
|
||||
@@ -228,7 +229,7 @@ function getDefinesCode(defines: ShaderDefines, ignore?: IgnoreDefine) {
|
||||
} else if (typeof v === 'boolean') {
|
||||
if (v) lines.push(`#define ${name}`);
|
||||
} else {
|
||||
throw new Error('unknown define type');
|
||||
assertUnreachable(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*
|
||||
@@ -8,61 +8,65 @@
|
||||
*/
|
||||
|
||||
export const apply_light_color = `
|
||||
#ifdef bumpEnabled
|
||||
if (uBumpFrequency > 0.0 && uBumpAmplitude > 0.0) {
|
||||
vec3 bumpNormal = perturbNormal(-vViewPosition, normal, fbm(vModelPosition * uBumpFrequency), (uBumpAmplitude * bumpiness) / uBumpFrequency);
|
||||
#ifdef enabledFragDepth
|
||||
if (!isNaN(bumpNormal.x) && !isNaN(bumpNormal.y) && !isNaN(bumpNormal.z)) {
|
||||
normal = bumpNormal;
|
||||
}
|
||||
#else
|
||||
normal = bumpNormal;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
vec4 color = material;
|
||||
|
||||
ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
|
||||
|
||||
PhysicalMaterial physicalMaterial;
|
||||
physicalMaterial.diffuseColor = color.rgb * (1.0 - metalness);
|
||||
#ifdef enabledFragDepth
|
||||
physicalMaterial.roughness = min(max(roughness, 0.0525), 1.0);
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
vec3 dxy = max(abs(dFdx(normal)), abs(dFdy(normal)));
|
||||
float geometryRoughness = max(max(dxy.x, dxy.y), dxy.z);
|
||||
physicalMaterial.roughness = min(max(roughness, 0.0525) + geometryRoughness, 1.0);
|
||||
#ifdef bumpEnabled
|
||||
if (uBumpFrequency > 0.0 && uBumpAmplitude > 0.0) {
|
||||
vec3 bumpNormal = perturbNormal(-vViewPosition, normal, fbm(vModelPosition * uBumpFrequency), (uBumpAmplitude * bumpiness) / uBumpFrequency);
|
||||
#ifdef enabledFragDepth
|
||||
if (!isNaN(bumpNormal.x) && !isNaN(bumpNormal.y) && !isNaN(bumpNormal.z)) {
|
||||
normal = bumpNormal;
|
||||
}
|
||||
#else
|
||||
normal = bumpNormal;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
vec4 color = material;
|
||||
|
||||
ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
|
||||
|
||||
PhysicalMaterial physicalMaterial;
|
||||
physicalMaterial.diffuseColor = color.rgb * (1.0 - metalness);
|
||||
#ifdef enabledFragDepth
|
||||
physicalMaterial.roughness = min(max(roughness, 0.0525), 1.0);
|
||||
#else
|
||||
vec3 dxy = max(abs(dFdx(normal)), abs(dFdy(normal)));
|
||||
float geometryRoughness = max(max(dxy.x, dxy.y), dxy.z);
|
||||
physicalMaterial.roughness = min(max(roughness, 0.0525) + geometryRoughness, 1.0);
|
||||
#endif
|
||||
physicalMaterial.specularColor = mix(vec3(0.04), color.rgb, metalness);
|
||||
physicalMaterial.specularF90 = 1.0;
|
||||
|
||||
GeometricContext geometry;
|
||||
geometry.position = -vViewPosition;
|
||||
geometry.normal = normal;
|
||||
geometry.viewDir = normalize(vViewPosition);
|
||||
|
||||
IncidentLight directLight;
|
||||
#pragma unroll_loop_start
|
||||
for (int i = 0; i < dLightCount; ++i) {
|
||||
directLight.direction = uLightDirection[i];
|
||||
directLight.color = uLightColor[i] * PI; // * PI for punctual light
|
||||
RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);
|
||||
}
|
||||
#pragma unroll_loop_end
|
||||
|
||||
vec3 irradiance = uAmbientColor * PI; // * PI for punctual light
|
||||
RE_IndirectDiffuse_Physical(irradiance, geometry, physicalMaterial, reflectedLight);
|
||||
|
||||
// indirect specular only metals
|
||||
vec3 radiance = uAmbientColor * metalness;
|
||||
vec3 iblIrradiance = uAmbientColor * metalness;
|
||||
vec3 clearcoatRadiance = vec3(0.0);
|
||||
RE_IndirectSpecular_Physical(radiance, iblIrradiance, clearcoatRadiance, geometry, physicalMaterial, reflectedLight);
|
||||
|
||||
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular;
|
||||
|
||||
gl_FragColor = vec4(outgoingLight, color.a);
|
||||
#endif
|
||||
physicalMaterial.specularColor = mix(vec3(0.04), color.rgb, metalness);
|
||||
physicalMaterial.specularF90 = 1.0;
|
||||
|
||||
GeometricContext geometry;
|
||||
geometry.position = -vViewPosition;
|
||||
geometry.normal = normal;
|
||||
geometry.viewDir = normalize(vViewPosition);
|
||||
|
||||
IncidentLight directLight;
|
||||
#pragma unroll_loop_start
|
||||
for (int i = 0; i < dLightCount; ++i) {
|
||||
directLight.direction = uLightDirection[i];
|
||||
directLight.color = uLightColor[i] * PI; // * PI for punctual light
|
||||
RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);
|
||||
}
|
||||
#pragma unroll_loop_end
|
||||
|
||||
vec3 irradiance = uAmbientColor * PI; // * PI for punctual light
|
||||
RE_IndirectDiffuse_Physical(irradiance, geometry, physicalMaterial, reflectedLight);
|
||||
|
||||
// indirect specular only metals
|
||||
vec3 radiance = uAmbientColor * metalness;
|
||||
vec3 iblIrradiance = uAmbientColor * metalness;
|
||||
vec3 clearcoatRadiance = vec3(0.0);
|
||||
RE_IndirectSpecular_Physical(radiance, iblIrradiance, clearcoatRadiance, geometry, physicalMaterial, reflectedLight);
|
||||
|
||||
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular;
|
||||
|
||||
gl_FragColor = vec4(outgoingLight, color.a);
|
||||
|
||||
#ifdef dXrayShaded
|
||||
gl_FragColor.a *= 1.0 - pow(abs(dot(normal, vec3(0.0, 0.0, 1.0))), uXrayEdgeFalloff);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -127,13 +127,9 @@ void main() {
|
||||
#elif defined(dRenderVariant_marking)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color)
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(uView)));
|
||||
vec3 normal = normalize(normalMatrix * -normalize(intersection.yzw));
|
||||
#include apply_light_color
|
||||
#endif
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(uView)));
|
||||
vec3 normal = normalize(normalMatrix * -normalize(intersection.yzw));
|
||||
#include apply_light_color
|
||||
|
||||
#include apply_interior_color
|
||||
#include apply_marker_color
|
||||
|
||||
@@ -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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Michael Krone <michael.krone@uni-tuebingen.de>
|
||||
@@ -166,9 +166,7 @@ vec3 v3m4(vec3 p, mat4 m) {
|
||||
float preFogAlphaBlended = 0.0;
|
||||
|
||||
vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
|
||||
#if !defined(dIgnoreLight)
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(uModelView * vTransform)));
|
||||
#endif
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(uModelView * vTransform)));
|
||||
mat4 cartnToUnit = uCartnToUnit * inverse4(vTransform);
|
||||
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
|
||||
mat4 modelTransform = uModel * vTransform * uTransform;
|
||||
@@ -296,24 +294,20 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
|
||||
material.rgb = mix(material.rgb, overpaint.rgb, overpaint.a);
|
||||
#endif
|
||||
|
||||
#ifdef dIgnoreLight
|
||||
if (material.a >= 0.01) {
|
||||
#ifdef dPackedGroup
|
||||
// compute gradient by central differences
|
||||
gradient.x = textureVal(unitPos - dx).a - textureVal(unitPos + dx).a;
|
||||
gradient.y = textureVal(unitPos - dy).a - textureVal(unitPos + dy).a;
|
||||
gradient.z = textureVal(unitPos - dz).a - textureVal(unitPos + dz).a;
|
||||
#else
|
||||
gradient = cell.xyz * 2.0 - 1.0;
|
||||
#endif
|
||||
vec3 normal = -normalize(normalMatrix * normalize(gradient));
|
||||
#include apply_light_color
|
||||
} else {
|
||||
gl_FragColor.rgb = material.rgb;
|
||||
#else
|
||||
if (material.a >= 0.01) {
|
||||
#ifdef dPackedGroup
|
||||
// compute gradient by central differences
|
||||
gradient.x = textureVal(unitPos - dx).a - textureVal(unitPos + dx).a;
|
||||
gradient.y = textureVal(unitPos - dy).a - textureVal(unitPos + dy).a;
|
||||
gradient.z = textureVal(unitPos - dz).a - textureVal(unitPos + dz).a;
|
||||
#else
|
||||
gradient = cell.xyz * 2.0 - 1.0;
|
||||
#endif
|
||||
vec3 normal = -normalize(normalMatrix * normalize(gradient));
|
||||
#include apply_light_color
|
||||
} else {
|
||||
gl_FragColor.rgb = material.rgb;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
gl_FragColor.a = material.a * uAlpha * uTransferScale;
|
||||
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -43,17 +43,13 @@ void main() {
|
||||
#elif defined(dRenderVariant_marking)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color)
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
#if defined(dFlatShaded)
|
||||
vec3 normal = -faceNormal;
|
||||
#else
|
||||
#if defined(dFlatShaded)
|
||||
vec3 normal = -faceNormal;
|
||||
#else
|
||||
vec3 normal = -normalize(vNormal);
|
||||
if (uDoubleSided) normal *= float(frontFacing) * 2.0 - 1.0;
|
||||
#endif
|
||||
#include apply_light_color
|
||||
vec3 normal = -normalize(vNormal);
|
||||
if (uDoubleSided) normal *= float(frontFacing) * 2.0 - 1.0;
|
||||
#endif
|
||||
#include apply_light_color
|
||||
|
||||
#include apply_interior_color
|
||||
#include apply_marker_color
|
||||
|
||||
@@ -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>
|
||||
@@ -24,8 +24,7 @@ uniform vec3 uFogColor;
|
||||
uniform vec3 uOutlineColor;
|
||||
uniform bool uTransparentBackground;
|
||||
|
||||
uniform float uOcclusionBias;
|
||||
uniform float uOcclusionRadius;
|
||||
uniform vec2 uOcclusionOffset;
|
||||
|
||||
uniform float uMaxPossibleViewZDiff;
|
||||
|
||||
@@ -102,7 +101,7 @@ void main(void) {
|
||||
if (!isBackground(depth)) {
|
||||
viewDist = abs(getViewZ(depth));
|
||||
fogFactor = smoothstep(uFogNear, uFogFar, viewDist);
|
||||
float occlusionFactor = getSsao(coords);
|
||||
float occlusionFactor = getSsao(coords + uOcclusionOffset);
|
||||
if (!uTransparentBackground) {
|
||||
color.rgb = mix(mix(occlusionColor, uFogColor, fogFactor), color.rgb, occlusionFactor);
|
||||
} else {
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -92,12 +92,8 @@ void main(void){
|
||||
#elif defined(dRenderVariant_marking)
|
||||
gl_FragColor = material;
|
||||
#elif defined(dRenderVariant_color)
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
vec3 normal = -cameraNormal;
|
||||
#include apply_light_color
|
||||
#endif
|
||||
vec3 normal = -cameraNormal;
|
||||
#include apply_light_color
|
||||
|
||||
#include apply_interior_color
|
||||
#include apply_marker_color
|
||||
|
||||
@@ -8,7 +8,7 @@ import { WebGLContext } from './context';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { RenderableSchema } from '../renderable/schema';
|
||||
import { idFactory } from '../../mol-util/id-factory';
|
||||
import { ValueOf } from '../../mol-util/type-helpers';
|
||||
import { assertUnreachable, ValueOf } from '../../mol-util/type-helpers';
|
||||
import { GLRenderingContext } from './compat';
|
||||
import { WebGLExtensions } from './extensions';
|
||||
import { WebGLState } from './state';
|
||||
@@ -66,9 +66,8 @@ function dataTypeFromArray(gl: GLRenderingContext, array: ArrayType) {
|
||||
return gl.INT;
|
||||
} else if (array instanceof Float32Array) {
|
||||
return gl.FLOAT;
|
||||
} else {
|
||||
throw new Error('Should nevver happen');
|
||||
}
|
||||
assertUnreachable(array);
|
||||
}
|
||||
|
||||
export function getBufferType(gl: GLRenderingContext, bufferType: BufferType) {
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -281,7 +281,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
|
||||
gl,
|
||||
isWebGL2: isWebGL2(gl),
|
||||
get pixelRatio() {
|
||||
const dpr = (typeof window !== 'undefined') ? window.devicePixelRatio : 1;
|
||||
const dpr = (typeof window !== 'undefined') ? (window.devicePixelRatio || 1) : 1;
|
||||
return dpr * (props.pixelScale || 1);
|
||||
},
|
||||
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -176,7 +176,7 @@ function isTexture2d(x: TextureImage<any> | TextureVolume<any>, target: number,
|
||||
return target === gl.TEXTURE_2D;
|
||||
}
|
||||
|
||||
function isTexture3d(x: TextureImage<any> | TextureVolume<any>, target: number, gl: WebGL2RenderingContext): x is TextureImage<any> {
|
||||
function isTexture3d(x: TextureImage<any> | TextureVolume<any>, target: number, gl: WebGL2RenderingContext): x is TextureVolume<any> {
|
||||
return target === gl.TEXTURE_3D;
|
||||
}
|
||||
|
||||
@@ -260,6 +260,10 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
let destroyed = false;
|
||||
|
||||
function define(_width: number, _height: number, _depth?: number) {
|
||||
if (_width === 0 || _height === 0 || (isWebGL2(gl) && target === gl.TEXTURE_3D && _depth === 0)) {
|
||||
throw new Error('empty textures are not allowed');
|
||||
}
|
||||
|
||||
if (width === _width && height === _height && depth === (_depth || 0)) return;
|
||||
|
||||
width = _width, height = _height, depth = _depth || 0;
|
||||
@@ -272,14 +276,20 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
throw new Error('unknown texture target');
|
||||
}
|
||||
}
|
||||
define(1, 1, isWebGL2(gl) && target === gl.TEXTURE_3D ? 1 : 0);
|
||||
|
||||
function load(data: TextureImage<any> | TextureVolume<any> | HTMLImageElement, sub = false) {
|
||||
if (data.width === 0 || data.height === 0 || (!isImage(data) && isWebGL2(gl) && isTexture3d(data, target, gl) && data.depth === 0)) {
|
||||
throw new Error('empty textures are not allowed');
|
||||
}
|
||||
|
||||
gl.bindTexture(target, texture);
|
||||
// unpack alignment of 1 since we use textures only for data
|
||||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
||||
gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
|
||||
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
|
||||
if (isImage(data)) {
|
||||
width = data.width, height = data.height;
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, format, type, data);
|
||||
|
||||
28
src/mol-io/common/_spec/encoder.spec.ts
Normal file
28
src/mol-io/common/_spec/encoder.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ArrayEncoding } from '../binary-cif/array-encoder';
|
||||
import { decode } from '../binary-cif/decoder';
|
||||
|
||||
const E = ArrayEncoding;
|
||||
|
||||
test('fixedPoint2', async () => {
|
||||
const fixedPoint2 = E.by(E.fixedPoint(100)).and(E.delta).and(E.integerPacking);
|
||||
|
||||
const x = [1.092, 1.960, 0.666, 0.480, 1.267];
|
||||
const y = [7.428, 7.026, 6.851, 7.524, 8.333];
|
||||
const z = [26.270, 26.561, 25.573, 27.055, 25.881];
|
||||
|
||||
const xEnc = fixedPoint2.encode(new Float32Array(x));
|
||||
const yEnc = fixedPoint2.encode(new Float32Array(y));
|
||||
const zEnc = fixedPoint2.encode(new Float32Array(z));
|
||||
|
||||
expect(xEnc.data.length).toEqual(6);
|
||||
expect(yEnc.data.length).toEqual(5);
|
||||
expect(zEnc.data.length).toEqual(6);
|
||||
|
||||
const xDec = decode(xEnc);
|
||||
const yDec = decode(yEnc);
|
||||
const zDec = decode(zEnc);
|
||||
|
||||
x.forEach((a, i) => expect(xDec[i]).toBeCloseTo(a, 2));
|
||||
y.forEach((a, i) => expect(yDec[i]).toBeCloseTo(a, 2));
|
||||
z.forEach((a, i) => expect(zDec[i]).toBeCloseTo(a, 2));
|
||||
});
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { ArrayEncoder, ArrayEncoding as E } from './array-encoder';
|
||||
import { getArrayDigitCount } from '../../../mol-util/number';
|
||||
import { assertUnreachable } from '../../../mol-util/type-helpers';
|
||||
|
||||
export function classifyIntArray(xs: ArrayLike<number>) {
|
||||
return IntClassifier.classify(xs as number[]);
|
||||
@@ -62,7 +63,7 @@ namespace IntClassifier {
|
||||
for (let i = 0, n = data.length; i < n; i++) {
|
||||
incSize(info, size, data[i]);
|
||||
}
|
||||
return { ...byteSize(size), kind: 'pack' };
|
||||
return { ...byteSize(size), kind: 'pack' as const };
|
||||
}
|
||||
|
||||
function deltaSize(data: number[], info: IntColumnInfo) {
|
||||
@@ -72,7 +73,7 @@ namespace IntClassifier {
|
||||
incSizeSigned(size, data[i] - prev);
|
||||
prev = data[i];
|
||||
}
|
||||
return { ...byteSize(size), kind: 'delta' };
|
||||
return { ...byteSize(size), kind: 'delta' as const };
|
||||
}
|
||||
|
||||
function rleSize(data: number[], info: IntColumnInfo) {
|
||||
@@ -90,7 +91,7 @@ namespace IntClassifier {
|
||||
incSize(info, size, data[data.length - 1]);
|
||||
incSize(info, size, run);
|
||||
|
||||
return { ...byteSize(size), kind: 'rle' };
|
||||
return { ...byteSize(size), kind: 'rle' as const };
|
||||
}
|
||||
|
||||
function deltaRleSize(data: number[], info: IntColumnInfo) {
|
||||
@@ -111,7 +112,7 @@ namespace IntClassifier {
|
||||
incSizeSigned(size, prevValue);
|
||||
incSizeSigned(size, run);
|
||||
|
||||
return { ...byteSize(size), kind: 'delta-rle' };
|
||||
return { ...byteSize(size), kind: 'delta-rle' as const };
|
||||
}
|
||||
|
||||
export function getSize(data: number[]) {
|
||||
@@ -132,9 +133,8 @@ namespace IntClassifier {
|
||||
case 'rle': return E.by(E.runLength).and(E.integerPacking);
|
||||
case 'delta': return E.by(E.delta).and(E.integerPacking);
|
||||
case 'delta-rle': return E.by(E.delta).and(E.runLength).and(E.integerPacking);
|
||||
default: assertUnreachable(size);
|
||||
}
|
||||
|
||||
throw new Error('should not happen :)');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,9 +169,8 @@ namespace FloatClassifier {
|
||||
case 'rle': return fp.and(E.runLength).and(E.integerPacking);
|
||||
case 'delta': return fp.and(E.delta).and(E.integerPacking);
|
||||
case 'delta-rle': return fp.and(E.delta).and(E.runLength).and(E.integerPacking);
|
||||
default: assertUnreachable(size);
|
||||
}
|
||||
|
||||
throw new Error('should not happen :)');
|
||||
}
|
||||
|
||||
function getMultiplier(mantissaDigits: number) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { Encoding, EncodedData } from './encoding';
|
||||
import { IsNativeEndianLittle, flipByteOrder } from '../binary';
|
||||
import { assertUnreachable } from '../../../mol-util/type-helpers';
|
||||
|
||||
/**
|
||||
* Fixed point, delta, RLE, integer packing adopted from https://github.com/rcsb/mmtf-javascript/
|
||||
@@ -33,7 +34,7 @@ function decodeStep(data: any, encoding: Encoding): any {
|
||||
case Encoding.IntDataType.Uint32: return uint32(data);
|
||||
case Encoding.FloatDataType.Float32: return float32(data);
|
||||
case Encoding.FloatDataType.Float64: return float64(data);
|
||||
default: throw new Error('Unsupported ByteArray type.');
|
||||
default: assertUnreachable(encoding.type);
|
||||
}
|
||||
}
|
||||
case 'FixedPoint': return fixedPoint(data, encoding);
|
||||
@@ -53,7 +54,7 @@ function getIntArray(type: Encoding.IntDataType, size: number) {
|
||||
case Encoding.IntDataType.Uint8: return new Uint8Array(size);
|
||||
case Encoding.IntDataType.Uint16: return new Uint16Array(size);
|
||||
case Encoding.IntDataType.Uint32: return new Uint32Array(size);
|
||||
default: throw new Error('Unsupported integer data type.');
|
||||
default: assertUnreachable(type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +62,7 @@ function getFloatArray(type: Encoding.FloatDataType, size: number) {
|
||||
switch (type) {
|
||||
case Encoding.FloatDataType.Float32: return new Float32Array(size);
|
||||
case Encoding.FloatDataType.Float64: return new Float64Array(size);
|
||||
default: throw new Error('Unsupported floating data type.');
|
||||
default: assertUnreachable(type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
453
src/mol-io/common/io-buffer.ts
Normal file
453
src/mol-io/common/io-buffer.ts
Normal file
@@ -0,0 +1,453 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Adapted and converted to TypeScript from https://github.com/image-js/iobuffer
|
||||
* MIT License, Copyright (c) 2015 Michaël Zasso
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { TypedArray } from '../../mol-util/type-helpers';
|
||||
|
||||
const defaultByteLength = 1024 * 8;
|
||||
const charArray: string[] = [];
|
||||
|
||||
export interface IOBufferParameters {
|
||||
offset?: number // Ignore the first n bytes of the ArrayBuffer
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for writing and reading binary data
|
||||
*/
|
||||
export class IOBuffer {
|
||||
private _lastWrittenByte: number;
|
||||
private _mark = 0;
|
||||
private _marks: number[] = [];
|
||||
private _data: DataView;
|
||||
|
||||
offset = 0; // The current offset of the buffer's pointer
|
||||
littleEndian = true;
|
||||
buffer: ArrayBuffer; // Reference to the internal ArrayBuffer object
|
||||
length: number; // Byte length of the internal ArrayBuffer
|
||||
byteLength: number; // Byte length of the internal ArrayBuffer
|
||||
byteOffset: number; // Byte offset of the internal ArrayBuffer
|
||||
|
||||
/**
|
||||
* If it's a number, it will initialize the buffer with the number as
|
||||
* the buffer's length. If it's undefined, it will initialize the buffer
|
||||
* with a default length of 8 Kb. If its an ArrayBuffer, a TypedArray,
|
||||
* it will create a view over the underlying ArrayBuffer.
|
||||
*/
|
||||
constructor(data: number | ArrayBuffer | TypedArray, params: IOBufferParameters = {}) {
|
||||
let dataIsGiven = false;
|
||||
if (data === undefined) {
|
||||
data = defaultByteLength;
|
||||
}
|
||||
if (typeof data === 'number') {
|
||||
data = new ArrayBuffer(data);
|
||||
} else {
|
||||
dataIsGiven = true;
|
||||
}
|
||||
|
||||
const offset = params.offset ? params.offset >>> 0 : 0;
|
||||
const byteLength = data.byteLength - offset;
|
||||
let dvOffset = offset;
|
||||
if (!(data instanceof ArrayBuffer)) {
|
||||
if (data.byteLength !== data.buffer.byteLength) {
|
||||
dvOffset = data.byteOffset + offset;
|
||||
}
|
||||
data = data.buffer;
|
||||
}
|
||||
if (dataIsGiven) {
|
||||
this._lastWrittenByte = byteLength;
|
||||
} else {
|
||||
this._lastWrittenByte = 0;
|
||||
}
|
||||
|
||||
this.buffer = data;
|
||||
this.length = byteLength;
|
||||
this.byteLength = byteLength;
|
||||
this.byteOffset = dvOffset;
|
||||
|
||||
this._data = new DataView(this.buffer, dvOffset, byteLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the memory allocated to the buffer is sufficient to store more bytes after the offset
|
||||
* @param byteLength The needed memory in bytes
|
||||
*/
|
||||
available(byteLength: number = 1) {
|
||||
return (this.offset + byteLength) <= this.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if little-endian mode is used for reading and writing multi-byte values
|
||||
* Returns true if little-endian mode is used, false otherwise
|
||||
*/
|
||||
isLittleEndian() {
|
||||
return this.littleEndian;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set little-endian mode for reading and writing multi-byte values
|
||||
*/
|
||||
setLittleEndian() {
|
||||
this.littleEndian = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if big-endian mode is used for reading and writing multi-byte values
|
||||
* Returns true if big-endian mode is used, false otherwise
|
||||
*/
|
||||
isBigEndian() {
|
||||
return !this.littleEndian;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches to big-endian mode for reading and writing multi-byte values
|
||||
*/
|
||||
setBigEndian() {
|
||||
this.littleEndian = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the pointer n bytes forward
|
||||
*/
|
||||
skip(n: number) {
|
||||
if (n === undefined) n = 1;
|
||||
this.offset += n;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the pointer to the given offset
|
||||
*/
|
||||
seek(offset: number) {
|
||||
this.offset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current pointer offset.
|
||||
*/
|
||||
mark() {
|
||||
this._mark = this.offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the pointer back to the last pointer offset set by mark
|
||||
*/
|
||||
reset() {
|
||||
this.offset = this._mark;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the current pointer offset to the mark stack
|
||||
*/
|
||||
pushMark() {
|
||||
this._marks.push(this.offset);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop the last pointer offset from the mark stack, and set the current pointer offset to the popped value
|
||||
*/
|
||||
popMark() {
|
||||
const offset = this._marks.pop();
|
||||
if (offset === undefined) throw new Error('Mark stack empty');
|
||||
this.seek(offset);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the pointer offset back to 0
|
||||
*/
|
||||
rewind() {
|
||||
this.offset = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the buffer has sufficient memory to write a given byteLength at the current pointer offset
|
||||
* If the buffer's memory is insufficient, this method will create a new buffer (a copy) with a length
|
||||
* that is twice (byteLength + current offset)
|
||||
*/
|
||||
ensureAvailable(byteLength: number) {
|
||||
if (byteLength === undefined) byteLength = 1;
|
||||
if (!this.available(byteLength)) {
|
||||
const lengthNeeded = this.offset + byteLength;
|
||||
const newLength = lengthNeeded * 2;
|
||||
const newArray = new Uint8Array(newLength);
|
||||
newArray.set(new Uint8Array(this.buffer));
|
||||
this.buffer = newArray.buffer;
|
||||
this.length = this.byteLength = newLength;
|
||||
this._data = new DataView(this.buffer);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a byte and return false if the byte's value is 0, or true otherwise
|
||||
* Moves pointer forward
|
||||
*/
|
||||
readBoolean() {
|
||||
return this.readUint8() !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a signed 8-bit integer and move pointer forward
|
||||
*/
|
||||
readInt8() {
|
||||
return this._data.getInt8(this.offset++);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an unsigned 8-bit integer and move pointer forward
|
||||
*/
|
||||
readUint8() {
|
||||
return this._data.getUint8(this.offset++);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for {@link IOBuffer#readUint8}
|
||||
*/
|
||||
readByte() {
|
||||
return this.readUint8();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read n bytes and move pointer forward.
|
||||
*/
|
||||
readBytes(n: number) {
|
||||
if (n === undefined) n = 1;
|
||||
const bytes = new Uint8Array(n);
|
||||
for (let i = 0; i < n; i++) {
|
||||
bytes[i] = this.readByte();
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a 16-bit signed integer and move pointer forward
|
||||
*/
|
||||
readInt16() {
|
||||
const value = this._data.getInt16(this.offset, this.littleEndian);
|
||||
this.offset += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a 16-bit unsigned integer and move pointer forward
|
||||
*/
|
||||
readUint16() {
|
||||
const value = this._data.getUint16(this.offset, this.littleEndian);
|
||||
this.offset += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a 32-bit signed integer and move pointer forward
|
||||
*/
|
||||
readInt32() {
|
||||
const value = this._data.getInt32(this.offset, this.littleEndian);
|
||||
this.offset += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a 32-bit unsigned integer and move pointer forward
|
||||
*/
|
||||
readUint32() {
|
||||
const value = this._data.getUint32(this.offset, this.littleEndian);
|
||||
this.offset += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a 32-bit floating number and move pointer forward
|
||||
*/
|
||||
readFloat32() {
|
||||
const value = this._data.getFloat32(this.offset, this.littleEndian);
|
||||
this.offset += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a 64-bit floating number and move pointer forward
|
||||
*/
|
||||
readFloat64() {
|
||||
const value = this._data.getFloat64(this.offset, this.littleEndian);
|
||||
this.offset += 8;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read 1-byte ascii character and move pointer forward
|
||||
*/
|
||||
readChar() {
|
||||
return String.fromCharCode(this.readInt8());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read n 1-byte ascii characters and move pointer forward
|
||||
*/
|
||||
readChars(n = 1) {
|
||||
charArray.length = n;
|
||||
for (let i = 0; i < n; i++) {
|
||||
charArray[i] = this.readChar();
|
||||
}
|
||||
return charArray.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write 0xff if the passed value is truthy, 0x00 otherwise
|
||||
*/
|
||||
writeBoolean(value = false) {
|
||||
this.writeUint8(value ? 0xff : 0x00);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write value as an 8-bit signed integer
|
||||
*/
|
||||
writeInt8(value: number) {
|
||||
this.ensureAvailable(1);
|
||||
this._data.setInt8(this.offset++, value);
|
||||
this._updateLastWrittenByte();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write value as a 8-bit unsigned integer
|
||||
*/
|
||||
writeUint8(value: number) {
|
||||
this.ensureAvailable(1);
|
||||
this._data.setUint8(this.offset++, value);
|
||||
this._updateLastWrittenByte();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* An alias for IOBuffer#writeUint8
|
||||
*/
|
||||
writeByte(value: number) {
|
||||
return this.writeUint8(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write bytes
|
||||
*/
|
||||
writeBytes(bytes: number[] | Uint8Array) {
|
||||
this.ensureAvailable(bytes.length);
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
this._data.setUint8(this.offset++, bytes[i]);
|
||||
}
|
||||
this._updateLastWrittenByte();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write value as an 16-bit signed integer
|
||||
*/
|
||||
writeInt16(value: number) {
|
||||
this.ensureAvailable(2);
|
||||
this._data.setInt16(this.offset, value, this.littleEndian);
|
||||
this.offset += 2;
|
||||
this._updateLastWrittenByte();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write value as a 16-bit unsigned integer
|
||||
*/
|
||||
writeUint16(value: number) {
|
||||
this.ensureAvailable(2);
|
||||
this._data.setUint16(this.offset, value, this.littleEndian);
|
||||
this.offset += 2;
|
||||
this._updateLastWrittenByte();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a 32-bit signed integer at the current pointer offset
|
||||
*/
|
||||
writeInt32(value: number) {
|
||||
this.ensureAvailable(4);
|
||||
this._data.setInt32(this.offset, value, this.littleEndian);
|
||||
this.offset += 4;
|
||||
this._updateLastWrittenByte();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a 32-bit unsigned integer at the current pointer offset
|
||||
*/
|
||||
writeUint32(value: number) {
|
||||
this.ensureAvailable(4);
|
||||
this._data.setUint32(this.offset, value, this.littleEndian);
|
||||
this.offset += 4;
|
||||
this._updateLastWrittenByte();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a 32-bit floating number at the current pointer offset
|
||||
*/
|
||||
writeFloat32(value: number) {
|
||||
this.ensureAvailable(4);
|
||||
this._data.setFloat32(this.offset, value, this.littleEndian);
|
||||
this.offset += 4;
|
||||
this._updateLastWrittenByte();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a 64-bit floating number at the current pointer offset
|
||||
*/
|
||||
writeFloat64(value: number) {
|
||||
this.ensureAvailable(8);
|
||||
this._data.setFloat64(this.offset, value, this.littleEndian);
|
||||
this.offset += 8;
|
||||
this._updateLastWrittenByte();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the charCode of the passed string's first character to the current pointer offset
|
||||
*/
|
||||
writeChar(str: string) {
|
||||
return this.writeUint8(str.charCodeAt(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the charCodes of the passed string's characters to the current pointer offset
|
||||
*/
|
||||
writeChars(str: string) {
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
this.writeUint8(str.charCodeAt(i));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a Uint8Array view of the internal buffer.
|
||||
* The view starts at the byte offset and its length
|
||||
* is calculated to stop at the last written byte or the original length.
|
||||
*/
|
||||
toArray() {
|
||||
return new Uint8Array(this.buffer, this.byteOffset, this._lastWrittenByte);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the last written byte offset
|
||||
*/
|
||||
private _updateLastWrittenByte() {
|
||||
if (this.offset > this._lastWrittenByte) {
|
||||
this._lastWrittenByte = this.offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
527
src/mol-io/common/netcdf/reader.ts
Normal file
527
src/mol-io/common/netcdf/reader.ts
Normal file
@@ -0,0 +1,527 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Adapted from https://github.com/cheminfo-js/netcdfjs
|
||||
* MIT License, Copyright (c) 2016 cheminfo
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
|
||||
import { IOBuffer } from '../io-buffer';
|
||||
|
||||
export interface NetCDFRecordDimension {
|
||||
length: number,
|
||||
id?: number,
|
||||
name?: string,
|
||||
recordStep?: number
|
||||
}
|
||||
|
||||
export interface NetCDFVariable {
|
||||
name: string
|
||||
dimensions: any[]
|
||||
attributes: any[]
|
||||
type: string
|
||||
size: number
|
||||
offset: number
|
||||
record: boolean
|
||||
}
|
||||
|
||||
export interface NetCDFHeader {
|
||||
recordDimension: NetCDFRecordDimension,
|
||||
version: number,
|
||||
dimensions: { name: string, size: number }[],
|
||||
globalAttributes: { name: string, type: string, value: string | number }[],
|
||||
variables: NetCDFVariable[]
|
||||
}
|
||||
|
||||
export interface NetCDFDimension {
|
||||
name: string,
|
||||
size: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a non-valid NetCDF exception if the statement it's true
|
||||
*/
|
||||
function notNetcdf(statement: boolean, reason: string) {
|
||||
if (statement) {
|
||||
throw new TypeError('Not a valid NetCDF v3.x file: ' + reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves 1, 2, or 3 bytes to next 4-byte boundary
|
||||
*/
|
||||
function padding(buffer: IOBuffer) {
|
||||
if ((buffer.offset % 4) !== 0) {
|
||||
buffer.skip(4 - (buffer.offset % 4));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the name
|
||||
*/
|
||||
function readName(buffer: IOBuffer) {
|
||||
// Read name
|
||||
const nameLength = buffer.readUint32();
|
||||
const name = buffer.readChars(nameLength);
|
||||
|
||||
// validate name
|
||||
// TODO
|
||||
|
||||
// Apply padding
|
||||
padding(buffer);
|
||||
return name;
|
||||
}
|
||||
|
||||
const types = {
|
||||
BYTE: 1,
|
||||
CHAR: 2,
|
||||
SHORT: 3,
|
||||
INT: 4,
|
||||
FLOAT: 5,
|
||||
DOUBLE: 6
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a number into their respective type
|
||||
*/
|
||||
function num2str(type: number) {
|
||||
switch (Number(type)) {
|
||||
case types.BYTE:
|
||||
return 'byte';
|
||||
case types.CHAR:
|
||||
return 'char';
|
||||
case types.SHORT:
|
||||
return 'short';
|
||||
case types.INT:
|
||||
return 'int';
|
||||
case types.FLOAT:
|
||||
return 'float';
|
||||
case types.DOUBLE:
|
||||
return 'double';
|
||||
default:
|
||||
return 'undefined';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a number type identifier to his size in bytes
|
||||
*/
|
||||
function num2bytes(type: number) {
|
||||
switch (Number(type)) {
|
||||
case types.BYTE:
|
||||
return 1;
|
||||
case types.CHAR:
|
||||
return 1;
|
||||
case types.SHORT:
|
||||
return 2;
|
||||
case types.INT:
|
||||
return 4;
|
||||
case types.FLOAT:
|
||||
return 4;
|
||||
case types.DOUBLE:
|
||||
return 8;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse search of num2str
|
||||
*/
|
||||
function str2num(type: string) {
|
||||
switch (String(type)) {
|
||||
case 'byte':
|
||||
return types.BYTE;
|
||||
case 'char':
|
||||
return types.CHAR;
|
||||
case 'short':
|
||||
return types.SHORT;
|
||||
case 'int':
|
||||
return types.INT;
|
||||
case 'float':
|
||||
return types.FLOAT;
|
||||
case 'double':
|
||||
return types.DOUBLE;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auxiliary function to read numeric data
|
||||
*/
|
||||
function readNumber(size: number, bufferReader: Function) {
|
||||
if (size !== 1) {
|
||||
const numbers = new Array(size);
|
||||
for (let i = 0; i < size; i++) {
|
||||
numbers[i] = bufferReader();
|
||||
}
|
||||
return numbers;
|
||||
} else {
|
||||
return bufferReader();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a type and a size reads the next element
|
||||
*/
|
||||
function readType(buffer: IOBuffer, type: number, size: number) {
|
||||
switch (type) {
|
||||
case types.BYTE:
|
||||
return buffer.readBytes(size);
|
||||
case types.CHAR:
|
||||
return trimNull(buffer.readChars(size));
|
||||
case types.SHORT:
|
||||
return readNumber(size, buffer.readInt16.bind(buffer));
|
||||
case types.INT:
|
||||
return readNumber(size, buffer.readInt32.bind(buffer));
|
||||
case types.FLOAT:
|
||||
return readNumber(size, buffer.readFloat32.bind(buffer));
|
||||
case types.DOUBLE:
|
||||
return readNumber(size, buffer.readFloat64.bind(buffer));
|
||||
default:
|
||||
notNetcdf(true, 'non valid type ' + type);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes null terminate value
|
||||
*/
|
||||
function trimNull(value: string) {
|
||||
if (value.charCodeAt(value.length - 1) === 0) {
|
||||
return value.substring(0, value.length - 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// const STREAMING = 4294967295;
|
||||
|
||||
/**
|
||||
* Read data for the given non-record variable
|
||||
*/
|
||||
function nonRecord(buffer: IOBuffer, variable: { type: string, size: number }) {
|
||||
// variable type
|
||||
const type = str2num(variable.type);
|
||||
|
||||
// size of the data
|
||||
const size = variable.size / num2bytes(type);
|
||||
|
||||
// iterates over the data
|
||||
const data = new Array(size);
|
||||
for (let i = 0; i < size; i++) {
|
||||
data[i] = readType(buffer, type, 1);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data for the given record variable
|
||||
*/
|
||||
function record(buffer: IOBuffer, variable: { type: string, size: number }, recordDimension: NetCDFRecordDimension) {
|
||||
// variable type
|
||||
const type = str2num(variable.type);
|
||||
const width = variable.size ? variable.size / num2bytes(type) : 1;
|
||||
|
||||
// size of the data
|
||||
// TODO streaming data
|
||||
const size = recordDimension.length;
|
||||
|
||||
// iterates over the data
|
||||
const data = new Array(size);
|
||||
const step = recordDimension.recordStep;
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
const currentOffset = buffer.offset;
|
||||
data[i] = readType(buffer, type, width);
|
||||
buffer.seek(currentOffset + step!);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Grammar constants
|
||||
const ZERO = 0;
|
||||
const NC_DIMENSION = 10;
|
||||
const NC_VARIABLE = 11;
|
||||
const NC_ATTRIBUTE = 12;
|
||||
|
||||
/**
|
||||
* Read the header of the file
|
||||
* Returns object with the fields:
|
||||
* - `recordDimension`: Number with the length of record dimension
|
||||
* - `dimensions`: List of dimensions
|
||||
* - `globalAttributes`: List of global attributes
|
||||
* - `variables`: List of variables
|
||||
*/
|
||||
function header(buffer: IOBuffer, version: number) {
|
||||
// Length of record dimension
|
||||
// sum of the varSize's of all the record variables.
|
||||
const header: Partial<NetCDFHeader> = { recordDimension: { length: buffer.readUint32() } };
|
||||
|
||||
// Version
|
||||
header.version = version;
|
||||
|
||||
// List of dimensions
|
||||
const dimList = dimensionsList(buffer) as { dimensions: NetCDFDimension[], recordId: number, recordName: string };
|
||||
header.recordDimension!.id = dimList.recordId;
|
||||
header.recordDimension!.name = dimList.recordName;
|
||||
header.dimensions = dimList.dimensions;
|
||||
|
||||
// List of global attributes
|
||||
header.globalAttributes = attributesList(buffer);
|
||||
|
||||
// List of variables
|
||||
const variables = variablesList(buffer, dimList.recordId, version) as { variables: any[], recordStep: number };
|
||||
header.variables = variables.variables;
|
||||
header.recordDimension!.recordStep = variables.recordStep;
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of dimensions
|
||||
*/
|
||||
function dimensionsList(buffer: IOBuffer) {
|
||||
let dimensions: NetCDFDimension[], recordId, recordName;
|
||||
const dimList = buffer.readUint32();
|
||||
if (dimList === ZERO) {
|
||||
notNetcdf((buffer.readUint32() !== ZERO), 'wrong empty tag for list of dimensions');
|
||||
return [];
|
||||
} else {
|
||||
notNetcdf((dimList !== NC_DIMENSION), 'wrong tag for list of dimensions');
|
||||
|
||||
// Length of dimensions
|
||||
const dimensionSize = buffer.readUint32();
|
||||
dimensions = new Array(dimensionSize);
|
||||
for (let dim = 0; dim < dimensionSize; dim++) {
|
||||
// Read name
|
||||
const name = readName(buffer);
|
||||
|
||||
// Read dimension size
|
||||
const size = buffer.readUint32();
|
||||
if (size === 0) {
|
||||
recordId = dim;
|
||||
recordName = name;
|
||||
}
|
||||
|
||||
dimensions[dim] = {
|
||||
name: name,
|
||||
size: size
|
||||
};
|
||||
}
|
||||
return {
|
||||
dimensions: dimensions,
|
||||
recordId: recordId,
|
||||
recordName: recordName
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of attributes
|
||||
*/
|
||||
function attributesList(buffer: IOBuffer) {
|
||||
let attributes: { name: string, type: ReturnType<typeof num2str>, value: any }[];
|
||||
const gAttList = buffer.readUint32();
|
||||
if (gAttList === ZERO) {
|
||||
notNetcdf((buffer.readUint32() !== ZERO), 'wrong empty tag for list of attributes');
|
||||
return [];
|
||||
} else {
|
||||
notNetcdf((gAttList !== NC_ATTRIBUTE), 'wrong tag for list of attributes');
|
||||
|
||||
// Length of attributes
|
||||
const attributeSize = buffer.readUint32();
|
||||
attributes = new Array(attributeSize);
|
||||
for (let gAtt = 0; gAtt < attributeSize; gAtt++) {
|
||||
// Read name
|
||||
const name = readName(buffer);
|
||||
|
||||
// Read type
|
||||
const type = buffer.readUint32();
|
||||
notNetcdf(((type < 1) || (type > 6)), 'non valid type ' + type);
|
||||
|
||||
// Read attribute
|
||||
const size = buffer.readUint32();
|
||||
const value = readType(buffer, type, size);
|
||||
|
||||
// Apply padding
|
||||
padding(buffer);
|
||||
|
||||
attributes[gAtt] = {
|
||||
name: name,
|
||||
type: num2str(type),
|
||||
value: value
|
||||
};
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of variables
|
||||
*/
|
||||
function variablesList(buffer: IOBuffer, recordId: number, version: number) {
|
||||
const varList = buffer.readUint32();
|
||||
let recordStep = 0;
|
||||
let variables;
|
||||
if (varList === ZERO) {
|
||||
notNetcdf(
|
||||
(buffer.readUint32() !== ZERO),
|
||||
'wrong empty tag for list of variables'
|
||||
);
|
||||
return [];
|
||||
} else {
|
||||
notNetcdf((varList !== NC_VARIABLE), 'wrong tag for list of variables');
|
||||
|
||||
// Length of variables
|
||||
const variableSize = buffer.readUint32();
|
||||
variables = new Array(variableSize);
|
||||
for (let v = 0; v < variableSize; v++) {
|
||||
// Read name
|
||||
const name = readName(buffer);
|
||||
|
||||
// Read dimensionality of the variable
|
||||
const dimensionality = buffer.readUint32();
|
||||
|
||||
// Index into the list of dimensions
|
||||
const dimensionsIds = new Array(dimensionality);
|
||||
for (let dim = 0; dim < dimensionality; dim++) {
|
||||
dimensionsIds[dim] = buffer.readUint32();
|
||||
}
|
||||
|
||||
// Read variables size
|
||||
const attributes = attributesList(buffer);
|
||||
|
||||
// Read type
|
||||
const type = buffer.readUint32();
|
||||
notNetcdf(((type < 1) && (type > 6)), 'non valid type ' + type);
|
||||
|
||||
// Read variable size
|
||||
// The 32-bit varSize field is not large enough to contain the
|
||||
// size of variables that require more than 2^32 - 4 bytes,
|
||||
// so 2^32 - 1 is used in the varSize field for such variables.
|
||||
const varSize = buffer.readUint32();
|
||||
|
||||
// Read offset
|
||||
let offset = buffer.readUint32();
|
||||
if (version === 2) {
|
||||
notNetcdf((offset > 0), 'offsets larger than 4GB not supported');
|
||||
offset = buffer.readUint32();
|
||||
}
|
||||
|
||||
// Count amount of record variables
|
||||
if (dimensionsIds[0] === recordId) {
|
||||
recordStep += varSize;
|
||||
}
|
||||
|
||||
variables[v] = {
|
||||
name: name,
|
||||
dimensions: dimensionsIds,
|
||||
attributes: attributes,
|
||||
type: num2str(type),
|
||||
size: varSize,
|
||||
offset: offset,
|
||||
record: (dimensionsIds[0] === recordId)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
variables: variables,
|
||||
recordStep: recordStep
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a NetCDF v3.x file
|
||||
* https://www.unidata.ucar.edu/software/netcdf/docs/file_format_specifications.html
|
||||
*/
|
||||
export class NetcdfReader {
|
||||
header: Partial<NetCDFHeader>;
|
||||
buffer: IOBuffer;
|
||||
|
||||
constructor(data: ArrayBuffer) {
|
||||
const buffer = new IOBuffer(data);
|
||||
buffer.setBigEndian();
|
||||
|
||||
// Validate that it's a NetCDF file
|
||||
notNetcdf((buffer.readChars(3) !== 'CDF'), 'should start with CDF');
|
||||
|
||||
// Check the NetCDF format
|
||||
const version = buffer.readByte();
|
||||
notNetcdf((version > 2), 'unknown version');
|
||||
|
||||
// Read the header
|
||||
this.header = header(buffer, version);
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Version for the NetCDF format
|
||||
*/
|
||||
get version() {
|
||||
if (this.header.version === 1) {
|
||||
return 'classic format';
|
||||
} else {
|
||||
return '64-bit offset format';
|
||||
}
|
||||
}
|
||||
|
||||
get recordDimension() {
|
||||
return this.header.recordDimension;
|
||||
}
|
||||
|
||||
get dimensions() {
|
||||
return this.header.dimensions;
|
||||
}
|
||||
|
||||
get globalAttributes() {
|
||||
return this.header.globalAttributes;
|
||||
}
|
||||
|
||||
get variables() {
|
||||
return this.header.variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a variable is available
|
||||
* @param {string|object} variableName - Name of the variable to check
|
||||
* @return {Boolean} - Variable existence
|
||||
*/
|
||||
hasDataVariable(variableName: string) {
|
||||
return this.header.variables && this.header.variables.findIndex(val => val.name === variableName) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the data for a given variable
|
||||
* @param {string|object} variableName - Name of the variable to search or variable object
|
||||
* @return {Array} - List with the variable values
|
||||
*/
|
||||
getDataVariable(variableName: string | NetCDFVariable) {
|
||||
let variable: NetCDFVariable | undefined;
|
||||
if (typeof variableName === 'string') {
|
||||
// search the variable
|
||||
variable = this.header.variables?.find((val) => val.name === variableName);
|
||||
} else {
|
||||
variable = variableName;
|
||||
}
|
||||
|
||||
// throws if variable not found
|
||||
if (variable === undefined) throw new Error('variable not found');
|
||||
|
||||
// go to the offset position
|
||||
this.buffer.seek(variable.offset);
|
||||
|
||||
if (variable.record) {
|
||||
// record variable case
|
||||
return record(this.buffer, variable, this.header.recordDimension!);
|
||||
} else {
|
||||
// non-record variable case
|
||||
return nonRecord(this.buffer, variable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.354, IHM 1.17, MA 1.3.3.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.356, IHM 1.17, MA 1.3.5.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -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.354, IHM 1.17, MA 1.3.3.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.356, IHM 1.17, MA 1.3.5.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -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.354, IHM 1.17, MA 1.3.3.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.356, IHM 1.17, MA 1.3.5.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -215,6 +215,28 @@ export const mmCIF_Schema = {
|
||||
* formal charge assignment normally found in chemical diagrams.
|
||||
*/
|
||||
pdbx_formal_charge: int,
|
||||
/**
|
||||
* This data item is an ordinal which identifies distinct chemical components in the atom_site category, both
|
||||
* polymeric and non-polymeric.
|
||||
*/
|
||||
pdbx_label_index: int,
|
||||
/**
|
||||
* The name of additional external databases with residue level mapping.
|
||||
*/
|
||||
pdbx_sifts_xref_db_name: str,
|
||||
/**
|
||||
* The accession code related to the additional external database entry.
|
||||
*/
|
||||
pdbx_sifts_xref_db_acc: str,
|
||||
/**
|
||||
* The sequence position of the external database entry that corresponds
|
||||
* to the residue mapping defined by the SIFTS process.
|
||||
*/
|
||||
pdbx_sifts_xref_db_num: str,
|
||||
/**
|
||||
* Describes the residue type of the given UniProt match
|
||||
*/
|
||||
pdbx_sifts_xref_db_res: str,
|
||||
/**
|
||||
* The model id corresponding to the atom site.
|
||||
* This data item is a pointer to _ihm_model_list.model_id
|
||||
@@ -682,7 +704,7 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* An abbreviation that identifies the database.
|
||||
*/
|
||||
database_id: Aliased<'AFDB' | 'CAS' | 'CSD' | 'EMDB' | 'ICSD' | 'MA' | 'MDF' | 'MODBASE' | 'NDB' | 'NBS' | 'PDB' | 'PDF' | 'RCSB' | 'SMR' | 'EBI' | 'PDBE' | 'BMRB' | 'WWPDB' | 'PDB_ACC'>(str),
|
||||
database_id: Aliased<'AlphaFoldDB' | 'CAS' | 'CSD' | 'EMDB' | 'ICSD' | 'ModelArchive' | 'MDF' | 'MODBASE' | 'NDB' | 'NBS' | 'PDB' | 'PDF' | 'RCSB' | 'SWISS-MODEL_REPOSITORY' | 'EBI' | 'PDBE' | 'BMRB' | 'WWPDB' | 'PDB_ACC'>(str),
|
||||
/**
|
||||
* The code assigned by the database identified in
|
||||
* _database_2.database_id.
|
||||
@@ -2211,6 +2233,10 @@ export const mmCIF_Schema = {
|
||||
oligomeric_count: int,
|
||||
/**
|
||||
* A description of special aspects of the macromolecular assembly.
|
||||
*
|
||||
* In the PDB, 'representative helical assembly', 'complete point assembly',
|
||||
* 'complete icosahedral assembly', 'software_defined_assembly', 'author_defined_assembly',
|
||||
* and 'author_and_software_defined_assembly' are considered "biologically relevant assemblies.
|
||||
*/
|
||||
details: str,
|
||||
/**
|
||||
|
||||
89
src/mol-io/reader/nctraj/parser.ts
Normal file
89
src/mol-io/reader/nctraj/parser.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Task } from '../../../mol-task';
|
||||
import { Mutable } from '../../../mol-util/type-helpers';
|
||||
import { NetcdfReader } from '../../common/netcdf/reader';
|
||||
import { ReaderResult as Result } from '../result';
|
||||
|
||||
export interface NctrajFile {
|
||||
coordinates: number[][],
|
||||
velocities?: number[][],
|
||||
forces?: number[][],
|
||||
cell_lengths?: number[][],
|
||||
cell_angles?: number[][],
|
||||
time?: number[],
|
||||
timeOffset: number,
|
||||
deltaTime: number
|
||||
}
|
||||
|
||||
async function parseInternal(data: Uint8Array) {
|
||||
// http://ambermd.org/netcdf/nctraj.xhtml
|
||||
|
||||
const nc = new NetcdfReader(data);
|
||||
|
||||
const f: Mutable<NctrajFile> = {
|
||||
coordinates: [],
|
||||
time: [],
|
||||
timeOffset: 0,
|
||||
deltaTime: 1
|
||||
};
|
||||
|
||||
for (const c of nc.getDataVariable('coordinates')) f.coordinates.push(c);
|
||||
|
||||
if (nc.hasDataVariable('velocities')) {
|
||||
const velocities: number[][] = [];
|
||||
for (const v of nc.getDataVariable('velocities')) velocities.push(v);
|
||||
f.velocities = velocities;
|
||||
}
|
||||
|
||||
if (nc.hasDataVariable('forces')) {
|
||||
const forces: number[][] = [];
|
||||
for (const f of nc.getDataVariable('forces')) forces.push(f);
|
||||
f.forces = forces;
|
||||
}
|
||||
|
||||
if (nc.hasDataVariable('cell_lengths')) {
|
||||
const cell_lengths: number[][] = [];
|
||||
for (const l of nc.getDataVariable('cell_lengths')) cell_lengths.push(l);
|
||||
f.cell_lengths = cell_lengths;
|
||||
}
|
||||
|
||||
if (nc.hasDataVariable('cell_angles')) {
|
||||
const cell_angles: number[][] = [];
|
||||
for (const a of nc.getDataVariable('cell_angles')) cell_angles.push(a);
|
||||
f.cell_angles = cell_angles;
|
||||
}
|
||||
|
||||
if (nc.hasDataVariable('time')) {
|
||||
const time: number[] = [];
|
||||
for (const t of nc.getDataVariable('time')) time.push(t);
|
||||
f.time = time;
|
||||
}
|
||||
|
||||
if (f.time) {
|
||||
if (f.time.length >= 1) {
|
||||
f.timeOffset = f.time[0];
|
||||
}
|
||||
if (f.time.length >= 2) {
|
||||
f.deltaTime = f.time[1] - f.time[0];
|
||||
}
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
export function parseNctraj(data: Uint8Array) {
|
||||
return Task.create<Result<NctrajFile>>('Parse NCTRAJ', async ctx => {
|
||||
try {
|
||||
ctx.update({ canAbort: true, message: 'Parsing trajectory...' });
|
||||
const file = await parseInternal(data);
|
||||
return Result.success(file);
|
||||
} catch (e) {
|
||||
return Result.error('' + e);
|
||||
}
|
||||
});
|
||||
}
|
||||
176
src/mol-io/reader/prmtop/parser.ts
Normal file
176
src/mol-io/reader/prmtop/parser.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Task, RuntimeContext } from '../../../mol-task';
|
||||
import { Tokenizer, TokenBuilder, Tokens } from '../common/text/tokenizer';
|
||||
import { ReaderResult as Result } from '../result';
|
||||
import { TokenColumnProvider as TokenColumn } from '../common/text/column/token';
|
||||
import { Column } from '../../../mol-data/db';
|
||||
import { Mutable } from '../../../mol-util/type-helpers';
|
||||
|
||||
// http://ambermd.org/prmtop.pdf
|
||||
// https://ambermd.org/FileFormats.php#topology
|
||||
|
||||
const Pointers = {
|
||||
'NATOM': '', 'NTYPES': '', 'NBONH': '', 'MBONA': '', 'NTHETH': '', 'MTHETA': '',
|
||||
'NPHIH': '', 'MPHIA': '', 'NHPARM': '', 'NPARM': '', 'NNB': '', 'NRES': '',
|
||||
'NBONA': '', 'NTHETA': '', 'NPHIA': '', 'NUMBND': '', 'NUMANG': '', 'NPTRA': '',
|
||||
'NATYP': '', 'NPHB': '', 'IFPERT': '', 'NBPER': '', 'NGPER': '', 'NDPER': '',
|
||||
'MBPER': '', 'MGPER': '', 'MDPER': '', 'IFBOX': '', 'NMXRS': '', 'IFCAP': '',
|
||||
'NUMEXTRA': '', 'NCOPY': '',
|
||||
};
|
||||
type PointerName = keyof typeof Pointers;
|
||||
const PointersNames = Object.keys(Pointers) as PointerName[];
|
||||
|
||||
export interface PrmtopFile {
|
||||
readonly version: string
|
||||
readonly title: ReadonlyArray<string>
|
||||
readonly pointers: Readonly<Record<PointerName, number>>
|
||||
readonly atomName: Column<string>
|
||||
readonly charge: Column<number>
|
||||
readonly mass: Column<number>
|
||||
readonly residueLabel: Column<string>
|
||||
readonly residuePointer: Column<number>
|
||||
readonly bondsIncHydrogen: Column<number>
|
||||
readonly bondsWithoutHydrogen: Column<number>
|
||||
readonly radii: Column<number>
|
||||
}
|
||||
|
||||
const { readLine, markLine, trim } = Tokenizer;
|
||||
|
||||
function State(tokenizer: Tokenizer, runtimeCtx: RuntimeContext) {
|
||||
return {
|
||||
tokenizer,
|
||||
runtimeCtx,
|
||||
};
|
||||
}
|
||||
type State = ReturnType<typeof State>
|
||||
|
||||
function handleTitle(state: State): string[] {
|
||||
const { tokenizer } = state;
|
||||
const title: string[] = [];
|
||||
|
||||
while (tokenizer.tokenEnd < tokenizer.length) {
|
||||
if (tokenizer.data[tokenizer.position] === '%') break;
|
||||
const line = readLine(tokenizer).trim();
|
||||
if (line) title.push(line);
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
function handlePointers(state: State): Record<PointerName, number> {
|
||||
const { tokenizer } = state;
|
||||
|
||||
const pointers: Record<PointerName, number> = Object.create(null);
|
||||
PointersNames.forEach(name => { pointers[name] = 0; });
|
||||
|
||||
let curIdx = 0;
|
||||
while (tokenizer.tokenEnd < tokenizer.length) {
|
||||
if (tokenizer.data[tokenizer.position] === '%') break;
|
||||
const line = readLine(tokenizer);
|
||||
|
||||
const n = Math.min(curIdx + 10, 32);
|
||||
for (let i = 0; curIdx < n; ++i, ++curIdx) {
|
||||
pointers[PointersNames[curIdx]] = parseInt(
|
||||
line.substring(i * 8, i * 8 + 8).trim()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return pointers;
|
||||
}
|
||||
|
||||
function handleTokens(state: State, count: number, countPerLine: number, itemSize: number): Tokens {
|
||||
const { tokenizer } = state;
|
||||
|
||||
const tokens = TokenBuilder.create(tokenizer.data, count * 2);
|
||||
|
||||
let curIdx = 0;
|
||||
while (tokenizer.tokenEnd < tokenizer.length) {
|
||||
if (tokenizer.data[tokenizer.position] === '%') break;
|
||||
|
||||
tokenizer.tokenStart = tokenizer.position;
|
||||
const n = Math.min(curIdx + countPerLine, count);
|
||||
for (let i = 0; curIdx < n; ++i, ++curIdx) {
|
||||
const p = tokenizer.position;
|
||||
trim(tokenizer, tokenizer.position, tokenizer.position + itemSize);
|
||||
TokenBuilder.addUnchecked(tokens, tokenizer.tokenStart, tokenizer.tokenEnd);
|
||||
tokenizer.position = p + itemSize;
|
||||
}
|
||||
|
||||
markLine(tokenizer);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<PrmtopFile>> {
|
||||
const t = Tokenizer(data);
|
||||
const state = State(t, ctx);
|
||||
|
||||
const result: Mutable<PrmtopFile> = Object.create(null);
|
||||
let prevPosition = 0;
|
||||
|
||||
while (t.tokenEnd < t.length) {
|
||||
if (t.position - prevPosition > 100000 && ctx.shouldUpdate) {
|
||||
prevPosition = t.position;
|
||||
await ctx.update({ current: t.position, max: t.length });
|
||||
}
|
||||
|
||||
const line = readLine(state.tokenizer).trim();
|
||||
if (line.startsWith('%VERSION')) {
|
||||
result.version = line.substring(8).trim();
|
||||
} else if (line.startsWith('%FLAG')) {
|
||||
const flag = line.substring(5).trim();
|
||||
const formatLine = readLine(state.tokenizer).trim();
|
||||
if (!formatLine.startsWith('%FORMAT')) throw new Error('expected %FORMAT');
|
||||
|
||||
if (flag === 'TITLE') {
|
||||
result.title = handleTitle(state);
|
||||
} else if (flag === 'POINTERS') {
|
||||
result.pointers = handlePointers(state);
|
||||
} else if (flag === 'ATOM_NAME') {
|
||||
const tokens = handleTokens(state, result.pointers['NATOM'], 20, 4);
|
||||
result.atomName = TokenColumn(tokens)(Column.Schema.str);
|
||||
} else if (flag === 'CHARGE') {
|
||||
const tokens = handleTokens(state, result.pointers['NATOM'], 5, 16);
|
||||
result.charge = TokenColumn(tokens)(Column.Schema.float);
|
||||
} else if (flag === 'MASS') {
|
||||
const tokens = handleTokens(state, result.pointers['NATOM'], 5, 16);
|
||||
result.mass = TokenColumn(tokens)(Column.Schema.float);
|
||||
} else if (flag === 'RESIDUE_LABEL') {
|
||||
const tokens = handleTokens(state, result.pointers['NRES'], 20, 4);
|
||||
result.residueLabel = TokenColumn(tokens)(Column.Schema.str);
|
||||
} else if (flag === 'RESIDUE_POINTER') {
|
||||
const tokens = handleTokens(state, result.pointers['NRES'], 10, 8);
|
||||
result.residuePointer = TokenColumn(tokens)(Column.Schema.int);
|
||||
} else if (flag === 'BONDS_INC_HYDROGEN') {
|
||||
const tokens = handleTokens(state, result.pointers['NBONH'] * 3, 10, 8);
|
||||
result.bondsIncHydrogen = TokenColumn(tokens)(Column.Schema.int);
|
||||
} else if (flag === 'BONDS_WITHOUT_HYDROGEN') {
|
||||
const tokens = handleTokens(state, result.pointers['NBONA'] * 3, 10, 8);
|
||||
result.bondsWithoutHydrogen = TokenColumn(tokens)(Column.Schema.int);
|
||||
} else if (flag === 'RADII') {
|
||||
const tokens = handleTokens(state, result.pointers['NATOM'], 5, 16);
|
||||
result.radii = TokenColumn(tokens)(Column.Schema.float);
|
||||
} else {
|
||||
while (t.tokenEnd < t.length) {
|
||||
if (t.data[t.position] === '%') break;
|
||||
markLine(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
export function parsePrmtop(data: string) {
|
||||
return Task.create<Result<PrmtopFile>>('Parse PRMTOP', async ctx => {
|
||||
return await parseInternal(data, ctx);
|
||||
});
|
||||
}
|
||||
303
src/mol-io/reader/top/parser.ts
Normal file
303
src/mol-io/reader/top/parser.ts
Normal file
@@ -0,0 +1,303 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Task, RuntimeContext } from '../../../mol-task';
|
||||
import { Tokenizer, TokenBuilder } from '../common/text/tokenizer';
|
||||
import { ReaderResult as Result } from '../result';
|
||||
import { TokenColumnProvider as TokenColumn } from '../common/text/column/token';
|
||||
import { Column, Table } from '../../../mol-data/db';
|
||||
import { Mutable } from '../../../mol-util/type-helpers';
|
||||
|
||||
// https://manual.gromacs.org/2021-current/reference-manual/file-formats.html#top
|
||||
|
||||
const AtomsSchema = {
|
||||
nr: Column.Schema.Int(),
|
||||
type: Column.Schema.Str(),
|
||||
resnr: Column.Schema.Int(),
|
||||
residu: Column.Schema.Str(),
|
||||
atom: Column.Schema.Str(),
|
||||
cgnr: Column.Schema.Int(),
|
||||
charge: Column.Schema.Float(),
|
||||
mass: Column.Schema.Float(),
|
||||
};
|
||||
|
||||
const BondsSchema = {
|
||||
ai: Column.Schema.Int(),
|
||||
aj: Column.Schema.Int(),
|
||||
};
|
||||
|
||||
const MoleculesSchema = {
|
||||
compound: Column.Schema.Str(),
|
||||
molCount: Column.Schema.Int(),
|
||||
};
|
||||
|
||||
type Compound = {
|
||||
atoms: Table<typeof AtomsSchema>
|
||||
bonds?: Table<typeof BondsSchema>
|
||||
}
|
||||
|
||||
export interface TopFile {
|
||||
readonly system: string
|
||||
readonly molecules: Table<typeof MoleculesSchema>
|
||||
readonly compounds: Record<string, Compound>
|
||||
}
|
||||
|
||||
const { readLine, markLine, skipWhitespace, markStart, eatValue, eatLine } = Tokenizer;
|
||||
|
||||
function State(tokenizer: Tokenizer, runtimeCtx: RuntimeContext) {
|
||||
return {
|
||||
tokenizer,
|
||||
runtimeCtx,
|
||||
};
|
||||
}
|
||||
type State = ReturnType<typeof State>
|
||||
|
||||
const reField = /\[ (.+) \]/;
|
||||
const reWhitespace = /\s+/;
|
||||
|
||||
function handleMoleculetype(state: State) {
|
||||
const { tokenizer } = state;
|
||||
|
||||
let molName: string | undefined = undefined;
|
||||
|
||||
while (tokenizer.tokenEnd < tokenizer.length) {
|
||||
skipWhitespace(tokenizer);
|
||||
const c = tokenizer.data[tokenizer.position];
|
||||
if (c === '[') break;
|
||||
if (c === ';' || c === '*') {
|
||||
markLine(tokenizer);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (molName !== undefined) throw new Error('more than one molName');
|
||||
|
||||
const line = readLine(tokenizer);
|
||||
molName = line.split(reWhitespace)[0];
|
||||
}
|
||||
|
||||
if (molName === undefined) throw new Error('missing molName');
|
||||
|
||||
return molName;
|
||||
}
|
||||
|
||||
function handleAtoms(state: State) {
|
||||
const { tokenizer } = state;
|
||||
|
||||
const nr = TokenBuilder.create(tokenizer.data, 64);
|
||||
const type = TokenBuilder.create(tokenizer.data, 64);
|
||||
const resnr = TokenBuilder.create(tokenizer.data, 64);
|
||||
const residu = TokenBuilder.create(tokenizer.data, 64);
|
||||
const atom = TokenBuilder.create(tokenizer.data, 64);
|
||||
const cgnr = TokenBuilder.create(tokenizer.data, 64);
|
||||
const charge = TokenBuilder.create(tokenizer.data, 64);
|
||||
const mass = TokenBuilder.create(tokenizer.data, 64);
|
||||
|
||||
while (tokenizer.tokenEnd < tokenizer.length) {
|
||||
skipWhitespace(tokenizer);
|
||||
const c = tokenizer.data[tokenizer.position];
|
||||
if (c === '[') break;
|
||||
if (c === ';' || c === '*') {
|
||||
markLine(tokenizer);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let j = 0; j < 8; ++j) {
|
||||
skipWhitespace(tokenizer);
|
||||
markStart(tokenizer);
|
||||
eatValue(tokenizer);
|
||||
|
||||
switch (j) {
|
||||
case 0: TokenBuilder.add(nr, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 1: TokenBuilder.add(type, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 2: TokenBuilder.add(resnr, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 3: TokenBuilder.add(residu, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 4: TokenBuilder.add(atom, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 5: TokenBuilder.add(cgnr, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 6: TokenBuilder.add(charge, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 7: TokenBuilder.add(mass, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
}
|
||||
}
|
||||
// ignore any extra columns
|
||||
markLine(tokenizer);
|
||||
}
|
||||
|
||||
return Table.ofColumns(AtomsSchema, {
|
||||
nr: TokenColumn(nr)(Column.Schema.int),
|
||||
type: TokenColumn(type)(Column.Schema.str),
|
||||
resnr: TokenColumn(resnr)(Column.Schema.int),
|
||||
residu: TokenColumn(residu)(Column.Schema.str),
|
||||
atom: TokenColumn(atom)(Column.Schema.str),
|
||||
cgnr: TokenColumn(cgnr)(Column.Schema.int),
|
||||
charge: TokenColumn(charge)(Column.Schema.float),
|
||||
mass: TokenColumn(mass)(Column.Schema.float),
|
||||
});
|
||||
}
|
||||
|
||||
function handleBonds(state: State) {
|
||||
const { tokenizer } = state;
|
||||
|
||||
const ai = TokenBuilder.create(tokenizer.data, 64);
|
||||
const aj = TokenBuilder.create(tokenizer.data, 64);
|
||||
|
||||
while (tokenizer.tokenEnd < tokenizer.length) {
|
||||
skipWhitespace(tokenizer);
|
||||
const c = tokenizer.data[tokenizer.position];
|
||||
if (c === '[') break;
|
||||
if (c === ';' || c === '*') {
|
||||
markLine(tokenizer);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let j = 0; j < 2; ++j) {
|
||||
skipWhitespace(tokenizer);
|
||||
markStart(tokenizer);
|
||||
eatValue(tokenizer);
|
||||
|
||||
switch (j) {
|
||||
case 0: TokenBuilder.add(ai, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 1: TokenBuilder.add(aj, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
}
|
||||
}
|
||||
// ignore any extra columns
|
||||
markLine(tokenizer);
|
||||
}
|
||||
|
||||
return Table.ofColumns(BondsSchema, {
|
||||
ai: TokenColumn(ai)(Column.Schema.int),
|
||||
aj: TokenColumn(aj)(Column.Schema.int),
|
||||
});
|
||||
}
|
||||
|
||||
function handleSystem(state: State) {
|
||||
const { tokenizer } = state;
|
||||
|
||||
let system: string | undefined = undefined;
|
||||
|
||||
while (tokenizer.tokenEnd < tokenizer.length) {
|
||||
skipWhitespace(tokenizer);
|
||||
const c = tokenizer.data[tokenizer.position];
|
||||
if (c === '[') break;
|
||||
if (c === ';' || c === '*') {
|
||||
markLine(tokenizer);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (system !== undefined) throw new Error('more than one system');
|
||||
system = readLine(tokenizer).trim();
|
||||
}
|
||||
|
||||
if (system === undefined) throw new Error('missing system');
|
||||
|
||||
return system;
|
||||
}
|
||||
|
||||
function handleMolecules(state: State) {
|
||||
const { tokenizer } = state;
|
||||
|
||||
const compound = TokenBuilder.create(tokenizer.data, 64);
|
||||
const molCount = TokenBuilder.create(tokenizer.data, 64);
|
||||
|
||||
while (tokenizer.tokenEnd < tokenizer.length) {
|
||||
skipWhitespace(tokenizer);
|
||||
if (tokenizer.position >= tokenizer.length) break;
|
||||
|
||||
const c = tokenizer.data[tokenizer.position];
|
||||
if (c === '[') break;
|
||||
if (c === ';' || c === '*') {
|
||||
markLine(tokenizer);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let j = 0; j < 2; ++j) {
|
||||
skipWhitespace(tokenizer);
|
||||
markStart(tokenizer);
|
||||
eatValue(tokenizer);
|
||||
|
||||
switch (j) {
|
||||
case 0: TokenBuilder.add(compound, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
case 1: TokenBuilder.add(molCount, tokenizer.tokenStart, tokenizer.tokenEnd); break;
|
||||
}
|
||||
}
|
||||
// ignore any extra columns
|
||||
eatLine(tokenizer);
|
||||
markStart(tokenizer);
|
||||
}
|
||||
|
||||
return Table.ofColumns(MoleculesSchema, {
|
||||
compound: TokenColumn(compound)(Column.Schema.str),
|
||||
molCount: TokenColumn(molCount)(Column.Schema.int),
|
||||
});
|
||||
}
|
||||
|
||||
async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<TopFile>> {
|
||||
const t = Tokenizer(data);
|
||||
const state = State(t, ctx);
|
||||
|
||||
const result: Mutable<TopFile> = Object.create(null);
|
||||
let prevPosition = 0;
|
||||
|
||||
result.compounds = {};
|
||||
let currentCompound: Partial<Compound> = {};
|
||||
let currentMolName = '';
|
||||
|
||||
function addMol() {
|
||||
if (currentMolName && currentCompound.atoms) {
|
||||
result.compounds[currentMolName] = currentCompound as Compound;
|
||||
currentCompound = {};
|
||||
currentMolName = '';
|
||||
}
|
||||
}
|
||||
|
||||
while (t.tokenEnd < t.length) {
|
||||
if (t.position - prevPosition > 100000 && ctx.shouldUpdate) {
|
||||
prevPosition = t.position;
|
||||
await ctx.update({ current: t.position, max: t.length });
|
||||
}
|
||||
|
||||
const line = readLine(state.tokenizer).trim();
|
||||
|
||||
if (!line || line[0] === '*' || line[0] === ';') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith('#include')) {
|
||||
throw new Error('#include statements not allowed');
|
||||
}
|
||||
|
||||
if (line.startsWith('[')) {
|
||||
const fieldMatch = line.match(reField);
|
||||
if (fieldMatch === null) throw new Error('expected field name');
|
||||
|
||||
const fieldName = fieldMatch[1];
|
||||
if (fieldName === 'moleculetype') {
|
||||
addMol();
|
||||
currentMolName = handleMoleculetype(state);
|
||||
} else if (fieldName === 'atoms') {
|
||||
currentCompound.atoms = handleAtoms(state);
|
||||
} else if (fieldName === 'bonds') {
|
||||
currentCompound.bonds = handleBonds(state);
|
||||
} else if (fieldName === 'system') {
|
||||
result.system = handleSystem(state);
|
||||
} else if (fieldName === 'molecules') {
|
||||
addMol(); // add the last compound
|
||||
result.molecules = handleMolecules(state);
|
||||
} else {
|
||||
while (t.tokenEnd < t.length) {
|
||||
if (t.data[t.position] === '[') break;
|
||||
markLine(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
export function parseTop(data: string) {
|
||||
return Task.create<Result<TopFile>>('Parse TOP', async ctx => {
|
||||
return await parseInternal(data, ctx);
|
||||
});
|
||||
}
|
||||
157
src/mol-io/reader/trr/parser.ts
Normal file
157
src/mol-io/reader/trr/parser.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Adapted from NGL.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Task } from '../../../mol-task';
|
||||
import { ReaderResult as Result } from '../result';
|
||||
|
||||
export interface TrrFile {
|
||||
frames: { count: number, x: Float32Array, y: Float32Array, z: Float32Array }[],
|
||||
boxes: number[][],
|
||||
times: number[],
|
||||
timeOffset: number,
|
||||
deltaTime: number
|
||||
}
|
||||
|
||||
async function parseInternal(data: Uint8Array) {
|
||||
// https://github.com/gromacs/gromacs/blob/master/src/gromacs/fileio/trrio.cpp
|
||||
|
||||
const dv = new DataView(data.buffer);
|
||||
|
||||
const f: TrrFile = {
|
||||
frames: [],
|
||||
boxes: [],
|
||||
times: [],
|
||||
timeOffset: 0,
|
||||
deltaTime: 0
|
||||
};
|
||||
const coordinates = f.frames;
|
||||
const boxes = f.boxes;
|
||||
const times = f.times;
|
||||
|
||||
let offset = 0;
|
||||
|
||||
while (true) {
|
||||
// const magicnum = dv.getInt32(offset)
|
||||
// const i1 = dv.getFloat32(offset + 4)
|
||||
offset += 8;
|
||||
|
||||
const versionSize = dv.getInt32(offset);
|
||||
offset += 4;
|
||||
offset += versionSize;
|
||||
|
||||
// const irSize = dv.getInt32(offset)
|
||||
// const eSize = dv.getInt32(offset + 4)
|
||||
const boxSize = dv.getInt32(offset + 8);
|
||||
const virSize = dv.getInt32(offset + 12);
|
||||
const presSize = dv.getInt32(offset + 16);
|
||||
// const topSize = dv.getInt32(offset + 20)
|
||||
// const symSize = dv.getInt32(offset + 24)
|
||||
const coordSize = dv.getInt32(offset + 28);
|
||||
const velocitySize = dv.getInt32(offset + 32);
|
||||
const forceSize = dv.getInt32(offset + 36);
|
||||
const natoms = dv.getInt32(offset + 40);
|
||||
// const step = dv.getInt32(offset + 44)
|
||||
// const nre = dv.getInt32(offset + 48)
|
||||
offset += 52;
|
||||
|
||||
const floatSize = boxSize / 9;
|
||||
const natoms3 = natoms * 3;
|
||||
|
||||
// let lambda
|
||||
if (floatSize === 8) {
|
||||
times.push(dv.getFloat64(offset));
|
||||
// lambda = dv.getFloat64(offset + 8)
|
||||
} else {
|
||||
times.push(dv.getFloat32(offset));
|
||||
// lambda = dv.getFloat32(offset + 4)
|
||||
}
|
||||
offset += 2 * floatSize;
|
||||
|
||||
if (boxSize) {
|
||||
const box = new Float32Array(9);
|
||||
if (floatSize === 8) {
|
||||
for (let i = 0; i < 9; ++i) {
|
||||
box[i] = dv.getFloat64(offset) * 10;
|
||||
offset += 8;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < 9; ++i) {
|
||||
box[i] = dv.getFloat32(offset) * 10;
|
||||
offset += 4;
|
||||
}
|
||||
}
|
||||
boxes.push(box as unknown as number[]);
|
||||
}
|
||||
|
||||
// ignore, unused
|
||||
offset += virSize;
|
||||
|
||||
// ignore, unused
|
||||
offset += presSize;
|
||||
|
||||
if (coordSize) {
|
||||
const x = new Float32Array(natoms);
|
||||
const y = new Float32Array(natoms);
|
||||
const z = new Float32Array(natoms);
|
||||
if (floatSize === 8) {
|
||||
for (let i = 0; i < natoms; ++i) {
|
||||
x[i] = dv.getFloat64(offset) * 10;
|
||||
y[i] = dv.getFloat64(offset + 8) * 10;
|
||||
z[i] = dv.getFloat64(offset + 16) * 10;
|
||||
offset += 24;
|
||||
}
|
||||
} else {
|
||||
const tmp = new Uint32Array(data.buffer, offset, natoms3);
|
||||
for (let i = 0; i < natoms3; ++i) {
|
||||
const value = tmp[i];
|
||||
tmp[i] = (
|
||||
((value & 0xFF) << 24) | ((value & 0xFF00) << 8) |
|
||||
((value >> 8) & 0xFF00) | ((value >> 24) & 0xFF)
|
||||
);
|
||||
}
|
||||
const frameCoords = new Float32Array(data.buffer, offset, natoms3);
|
||||
for (let i = 0; i < natoms; ++i) {
|
||||
x[i] = frameCoords[i * 3] * 10;
|
||||
y[i] = frameCoords[i * 3 + 1] * 10;
|
||||
z[i] = frameCoords[i * 3 + 2] * 10;
|
||||
offset += 12;
|
||||
}
|
||||
}
|
||||
coordinates.push({ count: natoms, x, y, z });
|
||||
}
|
||||
|
||||
// ignore, unused
|
||||
offset += velocitySize;
|
||||
|
||||
// ignore, unused
|
||||
offset += forceSize;
|
||||
|
||||
if (offset >= data.byteLength) break;
|
||||
}
|
||||
|
||||
if (times.length >= 1) {
|
||||
f.timeOffset = times[0];
|
||||
}
|
||||
if (times.length >= 2) {
|
||||
f.deltaTime = times[1] - times[0];
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
export function parseTrr(data: Uint8Array) {
|
||||
return Task.create<Result<TrrFile>>('Parse TRR', async ctx => {
|
||||
try {
|
||||
ctx.update({ canAbort: true, message: 'Parsing trajectory...' });
|
||||
const file = await parseInternal(data);
|
||||
return Result.success(file);
|
||||
} catch (e) {
|
||||
return Result.error('' + e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { Tensor } from '../../../mol-math/linear-algebra';
|
||||
import { Encoder as EncoderBase } from '../encoder';
|
||||
import { ArrayEncoder, ArrayEncoding } from '../../common/binary-cif';
|
||||
import { BinaryEncodingProvider } from './encoder/binary';
|
||||
import { assertUnreachable } from '../../../mol-util/type-helpers';
|
||||
|
||||
// TODO: support for "coordinate fields", make "coordinate precision" a parameter of the encoder
|
||||
// TODO: automatically detect "precision" of floating point arrays.
|
||||
@@ -324,7 +325,7 @@ function cifFieldsFromTableSchema(schema: Table.Schema) {
|
||||
} else if (t.valueType === 'tensor') {
|
||||
fields.push(...getTensorDefinitions(k, t.space));
|
||||
} else {
|
||||
throw new Error(`Unknown valueType ${t.valueType}`);
|
||||
assertUnreachable(t.valueType);
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
@@ -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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -26,7 +26,8 @@ export type DensityData = {
|
||||
transform: Mat4,
|
||||
field: Tensor,
|
||||
idField: Tensor,
|
||||
resolution: number
|
||||
resolution: number,
|
||||
maxRadius: number,
|
||||
}
|
||||
|
||||
export type DensityTextureData = {
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -21,12 +21,12 @@ export type GaussianDensityProps = typeof DefaultGaussianDensityProps
|
||||
|
||||
export type GaussianDensityData = {
|
||||
radiusFactor: number
|
||||
resolution: number
|
||||
} & DensityData
|
||||
|
||||
export type GaussianDensityTextureData = {
|
||||
radiusFactor: number
|
||||
resolution: number
|
||||
maxRadius: number
|
||||
} & DensityTextureData
|
||||
|
||||
export function computeGaussianDensity(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps) {
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -129,5 +129,5 @@ export async function GaussianDensityCPU(ctx: RuntimeContext, position: Position
|
||||
Mat4.fromScaling(transform, Vec3.create(resolution, resolution, resolution));
|
||||
Mat4.setTranslation(transform, expandedBox.min);
|
||||
|
||||
return { field, idField, transform, radiusFactor: 1, resolution };
|
||||
return { field, idField, transform, radiusFactor: 1, resolution, maxRadius };
|
||||
}
|
||||
@@ -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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Michael Krone <michael.krone@uni-tuebingen.de>
|
||||
@@ -70,12 +70,12 @@ export function GaussianDensityGPU(position: PositionData, box: Box3D, radius: (
|
||||
// it's faster than texture3d
|
||||
// console.time('GaussianDensityTexture2d')
|
||||
const tmpTexture = getTexture('tmp', webgl, 'image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
const { scale, bbox, texture, gridDim, gridTexDim, radiusFactor, resolution } = calcGaussianDensityTexture2d(webgl, position, box, radius, false, props, tmpTexture);
|
||||
const { scale, bbox, texture, gridDim, gridTexDim, radiusFactor, resolution, maxRadius } = calcGaussianDensityTexture2d(webgl, position, box, radius, false, props, tmpTexture);
|
||||
// webgl.waitForGpuCommandsCompleteSync()
|
||||
// console.timeEnd('GaussianDensityTexture2d')
|
||||
const { field, idField } = fieldFromTexture2d(webgl, texture, gridDim, gridTexDim);
|
||||
|
||||
return { field, idField, transform: getTransform(scale, bbox), radiusFactor, resolution };
|
||||
return { field, idField, transform: getTransform(scale, bbox), radiusFactor, resolution, maxRadius };
|
||||
}
|
||||
|
||||
export function GaussianDensityTexture(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, oldTexture?: Texture): GaussianDensityTextureData {
|
||||
@@ -92,8 +92,8 @@ export function GaussianDensityTexture3d(webgl: WebGLContext, position: Position
|
||||
return finalizeGaussianDensityTexture(calcGaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture));
|
||||
}
|
||||
|
||||
function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor, resolution }: _GaussianDensityTextureData): GaussianDensityTextureData {
|
||||
return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor, resolution };
|
||||
function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor, resolution, maxRadius }: _GaussianDensityTextureData): GaussianDensityTextureData {
|
||||
return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor, resolution, maxRadius };
|
||||
}
|
||||
|
||||
function getTransform(scale: Vec3, bbox: Box3D) {
|
||||
@@ -114,6 +114,7 @@ type _GaussianDensityTextureData = {
|
||||
gridTexScale: Vec2
|
||||
radiusFactor: number
|
||||
resolution: number
|
||||
maxRadius: number
|
||||
}
|
||||
|
||||
function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, powerOfTwo: boolean, props: GaussianDensityProps, texture?: Texture): _GaussianDensityTextureData {
|
||||
@@ -198,7 +199,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
|
||||
|
||||
// printTexture(webgl, minDistTex, 0.75);
|
||||
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale, radiusFactor, resolution };
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale, radiusFactor, resolution, maxRadius };
|
||||
}
|
||||
|
||||
function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture): _GaussianDensityTextureData {
|
||||
@@ -254,7 +255,7 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
|
||||
setupGroupIdRendering(webgl, renderable);
|
||||
render(texture, false);
|
||||
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale, radiusFactor, resolution };
|
||||
return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale, radiusFactor, resolution, maxRadius };
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -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 Fred Ludlow <fred.ludlow@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -370,5 +370,5 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
Mat4.fromScaling(transform, Vec3.create(resolution, resolution, resolution));
|
||||
Mat4.setTranslation(transform, expandedBox.min);
|
||||
// console.log({ field, idField, transform, updateChunk })
|
||||
return { field, idField, transform, resolution };
|
||||
return { field, idField, transform, resolution, maxRadius };
|
||||
}
|
||||
@@ -32,7 +32,9 @@ namespace Box3D {
|
||||
|
||||
/** Get box from sphere, uses extrema if available */
|
||||
export function fromSphere3D(out: Box3D, sphere: Sphere3D): Box3D {
|
||||
if (Sphere3D.hasExtrema(sphere)) return fromVec3Array(out, sphere.extrema);
|
||||
if (Sphere3D.hasExtrema(sphere) && sphere.extrema.length >= 14) { // 14 extrema with coarse boundary helper
|
||||
return fromVec3Array(out, sphere.extrema);
|
||||
}
|
||||
const r = Vec3.create(sphere.radius, sphere.radius, sphere.radius);
|
||||
Vec3.sub(out.min, sphere.center, r);
|
||||
Vec3.add(out.max, sphere.center, r);
|
||||
|
||||
@@ -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>
|
||||
@@ -11,6 +11,7 @@ import { OrderedSet } from '../../../mol-data/int';
|
||||
import { NumberArray, PickRequired } from '../../../mol-util/type-helpers';
|
||||
import { Box3D } from './box3d';
|
||||
import { Axes3D } from './axes3d';
|
||||
import { PrincipalAxes } from '../../linear-algebra/matrix/principal-axes';
|
||||
|
||||
interface Sphere3D {
|
||||
center: Vec3,
|
||||
@@ -202,11 +203,35 @@ namespace Sphere3D {
|
||||
return out;
|
||||
}
|
||||
if (hasExtrema(sphere)) {
|
||||
const positions = new Float32Array(sphere.extrema.length * 3);
|
||||
for (let i = 0; i < sphere.extrema.length; i++) {
|
||||
Vec3.toArray(sphere.extrema[i], positions, i * 3);
|
||||
}
|
||||
|
||||
const axes = PrincipalAxes.calculateMomentsAxes(positions);
|
||||
Axes3D.scale(axes, Axes3D.normalize(axes, axes), delta);
|
||||
|
||||
setExtrema(out, sphere.extrema.map(e => {
|
||||
Vec3.sub(tmpDir, e, sphere.center);
|
||||
const dist = Vec3.distance(sphere.center, e);
|
||||
Vec3.normalize(tmpDir, tmpDir);
|
||||
return Vec3.scaleAndAdd(Vec3(), sphere.center, tmpDir, dist + delta);
|
||||
Vec3.normalize(tmpDir, Vec3.sub(tmpDir, e, sphere.center));
|
||||
const o = Vec3.clone(e);
|
||||
|
||||
const sA = Vec3.dot(tmpDir, axes.dirA) < 0 ? -1 : 1;
|
||||
Vec3.scaleAndAdd(o, o, axes.dirA, sA);
|
||||
|
||||
const sB = Vec3.dot(tmpDir, axes.dirB) < 0 ? -1 : 1;
|
||||
Vec3.scaleAndAdd(o, o, axes.dirB, sB);
|
||||
|
||||
const sC = Vec3.dot(tmpDir, axes.dirC) < 0 ? -1 : 1;
|
||||
Vec3.scaleAndAdd(o, o, axes.dirC, sC);
|
||||
|
||||
if (Vec3.distance(out.center, o) > out.radius) {
|
||||
if (sphere.extrema.length >= 14) { // 14 extrema with coarse boundary helper
|
||||
Vec3.normalize(tmpDir, Vec3.sub(tmpDir, o, sphere.center));
|
||||
}
|
||||
Vec3.scaleAndAdd(o, out.center, tmpDir, out.radius);
|
||||
}
|
||||
|
||||
return o;
|
||||
}));
|
||||
}
|
||||
return out;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
@@ -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: [] };
|
||||
@@ -45,15 +45,6 @@ function findHierarchyOffsets(atom_site: AtomSite) {
|
||||
return { residues, chains };
|
||||
}
|
||||
|
||||
function substUndefinedColumn<T extends Table<any>>(table: T, a: keyof T, b: keyof T) {
|
||||
if (!(table as any)[a].isDefined) {
|
||||
(table as any)[a] = (table as any)[b];
|
||||
}
|
||||
if (!(table as any)[b].isDefined) {
|
||||
(table as any)[b] = (table as any)[a];
|
||||
}
|
||||
}
|
||||
|
||||
function createHierarchyData(atom_site: AtomSite, sourceIndex: Column<number>, offsets: { residues: ArrayLike<number>, chains: ArrayLike<number> }): AtomicData {
|
||||
const atoms = Table.ofColumns(AtomsSchema, {
|
||||
type_symbol: Column.ofArray({ array: Column.mapToArray(atom_site.type_symbol, ElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }),
|
||||
@@ -87,12 +78,6 @@ function createHierarchyData(atom_site: AtomSite, sourceIndex: Column<number>, o
|
||||
Table.columnToArray(residues, 'label_seq_id', Int32Array);
|
||||
Table.columnToArray(residues, 'auth_seq_id', Int32Array);
|
||||
|
||||
// Fix possibly missing auth_/label_ columns
|
||||
substUndefinedColumn(atoms, 'label_atom_id', 'auth_atom_id');
|
||||
substUndefinedColumn(atoms, 'label_comp_id', 'auth_comp_id');
|
||||
substUndefinedColumn(residues, 'label_seq_id', 'auth_seq_id');
|
||||
substUndefinedColumn(chains, 'label_asym_id', 'auth_asym_id');
|
||||
|
||||
return { atoms, residues, chains, atomSourceIndex: sourceIndex };
|
||||
}
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -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) {
|
||||
@@ -168,10 +186,8 @@ function splitTable<T extends Table<any>>(table: T, col: Column<number>) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
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 +222,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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -7,6 +7,7 @@
|
||||
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Table } from '../../../mol-data/db';
|
||||
import { mmCIF_chemComp_schema } from '../../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
import { getNormalizedAtomSite } from './util';
|
||||
|
||||
// TODO split into conformation and hierarchy parts
|
||||
|
||||
@@ -68,7 +69,7 @@ export interface BasicData {
|
||||
pdbx_molecule: Molecule
|
||||
}
|
||||
|
||||
export function createBasic(data: Partial<BasicData>): BasicData {
|
||||
export function createBasic(data: Partial<BasicData>, normalize = false): BasicData {
|
||||
const basic = Object.create(null);
|
||||
for (const name of Object.keys(BasicSchema)) {
|
||||
if (name in data) {
|
||||
@@ -77,5 +78,8 @@ export function createBasic(data: Partial<BasicData>): BasicData {
|
||||
basic[name] = Table.ofUndefinedColumns(BasicSchema[name as keyof typeof BasicSchema], 0);
|
||||
}
|
||||
}
|
||||
if (normalize) {
|
||||
basic.atom_site = getNormalizedAtomSite(basic.atom_site);
|
||||
}
|
||||
return basic;
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* 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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { BasicData } from './schema';
|
||||
import { Table } from '../../../mol-data/db';
|
||||
import { AtomSite, BasicData } from './schema';
|
||||
import { Column, Table } from '../../../mol-data/db';
|
||||
|
||||
export function getModelGroupName(model_id: number, data: BasicData) {
|
||||
const { ihm_model_group, ihm_model_group_link } = data;
|
||||
@@ -17,4 +17,28 @@ export function getModelGroupName(model_id: number, data: BasicData) {
|
||||
if (group) return group.name;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
function hasPresentValues(column: Column<any>) {
|
||||
for (let i = 0, il = column.rowCount; i < il; i++) {
|
||||
if (column.valueKind(i) === Column.ValueKind.Present) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function substUndefinedColumn<T extends Table<any>>(table: T, a: keyof T, b: keyof T) {
|
||||
if (!table[a].isDefined || !hasPresentValues(table[a])) table[a] = table[b];
|
||||
if (!table[b].isDefined || !hasPresentValues(table[b])) table[b] = table[a];
|
||||
}
|
||||
|
||||
/** Fix possibly missing auth_/label_ columns */
|
||||
export function getNormalizedAtomSite(atom_site: AtomSite) {
|
||||
const normalized = Table.ofColumns(atom_site._schema, atom_site);
|
||||
substUndefinedColumn(normalized, 'label_atom_id', 'auth_atom_id');
|
||||
substUndefinedColumn(normalized, 'label_comp_id', 'auth_comp_id');
|
||||
substUndefinedColumn(normalized, 'label_seq_id', 'auth_seq_id');
|
||||
substUndefinedColumn(normalized, 'label_asym_id', 'auth_asym_id');
|
||||
return normalized;
|
||||
}
|
||||
@@ -100,7 +100,7 @@ async function getModels(db: CifCore_Database, format: CifCoreFormat, ctx: Runti
|
||||
const element_symbol = new Array<string>(atomCount);
|
||||
for (let i = 0; i < atomCount; ++i) {
|
||||
// TODO can take as is if type_symbol not given?
|
||||
element_symbol[i] = guessElementSymbolString(label.value(i));
|
||||
element_symbol[i] = guessElementSymbolString(label.value(i), '');
|
||||
}
|
||||
typeSymbol = Column.ofStringArray(element_symbol);
|
||||
formalCharge = Column.Undefined(atomCount, Column.Schema.int);
|
||||
@@ -146,13 +146,13 @@ async function getModels(db: CifCore_Database, format: CifCoreFormat, ctx: Runti
|
||||
componentBuilder.setNames([['MOL', name || 'Unknown Molecule']]);
|
||||
componentBuilder.add('MOL', 0);
|
||||
|
||||
const basics = createBasic({
|
||||
const basic = createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
chem_comp: componentBuilder.getChemCompTable(),
|
||||
atom_site
|
||||
});
|
||||
|
||||
const models = await createModels(basics, format, ctx);
|
||||
const models = await createModels(basic, format, ctx);
|
||||
|
||||
if (models.frameCount > 0) {
|
||||
const first = models.representative;
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -9,6 +9,7 @@ import { WaterNames, PolymerNames } from '../../../mol-model/structure/model/typ
|
||||
import { SetUtils } from '../../../mol-util/set';
|
||||
import { BasicSchema } from '../basic/schema';
|
||||
import { mmCIF_chemComp_schema } from '../../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
import { SaccharideCompIdMap } from '../../../mol-model/structure/structure/carbohydrates/constants';
|
||||
|
||||
type Component = Table.Row<Pick<mmCIF_chemComp_schema, 'id' | 'name' | 'type'>>
|
||||
|
||||
@@ -30,7 +31,7 @@ const DnaAtomIdsList = [
|
||||
|
||||
/** Used to reduce false positives for atom name-based type guessing */
|
||||
const NonPolymerNames = new Set([
|
||||
'FMN', 'NCN', 'FNS', 'FMA' // Mononucleotides
|
||||
'FMN', 'NCN', 'FNS', 'FMA', 'ATP', 'ADP', 'AMP', 'GTP', 'GDP', 'GMP', // Mononucleotides
|
||||
]);
|
||||
|
||||
const StandardComponents = (function () {
|
||||
@@ -156,8 +157,10 @@ export class ComponentBuilder {
|
||||
this.set(StandardComponents.get(compId)!);
|
||||
} else if (WaterNames.has(compId)) {
|
||||
this.set({ id: compId, name: 'WATER', type: 'non-polymer' });
|
||||
} else if (NonPolymerNames.has(compId)) {
|
||||
} else if (NonPolymerNames.has(compId.toUpperCase())) {
|
||||
this.set({ id: compId, name: this.namesMap.get(compId) || compId, type: 'non-polymer' });
|
||||
} else if (SaccharideCompIdMap.has(compId.toUpperCase())) {
|
||||
this.set({ id: compId, name: this.namesMap.get(compId) || compId, type: 'saccharide' });
|
||||
} else {
|
||||
const atomIds = this.getAtomIds(index);
|
||||
if (atomIds.size === 1 && CharmmIonComponents.has(compId)) {
|
||||
|
||||
@@ -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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Model } from '../../../mol-model/structure';
|
||||
@@ -44,7 +45,7 @@ interface FormatPropertyProvider<T> {
|
||||
}
|
||||
|
||||
namespace FormatPropertyProvider {
|
||||
export function create<T>(descriptor: CustomPropertyDescriptor): FormatPropertyProvider<T> {
|
||||
export function create<T>(descriptor: CustomPropertyDescriptor, options?: { asDynamic?: boolean }): FormatPropertyProvider<T> {
|
||||
const { name } = descriptor;
|
||||
const formatRegistry = new FormatRegistry<T>();
|
||||
|
||||
@@ -55,21 +56,31 @@ namespace FormatPropertyProvider {
|
||||
return formatRegistry.isApplicable(model);
|
||||
},
|
||||
get(model: Model): T | undefined {
|
||||
if (model._staticPropertyData[name]) return model._staticPropertyData[name];
|
||||
const store = options?.asDynamic ? model._dynamicPropertyData : model._staticPropertyData;
|
||||
|
||||
if (store[name]) return store[name];
|
||||
if (model.customProperties.has(descriptor)) return;
|
||||
|
||||
const obtain = formatRegistry.get(model.sourceData.kind);
|
||||
if (!obtain) return;
|
||||
|
||||
model._staticPropertyData[name] = obtain(model);
|
||||
store[name] = obtain(model);
|
||||
model.customProperties.add(descriptor);
|
||||
return model._staticPropertyData[name];
|
||||
return store[name];
|
||||
},
|
||||
set(model: Model, value: T) {
|
||||
model._staticPropertyData[name] = value;
|
||||
if (options?.asDynamic) {
|
||||
model._dynamicPropertyData[name] = value;
|
||||
} else {
|
||||
model._staticPropertyData[name] = value;
|
||||
}
|
||||
},
|
||||
delete(model: Model) {
|
||||
delete model._staticPropertyData[name];
|
||||
if (options?.asDynamic) {
|
||||
delete model._dynamicPropertyData[name];
|
||||
} else {
|
||||
delete model._staticPropertyData[name];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,13 +53,13 @@ async function getModels(cube: CubeFile, ctx: RuntimeContext) {
|
||||
componentBuilder.setNames([['MOL', 'Unknown Molecule']]);
|
||||
componentBuilder.add('MOL', 0);
|
||||
|
||||
const basics = createBasic({
|
||||
const basic = createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
chem_comp: componentBuilder.getChemCompTable(),
|
||||
atom_site
|
||||
});
|
||||
|
||||
return await createModels(basics, MolFormat.create(cube), ctx);
|
||||
return await createModels(basic, MolFormat.create(cube), ctx);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -27,6 +27,7 @@ function getBasic(atoms: GroAtoms, modelNum: number): BasicData {
|
||||
const asymIds = new Array<string>(atoms.count);
|
||||
const seqIds = new Uint32Array(atoms.count);
|
||||
const ids = new Uint32Array(atoms.count);
|
||||
const typeSymbol = new Array<string>(atoms.count);
|
||||
|
||||
const entityBuilder = new EntityBuilder();
|
||||
const componentBuilder = new ComponentBuilder(atoms.residueNumber, atoms.atomName);
|
||||
@@ -66,6 +67,8 @@ function getBasic(atoms: GroAtoms, modelNum: number): BasicData {
|
||||
asymIds[i] = currentAsymId;
|
||||
seqIds[i] = currentSeqId;
|
||||
ids[i] = i;
|
||||
|
||||
typeSymbol[i] = guessElementSymbolString(atoms.atomName.value(i), atoms.residueName.value(i));
|
||||
}
|
||||
|
||||
const auth_asym_id = Column.ofStringArray(asymIds);
|
||||
@@ -87,7 +90,7 @@ function getBasic(atoms: GroAtoms, modelNum: number): BasicData {
|
||||
label_entity_id: Column.ofStringArray(entityIds),
|
||||
|
||||
occupancy: Column.ofConst(1, atoms.count, Column.Schema.float),
|
||||
type_symbol: Column.ofStringArray(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))),
|
||||
type_symbol: Column.ofStringArray(typeSymbol),
|
||||
|
||||
pdbx_PDB_model_num: Column.ofConst(modelNum, atoms.count, Column.Schema.int),
|
||||
}, atoms.count);
|
||||
|
||||
@@ -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>
|
||||
@@ -19,6 +19,7 @@ import { ComponentBond } from './property/bonds/chem_comp';
|
||||
import { StructConn } from './property/bonds/struct_conn';
|
||||
import { Trajectory } from '../../mol-model/structure';
|
||||
import { GlobalModelTransformInfo } from '../../mol-model/structure/model/properties/global-transform';
|
||||
import { createBasic } from './basic/schema';
|
||||
|
||||
function modelSymmetryFromMmcif(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return;
|
||||
@@ -100,5 +101,6 @@ namespace MmcifFormat {
|
||||
|
||||
export function trajectoryFromMmCIF(frame: CifFrame): Task<Trajectory> {
|
||||
const format = MmcifFormat.fromFrame(frame);
|
||||
return Task.create('Create mmCIF Model', ctx => createModels(format.data.db, format, ctx));
|
||||
const basic = createBasic(format.data.db, true);
|
||||
return Task.create('Create mmCIF Model', ctx => createModels(basic, format, ctx));
|
||||
}
|
||||
@@ -68,13 +68,13 @@ export async function getMolModels(mol: MolFile, format: ModelFormat<any> | unde
|
||||
componentBuilder.setNames([['MOL', 'Unknown Molecule']]);
|
||||
componentBuilder.add('MOL', 0);
|
||||
|
||||
const basics = createBasic({
|
||||
const basic = createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
chem_comp: componentBuilder.getChemCompTable(),
|
||||
atom_site
|
||||
});
|
||||
|
||||
const models = await createModels(basics, format ?? MolFormat.create(mol), ctx);
|
||||
const models = await createModels(basic, format ?? MolFormat.create(mol), ctx);
|
||||
|
||||
if (models.frameCount > 0) {
|
||||
const indexA = Column.ofIntArray(Column.mapToArray(bonds.atomIdxA, x => x - 1, Int32Array));
|
||||
|
||||
@@ -41,7 +41,7 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
|
||||
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));
|
||||
: guessElementSymbolString(atoms.atom_name.value(i), atoms.subst_name.value(i));
|
||||
}
|
||||
|
||||
const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
|
||||
@@ -75,13 +75,13 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
|
||||
componentBuilder.add(atoms.subst_name.value(i), i);
|
||||
}
|
||||
|
||||
const basics = createBasic({
|
||||
const basic = createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
chem_comp: componentBuilder.getChemCompTable(),
|
||||
atom_site
|
||||
});
|
||||
|
||||
const _models = await createModels(basics, Mol2Format.create(mol2), ctx);
|
||||
const _models = await createModels(basic, Mol2Format.create(mol2), ctx);
|
||||
|
||||
if (_models.frameCount > 0) {
|
||||
const indexA = Column.ofIntArray(Column.mapToArray(bonds.origin_atom_id, x => x - 1, Int32Array));
|
||||
@@ -103,11 +103,11 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
|
||||
const flag = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => {
|
||||
switch (x) {
|
||||
case 'ar': // aromatic
|
||||
case 'am': // amide
|
||||
return BondType.Flag.Aromatic | BondType.Flag.Covalent;
|
||||
case 'du': // dummy
|
||||
case 'nc': // not connected
|
||||
return BondType.Flag.None;
|
||||
case 'am': // amide
|
||||
case 'un': // unknown
|
||||
default:
|
||||
return BondType.Flag.Covalent;
|
||||
|
||||
52
src/mol-model-formats/structure/nctraj.ts
Normal file
52
src/mol-model-formats/structure/nctraj.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Task } from '../../mol-task';
|
||||
import { NctrajFile } from '../../mol-io/reader/nctraj/parser';
|
||||
import { Coordinates, Frame, Time } from '../../mol-model/structure/coordinates';
|
||||
import { Cell } from '../../mol-math/geometry/spacegroup/cell';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Mutable } from '../../mol-util/type-helpers';
|
||||
|
||||
export function coordinatesFromNctraj(file: NctrajFile): Task<Coordinates> {
|
||||
return Task.create('Parse NCTRAJ', async ctx => {
|
||||
await ctx.update('Converting to coordinates');
|
||||
|
||||
const deltaTime = Time(file.deltaTime, 'step');
|
||||
const offsetTime = Time(file.timeOffset, deltaTime.unit);
|
||||
|
||||
const frames: Frame[] = [];
|
||||
for (let i = 0, il = file.coordinates.length; i < il; ++i) {
|
||||
const c = file.coordinates[i];
|
||||
const elementCount = c.length / 3;
|
||||
const x = new Float32Array(elementCount);
|
||||
const y = new Float32Array(elementCount);
|
||||
const z = new Float32Array(elementCount);
|
||||
for (let j = 0, jl = c.length; j < jl; j += 3) {
|
||||
x[j / 3] = c[j];
|
||||
y[j / 3] = c[j + 1];
|
||||
z[j / 3] = c[j + 2];
|
||||
}
|
||||
const frame: Mutable<Frame> = {
|
||||
elementCount,
|
||||
x, y, z,
|
||||
xyzOrdering: { isIdentity: true },
|
||||
time: Time(offsetTime.value + deltaTime.value * i, deltaTime.unit)
|
||||
};
|
||||
// TODO: handle case where cell_lengths and cell_angles are set, i.e., angles not 90deg
|
||||
if (file.cell_lengths) {
|
||||
const lengths = file.cell_lengths[i];
|
||||
const x = Vec3.scale(Vec3(), Vec3.unitX, lengths[0]);
|
||||
const y = Vec3.scale(Vec3(), Vec3.unitY, lengths[1]);
|
||||
const z = Vec3.scale(Vec3(), Vec3.unitZ, lengths[2]);
|
||||
frame.cell = Cell.fromBasis(x, y, z);
|
||||
}
|
||||
frames.push(frame);
|
||||
}
|
||||
|
||||
return Coordinates.create(frames, deltaTime, offsetTime);
|
||||
});
|
||||
}
|
||||
@@ -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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -14,6 +14,7 @@ import { Column } from '../../mol-data/db';
|
||||
import { AtomPartialCharge } from './property/partial-charge';
|
||||
import { Trajectory } from '../../mol-model/structure';
|
||||
import { ModelFormat } from '../format';
|
||||
import { createBasic } from './basic/schema';
|
||||
|
||||
export { PdbFormat };
|
||||
|
||||
@@ -34,7 +35,8 @@ export function trajectoryFromPDB(pdb: PdbFile): Task<Trajectory> {
|
||||
await ctx.update('Converting to mmCIF');
|
||||
const cif = await pdbToMmCif(pdb);
|
||||
const format = MmcifFormat.fromFrame(cif, undefined, PdbFormat.create(pdb));
|
||||
const models = await createModels(format.data.db, format, ctx);
|
||||
const basic = createBasic(format.data.db, true);
|
||||
const models = await createModels(basic, format, ctx);
|
||||
const partial_charge = cif.categories['atom_site']?.getField('partial_charge');
|
||||
if (partial_charge) {
|
||||
// TODO works only for single, unsorted model, to work generally
|
||||
|
||||
174
src/mol-model-formats/structure/prmtop.ts
Normal file
174
src/mol-model-formats/structure/prmtop.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* Copyright (c) 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 { PrmtopFile } from '../../mol-io/reader/prmtop/parser';
|
||||
import { getMoleculeType, MoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { Topology } from '../../mol-model/structure/topology/topology';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ModelFormat } from '../format';
|
||||
import { BasicSchema, createBasic } from './basic/schema';
|
||||
import { ComponentBuilder } from './common/component';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
import { getChainId } from './common/util';
|
||||
import { guessElementSymbolString } from './util';
|
||||
|
||||
function getBasic(prmtop: PrmtopFile) {
|
||||
const { pointers, residuePointer, residueLabel, atomName } = prmtop;
|
||||
const atomCount = pointers.NATOM;
|
||||
const residueCount = pointers.NRES;
|
||||
|
||||
//
|
||||
|
||||
const residueIds = new Uint32Array(atomCount);
|
||||
const residueNames: string[] = [];
|
||||
|
||||
const addResidue = (i: number, from: number, to: number) => {
|
||||
const rn = residueLabel.value(i);
|
||||
for (let j = from, jl = to; j < jl; ++j) {
|
||||
residueIds[j] = i + 1;
|
||||
residueNames[j] = rn;
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0, il = residueCount - 1; i < il; ++i) {
|
||||
addResidue(i, residuePointer.value(i) - 1, residuePointer.value(i + 1) - 1);
|
||||
|
||||
}
|
||||
addResidue(residueCount - 1, residuePointer.value(residueCount - 1) - 1, atomCount);
|
||||
|
||||
const residueId = Column.ofIntArray(residueIds);
|
||||
const residueName = Column.ofStringArray(residueNames);
|
||||
|
||||
//
|
||||
|
||||
const entityIds = new Array<string>(atomCount);
|
||||
const asymIds = new Array<string>(atomCount);
|
||||
const seqIds = new Uint32Array(atomCount);
|
||||
const ids = new Uint32Array(atomCount);
|
||||
|
||||
const entityBuilder = new EntityBuilder();
|
||||
const componentBuilder = new ComponentBuilder(residueId, atomName);
|
||||
|
||||
let currentEntityId = '';
|
||||
let currentAsymIndex = 0;
|
||||
let currentAsymId = '';
|
||||
let currentSeqId = 0;
|
||||
let prevMoleculeType = MoleculeType.Unknown;
|
||||
let prevResidueNumber = -1;
|
||||
|
||||
for (let i = 0, il = atomCount; i < il; ++i) {
|
||||
const residueNumber = residueId.value(i);
|
||||
if (residueNumber !== prevResidueNumber) {
|
||||
const compId = residueName.value(i);
|
||||
const moleculeType = getMoleculeType(componentBuilder.add(compId, i).type, compId);
|
||||
|
||||
if (moleculeType !== prevMoleculeType) {
|
||||
currentAsymId = getChainId(currentAsymIndex);
|
||||
currentAsymIndex += 1;
|
||||
currentSeqId = 0;
|
||||
}
|
||||
|
||||
currentEntityId = entityBuilder.getEntityId(compId, moleculeType, currentAsymId);
|
||||
currentSeqId += 1;
|
||||
|
||||
prevResidueNumber = residueNumber;
|
||||
prevMoleculeType = moleculeType;
|
||||
}
|
||||
|
||||
entityIds[i] = currentEntityId;
|
||||
asymIds[i] = currentAsymId;
|
||||
seqIds[i] = currentSeqId;
|
||||
ids[i] = i;
|
||||
}
|
||||
|
||||
const id = Column.ofIntArray(ids);
|
||||
const asym_id = Column.ofStringArray(asymIds);
|
||||
|
||||
//
|
||||
|
||||
const type_symbol = new Array<string>(atomCount);
|
||||
for (let i = 0; i < atomCount; ++i) {
|
||||
type_symbol[i] = guessElementSymbolString(atomName.value(i), residueName.value(i));
|
||||
}
|
||||
|
||||
const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
|
||||
auth_asym_id: asym_id,
|
||||
auth_atom_id: Column.asArrayColumn(atomName),
|
||||
auth_comp_id: residueName,
|
||||
auth_seq_id: residueId,
|
||||
id: Column.asArrayColumn(id),
|
||||
|
||||
label_asym_id: asym_id,
|
||||
label_atom_id: Column.asArrayColumn(atomName),
|
||||
label_comp_id: residueName,
|
||||
label_seq_id: Column.ofIntArray(seqIds),
|
||||
label_entity_id: Column.ofStringArray(entityIds),
|
||||
|
||||
occupancy: Column.ofConst(1, atomCount, Column.Schema.float),
|
||||
type_symbol: Column.ofStringArray(type_symbol),
|
||||
|
||||
pdbx_PDB_model_num: Column.ofConst(1, atomCount, Column.Schema.int),
|
||||
}, atomCount);
|
||||
|
||||
const basic = createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
chem_comp: componentBuilder.getChemCompTable(),
|
||||
atom_site
|
||||
});
|
||||
|
||||
return basic;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export { PrmtopFormat };
|
||||
|
||||
type PrmtopFormat = ModelFormat<PrmtopFile>
|
||||
|
||||
namespace PrmtopFormat {
|
||||
export function is(x?: ModelFormat): x is PrmtopFormat {
|
||||
return x?.kind === 'prmtop';
|
||||
}
|
||||
|
||||
export function fromPrmtop(prmtop: PrmtopFile): PrmtopFormat {
|
||||
return { kind: 'prmtop', name: prmtop.title.join(' ') || 'PRMTOP', data: prmtop };
|
||||
}
|
||||
}
|
||||
|
||||
export function topologyFromPrmtop(prmtop: PrmtopFile): Task<Topology> {
|
||||
return Task.create('Parse PRMTOP', async ctx => {
|
||||
const format = PrmtopFormat.fromPrmtop(prmtop);
|
||||
const basic = getBasic(prmtop);
|
||||
|
||||
const { pointers: { NBONH, NBONA }, bondsIncHydrogen, bondsWithoutHydrogen } = prmtop;
|
||||
const bondCount = NBONH + NBONA;
|
||||
|
||||
const bonds = {
|
||||
indexA: Column.ofLambda({
|
||||
value: (row: number) => {
|
||||
return row < NBONH
|
||||
? bondsIncHydrogen.value(row * 3) / 3
|
||||
: bondsWithoutHydrogen.value((row - NBONH) * 3) / 3;
|
||||
},
|
||||
rowCount: bondCount,
|
||||
schema: Column.Schema.int,
|
||||
}),
|
||||
indexB: Column.ofLambda({
|
||||
value: (row: number) => {
|
||||
return row < NBONH
|
||||
? bondsIncHydrogen.value(row * 3 + 1) / 3
|
||||
: bondsWithoutHydrogen.value((row - NBONH) * 3 + 1) / 3;
|
||||
},
|
||||
rowCount: bondCount,
|
||||
schema: Column.Schema.int,
|
||||
}),
|
||||
order: Column.ofConst(1, bondCount, Column.Schema.int)
|
||||
};
|
||||
|
||||
return Topology.create(prmtop.title.join(' ') || 'PRMTOP', basic, bonds, format);
|
||||
});
|
||||
}
|
||||
@@ -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 David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -56,10 +56,10 @@ export namespace ComponentBond {
|
||||
const entries: Map<string, Entry> = new Map();
|
||||
|
||||
function addEntry(id: string) {
|
||||
// weird behavior when 'PRO' is requested - will report a single bond between N and H because a later operation would override real content
|
||||
if (entries.has(id)) {
|
||||
return entries.get(id)!;
|
||||
}
|
||||
// weird behavior when 'PRO' is requested - will report a single bond
|
||||
// between N and H because a later operation would override real content
|
||||
if (entries.has(id)) return entries.get(id)!;
|
||||
|
||||
const e = new Entry(id);
|
||||
entries.set(id, e);
|
||||
return e;
|
||||
@@ -83,10 +83,8 @@ export namespace ComponentBond {
|
||||
let ord = 1;
|
||||
if (aromatic) flags |= BondType.Flag.Aromatic;
|
||||
switch (order.toLowerCase()) {
|
||||
case 'doub':
|
||||
case 'delo':
|
||||
ord = 2;
|
||||
break;
|
||||
case 'delo': flags |= BondType.Flag.Aromatic; break;
|
||||
case 'doub': ord = 2; break;
|
||||
case 'trip': ord = 3; break;
|
||||
case 'quad': ord = 4; break;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* 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>
|
||||
*/
|
||||
|
||||
import { CustomPropertyDescriptor } from '../../../../mol-model/custom-property';
|
||||
@@ -42,7 +43,7 @@ export namespace IndexPairBonds {
|
||||
name: 'index_pair_bonds',
|
||||
};
|
||||
|
||||
export const Provider = FormatPropertyProvider.create<IndexPairBonds>(Descriptor);
|
||||
export const Provider = FormatPropertyProvider.create<IndexPairBonds>(Descriptor, { asDynamic: true });
|
||||
|
||||
export type Data = {
|
||||
pairs: {
|
||||
|
||||
@@ -63,10 +63,21 @@ function getSpacegroupNameOrNumber(symmetry: Table<mmCIF_Schema['symmetry']>) {
|
||||
|
||||
function getSpacegroup(symmetry: Table<mmCIF_Schema['symmetry']>, cell: Table<mmCIF_Schema['cell']>): Spacegroup {
|
||||
if (symmetry._rowCount === 0 || cell._rowCount === 0) return Spacegroup.ZeroP1;
|
||||
|
||||
const a = cell.length_a.value(0);
|
||||
const b = cell.length_b.value(0);
|
||||
const c = cell.length_c.value(0);
|
||||
if (a === 0 || b === 0 || c === 0) return Spacegroup.ZeroP1;
|
||||
|
||||
const alpha = cell.angle_alpha.value(0);
|
||||
const beta = cell.angle_beta.value(0);
|
||||
const gamma = cell.angle_gamma.value(0);
|
||||
if (alpha === 0 || beta === 0 || gamma === 0) return Spacegroup.ZeroP1;
|
||||
|
||||
const nameOrNumber = getSpacegroupNameOrNumber(symmetry);
|
||||
const spaceCell = SpacegroupCell.create(nameOrNumber,
|
||||
Vec3.create(cell.length_a.value(0), cell.length_b.value(0), cell.length_c.value(0)),
|
||||
Vec3.scale(Vec3.zero(), Vec3.create(cell.angle_alpha.value(0), cell.angle_beta.value(0), cell.angle_gamma.value(0)), Math.PI / 180));
|
||||
Vec3.create(a, b, c),
|
||||
Vec3.scale(Vec3(), Vec3.create(alpha, beta, gamma), Math.PI / 180));
|
||||
|
||||
return Spacegroup.create(spaceCell);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ function getBasic(atoms: PsfFile['atoms']) {
|
||||
const asymIds = new Array<string>(atoms.count);
|
||||
const seqIds = new Uint32Array(atoms.count);
|
||||
const ids = new Uint32Array(atoms.count);
|
||||
const typeSymbol = new Array<string>(atoms.count);
|
||||
|
||||
const entityBuilder = new EntityBuilder();
|
||||
const componentBuilder = new ComponentBuilder(atoms.residueId, atoms.atomName);
|
||||
@@ -68,6 +69,8 @@ function getBasic(atoms: PsfFile['atoms']) {
|
||||
asymIds[i] = currentAsymId;
|
||||
seqIds[i] = currentSeqId;
|
||||
ids[i] = i;
|
||||
|
||||
typeSymbol[i] = guessElementSymbolString(atoms.atomName.value(i), atoms.residueName.value(i));
|
||||
}
|
||||
|
||||
const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
|
||||
@@ -84,7 +87,7 @@ function getBasic(atoms: PsfFile['atoms']) {
|
||||
label_entity_id: Column.ofStringArray(entityIds),
|
||||
|
||||
occupancy: Column.ofConst(1, atoms.count, Column.Schema.float),
|
||||
type_symbol: Column.ofStringArray(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))),
|
||||
type_symbol: Column.ofStringArray(typeSymbol),
|
||||
|
||||
pdbx_PDB_model_num: Column.ofConst(1, atoms.count, Column.Schema.int),
|
||||
}, atoms.count);
|
||||
|
||||
226
src/mol-model-formats/structure/top.ts
Normal file
226
src/mol-model-formats/structure/top.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* Copyright (c) 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 { TopFile } from '../../mol-io/reader/top/parser';
|
||||
import { getMoleculeType, MoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { Topology } from '../../mol-model/structure/topology/topology';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ModelFormat } from '../format';
|
||||
import { BasicSchema, createBasic } from './basic/schema';
|
||||
import { ComponentBuilder } from './common/component';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
import { getChainId } from './common/util';
|
||||
import { guessElementSymbolString } from './util';
|
||||
|
||||
function getBasic(top: TopFile) {
|
||||
const { molecules, compounds } = top;
|
||||
|
||||
const singleResidue: Record<string, boolean> = {};
|
||||
let atomCount = 0;
|
||||
|
||||
for (let i = 0, il = molecules._rowCount; i < il; ++i) {
|
||||
const mol = molecules.compound.value(i);
|
||||
const count = molecules.molCount.value(i);
|
||||
const { atoms } = compounds[mol];
|
||||
|
||||
Column.asArrayColumn(atoms.atom);
|
||||
Column.asArrayColumn(atoms.resnr);
|
||||
Column.asArrayColumn(atoms.residu);
|
||||
|
||||
atomCount += count * atoms._rowCount;
|
||||
|
||||
let prevResnr = atoms.resnr.value(0);
|
||||
singleResidue[mol] = true;
|
||||
for (let j = 1, jl = atoms._rowCount; j < jl; ++j) {
|
||||
const resnr = atoms.resnr.value(j);
|
||||
if (resnr !== prevResnr) {
|
||||
singleResidue[mol] = false;
|
||||
break;
|
||||
}
|
||||
prevResnr = resnr;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const atomNames = new Array<string>(atomCount);
|
||||
const residueIds = new Uint32Array(atomCount);
|
||||
const residueNames = new Array<string>(atomCount);
|
||||
|
||||
let k = 0;
|
||||
for (let i = 0, il = molecules._rowCount; i < il; ++i) {
|
||||
const mol = molecules.compound.value(i);
|
||||
const count = molecules.molCount.value(i);
|
||||
const { atoms } = compounds[mol];
|
||||
const isSingleResidue = singleResidue[mol];
|
||||
for (let j = 0; j < count; ++j) {
|
||||
for (let l = 0, ll = atoms._rowCount; l < ll; ++l) {
|
||||
atomNames[k] = atoms.atom.value(l);
|
||||
residueIds[k] = atoms.resnr.value(l);
|
||||
residueNames[k] = atoms.residu.value(l);
|
||||
|
||||
if (isSingleResidue) residueIds[k] += j;
|
||||
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const atomName = Column.ofStringArray(atomNames);
|
||||
const residueId = Column.ofIntArray(residueIds);
|
||||
const residueName = Column.ofStringArray(residueNames);
|
||||
|
||||
//
|
||||
|
||||
const entityIds = new Array<string>(atomCount);
|
||||
const asymIds = new Array<string>(atomCount);
|
||||
const seqIds = new Uint32Array(atomCount);
|
||||
const ids = new Uint32Array(atomCount);
|
||||
|
||||
const entityBuilder = new EntityBuilder();
|
||||
const componentBuilder = new ComponentBuilder(residueId, atomName);
|
||||
|
||||
let currentEntityId = '';
|
||||
let currentAsymIndex = 0;
|
||||
let currentAsymId = '';
|
||||
let currentSeqId = 0;
|
||||
let prevMoleculeType = MoleculeType.Unknown;
|
||||
let prevResidueNumber = -1;
|
||||
|
||||
for (let i = 0, il = atomCount; i < il; ++i) {
|
||||
const residueNumber = residueId.value(i);
|
||||
if (residueNumber !== prevResidueNumber) {
|
||||
const compId = residueName.value(i);
|
||||
const moleculeType = getMoleculeType(componentBuilder.add(compId, i).type, compId);
|
||||
|
||||
if (moleculeType !== prevMoleculeType) {
|
||||
currentAsymId = getChainId(currentAsymIndex);
|
||||
currentAsymIndex += 1;
|
||||
currentSeqId = 0;
|
||||
}
|
||||
|
||||
currentEntityId = entityBuilder.getEntityId(compId, moleculeType, currentAsymId);
|
||||
currentSeqId += 1;
|
||||
|
||||
prevResidueNumber = residueNumber;
|
||||
prevMoleculeType = moleculeType;
|
||||
}
|
||||
|
||||
entityIds[i] = currentEntityId;
|
||||
asymIds[i] = currentAsymId;
|
||||
seqIds[i] = currentSeqId;
|
||||
ids[i] = i;
|
||||
}
|
||||
|
||||
const id = Column.ofIntArray(ids);
|
||||
const asym_id = Column.ofStringArray(asymIds);
|
||||
|
||||
//
|
||||
|
||||
const type_symbol = new Array<string>(atomCount);
|
||||
for (let i = 0; i < atomCount; ++i) {
|
||||
type_symbol[i] = guessElementSymbolString(atomName.value(i), residueName.value(i));
|
||||
}
|
||||
|
||||
const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
|
||||
auth_asym_id: asym_id,
|
||||
auth_atom_id: Column.asArrayColumn(atomName),
|
||||
auth_comp_id: residueName,
|
||||
auth_seq_id: residueId,
|
||||
id: Column.asArrayColumn(id),
|
||||
|
||||
label_asym_id: asym_id,
|
||||
label_atom_id: Column.asArrayColumn(atomName),
|
||||
label_comp_id: residueName,
|
||||
label_seq_id: Column.ofIntArray(seqIds),
|
||||
label_entity_id: Column.ofStringArray(entityIds),
|
||||
|
||||
occupancy: Column.ofConst(1, atomCount, Column.Schema.float),
|
||||
type_symbol: Column.ofStringArray(type_symbol),
|
||||
|
||||
pdbx_PDB_model_num: Column.ofConst(1, atomCount, Column.Schema.int),
|
||||
}, atomCount);
|
||||
|
||||
const basic = createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
chem_comp: componentBuilder.getChemCompTable(),
|
||||
atom_site
|
||||
});
|
||||
|
||||
return basic;
|
||||
}
|
||||
|
||||
function getBonds(top: TopFile) {
|
||||
const { molecules, compounds } = top;
|
||||
|
||||
const indexA: number[] = [];
|
||||
const indexB: number[] = [];
|
||||
|
||||
let atomOffset = 0;
|
||||
|
||||
for (let i = 0, il = molecules._rowCount; i < il; ++i) {
|
||||
const mol = molecules.compound.value(i);
|
||||
const count = molecules.molCount.value(i);
|
||||
const { atoms, bonds } = compounds[mol];
|
||||
|
||||
|
||||
|
||||
if (bonds) {
|
||||
for (let j = 0; j < count; ++j) {
|
||||
|
||||
for (let l = 0, ll = bonds._rowCount; l < ll; ++l) {
|
||||
indexA.push(bonds.ai.value(l) - 1 + atomOffset);
|
||||
indexB.push(bonds.aj.value(l) - 1 + atomOffset);
|
||||
}
|
||||
|
||||
atomOffset += atoms._rowCount;
|
||||
}
|
||||
} else if (mol === 'TIP3') {
|
||||
for (let j = 0; j < count; ++j) {
|
||||
indexA.push(0 + atomOffset);
|
||||
indexB.push(1 + atomOffset);
|
||||
indexA.push(0 + atomOffset);
|
||||
indexB.push(2 + atomOffset);
|
||||
atomOffset += atoms._rowCount;
|
||||
}
|
||||
} else {
|
||||
atomOffset += count * atoms._rowCount;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
indexA: Column.ofIntArray(indexA),
|
||||
indexB: Column.ofIntArray(indexB),
|
||||
order: Column.ofConst(1, indexA.length, Column.Schema.int)
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export { TopFormat };
|
||||
|
||||
type TopFormat = ModelFormat<TopFile>
|
||||
|
||||
namespace TopFormat {
|
||||
export function is(x?: ModelFormat): x is TopFormat {
|
||||
return x?.kind === 'top';
|
||||
}
|
||||
|
||||
export function fromTop(top: TopFile): TopFormat {
|
||||
return { kind: 'top', name: top.system || 'TOP', data: top };
|
||||
}
|
||||
}
|
||||
|
||||
export function topologyFromTop(top: TopFile): Task<Topology> {
|
||||
return Task.create('Parse TOP', async ctx => {
|
||||
const format = TopFormat.fromTop(top);
|
||||
const basic = getBasic(top);
|
||||
const bonds = getBonds(top);
|
||||
|
||||
return Topology.create(top.system || 'TOP', basic, bonds, format);
|
||||
});
|
||||
}
|
||||
39
src/mol-model-formats/structure/trr.ts
Normal file
39
src/mol-model-formats/structure/trr.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Task } from '../../mol-task';
|
||||
import { TrrFile } from '../../mol-io/reader/trr/parser';
|
||||
import { Coordinates, Frame, Time } from '../../mol-model/structure/coordinates';
|
||||
import { Cell } from '../../mol-math/geometry/spacegroup/cell';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
|
||||
export function coordinatesFromTrr(file: TrrFile): Task<Coordinates> {
|
||||
return Task.create('Parse TRR', async ctx => {
|
||||
await ctx.update('Converting to coordinates');
|
||||
|
||||
const deltaTime = Time(file.deltaTime, 'step');
|
||||
const offsetTime = Time(file.timeOffset, deltaTime.unit);
|
||||
|
||||
const frames: Frame[] = [];
|
||||
for (let i = 0, il = file.frames.length; i < il; ++i) {
|
||||
const box = file.boxes[i];
|
||||
const x = Vec3.fromArray(Vec3(), box, 0);
|
||||
const y = Vec3.fromArray(Vec3(), box, 3);
|
||||
const z = Vec3.fromArray(Vec3(), box, 6);
|
||||
frames.push({
|
||||
elementCount: file.frames[i].count,
|
||||
cell: Cell.fromBasis(x, y, z),
|
||||
x: file.frames[i].x,
|
||||
y: file.frames[i].y,
|
||||
z: file.frames[i].z,
|
||||
xyzOrdering: { isIdentity: true },
|
||||
time: Time(offsetTime.value + deltaTime.value * i, deltaTime.unit)
|
||||
});
|
||||
}
|
||||
|
||||
return Coordinates.create(frames, deltaTime, offsetTime);
|
||||
});
|
||||
}
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -46,31 +46,30 @@ export function guessElementSymbolTokens(tokens: Tokens, str: string, start: num
|
||||
TokenBuilder.add(tokens, s, s); // no reasonable guess, add empty token
|
||||
}
|
||||
|
||||
const TwoCharElementNames = new Set(['NA', 'CL', 'FE', 'SI', 'BR', 'AS']);
|
||||
const OneCharElementNames = new Set(['C', 'H', 'N', 'O', 'P', 'S']);
|
||||
|
||||
const reTrimSpacesAndNumbers = /^[\s\d]+|[\s\d]+$/g;
|
||||
export function guessElementSymbolString(str: string) {
|
||||
export function guessElementSymbolString(atomId: string, compId: string) {
|
||||
// trim spaces and numbers, convert to upper case
|
||||
str = str.replace(reTrimSpacesAndNumbers, '').toUpperCase();
|
||||
const l = str.length;
|
||||
atomId = atomId.replace(reTrimSpacesAndNumbers, '').toUpperCase();
|
||||
const l = atomId.length;
|
||||
|
||||
if (l === 0) return str; // empty
|
||||
if (l === 1) return str; // one char
|
||||
if (l === 0) return atomId; // empty
|
||||
if (l === 1) return atomId; // one char
|
||||
if (TwoCharElementNames.has(atomId)) return atomId; // two chars
|
||||
|
||||
if (l === 2) { // two chars
|
||||
if (str === 'NA' || str === 'CL' || str === 'FE' || str === 'SI' ||
|
||||
str === 'BR' || str === 'AS'
|
||||
) return str;
|
||||
// check for Charmm ion names where component and atom id are the same
|
||||
if (l === 3 && compId === atomId) {
|
||||
if (atomId === 'SOD') return 'NA';
|
||||
if (atomId === 'POT') return 'K';
|
||||
if (atomId === 'CES') return 'CS';
|
||||
if (atomId === 'CAL') return 'CA';
|
||||
if (atomId === 'CLA') return 'CL';
|
||||
}
|
||||
|
||||
if (l === 3) { // three chars
|
||||
if (str === 'SOD') return 'NA';
|
||||
if (str === 'POT') return 'K';
|
||||
if (str === 'CES') return 'CS';
|
||||
if (str === 'CAL') return 'CA';
|
||||
if (str === 'CLA') return 'CL';
|
||||
}
|
||||
|
||||
const c = str[0];
|
||||
if (c === 'C' || c === 'H' || c === 'N' || c === 'O' || c === 'P' || c === 'S') return c;
|
||||
if (OneCharElementNames.has(atomId[0])) return atomId[0];
|
||||
|
||||
return ''; // no reasonable guess, return empty string
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,13 +78,13 @@ function getModels(mol: XyzFile, ctx: RuntimeContext) {
|
||||
componentBuilder.setNames([['MOL', 'Unknown Molecule']]);
|
||||
componentBuilder.add('MOL', 0);
|
||||
|
||||
const basics = createBasic({
|
||||
const basic = createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
chem_comp: componentBuilder.getChemCompTable(),
|
||||
atom_site
|
||||
});
|
||||
|
||||
return createModels(basics, XyzFormat.create(mol), ctx);
|
||||
return createModels(basic, XyzFormat.create(mol), ctx);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,20 +74,26 @@ 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;
|
||||
}
|
||||
};
|
||||
|
||||
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * sizeFactor);
|
||||
m.setBoundingSphere(sphere);
|
||||
if (boundingSphere) {
|
||||
m.setBoundingSphere(boundingSphere);
|
||||
} else if (m.triangleCount > 0) {
|
||||
const { child } = structure;
|
||||
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * sizeFactor);
|
||||
m.setBoundingSphere(sphere);
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
@@ -144,6 +150,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 +171,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;
|
||||
}
|
||||
|
||||
@@ -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,17 +56,29 @@ 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);
|
||||
const { mesh: m, boundingSphere } = createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * sizeFactor);
|
||||
m.setBoundingSphere(sphere);
|
||||
if (boundingSphere) {
|
||||
m.setBoundingSphere(boundingSphere);
|
||||
} else if (m.triangleCount > 0) {
|
||||
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * sizeFactor);
|
||||
m.setBoundingSphere(sphere);
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
@@ -123,6 +135,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 +170,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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user