Compare commits

...

154 Commits

Author SHA1 Message Date
Alexander Rose
23ec35d1f9 3.4.0 2022-03-13 13:12:32 -07:00
Alexander Rose
ff089c2b9f changelog 2022-03-13 12:50:44 -07:00
Alexander Rose
1ab088718a package updates 2022-03-13 12:49:03 -07:00
Alexander Rose
0cb2e5857a Merge pull request #393 from molstar/zenodo-import
Zenodo import
2022-03-13 12:19:04 -07:00
Alexander Rose
7c5ae5d7ee tweak guessElementSymbolString 2022-03-13 12:16:12 -07:00
Alexander Rose
6e2665d98d Merge branch 'master' of https://github.com/molstar/molstar into zenodo-import 2022-03-12 18:03:45 -08:00
Alexander Rose
b3b4692237 add top format support 2022-03-12 17:46:29 -08:00
Alexander Rose
55ff1d4999 add nctraj format support 2022-03-12 14:17:58 -08:00
Alexander Rose
511c839237 add prmtop format support 2022-03-12 13:48:10 -08:00
Alexander Rose
384cd6e5d9 add trr format support 2022-03-12 13:02:50 -08:00
Alexander Rose
6fd9dcc72e split structure formats into topology & coordinates 2022-03-12 11:51:03 -08:00
Alexander Rose
12ca06fe91 fix handling of empty symmetry cell data 2022-03-12 11:44:27 -08:00
Alexander Rose
652f6c651b fix wrong element assignment 2022-03-12 10:58:33 -08:00
Alexander Rose
7dd808a772 add custom import controls 2022-03-12 10:52:43 -08:00
Alexander Rose
945e55f8a7 add formated file size 2022-03-07 22:06:22 -08:00
Alexander Rose
866a30abe5 cleanup getFileInfo 2022-03-07 22:05:11 -08:00
Alexander Rose
f0e33e1e4e fix legend of hydrophobicity color theme 2022-03-07 21:44:57 -08:00
Alexander Rose
8723ca38b4 improve saccharide detection 2022-03-07 21:31:33 -08:00
Alexander Rose
efffca0026 zenode import fixes 2022-03-06 21:57:42 -08:00
Alexander Rose
6c5eb3035f remove default record id 2022-03-06 17:55:52 -08:00
Alexander Rose
0bf385f2ca add zip file support to zenodo extension 2022-03-06 17:37:39 -08:00
Alexander Rose
9d5f51f513 improve handling of compressed files
- fix loading of some compressed files within sessions
- ignore some hidden MACOSX files
2022-03-06 17:32:56 -08:00
Alexander Rose
a1448131d8 add Zenodo import extension 2022-03-06 11:12:21 -08:00
Alexander Rose
664cacc7ac add LoadTrajectory action 2022-03-06 11:11:16 -08:00
Alexander Rose
1aec37dd05 fix 2022-03-06 11:09:51 -08:00
Alexander Rose
3ff2c0840e Merge pull request #392 from molstar/assert-unreachable
make use of assertUnreachable
2022-03-06 11:06:04 -08:00
Alexander Rose
5ca3c3ac52 Update src/mol-script/language/parser.ts 2022-03-06 11:04:58 -08:00
Alexander Rose
714ee50965 Update src/mol-script/language/parser.ts
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2022-03-06 11:03:33 -08:00
Alexander Rose
ae1df3c5aa Update src/mol-script/language/parser.ts
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2022-03-06 11:03:28 -08:00
Alexander Rose
28afb39550 make use of assertUnreachable 2022-03-05 10:18:40 -08:00
Alexander Rose
3466a8a024 Merge pull request #389 from molstar/cif-check-present
fix handling of mmcif with empty label_asym_id
2022-03-05 09:20:19 -08:00
Alexander Rose
90db3321f5 changelog
Co-authored-by: David Sehnal <dsehnal@users.noreply.github.com>
2022-03-05 09:20:04 -08:00
Alexander Rose
bf4e5ed7c2 normalize mmcif data 2022-03-04 22:47:14 -08:00
Alexander Rose
d3b2c20c26 normalize atom_site early 2022-03-03 18:31:54 -08:00
Alexander Rose
c3afabb4b1 spelling 2022-03-02 19:50:30 -08:00
Alexander Rose
18cc9790d1 fix handling of mmcif with empty label_asym_id 2022-03-02 19:17:31 -08:00
Alexander Rose
d5ed3aa674 3.3.1 2022-02-27 18:45:45 -08:00
Alexander Rose
cfb9c9acfe changelog 2022-02-27 18:40:48 -08:00
Alexander Rose
67feef0b1d add option to ignore ions for inter-unit bonds 2022-02-27 18:40:27 -08:00
Alexander Rose
e0192ab5aa fix issue with unit boundary reuse
- do at visual level instead
2022-02-27 18:03:41 -08:00
Alexander Rose
eae7c11c55 3.3.0 2022-02-27 13:03:01 -08:00
Alexander Rose
b8251e1ade changelog 2022-02-27 12:57:51 -08:00
Alexander Rose
2ff2b9f348 schema updates 2022-02-27 12:50:48 -08:00
Alexander Rose
164e3f3343 package updates 2022-02-27 12:47:45 -08:00
Alexander Rose
4901a1bd87 rename occlusion scaleFactor to resolutionScale 2022-02-27 12:16:37 -08:00
Alexander Rose
cd194cca65 Merge pull request #385 from molstar/traj-anim
Coordinate trajectory related improvements
2022-02-27 12:04:28 -08:00
Alexander Rose
1748efbc18 Merge branch 'master' of https://github.com/molstar/molstar into traj-anim 2022-02-27 12:03:54 -08:00
Alexander Rose
4d60b40403 fix interUnitBonds parent check 2022-02-27 12:02:36 -08:00
Alexander Rose
6e5a41879f Merge pull request #386 from molstar/improve-ao
Improve AO performance
2022-02-27 11:58:50 -08:00
Alexander Rose
c5f9eb54da set default occlusion scaleFactor to 1 2022-02-26 23:51:21 -08:00
Alexander Rose
aebbfeb061 add support for lower resolution AO
- downsample depth for AO
- add scaleFactor param
- full res for image/video
2022-02-26 22:23:52 -08:00
Alexander Rose
a0a5a6b578 add encoder spec
- mostly as example
2022-02-26 17:43:37 -08:00
Alexander Rose
6bdafb85d7 Check if marking passes are needed
- add Scene.getMarkerAverage
2022-02-26 13:30:05 -08:00
Alexander Rose
0dd7debf5d reuse occlusion in multi-sample pass 2022-02-26 13:02:53 -08:00
Alexander Rose
962b9ee7af Merge pull request #378 from molstar/ar-bonds
fix visuals for aromatic/delocalized bonds
2022-02-26 11:34:42 -08:00
Alexander Rose
15bcc5df88 Merge branch 'master' into ar-bonds 2022-02-26 11:26:29 -08:00
Alexander Rose
8495d834c8 add getTripletIndices & triplets to UnitResonance 2022-02-26 11:25:35 -08:00
Alexander Rose
7282399709 fix/improve canRemap handling in IntraUnitBonds 2022-02-26 10:56:32 -08:00
Alexander Rose
780bdd6e7e don't compute InterUnitBonds when parent ones empty 2022-02-26 10:53:54 -08:00
Alexander Rose
ad08e7c67f reuse unit boundary if it has not changed too much 2022-02-26 10:52:26 -08:00
Alexander Rose
ff089964ca improve line visuals in polymerAndLigand preset 2022-02-26 10:50:02 -08:00
Alexander Rose
990191529a reuse Model.CoarseGrained for coordinate trajectories 2022-02-26 10:48:05 -08:00
Alexander Rose
ce1bec12b4 fix mononucleotides detected as polymer components 2022-02-26 10:46:51 -08:00
Alexander Rose
703ea9af53 change line geometry default scaleFactor to 2
- 3 is too big after fixing line rendering
2022-02-26 10:45:51 -08:00
David Sehnal
df0227ae1e Merge pull request #383 from molstar/batch-highlights
Resolve marking in main render loop
2022-02-24 07:46:44 +01:00
dsehnal
486d12b6ac Fix multisample pass "forceOn" 2022-02-24 07:20:03 +01:00
dsehnal
9c18375ab4 typo 2022-02-23 23:34:09 +01:00
dsehnal
e1708aed68 dispose mark buffer 2022-02-23 19:09:40 +01:00
dsehnal
a42d778b84 changelog 2022-02-23 12:04:38 +01:00
dsehnal
7692b59c7c resolve marking in main render loop insread of eagerly 2022-02-23 11:59:29 +01:00
dsehnal
82de9b36b3 addRing check tweak 2022-02-22 16:02:11 +01:00
dsehnal
49b3c8f65f ring computation algorithm fixes 2022-02-22 13:16:32 +01:00
Alexander Rose
90ad32d936 add ring example PDB IDs 2022-02-21 13:48:43 -08:00
David Sehnal
2509e91f1a Merge pull request #381 from russellp17/disable-ts-config-for-sourceMappingURL
Disable ts config that leads to sourceMappingURL comment
2022-02-21 15:20:00 +01:00
David Sehnal
80dc2219e4 Merge pull request #380 from russellp17/allow-react-16-in-peer-dependency
Allow React ^16.14.0 as peer dependency
2022-02-21 15:10:33 +01:00
Russell Parker
931cb0fa7d Disable ts config that leads to sourceMappingURL comment in transpilation 2022-02-21 08:24:09 -05:00
Russell Parker
4383f2ea90 Allow React ^16.14.0 as peer dependency 2022-02-21 08:20:55 -05:00
dsehnal
add79dc242 ring computation algorithm fix 2022-02-21 14:15:13 +01:00
Alexander Rose
b6e142f04c move delocalizedTriplets code to unit.resonance 2022-02-20 16:28:42 -08:00
Alexander Rose
105f6c3041 fix visuals for aromatic/delocalized bonds 2022-02-20 12:50:44 -08:00
Alexander Rose
4babbb65c1 fix spec 2022-02-19 17:22:25 -08:00
Alexander Rose
878159f7ed fix texture warnings (#319)
- bind real texture to tDepth in renderer
- ensure textures are not empty; init as 1x1(x1)
2022-02-19 16:59:03 -08:00
Alexander Rose
c320386019 changelog 2022-02-19 14:23:37 -08:00
Alexander Rose
1e7a0159f0 Merge pull request #377 from JonStargaryen/master
parse contour-level from emdb v3 header files
2022-02-19 14:20:48 -08:00
JonStargaryen
bbf4f1d1d3 break early 2022-02-19 11:02:30 -08:00
Alexander Rose
8cd1c69c76 css tweaks, fixes #376 2022-02-19 10:46:06 -08:00
JonStargaryen
2372a878ac parse contour-level from emdb v3 header files 2022-02-19 09:12:20 -08:00
dsehnal
9bec644997 3.2.0 2022-02-17 19:24:55 +01:00
dsehnal
650d38dff8 Store IndexPairBonds as a dynamic property 2022-02-17 19:21:49 +01:00
David Sehnal
097277e397 Merge pull request #373 from JonStargaryen/master
Add TraceOnly option for Structure Superposition
2022-02-14 19:53:51 +01:00
Sebastian Bittrich
82a4d5eedf cleanup 2022-02-14 10:46:03 -08:00
Sebastian Bittrich
2a00248812 determine trace during buildIndex 2022-02-14 10:41:10 -08:00
Sebastian Bittrich
9841c773cb reset camera after all alignments 2022-02-10 13:55:09 -08:00
Sebastian Bittrich
b21a78ad14 support shuffled atoms when aligning SIFTS trace 2022-02-10 13:49:37 -08:00
Sebastian Bittrich
7329fa597d options obj for alignAndSuperposeWithSIFTSMapping 2022-02-10 11:36:50 -08:00
Sebastian Bittrich
f1d8f0ecb4 auth 2022-02-10 11:32:21 -08:00
Sebastian Bittrich
1d127f2364 changelog 2022-02-10 11:30:55 -08:00
Sebastian Bittrich
b244405cc3 polymer-based query 2022-02-10 10:39:22 -08:00
Sebastian Bittrich
38adfe0ca6 cleanup 2022-02-10 10:29:34 -08:00
Sebastian Bittrich
6f0d798847 optional filtering for trace during alignment 2022-02-10 10:23:31 -08:00
Sebastian Bittrich
6e58bfd2b0 traceOnly param for superpos 2022-02-08 16:03:05 -08:00
David Sehnal
bc2e8d8ac4 Merge pull request #369 from molstar/pdbx_sifts-export
Better support for atom_site.pdbx_sifts_xref
2022-02-08 10:19:02 +01:00
dsehnal
7be654d47f tweak labels 2022-02-08 10:18:01 +01:00
dsehnal
bfe46e3604 pdbx_sifts_xref PR feedback 2022-02-08 10:12:21 +01:00
dsehnal
008b597fc5 changelog 2022-02-07 17:51:13 +01:00
dsehnal
c4b4f2e3b1 rename BestDatabaseSequenceMapping -> SIFTSMapping 2022-02-07 17:49:10 +01:00
dsehnal
76ee97301b atom_site.pdbx_label_index support 2022-02-07 17:44:03 +01:00
dsehnal
289dc09eae add support for atom_site.pdbx_sifts_xref export 2022-02-07 17:33:11 +01:00
Alexander Rose
f23f84f0f3 3.1.0 2022-02-06 15:56:41 -08:00
Alexander Rose
62259f3295 changelog 2022-02-06 15:51:40 -08:00
Alexander Rose
854a430a12 tweak quick-styles order 2022-02-06 15:46:40 -08:00
Alexander Rose
d27cdb5637 schema updates 2022-02-06 15:41:09 -08:00
Alexander Rose
7d12d9ee90 package udpdates 2022-02-06 15:38:09 -08:00
Alexander Rose
d70a4ff347 Merge pull request #366 from molstar/repr-defaults-tweaks
cleaner default representation style
2022-02-06 15:24:27 -08:00
Alexander Rose
49541558d1 Merge branch 'master' into repr-defaults-tweaks 2022-02-06 15:24:16 -08:00
Alexander Rose
7b00a1227c changelog 2022-02-06 15:22:50 -08:00
Alexander Rose
7800603c81 Merge pull request #367 from molstar/quick-styles
Quick styles
2022-02-06 15:16:43 -08:00
Alexander Rose
fca00c8116 variable naming 2022-02-06 15:15:55 -08:00
Alexander Rose
00fa549e44 Merge pull request #368 from JonStargaryen/master
iterate over `structure.unitSymmetryGroups`, fixes #364
2022-02-06 15:13:57 -08:00
Alexander Rose
36181b6b87 tweak quick style names 2022-02-06 15:13:15 -08:00
JonStargaryen
88a95162e9 iterate over structure.unitSymmetryGroups, fixes #364 2022-02-06 10:47:40 -08:00
Alexander Rose
9c1d59a2c8 add Quick Styles panel 2022-02-05 12:58:32 -08:00
Alexander Rose
fdd894956a fix representation preset side effects 2022-02-05 12:56:55 -08:00
Alexander Rose
eb6dc0859d cleaner default representation style 2022-02-05 12:23:28 -08:00
Alexander Rose
b99026bba2 add ignoreLight to component params 2022-02-05 12:10:56 -08:00
Alexander Rose
6fa50eb8d5 fix xrayShader & ignoreLight not working together 2022-02-05 11:57:39 -08:00
David Sehnal
e5046f15a9 Merge pull request #365 from JonStargaryen/master
Volume Streaming Error Message
2022-02-04 17:11:31 +01:00
JonStargaryen
09f1c066a0 logic 2022-02-03 17:11:50 -08:00
Sebastian Bittrich
ed2f0b34c9 volume streaming err msg 2022-02-03 16:11:10 -08:00
Alexander Rose
c7f75861de 3.0.2 2022-01-30 12:24:48 -08:00
Alexander Rose
ccd04dbc9d changelog 2022-01-30 12:19:49 -08:00
Alexander Rose
e71f8d2c10 Merge pull request #360 from molstar/fix/visual-visibility
Fix visual visibility edge case
2022-01-30 12:17:12 -08:00
dsehnal
d9b4c60239 Fix visual visibility edge case 2022-01-30 15:02:46 +01:00
Alexander Rose
103c1fca21 measurement options tweaks
- allow larger text size
- make customText essential
2022-01-29 19:30:03 -08:00
Alexander Rose
49559bf5fb citation tweak 2022-01-29 16:05:17 -08:00
Alexander Rose
26dceabf83 add citation file 2022-01-29 15:56:39 -08:00
Alexander Rose
abe506182e fix multi-instance entity label display
- fix empty elements created in extendToAllInstances
2022-01-29 11:21:48 -08:00
Alexander Rose
582a0e2a38 fix Sphere.expand for highly directional extrema 2022-01-29 11:13:19 -08:00
dsehnal
2784ccf379 3.0.1 2022-01-27 12:15:53 +01:00
dsehnal
0ad1d578fe changelog 2022-01-27 12:13:22 +01:00
David Sehnal
31fd1c9c68 Merge pull request #353 from molstar/drag-tweak
emit drag event whenever started within viewport
2022-01-27 12:12:07 +01:00
David Sehnal
9c961297a2 Merge branch 'master' into drag-tweak 2022-01-27 12:11:59 +01:00
David Sehnal
b920053349 Merge pull request #352 from molstar/var-fixes
Various fixes
2022-01-27 12:09:39 +01:00
David Sehnal
0a5c764e4a Merge pull request #351 from molstar/volume-server-data-fix
Volume server data fix
2022-01-27 11:37:31 +01:00
Alexander Rose
b9a71c83ff emit drag event whenever started within viewport 2022-01-26 21:32:14 -08:00
Alexander Rose
3255f207d0 Merge branch 'master' of https://github.com/molstar/molstar into var-fixes 2022-01-26 20:46:11 -08:00
Alexander Rose
e3b4ca8862 add entity-id and entity-source as carbonColor 2022-01-26 20:42:38 -08:00
Alexander Rose
6810793015 fix marking of InteractionsInterUnitVisual 2022-01-26 20:40:12 -08:00
Alexander Rose
1feb3c2095 fix entity-id coloring broken for non-ihm models 2022-01-26 20:38:46 -08:00
Alexander Rose
f2da6033d0 fix pdbe xray maps url 2022-01-26 20:36:52 -08:00
Alexander Rose
28bc212132 fix marking pass for transparentBackground 2022-01-26 20:35:38 -08:00
dsehnal
1a7c62eec6 remove gl from dependencies & install it on demand instead
- installing gl on M1 Mac was taking several minutes to compile on each update
2022-01-26 23:12:12 +01:00
dsehnal
de67dbacba iso-value adjustment for VolumeServer data in default Viewer 2022-01-26 17:47:23 +01:00
dsehnal
57223a0f9a Fix VolumeServer/query CLI 2022-01-26 16:49:18 +01:00
161 changed files with 7159 additions and 5424 deletions

View File

@@ -6,6 +6,87 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [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:

72
CITATION.cff Normal file
View 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: "W431W437"

View File

@@ -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**

View File

@@ -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
1 atom_sites.entry_id
24 atom_site.pdbx_PDB_model_num
25 atom_site.ihm_model_id
26 atom_site_anisotrop.id atom_site.pdbx_label_index
27 atom_site.pdbx_sifts_xref_db_name
28 atom_site.pdbx_sifts_xref_db_acc
29 atom_site.pdbx_sifts_xref_db_num
30 atom_site.pdbx_sifts_xref_db_res
31 atom_site_anisotrop.id
32 atom_site_anisotrop.U
33 atom_site_anisotrop.U_esd
34 atom_site_anisotrop.pdbx_PDB_ins_code

View File

@@ -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)

6505
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "3.0.0",
"version": "3.4.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.4.0",
"@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.10.0",
"@typescript-eslint/parser": "^5.10.0",
"@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.5.2",
"jest": "^27.5.1",
"mini-css-extract-plugin": "^2.6.0",
"path-browserify": "^1.0.1",
"raw-loader": "^4.0.2",
"sass": "^1.49.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.5",
"webpack": "^5.67.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.21",
"@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.7",
"rxjs": "^7.5.2",
"swagger-ui-dist": "^4.2.1",
"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"
}
}

