Compare commits

...

234 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
Alexander Rose
2ad0754b90 3.0.0 2022-01-23 18:12:48 -08:00
Alexander Rose
3ecb3af57b changelog 2022-01-23 18:08:01 -08:00
Alexander Rose
ec4f15f549 improve/fix InteractionsIntraUnitVisual marking 2022-01-23 17:55:51 -08:00
Alexander Rose
2458ea7b92 Merge pull request #349 from molstar/custom-theme-colors
add custom theme colors
2022-01-23 15:13:24 -08:00
Alexander Rose
c5e6bedf11 Merge branch 'master' into custom-theme-colors 2022-01-23 13:54:57 -08:00
dsehnal
8528e5a666 Support/bugfixes for atom_site.pdbx_sifts_xref categories 2022-01-23 20:46:49 +01:00
dsehnal
6ed232b3d9 fix using default values for webgl1/wboit features in the default viewer app 2022-01-23 19:25:09 +01:00
dsehnal
f8aae8cbd1 skip Coarse models in export extension 2022-01-23 16:13:02 +01:00
Alexander Rose
00c2517045 package updates 2022-01-22 14:06:26 -08:00
Alexander Rose
99b043a929 avoid circular dependency 2022-01-22 11:10:41 -08:00
Alexander Rose
5900e27e39 add custom theme colors
- element-symbol
- molecule-type
- residue-name
- secondary-structure
2022-01-22 10:51:41 -08:00
Alexander Rose
1b79d34907 fix marking of carbohydrate visuals 2022-01-22 08:53:47 -08:00
Alexander Rose
fc52e29c92 re-add using _struct_asym cat in getStructAsymMap
- needed for ihm model support
2022-01-22 08:50:29 -08:00
Alexander Rose
df23b3c0fe fix coarse model support in entity-id color theme
- add StructureProperties.coarse.entity_id
2022-01-22 08:48:07 -08:00
David Sehnal
5e25716c98 Merge pull request #334 from molstar/export-extension
Model export extension & related improvements
2022-01-18 13:23:11 +01:00
dsehnal
f70a10bc56 add info about CIF export failure 2022-01-18 13:20:53 +01:00
dsehnal
0ccb045f4e Merge branch 'master' of https://github.com/molstar/molstar into export-extension 2022-01-18 12:30:45 +01:00
Alexander Rose
fa18d0d852 3.0.0-dev.10 2022-01-17 14:08:28 -08:00
Alexander Rose
687c4342fb changelog 2022-01-17 14:03:55 -08:00
Alexander Rose
9459af46b8 Merge pull request #337 from molstar/anim-rock
Add rock animation
2022-01-17 13:38:08 -08:00
Alexander Rose
fc5832747a Merge pull request #345 from molstar/bump-immutable
bump immutable to 4.0
2022-01-17 13:37:19 -08:00
Alexander Rose
01205d244b tweak trackball speed calc 2022-01-17 13:16:19 -08:00
dsehnal
31a555255a bump immutable to 4.0 2022-01-17 17:56:39 +01:00
dsehnal
fbb60c9493 Treat empty string as non-present value in BinaryCIF 2022-01-17 16:21:14 +01:00
dsehnal
9f953ef51c add Model Export overlay 2022-01-17 15:47:56 +01:00
dsehnal
4871f1547c Generate structAsymMap from normalized atomic hierarchy
- Fixes issue with remapped chains
- Requires to parse PRD separately
2022-01-17 14:52:53 +01:00
dsehnal
d6413529f4 PR feedback 2022-01-17 13:58:20 +01:00
dsehnal
724cf5a0da Add integrations section to readme 2022-01-17 12:58:35 +01:00
dsehnal
b6847907ca Add ModelExport to viewer/app.ts 2022-01-17 11:37:49 +01:00
dsehnal
fb54a1aed7 Merge branch 'master' of https://github.com/molstar/molstar into export-extension 2022-01-17 11:37:16 +01:00
Alexander Rose
9815318daf add entity-source option to illustrative coloring 2022-01-16 23:08:43 -08:00
Alexander Rose
bc13b98111 Merge branch 'master' of https://github.com/molstar/molstar into anim-rock 2022-01-16 15:48:33 -08:00
Alexander Rose
238b70c121 simplify rock state/trackball animation
- use sin() instead of smoothstep()
2022-01-16 15:47:25 -08:00
Alexander Rose
dcd23bc0cb improve illustrative style support
- add illustrative representation preset
- add style option to illustrative color theme
2022-01-16 13:13:00 -08:00
Alexander Rose
4694ea85fa support custom colors in molecule-type theme 2022-01-16 13:05:43 -08:00
Alexander Rose
bdb17743d7 update schemas 2022-01-15 19:54:22 -08:00
Alexander Rose
8b76ff2461 update packages 2022-01-15 19:53:04 -08:00
Alexander Rose
ea5421002b add camera rock state animation 2022-01-15 18:59:12 -08:00
Alexander Rose
76ac55917d type, tweaks 2022-01-15 18:45:56 -08:00
Alexander Rose
5cfb2376c4 Merge branch 'master' of https://github.com/molstar/molstar into anim-rock 2022-01-15 15:56:55 -08:00
Alexander Rose
6b9d3fd80e cleaner MembraneOrientationVisuals defaults 2022-01-15 13:52:14 -08:00
dsehnal
a09752b62e changelog and contibutors in package.json 2022-01-14 12:53:51 +01:00
David Sehnal
3134e1d9f9 Merge pull request #341 from molstar/fix-camera-spin-stutter
Pass animation info to state animations
2022-01-14 12:51:51 +01:00
David Sehnal
e94ecf2a0b Merge pull request #314 from ptourlas/feature/formal-charge-labels
Feature/formal charge labels
2022-01-14 12:51:09 +01:00
ptourlas
1bd4d841a1 ADD: Test multiple charge lines in molfiles 2022-01-12 17:01:36 +02:00
ptourlas
8e349f47a5 (author tags) 2022-01-12 16:50:06 +02:00
ptourlas
119c0a4231 ADD: Test formal charge parsing in sdf reader 2022-01-12 16:25:23 +02:00
ptourlas
f009f533e0 TWEAK: Default charges in V3000 sdf files 2022-01-12 15:05:07 +02:00
ptourlas
3ab0c1e509 ADD: Test formal charge parsing in mol reader 2022-01-12 12:42:29 +02:00
dsehnal
e3d264e239 Pass animation info to state animations
+ Fix camera stutter for "camera spin"
2022-01-11 19:38:32 +01:00
dsehnal
9bd60f8e8e Fix getOperatorsForIndex 2022-01-11 17:34:43 +01:00
ptourlas
bcec1d9637 (forgot to cleanup) 2022-01-11 16:41:23 +02:00
ptourlas
1b431b1d20 RFR: Formal charge assignment at model creation 2022-01-11 16:04:46 +02:00
ptourlas
23c2dcdfd4 FIX: Use tokenizer to ensure the loop terminates 2022-01-11 15:01:15 +02:00
ptourlas
dd415bf802 FTR: Support multiple formal charge lines
* Start with two empty arrays for indexes and charges.
* Each `M CHG` line is passed to `handleFormalCharges()` and the
parsed elements are pushed to the arrays.
* The `Column`s are made at the end of this process.
2022-01-11 14:40:57 +02:00
ptourlas
7bc0e9db7c RFR: formalChargeMapper tweaks
* The key is of type number, no need to stringify it.
* Simplified `switch()`.
2022-01-11 00:18:25 +02:00
ptourlas
ca10bb01db Merge branch 'molstar:master' into feature/formal-charge-labels 2022-01-10 23:46:37 +02:00
Alexander Rose
ade5e4d4b8 re-allow interaction during trackball animation
- was disallowed as a stop-gap measure
- ok after improving temporal multi sampling
2022-01-09 14:30:43 -08:00
ptourlas
cb76b53a1b FTR: Add formal charges during model creation
This implementation takes into account both the property and atom block
cases and makes sure the latter is ignored if the first one is present.
2022-01-10 00:30:25 +02:00
Alexander Rose
b9423f70d4 Merge branch 'master' of https://github.com/molstar/molstar into anim-rock 2022-01-09 14:25:36 -08:00
Alexander Rose
796a034fec add rock animation to trackball controls 2022-01-08 17:02:33 -08:00
ptourlas
f9deb54352 FIX: Found the right tokenizer operations
(at last)
2022-01-08 01:16:59 +02:00
ptourlas
eae3c1b33a FIX: Formal charges are all on the same line
Therefore we have to get the count of charges and iterate based on that.
A handle properties function is added so that new handler can be used based
the property type. Note that this function only returns the charges at
the moment for simplicity. A more general version should return multiple
properties.
2022-01-07 16:14:47 +02:00
ptourlas
5c5f8aa741 FTR:(WIP) Add formal charges during model creation 2022-01-06 16:12:44 +02:00
ptourlas
ba68ac2e32 FTR: Parse formal charges in the atom block 2022-01-06 15:58:49 +02:00
ptourlas
239fef281e Merge branch 'master' into feature/formal-charge-labels 2022-01-06 13:32:05 +02:00
dsehnal
c0880b647f changelog 2022-01-03 13:51:09 +01:00
dsehnal
039dc6a76b changelog 2022-01-03 13:49:59 +01:00
dsehnal
042a7625ad isWithoutOperator tweak 2022-01-03 13:46:13 +01:00
dsehnal
41827c478d open zip files with multiple entries 2022-01-03 13:42:37 +01:00
dsehnal
9a73180c3c support transformed export & structAsymMap parsing fix 2022-01-03 13:15:51 +01:00
dsehnal
333ee85fdb omit suffix for identity assembly operators 2022-01-03 12:52:05 +01:00
dsehnal
fa8ca45b6a do not include assembly categories in export if an operator has been applied 2022-01-03 12:34:37 +01:00
dsehnal
c2bae1aeb7 model export extension 2022-01-03 12:26:36 +01:00
ptourlas
13ea97bd98 Merge branch 'master' into feature/formal-charge-labels 2021-12-27 18:18:27 +02:00
ptourlas
009a17a9ca (forgot the author tag) 2021-12-27 17:56:25 +02:00
ptourlas
ca38d9adb1 (WIP) Formal charges handler implementation 2021-12-27 17:44:59 +02:00
ptourlas
4e5a86e3db (WIP) Add the formal charges handler to sdf parser 2021-12-27 17:37:52 +02:00
ptourlas
a736fe7989 Add formal charge option to atom site 2021-12-15 00:35:34 +02:00
ptourlas
1970b7f249 Spot the formal charge prefix 2021-12-15 00:34:06 +02:00
202 changed files with 9562 additions and 6719 deletions