View File

@@ -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,

View File

@@ -17,12 +17,15 @@ 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';
@@ -62,6 +65,7 @@ const Extensions = {
'mp4-export': PluginSpec.Behavior(Mp4Export),
'geo-export': PluginSpec.Behavior(GeometryExport),
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
'zenodo-import': PluginSpec.Behavior(ZenodoImport),
};
const DefaultViewerOptions = {
@@ -366,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 }
}));
@@ -450,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
}

View 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);

View File

@@ -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: {

View File

@@ -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;
});

View File

@@ -4,7 +4,7 @@ export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
// Generated on 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 };

View File

@@ -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 = {

View 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: () => ({ })
});

View 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}
</>;
}
}

View File

@@ -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);

View File

@@ -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();

View File

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

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -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) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -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],

View File

@@ -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) {

View File

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

View File

@@ -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

View File

@@ -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);

View File

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

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -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);
},
};
}
}

View File

@@ -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);
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author 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);

View File

@@ -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

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author 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;

View File

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

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -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 {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -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

View File

@@ -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) {

View File

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

View 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));
});

View File

@@ -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) {

View File

@@ -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);
}
}

View 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;
}
}
}

View 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);
}
}
}

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.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
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.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
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.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,
/**

View 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);
}
});
}

View 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);
});
}

View 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);
});
}

View 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);
}
});
}

View File

@@ -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;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -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,28 @@ 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);
const out = Vec3.clone(e);
const sA = Vec3.dot(tmpDir, axes.dirA) < 0 ? -1 : 1;
Vec3.scaleAndAdd(out, out, axes.dirA, sA);
const sB = Vec3.dot(tmpDir, axes.dirB) < 0 ? -1 : 1;
Vec3.scaleAndAdd(out, out, axes.dirB, sB);
const sC = Vec3.dot(tmpDir, axes.dirC) < 0 ? -1 : 1;
Vec3.scaleAndAdd(out, out, axes.dirC, sC);
return out;
}));
}
return out;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -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 };
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -186,8 +186,6 @@ function splitTable<T extends Table<any>>(table: T, col: Column<number>) {
return ret;
}
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`

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author 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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

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

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author 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];
}
}
};
}

View File

@@ -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);
}
//

View File

@@ -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);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -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));
}

View File

@@ -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));

View File

@@ -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;

View 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);
});
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author 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

View 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);
});
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author 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;
}

View File

@@ -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: {

View File

@@ -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);
}

View File

@@ -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);

View 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);
});
}

View 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);
});
}

View File

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

View File

@@ -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);
}
//

View File

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

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -74,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;
}

View File

@@ -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;
}
@@ -164,7 +176,6 @@ function eachInteraction(loci: Loci, structureGroup: StructureGroup, apply: (int
const unitIdx = group.unitIndexMap.get(e.unit.id);
if (unitIdx === undefined) continue;
__contactIndicesSet.clear();
OrderedSet.forEach(e.indices, v => {
for (let i = fOffsets[v], il = fOffsets[v + 1]; i < il; ++i) {
const fI = fIndices[i];
@@ -188,6 +199,8 @@ function eachInteraction(loci: Loci, structureGroup: StructureGroup, apply: (int
if (apply(Interval.ofSingleton(unitIdx * groupCount + i))) changed = true;
});
__contactIndicesSet.clear();
}
}
return changed;

View File

@@ -22,6 +22,7 @@ import { VisualUpdateState } from '../../../mol-repr/util';
import { ComplexRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider } from '../../../mol-repr/structure/representation';
import { CustomProperty } from '../../common/custom-property';
import { CrossLinkRestraintProvider, CrossLinkRestraint } from './property';
import { Sphere3D } from '../../../mol-math/geometry';
function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<CrossLinkRestraintCylinderParams>, mesh?: Mesh) {
@@ -47,7 +48,16 @@ function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Str
},
};
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 CrossLinkRestraintCylinderParams = {

View File

@@ -11,30 +11,43 @@ import { Model } from '../../mol-model/structure';
import { StructureElement } from '../../mol-model/structure/structure';
import { CustomModelProperty } from '../common/custom-model-property';
export { BestDatabaseSequenceMapping };
export { SIFTSMapping as SIFTSMapping };
interface BestDatabaseSequenceMapping {
interface SIFTSMappingMapping {
readonly dbName: string[],
readonly accession: string[],
readonly num: number[],
readonly num: string[],
readonly residue: string[]
}
namespace BestDatabaseSequenceMapping {
export const Provider: CustomModelProperty.Provider<{}, BestDatabaseSequenceMapping> = CustomModelProperty.createProvider({
label: 'Best Database Sequence Mapping',
namespace SIFTSMapping {
export const Provider: CustomModelProperty.Provider<{}, SIFTSMappingMapping> = CustomModelProperty.createProvider({
label: 'SIFTS Mapping',
descriptor: CustomPropertyDescriptor({
name: 'molstar_best_database_sequence_mapping'
name: 'sifts_sequence_mapping'
}),
type: 'static',
defaultParams: {},
getParams: () => ({}),
isApplicable: (data: Model) => MmcifFormat.is(data.sourceData) && data.sourceData.data.frame.categories?.atom_site?.fieldNames.indexOf('pdbx_sifts_xref_db_name') >= 0,
isApplicable: (data: Model) => isAvailable(data),
obtain: async (ctx, data) => {
return { value: fromCif(data) };
}
});
export function isAvailable(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return false;
const {
pdbx_sifts_xref_db_name: db_name,
pdbx_sifts_xref_db_acc: db_acc,
pdbx_sifts_xref_db_num: db_num,
pdbx_sifts_xref_db_res: db_res
} = model.sourceData.data.db.atom_site;
return db_name.isDefined && db_acc.isDefined && db_num.isDefined && db_res.isDefined;
}
export function getKey(loc: StructureElement.Location) {
const model = loc.unit.model;
const data = Provider.get(model).value;
@@ -55,22 +68,23 @@ namespace BestDatabaseSequenceMapping {
return `${dbName} ${data.accession[rI]} ${data.num[rI]} ${data.residue[rI]}`;
}
function fromCif(model: Model): BestDatabaseSequenceMapping | undefined {
function fromCif(model: Model): SIFTSMappingMapping | undefined {
if (!MmcifFormat.is(model.sourceData)) return;
const { atom_site } = model.sourceData.data.frame.categories;
const db_name = atom_site.getField('pdbx_sifts_xref_db_name');
const db_acc = atom_site.getField('pdbx_sifts_xref_db_acc');
const db_num = atom_site.getField('pdbx_sifts_xref_db_num');
const db_res = atom_site.getField('pdbx_sifts_xref_db_res');
const {
pdbx_sifts_xref_db_name: db_name,
pdbx_sifts_xref_db_acc: db_acc,
pdbx_sifts_xref_db_num: db_num,
pdbx_sifts_xref_db_res: db_res
} = model.sourceData.data.db.atom_site;
if (!db_name || !db_acc || !db_num || !db_res) return;
if (!db_name.isDefined || !db_acc.isDefined || !db_num.isDefined || !db_res.isDefined) return;
const { atomSourceIndex } = model.atomicHierarchy;
const { count, offsets: residueOffsets } = model.atomicHierarchy.residueAtomSegments;
const dbName = new Array<string>(count);
const accession = new Array<string>(count);
const num = new Array<number>(count);
const num = new Array<string>(count);
const residue = new Array<string>(count);
for (let i = 0; i < count; i++) {
@@ -79,15 +93,15 @@ namespace BestDatabaseSequenceMapping {
if (db_name.valueKind(row) !== Column.ValueKind.Present) {
dbName[i] = '';
accession[i] = '';
num[i] = 0;
num[i] = '';
residue[i] = '';
continue;
}
dbName[i] = db_name.str(row);
accession[i] = db_acc.str(row);
num[i] = db_num.int(row);
residue[i] = db_res.str(row);
dbName[i] = db_name.value(row);
accession[i] = db_acc.value(row);
num[i] = db_num.value(row);
residue[i] = db_res.value(row);
}
return { dbName, accession, num, residue };

View File

@@ -12,27 +12,27 @@ import { Color } from '../../../mol-util/color';
import { getPalette, getPaletteParams } from '../../../mol-util/color/palette';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { CustomProperty } from '../../common/custom-property';
import { BestDatabaseSequenceMapping } from '../best-database-mapping';
import { SIFTSMapping } from '../sifts-mapping';
const DefaultColor = Color(0xFAFAFA);
const Description = 'Assigns a color based on best dababase sequence mapping.';
const Description = 'Assigns a color based on SIFTS mapping.';
// same colors for same accessions
const globalAccessionMap = new Map<string, number>();
export const BestDatabaseSequenceMappingColorThemeParams = {
export const SIFTSMappingColorThemeParams = {
...getPaletteParams({ type: 'colors', colorList: 'set-1' }),
};
export type BestDatabaseSequenceMappingColorThemeParams = typeof BestDatabaseSequenceMappingColorThemeParams
export function getBestDatabaseSequenceMappingColorThemeParams(ctx: ThemeDataContext) {
return BestDatabaseSequenceMappingColorThemeParams; // TODO return copy
export type SIFTSMappingColorThemeParams = typeof SIFTSMappingColorThemeParams
export function getSIFTSMappingColorThemeParams(ctx: ThemeDataContext) {
return SIFTSMappingColorThemeParams; // TODO return copy
}
export function BestDatabaseSequenceMappingColorTheme(ctx: ThemeDataContext, props: PD.Values<BestDatabaseSequenceMappingColorThemeParams>): ColorTheme<BestDatabaseSequenceMappingColorThemeParams> {
export function SIFTSMappingColorTheme(ctx: ThemeDataContext, props: PD.Values<SIFTSMappingColorThemeParams>): ColorTheme<SIFTSMappingColorThemeParams> {
let color: LocationColor;
if (ctx.structure) {
for (const m of ctx.structure.models) {
const mapping = BestDatabaseSequenceMapping.Provider.get(m).value;
const mapping = SIFTSMapping.Provider.get(m).value;
if (!mapping) continue;
for (const acc of mapping.accession) {
if (!acc || globalAccessionMap.has(acc)) continue;
@@ -45,7 +45,7 @@ export function BestDatabaseSequenceMappingColorTheme(ctx: ThemeDataContext, pro
const colorMap = new Map<string, Color>();
const getColor = (location: StructureElement.Location) => {
const key = BestDatabaseSequenceMapping.getKey(location);
const key = SIFTSMapping.getKey(location);
if (!key) return DefaultColor;
if (colorMap.has(key)) return colorMap.get(key)!;
@@ -70,7 +70,7 @@ export function BestDatabaseSequenceMappingColorTheme(ctx: ThemeDataContext, pro
}
return {
factory: BestDatabaseSequenceMappingColorTheme,
factory: SIFTSMappingColorTheme,
granularity: 'group',
preferSmoothing: true,
color,
@@ -79,26 +79,26 @@ export function BestDatabaseSequenceMappingColorTheme(ctx: ThemeDataContext, pro
};
}
export const BestDatabaseSequenceMappingColorThemeProvider: ColorTheme.Provider<BestDatabaseSequenceMappingColorThemeParams, 'best-sequence-database-mapping'> = {
name: 'best-sequence-database-mapping',
label: 'Best Database Sequence Mapping',
export const SIFTSMappingColorThemeProvider: ColorTheme.Provider<SIFTSMappingColorThemeParams, 'sifts-mapping'> = {
name: 'sifts-mapping',
label: 'SIFTS Mapping',
category: ColorTheme.Category.Residue,
factory: BestDatabaseSequenceMappingColorTheme,
getParams: getBestDatabaseSequenceMappingColorThemeParams,
defaultValues: PD.getDefaultValues(BestDatabaseSequenceMappingColorThemeParams),
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure?.models.some(m => BestDatabaseSequenceMapping.Provider.isApplicable(m)),
factory: SIFTSMappingColorTheme,
getParams: getSIFTSMappingColorThemeParams,
defaultValues: PD.getDefaultValues(SIFTSMappingColorThemeParams),
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure?.models.some(m => SIFTSMapping.Provider.isApplicable(m)),
ensureCustomProperties: {
attach: async (ctx: CustomProperty.Context, data: ThemeDataContext) => {
if (!data.structure) return;
for (const m of data.structure.models) {
await BestDatabaseSequenceMapping.Provider.attach(ctx, m, void 0, true);
await SIFTSMapping.Provider.attach(ctx, m, void 0, true);
}
},
detach: (data) => {
if (!data.structure) return;
for (const m of data.structure.models) {
BestDatabaseSequenceMapping.Provider.ref(m, false);
SIFTSMapping.Provider.ref(m, false);
}
}
}

View File

@@ -7,6 +7,7 @@
import { AminoAlphabet, NuclecicAlphabet, getProteinOneLetterCode, getRnaOneLetterCode, getDnaOneLetterCode } from './constants';
import { Column } from '../../mol-data/db';
import { assertUnreachable } from '../../mol-util/type-helpers';
// TODO add mapping support to other sequence spaces, e.g. uniprot
@@ -66,7 +67,7 @@ namespace Sequence {
case Kind.DNA: code = getDnaOneLetterCode; break;
case Kind.RNA: code = getRnaOneLetterCode; break;
case Kind.Generic: code = () => 'X'; break;
default: throw new Error(`unknown kind '${kind}'`);
default: assertUnreachable(kind);
}
if (map && map.size > 0) {
return (name: string) => {

View File

@@ -5,7 +5,11 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Column } from '../../../../mol-data/db';
import { mmCIF_Database } from '../../../../mol-io/reader/cif/schema/mmcif';
import { CifWriter } from '../../../../mol-io/writer/cif';
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
import { SIFTSMapping } from '../../../../mol-model-props/sequence/sifts-mapping';
import { StructureElement, Structure, StructureProperties as P } from '../../structure';
import { CifExportContext } from '../mmcif';
import CifField = CifWriter.Field
@@ -26,7 +30,64 @@ function atom_site_auth_asym_id(e: StructureElement.Location) {
return l + suffix;
}
const atom_site_fields = () => CifWriter.fields<StructureElement.Location, Structure>()
const atom_site_pdbx_label_index = {
shouldInclude(s: AtomSiteData) {
return !!s.atom_site?.pdbx_label_index.isDefined;
},
value(e: StructureElement.Location, d: AtomSiteData) {
const srcIndex = d.sourceIndex.value(e.element);
return d.atom_site!.pdbx_label_index.value(srcIndex);
},
};
const SIFTS = {
shouldInclude(s: AtomSiteData) {
return SIFTSMapping.isAvailable(s.structure.models[0]);
},
pdbx_sifts_xref_db_name: {
value(e: StructureElement.Location, d: AtomSiteData) {
const srcIndex = d.sourceIndex.value(e.element);
return d.atom_site!.pdbx_sifts_xref_db_name.value(srcIndex);
},
valueKind(e: StructureElement.Location, d: any) {
const srcIndex = d.sourceIndex.value(e.element);
return d.atom_site!.pdbx_sifts_xref_db_name.valueKind(srcIndex);
},
},
pdbx_sifts_xref_db_acc: {
value(e: StructureElement.Location, d: AtomSiteData) {
const srcIndex = d.sourceIndex.value(e.element);
return d.atom_site!.pdbx_sifts_xref_db_acc.value(srcIndex);
},
valueKind(e: StructureElement.Location, d: any) {
const srcIndex = d.sourceIndex.value(e.element);
return d.atom_site!.pdbx_sifts_xref_db_acc.valueKind(srcIndex);
},
},
pdbx_sifts_xref_db_num: {
value(e: StructureElement.Location, d: AtomSiteData) {
const srcIndex = d.sourceIndex.value(e.element);
return d.atom_site!.pdbx_sifts_xref_db_num.value(srcIndex);
},
valueKind(e: StructureElement.Location, d: any) {
const srcIndex = d.sourceIndex.value(e.element);
return d.atom_site!.pdbx_sifts_xref_db_num.valueKind(srcIndex);
},
},
pdbx_sifts_xref_db_res: {
value(e: StructureElement.Location, d: AtomSiteData) {
const srcIndex = d.sourceIndex.value(e.element);
return d.atom_site!.pdbx_sifts_xref_db_res.value(srcIndex);
},
valueKind(e: StructureElement.Location, d: any) {
const srcIndex = d.sourceIndex.value(e.element);
return d.atom_site!.pdbx_sifts_xref_db_res.valueKind(srcIndex);
},
}
};
const atom_site_fields = () => CifWriter.fields<StructureElement.Location, AtomSiteData>()
.str('group_PDB', P.residue.group_PDB)
.index('id')
.str('type_symbol', P.atom.type_symbol as any)
@@ -62,18 +123,37 @@ const atom_site_fields = () => CifWriter.fields<StructureElement.Location, Struc
.str('auth_asym_id', atom_site_auth_asym_id)
.int('pdbx_PDB_model_num', P.unit.model_num, { encoder: E.deltaRLE })
.int('pdbx_label_index', atom_site_pdbx_label_index.value, { shouldInclude: atom_site_pdbx_label_index.shouldInclude })
// SIFTS
.str('pdbx_sifts_xref_db_name', SIFTS.pdbx_sifts_xref_db_name.value, { shouldInclude: SIFTS.shouldInclude, valueKind: SIFTS.pdbx_sifts_xref_db_name.valueKind })
.str('pdbx_sifts_xref_db_acc', SIFTS.pdbx_sifts_xref_db_acc.value, { shouldInclude: SIFTS.shouldInclude, valueKind: SIFTS.pdbx_sifts_xref_db_acc.valueKind })
.str('pdbx_sifts_xref_db_num', SIFTS.pdbx_sifts_xref_db_num.value, { shouldInclude: SIFTS.shouldInclude, valueKind: SIFTS.pdbx_sifts_xref_db_num.valueKind })
.str('pdbx_sifts_xref_db_res', SIFTS.pdbx_sifts_xref_db_res.value, { shouldInclude: SIFTS.shouldInclude, valueKind: SIFTS.pdbx_sifts_xref_db_res.valueKind })
// .str('operator_name', P.unit.operator_name, {
// shouldInclude: structure => structure.units.some(u => !u.conformation.operator.isIdentity)
// })
.getFields();
interface AtomSiteData {
structure: Structure,
sourceIndex: Column<number>,
atom_site?: mmCIF_Database['atom_site']
}
export const _atom_site: CifCategory<CifExportContext> = {
name: 'atom_site',
instance({ structures }: CifExportContext) {
return {
fields: atom_site_fields(),
source: structures.map(s => ({
data: s,
data: {
structure: s,
sourceIndex: s.model.atomicHierarchy.atomSourceIndex,
atom_site: MmcifFormat.is(s.model.sourceData) ? s.model.sourceData.data.db.atom_site : void 0
} as AtomSiteData,
rowCount: s.elementCount,
keys: () => s.elementLocations()
}))

View File

@@ -78,12 +78,12 @@ function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContex
const ssElements: SSElement<any>[] = [];
const structure = ctx.structures[0];
for (const unit of structure.units) {
// currently can only support this for "identity" operators.
if (!Unit.isAtomic(unit) || !unit.conformation.operator.isIdentity) continue;
for (const { units } of structure.unitSymmetryGroups) {
const u = units[0];
if (!Unit.isAtomic(u)) continue;
const segs = unit.model.atomicHierarchy.residueAtomSegments;
const residues = Segmentation.transientSegments(segs, unit.elements);
const segs = u.model.atomicHierarchy.residueAtomSegments;
const residues = Segmentation.transientSegments(segs, u.elements);
let current: Segmentation.Segment, move = true;
while (residues.hasNext) {
@@ -104,8 +104,8 @@ function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContex
if (startIdx !== key[current.index]) {
move = false;
ssElements[ssElements.length] = {
start: StructureElement.Location.create(structure, unit, segs.offsets[start]),
end: StructureElement.Location.create(structure, unit, segs.offsets[prev]),
start: StructureElement.Location.create(structure, u, segs.offsets[start]),
end: StructureElement.Location.create(structure, u, segs.offsets[prev]),
length: prev - start + 1,
element
};

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -98,6 +98,7 @@ export namespace Model {
const srcIndex = model.atomicHierarchy.atomSourceIndex;
const isIdentity = Column.isIdentity(srcIndex);
const srcIndexArray = isIdentity ? void 0 : srcIndex.toArray({ array: Int32Array });
const coarseGrained = isCoarseGrained(model);
for (let i = 0, il = frames.length; i < il; ++i) {
const f = frames[i];
@@ -119,6 +120,7 @@ export namespace Model {
}
TrajectoryInfo.set(m, { index: i, size: frames.length });
CoarseGrained.set(m, coarseGrained);
trajectory.push(m);
}
@@ -138,11 +140,13 @@ export namespace Model {
const bondData = { pairs: topology.bonds, count: model.atomicHierarchy.atoms._rowCount };
const indexPairBonds = IndexPairBonds.fromData(bondData);
const coarseGrained = isCoarseGrained(model);
let index = 0;
for (const m of trajectory) {
IndexPairBonds.Provider.set(m, indexPairBonds);
TrajectoryInfo.set(m, { index: index++, size: trajectory.length });
CoarseGrained.set(m, coarseGrained);
}
return new ArrayTrajectory(trajectory);
});
@@ -225,35 +229,44 @@ export namespace Model {
};
const CoarseGrainedProp = '__CoarseGrained__';
export const CoarseGrained = {
get(model: Model): boolean | undefined {
return model._staticPropertyData[CoarseGrainedProp];
},
set(model: Model, coarseGrained: boolean) {
return model._staticPropertyData[CoarseGrainedProp] = coarseGrained;
}
};
/**
* Has typical coarse grained atom names (BB, SC1) or less than three times as many
* atoms as polymer residues (C-alpha only models).
*/
export function isCoarseGrained(model: Model): boolean {
if (model._staticPropertyData[CoarseGrainedProp] !== undefined) return model._staticPropertyData[CoarseGrainedProp];
let coarseGrained = CoarseGrained.get(model);
if (coarseGrained === undefined) {
let polymerResidueCount = 0;
const { polymerType } = model.atomicHierarchy.derived.residue;
for (let i = 0; i < polymerType.length; ++i) {
if (polymerType[i] !== PolymerType.NA) polymerResidueCount += 1;
}
let polymerResidueCount = 0;
const { polymerType } = model.atomicHierarchy.derived.residue;
for (let i = 0; i < polymerType.length; ++i) {
if (polymerType[i] !== PolymerType.NA) polymerResidueCount += 1;
// check for coarse grained atom names
let hasBB = false, hasSC1 = false;
const { label_atom_id, _rowCount: atomCount } = model.atomicHierarchy.atoms;
for (let i = 0; i < atomCount; ++i) {
const atomName = label_atom_id.value(i);
if (!hasBB && atomName === 'BB') hasBB = true;
if (!hasSC1 && atomName === 'SC1') hasSC1 = true;
if (hasBB && hasSC1) break;
}
coarseGrained = (hasBB && hasSC1) || (
polymerResidueCount && atomCount
? atomCount / polymerResidueCount < 3
: false
);
CoarseGrained.set(model, coarseGrained);
}
// check for coarse grained atom names
let hasBB = false, hasSC1 = false;
const { label_atom_id, _rowCount: atomCount } = model.atomicHierarchy.atoms;
for (let i = 0; i < atomCount; ++i) {
const atomName = label_atom_id.value(i);
if (!hasBB && atomName === 'BB') hasBB = true;
if (!hasSC1 && atomName === 'SC1') hasSC1 = true;
if (hasBB && hasSC1) break;
}
const coarseGrained = (hasBB && hasSC1) || (
polymerResidueCount && atomCount
? atomCount / polymerResidueCount < 3
: false
);
model._staticPropertyData[CoarseGrainedProp] = coarseGrained;
return coarseGrained;
}

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,12 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Color, ColorMap } from '../../../../mol-util/color';
import { SaccharideNames } from '../../model/types/saccharides';
// follows community standard from https://www.ncbi.nlm.nih.gov/glycans/snfg.html
@@ -302,13 +303,6 @@ const CommonSaccharideNames: { [k: string]: string[] } = {
Psi: ['PSV', 'SF6', 'SF9', 'TTV'],
};
const UnknownSaccharideNames = [
'NGZ', // via CCD
'LAT', // BETA-LACTOSE, Gal-Glc di-saccharide via GlyFinder
'PUF', 'GDA', '9WJ', // via updated CCD
];
/**
* From http://glycam.org/docs/othertoolsservice/2016/06/09/3d-snfg-list-of-residue-names/#CHARMM
*/
@@ -354,9 +348,9 @@ export const SaccharideCompIdMap = (function () {
}
}
}
for (let i = 0, il = UnknownSaccharideNames.length; i < il; ++i) {
map.set(UnknownSaccharideNames[i], UnknownSaccharideComponent);
}
SaccharideNames.forEach(name => {
if (!map.has(name)) map.set(name, UnknownSaccharideComponent);
});
return map;
})();

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -497,7 +497,9 @@ export namespace Loci {
if (!elementIndices) continue;
const indices = getUnitIndices(unit.elements, elementIndices);
elements[elements.length] = { unit, indices };
if (OrderedSet.size(indices)) {
elements[elements.length] = { unit, indices };
}
}
return Loci(loci.structure, elements);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -232,7 +232,17 @@ class Structure {
get interUnitBonds() {
if (this.state.interUnitBonds) return this.state.interUnitBonds;
this.state.interUnitBonds = computeInterUnitBonds(this, { ignoreWater: !this.dynamicBonds });
if (this.parent && this.state.dynamicBonds === this.parent.state.dynamicBonds &&
this.parent.state.interUnitBonds && this.parent.state.interUnitBonds.edgeCount === 0
) {
// no need to compute InterUnitBonds if parent's ones are empty
this.state.interUnitBonds = new InterUnitBonds(new Map());
} else {
this.state.interUnitBonds = computeInterUnitBonds(this, {
ignoreWater: !this.dynamicBonds,
ignoreIon: !this.dynamicBonds,
});
}
return this.state.interUnitBonds;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -25,6 +25,7 @@ import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair';
import { ElementSetIntraBondCache } from './unit/bonds/element-set-intra-bond-cache';
import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
import { getResonance, UnitResonance } from './unit/resonance';
/**
* A building block of a structure that corresponds to an atomic or
@@ -220,7 +221,12 @@ namespace Unit {
remapModel(model: Model, dynamicBonds: boolean, props?: AtomicProperties) {
if (!props) {
props = { ...this.props, bonds: dynamicBonds ? undefined : tryRemapBonds(this, this.props.bonds, model) };
props = {
...this.props,
bonds: dynamicBonds && !this.props.bonds?.props?.canRemap
? undefined
: tryRemapBonds(this, this.props.bonds, model, dynamicBonds)
};
if (!Unit.isSameConformation(this, model)) {
props.boundary = undefined;
props.lookup3d = undefined;
@@ -282,6 +288,12 @@ namespace Unit {
return this.props.rings;
}
get resonance() {
if (this.props.resonance) return this.props.resonance;
this.props.resonance = getResonance(this);
return this.props.resonance;
}
get polymerElements() {
if (this.props.polymerElements) return this.props.polymerElements;
this.props.polymerElements = getAtomicPolymerElements(this);
@@ -342,6 +354,7 @@ namespace Unit {
interface AtomicProperties extends BaseProperties {
bonds?: IntraUnitBonds
rings?: UnitRings
resonance?: UnitResonance
nucleotideElements?: SortedArray<ElementIndex>
proteinElements?: SortedArray<ElementIndex>
residueCount?: number
@@ -481,7 +494,7 @@ namespace Unit {
return isSameConformation(a, b.model);
}
function tryRemapBonds(a: Atomic, old: IntraUnitBonds | undefined, model: Model) {
function tryRemapBonds(a: Atomic, old: IntraUnitBonds | undefined, model: Model, dynamicBonds: boolean) {
// TODO: should include additional checks?
if (!old) return void 0;
@@ -495,7 +508,7 @@ namespace Unit {
return void 0;
}
if (old.props?.canRemap) {
if (old.props?.canRemap || !dynamicBonds) {
return old;
}
return isSameConformation(a, model) ? old : void 0;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -12,7 +12,13 @@ import { StructureElement } from '../../element';
import { Bond } from '../bonds';
import { InterUnitGraph } from '../../../../../mol-math/graph/inter-unit-graph';
type IntraUnitBonds = IntAdjacencyGraph<StructureElement.UnitIndex, { readonly order: ArrayLike<number>, readonly flags: ArrayLike<BondType.Flag> }, { readonly canRemap?: boolean }>
type IntraUnitBonds = IntAdjacencyGraph<StructureElement.UnitIndex, {
readonly order: ArrayLike<number>,
readonly flags: ArrayLike<BondType.Flag>
}, {
/** can remap even with dynamicBonds on, e.g., for water molecules */
readonly canRemap?: boolean
}>
namespace IntraUnitBonds {
export const Empty: IntraUnitBonds = IntAdjacencyGraph.create([], [], [], 0, { flags: [], order: [] });

View File

@@ -193,11 +193,13 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
export interface InterBondComputationProps extends BondComputationProps {
validUnitPair: (structure: Structure, unitA: Unit, unitB: Unit) => boolean
ignoreWater: boolean
ignoreIon: boolean
}
const DefaultInterBondComputationProps = {
...DefaultBondComputationProps,
ignoreWater: true
ignoreWater: true,
ignoreIon: true,
};
function findBonds(structure: Structure, props: InterBondComputationProps) {
@@ -233,7 +235,11 @@ function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondCo
(!Unit.isAtomic(a) || mtA[a.residueIndex[a.elements[0]]] !== MoleculeType.Water) &&
(!Unit.isAtomic(b) || mtB[b.residueIndex[b.elements[0]]] !== MoleculeType.Water)
);
return Structure.validUnitPair(s, a, b) && (notWater || !p.ignoreWater);
const notIon = (
(!Unit.isAtomic(a) || mtA[a.residueIndex[a.elements[0]]] !== MoleculeType.Ion) &&
(!Unit.isAtomic(b) || mtB[b.residueIndex[b.elements[0]]] !== MoleculeType.Ion)
);
return Structure.validUnitPair(s, a, b) && (notWater || !p.ignoreWater) && (notIon || !p.ignoreIon);
}),
});
}