View File

@@ -6,6 +6,118 @@ 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:
- Do not include suffix for "identity assembly operators"
- Do not include assembly-related categories to export if the structure was composed from an assembly
- Special case for ``structAsymMap`` if Mol* asym id operator mapping is present
- Support for opening ZIP files with multiple entries
- Add Model Export extension
- Bugfix: Automatically treat empty string as "non-present" value in BinaryCIF writer.
- Fix coarse model support in entity-id color theme
- Fix marking of carbohydrate visuals (whole chain could get marked instead of single residue)
- Add custom colors to "element-symbol", "molecule-type", "residue-name", and "secondary-structure" themes
- Support/bugfixes for ``atom_site.pdbx_sifts_xref`` categories
- Improve/fix marking of ``InteractionsIntraUnitVisual`` (mark when all contact-feature members are given)
## [v3.0.0-dev.10] - 2022-01-17
- Fix ``getOperatorsForIndex``
- Pass animation info (current frame & count) to state animations
- Fix camera stutter for "camera spin" animation
- Add formal charge parsing support for MOL/SDF files (thanks @ptourlas)
- [Breaking] Cleaner looking ``MembraneOrientationVisuals`` defaults
- [Breaking] Add rock animation to trackball controls
- Add ``animate`` to ``TrackballControlsParams``, remove ``spin`` and ``spinSpeed``
- Add ``animate`` to ``SimpleSettingsParams``, remove ``spin``
- Add "camera rock" state animation
- Add support for custom colors to "molecule-type" theme
- [Breaking] Add style parameter to "illustrative" color theme
- Defaults to "entity-id" style instead of "chain-id"
- Add "illustrative" representation preset
## [v3.0.0-dev.9] - 2022-01-09
- Add PDBj as a ``pdb-provider`` option

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

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

8903
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "3.0.0-dev.9",
"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",
@@ -86,77 +86,75 @@
"Áron Samuel Kovács <aron.kovacs@mail.muni.cz>",
"Ludovic Autin <autin@scripps.edu>",
"Michal Malý <michal.maly@ibt.cas.cz>",
"Jiří Černý <jiri.cerny@ibt.cas.cz>"
"Jiří Černý <jiri.cerny@ibt.cas.cz>",
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>"
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^3.1.0",
"@graphql-codegen/cli": "^2.3.0",
"@graphql-codegen/time": "^3.1.0",
"@graphql-codegen/typescript": "^2.4.1",
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.0",
"@graphql-codegen/typescript-graphql-request": "^4.3.1",
"@graphql-codegen/typescript-operations": "^2.2.1",
"@graphql-codegen/add": "^3.1.1",
"@graphql-codegen/cli": "^2.6.2",
"@graphql-codegen/time": "^3.1.1",
"@graphql-codegen/typescript": "^2.4.7",
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.1",
"@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.0.3",
"@typescript-eslint/eslint-plugin": "^5.5.0",
"@typescript-eslint/parser": "^5.5.0",
"@types/jest": "^27.4.1",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"benchmark": "^2.1.4",
"concurrently": "^6.4.0",
"cpx2": "^4.0.0",
"concurrently": "^7.0.0",
"cpx2": "^4.2.0",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.5.1",
"eslint": "^8.3.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": "^15.7.2",
"http-server": "^14.0.0",
"jest": "^27.3.1",
"mini-css-extract-plugin": "^2.4.5",
"fs-extra": "^10.0.1",
"graphql": "^16.3.0",
"http-server": "^14.1.0",
"jest": "^27.5.1",
"mini-css-extract-plugin": "^2.6.0",
"path-browserify": "^1.0.1",
"raw-loader": "^4.0.2",
"sass": "^1.43.5",
"sass-loader": "^12.3.0",
"simple-git": "^2.47.0",
"sass": "^1.49.9",
"sass-loader": "^12.6.0",
"simple-git": "^3.3.0",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.1",
"ts-jest": "^27.0.7",
"typescript": "^4.5.2",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1"
"ts-jest": "^27.1.3",
"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.10",
"@types/node-fetch": "^2.5.12",
"@types/react": "^17.0.37",
"@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.0",
"body-parser": "^1.19.2",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.17.1",
"express": "^4.17.3",
"h264-mp4-encoder": "^1.0.12",
"immer": "^9.0.7",
"immutable": "^3.8.2",
"node-fetch": "^2.6.2",
"rxjs": "^7.4.0",
"swagger-ui-dist": "^4.1.1",
"immer": "^9.0.12",
"immutable": "^4.0.0",
"node-fetch": "^2.6.7",
"rxjs": "^7.5.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": "^4.9.2"
"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