View File

@@ -141,7 +141,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
const aI = atoms[_aI];
const elemA = type_symbol.value(aI);
if (isWatery && (elemA !== 'H' || elemA !== 'O')) isWatery = false;
if (isWatery && (elemA !== 'H' && elemA !== 'O')) isWatery = false;
const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.byAtomIndex.get(aI);
let hasStructConn = false;

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { SortedArray } from '../../../../mol-data/int/sorted-array';
import { sortedCantorPairing } from '../../../../mol-data/util';
import { BondType } from '../../model/types';
import { StructureElement } from '../element';
import { Unit } from '../unit';
export type UnitResonance = {
/**
* Lookup for triplets of atoms in delocalized bonds.
*
* Does not include triplets that are part of aromatic rings.
*/
readonly delocalizedTriplets: {
/** Return 3rd element in triplet or undefined if `a` and `b` are not part of a triplet */
readonly getThirdElement: (a: StructureElement.UnitIndex, b: StructureElement.UnitIndex) => StructureElement.UnitIndex | undefined
/** Return index into `triplets` or undefined if `a` is not part of any triplet */
readonly getTripletIndices: (a: StructureElement.UnitIndex) => number[] | undefined
readonly triplets: SortedArray<StructureElement.UnitIndex>[]
}
}
export function getResonance(unit: Unit.Atomic): UnitResonance {
return {
delocalizedTriplets: getDelocalizedTriplets(unit)
};
}
function getDelocalizedTriplets(unit: Unit.Atomic) {
const bonds = unit.bonds;
const { b, edgeProps, offset } = bonds;
const { order: _order, flags: _flags } = edgeProps;
const { elementAromaticRingIndices } = unit.rings;
const triplets: SortedArray<StructureElement.UnitIndex>[] = [];
const thirdElementMap = new Map<number, StructureElement.UnitIndex>();
const indicesMap = new Map<number, number[]>();
const add = (a: StructureElement.UnitIndex, b: StructureElement.UnitIndex, c: StructureElement.UnitIndex) => {
const index = triplets.length;
triplets.push(SortedArray.ofUnsortedArray([a, b, c]));
thirdElementMap.set(sortedCantorPairing(a, b), c);
if (indicesMap.has(a)) indicesMap.get(a)!.push(index);
else indicesMap.set(a, [index]);
};
for (let i = 0 as StructureElement.UnitIndex; i < unit.elements.length; i++) {
if (elementAromaticRingIndices.has(i)) continue;
const count = offset[i + 1] - offset[i] + 1;
if (count < 2) continue;
const deloBonds: StructureElement.UnitIndex[] = [];
for (let t = offset[i], _t = offset[i + 1]; t < _t; t++) {
const f = _flags[t];
if (!BondType.is(f, BondType.Flag.Aromatic)) continue;
deloBonds.push(b[t]);
}
if (deloBonds.length >= 2) {
add(i, deloBonds[0], deloBonds[1]);
for (let j = 1, jl = deloBonds.length; j < jl; j++) {
add(i, deloBonds[j], deloBonds[0]);
}
}
}
return {
getThirdElement: (a: StructureElement.UnitIndex, b: StructureElement.UnitIndex) => {
return thirdElementMap.get(sortedCantorPairing(a, b));
},
getTripletIndices: (a: StructureElement.UnitIndex) => {
return indicesMap.get(a);
},
triplets,
};
}

View File

@@ -28,17 +28,19 @@ export function computeRings(unit: Unit.Atomic) {
}
const enum Constants {
MaxDepth = 4
MaxDepth = 5
}
interface State {
startVertex: number,
endVertex: number,
count: number,
visited: Int32Array,
isRingAtom: Int32Array,
marked: Int32Array,
queue: Int32Array,
color: Int32Array,
pred: Int32Array,
depth: Int32Array,
left: Int32Array,
right: Int32Array,
@@ -59,9 +61,11 @@ function State(unit: Unit.Atomic, capacity: number): State {
startVertex: 0,
endVertex: 0,
count: 0,
visited: new Int32Array(capacity),
isRingAtom: new Int32Array(capacity),
marked: new Int32Array(capacity),
queue: new Int32Array(capacity),
pred: new Int32Array(capacity),
depth: new Int32Array(capacity),
left: new Int32Array(Constants.MaxDepth),
right: new Int32Array(Constants.MaxDepth),
color: new Int32Array(capacity),
@@ -78,17 +82,26 @@ function State(unit: Unit.Atomic, capacity: number): State {
function resetState(state: State) {
state.count = state.endVertex - state.startVertex;
const { visited, pred, color } = state;
const { isRingAtom, pred, color, depth, marked } = state;
for (let i = 0; i < state.count; i++) {
visited[i] = -1;
isRingAtom[i] = 0;
pred[i] = -1;
marked[i] = -1;
color[i] = 0;
depth[i] = 0;
}
state.currentColor = 0;
state.currentAltLoc = '';
state.hasAltLoc = false;
}
function resetDepth(state: State) {
const { depth } = state;
for (let i = 0; i < state.count; i++) {
depth[i] = state.count + 1;
}
}
function largestResidue(unit: Unit.Atomic) {
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
let size = 0;
@@ -99,8 +112,16 @@ function largestResidue(unit: Unit.Atomic) {
return size;
}
function isStartIndex(state: State, i: number) {
const bondOffset = state.bonds.offset;
const a = state.startVertex + i;
const bStart = bondOffset[a], bEnd = bondOffset[a + 1];
const bondCount = bEnd - bStart;
if (bondCount <= 1 || (state.isRingAtom[i] && bondCount === 2)) return false;
return true;
}
function processResidue(state: State, start: number, end: number) {
const { visited } = state;
state.startVertex = start;
state.endVertex = end;
@@ -117,11 +138,13 @@ function processResidue(state: State, start: number, end: number) {
}
arraySetRemove(altLocs, '');
let mark = 1;
if (altLocs.length === 0) {
resetState(state);
for (let i = 0; i < state.count; i++) {
if (visited[i] >= 0) continue;
findRings(state, i);
if (!isStartIndex(state, i)) continue;
resetDepth(state);
mark = findRings(state, i, mark);
}
} else {
for (let aI = 0; aI < altLocs.length; aI++) {
@@ -129,12 +152,13 @@ function processResidue(state: State, start: number, end: number) {
state.hasAltLoc = true;
state.currentAltLoc = altLocs[aI];
for (let i = 0; i < state.count; i++) {
if (visited[i] >= 0) continue;
if (!isStartIndex(state, i)) continue;
const altLoc = state.altLoc.value(elements[state.startVertex + i]);
if (altLoc && altLoc !== state.currentAltLoc) {
continue;
}
findRings(state, i);
resetDepth(state);
mark = findRings(state, i, mark);
}
}
}
@@ -144,10 +168,10 @@ function processResidue(state: State, start: number, end: number) {
}
}
function addRing(state: State, a: number, b: number) {
function addRing(state: State, a: number, b: number, isRingAtom: Int32Array) {
// only "monotonous" rings
if (b < a) {
return;
return false;
}
const { pred, color, left, right } = state;
@@ -176,7 +200,7 @@ function addRing(state: State, a: number, b: number) {
if (current < 0) break;
}
if (!found) {
return;
return false;
}
current = a;
@@ -190,50 +214,50 @@ function addRing(state: State, a: number, b: number) {
const len = leftOffset + rightOffset;
// rings must have at least three elements
if (len < 3) {
return;
return false;
}
const ring = new Int32Array(len);
let ringOffset = 0;
for (let t = 0; t < leftOffset; t++) ring[ringOffset++] = state.startVertex + left[t];
for (let t = rightOffset - 1; t >= 0; t--) ring[ringOffset++] = state.startVertex + right[t];
for (let t = 0; t < leftOffset; t++) {
ring[ringOffset++] = state.startVertex + left[t];
isRingAtom[left[t]] = 1;
}
for (let t = rightOffset - 1; t >= 0; t--) {
ring[ringOffset++] = state.startVertex + right[t];
isRingAtom[right[t]] = 1;
}
sortArray(ring);
if (state.hasAltLoc) {
// we need to check if the ring was already added because alt locs are present.
// Check if the ring is unique and another one is not it's subset
for (let rI = 0, _rI = state.currentRings.length; rI < _rI; rI++) {
const r = state.currentRings[rI];
for (let rI = 0, _rI = state.currentRings.length; rI < _rI; rI++) {
const r = state.currentRings[rI];
if (ring[0] !== r[0]) continue;
if (ring.length !== r.length) continue;
let areSame = true;
for (let aI = 0, _aI = ring.length; aI < _aI; aI++) {
if (ring[aI] !== r[aI]) {
areSame = false;
break;
}
}
if (areSame) {
return;
}
if (ring.length === r.length) {
if (SortedArray.areEqual(ring as any, r)) return false;
} else if (ring.length > r.length) {
if (SortedArray.isSubset(ring as any, r)) return false;
}
}
state.currentRings.push(SortedArray.ofSortedArray(ring));
return true;
}
function findRings(state: State, from: number) {
const { bonds, startVertex, endVertex, visited, queue, pred } = state;
function findRings(state: State, from: number, mark: number) {
const { bonds, startVertex, endVertex, isRingAtom, marked, queue, pred, depth } = state;
const { elements } = state.unit;
const { b: neighbor, edgeProps: { flags: bondFlags }, offset } = bonds;
visited[from] = 1;
marked[from] = mark;
depth[from] = 0;
queue[0] = from;
let head = 0, size = 1;
while (head < size) {
const top = queue[head++];
const d = depth[top];
const a = startVertex + top;
const start = offset[a], end = offset[a + 1];
@@ -250,18 +274,25 @@ function findRings(state: State, from: number) {
const other = b - startVertex;
if (visited[other] > 0) {
if (marked[other] === mark) {
if (pred[other] !== top && pred[top] !== other) {
addRing(state, top, other);
if (addRing(state, top, other, isRingAtom)) {
return mark + 1;
}
}
continue;
}
visited[other] = 1;
const newDepth = Math.min(depth[other], d + 1);
if (newDepth > Constants.MaxDepth) continue;
depth[other] = newDepth;
marked[other] = mark;
queue[size++] = other;
pred[other] = top;
}
}
return mark + 1;
}
export function getFingerprint(elements: string[]) {

View File

@@ -1,28 +1,34 @@
/**
* 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 David Sehnal <david.sehnal@gmail.com>
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
*/
import { Segmentation } from '../../../../mol-data/int';
import { Mat4 } from '../../../../mol-math/linear-algebra';
import { MinimizeRmsd } from '../../../../mol-math/linear-algebra/3d/minimize-rmsd';
import { BestDatabaseSequenceMapping } from '../../../../mol-model-props/sequence/best-database-mapping';
import { SIFTSMapping } from '../../../../mol-model-props/sequence/sifts-mapping';
import { ElementIndex } from '../../model/indexing';
import { Structure } from '../structure';
import { Unit } from '../unit';
export interface AlignmentResult {
export interface AlignmentResultEntry {
transform: MinimizeRmsd.Result,
pivot: number,
other: number
}
export function alignAndSuperposeWithBestDatabaseMapping(structures: Structure[]): AlignmentResult[] {
export interface AlignmentResult {
entries: AlignmentResultEntry[],
zeroOverlapPairs: [number, number][],
failedPairs: [number, number][]
}
export function alignAndSuperposeWithSIFTSMapping(structures: Structure[], options?: { traceOnly?: boolean }): AlignmentResult {
const indexMap = new Map<string, IndexEntry>();
for (let i = 0; i < structures.length; i++) {
buildIndex(structures[i], indexMap, i);
buildIndex(structures[i], indexMap, i, options?.traceOnly ?? true);
}
const index = Array.from(indexMap.values());
@@ -30,15 +36,26 @@ export function alignAndSuperposeWithBestDatabaseMapping(structures: Structure[]
// TODO: support non-first structure pivots
const pairs = findPairs(structures.length, index);
const ret: AlignmentResult[] = [];
const zeroOverlapPairs: AlignmentResult['zeroOverlapPairs'] = [];
const failedPairs: AlignmentResult['failedPairs'] = [];
const entries: AlignmentResultEntry[] = [];
for (const p of pairs) {
const [a, b] = getPositionTables(index, p.i, p.j, p.count);
const transform = MinimizeRmsd.compute({ a, b });
console.log(Mat4.makeTable(transform.bTransform), transform.rmsd);
ret.push({ transform, pivot: p.i, other: p.j });
if (p.count === 0) {
zeroOverlapPairs.push([p.i, p.j]);
} else {
const [a, b] = getPositionTables(index, p.i, p.j, p.count);
const transform = MinimizeRmsd.compute({ a, b });
if (Number.isNaN(transform.rmsd)) {
failedPairs.push([p.i, p.j]);
} else {
entries.push({ transform, pivot: p.i, other: p.j });
}
}
}
return ret;
return { entries, zeroOverlapPairs, failedPairs };
}
function getPositionTables(index: IndexEntry[], pivot: number, other: number, N: number) {
@@ -53,7 +70,6 @@ function getPositionTables(index: IndexEntry[], pivot: number, other: number, N:
const l = Math.min(a[2] - a[1], b[2] - b[1]);
// TODO: allow to use just backbone atoms?
// TODO: check if residue types match?
for (let i = 0; i < l; i++) {
let eI = (a[1] + i) as ElementIndex;
@@ -121,19 +137,20 @@ interface IndexEntry {
pivots: { [i: number]: [unit: Unit.Atomic, start: ElementIndex, end: ElementIndex] | undefined }
}
function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: number) {
function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: number, traceOnly: boolean) {
for (const unit of structure.units) {
if (unit.kind !== Unit.Kind.Atomic) continue;
const { elements, model } = unit;
const map = BestDatabaseSequenceMapping.Provider.get(model).value;
const map = SIFTSMapping.Provider.get(model).value;
if (!map) return;
const { dbName, accession, num } = map;
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
const traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex;
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
@@ -144,8 +161,15 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
if (!dbName[rI]) continue;
const start = elements[residueSegment.start];
const end = elements[residueSegment.end - 1] + 1 as ElementIndex;
let start, end;
if (traceOnly) {
start = traceElementIndex[rI];
if (start === -1) continue;
end = start + 1 as ElementIndex;
} else {
start = elements[residueSegment.start];
end = elements[residueSegment.end - 1] + 1 as ElementIndex;
}
const key = `${dbName[rI]}-${accession[rI]}-${num[rI]}`;
@@ -161,6 +185,4 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
}
}
}
console.log(index);
}

View File

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

View File

@@ -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>
*/
@@ -13,6 +13,27 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { unzip } from '../../mol-util/zip/zip';
import { PluginStateObject } from '../objects';
async function processFile(file: Asset.File, plugin: PluginContext, format: string, visuals: boolean) {
const info = getFileInfo(file.file!);
const isBinary = plugin.dataFormats.binaryExtensions.has(info.ext);
const { data } = await plugin.builders.data.readFile({ file, isBinary });
const provider = format === 'auto'
? plugin.dataFormats.auto(info, data.cell?.obj!)
: plugin.dataFormats.get(format);
if (!provider) {
plugin.log.warn(`OpenFiles: could not find data provider for '${info.ext}'`);
await plugin.state.data.build().delete(data).commit();
return;
}
// need to await so that the enclosing Task finishes after the update is done.
const parsed = await provider.parse(plugin, data);
if (visuals) {
await provider.visuals?.(plugin, parsed);
}
};
export const OpenFiles = StateAction.build({
display: { name: 'Open Files', description: 'Load one or more files and optionally create default visuals' },
from: PluginStateObject.Root,
@@ -36,36 +57,19 @@ export const OpenFiles = StateAction.build({
return;
}
const processFile = async (file: Asset.File) => {
const info = getFileInfo(file.file!);
const isBinary = plugin.dataFormats.binaryExtensions.has(info.ext);
const { data } = await plugin.builders.data.readFile({ file, isBinary });
const provider = params.format.name === 'auto'
? plugin.dataFormats.auto(info, data.cell?.obj!)
: plugin.dataFormats.get(params.format.params);
if (!provider) {
plugin.log.warn(`OpenFiles: could not find data provider for '${info.name}.${info.ext}'`);
return;
}
// need to await so that the enclosing Task finishes after the update is done.
const parsed = await provider.parse(plugin, data);
if (params.visuals) {
await provider.visuals?.(plugin, parsed);
}
};
for (const file of params.files) {
try {
if (file.file && file.name.toLowerCase().endsWith('.zip')) {
const zippedFiles = await unzip(taskCtx, await file.file.arrayBuffer());
for (const [fn, filedata] of Object.entries(zippedFiles)) {
const asset = Asset.File(new File([filedata as Uint8Array], fn));
await processFile(asset);
if (!(filedata instanceof Uint8Array) || filedata.length === 0) continue;
const asset = Asset.File(new File([filedata], fn));
await processFile(asset, plugin, 'auto', params.visuals);
}
} else {
await processFile(file);
const format = params.format.name === 'auto' ? 'auto' : params.format.params;
await processFile(file, plugin, format, params.visuals);
}
} catch (e) {
console.error(e);
@@ -79,7 +83,7 @@ export const DownloadFile = StateAction.build({
display: { name: 'Download File', description: 'Load one or more file from an URL' },
from: PluginStateObject.Root,
params: (a, ctx: PluginContext) => {
const { options } = ctx.dataFormats;
const options = [...ctx.dataFormats.options, ['zip', 'Zip'] as const];
return {
url: PD.Url(''),
format: PD.Select(options[0][0], options),
@@ -92,16 +96,30 @@ export const DownloadFile = StateAction.build({
await state.transaction(async () => {
try {
const provider = plugin.dataFormats.get(params.format);
if (!provider) {
plugin.log.warn(`DownloadFile: could not find data provider for '${params.format}'`);
return;
}
if (params.format === 'zip') {
// TODO: add ReadZipFile transformer so this can be saved as a simple state snaphot,
// would need support for extracting individual files from zip
const data = await plugin.builders.data.download({ url: params.url, isBinary: true });
const zippedFiles = await unzip(taskCtx, (data.obj?.data as Uint8Array).buffer);
for (const [fn, filedata] of Object.entries(zippedFiles)) {
if (!(filedata instanceof Uint8Array) || filedata.length === 0) continue;
const data = await plugin.builders.data.download({ url: params.url, isBinary: params.isBinary });
const parsed = await provider.parse(plugin, data);
if (params.visuals) {
await provider.visuals?.(plugin, parsed);
const asset = Asset.File(new File([filedata], fn));
await processFile(asset, plugin, 'auto', params.visuals);
}
} else {
const provider = plugin.dataFormats.get(params.format);
if (!provider) {
plugin.log.warn(`DownloadFile: could not find data provider for '${params.format}'`);
return;
}
const data = await plugin.builders.data.download({ url: params.url, isBinary: params.isBinary });
const parsed = await provider.parse(plugin, data);
if (params.visuals) {
await provider.visuals?.(plugin, parsed);
}
}
} catch (e) {
console.error(e);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -10,16 +10,18 @@ import { StateAction, StateSelection, StateTransformer } from '../../mol-state';
import { Task } from '../../mol-task';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../builder/structure/representation-preset';
import { BuiltInTrajectoryFormat, BuiltInTrajectoryFormats } from '../formats/trajectory';
import { BuiltInTrajectoryFormat, BuiltInTrajectoryFormats, TrajectoryFormatCategory } from '../formats/trajectory';
import { RootStructureDefinition } from '../helpers/root-structure';
import { PluginStateObject } from '../objects';
import { StateTransforms } from '../transforms';
import { Download } from '../transforms/data';
import { CustomModelProperties, CustomStructureProperties, TrajectoryFromModelAndCoordinates } from '../transforms/model';
import { CustomModelProperties, CustomStructureProperties, ModelFromTrajectory, TrajectoryFromModelAndCoordinates } from '../transforms/model';
import { Asset } from '../../mol-util/assets';
import { PluginConfig } from '../../mol-plugin/config';
import { getFileInfo } from '../../mol-util/file-info';
import { assertUnreachable } from '../../mol-util/type-helpers';
import { TopologyFormatCategory } from '../formats/topology';
import { CoordinatesFormatCategory } from '../formats/coordinates';
const DownloadModelRepresentationOptions = (plugin: PluginContext) => {
const representationDefault = plugin.config.get(PluginConfig.Structure.DefaultRepresentationPreset) || PresetStructureRepresentations.auto.id;
@@ -155,7 +157,7 @@ const DownloadStructure = StateAction.build({
asTrajectory = !!src.params.options.asTrajectory;
format = 'mol';
break;
default: throw new Error(`${(src as any).name} not supported.`);
default: assertUnreachable(src);
}
const representationPreset: any = params.source.params.options.representation || plugin.config.get(PluginConfig.Structure.DefaultRepresentationPreset) || PresetStructureRepresentations.auto.id;
@@ -311,4 +313,127 @@ export const AddTrajectory = StateAction.build({
const structure = await ctx.builders.structure.createStructure(model.selector);
await ctx.builders.structure.representation.applyPreset(structure, 'auto');
}).runInContext(taskCtx);
}));
export const LoadTrajectory = StateAction.build({
display: { name: 'Load Trajectory', description: 'Load trajectory of model/topology and coordinates from URL or file.' },
from: PluginStateObject.Root,
params(a, ctx: PluginContext) {
const { options } = ctx.dataFormats;
const modelOptions = options.filter(o => o[2] === TrajectoryFormatCategory || o[2] === TopologyFormatCategory);
const coordinatesOptions = options.filter(o => o[2] === CoordinatesFormatCategory);
const modelExts: string[] = [];
const coordinatesExts: string[] = [];
for (const { provider } of ctx.dataFormats.list) {
if (provider.category === TrajectoryFormatCategory || provider.category === TopologyFormatCategory) {
if (provider.binaryExtensions) modelExts.push(...provider.binaryExtensions);
if (provider.stringExtensions) modelExts.push(...provider.stringExtensions);
} else if (provider.category === CoordinatesFormatCategory) {
if (provider.binaryExtensions) coordinatesExts.push(...provider.binaryExtensions);
if (provider.stringExtensions) coordinatesExts.push(...provider.stringExtensions);
}
}
return {
source: PD.MappedStatic('file', {
url: PD.Group({
model: PD.Group({
url: PD.Url(''),
format: PD.Select(modelOptions[0][0], modelOptions),
isBinary: PD.Boolean(false),
}, { isExpanded: true }),
coordinates: PD.Group({
url: PD.Url(''),
format: PD.Select(coordinatesOptions[0][0], coordinatesOptions),
}, { isExpanded: true })
}, { isFlat: true }),
file: PD.Group({
model: PD.File({ accept: modelExts.map(e => `.${e}`).join(','), label: 'Model' }),
coordinates: PD.File({ accept: coordinatesExts.map(e => `.${e}`).join(','), label: 'Coordinates' }),
}, { isFlat: true }),
}, { options: [['url', 'URL'], ['file', 'File']] })
};
}
})(({ params, state }, ctx: PluginContext) => Task.create('Load Trajectory', taskCtx => {
return state.transaction(async () => {
const s = params.source;
if (s.name === 'file' && (s.params.model === null || s.params.coordinates === null)) {
ctx.log.error('No file(s) selected');
return;
}
if (s.name === 'url' && (!s.params.model || !s.params.coordinates)) {
ctx.log.error('No URL(s) given');
return;
}
const processUrl = async (url: string | Asset.Url, format: string, isBinary: boolean) => {
const data = await ctx.builders.data.download({ url, isBinary });
const provider = ctx.dataFormats.get(format);
if (!provider) {
ctx.log.warn(`LoadTrajectory: could not find data provider for '${format}'`);
return;
}
return provider.parse(ctx, data);
};
const processFile = async (file: Asset.File | null) => {
if (!file) throw new Error('No file selected');
const info = getFileInfo(file.file!);
const isBinary = ctx.dataFormats.binaryExtensions.has(info.ext);
const { data } = await ctx.builders.data.readFile({ file, isBinary });
const provider = ctx.dataFormats.auto(info, data.cell?.obj!);
if (!provider) {
ctx.log.warn(`LoadTrajectory: could not find data provider for '${info.ext}'`);
await ctx.state.data.build().delete(data).commit();
return;
}
return provider.parse(ctx, data);
};
try {
const modelParsed = s.name === 'url'
? await processUrl(s.params.model.url, s.params.model.format, s.params.model.isBinary)
: await processFile(s.params.model);
let model;
if ('trajectory' in modelParsed) {
model = await state.build().to(modelParsed.trajectory)
.apply(ModelFromTrajectory, { modelIndex: 0 })
.commit();
} else {
model = modelParsed.topology;
}
//
const coordinates = s.name === 'url'
? await processUrl(s.params.coordinates.url, s.params.coordinates.format, true)
: await processFile(s.params.coordinates);
//
const dependsOn = [model.ref, coordinates.ref];
const traj = state.build().toRoot()
.apply(TrajectoryFromModelAndCoordinates, {
modelRef: model.ref,
coordinatesRef: coordinates.ref
}, { dependsOn })
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
await state.updateTree(traj).runInContext(taskCtx);
const structure = await ctx.builders.structure.createStructure(traj.selector);
await ctx.builders.structure.representation.applyPreset(structure, 'auto');
} catch (e) {
console.error(e);
ctx.log.error(`Error loading trajectory`);
}
}).runInContext(taskCtx);
}));

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -15,6 +15,7 @@ import { Download } from '../transforms/data';
import { DataFormatProvider } from '../formats/provider';
import { Asset } from '../../mol-util/assets';
import { StateTransforms } from '../transforms';
import { assertUnreachable } from '../../mol-util/type-helpers';
export type EmdbDownloadProvider = 'pdbe' | 'rcsb'
@@ -75,8 +76,8 @@ const DownloadDensity = StateAction.build({
case 'pdb-xray':
downloadParams = src.params.provider.server === 'pdbe' ? {
url: Asset.Url(src.params.type === '2fofc'
? `http://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.provider.id.toLowerCase()}.ccp4`
: `http://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.provider.id.toLowerCase()}_diff.ccp4`),
? `https://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.provider.id.toLowerCase()}.ccp4`
: `https://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.provider.id.toLowerCase()}_diff.ccp4`),
isBinary: true,
label: `PDBe X-ray map: ${src.params.provider.id}`
} : {
@@ -109,7 +110,7 @@ const DownloadDensity = StateAction.build({
label: `RCSB PDB X-ray Density Server: ${src.params.provider.id}`
};
break;
default: throw new Error(`${(src as any).name} not supported.`);
default: assertUnreachable(src);
}
const data = await plugin.builders.data.download(downloadParams);
@@ -131,7 +132,7 @@ const DownloadDensity = StateAction.build({
entryId = src.params.provider.id;
provider = plugin.dataFormats.get('dscif');
break;
default: throw new Error(`${(src as any).name} not supported.`);
default: assertUnreachable(src);
}
if (!provider) {

View File

@@ -24,8 +24,6 @@ import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bo
import { StructConn } from '../../../mol-model-formats/structure/property/bonds/struct_conn';
import { StructureRepresentationRegistry } from '../../../mol-repr/structure/registry';
import { assertUnreachable } from '../../../mol-util/type-helpers';
import { Color } from '../../../mol-util/color';
import { PostprocessingParams } from '../../../mol-canvas3d/passes/postprocessing';
export interface StructureRepresentationPresetProvider<P = any, S extends _Result = _Result> extends PresetProvider<PluginStateObject.Molecule.Structure, P, S> { }
export function StructureRepresentationPresetProvider<P, S extends _Result>(repr: StructureRepresentationPresetProvider<P, S>) { return repr; }
@@ -39,6 +37,7 @@ export namespace StructureRepresentationPresetProvider {
export const CommonParams = {
ignoreHydrogens: PD.Optional(PD.Boolean(false)),
ignoreLight: PD.Optional(PD.Boolean(false)),
quality: PD.Optional(PD.Select<VisualQuality>('auto', VisualQualityOptions)),
theme: PD.Optional(PD.Group({
globalName: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
@@ -70,9 +69,11 @@ export namespace StructureRepresentationPresetProvider {
const typeParams = {
quality: plugin.managers.structure.component.state.options.visualQuality,
ignoreHydrogens: !plugin.managers.structure.component.state.options.showHydrogens,
ignoreLight: plugin.managers.structure.component.state.options.ignoreLight,
};
if (params.quality && params.quality !== 'auto') typeParams.quality = params.quality;
if (params.ignoreHydrogens !== void 0) typeParams.ignoreHydrogens = !!params.ignoreHydrogens;
if (params.ignoreLight !== void 0) typeParams.ignoreLight = !!params.ignoreLight;
const color: ColorTheme.BuiltIn | undefined = params.theme?.globalName ? params.theme?.globalName : void 0;
const ballAndStickColor: ColorTheme.BuiltInParams<'element-symbol'> = params.theme?.carbonColor !== undefined
? { carbonColor: getCarbonColorParams(params.theme?.carbonColor) }
@@ -100,15 +101,6 @@ type CommonParams = StructureRepresentationPresetProvider.CommonParams
const reprBuilder = StructureRepresentationPresetProvider.reprBuilder;
const updateFocusRepr = StructureRepresentationPresetProvider.updateFocusRepr;
function resetPostprocessingProps(plugin: PluginContext) {
if (plugin.canvas3d) {
const p = PD.getDefaultValues(PostprocessingParams);
plugin.canvas3d.setProps({
postprocessing: { outline: p.outline, occlusion: p.occlusion }
});
}
}
const auto = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-auto',
display: {
@@ -148,7 +140,6 @@ const empty = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-empty',
display: { name: 'Empty', description: 'Removes all existing representations.' },
async apply(ref, params, plugin) {
resetPostprocessingProps(plugin);
return { };
}
});
@@ -194,17 +185,15 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'non-standard' }),
branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.3 }, color, colorParams: ballAndStickColor }, { tag: 'branched-ball-and-stick' }),
branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }, { tag: 'branched-snfg-3d' }),
water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6 }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'water' }),
water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6, visuals: waterType === 'line' ? ['intra-bond', 'element-point'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'water' }),
ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ion' }),
lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6 }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'lipid' }),
lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6, visuals: lipidType === 'line' ? ['intra-bond'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'lipid' }),
coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id' }, { tag: 'coarse' })
};
await update.commit({ revertOnError: false });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
resetPostprocessingProps(plugin);
return { components, representations };
}
});
@@ -244,8 +233,6 @@ const proteinAndNucleic = StructureRepresentationPresetProvider({
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
resetPostprocessingProps(plugin);
return { components, representations };
}
});
@@ -298,8 +285,6 @@ const coarseSurface = StructureRepresentationPresetProvider({
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
resetPostprocessingProps(plugin);
return { components, representations };
}
});
@@ -333,8 +318,6 @@ const polymerCartoon = StructureRepresentationPresetProvider({
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
resetPostprocessingProps(plugin);
return { components, representations };
}
});
@@ -401,8 +384,6 @@ const atomicDetail = StructureRepresentationPresetProvider({
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name ?? color, params.theme?.focus?.params ?? colorParams);
resetPostprocessingProps(plugin);
return { components, representations };
}
});
@@ -436,21 +417,6 @@ const illustrative = StructureRepresentationPresetProvider({
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name ?? color, params.theme?.focus?.params);
if (plugin.canvas3d) {
plugin.canvas3d.setProps({
postprocessing: {
outline: {
name: 'on',
params: { scale: 1, color: Color(0x000000), threshold: 0.25 }
},
occlusion: {
name: 'on',
params: { bias: 0.9, blurKernelSize: 15, radius: 5, samples: 32 }
},
}
});
}
return { components, representations };
}
});
@@ -471,6 +437,6 @@ export const PresetStructureRepresentations = {
'polymer-and-ligand': polymerAndLigand,
'protein-and-nucleic': proteinAndNucleic,
'coarse-surface': coarseSurface,
'illustrative': illustrative,
illustrative,
};
export type PresetStructureRepresentations = typeof PresetStructureRepresentations;

Some files were not shown because too many files have changed in this diff Show More