@@ -13,15 +13,19 @@ import { GeometryExport } from '../../extensions/geo-export';
import { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
import { ModelExport } from '../../extensions/model-export';
import { Mp4Export } from '../../extensions/mp4-export';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { ZenodoImport } from '../../extensions/zenodo';
import { Volume } from '../../mol-model/volume';
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset';
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
import { BuildInStructureFormat } from '../../mol-plugin-state/formats/structure';
import { BuiltInTopologyFormat } from '../../mol-plugin-state/formats/topology';
import { BuiltInCoordinatesFormat } from '../../mol-plugin-state/formats/coordinates';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
@@ -57,9 +61,11 @@ const Extensions = {
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
'g3d': PluginSpec.Behavior(G3DFormat),
'model-export': PluginSpec.Behavior(ModelExport),
'mp4-export': PluginSpec.Behavior(Mp4Export),
'geo-export': PluginSpec.Behavior(GeometryExport),
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
'zenodo-import': PluginSpec.Behavior(ZenodoImport),
};
const DefaultViewerOptions = {
@@ -99,7 +105,14 @@ export class Viewer {
}
static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
const o = { ...DefaultViewerOptions, ...options };
const definedOptions = {} as any;
// filter for defined properies only so the default values
// are property applied
for (const p of Object.keys(options) as (keyof ViewerOptions)[]) {
if (options[p] !== void 0) definedOptions[p] = options[p];
}
const o: ViewerOptions = { ...DefaultViewerOptions, ...definedOptions };
const defaultSpec = DefaultPluginUISpec();
const spec: PluginUISpec = {
@@ -357,11 +370,13 @@ export class Viewer {
const repr = plugin.build();
for (const iso of isovalues) {
const volume: StateObjectSelector<PluginStateObject.Volume.Data> = parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume;
const volumeData = volume.cell!.obj!.data;
repr
.to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume)
.to(volume)
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
type: 'isosurface',
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
typeParams: { alpha: iso.alpha ?? 1, isoValue: Volume.adjustedIsoValue(volumeData, iso.value, iso.type) },
color: 'uniform',
colorParams: { value: iso.color }
}));
@@ -441,11 +456,11 @@ export interface VolumeIsovalueInfo {
export interface LoadTrajectoryParams {
model: { kind: 'model-url', url: string, format?: BuiltInTrajectoryFormat /* mmcif */, isBinary?: boolean }
| { kind: 'model-data', data: string | number[] | ArrayBuffer | Uint8Array, format?: BuiltInTrajectoryFormat /* mmcif */ }
| { kind: 'topology-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
| { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
| { kind: 'topology-url', url: string, format: BuiltInTopologyFormat, isBinary?: boolean }
| { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuiltInTopologyFormat },
modelLabel?: string,
coordinates: { kind: 'coordinates-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
coordinates: { kind: 'coordinates-url', url: string, format: BuiltInCoordinatesFormat, isBinary?: boolean }
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuiltInCoordinatesFormat },
coordinatesLabel?: string,
preset?: keyof PresetTrajectoryHierarchy
}

View File

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

View File

@@ -9,4 +9,4 @@ import './embedded.html';
import './favicon.ico';
import './index.html';
require('mol-plugin-ui/skin/light.scss');
export * from './app';
export * from './app';

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

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
// Generated on 2021-11-25T14:34:23-08:00
// Generated on 2022-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']>;
@@ -11113,11 +11282,11 @@ export type RcsbUniprotAlignments = {
export type RcsbUniprotAlignmentsCoreEntityAlignments = {
/** Aligned region */
readonly aligned_regions?: Maybe<ReadonlyArray<Maybe<CoreEntityAlignmentsAlignedRegions>>>;
readonly aligned_regions: ReadonlyArray<Maybe<CoreEntityAlignmentsAlignedRegions>>;
/** core_entity identifiers */
readonly core_entity_identifiers?: Maybe<CoreEntityAlignmentsCoreEntityIdentifiers>;
/** Alignment scores */
readonly scores?: Maybe<CoreEntityAlignmentsScores>;
readonly scores: CoreEntityAlignmentsScores;
};
export type RcsbUniprotAnnotation = {
@@ -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.props.spin;
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

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

View File

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

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -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,5 +1,12 @@
/**
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
*/
import { parseMol } from '../mol/parser';
import { parseMol, formalChargeMapper } from '../mol/parser';
const MolString = `2244
-OEChem-04072009073D
@@ -49,6 +56,48 @@ const MolString = `2244
13 20 1 0 0 0 0
M END`;
const MolStringWithAtomBlockCharge = `
Ketcher 1 72215442D 1 1.00000 0.00000 0
4 3 0 0 0 0 999 V2000
0.0000 0.0000 0.0000 C 0 1 0 0 0 0 0 0 0 0 0 0
0.8660 0.5000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
-0.8660 0.5000 0.0000 S 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 -1.0000 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
1 4 2 0 0 0 0
3 1 1 0 0 0 0
2 1 1 0 0 0 0
M END`;
const MolStringWithPropertyBlockCharge = `
Ketcher 1 72215442D 1 1.00000 0.00000 0
4 3 0 0 0 0 999 V2000
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.8660 0.5000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
-0.8660 0.5000 0.0000 S 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 -1.0000 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
1 4 2 0 0 0 0
3 1 1 0 0 0 0
2 1 1 0 0 0 0
M CHG 3 2 -1 3 1 4 1
M END`;
const MolStringWithMultipleChargeLines = `
Ketcher 1 72215442D 1 1.00000 0.00000 0
4 3 0 0 0 0 999 V2000
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.8660 0.5000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
-0.8660 0.5000 0.0000 S 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 -1.0000 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
1 4 2 0 0 0 0
3 1 1 0 0 0 0
2 1 1 0 0 0 0
M CHG 1 2 -1
M CHG 2 3 1 4 1
M END`;
describe('mol reader', () => {
it('basic', async () => {
const parsed = await parseMol(MolString).run();
@@ -70,4 +119,63 @@ describe('mol reader', () => {
expect(bonds.atomIdxB.value(20)).toBe(20);
expect(bonds.order.value(20)).toBe(1);
});
it('property block charges', async () => {
const parsed = await parseMol(MolStringWithPropertyBlockCharge).run();
if (parsed.isError) {
throw new Error(parsed.message);
}
const { formalCharges } = parsed.result;
expect(formalCharges.atomIdx.rowCount).toBe(3);
expect(formalCharges.charge.rowCount).toBe(3);
expect(formalCharges.atomIdx.value(0)).toBe(2);
expect(formalCharges.atomIdx.value(1)).toBe(3);
expect(formalCharges.charge.value(0)).toBe(-1);
expect(formalCharges.charge.value(1)).toBe(1);
});
it('multiple charge lines', async () => {
const parsed = await parseMol(MolStringWithMultipleChargeLines).run();
if (parsed.isError) {
throw new Error(parsed.message);
}
const { formalCharges } = parsed.result;
expect(formalCharges.atomIdx.rowCount).toBe(3);
expect(formalCharges.charge.rowCount).toBe(3);
expect(formalCharges.atomIdx.value(0)).toBe(2);
expect(formalCharges.atomIdx.value(1)).toBe(3);
expect(formalCharges.charge.value(0)).toBe(-1);
expect(formalCharges.charge.value(1)).toBe(1);
});
it('atom block charge mapping', async () => {
expect(formalChargeMapper(7)).toBe(-3);
expect(formalChargeMapper(6)).toBe(-2);
expect(formalChargeMapper(5)).toBe(-1);
expect(formalChargeMapper(0)).toBe(0);
expect(formalChargeMapper(3)).toBe(1);
expect(formalChargeMapper(2)).toBe(2);
expect(formalChargeMapper(1)).toBe(3);
expect(formalChargeMapper(4)).toBe(0);
});
it('atom block charges', async () => {
const parsed = await parseMol(MolStringWithAtomBlockCharge).run();
if (parsed.isError) {
throw new Error(parsed.message);
}
const { atoms, formalCharges } = parsed.result;
/* No property block charges */
expect(formalCharges.atomIdx.rowCount).toBe(0);
expect(formalCharges.charge.rowCount).toBe(0);
expect(atoms.formal_charge.value(0)).toBe(1);
expect(atoms.formal_charge.value(1)).toBe(0);
expect(atoms.formal_charge.value(2)).toBe(0);
expect(atoms.formal_charge.value(3)).toBe(0);
});
});

View File

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

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.352, IHM 1.17, CARB draft.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.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.352, IHM 1.17, CARB draft.
* 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.353, 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<'CAS' | 'CSD' | 'EMDB' | 'ICSD' | 'MDF' | 'NDB' | 'NBS' | 'PDB' | 'PDF' | 'RCSB' | '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

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

View File

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

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

View File

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

View File

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

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

View File

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

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

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -7,21 +7,21 @@
import { Column, Table } from '../../../mol-data/db';
import { Interval, Segmentation } from '../../../mol-data/int';
import { UUID } from '../../../mol-util/uuid';
import { ElementIndex, ChainIndex } from '../../../mol-model/structure';
import { toDatabase } from '../../../mol-io/reader/cif/schema';
import { SymmetryOperator } from '../../../mol-math/geometry';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { ChainIndex, ElementIndex } from '../../../mol-model/structure';
import { AtomSiteOperatorMappingSchema } from '../../../mol-model/structure/export/categories/atom_site_operator_mapping';
import { Model } from '../../../mol-model/structure/model/model';
import { AtomicConformation, AtomicData, AtomicHierarchy, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../../../mol-model/structure/model/properties/atomic';
import { getAtomicIndex } from '../../../mol-model/structure/model/properties/utils/atomic-index';
import { ElementSymbol } from '../../../mol-model/structure/model/types';
import { Entities } from '../../../mol-model/structure/model/properties/common';
import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived';
import { AtomSite } from './schema';
import { getAtomicIndex } from '../../../mol-model/structure/model/properties/utils/atomic-index';
import { ElementSymbol } from '../../../mol-model/structure/model/types';
import { UUID } from '../../../mol-util/uuid';
import { ModelFormat } from '../../format';
import { SymmetryOperator } from '../../../mol-math/geometry';
import { MmcifFormat } from '../mmcif';
import { AtomSiteOperatorMappingSchema } from '../../../mol-model/structure/export/categories/atom_site_operator_mapping';
import { toDatabase } from '../../../mol-io/reader/cif/schema';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { AtomSite } from './schema';
function findHierarchyOffsets(atom_site: AtomSite) {
if (atom_site._rowCount === 0) return { residues: [], chains: [] };
@@ -45,15 +45,6 @@ function findHierarchyOffsets(atom_site: AtomSite) {
return { residues, chains };
}
function substUndefinedColumn<T extends Table<any>>(table: T, a: keyof T, b: keyof T) {
if (!(table as any)[a].isDefined) {
(table as any)[a] = (table as any)[b];
}
if (!(table as any)[b].isDefined) {
(table as any)[b] = (table as any)[a];
}
}
function createHierarchyData(atom_site: AtomSite, sourceIndex: Column<number>, offsets: { residues: ArrayLike<number>, chains: ArrayLike<number> }): AtomicData {
const atoms = Table.ofColumns(AtomsSchema, {
type_symbol: Column.ofArray({ array: Column.mapToArray(atom_site.type_symbol, ElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }),
@@ -87,12 +78,6 @@ function createHierarchyData(atom_site: AtomSite, sourceIndex: Column<number>, o
Table.columnToArray(residues, 'label_seq_id', Int32Array);
Table.columnToArray(residues, 'auth_seq_id', Int32Array);
// Fix possibly missing auth_/label_ columns
substUndefinedColumn(atoms, 'label_atom_id', 'auth_atom_id');
substUndefinedColumn(atoms, 'label_comp_id', 'auth_comp_id');
substUndefinedColumn(residues, 'label_seq_id', 'auth_seq_id');
substUndefinedColumn(chains, 'label_asym_id', 'auth_asym_id');
return { atoms, residues, chains, atomSourceIndex: sourceIndex };
}

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -18,13 +18,13 @@ import { sortAtomSite } from './sort';
import { ModelFormat } from '../../format';
import { getAtomicRanges } from '../../../mol-model/structure/model/properties/utils/atomic-ranges';
import { AtomSite, BasicData } from './schema';
import { getProperties } from './properties';
import { getEntities } from './entities';
import { getChemicalComponentMap, getMissingResidues, getSaccharideComponentMap, getStructAsymMap } from './properties';
import { getEntitiesWithPRD, getEntityData } from './entities';
import { getModelGroupName } from './util';
import { ArrayTrajectory } from '../../../mol-model/structure/trajectory';
export async function createModels(data: BasicData, format: ModelFormat, ctx: RuntimeContext) {
const properties = getProperties(data);
const properties = getCommonProperties(data, format);
const models = data.ihm_model_list._rowCount > 0
? await readIntegrative(ctx, data, properties, format)
: await readStandard(ctx, data, properties, format);
@@ -36,9 +36,18 @@ export async function createModels(data: BasicData, format: ModelFormat, ctx: Ru
return new ArrayTrajectory(models);
}
/** Standard atomic model */
function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, properties: Model['properties'], format: ModelFormat, previous?: Model): Model {
type CommonProperties = Omit<Model['properties'], 'structAsymMap'>
function getCommonProperties(data: BasicData, format: ModelFormat): CommonProperties {
return {
missingResidues: getMissingResidues(data),
chemicalComponentMap: getChemicalComponentMap(data),
saccharideComponentMap: getSaccharideComponentMap(data)
};
}
/** Standard atomic model */
function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, properties: CommonProperties, format: ModelFormat, previous?: Model): Model {
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, properties.chemicalComponentMap, format, previous);
const modelNum = atom_site.pdbx_PDB_model_num.value(0);
if (previous && atomic.sameAsPrevious) {
@@ -54,6 +63,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
const coarse = EmptyCoarse;
const sequence = getSequence(data, entities, atomic.hierarchy, coarse.hierarchy);
const atomicRanges = getAtomicRanges(atomic.hierarchy, entities, atomic.conformation, sequence);
const structAsymMap = getStructAsymMap(atomic.hierarchy);
const entry = data.entry.id.valueKind(0) === Column.ValueKind.Present
? data.entry.id.value(0) : format.name;
@@ -70,7 +80,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
sourceData: format,
modelNum,
parent: undefined,
entities,
entities: getEntitiesWithPRD(data, entities, structAsymMap),
sequence,
atomicHierarchy: atomic.hierarchy,
atomicConformation: atomic.conformation,
@@ -78,7 +88,10 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
coarseHierarchy: coarse.hierarchy,
coarseConformation: coarse.conformation,
properties,
properties: {
...properties,
structAsymMap
},
customProperties: new CustomProperties(),
_staticPropertyData: Object.create(null),
_dynamicPropertyData: Object.create(null)
@@ -86,9 +99,9 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
}
/** Integrative model with atomic/coarse parts */
function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Model['properties'], format: ModelFormat): Model {
function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: CommonProperties, format: ModelFormat): Model {
const atomic = getAtomicHierarchyAndConformation(ihm.atom_site, ihm.atom_site_sourceIndex, ihm.entities, properties.chemicalComponentMap, format);
const coarse = getCoarse(ihm, properties);
const coarse = getCoarse(ihm, properties.chemicalComponentMap);
const sequence = getSequence(data, ihm.entities, atomic.hierarchy, coarse.hierarchy);
const atomicRanges = getAtomicRanges(atomic.hierarchy, ihm.entities, atomic.conformation, sequence);
@@ -101,6 +114,8 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
if (ihm.model_name) label.push(ihm.model_name);
if (ihm.model_group_name) label.push(ihm.model_group_name);
const structAsymMap = getStructAsymMap(atomic.hierarchy, data);
return {
id: UUID.create22(),
entryId: entry,
@@ -109,7 +124,7 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
sourceData: format,
modelNum: ihm.model_id,
parent: undefined,
entities: ihm.entities,
entities: getEntitiesWithPRD(data, ihm.entities, structAsymMap),
sequence,
atomicHierarchy: atomic.hierarchy,
atomicConformation: atomic.conformation,
@@ -117,7 +132,10 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
coarseHierarchy: coarse.hierarchy,
coarseConformation: coarse.conformation,
properties,
properties: {
...properties,
structAsymMap
},
customProperties: new CustomProperties(),
_staticPropertyData: Object.create(null),
_dynamicPropertyData: Object.create(null)
@@ -132,12 +150,12 @@ function findModelEnd(num: Column<number>, startIndex: number) {
return endIndex;
}
async function readStandard(ctx: RuntimeContext, data: BasicData, properties: Model['properties'], format: ModelFormat) {
async function readStandard(ctx: RuntimeContext, data: BasicData, properties: CommonProperties, format: ModelFormat) {
const models: Model[] = [];
if (data.atom_site) {
const atomCount = data.atom_site.id.rowCount;
const entities = getEntities(data, properties);
const entities = getEntityData(data);
let modelStart = 0;
while (modelStart < atomCount) {
@@ -168,10 +186,8 @@ function splitTable<T extends Table<any>>(table: T, col: Column<number>) {
return ret;
}
async function readIntegrative(ctx: RuntimeContext, data: BasicData, properties: Model['properties'], format: ModelFormat) {
const entities = getEntities(data, properties);
async function readIntegrative(ctx: RuntimeContext, data: BasicData, properties: CommonProperties, format: ModelFormat) {
const entities = getEntityData(data);
// when `atom_site.ihm_model_id` is undefined fall back to `atom_site.pdbx_PDB_model_num`
const atom_sites_modelColumn = data.atom_site.ihm_model_id.isDefined
? data.atom_site.ihm_model_id : data.atom_site.pdbx_PDB_model_num;
@@ -206,7 +222,7 @@ async function readIntegrative(ctx: RuntimeContext, data: BasicData, properties:
model_id: id,
model_name: model_name.value(i),
model_group_name: getModelGroupName(id, data),
entities: entities,
entities,
atom_site,
atom_site_sourceIndex,
ihm_sphere_obj_site: sphere_sites.has(id) ? sphere_sites.get(id)!.table : Table.window(data.ihm_sphere_obj_site, data.ihm_sphere_obj_site._schema, 0, 0),

View File

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

View File

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

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

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

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,36 +11,49 @@ 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('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;
if (!data) return '';
const eI = loc.unit.elements[loc.element];
const rI = model.atomicHierarchy.residueAtomSegments.offsets[eI];
const rI = model.atomicHierarchy.residueAtomSegments.index[eI];
return data.accession[rI];
}
@@ -49,45 +62,46 @@ namespace BestDatabaseSequenceMapping {
const data = Provider.get(model).value;
if (!data) return;
const eI = loc.unit.elements[loc.element];
const rI = model.atomicHierarchy.residueAtomSegments.offsets[eI];
const rI = model.atomicHierarchy.residueAtomSegments.index[eI];
const dbName = data.dbName[rI];
if (!dbName) return;
return `${dbName} ${data.accession[rI]} ${data.num[rI]} ${data.residue[rI]}`;
}
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('db_name');
const db_acc = atom_site.getField('db_acc');
const db_num = atom_site.getField('db_num');
const db_res = atom_site.getField('db_res');
const {
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++) {
const row = atomSourceIndex.value(residueOffsets[i]);
if (db_name.valueKind(row) !== Column.ValueKind.Present) {
dbName[row] = '';
accession[row] = '';
num[row] = 0;
residue[row] = '';
dbName[i] = '';
accession[i] = '';
num[i] = '';
residue[i] = '';
continue;
}
dbName[row] = db_name.str(row);
accession[row] = db_acc.str(row);
num[row] = db_num.int(row);
residue[row] = db_res.str(row);
dbName[i] = db_name.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) => {

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