mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 21:34:23 +08:00
Compare commits
736 Commits
v0.5.0-dev
...
v0.7.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16d5c07224 | ||
|
|
2392bfb579 | ||
|
|
b4036f576c | ||
|
|
690d6812dc | ||
|
|
a44aa02f13 | ||
|
|
65ddd6d68a | ||
|
|
754025b3b1 | ||
|
|
f0649c5aa3 | ||
|
|
6df045211c | ||
|
|
8a00540de0 | ||
|
|
0d78905686 | ||
|
|
6edab203c2 | ||
|
|
0abfdb5ee3 | ||
|
|
88369158c9 | ||
|
|
8926575283 | ||
|
|
15b0288ce4 | ||
|
|
28853ec19d | ||
|
|
e3480a076a | ||
|
|
abf6452124 | ||
|
|
2cdd811dd3 | ||
|
|
be6fea39bf | ||
|
|
ed1bc2cd07 | ||
|
|
28d3d5861a | ||
|
|
95d3ef491f | ||
|
|
5c37ddfc6d | ||
|
|
4d9e2d9c91 | ||
|
|
0b1c18913d | ||
|
|
c8c2355d3e | ||
|
|
3d1366024d | ||
|
|
f6964d2a66 | ||
|
|
de60f70af5 | ||
|
|
8f2e619162 | ||
|
|
fbcef01c55 | ||
|
|
641e0639d4 | ||
|
|
5048573976 | ||
|
|
78e4d8536d | ||
|
|
a01d088205 | ||
|
|
4b1d1a045d | ||
|
|
e2857d00b4 | ||
|
|
a55a71d31a | ||
|
|
acf793f112 | ||
|
|
2d58ea28ea | ||
|
|
b21de78eb5 | ||
|
|
376d4b4ee1 | ||
|
|
e39304c7cf | ||
|
|
7cba9cda0c | ||
|
|
04c690e8f9 | ||
|
|
170d0fbc9d | ||
|
|
40d632a7b1 | ||
|
|
2e754d23f4 | ||
|
|
5639a4b37c | ||
|
|
8c959f8a60 | ||
|
|
d42c9a6e15 | ||
|
|
da4dabc3f5 | ||
|
|
92217905f8 | ||
|
|
598441a727 | ||
|
|
f707cb19a4 | ||
|
|
63fc408be6 | ||
|
|
69dedd8c22 | ||
|
|
5480805754 | ||
|
|
655ae65b8d | ||
|
|
1a23cb672e | ||
|
|
19e18b4089 | ||
|
|
3d909d5012 | ||
|
|
ad521948b6 | ||
|
|
2f3b6a28c1 | ||
|
|
c779da674c | ||
|
|
d9b140f9f2 | ||
|
|
052648023e | ||
|
|
f5d12d440e | ||
|
|
99d7a90863 | ||
|
|
901d5c86e6 | ||
|
|
df9efd05e6 | ||
|
|
26b8adaec4 | ||
|
|
fc6f5a0336 | ||
|
|
f823a887b7 | ||
|
|
b346d4d85d | ||
|
|
70bd035898 | ||
|
|
7e5cdd8e06 | ||
|
|
a21dac60e0 | ||
|
|
9bd2e0d96e | ||
|
|
dd15a000e1 | ||
|
|
ebcfa44f22 | ||
|
|
43845adb71 | ||
|
|
9e3fff65a7 | ||
|
|
05c35a3a3a | ||
|
|
6f46965344 | ||
|
|
27ee576340 | ||
|
|
58492328df | ||
|
|
fd102bede1 | ||
|
|
3d8c47eefa | ||
|
|
c4e43228a2 | ||
|
|
6526090b8b | ||
|
|
094a018b5b | ||
|
|
a0a9c994b2 | ||
|
|
524ed90e3f | ||
|
|
7abba750b4 | ||
|
|
ecff1641d2 | ||
|
|
47ecf04386 | ||
|
|
c4697a9f45 | ||
|
|
53d5414492 | ||
|
|
311f5c09f5 | ||
|
|
be4439451f | ||
|
|
09c83e83ba | ||
|
|
bea4c64d85 | ||
|
|
0387dcd444 | ||
|
|
a89f21dafd | ||
|
|
7921c05d55 | ||
|
|
dafa946937 | ||
|
|
95673b0131 | ||
|
|
0118136869 | ||
|
|
573c2a7ad6 | ||
|
|
41977ea758 | ||
|
|
75ccded612 | ||
|
|
2429111a59 | ||
|
|
c475cb292e | ||
|
|
04d34b369a | ||
|
|
cfe4c6c559 | ||
|
|
4d13f99d22 | ||
|
|
62ebd4d8a9 | ||
|
|
026c25621f | ||
|
|
c096ae299d | ||
|
|
ae306d1761 | ||
|
|
4caae32933 | ||
|
|
6bbf20980e | ||
|
|
0fd00ecab8 | ||
|
|
70de73a95b | ||
|
|
578ad69c36 | ||
|
|
af263b8c88 | ||
|
|
bef6775de5 | ||
|
|
3c27450e82 | ||
|
|
9400f27f82 | ||
|
|
c176313f7b | ||
|
|
77461d5a23 | ||
|
|
a5c039bf11 | ||
|
|
e3c08f23bf | ||
|
|
67a26e93e4 | ||
|
|
a1d261b7c8 | ||
|
|
3826394940 | ||
|
|
791a54aeec | ||
|
|
48f1bb7755 | ||
|
|
53e028325f | ||
|
|
02eda0a1e2 | ||
|
|
92600160ff | ||
|
|
6ec67a7faf | ||
|
|
a4386744a2 | ||
|
|
09210279e1 | ||
|
|
e78cf18a38 | ||
|
|
9d1d7bbf72 | ||
|
|
60d5e85b4c | ||
|
|
2c263e216f | ||
|
|
e1f671a5b3 | ||
|
|
7ffaea48cb | ||
|
|
0e4527613c | ||
|
|
f363621de3 | ||
|
|
4901ec3b5d | ||
|
|
c592b8a53f | ||
|
|
325e4e44dc | ||
|
|
a81f7f6cfd | ||
|
|
31b83e9675 | ||
|
|
dc89f33cd0 | ||
|
|
f5f9237a78 | ||
|
|
4429ff0ac1 | ||
|
|
a0d38e6b10 | ||
|
|
c060664f84 | ||
|
|
10f7f15f70 | ||
|
|
d40ff29337 | ||
|
|
7ca666ae98 | ||
|
|
dcaea9d40b | ||
|
|
b878273d81 | ||
|
|
c15d29f2da | ||
|
|
3e3a13070a | ||
|
|
11bcd84e66 | ||
|
|
44b63e5953 | ||
|
|
fb0634a0f4 | ||
|
|
0b651db35b | ||
|
|
b24062b575 | ||
|
|
35c5c4cfc0 | ||
|
|
4b8bf57d1a | ||
|
|
a81f0d911a | ||
|
|
28e2227989 | ||
|
|
4f97b6836a | ||
|
|
5ab0dfaba8 | ||
|
|
66751fa006 | ||
|
|
f1d78e4805 | ||
|
|
715c78a04f | ||
|
|
0cd8b4ee8c | ||
|
|
c6efa475a5 | ||
|
|
e846e1fdd7 | ||
|
|
4221067f8f | ||
|
|
36951d6f19 | ||
|
|
ac45500e35 | ||
|
|
0f22eab8b9 | ||
|
|
5a8fd6d518 | ||
|
|
60ee04b9b9 | ||
|
|
d99b5bd505 | ||
|
|
6312c1f99b | ||
|
|
ef870a510f | ||
|
|
394ff05626 | ||
|
|
e90ccfdd20 | ||
|
|
92a86e324b | ||
|
|
e5d6816392 | ||
|
|
a1b2de16a3 | ||
|
|
f605021cfb | ||
|
|
619a2ccb3a | ||
|
|
0cc077c346 | ||
|
|
e34aad991b | ||
|
|
2a925cdd6a | ||
|
|
36c5e444ca | ||
|
|
c7f6041aa4 | ||
|
|
68401b3556 | ||
|
|
3843064eac | ||
|
|
d0de9073e7 | ||
|
|
35bd0ec5d8 | ||
|
|
373a69abbd | ||
|
|
9fa56bf76c | ||
|
|
3ad42fc7e0 | ||
|
|
7dd6efafb2 | ||
|
|
61e26df637 | ||
|
|
a47283bf5b | ||
|
|
d7e5385b04 | ||
|
|
8eaac83238 | ||
|
|
9fbe224f00 | ||
|
|
26dc7b2f65 | ||
|
|
bb6b6f9690 | ||
|
|
0fabfc9873 | ||
|
|
9c3e057410 | ||
|
|
f4cbb1ec73 | ||
|
|
1175f589eb | ||
|
|
8c7563d7f2 | ||
|
|
33533cbb09 | ||
|
|
0ec37a7b95 | ||
|
|
f3db0c171b | ||
|
|
cd10d23371 | ||
|
|
d60c88c506 | ||
|
|
7266aab4be | ||
|
|
54c2ef7a0b | ||
|
|
b882a72d77 | ||
|
|
1eab4dac96 | ||
|
|
b2376aea70 | ||
|
|
1641cfbd03 | ||
|
|
f27540ca02 | ||
|
|
b4320de291 | ||
|
|
1b26aa4b36 | ||
|
|
1ca91f35e2 | ||
|
|
1c5ac56ecf | ||
|
|
ee85e13206 | ||
|
|
440474d53b | ||
|
|
d9c251b2ce | ||
|
|
975b4aee2a | ||
|
|
4a7413a8d9 | ||
|
|
593b34bfe3 | ||
|
|
691b1721b2 | ||
|
|
b8133b4300 | ||
|
|
0c459b98db | ||
|
|
060725642f | ||
|
|
eaf28bf016 | ||
|
|
ed6084c40b | ||
|
|
e56063b065 | ||
|
|
f8ddfb1638 | ||
|
|
900f2e1f76 | ||
|
|
00c9e05a65 | ||
|
|
ecaab51315 | ||
|
|
2a4d45714c | ||
|
|
6e13ef5cbc | ||
|
|
f48e2ab238 | ||
|
|
0e34d976c0 | ||
|
|
2d6cd4c6da | ||
|
|
4a4b3ef5b4 | ||
|
|
67e6167b55 | ||
|
|
79b33bfbd8 | ||
|
|
f5fc96ee3b | ||
|
|
691571ec39 | ||
|
|
f2b20a646e | ||
|
|
bef9ff86d2 | ||
|
|
ff3da5f2db | ||
|
|
129727d5d1 | ||
|
|
6e26b4d5f9 | ||
|
|
371bb11ce8 | ||
|
|
556196ae3f | ||
|
|
f5667411d7 | ||
|
|
774f419a53 | ||
|
|
9fa5d40306 | ||
|
|
ba89d5ec1e | ||
|
|
b7fa577d9b | ||
|
|
949425d14d | ||
|
|
2cb0d63750 | ||
|
|
038aa47578 | ||
|
|
c833598c26 | ||
|
|
489c52361a | ||
|
|
9edf41a2f2 | ||
|
|
5bcca99f60 | ||
|
|
1b0a310fc7 | ||
|
|
e3e7fa3040 | ||
|
|
5297dd6f11 | ||
|
|
c557e93255 | ||
|
|
ad5f0c987a | ||
|
|
30aa6d035d | ||
|
|
b198289fc4 | ||
|
|
51e45085f1 | ||
|
|
97ebb2dccc | ||
|
|
29d28bd3f4 | ||
|
|
09c5c040f2 | ||
|
|
6e60a2f9dc | ||
|
|
5d36108113 | ||
|
|
f4a423ebe5 | ||
|
|
9942fbe549 | ||
|
|
dbde7521e4 | ||
|
|
6eee3e8368 | ||
|
|
cc0ccd7830 | ||
|
|
7ad25249a9 | ||
|
|
687c7d54ff | ||
|
|
d6fff1ffdf | ||
|
|
20a8b8892e | ||
|
|
5c09ecc98d | ||
|
|
19539a2c9d | ||
|
|
5c093c7f22 | ||
|
|
c43ed90607 | ||
|
|
a85242d9c5 | ||
|
|
722d2a9c6c | ||
|
|
f7d65ff52c | ||
|
|
7bee2be12d | ||
|
|
326649d4f5 | ||
|
|
6aba5df55d | ||
|
|
57bbcf9425 | ||
|
|
08ba34d607 | ||
|
|
4c122227a7 | ||
|
|
e14afb4dad | ||
|
|
472def49f6 | ||
|
|
90549893e3 | ||
|
|
73a8e45202 | ||
|
|
35df55cb4f | ||
|
|
c2028d20a8 | ||
|
|
eaffdc6a98 | ||
|
|
0b1e6100a9 | ||
|
|
2168905c11 | ||
|
|
f955e6a299 | ||
|
|
98f3981e12 | ||
|
|
82f94d20ea | ||
|
|
fbc6d47117 | ||
|
|
6a2e4cf813 | ||
|
|
4aabef7683 | ||
|
|
74ae91ee1b | ||
|
|
8b62766474 | ||
|
|
2995504916 | ||
|
|
63f6848d26 | ||
|
|
988e429693 | ||
|
|
ba1c6ef046 | ||
|
|
7ceff92a4e | ||
|
|
1f968b2836 | ||
|
|
d97d7e3b14 | ||
|
|
5c4c4811e4 | ||
|
|
f2a6e63a20 | ||
|
|
1aace4a26f | ||
|
|
b1c140d23e | ||
|
|
ee16212c31 | ||
|
|
f6d232b1c5 | ||
|
|
c8a002933e | ||
|
|
4757ca9913 | ||
|
|
5264c75e37 | ||
|
|
032bf44863 | ||
|
|
2f4f5e43f3 | ||
|
|
7b1edcadf6 | ||
|
|
f0f74d182d | ||
|
|
59142adbbc | ||
|
|
2fda8c5db1 | ||
|
|
7b5efa3e42 | ||
|
|
d784d202bd | ||
|
|
8833474a43 | ||
|
|
57cbb2f8b6 | ||
|
|
b6112a914f | ||
|
|
2008f8538c | ||
|
|
09fba43a1c | ||
|
|
b76c3613f9 | ||
|
|
cf4ddcb587 | ||
|
|
696106f48b | ||
|
|
fb286cd9cf | ||
|
|
5121bd700e | ||
|
|
6173520ad0 | ||
|
|
a7189232dd | ||
|
|
4a96b45b04 | ||
|
|
20ac549dd6 | ||
|
|
38be00c0b7 | ||
|
|
0a9bdc8cf6 | ||
|
|
0b4318280a | ||
|
|
b1da60e1c0 | ||
|
|
2cc5987f9e | ||
|
|
745415a1d8 | ||
|
|
c80658f368 | ||
|
|
033675a417 | ||
|
|
3dd57e9dc8 | ||
|
|
1e3daa6c98 | ||
|
|
8855f51cfc | ||
|
|
50d8debb2b | ||
|
|
e682eb78b0 | ||
|
|
bd2bbf3e2d | ||
|
|
f38f040aea | ||
|
|
f912b2d802 | ||
|
|
0ad03e6a0b | ||
|
|
160d8c529e | ||
|
|
28edfd810c | ||
|
|
7b931cfb66 | ||
|
|
5575c61577 | ||
|
|
8f93dce105 | ||
|
|
0eb3d7226a | ||
|
|
a9533b666c | ||
|
|
6d67b4db56 | ||
|
|
1b5eff6454 | ||
|
|
83fb28cc9d | ||
|
|
c39ffc0b0e | ||
|
|
23892cfbdd | ||
|
|
3bdabc444d | ||
|
|
c233be4467 | ||
|
|
8468f33803 | ||
|
|
0e77369fdb | ||
|
|
9fcc8e7977 | ||
|
|
538371ced8 | ||
|
|
380887bd22 | ||
|
|
17b4b1cb86 | ||
|
|
3e7c358c07 | ||
|
|
05b592a173 | ||
|
|
e1d0515fae | ||
|
|
d4d3b9645e | ||
|
|
d12d99dcfa | ||
|
|
174324d21c | ||
|
|
26156a5982 | ||
|
|
af5ddf6950 | ||
|
|
2ef35e5fb9 | ||
|
|
513bfeaae7 | ||
|
|
7311e6f484 | ||
|
|
90ecedcae8 | ||
|
|
352a20ac48 | ||
|
|
b1d8c5f6ea | ||
|
|
6497be9e52 | ||
|
|
40a8cee8e5 | ||
|
|
112cfcac90 | ||
|
|
8016fcbd54 | ||
|
|
6de97f1d03 | ||
|
|
204075bbe0 | ||
|
|
eb448bce37 | ||
|
|
3d09b5cb67 | ||
|
|
35d06040f7 | ||
|
|
0868e81944 | ||
|
|
7efbeb7d0f | ||
|
|
815f61b550 | ||
|
|
c3a90ab499 | ||
|
|
321126afa2 | ||
|
|
9ad4a9c3c9 | ||
|
|
b12f7c7ce4 | ||
|
|
6d7d3c0794 | ||
|
|
92b988a8d5 | ||
|
|
18952ee2bd | ||
|
|
bff07888f9 | ||
|
|
8e6b0b220a | ||
|
|
74acb0d078 | ||
|
|
d4727eea02 | ||
|
|
76d14eba0c | ||
|
|
90d05d9260 | ||
|
|
23b24bbb6c | ||
|
|
91bc6f07c5 | ||
|
|
9b2181667d | ||
|
|
40347e3e46 | ||
|
|
ed277f6e16 | ||
|
|
8f2085d8b4 | ||
|
|
b5a48ad201 | ||
|
|
14b900791f | ||
|
|
de80799e68 | ||
|
|
53eca387fc | ||
|
|
fc3005f271 | ||
|
|
c95fdff00c | ||
|
|
3cd9042c72 | ||
|
|
4d3914426e | ||
|
|
af1fb7041e | ||
|
|
e2eb1bf223 | ||
|
|
5a64a6f1a3 | ||
|
|
88aa9303d7 | ||
|
|
5c77eec184 | ||
|
|
a931ed7c01 | ||
|
|
94cd2b618c | ||
|
|
9c97fc258d | ||
|
|
0ac1cfe555 | ||
|
|
3942f1bc33 | ||
|
|
a66e38a901 | ||
|
|
f65f4f4aeb | ||
|
|
f28b13bf87 | ||
|
|
8f211a0785 | ||
|
|
ba1dfb2851 | ||
|
|
964d56752e | ||
|
|
cb6a66eba5 | ||
|
|
94adf7259d | ||
|
|
3c831b549a | ||
|
|
7ccf36a0fa | ||
|
|
b0ee640c12 | ||
|
|
cd6d41a035 | ||
|
|
9c8f1f3e11 | ||
|
|
7c50a5a456 | ||
|
|
f3b6703fd4 | ||
|
|
60f2716b5a | ||
|
|
d7007ef99d | ||
|
|
4aca41cdbb | ||
|
|
7e31fac99a | ||
|
|
3bcf1cb6b5 | ||
|
|
a29cc74304 | ||
|
|
95c43d0864 | ||
|
|
369b958335 | ||
|
|
9a17da8f65 | ||
|
|
0c4ba20d6e | ||
|
|
22936ff6c9 | ||
|
|
3369ad0bdc | ||
|
|
20853ef60b | ||
|
|
da0c66bd8e | ||
|
|
44664917ff | ||
|
|
ce26e002c7 | ||
|
|
345b9f546c | ||
|
|
f4ed5e301d | ||
|
|
4818f851b3 | ||
|
|
6f2c47c0ca | ||
|
|
5e6bc0f0df | ||
|
|
083daa0b76 | ||
|
|
139a10ba8c | ||
|
|
0b512487f5 | ||
|
|
a85adfdf1f | ||
|
|
0171b785af | ||
|
|
4c1c484af9 | ||
|
|
db7585b6b6 | ||
|
|
d263f6d929 | ||
|
|
034c28e487 | ||
|
|
9a88f57ce6 | ||
|
|
28415646a2 | ||
|
|
b03295ef81 | ||
|
|
7d3e849ff3 | ||
|
|
926f20a6a4 | ||
|
|
c12d577ce6 | ||
|
|
aa8d3a9841 | ||
|
|
f11e03eb85 | ||
|
|
ab996b5189 | ||
|
|
4c91e730a2 | ||
|
|
5ca5f44c61 | ||
|
|
f8a89b1c0d | ||
|
|
99dacef747 | ||
|
|
24fc37105e | ||
|
|
cbd493d834 | ||
|
|
53dbd1aedf | ||
|
|
6ab1af54aa | ||
|
|
615d9758bb | ||
|
|
341f9f8c91 | ||
|
|
ba7d4a5215 | ||
|
|
428187ad81 | ||
|
|
fe96172fcd | ||
|
|
8b13be698c | ||
|
|
5edc924e4f | ||
|
|
78c50a4339 | ||
|
|
607284585c | ||
|
|
f7af352f03 | ||
|
|
008ec2c88c | ||
|
|
c91074ad91 | ||
|
|
51fbb619e5 | ||
|
|
062c4b335b | ||
|
|
d4107e273d | ||
|
|
4dad67fc2e | ||
|
|
64b0713742 | ||
|
|
22c45e788e | ||
|
|
75de544669 | ||
|
|
768ec10809 | ||
|
|
4d5d9be399 | ||
|
|
7f4afc111e | ||
|
|
0365f883dd | ||
|
|
6f98549f31 | ||
|
|
77920818d9 | ||
|
|
0682d54ee2 | ||
|
|
fbcc3b2fa2 | ||
|
|
c3f47b2ecb | ||
|
|
8b9f59ac5a | ||
|
|
362dcabe5c | ||
|
|
6e205661bd | ||
|
|
c1a8627702 | ||
|
|
2c5253943c | ||
|
|
03668216fa | ||
|
|
37ef234803 | ||
|
|
d25aa97fca | ||
|
|
17dc973305 | ||
|
|
eed4e4a134 | ||
|
|
5fd28c6cad | ||
|
|
0f69ea197d | ||
|
|
e1a214e1a8 | ||
|
|
e7e14750cb | ||
|
|
cb83013967 | ||
|
|
0a84e1bb7b | ||
|
|
aee7f4988a | ||
|
|
e762c402fa | ||
|
|
4375cae70d | ||
|
|
90268a9041 | ||
|
|
a3ebc4df45 | ||
|
|
b2743c76e0 | ||
|
|
969a70d571 | ||
|
|
e6a538dbd7 | ||
|
|
9cffce55c9 | ||
|
|
7bc91d7e99 | ||
|
|
943f5feb59 | ||
|
|
dc72dbbc97 | ||
|
|
1513a1e2d2 | ||
|
|
82989cae57 | ||
|
|
74f8a5053e | ||
|
|
79f7ea209a | ||
|
|
ba65e35c51 | ||
|
|
5bc4e3ce3b | ||
|
|
1c7ac60c11 | ||
|
|
00cd54b69c | ||
|
|
90e3a3f0c7 | ||
|
|
5eef3fc42a | ||
|
|
83a8474731 | ||
|
|
a5e13c5152 | ||
|
|
b19f53d380 | ||
|
|
abadef1d2e | ||
|
|
a3d976c5b8 | ||
|
|
414b20f06d | ||
|
|
d03a442b6c | ||
|
|
e5acce03e5 | ||
|
|
fe714d4515 | ||
|
|
7959344bca | ||
|
|
b9582f171d | ||
|
|
410123d933 | ||
|
|
ae484443d8 | ||
|
|
9d97e56f03 | ||
|
|
01269ec1ef | ||
|
|
a204264bcc | ||
|
|
eb0a048926 | ||
|
|
ba1509b37a | ||
|
|
ad379e7a32 | ||
|
|
9d45beea3b | ||
|
|
b2d134aeb4 | ||
|
|
d500b8ea19 | ||
|
|
8b3df4b373 | ||
|
|
f01fc7c8ba | ||
|
|
0e6da0bffa | ||
|
|
eab8b1c2bf | ||
|
|
8b42a0fede | ||
|
|
4bbe078230 | ||
|
|
aabb931d27 | ||
|
|
b1c1b21975 | ||
|
|
7b5dc3ef96 | ||
|
|
1f831f90e0 | ||
|
|
ef2c1b51e9 | ||
|
|
ace73c041b | ||
|
|
3c70fe5303 | ||
|
|
137e23c025 | ||
|
|
969a19d515 | ||
|
|
7536abe96c | ||
|
|
8e20e163f9 | ||
|
|
adbe8e1f67 | ||
|
|
5db134f34f | ||
|
|
80cf7c1dd2 | ||
|
|
9e5cc184ed | ||
|
|
a5185b456c | ||
|
|
0615198ad2 | ||
|
|
6cd422f5b3 | ||
|
|
4416178d6f | ||
|
|
b8cef99dc1 | ||
|
|
4dcb68af33 | ||
|
|
b4c70ab14a | ||
|
|
8436e17af9 | ||
|
|
bda04cbdc7 | ||
|
|
7287947a55 | ||
|
|
fb5eb1e19c | ||
|
|
d735dcdc3e | ||
|
|
83d8a61400 | ||
|
|
b72f57c040 | ||
|
|
8a7ef1c704 | ||
|
|
8731bc13d1 | ||
|
|
d103cda0c8 | ||
|
|
ca4e864e46 | ||
|
|
0e25950d51 | ||
|
|
2c29a90b5e | ||
|
|
2900836208 | ||
|
|
928a1df525 | ||
|
|
16e0dff551 | ||
|
|
7e443d5c9b | ||
|
|
172ae17966 | ||
|
|
8cbf7b5d0a | ||
|
|
2c40b85880 | ||
|
|
ad388f23ae | ||
|
|
27b559a2d8 | ||
|
|
c6d19e14c5 | ||
|
|
b807dca2d8 | ||
|
|
2cae6e3f59 | ||
|
|
69c73f3dcd | ||
|
|
4456ab2cd5 | ||
|
|
bdda18de23 | ||
|
|
1b2c2f3d41 | ||
|
|
fd19d29ef6 | ||
|
|
1f0c0fd756 | ||
|
|
83698cc52d | ||
|
|
70b94deb20 | ||
|
|
2da3df6e4d | ||
|
|
509e633a69 | ||
|
|
17fac2b82a | ||
|
|
c543c4e10a | ||
|
|
10073800dc | ||
|
|
04c38250b4 | ||
|
|
5ccb329af1 | ||
|
|
3cecb53bc5 | ||
|
|
0daf431d68 | ||
|
|
3e0c4242ad | ||
|
|
17b775c377 | ||
|
|
4525b98288 | ||
|
|
755699d479 | ||
|
|
a84309b800 | ||
|
|
16863535b8 | ||
|
|
dd9773d72e | ||
|
|
ec45f6c0ee | ||
|
|
59235630bb | ||
|
|
11ed0ca89b | ||
|
|
188ea6e8e2 | ||
|
|
293b464d9f | ||
|
|
0e91aa521e | ||
|
|
5272593cc3 | ||
|
|
c5f336b0e4 | ||
|
|
b1a6fa3ffc | ||
|
|
fe47134934 | ||
|
|
131cc606f0 | ||
|
|
3681f01fad | ||
|
|
e966c112ab | ||
|
|
d7b232b00b | ||
|
|
7986509ad3 | ||
|
|
73dcf970f3 | ||
|
|
9377aa2d05 | ||
|
|
686fa5a5ed | ||
|
|
c946ae6eab | ||
|
|
9b2f1d9415 | ||
|
|
f6c28aa8e2 | ||
|
|
2c327cfdf6 | ||
|
|
307f2efc97 | ||
|
|
64c51f0d94 | ||
|
|
7a0b4c4d23 | ||
|
|
f45edbc4f0 | ||
|
|
65d3355b18 |
3
.eslintignore
Normal file
3
.eslintignore
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules/*
|
||||
build/*
|
||||
lib/*
|
||||
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "tsconfig.json",
|
||||
"project": ["tsconfig.json", "tsconfig.servers.json"],
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
@@ -14,8 +14,9 @@
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-types": "warn",
|
||||
"@typescript-eslint/class-name-casing": "off",
|
||||
"indent": "off",
|
||||
"@typescript-eslint/indent": [
|
||||
"warn",
|
||||
"error",
|
||||
4
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
@@ -33,9 +34,9 @@
|
||||
],
|
||||
"@typescript-eslint/prefer-namespace-keyword": "warn",
|
||||
"@typescript-eslint/quotes": [
|
||||
"warn",
|
||||
"error",
|
||||
"single",
|
||||
{
|
||||
{
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
@@ -44,22 +45,31 @@
|
||||
"off",
|
||||
null
|
||||
],
|
||||
"@typescript-eslint/type-annotation-spacing": "warn",
|
||||
"@typescript-eslint/type-annotation-spacing": "error",
|
||||
"arrow-parens": [
|
||||
"off",
|
||||
"as-needed"
|
||||
],
|
||||
"brace-style": "off",
|
||||
"@typescript-eslint/brace-style": [
|
||||
"error",
|
||||
"1tbs", { "allowSingleLine": true }
|
||||
],
|
||||
"comma-spacing": "off",
|
||||
"@typescript-eslint/comma-spacing": "error",
|
||||
"space-infix-ops": "error",
|
||||
"comma-dangle": "off",
|
||||
"eqeqeq": [
|
||||
"warn",
|
||||
"error",
|
||||
"smart"
|
||||
],
|
||||
"import/order": "off",
|
||||
"no-eval": "warn",
|
||||
"no-new-wrappers": "warn",
|
||||
"no-trailing-spaces": "warn",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-unsafe-finally": "warn",
|
||||
"no-var": "warn",
|
||||
"spaced-comment": "warn"
|
||||
"no-var": "error",
|
||||
"spaced-comment": "error",
|
||||
"semi": "warn"
|
||||
}
|
||||
}
|
||||
18
.github/workflows/lint.yml
vendored
Normal file
18
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
name: eslint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: install node v12
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- name: yarn install
|
||||
run: yarn install
|
||||
- name: eslint
|
||||
uses: icrawl/action-eslint@v1
|
||||
1
.npmignore
Normal file
1
.npmignore
Normal file
@@ -0,0 +1 @@
|
||||
tsconfig.servers.buildinfo
|
||||
14
.vscode/tasks.json
vendored
14
.vscode/tasks.json
vendored
@@ -9,6 +9,20 @@
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "build-tsc",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
48
README.md
48
README.md
@@ -5,7 +5,7 @@
|
||||
|
||||
# Mol*
|
||||
|
||||
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that will serve as basis for the next-generation data delivery and analysis tools for macromolecular structure data. This is a collaboration between PDBe and RCSB PDB teams and the development will be open source and available to anyone who wants to use it for developing visualisation tools for macromolecular structure data available from [PDB](https://www.wwpdb.org/) and other institutions.
|
||||
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that will serve as a basis for the next-generation data delivery and analysis tools for macromolecular structure data. This is a collaboration between PDBe and RCSB PDB teams and the development will be open-source and available to anyone who wants to use it for developing visualization tools for macromolecular structure data available from [PDB](https://www.wwpdb.org/) and other institutions.
|
||||
|
||||
This particular project is the implementation of this technology (still under development).
|
||||
|
||||
@@ -16,25 +16,26 @@ This particular project is the implementation of this technology (still under de
|
||||
The core of Mol* currently consists of these modules (see under `src/`):
|
||||
|
||||
- `mol-task` Computation abstraction with progress tracking and cancellation support.
|
||||
- `mol-data` Collections (integer based sets, interface to columns/tables, etc.)
|
||||
- `mol-data` Collections (integer-based sets, interface to columns/tables, etc.)
|
||||
- `mol-math` Math related (loosely) algorithms and data structures.
|
||||
- `mol-io` Parsing library. Each format is parsed into an interface that corresponds to the data stored by it. Support for common coordinate, experimental/map, and annotation data formats.
|
||||
- `mol-model` Data structures and algorithms (such as querying) for representing molecular data (including coordinate, experimental/map, and annotation data).
|
||||
- `mol-model-formats` Data format parsers for `mol-model`.
|
||||
- `mol-model-props` Common "custom properties".
|
||||
- `mol-script` A scriting language for creating representations/scenes and querying (includes the [MolQL query language](https://molql.github.io)).
|
||||
- `mol-script` A scripting language for creating representations/scenes and querying (includes the [MolQL query language](https://molql.github.io)).
|
||||
- `mol-geo` Creating (molecular) geometries.
|
||||
- `mol-theme` Theming for structure, volume and shape representations.
|
||||
- `mol-repr` Molecular representations for structures, volumes and shapes.
|
||||
- `mol-gl` A wrapper around WebGL.
|
||||
- `mol-canvas3d` A low level 3d view component. Uses `mol-geo` to generate geometries.
|
||||
- `mol-canvas3d` A low-level 3d view component. Uses `mol-geo` to generate geometries.
|
||||
- `mol-state` State representation tree with state saving and automatic updates.
|
||||
- `mol-app` Components for builduing UIs.
|
||||
- `mol-app` Components for building UIs.
|
||||
- `mol-plugin` Allow to define modular Mol* plugin instances utilizing `mol-state` and `mol-canvas3d`.
|
||||
- `mol-plugin-ui` React based user interface for the Mol* plugin. Some components of the UI are usable outside the main plugin and can be integrated to 3rd party solutions.
|
||||
- `mol-plugin-state` State transformations, builders, and managers.
|
||||
- `mol-plugin-ui` React-based user interface for the Mol* plugin. Some components of the UI are usable outside the main plugin and can be integrated into 3rd party solutions.
|
||||
- `mol-util` Useful things that do not fit elsewhere.
|
||||
|
||||
Moreover, the project contains the imlementation of `servers`, including
|
||||
Moreover, the project contains the implementation of `servers`, including
|
||||
|
||||
- `servers/model` A tool for accessing coordinate and annotation data of molecular structures.
|
||||
- `servers/volume` A tool for accessing volumetric experimental data related to molecular structures.
|
||||
@@ -61,6 +62,8 @@ This project builds on experience from previous solutions:
|
||||
### Build automatically on file save:
|
||||
npm run watch
|
||||
|
||||
If working on just the viewer, ``npm run watch-viewer`` will provide shorter compile times.
|
||||
|
||||
### Build with debug mode enabled:
|
||||
DEBUG=molstar npm run watch
|
||||
|
||||
@@ -86,21 +89,20 @@ and navigate to `build/viewer`
|
||||
|
||||
### Code generation
|
||||
**CIF schemas**
|
||||
Install CIFTools `npm install ciftools -g`
|
||||
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/cif-core.ts -p CifCore -aa
|
||||
node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF
|
||||
node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD
|
||||
node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD
|
||||
node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/cif-core.ts -p CifCore -aa
|
||||
|
||||
**GraphQL schemas**
|
||||
|
||||
./node_modules/.bin/graphql-codegen -c ./data/rcsb-graphql/codegen.yml
|
||||
./node_modules/.bin/graphql-codegen -c ./src/extensions/rcsb/graphql/codegen.yml
|
||||
|
||||
### Other scripts
|
||||
**Create chem comp bond table**
|
||||
|
||||
export NODE_PATH="lib"; node --max-old-space-size=4096 lib/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
|
||||
node --max-old-space-size=4096 lib/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
|
||||
|
||||
**Test model server**
|
||||
|
||||
@@ -112,22 +114,26 @@ Install CIFTools `npm install ciftools -g`
|
||||
|
||||
**Convert any CIF to BinaryCIF**
|
||||
|
||||
node build/model-server/preprocess -i file.cif -ob file.bcif
|
||||
node lib/servers/model/preprocess -i file.cif -ob file.bcif
|
||||
|
||||
To see all available commands, use ``node build/model-server/preprocess -h``.
|
||||
To see all available commands, use ``node lib/servers/model/preprocess -h``.
|
||||
|
||||
Or
|
||||
|
||||
node ./lib/apps/cif2bcif
|
||||
|
||||
## Development
|
||||
|
||||
### Intallation
|
||||
### Installation
|
||||
|
||||
If node complains about a missine acorn peer dependency, run the following commands
|
||||
If node complains about a missing acorn peer dependency, run the following commands
|
||||
|
||||
npm update acorn --depth 20
|
||||
npm dedupe
|
||||
|
||||
### Editor
|
||||
|
||||
To get syntax highlighting for shader and graphql files add the following to Visual Code's settings files and make sure relevant extanesions are installed in the editor.
|
||||
To get syntax highlighting for shader and graphql files add the following to Visual Code's settings files and make sure relevant extensions are installed in the editor.
|
||||
|
||||
"files.associations": {
|
||||
"*.glsl.ts": "glsl",
|
||||
@@ -139,7 +145,7 @@ To get syntax highlighting for shader and graphql files add the following to Vis
|
||||
## Publish
|
||||
|
||||
### Prerelease
|
||||
npm version prerelease # asumes the current version ends with '-dev.X'
|
||||
npm version prerelease # assumes the current version ends with '-dev.X'
|
||||
npm publish --tag next
|
||||
|
||||
### Release
|
||||
@@ -161,4 +167,4 @@ Continually develop this prototype project. As individual modules become stable,
|
||||
Funding sources include but are not limited to:
|
||||
* [RCSB PDB](https://www.rcsb.org) funding by a grant [DBI-1338415; PI: SK Burley] from the NSF, the NIH, and the US DoE
|
||||
* [PDBe, EMBL-EBI](https://pdbe.org)
|
||||
* [CEITEC](https://www.ceitec.eu/)
|
||||
* [CEITEC](https://www.ceitec.eu/)
|
||||
|
||||
88
data/cif-field-names/bird-field-names.csv
Normal file
88
data/cif-field-names/bird-field-names.csv
Normal file
@@ -0,0 +1,88 @@
|
||||
pdbx_reference_molecule.prd_id
|
||||
pdbx_reference_molecule.name
|
||||
pdbx_reference_molecule.represent_as
|
||||
pdbx_reference_molecule.type
|
||||
pdbx_reference_molecule.type_evidence_code
|
||||
pdbx_reference_molecule.class
|
||||
pdbx_reference_molecule.class_evidence_code
|
||||
pdbx_reference_molecule.formula
|
||||
pdbx_reference_molecule.chem_comp_id
|
||||
pdbx_reference_molecule.formula_weight
|
||||
pdbx_reference_molecule.release_status
|
||||
pdbx_reference_molecule.replaces
|
||||
pdbx_reference_molecule.replaced_by
|
||||
pdbx_reference_molecule.compound_details
|
||||
pdbx_reference_molecule.description
|
||||
pdbx_reference_molecule.representative_PDB_id_code
|
||||
|
||||
pdbx_reference_entity_list.prd_id
|
||||
pdbx_reference_entity_list.ref_entity_id
|
||||
pdbx_reference_entity_list.component_id
|
||||
pdbx_reference_entity_list.type
|
||||
pdbx_reference_entity_list.details
|
||||
|
||||
pdbx_reference_entity_nonpoly.prd_id
|
||||
pdbx_reference_entity_nonpoly.ref_entity_id
|
||||
pdbx_reference_entity_nonpoly.name
|
||||
pdbx_reference_entity_nonpoly.chem_comp_id
|
||||
|
||||
pdbx_reference_entity_link.prd_id
|
||||
pdbx_reference_entity_link.link_id
|
||||
pdbx_reference_entity_link.link_class
|
||||
pdbx_reference_entity_link.ref_entity_id_1
|
||||
pdbx_reference_entity_link.entity_seq_num_1
|
||||
pdbx_reference_entity_link.comp_id_1
|
||||
pdbx_reference_entity_link.atom_id_1
|
||||
pdbx_reference_entity_link.ref_entity_id_2
|
||||
pdbx_reference_entity_link.entity_seq_num_2
|
||||
pdbx_reference_entity_link.comp_id_2
|
||||
pdbx_reference_entity_link.atom_id_2
|
||||
pdbx_reference_entity_link.value_order
|
||||
pdbx_reference_entity_link.component_1
|
||||
pdbx_reference_entity_link.component_2
|
||||
pdbx_reference_entity_link.details
|
||||
|
||||
pdbx_reference_entity_poly_link.prd_id
|
||||
pdbx_reference_entity_poly_link.ref_entity_id
|
||||
pdbx_reference_entity_poly_link.link_id
|
||||
pdbx_reference_entity_poly_link.atom_id_1
|
||||
pdbx_reference_entity_poly_link.comp_id_1
|
||||
pdbx_reference_entity_poly_link.entity_seq_num_1
|
||||
pdbx_reference_entity_poly_link.atom_id_2
|
||||
pdbx_reference_entity_poly_link.comp_id_2
|
||||
pdbx_reference_entity_poly_link.entity_seq_num_2
|
||||
pdbx_reference_entity_poly_link.value_order
|
||||
pdbx_reference_entity_poly_link.component_id
|
||||
|
||||
pdbx_reference_entity_poly.prd_id
|
||||
pdbx_reference_entity_poly.ref_entity_id
|
||||
pdbx_reference_entity_poly.db_code
|
||||
pdbx_reference_entity_poly.db_name
|
||||
pdbx_reference_entity_poly.type
|
||||
|
||||
pdbx_reference_entity_sequence.prd_id
|
||||
pdbx_reference_entity_sequence.ref_entity_id
|
||||
pdbx_reference_entity_sequence.type
|
||||
pdbx_reference_entity_sequence.NRP_flag
|
||||
pdbx_reference_entity_sequence.one_letter_codes
|
||||
|
||||
pdbx_reference_entity_poly_seq.prd_id
|
||||
pdbx_reference_entity_poly_seq.ref_entity_id
|
||||
pdbx_reference_entity_poly_seq.num
|
||||
pdbx_reference_entity_poly_seq.mon_id
|
||||
pdbx_reference_entity_poly_seq.parent_mon_id
|
||||
pdbx_reference_entity_poly_seq.hetero
|
||||
pdbx_reference_entity_poly_seq.observed
|
||||
|
||||
pdbx_reference_entity_src_nat.prd_id
|
||||
pdbx_reference_entity_src_nat.ref_entity_id
|
||||
pdbx_reference_entity_src_nat.ordinal
|
||||
pdbx_reference_entity_src_nat.taxid
|
||||
pdbx_reference_entity_src_nat.organism_scientific
|
||||
pdbx_reference_entity_src_nat.db_code
|
||||
pdbx_reference_entity_src_nat.db_name
|
||||
|
||||
pdbx_prd_audit.prd_id
|
||||
pdbx_prd_audit.date
|
||||
pdbx_prd_audit.processing_site
|
||||
pdbx_prd_audit.action_type
|
||||
|
60
data/cif-field-names/ccd-field-names.csv
Normal file
60
data/cif-field-names/ccd-field-names.csv
Normal file
@@ -0,0 +1,60 @@
|
||||
chem_comp.id
|
||||
chem_comp.name
|
||||
chem_comp.type
|
||||
chem_comp.pdbx_type
|
||||
chem_comp.formula
|
||||
chem_comp.mon_nstd_parent_comp_id
|
||||
chem_comp.pdbx_synonyms
|
||||
chem_comp.pdbx_formal_charge
|
||||
chem_comp.pdbx_initial_date
|
||||
chem_comp.pdbx_modified_date
|
||||
chem_comp.pdbx_ambiguous_flag
|
||||
chem_comp.pdbx_release_status
|
||||
chem_comp.pdbx_replaced_by
|
||||
chem_comp.pdbx_replaces
|
||||
chem_comp.formula_weight
|
||||
chem_comp.one_letter_code
|
||||
chem_comp.three_letter_code
|
||||
chem_comp.pdbx_model_coordinates_details
|
||||
chem_comp.pdbx_model_coordinates_missing_flag
|
||||
chem_comp.pdbx_ideal_coordinates_details
|
||||
chem_comp.pdbx_ideal_coordinates_missing_flag
|
||||
chem_comp.pdbx_model_coordinates_db_code
|
||||
chem_comp.pdbx_processing_site
|
||||
|
||||
chem_comp_atom.comp_id
|
||||
chem_comp_atom.atom_id
|
||||
chem_comp_atom.alt_atom_id
|
||||
chem_comp_atom.type_symbol
|
||||
chem_comp_atom.charge
|
||||
chem_comp_atom.pdbx_align
|
||||
chem_comp_atom.pdbx_aromatic_flag
|
||||
chem_comp_atom.pdbx_leaving_atom_flag
|
||||
chem_comp_atom.pdbx_stereo_config
|
||||
chem_comp_atom.model_Cartn_x
|
||||
chem_comp_atom.model_Cartn_y
|
||||
chem_comp_atom.model_Cartn_z
|
||||
chem_comp_atom.pdbx_model_Cartn_x_ideal
|
||||
chem_comp_atom.pdbx_model_Cartn_y_ideal
|
||||
chem_comp_atom.pdbx_model_Cartn_z_ideal
|
||||
chem_comp_atom.pdbx_ordinal
|
||||
|
||||
chem_comp_bond.comp_id
|
||||
chem_comp_bond.atom_id_1
|
||||
chem_comp_bond.atom_id_2
|
||||
chem_comp_bond.value_order
|
||||
chem_comp_bond.pdbx_aromatic_flag
|
||||
chem_comp_bond.pdbx_stereo_config
|
||||
chem_comp_bond.pdbx_ordinal
|
||||
|
||||
pdbx_chem_comp_descriptor.comp_id
|
||||
pdbx_chem_comp_descriptor.type
|
||||
pdbx_chem_comp_descriptor.program
|
||||
pdbx_chem_comp_descriptor.program_version
|
||||
pdbx_chem_comp_descriptor.descriptor
|
||||
|
||||
pdbx_chem_comp_identifier.comp_id
|
||||
pdbx_chem_comp_identifier.type
|
||||
pdbx_chem_comp_identifier.program
|
||||
pdbx_chem_comp_identifier.program_version
|
||||
pdbx_chem_comp_identifier.identifier
|
||||
|
76
data/cif-field-names/cif-core-field-names.csv
Normal file
76
data/cif-field-names/cif-core-field-names.csv
Normal file
@@ -0,0 +1,76 @@
|
||||
audit.block_doi
|
||||
|
||||
database_code.depnum_ccdc_archive
|
||||
database_code.depnum_ccdc_fiz
|
||||
database_code.ICSD
|
||||
database_code.MDF
|
||||
database_code.NBS
|
||||
database_code.CSD
|
||||
database_code.COD
|
||||
|
||||
chemical.name_systematic
|
||||
chemical.name_common
|
||||
chemical.melting_point
|
||||
|
||||
chemical_formula.moiety
|
||||
chemical_formula.sum
|
||||
chemical_formula.weight
|
||||
|
||||
atom_type.symbol
|
||||
atom_type.description
|
||||
|
||||
atom_type_scat.dispersion_real
|
||||
atom_type_scat.dispersion_imag
|
||||
atom_type_scat.source
|
||||
|
||||
space_group.crystal_system
|
||||
space_group.name_H-M_full
|
||||
space_group.IT_number
|
||||
space_group_symop.operation_xyz
|
||||
|
||||
cell.length_a
|
||||
cell.length_b
|
||||
cell.length_c
|
||||
cell.angle_alpha
|
||||
cell.angle_beta
|
||||
cell.angle_gamma
|
||||
cell.volume
|
||||
cell.formula_units_Z
|
||||
|
||||
atom_site.label
|
||||
atom_site.type_symbol
|
||||
atom_site.fract_x
|
||||
atom_site.fract_y
|
||||
atom_site.fract_z
|
||||
atom_site.U_iso_or_equiv
|
||||
atom_site.adp_type
|
||||
atom_site.occupancy
|
||||
atom_site.calc_flag
|
||||
atom_site.refinement_flags
|
||||
atom_site.disorder_assembly
|
||||
atom_site.disorder_group
|
||||
atom_site.site_symmetry_multiplicity
|
||||
|
||||
atom_site_aniso.label
|
||||
atom_site_aniso.U
|
||||
atom_site_aniso.U_11
|
||||
atom_site_aniso.U_22
|
||||
atom_site_aniso.U_33
|
||||
atom_site_aniso.U_23
|
||||
atom_site_aniso.U_13
|
||||
atom_site_aniso.U_12
|
||||
atom_site_aniso.U_su
|
||||
atom_site_aniso.U_11_su
|
||||
atom_site_aniso.U_22_su
|
||||
atom_site_aniso.U_33_su
|
||||
atom_site_aniso.U_23_su
|
||||
atom_site_aniso.U_13_su
|
||||
atom_site_aniso.U_12_su
|
||||
|
||||
geom_bond.atom_site_label_1
|
||||
geom_bond.atom_site_label_2
|
||||
geom_bond.distance
|
||||
geom_bond.site_symmetry_1
|
||||
geom_bond.site_symmetry_2
|
||||
geom_bond.publ_flag
|
||||
geom_bond.valence
|
||||
|
805
data/cif-field-names/mmcif-field-names.csv
Normal file
805
data/cif-field-names/mmcif-field-names.csv
Normal file
@@ -0,0 +1,805 @@
|
||||
atom_sites.entry_id
|
||||
atom_sites.fract_transf_matrix
|
||||
atom_sites.fract_transf_vector
|
||||
|
||||
atom_site.group_PDB
|
||||
atom_site.id
|
||||
atom_site.type_symbol
|
||||
atom_site.label_atom_id
|
||||
atom_site.label_alt_id
|
||||
atom_site.label_comp_id
|
||||
atom_site.label_asym_id
|
||||
atom_site.label_entity_id
|
||||
atom_site.label_seq_id
|
||||
atom_site.pdbx_PDB_ins_code
|
||||
atom_site.pdbx_formal_charge
|
||||
atom_site.Cartn_x
|
||||
atom_site.Cartn_y
|
||||
atom_site.Cartn_z
|
||||
atom_site.occupancy
|
||||
atom_site.B_iso_or_equiv
|
||||
atom_site.auth_atom_id
|
||||
atom_site.auth_comp_id
|
||||
atom_site.auth_asym_id
|
||||
atom_site.auth_seq_id
|
||||
atom_site.pdbx_PDB_model_num
|
||||
atom_site.ihm_model_id
|
||||
|
||||
atom_site_anisotrop.id
|
||||
atom_site_anisotrop.U
|
||||
atom_site_anisotrop.U_esd
|
||||
atom_site_anisotrop.pdbx_PDB_ins_code
|
||||
atom_site_anisotrop.pdbx_auth_asym_id
|
||||
atom_site_anisotrop.pdbx_auth_atom_id
|
||||
atom_site_anisotrop.pdbx_auth_comp_id
|
||||
atom_site_anisotrop.pdbx_auth_seq_id
|
||||
atom_site_anisotrop.pdbx_label_alt_id
|
||||
atom_site_anisotrop.pdbx_label_asym_id
|
||||
atom_site_anisotrop.pdbx_label_atom_id
|
||||
atom_site_anisotrop.pdbx_label_comp_id
|
||||
atom_site_anisotrop.pdbx_label_seq_id
|
||||
atom_site_anisotrop.type_symbol
|
||||
|
||||
chem_comp.id
|
||||
chem_comp.type
|
||||
chem_comp.mon_nstd_flag
|
||||
chem_comp.name
|
||||
chem_comp.pdbx_synonyms
|
||||
chem_comp.formula
|
||||
chem_comp.formula_weight
|
||||
|
||||
chem_comp_bond.comp_id
|
||||
chem_comp_bond.pdbx_stereo_config
|
||||
chem_comp_bond.pdbx_ordinal
|
||||
chem_comp_bond.pdbx_aromatic_flag
|
||||
chem_comp_bond.atom_id_1
|
||||
chem_comp_bond.atom_id_2
|
||||
chem_comp_bond.value_order
|
||||
|
||||
pdbx_chem_comp_identifier.comp_id
|
||||
pdbx_chem_comp_identifier.type
|
||||
pdbx_chem_comp_identifier.program
|
||||
pdbx_chem_comp_identifier.program_version
|
||||
pdbx_chem_comp_identifier.identifier
|
||||
|
||||
pdbx_chem_comp_related.comp_id
|
||||
pdbx_chem_comp_related.related_comp_id
|
||||
pdbx_chem_comp_related.relationship_type
|
||||
pdbx_chem_comp_related.details
|
||||
|
||||
pdbx_chem_comp_synonyms.comp_id
|
||||
pdbx_chem_comp_synonyms.name
|
||||
pdbx_chem_comp_synonyms.provenance
|
||||
|
||||
cell.entry_id
|
||||
cell.length_a
|
||||
cell.length_b
|
||||
cell.length_c
|
||||
cell.angle_alpha
|
||||
cell.angle_beta
|
||||
cell.angle_gamma
|
||||
cell.Z_PDB
|
||||
cell.pdbx_unique_axis
|
||||
|
||||
pdbx_database_related.db_name
|
||||
pdbx_database_related.details
|
||||
pdbx_database_related.db_id
|
||||
pdbx_database_related.content_type
|
||||
|
||||
pdbx_database_status.status_code
|
||||
pdbx_database_status.status_code_sf
|
||||
pdbx_database_status.status_code_mr
|
||||
pdbx_database_status.entry_id
|
||||
pdbx_database_status.recvd_initial_deposition_date
|
||||
pdbx_database_status.SG_entry
|
||||
pdbx_database_status.deposit_site
|
||||
pdbx_database_status.process_site
|
||||
pdbx_database_status.status_code_cs
|
||||
pdbx_database_status.methods_development_category
|
||||
pdbx_database_status.pdb_format_compatible
|
||||
|
||||
entity.id
|
||||
entity.type
|
||||
entity.src_method
|
||||
entity.pdbx_description
|
||||
entity.formula_weight
|
||||
entity.pdbx_number_of_molecules
|
||||
entity.details
|
||||
entity.pdbx_mutation
|
||||
entity.pdbx_fragment
|
||||
entity.pdbx_ec
|
||||
|
||||
entity_poly.entity_id
|
||||
entity_poly.type
|
||||
entity_poly.nstd_linkage
|
||||
entity_poly.nstd_monomer
|
||||
entity_poly.pdbx_seq_one_letter_code
|
||||
entity_poly.pdbx_seq_one_letter_code_can
|
||||
entity_poly.pdbx_strand_id
|
||||
entity_poly.pdbx_target_identifier
|
||||
|
||||
entity_poly_seq.entity_id
|
||||
entity_poly_seq.num
|
||||
entity_poly_seq.mon_id
|
||||
entity_poly_seq.hetero
|
||||
|
||||
entity_src_gen.entity_id
|
||||
entity_src_gen.pdbx_src_id
|
||||
entity_src_gen.pdbx_beg_seq_num
|
||||
entity_src_gen.pdbx_end_seq_num
|
||||
entity_src_gen.pdbx_gene_src_gene
|
||||
entity_src_gen.pdbx_gene_src_scientific_name
|
||||
entity_src_gen.plasmid_name
|
||||
|
||||
entity_src_nat.entity_id
|
||||
entity_src_nat.pdbx_src_id
|
||||
entity_src_nat.pdbx_beg_seq_num
|
||||
entity_src_nat.pdbx_end_seq_num
|
||||
entity_src_nat.pdbx_organism_scientific
|
||||
entity_src_nat.pdbx_plasmid_name
|
||||
|
||||
pdbx_entity_instance_feature.ordinal
|
||||
pdbx_entity_instance_feature.feature_type
|
||||
pdbx_entity_instance_feature.details
|
||||
pdbx_entity_instance_feature.asym_id
|
||||
pdbx_entity_instance_feature.comp_id
|
||||
pdbx_entity_instance_feature.seq_num
|
||||
pdbx_entity_instance_feature.auth_asym_id
|
||||
pdbx_entity_instance_feature.auth_comp_id
|
||||
pdbx_entity_instance_feature.auth_seq_num
|
||||
|
||||
pdbx_entity_src_syn.entity_id
|
||||
pdbx_entity_src_syn.pdbx_src_id
|
||||
pdbx_entity_src_syn.pdbx_beg_seq_num
|
||||
pdbx_entity_src_syn.pdbx_end_seq_num
|
||||
pdbx_entity_src_syn.organism_scientific
|
||||
|
||||
pdbx_entity_branch.entity_id
|
||||
pdbx_entity_branch.type
|
||||
|
||||
pdbx_entity_branch_list.entity_id
|
||||
pdbx_entity_branch_list.comp_id
|
||||
pdbx_entity_branch_list.num
|
||||
pdbx_entity_branch_list.hetero
|
||||
|
||||
pdbx_entity_branch_link.link_id
|
||||
pdbx_entity_branch_link.entity_id
|
||||
pdbx_entity_branch_link.entity_branch_list_num_1
|
||||
pdbx_entity_branch_link.comp_id_1
|
||||
pdbx_entity_branch_link.atom_id_1
|
||||
pdbx_entity_branch_link.leaving_atom_id_1
|
||||
pdbx_entity_branch_link.atom_stereo_config_1
|
||||
pdbx_entity_branch_link.entity_branch_list_num_2
|
||||
pdbx_entity_branch_link.comp_id_2
|
||||
pdbx_entity_branch_link.atom_id_2
|
||||
pdbx_entity_branch_link.leaving_atom_id_2
|
||||
pdbx_entity_branch_link.atom_stereo_config_2
|
||||
pdbx_entity_branch_link.value_order
|
||||
pdbx_entity_branch_link.details
|
||||
|
||||
pdbx_branch_scheme.asym_id
|
||||
pdbx_branch_scheme.entity_id
|
||||
pdbx_branch_scheme.mon_id
|
||||
pdbx_branch_scheme.num
|
||||
pdbx_branch_scheme.auth_asym_id
|
||||
pdbx_branch_scheme.auth_mon_id
|
||||
pdbx_branch_scheme.auth_seq_num
|
||||
pdbx_branch_scheme.hetero
|
||||
pdbx_branch_scheme.pdb_mon_id
|
||||
pdbx_branch_scheme.pdb_asym_id
|
||||
pdbx_branch_scheme.pdb_seq_num
|
||||
|
||||
pdbx_entity_branch_descriptor.ordinal
|
||||
pdbx_entity_branch_descriptor.entity_id
|
||||
pdbx_entity_branch_descriptor.descriptor
|
||||
pdbx_entity_branch_descriptor.type
|
||||
pdbx_entity_branch_descriptor.program
|
||||
pdbx_entity_branch_descriptor.program_version
|
||||
|
||||
pdbx_entity_nonpoly.entity_id
|
||||
pdbx_entity_nonpoly.name
|
||||
pdbx_entity_nonpoly.comp_id
|
||||
|
||||
pdbx_nonpoly_scheme.asym_id
|
||||
pdbx_nonpoly_scheme.entity_id
|
||||
pdbx_nonpoly_scheme.mon_id
|
||||
pdbx_nonpoly_scheme.ndb_seq_num
|
||||
pdbx_nonpoly_scheme.pdb_seq_num
|
||||
pdbx_nonpoly_scheme.auth_seq_num
|
||||
pdbx_nonpoly_scheme.pdb_mon_id
|
||||
pdbx_nonpoly_scheme.auth_mon_id
|
||||
pdbx_nonpoly_scheme.pdb_strand_id
|
||||
pdbx_nonpoly_scheme.pdb_ins_code
|
||||
|
||||
entry.id
|
||||
|
||||
audit_conform.dict_name
|
||||
audit_conform.dict_version
|
||||
audit_conform.dict_location
|
||||
|
||||
database_2.database_id
|
||||
database_2.database_code
|
||||
|
||||
audit_author.name
|
||||
audit_author.pdbx_ordinal
|
||||
audit_author.identifier_ORCID
|
||||
|
||||
citation.id
|
||||
citation.title
|
||||
citation.journal_abbrev
|
||||
citation.journal_volume
|
||||
citation.page_first
|
||||
citation.page_last
|
||||
citation.year
|
||||
citation.journal_id_ASTM
|
||||
citation.country
|
||||
citation.journal_id_ISSN
|
||||
citation.journal_id_CSD
|
||||
citation.book_publisher
|
||||
citation.pdbx_database_id_PubMed
|
||||
citation.pdbx_database_id_DOI
|
||||
|
||||
citation_author.citation_id
|
||||
citation_author.name
|
||||
citation_author.ordinal
|
||||
|
||||
exptl.entry_id
|
||||
exptl.method
|
||||
|
||||
struct.entry_id
|
||||
struct.title
|
||||
struct.pdbx_descriptor
|
||||
|
||||
struct_asym.id
|
||||
struct_asym.pdbx_blank_PDB_chainid_flag
|
||||
struct_asym.pdbx_modified
|
||||
struct_asym.entity_id
|
||||
struct_asym.details
|
||||
|
||||
struct_conf.conf_type_id
|
||||
struct_conf.id
|
||||
struct_conf.pdbx_PDB_helix_id
|
||||
struct_conf.beg_label_comp_id
|
||||
struct_conf.beg_label_asym_id
|
||||
struct_conf.beg_label_seq_id
|
||||
struct_conf.pdbx_beg_PDB_ins_code
|
||||
struct_conf.end_label_comp_id
|
||||
struct_conf.end_label_asym_id
|
||||
struct_conf.end_label_seq_id
|
||||
struct_conf.pdbx_end_PDB_ins_code
|
||||
struct_conf.beg_auth_comp_id
|
||||
struct_conf.beg_auth_asym_id
|
||||
struct_conf.beg_auth_seq_id
|
||||
struct_conf.end_auth_comp_id
|
||||
struct_conf.end_auth_asym_id
|
||||
struct_conf.end_auth_seq_id
|
||||
struct_conf.pdbx_PDB_helix_class
|
||||
struct_conf.details
|
||||
struct_conf.pdbx_PDB_helix_length
|
||||
|
||||
struct_conn.id
|
||||
struct_conn.conn_type_id
|
||||
struct_conn.pdbx_PDB_id
|
||||
struct_conn.ptnr1_label_asym_id
|
||||
struct_conn.ptnr1_label_comp_id
|
||||
struct_conn.ptnr1_label_seq_id
|
||||
struct_conn.ptnr1_label_atom_id
|
||||
struct_conn.pdbx_ptnr1_label_alt_id
|
||||
struct_conn.pdbx_ptnr1_PDB_ins_code
|
||||
struct_conn.pdbx_ptnr1_standard_comp_id
|
||||
struct_conn.ptnr1_symmetry
|
||||
struct_conn.ptnr2_label_asym_id
|
||||
struct_conn.ptnr2_label_comp_id
|
||||
struct_conn.ptnr2_label_seq_id
|
||||
struct_conn.ptnr2_label_atom_id
|
||||
struct_conn.pdbx_ptnr2_label_alt_id
|
||||
struct_conn.pdbx_ptnr2_PDB_ins_code
|
||||
struct_conn.ptnr1_auth_asym_id
|
||||
struct_conn.ptnr1_auth_comp_id
|
||||
struct_conn.ptnr1_auth_seq_id
|
||||
struct_conn.ptnr2_auth_asym_id
|
||||
struct_conn.ptnr2_auth_comp_id
|
||||
struct_conn.ptnr2_auth_seq_id
|
||||
struct_conn.ptnr2_symmetry
|
||||
struct_conn.pdbx_ptnr3_label_atom_id
|
||||
struct_conn.pdbx_ptnr3_label_seq_id
|
||||
struct_conn.pdbx_ptnr3_label_comp_id
|
||||
struct_conn.pdbx_ptnr3_label_asym_id
|
||||
struct_conn.pdbx_ptnr3_label_alt_id
|
||||
struct_conn.pdbx_ptnr3_PDB_ins_code
|
||||
struct_conn.details
|
||||
struct_conn.pdbx_dist_value
|
||||
struct_conn.pdbx_value_order
|
||||
|
||||
struct_conn_type.id
|
||||
struct_conn_type.criteria
|
||||
struct_conn_type.reference
|
||||
|
||||
struct_keywords.entry_id
|
||||
struct_keywords.pdbx_keywords
|
||||
struct_keywords.text
|
||||
|
||||
struct_ncs_oper.id
|
||||
struct_ncs_oper.code
|
||||
struct_ncs_oper.matrix
|
||||
struct_ncs_oper.vector
|
||||
struct_ncs_oper.details
|
||||
|
||||
struct_sheet_range.sheet_id
|
||||
struct_sheet_range.id
|
||||
struct_sheet_range.beg_label_comp_id
|
||||
struct_sheet_range.beg_label_asym_id
|
||||
struct_sheet_range.beg_label_seq_id
|
||||
struct_sheet_range.pdbx_beg_PDB_ins_code
|
||||
struct_sheet_range.end_label_comp_id
|
||||
struct_sheet_range.end_label_asym_id
|
||||
struct_sheet_range.end_label_seq_id
|
||||
struct_sheet_range.pdbx_end_PDB_ins_code
|
||||
struct_sheet_range.beg_auth_comp_id
|
||||
struct_sheet_range.beg_auth_asym_id
|
||||
struct_sheet_range.beg_auth_seq_id
|
||||
struct_sheet_range.end_auth_comp_id
|
||||
struct_sheet_range.end_auth_asym_id
|
||||
struct_sheet_range.end_auth_seq_id
|
||||
|
||||
struct_site.id
|
||||
struct_site.pdbx_evidence_code
|
||||
struct_site.pdbx_auth_asym_id
|
||||
struct_site.pdbx_auth_comp_id
|
||||
struct_site.pdbx_auth_seq_id
|
||||
struct_site.pdbx_auth_ins_code
|
||||
struct_site.pdbx_num_residues
|
||||
struct_site.details
|
||||
|
||||
struct_site_gen.id
|
||||
struct_site_gen.site_id
|
||||
struct_site_gen.pdbx_num_res
|
||||
struct_site_gen.label_comp_id
|
||||
struct_site_gen.label_asym_id
|
||||
struct_site_gen.label_seq_id
|
||||
struct_site_gen.pdbx_auth_ins_code
|
||||
struct_site_gen.auth_comp_id
|
||||
struct_site_gen.auth_asym_id
|
||||
struct_site_gen.auth_seq_id
|
||||
struct_site_gen.label_atom_id
|
||||
struct_site_gen.label_alt_id
|
||||
struct_site_gen.symmetry
|
||||
struct_site_gen.details
|
||||
|
||||
symmetry.entry_id
|
||||
symmetry.cell_setting
|
||||
symmetry.Int_Tables_number
|
||||
symmetry.space_group_name_Hall
|
||||
symmetry.space_group_name_H-M
|
||||
|
||||
pdbx_molecule.instance_id
|
||||
pdbx_molecule.prd_id
|
||||
pdbx_molecule.asym_id
|
||||
|
||||
pdbx_molecule_features.prd_id
|
||||
pdbx_molecule_features.name
|
||||
pdbx_molecule_features.type
|
||||
pdbx_molecule_features.class
|
||||
pdbx_molecule_features.details
|
||||
|
||||
pdbx_reference_entity_link.prd_id
|
||||
pdbx_reference_entity_link.link_id
|
||||
pdbx_reference_entity_link.link_class
|
||||
pdbx_reference_entity_link.ref_entity_id_1
|
||||
pdbx_reference_entity_link.entity_seq_num_1
|
||||
pdbx_reference_entity_link.comp_id_1
|
||||
pdbx_reference_entity_link.atom_id_1
|
||||
pdbx_reference_entity_link.ref_entity_id_2
|
||||
pdbx_reference_entity_link.entity_seq_num_2
|
||||
pdbx_reference_entity_link.comp_id_2
|
||||
pdbx_reference_entity_link.atom_id_2
|
||||
pdbx_reference_entity_link.value_order
|
||||
pdbx_reference_entity_link.component_1
|
||||
pdbx_reference_entity_link.component_2
|
||||
pdbx_reference_entity_link.details
|
||||
|
||||
pdbx_reference_entity_list.prd_id
|
||||
pdbx_reference_entity_list.ref_entity_id
|
||||
pdbx_reference_entity_list.component_id
|
||||
pdbx_reference_entity_list.type
|
||||
pdbx_reference_entity_list.details
|
||||
|
||||
pdbx_reference_entity_poly_link.prd_id
|
||||
pdbx_reference_entity_poly_link.ref_entity_id
|
||||
pdbx_reference_entity_poly_link.link_id
|
||||
pdbx_reference_entity_poly_link.atom_id_1
|
||||
pdbx_reference_entity_poly_link.comp_id_1
|
||||
pdbx_reference_entity_poly_link.entity_seq_num_1
|
||||
pdbx_reference_entity_poly_link.atom_id_2
|
||||
pdbx_reference_entity_poly_link.comp_id_2
|
||||
pdbx_reference_entity_poly_link.entity_seq_num_2
|
||||
pdbx_reference_entity_poly_link.value_order
|
||||
pdbx_reference_entity_poly_link.component_id
|
||||
|
||||
pdbx_struct_assembly.id
|
||||
pdbx_struct_assembly.details
|
||||
pdbx_struct_assembly.method_details
|
||||
pdbx_struct_assembly.oligomeric_details
|
||||
pdbx_struct_assembly.oligomeric_count
|
||||
|
||||
pdbx_struct_assembly_gen.assembly_id
|
||||
pdbx_struct_assembly_gen.oper_expression
|
||||
pdbx_struct_assembly_gen.asym_id_list
|
||||
|
||||
pdbx_struct_oper_list.id
|
||||
pdbx_struct_oper_list.type
|
||||
pdbx_struct_oper_list.name
|
||||
pdbx_struct_oper_list.symmetry_operation
|
||||
pdbx_struct_oper_list.matrix
|
||||
pdbx_struct_oper_list.vector
|
||||
|
||||
pdbx_struct_mod_residue.id
|
||||
pdbx_struct_mod_residue.label_asym_id
|
||||
pdbx_struct_mod_residue.label_seq_id
|
||||
pdbx_struct_mod_residue.label_comp_id
|
||||
pdbx_struct_mod_residue.auth_asym_id
|
||||
pdbx_struct_mod_residue.auth_seq_id
|
||||
pdbx_struct_mod_residue.auth_comp_id
|
||||
pdbx_struct_mod_residue.PDB_ins_code
|
||||
pdbx_struct_mod_residue.parent_comp_id
|
||||
pdbx_struct_mod_residue.details
|
||||
|
||||
pdbx_unobs_or_zero_occ_residues.id
|
||||
pdbx_unobs_or_zero_occ_residues.PDB_model_num
|
||||
pdbx_unobs_or_zero_occ_residues.polymer_flag
|
||||
pdbx_unobs_or_zero_occ_residues.occupancy_flag
|
||||
pdbx_unobs_or_zero_occ_residues.auth_asym_id
|
||||
pdbx_unobs_or_zero_occ_residues.auth_comp_id
|
||||
pdbx_unobs_or_zero_occ_residues.auth_seq_id
|
||||
pdbx_unobs_or_zero_occ_residues.PDB_ins_code
|
||||
pdbx_unobs_or_zero_occ_residues.label_asym_id
|
||||
pdbx_unobs_or_zero_occ_residues.label_comp_id
|
||||
pdbx_unobs_or_zero_occ_residues.label_seq_id
|
||||
|
||||
ihm_struct_assembly.id
|
||||
ihm_struct_assembly.name
|
||||
ihm_struct_assembly.description
|
||||
|
||||
ihm_struct_assembly_details.id
|
||||
ihm_struct_assembly_details.assembly_id
|
||||
ihm_struct_assembly_details.parent_assembly_id
|
||||
ihm_struct_assembly_details.entity_description
|
||||
ihm_struct_assembly_details.entity_id
|
||||
ihm_struct_assembly_details.asym_id
|
||||
ihm_struct_assembly_details.entity_poly_segment_id
|
||||
|
||||
ihm_model_representation.id
|
||||
ihm_model_representation.name
|
||||
ihm_model_representation.details
|
||||
|
||||
ihm_model_representation_details.id
|
||||
ihm_model_representation_details.representation_id
|
||||
ihm_model_representation_details.entity_id
|
||||
ihm_model_representation_details.entity_description
|
||||
ihm_model_representation_details.entity_asym_id
|
||||
ihm_model_representation_details.entity_poly_segment_id
|
||||
ihm_model_representation_details.model_object_primitive
|
||||
ihm_model_representation_details.starting_model_id
|
||||
ihm_model_representation_details.model_mode
|
||||
ihm_model_representation_details.model_granularity
|
||||
ihm_model_representation_details.model_object_count
|
||||
|
||||
ihm_external_reference_info.reference_id
|
||||
ihm_external_reference_info.reference_provider
|
||||
ihm_external_reference_info.reference_type
|
||||
ihm_external_reference_info.reference
|
||||
ihm_external_reference_info.refers_to
|
||||
ihm_external_reference_info.associated_url
|
||||
|
||||
ihm_external_files.id
|
||||
ihm_external_files.reference_id
|
||||
ihm_external_files.file_path
|
||||
ihm_external_files.content_type
|
||||
ihm_external_files.file_size_bytes
|
||||
ihm_external_files.details
|
||||
|
||||
ihm_dataset_list.id
|
||||
ihm_dataset_list.data_type
|
||||
ihm_dataset_list.database_hosted
|
||||
|
||||
ihm_dataset_group.id
|
||||
ihm_dataset_group.name
|
||||
ihm_dataset_group.application
|
||||
ihm_dataset_group.details
|
||||
|
||||
ihm_dataset_group_link.group_id
|
||||
ihm_dataset_group_link.dataset_list_id
|
||||
|
||||
ihm_dataset_external_reference.id
|
||||
ihm_dataset_external_reference.dataset_list_id
|
||||
ihm_dataset_external_reference.file_id
|
||||
|
||||
ihm_dataset_related_db_reference.id
|
||||
ihm_dataset_related_db_reference.dataset_list_id
|
||||
ihm_dataset_related_db_reference.db_name
|
||||
ihm_dataset_related_db_reference.accession_code
|
||||
ihm_dataset_related_db_reference.version
|
||||
ihm_dataset_related_db_reference.details
|
||||
|
||||
ihm_related_datasets.dataset_list_id_derived
|
||||
ihm_related_datasets.dataset_list_id_primary
|
||||
|
||||
ihm_poly_residue_feature.ordinal_id
|
||||
ihm_poly_residue_feature.feature_id
|
||||
ihm_poly_residue_feature.entity_id
|
||||
ihm_poly_residue_feature.asym_id
|
||||
ihm_poly_residue_feature.seq_id_begin
|
||||
ihm_poly_residue_feature.comp_id_begin
|
||||
ihm_poly_residue_feature.seq_id_end
|
||||
ihm_poly_residue_feature.comp_id_end
|
||||
|
||||
ihm_feature_list.feature_id
|
||||
ihm_feature_list.feature_type
|
||||
ihm_feature_list.entity_type
|
||||
|
||||
ihm_cross_link_list.id
|
||||
ihm_cross_link_list.group_id
|
||||
ihm_cross_link_list.entity_description_1
|
||||
ihm_cross_link_list.entity_id_1
|
||||
ihm_cross_link_list.seq_id_1
|
||||
ihm_cross_link_list.comp_id_1
|
||||
ihm_cross_link_list.entity_description_2
|
||||
ihm_cross_link_list.entity_id_2
|
||||
ihm_cross_link_list.seq_id_2
|
||||
ihm_cross_link_list.comp_id_2
|
||||
ihm_cross_link_list.linker_type
|
||||
ihm_cross_link_list.dataset_list_id
|
||||
|
||||
ihm_cross_link_restraint.id
|
||||
ihm_cross_link_restraint.group_id
|
||||
ihm_cross_link_restraint.entity_id_1
|
||||
ihm_cross_link_restraint.asym_id_1
|
||||
ihm_cross_link_restraint.seq_id_1
|
||||
ihm_cross_link_restraint.atom_id_1
|
||||
ihm_cross_link_restraint.comp_id_1
|
||||
ihm_cross_link_restraint.entity_id_2
|
||||
ihm_cross_link_restraint.asym_id_2
|
||||
ihm_cross_link_restraint.seq_id_2
|
||||
ihm_cross_link_restraint.atom_id_2
|
||||
ihm_cross_link_restraint.comp_id_2
|
||||
ihm_cross_link_restraint.restraint_type
|
||||
ihm_cross_link_restraint.conditional_crosslink_flag
|
||||
ihm_cross_link_restraint.model_granularity
|
||||
ihm_cross_link_restraint.distance_threshold
|
||||
ihm_cross_link_restraint.psi
|
||||
ihm_cross_link_restraint.sigma_1
|
||||
ihm_cross_link_restraint.sigma_2
|
||||
|
||||
ihm_cross_link_result_parameters.id
|
||||
ihm_cross_link_result_parameters.restraint_id
|
||||
ihm_cross_link_result_parameters.model_id
|
||||
ihm_cross_link_result_parameters.psi
|
||||
ihm_cross_link_result_parameters.sigma_1
|
||||
ihm_cross_link_result_parameters.sigma_2
|
||||
|
||||
ihm_sas_restraint.id
|
||||
ihm_sas_restraint.dataset_list_id
|
||||
ihm_sas_restraint.model_id
|
||||
ihm_sas_restraint.struct_assembly_id
|
||||
ihm_sas_restraint.profile_segment_flag
|
||||
ihm_sas_restraint.fitting_atom_type
|
||||
ihm_sas_restraint.fitting_method
|
||||
ihm_sas_restraint.fitting_state
|
||||
ihm_sas_restraint.radius_of_gyration
|
||||
ihm_sas_restraint.chi_value
|
||||
ihm_sas_restraint.details
|
||||
|
||||
ihm_derived_distance_restraint.id
|
||||
ihm_derived_distance_restraint.group_id
|
||||
ihm_derived_distance_restraint.feature_id_1
|
||||
ihm_derived_distance_restraint.feature_id_2
|
||||
ihm_derived_distance_restraint.group_conditionality
|
||||
ihm_derived_distance_restraint.restraint_type
|
||||
ihm_derived_distance_restraint.distance_upper_limit
|
||||
ihm_derived_distance_restraint.random_exclusion_fraction
|
||||
ihm_derived_distance_restraint.dataset_list_id
|
||||
|
||||
ihm_2dem_class_average_restraint.id
|
||||
ihm_2dem_class_average_restraint.dataset_list_id
|
||||
ihm_2dem_class_average_restraint.number_raw_micrographs
|
||||
ihm_2dem_class_average_restraint.pixel_size_width
|
||||
ihm_2dem_class_average_restraint.pixel_size_height
|
||||
ihm_2dem_class_average_restraint.image_resolution
|
||||
ihm_2dem_class_average_restraint.image_segment_flag
|
||||
ihm_2dem_class_average_restraint.number_of_projections
|
||||
ihm_2dem_class_average_restraint.struct_assembly_id
|
||||
ihm_2dem_class_average_restraint.details
|
||||
|
||||
ihm_2dem_class_average_fitting.id
|
||||
ihm_2dem_class_average_fitting.restraint_id
|
||||
ihm_2dem_class_average_fitting.model_id
|
||||
ihm_2dem_class_average_fitting.cross_correlation_coefficient
|
||||
ihm_2dem_class_average_fitting.rot_matrix
|
||||
ihm_2dem_class_average_fitting.tr_vector
|
||||
|
||||
ihm_3dem_restraint.id
|
||||
ihm_3dem_restraint.dataset_list_id
|
||||
ihm_3dem_restraint.fitting_method
|
||||
ihm_3dem_restraint.struct_assembly_id
|
||||
ihm_3dem_restraint.number_of_gaussians
|
||||
ihm_3dem_restraint.model_id
|
||||
ihm_3dem_restraint.cross_correlation_coefficient
|
||||
|
||||
ihm_predicted_contact_restraint.id
|
||||
ihm_predicted_contact_restraint.group_id
|
||||
ihm_predicted_contact_restraint.entity_id_1
|
||||
ihm_predicted_contact_restraint.asym_id_1
|
||||
ihm_predicted_contact_restraint.seq_id_1
|
||||
ihm_predicted_contact_restraint.comp_id_1
|
||||
ihm_predicted_contact_restraint.rep_atom_1
|
||||
ihm_predicted_contact_restraint.entity_id_2
|
||||
ihm_predicted_contact_restraint.asym_id_2
|
||||
ihm_predicted_contact_restraint.seq_id_2
|
||||
ihm_predicted_contact_restraint.comp_id_2
|
||||
ihm_predicted_contact_restraint.rep_atom_2
|
||||
ihm_predicted_contact_restraint.restraint_type
|
||||
ihm_predicted_contact_restraint.distance_lower_limit
|
||||
ihm_predicted_contact_restraint.distance_upper_limit
|
||||
ihm_predicted_contact_restraint.probability
|
||||
ihm_predicted_contact_restraint.model_granularity
|
||||
ihm_predicted_contact_restraint.dataset_list_id
|
||||
ihm_predicted_contact_restraint.software_id
|
||||
|
||||
ihm_starting_model_details.starting_model_id
|
||||
ihm_starting_model_details.entity_id
|
||||
ihm_starting_model_details.entity_description
|
||||
ihm_starting_model_details.asym_id
|
||||
ihm_starting_model_details.entity_poly_segment_id
|
||||
ihm_starting_model_details.starting_model_source
|
||||
ihm_starting_model_details.starting_model_auth_asym_id
|
||||
ihm_starting_model_details.starting_model_sequence_offset
|
||||
ihm_starting_model_details.dataset_list_id
|
||||
|
||||
ihm_starting_comparative_models.id
|
||||
ihm_starting_comparative_models.starting_model_id
|
||||
ihm_starting_comparative_models.starting_model_auth_asym_id
|
||||
ihm_starting_comparative_models.starting_model_seq_id_begin
|
||||
ihm_starting_comparative_models.starting_model_seq_id_end
|
||||
ihm_starting_comparative_models.template_auth_asym_id
|
||||
ihm_starting_comparative_models.template_seq_id_begin
|
||||
ihm_starting_comparative_models.template_seq_id_end
|
||||
ihm_starting_comparative_models.template_sequence_identity
|
||||
ihm_starting_comparative_models.template_sequence_identity_denominator
|
||||
ihm_starting_comparative_models.template_dataset_list_id
|
||||
ihm_starting_comparative_models.alignment_file_id
|
||||
|
||||
ihm_starting_model_coord.starting_model_id
|
||||
ihm_starting_model_coord.group_PDB
|
||||
ihm_starting_model_coord.id
|
||||
ihm_starting_model_coord.type_symbol
|
||||
ihm_starting_model_coord.atom_id
|
||||
ihm_starting_model_coord.comp_id
|
||||
ihm_starting_model_coord.entity_id
|
||||
ihm_starting_model_coord.asym_id
|
||||
ihm_starting_model_coord.seq_id
|
||||
ihm_starting_model_coord.Cartn_x
|
||||
ihm_starting_model_coord.Cartn_y
|
||||
ihm_starting_model_coord.Cartn_z
|
||||
ihm_starting_model_coord.B_iso_or_equiv
|
||||
ihm_starting_model_coord.ordinal_id
|
||||
|
||||
ihm_starting_model_seq_dif.id
|
||||
ihm_starting_model_seq_dif.entity_id
|
||||
ihm_starting_model_seq_dif.asym_id
|
||||
ihm_starting_model_seq_dif.seq_id
|
||||
ihm_starting_model_seq_dif.comp_id
|
||||
ihm_starting_model_seq_dif.starting_model_id
|
||||
ihm_starting_model_seq_dif.db_asym_id
|
||||
ihm_starting_model_seq_dif.db_seq_id
|
||||
ihm_starting_model_seq_dif.db_comp_id
|
||||
ihm_starting_model_seq_dif.details
|
||||
|
||||
ihm_modeling_protocol.id
|
||||
ihm_modeling_protocol.protocol_name
|
||||
ihm_modeling_protocol.num_steps
|
||||
|
||||
ihm_modeling_protocol_details.id
|
||||
ihm_modeling_protocol_details.protocol_id
|
||||
ihm_modeling_protocol_details.step_id
|
||||
ihm_modeling_protocol_details.struct_assembly_id
|
||||
ihm_modeling_protocol_details.dataset_group_id
|
||||
ihm_modeling_protocol_details.struct_assembly_description
|
||||
ihm_modeling_protocol_details.step_name
|
||||
ihm_modeling_protocol_details.step_method
|
||||
ihm_modeling_protocol_details.num_models_begin
|
||||
ihm_modeling_protocol_details.num_models_end
|
||||
ihm_modeling_protocol_details.multi_scale_flag
|
||||
ihm_modeling_protocol_details.multi_state_flag
|
||||
ihm_modeling_protocol_details.ordered_flag
|
||||
ihm_modeling_protocol_details.software_id
|
||||
ihm_modeling_protocol_details.script_file_id
|
||||
|
||||
ihm_modeling_post_process.id
|
||||
ihm_modeling_post_process.protocol_id
|
||||
ihm_modeling_post_process.analysis_id
|
||||
ihm_modeling_post_process.step_id
|
||||
ihm_modeling_post_process.type
|
||||
ihm_modeling_post_process.feature
|
||||
ihm_modeling_post_process.num_models_begin
|
||||
ihm_modeling_post_process.num_models_end
|
||||
|
||||
ihm_ensemble_info.ensemble_id
|
||||
ihm_ensemble_info.ensemble_name
|
||||
ihm_ensemble_info.post_process_id
|
||||
ihm_ensemble_info.model_group_id
|
||||
ihm_ensemble_info.ensemble_clustering_method
|
||||
ihm_ensemble_info.ensemble_clustering_feature
|
||||
ihm_ensemble_info.num_ensemble_models
|
||||
ihm_ensemble_info.num_ensemble_models_deposited
|
||||
ihm_ensemble_info.ensemble_precision_value
|
||||
ihm_ensemble_info.ensemble_file_id
|
||||
|
||||
ihm_localization_density_files.id
|
||||
ihm_localization_density_files.file_id
|
||||
ihm_localization_density_files.ensemble_id
|
||||
ihm_localization_density_files.entity_id
|
||||
ihm_localization_density_files.asym_id
|
||||
ihm_localization_density_files.entity_poly_segment_id
|
||||
|
||||
ihm_model_list.model_id
|
||||
ihm_model_list.model_name
|
||||
ihm_model_list.assembly_id
|
||||
ihm_model_list.protocol_id
|
||||
ihm_model_list.representation_id
|
||||
|
||||
ihm_model_group.id
|
||||
ihm_model_group.name
|
||||
ihm_model_group.details
|
||||
|
||||
ihm_model_group_link.group_id
|
||||
ihm_model_group_link.model_id
|
||||
|
||||
ihm_model_representative.id
|
||||
ihm_model_representative.model_group_id
|
||||
ihm_model_representative.model_id
|
||||
ihm_model_representative.selection_criteria
|
||||
|
||||
ihm_sphere_obj_site.id
|
||||
ihm_sphere_obj_site.entity_id
|
||||
ihm_sphere_obj_site.seq_id_begin
|
||||
ihm_sphere_obj_site.seq_id_end
|
||||
ihm_sphere_obj_site.asym_id
|
||||
ihm_sphere_obj_site.Cartn_x
|
||||
ihm_sphere_obj_site.Cartn_y
|
||||
ihm_sphere_obj_site.Cartn_z
|
||||
ihm_sphere_obj_site.object_radius
|
||||
ihm_sphere_obj_site.rmsf
|
||||
ihm_sphere_obj_site.model_id
|
||||
|
||||
ihm_gaussian_obj_site.id
|
||||
ihm_gaussian_obj_site.entity_id
|
||||
ihm_gaussian_obj_site.seq_id_begin
|
||||
ihm_gaussian_obj_site.seq_id_end
|
||||
ihm_gaussian_obj_site.asym_id
|
||||
ihm_gaussian_obj_site.mean_Cartn_x
|
||||
ihm_gaussian_obj_site.mean_Cartn_y
|
||||
ihm_gaussian_obj_site.mean_Cartn_z
|
||||
ihm_gaussian_obj_site.weight
|
||||
ihm_gaussian_obj_site.covariance_matrix
|
||||
ihm_gaussian_obj_site.model_id
|
||||
|
||||
ihm_gaussian_obj_ensemble.id
|
||||
ihm_gaussian_obj_ensemble.entity_id
|
||||
ihm_gaussian_obj_ensemble.seq_id_begin
|
||||
ihm_gaussian_obj_ensemble.seq_id_end
|
||||
ihm_gaussian_obj_ensemble.asym_id
|
||||
ihm_gaussian_obj_ensemble.mean_Cartn_x
|
||||
ihm_gaussian_obj_ensemble.mean_Cartn_y
|
||||
ihm_gaussian_obj_ensemble.mean_Cartn_z
|
||||
ihm_gaussian_obj_ensemble.weight
|
||||
ihm_gaussian_obj_ensemble.covariance_matrix
|
||||
ihm_gaussian_obj_ensemble.ensemble_id
|
||||
|
||||
ihm_multi_state_modeling.state_id
|
||||
ihm_multi_state_modeling.state_group_id
|
||||
ihm_multi_state_modeling.population_fraction
|
||||
ihm_multi_state_modeling.population_fraction_sd
|
||||
ihm_multi_state_modeling.state_type
|
||||
ihm_multi_state_modeling.state_name
|
||||
ihm_multi_state_modeling.experiment_type
|
||||
ihm_multi_state_modeling.details
|
||||
|
76
data/cif-field-names/mmtf-filter.csv
Normal file
76
data/cif-field-names/mmtf-filter.csv
Normal file
@@ -0,0 +1,76 @@
|
||||
cell.length_a
|
||||
cell.length_b
|
||||
cell.length_c
|
||||
cell.angle_alpha
|
||||
cell.angle_beta
|
||||
cell.angle_gamma
|
||||
|
||||
symmetry.space_group_name_H-M
|
||||
|
||||
entry.id
|
||||
|
||||
struct.title
|
||||
|
||||
pdbx_database_status.recvd_initial_deposition_date
|
||||
|
||||
pdbx_audit_revision_history.revision_date
|
||||
|
||||
struct_ncs_oper
|
||||
|
||||
pdbx_struct_assembly_gen
|
||||
|
||||
pdbx_struct_oper_list
|
||||
|
||||
entity.id
|
||||
entity.type
|
||||
entity.pdbx_description
|
||||
|
||||
entity_poly.entity_id
|
||||
entity_poly.pdbx_seq_one_letter_code
|
||||
entity_poly.pdbx_strand_id
|
||||
|
||||
exptl.method
|
||||
|
||||
refine.ls_d_res_low
|
||||
refine.ls_R_factor_R_free
|
||||
refine.ls_R_factor_R_work
|
||||
|
||||
atom_site.pdbx_formal_charge
|
||||
atom_site.label_atom_id
|
||||
atom_site.type_symbol
|
||||
|
||||
chem_comp.id
|
||||
chem_comp.type
|
||||
chem_comp.name
|
||||
|
||||
chem_comp_bond
|
||||
|
||||
atom_site.Cartn_x
|
||||
atom_site.Cartn_y
|
||||
atom_site.Cartn_z
|
||||
atom_site.B_iso_or_equiv
|
||||
atom_site.id
|
||||
atom_site.label_alt_id
|
||||
atom_site.occupancy
|
||||
atom_site.label_seq_id
|
||||
atom_site.label_comp_id
|
||||
|
||||
struct_sheet_range.id
|
||||
struct_sheet_range.beg_label_asym_id
|
||||
struct_sheet_range.beg_label_seq_id
|
||||
struct_sheet_range.pdbx_beg_PDB_ins_code
|
||||
struct_sheet_range.end_label_asym_id
|
||||
struct_sheet_range.end_label_seq_id
|
||||
struct_sheet_range.pdbx_end_PDB_ins_code
|
||||
struct_conf.conf_type_id
|
||||
struct_conf.id
|
||||
struct_conf.beg_label_asym_id
|
||||
struct_conf.beg_label_seq_id
|
||||
struct_conf.pdbx_beg_PDB_ins_code
|
||||
struct_conf.end_label_asym_id
|
||||
struct_conf.end_label_seq_id
|
||||
struct_conf.pdbx_end_PDB_ins_code
|
||||
|
||||
atom_site.pdbx_PDB_ins_code
|
||||
atom_site.label_asym_id
|
||||
atom_site.auth_asym_id
|
||||
|
@@ -6,133 +6,64 @@ Model Server is a tool for preprocessing and querying macromolecular structure d
|
||||
Installing and Running
|
||||
=====================
|
||||
|
||||
Getting the code (use node 8+):
|
||||
Requires nodejs 8+.
|
||||
|
||||
## From GitHub
|
||||
|
||||
```
|
||||
git clone https://github.com/molstar/molstar
|
||||
npm install
|
||||
```
|
||||
|
||||
Customize configuration at ``src/server/model/config.ts`` to point to your data and which custom properties to include (see the [Custom Properties](#custom-properties) section). Alternatively, the config can be edited in the compiled version in ``build/node_modules/servers/model/config.js``.
|
||||
|
||||
Afterwards, build the project:
|
||||
Afterwards, build the project source:
|
||||
|
||||
```
|
||||
npm run build
|
||||
npm run build-tsc
|
||||
```
|
||||
|
||||
(or run watch mode for automatic rebuilds: ``npm run watch``)
|
||||
and run the server by
|
||||
|
||||
Running the server locally for testing:
|
||||
```
|
||||
npm run model-server
|
||||
```
|
||||
or
|
||||
```
|
||||
node build/node_modules/servers/model/server
|
||||
node lib/servers/model/server/server
|
||||
```
|
||||
|
||||
In production it is a good idea to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
## From NPM
|
||||
|
||||
```
|
||||
npm install --production molstar
|
||||
./model-server
|
||||
```
|
||||
|
||||
(or ``node node_modules\.bin\model-server`` in Windows).
|
||||
|
||||
The NPM package contains all the tools mentioned here as "binaries":
|
||||
|
||||
- ``model-server``
|
||||
- ``model-server-query``
|
||||
- ``model-server-preprocess``
|
||||
|
||||
|
||||
## Memory issues
|
||||
### Production use
|
||||
|
||||
In production it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
|
||||
|
||||
### Memory issues
|
||||
|
||||
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
|
||||
|
||||
Preprocessor
|
||||
============
|
||||
## Preprocessor
|
||||
|
||||
The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. See the [Custom Properties](#custom-properties) section for providing custom properties.
|
||||
The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. ``node lib/servers/servers/model/preprocess`` or ``model-server-preprocess`` binary from the NPM package.
|
||||
|
||||
## Usage
|
||||
|
||||
The app works in two modes: single files and folders.
|
||||
## Local Mode
|
||||
|
||||
Single files:
|
||||
|
||||
```
|
||||
node build\node_modules\servers\model\preprocess -i input.cif [-oc output.cif] [-ob output.bcif] [--cfg config.json]
|
||||
```
|
||||
|
||||
Folder:
|
||||
```
|
||||
node build\node_modules\servers\model\preprocess -fin input_folder [-foc output_cif_folder] [-fob output_bcif_folder] [--cfg config.json]
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
The config speficies the maximum number of processes to use (in case of folder processing) and defines sources and parameters for custom properties.
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"numProcesses": 4,
|
||||
"customProperties": {
|
||||
"sources": [
|
||||
"./properties/pdbe"
|
||||
],
|
||||
"params": {
|
||||
"PDBe": {
|
||||
"UseFileSource": false,
|
||||
"API": {
|
||||
"residuewise_outlier_summary": "https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry",
|
||||
"preferred_assembly": "https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary",
|
||||
"struct_ref_domain": "https://www.ebi.ac.uk/pdbe/api/mappings/sequence_domains"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
The server can be run in local/file based mode using ``node lib/servers/servers/model/query`` (``model-server-query`` binary from the NPM package).
|
||||
|
||||
Custom Properties
|
||||
=================
|
||||
|
||||
It is possible to provide property descriptors that transform data to internal representation and define how it should be exported into one or mode CIF categories. Examples of this are located in the ``mol-model-props`` module and are linked to the server in the config and ``servers/model/properties``.
|
||||
This feature is still in development.
|
||||
|
||||
Local Mode
|
||||
==========
|
||||
|
||||
The server can be run in local/file based mode:
|
||||
|
||||
```
|
||||
node build/node_modules/servers/model/server jobs.json
|
||||
```
|
||||
|
||||
where ``jobs.json`` is an array of
|
||||
|
||||
```ts
|
||||
type LocalInput = {
|
||||
input: string,
|
||||
output: string,
|
||||
query: QueryName,
|
||||
modelNums?: number[],
|
||||
params?: any,
|
||||
binary?: boolean
|
||||
}[]
|
||||
```
|
||||
|
||||
For example
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"input": "c:/test/quick/1tqn.cif",
|
||||
"output": "c:/test/quick/localapi/1tqn_full.cif",
|
||||
"query": "full"
|
||||
},
|
||||
{
|
||||
"input": "c:/test/quick/1tqn.cif",
|
||||
"output": "c:/test/quick/localapi/1tqn_full.bcif",
|
||||
"query": "full",
|
||||
"params": {}
|
||||
},
|
||||
{
|
||||
"input": "c:/test/quick/1cbs_updated.cif",
|
||||
"output": "c:/test/quick/localapi/1cbs_ligint.cif",
|
||||
"query": "residueInteraction",
|
||||
"params": {
|
||||
"atom_site": { "label_comp_id": "REA" }
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
It is possible to provide property descriptors that transform data to internal representation and define how it should be exported into one or mode CIF categories. Examples of this are located in the ``mol-model-props`` module and are linked to the server in the config and ``servers/model/properties``.
|
||||
@@ -7,107 +7,64 @@ It uses the text based CIF and BinaryCIF formats to deliver the data to the clie
|
||||
|
||||
For quick info about the benefits of using the server, check out the [examples](examples.md).
|
||||
|
||||
Installing the Server
|
||||
Installing and Running
|
||||
=====================
|
||||
|
||||
- Install [Node.js](https://nodejs.org/en/) (tested on Node 6.* and 7.*; x64 version is strongly preferred).
|
||||
- Get the code.
|
||||
- Prepare the data.
|
||||
- Run the server.
|
||||
Requires nodejs 8+.
|
||||
|
||||
Preparing the Data
|
||||
------------------
|
||||
## From GitHub
|
||||
|
||||
```
|
||||
git clone https://github.com/molstar/molstar
|
||||
npm install
|
||||
```
|
||||
|
||||
Afterwards, build the project source:
|
||||
|
||||
```
|
||||
npm run build-tsc
|
||||
```
|
||||
|
||||
and run the server by
|
||||
|
||||
```
|
||||
node lib/servers/servers/volume/server
|
||||
```
|
||||
|
||||
## From NPM
|
||||
|
||||
```
|
||||
npm install --production molstar
|
||||
./volume-server
|
||||
```
|
||||
|
||||
(or ``node node_modules\.bin\volume-server`` in Windows).
|
||||
|
||||
The NPM package contains all the tools mentioned here as "binaries":
|
||||
|
||||
- ``volume-server``
|
||||
- ``volume-server-pack``
|
||||
- ``volume-server-query``
|
||||
|
||||
|
||||
### Production use
|
||||
|
||||
In production it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
|
||||
|
||||
### Memory issues
|
||||
|
||||
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
|
||||
|
||||
|
||||
## Preparing the Data
|
||||
|
||||
For the server to work, CCP4/MAP (models 0, 1, 2 are supported) input data need to be converted into a custom block format.
|
||||
To achieve this, use the ``pack`` application.
|
||||
To achieve this, use the ``pack`` application (``node lib/servers/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
|
||||
|
||||
- To prepare data from x-ray based methods, use:
|
||||
## Local Mode
|
||||
|
||||
```
|
||||
node pack -xray main.ccp4 diff.ccp4 out.mdb
|
||||
```
|
||||
|
||||
- For EM data, use:
|
||||
|
||||
```
|
||||
node pack -em em.map out.mdb
|
||||
```
|
||||
|
||||
Running the Server
|
||||
------------------
|
||||
|
||||
- Install production dependencies:
|
||||
|
||||
```
|
||||
npm install --only=production
|
||||
```
|
||||
|
||||
- Update ``server-config.js`` to link to your data and optionally tweak the other parameters.
|
||||
|
||||
- Run it:
|
||||
|
||||
```
|
||||
node server
|
||||
```
|
||||
|
||||
In production it is a good idea to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
|
||||
### Local Mode
|
||||
|
||||
The program ``local`` in the build folder can be used to query the data without running a http server.
|
||||
|
||||
- ``node local`` prints the program usage.
|
||||
- ``node local jobs.json`` takes a list of jobs to execute in JSON format. A job entry is defined by this interface:
|
||||
|
||||
```TypeScript
|
||||
interface JobEntry {
|
||||
source: {
|
||||
filename: string,
|
||||
name: string,
|
||||
id: string
|
||||
},
|
||||
query: {
|
||||
kind: 'box' | 'cell',
|
||||
space?: 'fractional' | 'cartesian',
|
||||
bottomLeft?: number[],
|
||||
topRight?: number[],
|
||||
}
|
||||
params: {
|
||||
/** Determines the detail level as specified in server-config */
|
||||
detail?: number,
|
||||
/**
|
||||
* Determines the sampling level:
|
||||
* 1: Original data
|
||||
* 2: Downsampled by factor 1/2
|
||||
* ...
|
||||
* N: downsampled 1/2^(N-1)
|
||||
*/
|
||||
forcedSamplingLevel?: number,
|
||||
asBinary: boolean,
|
||||
},
|
||||
outputFolder: string
|
||||
}
|
||||
```
|
||||
|
||||
Example ``jobs.json`` file content:
|
||||
|
||||
```TypeScript
|
||||
[{
|
||||
source: {
|
||||
filename: `g:/test/mdb/emd-8116.mdb`,
|
||||
name: 'em',
|
||||
id: '8116',
|
||||
},
|
||||
query: {
|
||||
kind: 'cell'
|
||||
},
|
||||
params: {
|
||||
detail: 4,
|
||||
asBinary: true
|
||||
},
|
||||
outputFolder: 'g:/test/local-test'
|
||||
}]
|
||||
```
|
||||
The program ``lib/servers/servers/volume/pack`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
|
||||
|
||||
## Navigating the Source Code
|
||||
|
||||
@@ -122,8 +79,8 @@ The source code is split into 2 mains parts: ``pack`` and ``server``:
|
||||
Consuming the Data
|
||||
==================
|
||||
|
||||
The data can be consumed in any (modern) browser using the [CIFTools.js library](https://github.com/dsehnal/CIFTools.js) (or any other piece of code that can read text or binary CIF).
|
||||
The data can be consumed in any (modern) browser using the [ciftools library](https://github.com/molstar/ciftools) (or any other piece of code that can read text or binary CIF).
|
||||
|
||||
The [Data Format](DataFormat.md) document gives a detailed description of the server response format.
|
||||
|
||||
As a reference/example of the server usage, please see the implementation in [LiteMol](https://github.com/dsehnal/LiteMol) ([CIF.ts + Data.ts](https://github.com/dsehnal/LiteMol/tree/master/src/lib/Core/Formats/Density), [UI](https://github.com/dsehnal/LiteMol/tree/master/src/Viewer/Extensions/DensityStreaming)) or in Mol*.
|
||||
As a reference/example of the server usage is available in Mol* ``mol-plugin`` module.
|
||||
10752
package-lock.json
generated
10752
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
111
package.json
111
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.5.0-dev.3",
|
||||
"version": "0.7.0-dev.11",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -11,21 +11,29 @@
|
||||
"url": "https://github.com/molstar/molstar/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"lint": "eslint ./**/*.{ts,tsx}",
|
||||
"lint-fix": "eslint ./**/*.{ts,tsx} --fix",
|
||||
"test": "npm run lint && jest",
|
||||
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
|
||||
"build-tsc": "tsc",
|
||||
"build-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/",
|
||||
"build-webpack": "webpack --mode production",
|
||||
"watch": "concurrently --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack\"",
|
||||
"watch-tsc": "tsc -watch",
|
||||
"watch-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/ --watch",
|
||||
"build-viewer": "npm run build-tsc && npm run build-extra && npm run build-webpack-viewer",
|
||||
"build-tsc": "tsc --incremental && tsc --build tsconfig.servers.json --incremental",
|
||||
"build-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/",
|
||||
"build-webpack": "webpack --mode production --config ./webpack.config.production.js",
|
||||
"build-webpack-viewer": "webpack --mode production --config ./webpack.config.viewer.js",
|
||||
"watch": "concurrently -c \"green,green,gray,gray\" --names \"tsc,srv,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-servers\" \"npm:watch-extra\" \"npm:watch-webpack\"",
|
||||
"watch-viewer": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer\"",
|
||||
"watch-viewer-debug": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer-debug\"",
|
||||
"watch-tsc": "tsc --watch --incremental",
|
||||
"watch-servers": "tsc --build tsconfig.servers.json --watch --incremental",
|
||||
"watch-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/ --watch",
|
||||
"watch-webpack": "webpack -w --mode development --display minimal",
|
||||
"watch-webpack-viewer": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.js",
|
||||
"watch-webpack-viewer-debug": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.debug.js",
|
||||
"serve": "http-server -p 1338",
|
||||
"model-server": "node lib/servers/model/server.js",
|
||||
"model-server-watch": "nodemon --watch lib lib/servers/model/server.js",
|
||||
"volume-server": "node lib/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
|
||||
"plugin-state": "node lib/servers/plugin-state/index.js",
|
||||
"model-server": "node lib/servers/servers/model/server.js",
|
||||
"model-server-watch": "nodemon --watch lib lib/servers/servers/model/server.js",
|
||||
"volume-server-test": "node lib/servers/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
|
||||
"plugin-state": "node lib/servers/servers/plugin-state/index.js",
|
||||
"preversion": "npm run test",
|
||||
"postversion": "git push && git push --tags",
|
||||
"prepublishOnly": "npm run test && npm run build"
|
||||
@@ -33,6 +41,16 @@
|
||||
"files": [
|
||||
"lib/"
|
||||
],
|
||||
"bin": {
|
||||
"cif2bcif": "lib/apps/cif2bcif/index.js",
|
||||
"cifschema": "lib/apps/cifschema/index.js",
|
||||
"model-server": "lib/servers/servers/model/server.js",
|
||||
"model-server-query": "lib/servers/servers/model/query.js",
|
||||
"model-server-preprocess": "lib/servers/servers/model/preprocess.js",
|
||||
"volume-server": "lib/servers/servers/volume/server.js",
|
||||
"volume-server-query": "lib/servers/servers/volume/query.js",
|
||||
"volume-server-pack": "lib/servers/servers/volume/pack.js"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ignoreRoot": [
|
||||
"./node_modules",
|
||||
@@ -64,65 +82,68 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^1.12.2",
|
||||
"@graphql-codegen/cli": "^1.12.2",
|
||||
"@graphql-codegen/time": "^1.12.2",
|
||||
"@graphql-codegen/typescript": "^1.12.2",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.12.2",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.12.2",
|
||||
"@graphql-codegen/typescript-operations": "^1.12.2",
|
||||
"@graphql-codegen/add": "^1.13.3",
|
||||
"@graphql-codegen/cli": "^1.13.3",
|
||||
"@graphql-codegen/time": "^1.13.3",
|
||||
"@graphql-codegen/typescript": "^1.13.3",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.13.3",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.13.3",
|
||||
"@graphql-codegen/typescript-operations": "^1.13.3",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@typescript-eslint/eslint-plugin": "^2.19.2",
|
||||
"@typescript-eslint/eslint-plugin-tslint": "^2.19.2",
|
||||
"@typescript-eslint/parser": "^2.19.2",
|
||||
"@typescript-eslint/eslint-plugin": "^2.29.0",
|
||||
"@typescript-eslint/parser": "^2.29.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"circular-dependency-plugin": "^5.2.0",
|
||||
"concurrently": "^5.1.0",
|
||||
"cpx2": "^2.0.0",
|
||||
"css-loader": "^3.4.2",
|
||||
"css-loader": "^3.5.3",
|
||||
"eslint": "^6.8.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^5.0.2",
|
||||
"fs-extra": "^8.1.0",
|
||||
"file-loader": "^6.0.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"graphql": "^15.0.0",
|
||||
"http-server": "^0.12.1",
|
||||
"jest": "^25.1.0",
|
||||
"jest": "^25.4.0",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"pascal-case": "^3.1.1",
|
||||
"raw-loader": "^4.0.0",
|
||||
"node-sass": "^4.14.0",
|
||||
"raw-loader": "^4.0.1",
|
||||
"resolve-url-loader": "^3.1.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"simple-git": "^1.131.0",
|
||||
"style-loader": "^1.1.3",
|
||||
"ts-jest": "^25.2.0",
|
||||
"typescript": "^3.7.5",
|
||||
"webpack": "^4.41.5",
|
||||
"webpack-cli": "^3.3.10"
|
||||
"simple-git": "^1.132.0",
|
||||
"style-loader": "^1.2.0",
|
||||
"ts-jest": "^25.4.0",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-version-file-plugin": "^0.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.9.11",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@types/argparse": "^1.0.38",
|
||||
"@types/benchmark": "^1.0.31",
|
||||
"@types/compression": "1.7.0",
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/jest": "^25.1.2",
|
||||
"@types/node": "^13.7.0",
|
||||
"@types/node-fetch": "^2.5.4",
|
||||
"@types/react": "^16.9.19",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/node": "^13.13.2",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/react": "^16.9.34",
|
||||
"@types/react-dom": "^16.9.6",
|
||||
"@types/swagger-ui-dist": "3.0.5",
|
||||
"argparse": "^1.0.10",
|
||||
"body-parser": "^1.19.0",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"graphql": "^14.6.0",
|
||||
"immer": "^6.0.3",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"rxjs": "^6.5.4",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"rxjs": "^6.5.5",
|
||||
"swagger-ui-dist": "^3.25.0",
|
||||
"tslib": "^1.11.1",
|
||||
"util.promisify": "^1.0.1",
|
||||
"xhr2": "^0.2.0"
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginUIComponent } from '../../mol-plugin-ui/base';
|
||||
import * as React from 'react';
|
||||
import { TransformUpdaterControl } from '../../mol-plugin-ui/state/update-transform';
|
||||
|
||||
export class BasicWrapperControls extends PluginUIComponent {
|
||||
|
||||
render() {
|
||||
return <div style={{ overflowY: 'auto', display: 'block', height: '100%' }}>
|
||||
<TransformUpdaterControl nodeRef='asm' />
|
||||
<TransformUpdaterControl nodeRef='seq-visual' header={{ name: 'Sequence Visual' }} />
|
||||
<TransformUpdaterControl nodeRef='het-visual' header={{ name: 'HET Visual' }} />
|
||||
<TransformUpdaterControl nodeRef='water-visual' header={{ name: 'Water Visual' }} initiallyCollapsed={true} />
|
||||
<TransformUpdaterControl nodeRef='ihm-visual' header={{ name: 'I/HM Visual' }} initiallyCollapsed={true} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomToastMessage extends PluginUIComponent {
|
||||
render() {
|
||||
return <>
|
||||
Custom <i>Toast</i> content. No timeout.
|
||||
</>;
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin/state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin/state/transforms';
|
||||
import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transforms/representation';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { StateBuilder } from '../../mol-state';
|
||||
import Expression from '../../mol-script/language/expression';
|
||||
import { BuiltInColorThemeName } from '../../mol-theme/color';
|
||||
type SupportedFormats = 'cif' | 'pdb'
|
||||
|
||||
export namespace StateHelper {
|
||||
export function download(b: StateBuilder.To<PSO.Root>, url: string, ref?: string) {
|
||||
return b.apply(StateTransforms.Data.Download, { url, isBinary: false }, { ref });
|
||||
}
|
||||
|
||||
export function getModel(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, modelIndex = 0) {
|
||||
const parsed = format === 'cif'
|
||||
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
|
||||
return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex });
|
||||
}
|
||||
|
||||
export function structure(b: StateBuilder.To<PSO.Molecule.Model>) {
|
||||
return b.apply(StateTransforms.Model.StructureFromModel, void 0, { tags: 'structure' })
|
||||
};
|
||||
|
||||
export function selectChain(b: StateBuilder.To<PSO.Molecule.Structure>, auth_asym_id: string) {
|
||||
const expression = MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), auth_asym_id])
|
||||
})
|
||||
return b.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression, label: `Chain ${auth_asym_id}` });
|
||||
}
|
||||
|
||||
export function select(b: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression) {
|
||||
return b.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression });
|
||||
}
|
||||
|
||||
export function selectSurroundingsOfFirstResidue(b: StateBuilder.To<PSO.Molecule.Structure>, comp_id: string, radius: number) {
|
||||
const expression = MS.struct.modifier.includeSurroundings({
|
||||
0: MS.struct.filter.first([
|
||||
MS.struct.generator.atomGroups({
|
||||
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), comp_id]),
|
||||
'group-by': MS.struct.atomProperty.macromolecular.residueKey()
|
||||
})
|
||||
]),
|
||||
radius
|
||||
})
|
||||
return b.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression, label: `Surr. ${comp_id} (${radius} ang)` });
|
||||
}
|
||||
|
||||
export function identityTransform(b: StateBuilder.To<PSO.Molecule.Structure>, m: Mat4) {
|
||||
return b.apply(StateTransforms.Model.TransformStructureConformation,
|
||||
{ axis: Vec3.create(1, 0, 0), angle: 0, translation: Vec3.zero() },
|
||||
{ tags: 'transform' });
|
||||
}
|
||||
|
||||
export function transform(b: StateBuilder.To<PSO.Molecule.Structure>, matrix: Mat4) {
|
||||
return b.apply(StateTransforms.Model.TransformStructureConformationByMatrix, { matrix }, { tags: 'transform' });
|
||||
}
|
||||
|
||||
export function assemble(b: StateBuilder.To<PSO.Molecule.Model>, id?: string) {
|
||||
return b.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: id || 'deposited' }, { tags: 'asm' })
|
||||
}
|
||||
|
||||
export function visual(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'cartoon'), { tags: 'seq-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick'), { tags: 'het-visual' });
|
||||
// visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
|
||||
// .apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
// StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick', { alpha: 0.51 }), { tags: 'water-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
export function ballsAndSticks(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression, coloring?: BuiltInColorThemeName) {
|
||||
visualRoot
|
||||
.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick', void 0, coloring), { tags: 'het-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/command';
|
||||
import { StateTransforms } from '../../mol-plugin/state/transforms';
|
||||
import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transforms/representation';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin/state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin/state/animation/built-in';
|
||||
import { StateBuilder, StateTransform } from '../../mol-state';
|
||||
import { StripedResidues } from './coloring';
|
||||
import { StaticSuperpositionTestData, buildStaticSuperposition, dynamicSuperpositionTest } from './superposition';
|
||||
import { PDBeStructureQualityReport } from '../../mol-plugin/behavior/dynamic/custom-props';
|
||||
import { CustomToastMessage } from './controls';
|
||||
import { EmptyLoci } from '../../mol-model/loci';
|
||||
import { StructureSelection } from '../../mol-model/structure';
|
||||
import { Script } from '../../mol-script/script';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
|
||||
type SupportedFormats = 'cif' | 'pdb'
|
||||
type LoadParams = { url: string, format?: SupportedFormats, assemblyId?: string }
|
||||
|
||||
class BasicWrapper {
|
||||
plugin: PluginContext;
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: {
|
||||
// left: 'none',
|
||||
// right: BasicWrapperControls
|
||||
}
|
||||
},
|
||||
components: {
|
||||
remoteState: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(StripedResidues.propertyProvider.descriptor.name, StripedResidues.colorThemeProvider!);
|
||||
this.plugin.lociLabels.addProvider(StripedResidues.labelProvider!);
|
||||
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
|
||||
}
|
||||
|
||||
private download(b: StateBuilder.To<PSO.Root>, url: string) {
|
||||
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
|
||||
}
|
||||
|
||||
private parse(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) {
|
||||
const parsed = format === 'cif'
|
||||
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
|
||||
return parsed
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
|
||||
.apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [StripedResidues.propertyProvider.descriptor.name], properties: {} }, { ref: 'props', state: { isGhost: false } })
|
||||
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' });
|
||||
}
|
||||
|
||||
private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: 'seq' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'cartoon'), { ref: 'seq-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick'), { ref: 'het-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick', { alpha: 0.51 }), { ref: 'water-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'spheres' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'spacefill'), { ref: 'ihm-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = '' }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.dataState;
|
||||
|
||||
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
|
||||
loadType = 'full';
|
||||
} else if (this.loadedParams.url === url) {
|
||||
if (state.select('asm').length > 0) loadType = 'update';
|
||||
}
|
||||
|
||||
let tree: StateBuilder.Root;
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
|
||||
tree = state.build();
|
||||
this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId));
|
||||
} else {
|
||||
tree = state.build();
|
||||
tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
|
||||
this.loadedParams = { url, format, assemblyId };
|
||||
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
}
|
||||
|
||||
setBackground(color: number) {
|
||||
const renderer = this.plugin.canvas3d!.props.renderer;
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
}
|
||||
|
||||
toggleSpin() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
const trackball = this.plugin.canvas3d.props.trackball;
|
||||
const spinning = trackball.spin;
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
}
|
||||
|
||||
animate = {
|
||||
modelIndex: {
|
||||
maxFPS: 8,
|
||||
onceForward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }) },
|
||||
onceBackward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }) },
|
||||
palindrome: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }) },
|
||||
loop: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }) },
|
||||
stop: () => this.plugin.state.animation.stop()
|
||||
}
|
||||
}
|
||||
|
||||
coloring = {
|
||||
applyStripes: async () => {
|
||||
const state = this.plugin.state.dataState;
|
||||
|
||||
const visuals = state.selectQ(q => q.ofTransformer(StateTransforms.Representation.StructureRepresentation3D));
|
||||
const tree = state.build();
|
||||
const colorTheme = { name: StripedResidues.propertyProvider.descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(StripedResidues.propertyProvider.descriptor.name).defaultValues };
|
||||
|
||||
for (const v of visuals) {
|
||||
tree.to(v).update(old => ({ ...old, colorTheme }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
}
|
||||
}
|
||||
|
||||
interactivity = {
|
||||
highlightOn: () => {
|
||||
const seq_id = 7;
|
||||
const data = (this.plugin.state.dataState.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
const sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
|
||||
'residue-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), seq_id]),
|
||||
'group-by': Q.struct.atomProperty.macromolecular.residueKey()
|
||||
}), data);
|
||||
const loci = StructureSelection.toLociWithSourceUnits(sel);
|
||||
this.plugin.interactivity.lociHighlights.highlightOnly({ loci });
|
||||
},
|
||||
clearHighlight: () => {
|
||||
this.plugin.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
|
||||
}
|
||||
}
|
||||
|
||||
tests = {
|
||||
staticSuperposition: async () => {
|
||||
const state = this.plugin.state.dataState;
|
||||
const tree = buildStaticSuperposition(this.plugin, StaticSuperpositionTestData);
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: StateTransform.RootRef });
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
},
|
||||
dynamicSuperposition: async () => {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.plugin.state.dataState, ref: StateTransform.RootRef });
|
||||
await dynamicSuperpositionTest(this.plugin, ['1tqn', '2hhb', '4hhb'], 'HEM');
|
||||
},
|
||||
toggleValidationTooltip: async () => {
|
||||
const state = this.plugin.state.behaviorState;
|
||||
const tree = state.build().to(PDBeStructureQualityReport.id).update(PDBeStructureQualityReport, p => ({ ...p, showTooltip: !p.showTooltip }));
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
},
|
||||
showToasts: () => {
|
||||
PluginCommands.Toast.Show.dispatch(this.plugin, {
|
||||
title: 'Toast 1',
|
||||
message: 'This is an example text, timeout 3s',
|
||||
key: 'toast-1',
|
||||
timeoutMs: 3000
|
||||
});
|
||||
PluginCommands.Toast.Show.dispatch(this.plugin, {
|
||||
title: 'Toast 2',
|
||||
message: CustomToastMessage,
|
||||
key: 'toast-2'
|
||||
});
|
||||
},
|
||||
hideToasts: () => {
|
||||
PluginCommands.Toast.Hide.dispatch(this.plugin, { key: 'toast-1' });
|
||||
PluginCommands.Toast.Hide.dispatch(this.plugin, { key: 'toast-2' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).BasicMolStarWrapper = new BasicWrapper();
|
||||
@@ -1,108 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
// TODO: move to an "example"
|
||||
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { StateHelper } from './helpers';
|
||||
import { PluginCommands } from '../../mol-plugin/command';
|
||||
import { StateSelection, StateBuilder } from '../../mol-state';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin/state/objects';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
import { StructureSelection, QueryContext } from '../../mol-model/structure';
|
||||
import { superposeStructures } from '../../mol-model/structure/structure/util/superposition';
|
||||
import Expression from '../../mol-script/language/expression';
|
||||
|
||||
export type SuperpositionTestInput = {
|
||||
pdbId: string,
|
||||
auth_asym_id: string,
|
||||
matrix: Mat4
|
||||
}[];
|
||||
|
||||
// function getAxisAngleTranslation(m: Mat4) {
|
||||
// const translation = Mat4.getTranslation(Vec3.zero(), m);
|
||||
// const axis = Vec3.zero();
|
||||
// const angle = 180 / Math.PI * Quat.getAxisAngle(axis, Mat4.getRotation(Quat.zero(), m));
|
||||
// return { translation, axis, angle };
|
||||
// }
|
||||
|
||||
export function buildStaticSuperposition(ctx: PluginContext, src: SuperpositionTestInput) {
|
||||
const b = ctx.state.dataState.build().toRoot();
|
||||
for (const s of src) {
|
||||
StateHelper.visual(ctx,
|
||||
StateHelper.transform(
|
||||
StateHelper.selectChain(
|
||||
StateHelper.structure(
|
||||
StateHelper.getModel(StateHelper.download(b, `https://www.ebi.ac.uk/pdbe/static/entry/${s.pdbId}_updated.cif`), 'cif')),
|
||||
s.auth_asym_id
|
||||
),
|
||||
s.matrix
|
||||
)
|
||||
);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
export const StaticSuperpositionTestData: SuperpositionTestInput = [
|
||||
{ pdbId: '1aj5', auth_asym_id: 'A', matrix: Mat4.identity() },
|
||||
{ pdbId: '1df0', auth_asym_id: 'B', matrix: Mat4.ofRows([
|
||||
[0.406, 0.879, 0.248, -200.633],
|
||||
[0.693, -0.473, 0.544, 73.403],
|
||||
[0.596, -0.049, -0.802, -14.209],
|
||||
[0, 0, 0, 1]] )},
|
||||
{ pdbId: '1dvi', auth_asym_id: 'A', matrix: Mat4.ofRows([
|
||||
[-0.053, -0.077, 0.996, -45.633],
|
||||
[-0.312, 0.949, 0.057, -12.255],
|
||||
[-0.949, -0.307, -0.074, 53.562],
|
||||
[0, 0, 0, 1]] )}
|
||||
];
|
||||
|
||||
export async function dynamicSuperpositionTest(ctx: PluginContext, src: string[], comp_id: string) {
|
||||
const state = ctx.state.dataState;
|
||||
|
||||
const structures = state.build().toRoot();
|
||||
for (const s of src) {
|
||||
StateHelper.structure(
|
||||
StateHelper.getModel(StateHelper.download(structures, `https://www.ebi.ac.uk/pdbe/static/entry/${s}_updated.cif`), 'cif'));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(ctx, { state, tree: structures });
|
||||
|
||||
const pivot = MS.struct.filter.first([
|
||||
MS.struct.generator.atomGroups({
|
||||
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), comp_id]),
|
||||
'group-by': MS.struct.atomProperty.macromolecular.residueKey()
|
||||
})
|
||||
]);
|
||||
const rest = MS.struct.modifier.exceptBy({
|
||||
0: MS.struct.generator.all(),
|
||||
by: pivot
|
||||
});
|
||||
|
||||
const query = compile<StructureSelection>(pivot);
|
||||
const xs = state.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure));
|
||||
const selections = xs.map(s => StructureSelection.toLociWithCurrentUnits(query(new QueryContext(s.obj!.data))));
|
||||
|
||||
const transforms = superposeStructures(selections);
|
||||
const visuals = state.build();
|
||||
|
||||
siteVisual(ctx, StateHelper.selectSurroundingsOfFirstResidue(visuals.to(xs[0].transform.ref), 'HEM', 7), pivot, rest);
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
const root = visuals.to(xs[i].transform.ref);
|
||||
siteVisual(ctx,
|
||||
StateHelper.transform(StateHelper.selectSurroundingsOfFirstResidue(root, 'HEM', 7), transforms[i - 1].bTransform),
|
||||
pivot, rest);
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(ctx, { state, tree: visuals });
|
||||
}
|
||||
|
||||
function siteVisual(ctx: PluginContext, b: StateBuilder.To<PSO.Molecule.Structure>, pivot: Expression, rest: Expression) {
|
||||
StateHelper.ballsAndSticks(ctx, b, pivot, 'residue-name');
|
||||
StateHelper.ballsAndSticks(ctx, b, rest, 'uniform');
|
||||
}
|
||||
@@ -4,57 +4,57 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as argparse from 'argparse'
|
||||
import * as util from 'util'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as zlib from 'zlib'
|
||||
import fetch from 'node-fetch'
|
||||
require('util.promisify').shim()
|
||||
const readFile = util.promisify(fs.readFile)
|
||||
const writeFile = util.promisify(fs.writeFile)
|
||||
import * as argparse from 'argparse';
|
||||
import * as util from 'util';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as zlib from 'zlib';
|
||||
import fetch from 'node-fetch';
|
||||
require('util.promisify').shim();
|
||||
const readFile = util.promisify(fs.readFile);
|
||||
const writeFile = util.promisify(fs.writeFile);
|
||||
|
||||
import { Progress } from '../../mol-task'
|
||||
import { Database, Table, DatabaseCollection } from '../../mol-data/db'
|
||||
import { CIF } from '../../mol-io/reader/cif'
|
||||
import { CifWriter } from '../../mol-io/writer/cif'
|
||||
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd'
|
||||
import { SetUtils } from '../../mol-util/set'
|
||||
import { DefaultMap } from '../../mol-util/map'
|
||||
import { Progress } from '../../mol-task';
|
||||
import { Database, Table, DatabaseCollection } from '../../mol-data/db';
|
||||
import { CIF } from '../../mol-io/reader/cif';
|
||||
import { CifWriter } from '../../mol-io/writer/cif';
|
||||
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
|
||||
import { SetUtils } from '../../mol-util/set';
|
||||
import { DefaultMap } from '../../mol-util/map';
|
||||
import { mmCIF_chemCompBond_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
|
||||
export async function ensureAvailable(path: string, url: string) {
|
||||
if (FORCE_DOWNLOAD || !fs.existsSync(path)) {
|
||||
console.log(`downloading ${url}...`)
|
||||
const data = await fetch(url)
|
||||
console.log(`downloading ${url}...`);
|
||||
const data = await fetch(url);
|
||||
if (!fs.existsSync(DATA_DIR)) {
|
||||
fs.mkdirSync(DATA_DIR);
|
||||
}
|
||||
if (url.endsWith('.gz')) {
|
||||
await writeFile(path, zlib.gunzipSync(await data.buffer()))
|
||||
await writeFile(path, zlib.gunzipSync(await data.buffer()));
|
||||
} else {
|
||||
await writeFile(path, await data.text())
|
||||
await writeFile(path, await data.text());
|
||||
}
|
||||
console.log(`done downloading ${url}`)
|
||||
console.log(`done downloading ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureDataAvailable() {
|
||||
await ensureAvailable(CCD_PATH, CCD_URL)
|
||||
await ensureAvailable(PVCD_PATH, PVCD_URL)
|
||||
await ensureAvailable(CCD_PATH, CCD_URL);
|
||||
await ensureAvailable(PVCD_PATH, PVCD_URL);
|
||||
}
|
||||
|
||||
export async function readFileAsCollection<S extends Database.Schema>(path: string, schema: S) {
|
||||
const parsed = await parseCif(await readFile(path, 'utf8'))
|
||||
return CIF.toDatabaseCollection(schema, parsed.result)
|
||||
const parsed = await parseCif(await readFile(path, 'utf8'));
|
||||
return CIF.toDatabaseCollection(schema, parsed.result);
|
||||
}
|
||||
|
||||
export async function readCCD() {
|
||||
return readFileAsCollection(CCD_PATH, CCD_Schema)
|
||||
return readFileAsCollection(CCD_PATH, CCD_Schema);
|
||||
}
|
||||
|
||||
export async function readPVCD() {
|
||||
return readFileAsCollection(PVCD_PATH, CCD_Schema)
|
||||
return readFileAsCollection(PVCD_PATH, CCD_Schema);
|
||||
}
|
||||
|
||||
async function parseCif(data: string | Uint8Array) {
|
||||
@@ -63,12 +63,12 @@ async function parseCif(data: string | Uint8Array) {
|
||||
const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
|
||||
console.timeEnd('parse cif');
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export function getEncodedCif(name: string, database: Database<Database.Schema>, binary = false) {
|
||||
const encoder = CifWriter.createEncoder({ binary, encoderName: 'mol*' });
|
||||
CifWriter.Encoder.writeDatabase(encoder, name, database)
|
||||
CifWriter.Encoder.writeDatabase(encoder, name, database);
|
||||
return encoder.getData();
|
||||
}
|
||||
|
||||
@@ -76,58 +76,58 @@ type CCB = Table<CCD_Schema['chem_comp_bond']>
|
||||
type CCA = Table<CCD_Schema['chem_comp_atom']>
|
||||
|
||||
function ccbKey(compId: string, atomId1: string, atomId2: string) {
|
||||
return atomId1 < atomId2 ? `${compId}:${atomId1}-${atomId2}` : `${compId}:${atomId2}-${atomId1}`
|
||||
return atomId1 < atomId2 ? `${compId}:${atomId1}-${atomId2}` : `${compId}:${atomId2}-${atomId1}`;
|
||||
}
|
||||
|
||||
function addChemCompBondToSet(set: Set<string>, ccb: CCB) {
|
||||
for (let i = 0, il = ccb._rowCount; i < il; ++i) {
|
||||
set.add(ccbKey(ccb.comp_id.value(i), ccb.atom_id_1.value(i), ccb.atom_id_2.value(i)))
|
||||
set.add(ccbKey(ccb.comp_id.value(i), ccb.atom_id_1.value(i), ccb.atom_id_2.value(i)));
|
||||
}
|
||||
return set
|
||||
return set;
|
||||
}
|
||||
|
||||
function addChemCompAtomToSet(set: Set<string>, cca: CCA) {
|
||||
for (let i = 0, il = cca._rowCount; i < il; ++i) {
|
||||
set.add(cca.atom_id.value(i))
|
||||
set.add(cca.atom_id.value(i));
|
||||
}
|
||||
return set
|
||||
return set;
|
||||
}
|
||||
|
||||
function checkAddingBondsFromPVCD(pvcd: DatabaseCollection<CCD_Schema>) {
|
||||
const ccbSetByParent = DefaultMap<string, Set<string>>(() => new Set())
|
||||
const ccbSetByParent = DefaultMap<string, Set<string>>(() => new Set());
|
||||
|
||||
for (const k in pvcd) {
|
||||
const { chem_comp, chem_comp_bond } = pvcd[k]
|
||||
const { chem_comp, chem_comp_bond } = pvcd[k];
|
||||
if (chem_comp_bond._rowCount) {
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0)
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0);
|
||||
if (parentIds.length === 0) {
|
||||
const set = ccbSetByParent.getDefault(chem_comp.id.value(0))
|
||||
addChemCompBondToSet(set, chem_comp_bond)
|
||||
const set = ccbSetByParent.getDefault(chem_comp.id.value(0));
|
||||
addChemCompBondToSet(set, chem_comp_bond);
|
||||
} else {
|
||||
for (let i = 0, il = parentIds.length; i < il; ++i) {
|
||||
const parentId = parentIds[i]
|
||||
const set = ccbSetByParent.getDefault(parentId)
|
||||
addChemCompBondToSet(set, chem_comp_bond)
|
||||
const parentId = parentIds[i];
|
||||
const set = ccbSetByParent.getDefault(parentId);
|
||||
addChemCompBondToSet(set, chem_comp_bond);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const k in pvcd) {
|
||||
const { chem_comp, chem_comp_atom, chem_comp_bond } = pvcd[k]
|
||||
const { chem_comp, chem_comp_atom, chem_comp_bond } = pvcd[k];
|
||||
if (chem_comp_bond._rowCount) {
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0)
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0);
|
||||
if (parentIds.length > 0) {
|
||||
for (let i = 0, il = parentIds.length; i < il; ++i) {
|
||||
const entryBonds = addChemCompBondToSet(new Set<string>(), chem_comp_bond)
|
||||
const entryAtoms = addChemCompAtomToSet(new Set<string>(), chem_comp_atom)
|
||||
const extraBonds = SetUtils.difference(ccbSetByParent.get(parentIds[i])!, entryBonds)
|
||||
const entryBonds = addChemCompBondToSet(new Set<string>(), chem_comp_bond);
|
||||
const entryAtoms = addChemCompAtomToSet(new Set<string>(), chem_comp_atom);
|
||||
const extraBonds = SetUtils.difference(ccbSetByParent.get(parentIds[i])!, entryBonds);
|
||||
extraBonds.forEach(bk => {
|
||||
const [a1, a2] = bk.split('|')
|
||||
const [a1, a2] = bk.split('|');
|
||||
if (entryAtoms.has(a1) && entryAtoms.has(a2)) {
|
||||
console.error(`Adding all PVCD bonds would wrongly add bond ${bk} for ${k}`)
|
||||
console.error(`Adding all PVCD bonds would wrongly add bond ${bk} for ${k}`);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,51 +135,51 @@ function checkAddingBondsFromPVCD(pvcd: DatabaseCollection<CCD_Schema>) {
|
||||
}
|
||||
|
||||
async function createBonds() {
|
||||
await ensureDataAvailable()
|
||||
const ccd = await readCCD()
|
||||
const pvcd = await readPVCD()
|
||||
await ensureDataAvailable();
|
||||
const ccd = await readCCD();
|
||||
const pvcd = await readPVCD();
|
||||
|
||||
const ccbSet = new Set<string>()
|
||||
const ccbSet = new Set<string>();
|
||||
|
||||
const comp_id: string[] = []
|
||||
const atom_id_1: string[] = []
|
||||
const atom_id_2: string[] = []
|
||||
const value_order: string[] = []
|
||||
const pdbx_aromatic_flag: string[] = []
|
||||
const pdbx_stereo_config: string[] = []
|
||||
const molstar_protonation_variant: string[] = []
|
||||
const comp_id: string[] = [];
|
||||
const atom_id_1: string[] = [];
|
||||
const atom_id_2: string[] = [];
|
||||
const value_order: typeof mmCIF_chemCompBond_schema['value_order']['T'][] = [];
|
||||
const pdbx_aromatic_flag: typeof mmCIF_chemCompBond_schema['pdbx_aromatic_flag']['T'][] = [];
|
||||
const pdbx_stereo_config: typeof mmCIF_chemCompBond_schema['pdbx_stereo_config']['T'][] = [];
|
||||
const molstar_protonation_variant: string[] = [];
|
||||
|
||||
function addBonds(compId: string, ccb: CCB, protonationVariant: boolean) {
|
||||
for (let i = 0, il = ccb._rowCount; i < il; ++i) {
|
||||
const atomId1 = ccb.atom_id_1.value(i)
|
||||
const atomId2 = ccb.atom_id_2.value(i)
|
||||
const k = ccbKey(compId, atomId1, atomId2)
|
||||
const atomId1 = ccb.atom_id_1.value(i);
|
||||
const atomId2 = ccb.atom_id_2.value(i);
|
||||
const k = ccbKey(compId, atomId1, atomId2);
|
||||
if (!ccbSet.has(k)) {
|
||||
atom_id_1.push(atomId1)
|
||||
atom_id_2.push(atomId2)
|
||||
comp_id.push(compId)
|
||||
value_order.push(ccb.value_order.value(i))
|
||||
pdbx_aromatic_flag.push(ccb.pdbx_aromatic_flag.value(i))
|
||||
pdbx_stereo_config.push(ccb.pdbx_stereo_config.value(i))
|
||||
molstar_protonation_variant.push(protonationVariant ? 'Y' : 'N')
|
||||
ccbSet.add(k)
|
||||
atom_id_1.push(atomId1);
|
||||
atom_id_2.push(atomId2);
|
||||
comp_id.push(compId);
|
||||
value_order.push(ccb.value_order.value(i));
|
||||
pdbx_aromatic_flag.push(ccb.pdbx_aromatic_flag.value(i));
|
||||
pdbx_stereo_config.push(ccb.pdbx_stereo_config.value(i));
|
||||
molstar_protonation_variant.push(protonationVariant ? 'Y' : 'N');
|
||||
ccbSet.add(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check adding bonds from PVCD
|
||||
checkAddingBondsFromPVCD(pvcd)
|
||||
checkAddingBondsFromPVCD(pvcd);
|
||||
|
||||
// add bonds from PVCD
|
||||
for (const k in pvcd) {
|
||||
const { chem_comp, chem_comp_bond } = pvcd[k]
|
||||
const { chem_comp, chem_comp_bond } = pvcd[k];
|
||||
if (chem_comp_bond._rowCount) {
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0)
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0);
|
||||
if (parentIds.length === 0) {
|
||||
addBonds(chem_comp.id.value(0), chem_comp_bond, false)
|
||||
addBonds(chem_comp.id.value(0), chem_comp_bond, false);
|
||||
} else {
|
||||
for (let i = 0, il = parentIds.length; i < il; ++i) {
|
||||
addBonds(parentIds[i], chem_comp_bond, true)
|
||||
addBonds(parentIds[i], chem_comp_bond, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,43 +187,43 @@ async function createBonds() {
|
||||
|
||||
// add bonds from CCD
|
||||
for (const k in ccd) {
|
||||
const { chem_comp, chem_comp_bond } = ccd[k]
|
||||
const { chem_comp, chem_comp_bond } = ccd[k];
|
||||
if (chem_comp_bond._rowCount) {
|
||||
addBonds(chem_comp.id.value(0), chem_comp_bond, false)
|
||||
addBonds(chem_comp.id.value(0), chem_comp_bond, false);
|
||||
}
|
||||
}
|
||||
|
||||
const bondTable = Table.ofArrays(mmCIF_chemCompBond_schema, {
|
||||
comp_id, atom_id_1, atom_id_2, value_order,
|
||||
pdbx_aromatic_flag, pdbx_stereo_config, molstar_protonation_variant
|
||||
})
|
||||
});
|
||||
|
||||
const bondDatabase = Database.ofTables(
|
||||
TABLE_NAME,
|
||||
{ chem_comp_bond: mmCIF_chemCompBond_schema },
|
||||
{ chem_comp_bond: bondTable }
|
||||
)
|
||||
);
|
||||
|
||||
return bondDatabase
|
||||
return bondDatabase;
|
||||
}
|
||||
|
||||
async function run(out: string, binary = false) {
|
||||
const bonds = await createBonds()
|
||||
const bonds = await createBonds();
|
||||
|
||||
const cif = getEncodedCif(TABLE_NAME, bonds, binary)
|
||||
const cif = getEncodedCif(TABLE_NAME, bonds, binary);
|
||||
if (!fs.existsSync(path.dirname(out))) {
|
||||
fs.mkdirSync(path.dirname(out));
|
||||
}
|
||||
writeFile(out, cif)
|
||||
writeFile(out, cif);
|
||||
}
|
||||
|
||||
const TABLE_NAME = 'CHEM_COMP_BONDS'
|
||||
const TABLE_NAME = 'CHEM_COMP_BONDS';
|
||||
|
||||
const DATA_DIR = path.join(__dirname, '..', '..', '..', 'build/data')
|
||||
const CCD_PATH = path.join(DATA_DIR, 'components.cif')
|
||||
const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif')
|
||||
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif'
|
||||
const PVCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/aa-variants-v1.cif'
|
||||
const DATA_DIR = path.join(__dirname, '..', '..', '..', 'build/data');
|
||||
const CCD_PATH = path.join(DATA_DIR, 'components.cif');
|
||||
const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif');
|
||||
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif';
|
||||
const PVCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/aa-variants-v1.cif';
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
@@ -247,6 +247,6 @@ interface Args {
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
|
||||
const FORCE_DOWNLOAD = args.forceDownload
|
||||
const FORCE_DOWNLOAD = args.forceDownload;
|
||||
|
||||
run(args.out, args.binary)
|
||||
run(args.out, args.binary);
|
||||
|
||||
119
src/apps/cif2bcif/converter.ts
Normal file
119
src/apps/cif2bcif/converter.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
*/
|
||||
|
||||
import { CIF, CifCategory, getCifFieldType, CifField, CifFile } from '../../mol-io/reader/cif';
|
||||
import { CifWriter, EncodingStrategyHint } from '../../mol-io/writer/cif';
|
||||
import * as util from 'util';
|
||||
import * as fs from 'fs';
|
||||
import * as zlib from 'zlib';
|
||||
import { Progress, Task, RuntimeContext } from '../../mol-task';
|
||||
import { classifyFloatArray, classifyIntArray } from '../../mol-io/common/binary-cif';
|
||||
import { BinaryEncodingProvider } from '../../mol-io/writer/cif/encoder/binary';
|
||||
import { Category } from '../../mol-io/writer/cif/encoder';
|
||||
import { ReaderResult } from '../../mol-io/reader/result';
|
||||
|
||||
function showProgress(p: Progress) {
|
||||
process.stdout.write(`\r${new Array(80).join(' ')}`);
|
||||
process.stdout.write(`\r${Progress.format(p)}`);
|
||||
}
|
||||
|
||||
const readFileAsync = util.promisify(fs.readFile);
|
||||
const unzipAsync = util.promisify<zlib.InputType, Buffer>(zlib.unzip);
|
||||
|
||||
async function readFile(ctx: RuntimeContext, filename: string): Promise<ReaderResult<CifFile>> {
|
||||
const isGz = /\.gz$/i.test(filename);
|
||||
if (filename.match(/\.bcif/)) {
|
||||
let input = await readFileAsync(filename);
|
||||
if (isGz) input = await unzipAsync(input);
|
||||
return await CIF.parseBinary(new Uint8Array(input)).runInContext(ctx);
|
||||
} else {
|
||||
let str: string;
|
||||
if (isGz) {
|
||||
const data = await unzipAsync(await readFileAsync(filename));
|
||||
str = data.toString('utf8');
|
||||
} else {
|
||||
str = await readFileAsync(filename, 'utf8');
|
||||
}
|
||||
return await CIF.parseText(str).runInContext(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
async function getCIF(ctx: RuntimeContext, filename: string) {
|
||||
const parsed = await readFile(ctx, filename);
|
||||
if (parsed.isError) {
|
||||
throw new Error(parsed.toString());
|
||||
}
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
function getCategoryInstanceProvider(cat: CifCategory, fields: CifWriter.Field[]): CifWriter.Category {
|
||||
return {
|
||||
name: cat.name,
|
||||
instance: () => CifWriter.categoryInstance(fields, { data: cat, rowCount: cat.rowCount })
|
||||
};
|
||||
}
|
||||
|
||||
function classify(name: string, field: CifField): CifWriter.Field {
|
||||
const type = getCifFieldType(field);
|
||||
if (type['@type'] === 'str') {
|
||||
return { name, type: CifWriter.Field.Type.Str, value: field.str, valueKind: field.valueKind };
|
||||
} else if (type['@type'] === 'float') {
|
||||
const encoder = classifyFloatArray(field.toFloatArray({ array: Float64Array }));
|
||||
return CifWriter.Field.float(name, field.float, { valueKind: field.valueKind, encoder, typedArray: Float64Array });
|
||||
} else {
|
||||
const encoder = classifyIntArray(field.toIntArray({ array: Int32Array }));
|
||||
return CifWriter.Field.int(name, field.int, { valueKind: field.valueKind, encoder, typedArray: Int32Array });
|
||||
}
|
||||
}
|
||||
|
||||
export default function convert(path: string, asText = false, hints?: EncodingStrategyHint[], filter?: string) {
|
||||
return Task.create<Uint8Array>('BinaryCIF', async ctx => {
|
||||
const encodingProvider: BinaryEncodingProvider = hints
|
||||
? CifWriter.createEncodingProviderFromJsonConfig(hints)
|
||||
: { get: (c, f) => void 0 };
|
||||
const cif = await getCIF(ctx, path);
|
||||
|
||||
const encoder = CifWriter.createEncoder({
|
||||
binary: !asText,
|
||||
encoderName: 'mol*/ciftools cif2bcif',
|
||||
binaryAutoClassifyEncoding: true,
|
||||
binaryEncodingPovider: encodingProvider
|
||||
});
|
||||
|
||||
if (filter) {
|
||||
encoder.setFilter(Category.filterOf(filter));
|
||||
}
|
||||
|
||||
let maxProgress = 0;
|
||||
for (const b of cif.blocks) {
|
||||
maxProgress += b.categoryNames.length;
|
||||
for (const c of b.categoryNames) maxProgress += b.categories[c].fieldNames.length;
|
||||
}
|
||||
|
||||
let current = 0;
|
||||
for (const b of cif.blocks) {
|
||||
encoder.startDataBlock(b.header);
|
||||
for (const c of b.categoryNames) {
|
||||
const cat = b.categories[c];
|
||||
const fields: CifWriter.Field[] = [];
|
||||
for (const f of cat.fieldNames) {
|
||||
fields.push(classify(f, cat.getField(f)!));
|
||||
current++;
|
||||
if (ctx.shouldUpdate) await ctx.update({ message: 'Encoding...', current, max: maxProgress });
|
||||
}
|
||||
|
||||
encoder.writeCategory(getCategoryInstanceProvider(b.categories[c], fields));
|
||||
current++;
|
||||
if (ctx.shouldUpdate) await ctx.update({ message: 'Encoding...', current, max: maxProgress });
|
||||
}
|
||||
}
|
||||
await ctx.update('Exporting...');
|
||||
const ret = encoder.getData() as Uint8Array;
|
||||
await ctx.update('Done.\n');
|
||||
return ret;
|
||||
}).run(showProgress, 250);
|
||||
}
|
||||
67
src/apps/cif2bcif/index.ts
Normal file
67
src/apps/cif2bcif/index.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as argparse from 'argparse';
|
||||
import * as util from 'util';
|
||||
import * as fs from 'fs';
|
||||
import * as zlib from 'zlib';
|
||||
import convert from './converter';
|
||||
|
||||
require('util.promisify').shim();
|
||||
|
||||
async function process(srcPath: string, outPath: string, configPath?: string, filterPath?: string) {
|
||||
const config = configPath ? JSON.parse(fs.readFileSync(configPath, 'utf8')) : void 0;
|
||||
const filter = filterPath ? fs.readFileSync(filterPath, 'utf8') : void 0;
|
||||
|
||||
const res = await convert(srcPath, false, config, filter);
|
||||
await write(outPath, res);
|
||||
}
|
||||
|
||||
const zipAsync = util.promisify<zlib.InputType, Buffer>(zlib.gzip);
|
||||
|
||||
async function write(outPath: string, res: Uint8Array) {
|
||||
const isGz = /\.gz$/i.test(outPath);
|
||||
if (isGz) {
|
||||
res = await zipAsync(res);
|
||||
}
|
||||
fs.writeFileSync(outPath, res);
|
||||
}
|
||||
|
||||
function run(args: Args) {
|
||||
process(args.src, args.out, args.config, args.filter);
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
description: 'Convert any CIF file to a BCIF file'
|
||||
});
|
||||
parser.addArgument([ 'src' ], {
|
||||
help: 'Source CIF path'
|
||||
});
|
||||
parser.addArgument([ 'out' ], {
|
||||
help: 'Output BCIF path'
|
||||
});
|
||||
parser.addArgument([ '-c', '--config' ], {
|
||||
help: 'Optional encoding strategy/precision config path',
|
||||
required: false
|
||||
});
|
||||
parser.addArgument([ '-f', '--filter' ], {
|
||||
help: 'Optional filter whitelist/blacklist path',
|
||||
required: false
|
||||
});
|
||||
|
||||
interface Args {
|
||||
src: string
|
||||
out: string
|
||||
config?: string
|
||||
filter?: string
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
|
||||
if (args) {
|
||||
run(args);
|
||||
}
|
||||
274
src/apps/cifschema/index.ts
Normal file
274
src/apps/cifschema/index.ts
Normal file
@@ -0,0 +1,274 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 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 fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
import { parseCsv } from '../../mol-io/reader/csv/parser';
|
||||
import { CifFrame, CifBlock } from '../../mol-io/reader/cif';
|
||||
import parseText from '../../mol-io/reader/cif/text/parser';
|
||||
import { generateSchema } from './util/cif-dic';
|
||||
import { generate } from './util/generate';
|
||||
import { Filter, Database } from './util/schema';
|
||||
import { parseImportGet } from './util/helper';
|
||||
|
||||
function getDicVersion(block: CifBlock) {
|
||||
return block.categories.dictionary.getField('version')!.str(0);
|
||||
}
|
||||
|
||||
function getDicNamespace(block: CifBlock) {
|
||||
return block.categories.dictionary.getField('namespace')!.str(0);
|
||||
}
|
||||
|
||||
async function runGenerateSchemaMmcif(name: string, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
|
||||
await ensureMmcifDicAvailable();
|
||||
const mmcifDic = await parseText(fs.readFileSync(MMCIF_DIC_PATH, 'utf8')).run();
|
||||
if (mmcifDic.isError) throw mmcifDic;
|
||||
|
||||
await ensureIhmDicAvailable();
|
||||
const ihmDic = await parseText(fs.readFileSync(IHM_DIC_PATH, 'utf8')).run();
|
||||
if (ihmDic.isError) throw ihmDic;
|
||||
|
||||
await ensureCarbBranchDicAvailable();
|
||||
const carbBranchDic = await parseText(fs.readFileSync(CARB_BRANCH_DIC_PATH, 'utf8')).run();
|
||||
if (carbBranchDic.isError) throw carbBranchDic;
|
||||
|
||||
await ensureCarbCompDicAvailable();
|
||||
const carbCompDic = await parseText(fs.readFileSync(CARB_COMP_DIC_PATH, 'utf8')).run();
|
||||
if (carbCompDic.isError) throw carbCompDic;
|
||||
|
||||
const mmcifDicVersion = getDicVersion(mmcifDic.result.blocks[0]);
|
||||
const ihmDicVersion = getDicVersion(ihmDic.result.blocks[0]);
|
||||
const carbDicVersion = 'draft';
|
||||
const version = `Dictionary versions: mmCIF ${mmcifDicVersion}, IHM ${ihmDicVersion}, CARB ${carbDicVersion}.`;
|
||||
|
||||
const frames: CifFrame[] = [...mmcifDic.result.blocks[0].saveFrames, ...ihmDic.result.blocks[0].saveFrames, ...carbBranchDic.result.blocks[0].saveFrames, ...carbCompDic.result.blocks[0].saveFrames];
|
||||
const schema = generateSchema(frames);
|
||||
|
||||
await runGenerateSchema(name, version, schema, fieldNamesPath, typescript, out, moldbImportPath, addAliases);
|
||||
}
|
||||
|
||||
async function runGenerateSchemaCifCore(name: string, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
|
||||
await ensureCifCoreDicAvailable();
|
||||
const cifCoreDic = await parseText(fs.readFileSync(CIF_CORE_DIC_PATH, 'utf8')).run();
|
||||
if (cifCoreDic.isError) throw cifCoreDic;
|
||||
|
||||
const cifCoreDicVersion = getDicVersion(cifCoreDic.result.blocks[0]);
|
||||
const version = `Dictionary versions: CifCore ${cifCoreDicVersion}.`;
|
||||
|
||||
const frames: CifFrame[] = [...cifCoreDic.result.blocks[0].saveFrames];
|
||||
const imports = await resolveImports(frames, DIC_DIR);
|
||||
const schema = generateSchema(frames, imports);
|
||||
|
||||
await runGenerateSchema(name, version, schema, fieldNamesPath, typescript, out, moldbImportPath, addAliases);
|
||||
}
|
||||
|
||||
async function resolveImports(frames: CifFrame[], baseDir: string): Promise<Map<string, CifFrame[]>> {
|
||||
const imports = new Map<string, CifFrame[]>();
|
||||
|
||||
for (const d of frames) {
|
||||
if ('import' in d.categories) {
|
||||
const importGet = parseImportGet(d.categories['import'].getField('get')!.str(0));
|
||||
for (const g of importGet) {
|
||||
const { file } = g;
|
||||
if (!file) continue;
|
||||
if (imports.has(file)) continue;
|
||||
|
||||
const dic = await parseText(fs.readFileSync(path.join(baseDir, file), 'utf8')).run();
|
||||
if (dic.isError) throw dic;
|
||||
|
||||
imports.set(file, [...dic.result.blocks[0].saveFrames]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
async function runGenerateSchemaDic(name: string, dicPath: string, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
|
||||
const dic = await parseText(fs.readFileSync(dicPath, 'utf8')).run();
|
||||
if (dic.isError) throw dic;
|
||||
|
||||
const dicVersion = getDicVersion(dic.result.blocks[0]);
|
||||
const dicName = getDicNamespace(dic.result.blocks[0]);
|
||||
const version = `Dictionary versions: ${dicName} ${dicVersion}.`;
|
||||
|
||||
const frames: CifFrame[] = [...dic.result.blocks[0].saveFrames];
|
||||
const imports = await resolveImports(frames, path.dirname(dicPath));
|
||||
const schema = generateSchema(frames, imports);
|
||||
|
||||
await runGenerateSchema(name, version, schema, fieldNamesPath, typescript, out, moldbImportPath, addAliases);
|
||||
}
|
||||
|
||||
async function runGenerateSchema(name: string, version: string, schema: Database, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
|
||||
const filter = fieldNamesPath ? await getFieldNamesFilter(fieldNamesPath) : undefined;
|
||||
const output = typescript ? generate(name, version, schema, filter, moldbImportPath, addAliases) : JSON.stringify(schema, undefined, 4);
|
||||
|
||||
if (out) {
|
||||
fs.writeFileSync(out, output);
|
||||
} else {
|
||||
console.log(output);
|
||||
}
|
||||
}
|
||||
|
||||
async function getFieldNamesFilter(fieldNamesPath: string): Promise<Filter> {
|
||||
const fieldNamesStr = fs.readFileSync(fieldNamesPath, 'utf8');
|
||||
const parsed = await parseCsv(fieldNamesStr, { noColumnNames: true }).run();
|
||||
if (parsed.isError) throw parser.error;
|
||||
const csvFile = parsed.result;
|
||||
|
||||
const fieldNamesCol = csvFile.table.getColumn('0');
|
||||
if (!fieldNamesCol) throw 'error getting fields columns';
|
||||
const fieldNames = fieldNamesCol.toStringArray();
|
||||
|
||||
const filter: Filter = {};
|
||||
fieldNames.forEach((name, i) => {
|
||||
const [ category, field ] = name.split('.');
|
||||
// console.log(category, field)
|
||||
if (!filter[ category ]) filter[ category ] = {};
|
||||
filter[ category ][ field ] = true;
|
||||
});
|
||||
return filter;
|
||||
}
|
||||
|
||||
async function ensureMmcifDicAvailable() { await ensureDicAvailable(MMCIF_DIC_PATH, MMCIF_DIC_URL); }
|
||||
async function ensureIhmDicAvailable() { await ensureDicAvailable(IHM_DIC_PATH, IHM_DIC_URL); }
|
||||
async function ensureCarbBranchDicAvailable() { await ensureDicAvailable(CARB_BRANCH_DIC_PATH, CARB_BRANCH_DIC_URL); }
|
||||
async function ensureCarbCompDicAvailable() { await ensureDicAvailable(CARB_COMP_DIC_PATH, CARB_COMP_DIC_URL); }
|
||||
async function ensureCifCoreDicAvailable() {
|
||||
await ensureDicAvailable(CIF_CORE_DIC_PATH, CIF_CORE_DIC_URL);
|
||||
await ensureDicAvailable(CIF_CORE_ENUM_PATH, CIF_CORE_ENUM_URL);
|
||||
await ensureDicAvailable(CIF_CORE_ATTR_PATH, CIF_CORE_ATTR_URL);
|
||||
}
|
||||
|
||||
async function ensureDicAvailable(dicPath: string, dicUrl: string) {
|
||||
if (FORCE_DIC_DOWNLOAD || !fs.existsSync(dicPath)) {
|
||||
const name = dicUrl.substr(dicUrl.lastIndexOf('/') + 1);
|
||||
console.log(`downloading ${name}...`);
|
||||
const data = await fetch(dicUrl);
|
||||
if (!fs.existsSync(DIC_DIR)) {
|
||||
fs.mkdirSync(DIC_DIR);
|
||||
}
|
||||
fs.writeFileSync(dicPath, await data.text());
|
||||
console.log(`done downloading ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
const DIC_DIR = path.resolve(__dirname, '../../../build/dics/');
|
||||
const MMCIF_DIC_PATH = `${DIC_DIR}/mmcif_pdbx_v50.dic`;
|
||||
const MMCIF_DIC_URL = 'http://mmcif.wwpdb.org/dictionaries/ascii/mmcif_pdbx_v50.dic';
|
||||
const IHM_DIC_PATH = `${DIC_DIR}/ihm-extension.dic`;
|
||||
const IHM_DIC_URL = 'https://raw.githubusercontent.com/ihmwg/IHM-dictionary/master/ihm-extension.dic';
|
||||
const CARB_BRANCH_DIC_PATH = `${DIC_DIR}/entity_branch-extension.dic`;
|
||||
const CARB_BRANCH_DIC_URL = 'https://raw.githubusercontent.com/pdbxmmcifwg/carbohydrate-extension/master/dict/entity_branch-extension.dic';
|
||||
const CARB_COMP_DIC_PATH = `${DIC_DIR}/chem_comp-extension.dic`;
|
||||
const CARB_COMP_DIC_URL = 'https://raw.githubusercontent.com/pdbxmmcifwg/carbohydrate-extension/master/dict/chem_comp-extension.dic';
|
||||
|
||||
const CIF_CORE_DIC_PATH = `${DIC_DIR}/cif_core.dic`;
|
||||
const CIF_CORE_DIC_URL = 'https://raw.githubusercontent.com/COMCIFS/cif_core/master/cif_core.dic';
|
||||
const CIF_CORE_ENUM_PATH = `${DIC_DIR}/templ_enum.cif`;
|
||||
const CIF_CORE_ENUM_URL = 'https://raw.githubusercontent.com/COMCIFS/cif_core/master/templ_enum.cif';
|
||||
const CIF_CORE_ATTR_PATH = `${DIC_DIR}/templ_attr.cif`;
|
||||
const CIF_CORE_ATTR_URL = 'https://raw.githubusercontent.com/COMCIFS/cif_core/master/templ_attr.cif';
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
description: 'Create schema from mmcif dictionary (v50 plus IHM and entity_branch extensions, downloaded from wwPDB)'
|
||||
});
|
||||
parser.addArgument([ '--preset', '-p' ], {
|
||||
defaultValue: '',
|
||||
choices: ['', 'mmCIF', 'CCD', 'BIRD', 'CifCore'],
|
||||
help: 'Preset name'
|
||||
});
|
||||
parser.addArgument([ '--name', '-n' ], {
|
||||
defaultValue: '',
|
||||
help: 'Schema name'
|
||||
});
|
||||
parser.addArgument([ '--out', '-o' ], {
|
||||
help: 'Generated schema output path, if not given printed to stdout'
|
||||
});
|
||||
parser.addArgument([ '--targetFormat', '-tf' ], {
|
||||
defaultValue: 'typescript-molstar',
|
||||
choices: ['typescript-molstar', 'json-internal'],
|
||||
help: 'Target format'
|
||||
});
|
||||
parser.addArgument([ '--dicPath', '-d' ], {
|
||||
defaultValue: '',
|
||||
help: 'Path to dictionary'
|
||||
});
|
||||
parser.addArgument([ '--fieldNamesPath', '-fn' ], {
|
||||
defaultValue: '',
|
||||
help: 'Field names to include'
|
||||
});
|
||||
parser.addArgument([ '--forceDicDownload', '-f' ], {
|
||||
action: 'storeTrue',
|
||||
help: 'Force download of dictionaries'
|
||||
});
|
||||
parser.addArgument([ '--moldataImportPath', '-mip' ], {
|
||||
defaultValue: 'molstar/lib/mol-data',
|
||||
help: 'mol-data import path (for typescript target only)'
|
||||
});
|
||||
parser.addArgument([ '--addAliases', '-aa' ], {
|
||||
action: 'storeTrue',
|
||||
help: 'Add field name/path aliases'
|
||||
});
|
||||
interface Args {
|
||||
name: string
|
||||
preset: '' | 'mmCIF' | 'CCD' | 'BIRD' | 'CifCore'
|
||||
forceDicDownload: boolean
|
||||
dic: '' | 'mmCIF' | 'CifCore'
|
||||
dicPath: string,
|
||||
fieldNamesPath: string
|
||||
targetFormat: 'typescript-molstar' | 'json-internal'
|
||||
out: string,
|
||||
moldataImportPath: string
|
||||
addAliases: boolean
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
|
||||
const FORCE_DIC_DOWNLOAD = args.forceDicDownload;
|
||||
|
||||
switch (args.preset) {
|
||||
case 'mmCIF':
|
||||
args.name = 'mmCIF';
|
||||
args.dic = 'mmCIF';
|
||||
args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/mmcif-field-names.csv');
|
||||
break;
|
||||
case 'CCD':
|
||||
args.name = 'CCD';
|
||||
args.dic = 'mmCIF';
|
||||
args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/ccd-field-names.csv');
|
||||
break;
|
||||
case 'BIRD':
|
||||
args.name = 'BIRD';
|
||||
args.dic = 'mmCIF';
|
||||
args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/bird-field-names.csv');
|
||||
break;
|
||||
case 'CifCore':
|
||||
args.name = 'CifCore';
|
||||
args.dic = 'CifCore';
|
||||
args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/cif-core-field-names.csv');
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.name) {
|
||||
const typescript = args.targetFormat === 'typescript-molstar';
|
||||
if (args.dicPath) {
|
||||
runGenerateSchemaDic(args.name, args.dicPath, args.fieldNamesPath, typescript, args.out, args.moldataImportPath, args.addAliases).catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
} else if (args.dic === 'mmCIF') {
|
||||
runGenerateSchemaMmcif(args.name, args.fieldNamesPath, typescript, args.out, args.moldataImportPath, args.addAliases).catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
} else if (args.dic === 'CifCore') {
|
||||
runGenerateSchemaCifCore(args.name, args.fieldNamesPath, typescript, args.out, args.moldataImportPath, args.addAliases).catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
475
src/apps/cifschema/util/cif-dic.ts
Normal file
475
src/apps/cifschema/util/cif-dic.ts
Normal file
@@ -0,0 +1,475 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Database, Column, EnumCol, StrCol, IntCol, ListCol, FloatCol, CoordCol, MatrixCol, VectorCol } from './schema';
|
||||
import { parseImportGet } from './helper';
|
||||
import * as Data from '../../../mol-io/reader/cif/data-model';
|
||||
import { CifFrame } from '../../../mol-io/reader/cif/data-model';
|
||||
|
||||
export function getFieldType(type: string, description: string, values?: string[], container?: string): Column {
|
||||
switch (type) {
|
||||
// mmCIF
|
||||
case 'code':
|
||||
case 'ucode':
|
||||
case 'line':
|
||||
case 'uline':
|
||||
case 'text':
|
||||
case 'char':
|
||||
case 'uchar3':
|
||||
case 'uchar1':
|
||||
case 'boolean':
|
||||
return values && values.length ? EnumCol(values, 'str', description) : StrCol(description);
|
||||
case 'aliasname':
|
||||
case 'name':
|
||||
case 'idname':
|
||||
case 'any':
|
||||
case 'atcode':
|
||||
case 'fax':
|
||||
case 'phone':
|
||||
case 'email':
|
||||
case 'code30':
|
||||
case 'seq-one-letter-code':
|
||||
case 'author':
|
||||
case 'orcid_id':
|
||||
case 'sequence_dep':
|
||||
case 'pdb_id':
|
||||
case 'emd_id':
|
||||
// todo, consider adding specialised fields
|
||||
case 'yyyy-mm-dd':
|
||||
case 'yyyy-mm-dd:hh:mm':
|
||||
case 'yyyy-mm-dd:hh:mm-flex':
|
||||
case 'int-range':
|
||||
case 'float-range':
|
||||
case 'binary':
|
||||
case 'operation_expression':
|
||||
case 'point_symmetry':
|
||||
case '4x3_matrix':
|
||||
case '3x4_matrices':
|
||||
case 'point_group':
|
||||
case 'point_group_helical':
|
||||
case 'symmetry_operation':
|
||||
case 'date_dep':
|
||||
case 'url':
|
||||
case 'symop':
|
||||
case 'exp_data_doi':
|
||||
case 'asym_id':
|
||||
return StrCol(description);
|
||||
case 'int':
|
||||
case 'non_negative_int':
|
||||
case 'positive_int':
|
||||
return values && values.length ? EnumCol(values, 'int', description) : IntCol(description);
|
||||
case 'float':
|
||||
return FloatCol(description);
|
||||
case 'ec-type':
|
||||
case 'ucode-alphanum-csv':
|
||||
case 'id_list':
|
||||
return ListCol('str', ',', description);
|
||||
case 'id_list_spc':
|
||||
return ListCol('str', ' ', description);
|
||||
|
||||
// cif
|
||||
case 'Text':
|
||||
case 'Code':
|
||||
case 'Complex':
|
||||
case 'Symop':
|
||||
case 'List':
|
||||
case 'List(Real,Real)':
|
||||
case 'List(Real,Real,Real,Real)':
|
||||
case 'Date':
|
||||
case 'Datetime':
|
||||
case 'Tag':
|
||||
case 'Implied':
|
||||
return wrapContainer('str', ',', description, container);
|
||||
case 'Real':
|
||||
return wrapContainer('float', ',', description, container);
|
||||
case 'Integer':
|
||||
return wrapContainer('int', ',', description, container);
|
||||
|
||||
}
|
||||
console.log(`unknown type '${type}'`);
|
||||
return StrCol(description);
|
||||
}
|
||||
|
||||
function ColFromType(type: 'int' | 'str' | 'float' | 'coord', description: string): Column {
|
||||
switch (type) {
|
||||
case 'int': return IntCol(description);
|
||||
case 'str': return StrCol(description);
|
||||
case 'float': return FloatCol(description);
|
||||
case 'coord': return CoordCol(description);
|
||||
}
|
||||
}
|
||||
|
||||
function wrapContainer(type: 'int' | 'str' | 'float' | 'coord', separator: string, description: string, container?: string) {
|
||||
return container && container === 'List' ? ListCol(type, separator, description) : ColFromType(type, description);
|
||||
}
|
||||
|
||||
type FrameCategories = { [category: string]: Data.CifFrame }
|
||||
type FrameLinks = { [k: string]: string }
|
||||
|
||||
interface FrameData {
|
||||
categories: FrameCategories
|
||||
links: FrameLinks
|
||||
}
|
||||
|
||||
type Imports = Map<string, CifFrame[]>
|
||||
|
||||
function getImportFrames(d: Data.CifFrame, imports: Imports) {
|
||||
const frames: Data.CifFrame[] = [];
|
||||
if (!('import' in d.categories)) return frames;
|
||||
|
||||
const importGet = parseImportGet(d.categories['import'].getField('get')!.str(0));
|
||||
for (const g of importGet) {
|
||||
const { file, save } = g;
|
||||
if (!file || !save) {
|
||||
console.warn(`missing 'save' or 'file' for import in '${d.header}'`);
|
||||
continue;
|
||||
}
|
||||
const importFrames = imports.get(file);
|
||||
if (!importFrames) {
|
||||
console.warn(`missing '${file}' entry in imports`);
|
||||
continue;
|
||||
}
|
||||
const importSave = importFrames.find(id => id.header.toLowerCase() === save.toLowerCase());
|
||||
if (!importSave) {
|
||||
console.warn(`missing '${save}' save frame in '${file}'`);
|
||||
continue;
|
||||
}
|
||||
|
||||
frames.push(importSave);
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
/** get field from given or linked category */
|
||||
function getField(category: string, field: string, d: Data.CifFrame, imports: Imports, ctx: FrameData): Data.CifField|undefined {
|
||||
const { categories, links } = ctx;
|
||||
const cat = d.categories[category];
|
||||
if (cat) {
|
||||
return cat.getField(field);
|
||||
} else if (d.header in links) {
|
||||
const linkName = links[d.header];
|
||||
if (linkName in categories) {
|
||||
return getField(category, field, categories[linkName], imports, ctx);
|
||||
} else {
|
||||
// console.log(`link '${linkName}' not found`)
|
||||
}
|
||||
} else {
|
||||
const importFrames = getImportFrames(d, imports);
|
||||
for (const idf of importFrames) {
|
||||
return getField(category, field, idf, imports, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getEnums(d: Data.CifFrame, imports: Imports, ctx: FrameData) {
|
||||
const value = getField('item_enumeration', 'value', d, imports, ctx);
|
||||
const enums: string[] = [];
|
||||
if (value) {
|
||||
for (let i = 0; i < value.rowCount; ++i) {
|
||||
enums.push(value.str(i));
|
||||
// console.log(value.str(i))
|
||||
}
|
||||
return enums;
|
||||
} else {
|
||||
// console.log(`item_enumeration.value not found for '${d.header}'`)
|
||||
}
|
||||
}
|
||||
|
||||
function getContainer(d: Data.CifFrame, imports: Imports, ctx: FrameData) {
|
||||
const value = getField('type', 'container', d, imports, ctx);
|
||||
return value ? value.str(0) : undefined;
|
||||
}
|
||||
|
||||
function getCode(d: Data.CifFrame, imports: Imports, ctx: FrameData): [string, string[] | undefined, string | undefined ] | undefined {
|
||||
const code = getField('item_type', 'code', d, imports, ctx) || getField('type', 'contents', d, imports, ctx);
|
||||
if (code) {
|
||||
return [ code.str(0), getEnums(d, imports, ctx), getContainer(d, imports, ctx) ];
|
||||
} else {
|
||||
console.log(`item_type.code or type.contents not found for '${d.header}'`);
|
||||
}
|
||||
}
|
||||
|
||||
function getSubCategory(d: Data.CifFrame, imports: Imports, ctx: FrameData): string | undefined {
|
||||
const value = getField('item_sub_category', 'id', d, imports, ctx);
|
||||
if (value) {
|
||||
return value.str(0);
|
||||
}
|
||||
}
|
||||
|
||||
function getDescription(d: Data.CifFrame, imports: Imports, ctx: FrameData): string | undefined {
|
||||
const value = getField('item_description', 'description', d, imports, ctx) || getField('description', 'text', d, imports, ctx);
|
||||
if (value) {
|
||||
// trim (after newlines) and remove references to square brackets
|
||||
return value.str(0).trim()
|
||||
.replace(/(\r\n|\r|\n)([ \t]+)/g, '\n')
|
||||
.replace(/(\[[1-3]\])+ element/, 'elements')
|
||||
.replace(/(\[[1-3]\])+/, '');
|
||||
}
|
||||
}
|
||||
|
||||
function getAliases(d: Data.CifFrame, imports: Imports, ctx: FrameData): string[] | undefined {
|
||||
const value = getField('item_aliases', 'alias_name', d, imports, ctx) || getField('alias', 'definition_id', d, imports, ctx);
|
||||
return value ? value.toStringArray().map(v => v.substr(1)) : undefined;
|
||||
}
|
||||
|
||||
const reMatrixField = /\[[1-3]\]\[[1-3]\]/;
|
||||
const reVectorField = /\[[1-3]\]/;
|
||||
|
||||
const FORCE_INT_FIELDS = [
|
||||
'_atom_site.id',
|
||||
'_atom_site.auth_seq_id',
|
||||
'_atom_site_anisotrop.id',
|
||||
'_pdbx_struct_mod_residue.auth_seq_id',
|
||||
'_struct_conf.beg_auth_seq_id',
|
||||
'_struct_conf.end_auth_seq_id',
|
||||
'_struct_conn.ptnr1_auth_seq_id',
|
||||
'_struct_conn.ptnr2_auth_seq_id',
|
||||
'_struct_sheet_range.beg_auth_seq_id',
|
||||
'_struct_sheet_range.end_auth_seq_id',
|
||||
];
|
||||
|
||||
const FORCE_MATRIX_FIELDS_MAP: { [k: string]: string } = {
|
||||
'atom_site_aniso.U_11': 'U',
|
||||
'atom_site_aniso.U_22': 'U',
|
||||
'atom_site_aniso.U_33': 'U',
|
||||
'atom_site_aniso.U_23': 'U',
|
||||
'atom_site_aniso.U_13': 'U',
|
||||
'atom_site_aniso.U_12': 'U',
|
||||
'atom_site_aniso.U_11_su': 'U_su',
|
||||
'atom_site_aniso.U_22_su': 'U_su',
|
||||
'atom_site_aniso.U_33_su': 'U_su',
|
||||
'atom_site_aniso.U_23_su': 'U_su',
|
||||
'atom_site_aniso.U_13_su': 'U_su',
|
||||
'atom_site_aniso.U_12_su': 'U_su',
|
||||
};
|
||||
const FORCE_MATRIX_FIELDS = Object.keys(FORCE_MATRIX_FIELDS_MAP);
|
||||
|
||||
const EXTRA_ALIASES: Database['aliases'] = {
|
||||
'atom_site_aniso.U': [
|
||||
'atom_site_anisotrop_U'
|
||||
],
|
||||
'atom_site_aniso.U_su': [
|
||||
'atom_site_aniso_U_esd',
|
||||
'atom_site_anisotrop_U_esd',
|
||||
],
|
||||
};
|
||||
|
||||
const COMMA_SEPARATED_LIST_FIELDS = [
|
||||
'_atom_site.pdbx_struct_group_id',
|
||||
'_chem_comp.mon_nstd_parent_comp_id',
|
||||
'_diffrn_radiation.pdbx_wavelength_list',
|
||||
'_diffrn_source.pdbx_wavelength_list',
|
||||
'_em_diffraction.tilt_angle_list', // 20,40,50,55
|
||||
'_em_entity_assembly.entity_id_list',
|
||||
'_entity.pdbx_description', // Endolysin,Beta-2 adrenergic receptor
|
||||
'_entity.pdbx_ec',
|
||||
'_entity_poly.pdbx_strand_id', // A,B
|
||||
'_entity_src_gen.pdbx_gene_src_gene', // ADRB2, ADRB2R, B2AR
|
||||
'_pdbx_depui_entry_details.experimental_methods',
|
||||
'_pdbx_depui_entry_details.requested_accession_types',
|
||||
'_pdbx_soln_scatter_model.software_list', // INSIGHT II, HOMOLOGY, DISCOVERY, BIOPOLYMER, DELPHI
|
||||
'_pdbx_soln_scatter_model.software_author_list', // MSI
|
||||
'_pdbx_soln_scatter_model.entry_fitting_list', // Odd example: 'PDB CODE 1HFI, 1HCC, 1HFH, 1VCC'
|
||||
'_pdbx_struct_assembly_gen.entity_inst_id',
|
||||
'_pdbx_struct_assembly_gen.asym_id_list',
|
||||
'_pdbx_struct_assembly_gen.auth_asym_id_list',
|
||||
'_pdbx_struct_assembly_gen_depositor_info.asym_id_list',
|
||||
'_pdbx_struct_assembly_gen_depositor_info.chain_id_list',
|
||||
'_pdbx_struct_group_list.group_enumeration_type',
|
||||
'_reflns.pdbx_diffrn_id',
|
||||
'_refine.pdbx_diffrn_id',
|
||||
'_reflns_shell.pdbx_diffrn_id',
|
||||
'_struct_keywords.text',
|
||||
];
|
||||
|
||||
const SPACE_SEPARATED_LIST_FIELDS = [
|
||||
'_chem_comp.pdbx_subcomponent_list', // TSM DPH HIS CHF EMR
|
||||
'_pdbx_soln_scatter.data_reduction_software_list', // OTOKO
|
||||
'_pdbx_soln_scatter.data_analysis_software_list', // SCTPL5 GNOM
|
||||
];
|
||||
|
||||
const SEMICOLON_SEPARATED_LIST_FIELDS = [
|
||||
'_chem_comp.pdbx_synonyms' // GLYCERIN; PROPANE-1,2,3-TRIOL
|
||||
];
|
||||
|
||||
/**
|
||||
* Useful when a dictionary extension will add enum values to an existing dictionary.
|
||||
* By adding them here, the dictionary extension can be tested before the added enum
|
||||
* values are available in the existing dictionary.
|
||||
*/
|
||||
const EXTRA_ENUM_VALUES: { [k: string]: string[] } = {
|
||||
|
||||
};
|
||||
|
||||
export function generateSchema(frames: CifFrame[], imports: Imports = new Map()): Database {
|
||||
const tables: Database['tables'] = {};
|
||||
const aliases: Database['aliases'] = { ...EXTRA_ALIASES };
|
||||
|
||||
const categories: FrameCategories = {};
|
||||
const links: FrameLinks = {};
|
||||
const ctx = { categories, links };
|
||||
|
||||
// get category metadata
|
||||
frames.forEach(d => {
|
||||
// category definitions in mmCIF start with '_' and don't include a '.'
|
||||
// category definitions in cifCore don't include a '.'
|
||||
if (d.header[0] === '_' || d.header.includes('.')) return;
|
||||
const categoryName = d.header.toLowerCase();
|
||||
// console.log(d.header, d.categoryNames, d.categories)
|
||||
let descriptionField: Data.CifField | undefined;
|
||||
const categoryKeyNames = new Set<string>();
|
||||
|
||||
if ('category' in d.categories && 'category_key' in d.categories) {
|
||||
const category = d.categories['category'];
|
||||
const categoryKey = d.categories['category_key'];
|
||||
if (categoryKey) {
|
||||
const categoryKey_names = categoryKey.getField('name');
|
||||
if (categoryKey_names) {
|
||||
for (let i = 0, il = categoryKey_names.rowCount; i < il; ++i) {
|
||||
categoryKeyNames.add(categoryKey_names.str(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
descriptionField = category.getField('description');
|
||||
|
||||
if (categoryKeyNames.size === 0) {
|
||||
console.log(`no key given for category '${categoryName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
if ('description' in d.categories) {
|
||||
descriptionField = d.categories['description'].getField('text');
|
||||
}
|
||||
|
||||
let description = '';
|
||||
if (descriptionField) {
|
||||
description = descriptionField.str(0).trim()
|
||||
.replace(/(\r\n|\r|\n)([ \t]+)/g, '\n'); // remove padding after newlines
|
||||
} else {
|
||||
console.log(`no description given for category '${categoryName}'`);
|
||||
}
|
||||
|
||||
tables[categoryName] = { description, key: categoryKeyNames, columns: {} };
|
||||
|
||||
// console.log('++++++++++++++++++++++++++++++++++++++++++')
|
||||
// console.log('name', categoryName)
|
||||
// console.log('desc', description)
|
||||
// console.log('key', categoryKeyNames)
|
||||
});
|
||||
|
||||
// build list of links between categories
|
||||
frames.forEach(d => {
|
||||
if (d.header[0] !== '_' && !d.header.includes('.')) return;
|
||||
categories[d.header] = d;
|
||||
const item_linked = d.categories['item_linked'];
|
||||
if (item_linked) {
|
||||
const child_name = item_linked.getField('child_name');
|
||||
const parent_name = item_linked.getField('parent_name');
|
||||
if (child_name && parent_name) {
|
||||
for (let i = 0; i < item_linked.rowCount; ++i) {
|
||||
const childName = child_name.str(i);
|
||||
const parentName = parent_name.str(i);
|
||||
if (childName in links && links[childName] !== parentName) {
|
||||
console.log(`${childName} linked to ${links[childName]}, ignoring link to ${parentName}`);
|
||||
}
|
||||
links[childName] = parentName;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// get field data
|
||||
Object.keys(categories).forEach(fullName => {
|
||||
const d = categories[fullName];
|
||||
if (!d) {
|
||||
console.log(`'${fullName}' not found, moving on`);
|
||||
return;
|
||||
}
|
||||
|
||||
const categoryName = d.header.substring(d.header[0] === '_' ? 1 : 0, d.header.indexOf('.'));
|
||||
const itemName = d.header.substring(d.header.indexOf('.') + 1);
|
||||
let fields: { [k: string]: Column };
|
||||
if (categoryName in tables) {
|
||||
fields = tables[categoryName].columns;
|
||||
tables[categoryName].key.add(itemName);
|
||||
} else if (categoryName.toLowerCase() in tables) {
|
||||
// take case from category name in 'field' data as it is better if data is from cif dictionaries
|
||||
tables[categoryName] = tables[categoryName.toLowerCase()];
|
||||
fields = tables[categoryName].columns;
|
||||
} else {
|
||||
console.log(`category '${categoryName}' has no metadata`);
|
||||
fields = {};
|
||||
tables[categoryName] = {
|
||||
description: '',
|
||||
key: new Set(),
|
||||
columns: fields
|
||||
};
|
||||
}
|
||||
|
||||
const itemAliases = getAliases(d, imports, ctx);
|
||||
if (itemAliases) aliases[`${categoryName}.${itemName}`] = itemAliases;
|
||||
|
||||
const description = getDescription(d, imports, ctx) || '';
|
||||
|
||||
// need to use regex to check for matrix or vector items
|
||||
// as sub_category assignment is missing for some entries
|
||||
const subCategory = getSubCategory(d, imports, ctx);
|
||||
if (subCategory === 'cartesian_coordinate' || subCategory === 'fractional_coordinate') {
|
||||
fields[itemName] = CoordCol(description);
|
||||
} else if (FORCE_INT_FIELDS.includes(d.header)) {
|
||||
fields[itemName] = IntCol(description);
|
||||
console.log(`forcing int: ${d.header}`);
|
||||
} else if (FORCE_MATRIX_FIELDS.includes(d.header)) {
|
||||
fields[itemName] = FloatCol(description);
|
||||
fields[FORCE_MATRIX_FIELDS_MAP[d.header]] = MatrixCol(3, 3, description);
|
||||
console.log(`forcing matrix: ${d.header}`);
|
||||
} else if (subCategory === 'matrix') {
|
||||
fields[itemName.replace(reMatrixField, '')] = MatrixCol(3, 3, description);
|
||||
} else if (subCategory === 'vector') {
|
||||
fields[itemName.replace(reVectorField, '')] = VectorCol(3, description);
|
||||
} else {
|
||||
if (itemName.match(reMatrixField)) {
|
||||
fields[itemName.replace(reMatrixField, '')] = MatrixCol(3, 3, description);
|
||||
console.log(`${d.header} should have 'matrix' _item_sub_category.id`);
|
||||
} else if (itemName.match(reVectorField)) {
|
||||
fields[itemName.replace(reVectorField, '')] = VectorCol(3, description);
|
||||
console.log(`${d.header} should have 'vector' _item_sub_category.id`);
|
||||
} else {
|
||||
const code = getCode(d, imports, ctx);
|
||||
if (code) {
|
||||
let fieldType = getFieldType(code[0], description, code[1], code[2]);
|
||||
if (fieldType.type === 'str') {
|
||||
if (COMMA_SEPARATED_LIST_FIELDS.includes(d.header)) {
|
||||
fieldType = ListCol('str', ',', description);
|
||||
console.log(`forcing comma separated: ${d.header}`);
|
||||
} else if (SPACE_SEPARATED_LIST_FIELDS.includes(d.header)) {
|
||||
fieldType = ListCol('str', ' ', description);
|
||||
console.log(`forcing space separated: ${d.header}`);
|
||||
} else if (SEMICOLON_SEPARATED_LIST_FIELDS.includes(d.header)) {
|
||||
fieldType = ListCol('str', ';', description);
|
||||
console.log(`forcing space separated: ${d.header}`);
|
||||
}
|
||||
}
|
||||
if (d.header in EXTRA_ENUM_VALUES) {
|
||||
if (fieldType.type === 'enum') {
|
||||
fieldType.values.push(...EXTRA_ENUM_VALUES[d.header]);
|
||||
} else {
|
||||
console.warn(`expected enum: ${d.header}`);
|
||||
}
|
||||
}
|
||||
fields[itemName] = fieldType;
|
||||
} else {
|
||||
fields[itemName] = StrCol(description);
|
||||
// console.log(`could not determine code for '${d.header}'`)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { tables, aliases };
|
||||
}
|
||||
151
src/apps/cifschema/util/generate.ts
Normal file
151
src/apps/cifschema/util/generate.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Database, Filter, Column } from './schema';
|
||||
import { indentString } from '../../../mol-util/string';
|
||||
import { FieldPath } from '../../../mol-io/reader/cif/schema';
|
||||
|
||||
function header (name: string, info: string, moldataImportPath: string) {
|
||||
return `/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated '${name}' schema file. ${info}
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
import { Database, Column } from '${moldataImportPath}/db'
|
||||
|
||||
import Schema = Column.Schema`;
|
||||
}
|
||||
|
||||
function footer (name: string) {
|
||||
return `
|
||||
export type ${name}_Schema = typeof ${name}_Schema;
|
||||
export interface ${name}_Database extends Database<${name}_Schema> {}`;
|
||||
}
|
||||
|
||||
function getTypeShorthands(schema: Database, fields?: Filter) {
|
||||
const types = new Set<string>();
|
||||
Object.keys(schema.tables).forEach(table => {
|
||||
if (fields && !fields[table]) return;
|
||||
const { columns } = schema.tables[table];
|
||||
Object.keys(columns).forEach(columnName => {
|
||||
if (fields && !fields[table][columnName]) return;
|
||||
types.add(schema.tables[table].columns[columnName].type);
|
||||
});
|
||||
});
|
||||
const shorthands: string[] = [];
|
||||
types.forEach(type => {
|
||||
switch (type) {
|
||||
case 'str': shorthands.push('const str = Schema.str;'); break;
|
||||
case 'int': shorthands.push('const int = Schema.int;'); break;
|
||||
case 'float': shorthands.push('const float = Schema.float;'); break;
|
||||
case 'coord': shorthands.push('const coord = Schema.coord;'); break;
|
||||
case 'enum': shorthands.push('const Aliased = Schema.Aliased;'); break;
|
||||
case 'matrix': shorthands.push('const Matrix = Schema.Matrix;'); break;
|
||||
case 'vector': shorthands.push('const Vector = Schema.Vector;'); break;
|
||||
case 'list': shorthands.push('const List = Schema.List;'); break;
|
||||
}
|
||||
});
|
||||
return shorthands.join('\n');
|
||||
}
|
||||
|
||||
function getTypeDef(c: Column): string {
|
||||
switch (c.type) {
|
||||
case 'str': return 'str';
|
||||
case 'int': return 'int';
|
||||
case 'float': return 'float';
|
||||
case 'coord': return 'coord';
|
||||
case 'enum':
|
||||
return `Aliased<'${c.values.map(v => v.replace(/'/g, '\\\'')).join(`' | '`)}'>(${c.subType})`;
|
||||
case 'matrix':
|
||||
return `Matrix(${c.rows}, ${c.columns})`;
|
||||
case 'vector':
|
||||
return `Vector(${c.length})`;
|
||||
case 'list':
|
||||
if (c.subType === 'int') {
|
||||
return `List('${c.separator}', x => parseInt(x, 10))`;
|
||||
} else if (c.subType === 'float' || c.subType === 'coord') {
|
||||
return `List('${c.separator}', x => parseFloat(x))`;
|
||||
} else {
|
||||
return `List('${c.separator}', x => x)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const reSafePropertyName = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
|
||||
function safePropertyString(name: string) { return name.match(reSafePropertyName) ? name : `'${name}'`; }
|
||||
|
||||
function doc(description: string, spacesCount: number) {
|
||||
const spaces = ' '.repeat(spacesCount);
|
||||
return [
|
||||
`${spaces}/**`,
|
||||
`${indentString(description, 1, `${spaces} * `)}`.replace(/ +\n/g, '\n'),
|
||||
`${spaces} */`
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function generate (name: string, info: string, schema: Database, fields: Filter | undefined, moldataImportPath: string, addAliases: boolean) {
|
||||
const codeLines: string[] = [];
|
||||
|
||||
if (fields) {
|
||||
Object.keys(fields).forEach(table => {
|
||||
if (table in schema.tables) {
|
||||
const schemaTable = schema.tables[table];
|
||||
Object.keys(fields[table]).forEach(column => {
|
||||
if (!(column in schemaTable.columns)) {
|
||||
console.log(`filter field '${table}.${column}' not found in schema`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log(`filter category '${table}' not found in schema`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
codeLines.push(`export const ${name}_Schema = {`);
|
||||
Object.keys(schema.tables).forEach(table => {
|
||||
if (fields && !fields[table]) return;
|
||||
const { description, columns } = schema.tables[table];
|
||||
if (description) codeLines.push(doc(description, 4));
|
||||
codeLines.push(` ${safePropertyString(table)}: {`);
|
||||
Object.keys(columns).forEach(columnName => {
|
||||
if (fields && !fields[table][columnName]) return;
|
||||
const c = columns[columnName];
|
||||
const typeDef = getTypeDef(c);
|
||||
if (c.description) codeLines.push(doc(c.description, 8));
|
||||
codeLines.push(` ${safePropertyString(columnName)}: ${typeDef},`);
|
||||
});
|
||||
codeLines.push(' },');
|
||||
});
|
||||
codeLines.push('}');
|
||||
|
||||
if (addAliases) {
|
||||
codeLines.push('');
|
||||
codeLines.push(`export const ${name}_Aliases = {`);
|
||||
Object.keys(schema.aliases).forEach(path => {
|
||||
const [ table, columnName ] = path.split('.');
|
||||
if (fields && !fields[table]) return;
|
||||
if (fields && !fields[table][columnName]) return;
|
||||
|
||||
const filteredAliases = new Set<string>();
|
||||
schema.aliases[path].forEach(p => {
|
||||
if (!FieldPath.equal(p, path)) filteredAliases.add(FieldPath.canonical(p));
|
||||
});
|
||||
|
||||
if (filteredAliases.size === 0) return;
|
||||
codeLines.push(` ${safePropertyString(path)}: [`);
|
||||
filteredAliases.forEach(alias => {
|
||||
codeLines.push(` '${alias}',`);
|
||||
});
|
||||
codeLines.push(' ],');
|
||||
});
|
||||
codeLines.push('}');
|
||||
}
|
||||
|
||||
return `${header(name, info, moldataImportPath)}\n\n${getTypeShorthands(schema, fields)}\n\n${codeLines.join('\n')}\n${footer(name)}`;
|
||||
}
|
||||
20
src/apps/cifschema/util/helper.ts
Normal file
20
src/apps/cifschema/util/helper.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
export type Import = { save?: string, file?: string }
|
||||
|
||||
export function parseImportGet(s: string): Import[] {
|
||||
// [{'save':hi_ang_Fox_coeffs 'file':templ_attr.cif} {'save':hi_ang_Fox_c0 'file':templ_enum.cif}]
|
||||
// [{"file":'templ_enum.cif' "save":'H_M_ref'}]
|
||||
return s.trim().substring(2, s.length - 2).split(/}[ \n\t]*{/g).map(s => {
|
||||
const save = s.match(/('save'|"save"):([^ \t\n]+)/);
|
||||
const file = s.match(/('file'|"file"):([^ \t\n]+)/);
|
||||
return {
|
||||
save: save ? save[0].substr(7).replace(/['"]/g, '') : undefined,
|
||||
file: file ? file[0].substr(7).replace(/['"]/g, '') : undefined
|
||||
};
|
||||
});
|
||||
}
|
||||
77
src/apps/cifschema/util/schema.ts
Normal file
77
src/apps/cifschema/util/schema.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
export interface Database {
|
||||
tables: { [ tableName: string ]: Table }
|
||||
aliases: { [ path: string ]: string[] }
|
||||
}
|
||||
export interface Table {
|
||||
description: string
|
||||
key: Set<string>
|
||||
columns: { [ columnName: string ]: Column }
|
||||
}
|
||||
export type Column = IntCol | StrCol | FloatCol | CoordCol | EnumCol | VectorCol | MatrixCol | ListCol
|
||||
|
||||
type BaseCol = { description: string }
|
||||
|
||||
export type IntCol = { type: 'int' } & BaseCol
|
||||
export function IntCol(description: string): IntCol { return { type: 'int', description }; }
|
||||
|
||||
export type StrCol = { type: 'str' } & BaseCol
|
||||
export function StrCol(description: string): StrCol { return { type: 'str', description }; }
|
||||
|
||||
export type FloatCol = { type: 'float' } & BaseCol
|
||||
export function FloatCol(description: string): FloatCol { return { type: 'float', description }; }
|
||||
|
||||
export type CoordCol = { type: 'coord' } & BaseCol
|
||||
export function CoordCol(description: string): CoordCol { return { type: 'coord', description }; }
|
||||
|
||||
export type EnumCol = { type: 'enum', subType: 'int' | 'str', values: string[] } & BaseCol
|
||||
export function EnumCol(values: string[], subType: 'int' | 'str', description: string): EnumCol {
|
||||
return { type: 'enum', description, values, subType };
|
||||
}
|
||||
|
||||
export type VectorCol = { type: 'vector', length: number } & BaseCol
|
||||
export function VectorCol(length: number, description: string): VectorCol {
|
||||
return { type: 'vector', description, length };
|
||||
}
|
||||
|
||||
export type MatrixCol = { type: 'matrix', rows: number, columns: number } & BaseCol
|
||||
export function MatrixCol(columns: number, rows: number, description: string): MatrixCol {
|
||||
return { type: 'matrix', description, columns, rows };
|
||||
}
|
||||
|
||||
export type ListCol = { type: 'list', subType: 'int' | 'str' | 'float' | 'coord', separator: string } & BaseCol
|
||||
export function ListCol(subType: 'int' | 'str' | 'float' | 'coord', separator: string, description: string): ListCol {
|
||||
return { type: 'list', description, separator, subType };
|
||||
}
|
||||
|
||||
export type Filter = { [ table: string ]: { [ column: string ]: true } }
|
||||
|
||||
export function mergeFilters (...filters: Filter[]) {
|
||||
const n = filters.length;
|
||||
const mergedFilter: Filter = {};
|
||||
const fields: Map<string, number> = new Map();
|
||||
filters.forEach(filter => {
|
||||
Object.keys(filter).forEach(category => {
|
||||
Object.keys(filter[ category ]).forEach(field => {
|
||||
const key = `${category}.${field}`;
|
||||
const value = fields.get(key) || 0;
|
||||
fields.set(key, value + 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
fields.forEach((v, k) => {
|
||||
if (v !== n) return;
|
||||
const [categoryName, fieldName] = k.split('.');
|
||||
if (categoryName in mergedFilter) {
|
||||
mergedFilter[categoryName][fieldName] = true;
|
||||
} else {
|
||||
mergedFilter[categoryName] = { fieldName: true };
|
||||
}
|
||||
});
|
||||
return mergedFilter;
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createPlugin, DefaultPluginSpec } from '../../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../../mol-plugin/command';
|
||||
import { StateTransforms } from '../../../mol-plugin/state/transforms';
|
||||
import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation';
|
||||
import { PluginStateObject as PSO } from '../../../mol-plugin/state/objects';
|
||||
import { StateBuilder } from '../../../mol-state';
|
||||
import { Canvas3DProps } from '../../../mol-canvas3d/canvas3d';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
|
||||
type SupportedFormats = 'cif' | 'pdb'
|
||||
type LoadParams = { url: string, format?: SupportedFormats, assemblyId?: string }
|
||||
|
||||
const Canvas3DPresets = {
|
||||
illustrative: {
|
||||
multiSample: {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusionEnable: true,
|
||||
occlusionBias: 0.8,
|
||||
occlusionKernelSize: 6,
|
||||
outlineEnable: true,
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 1,
|
||||
lightIntensity: 0,
|
||||
}
|
||||
},
|
||||
occlusion: {
|
||||
multiSample: {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusionEnable: true,
|
||||
occlusionBias: 0.8,
|
||||
occlusionKernelSize: 6,
|
||||
outlineEnable: false,
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
lightIntensity: 0.6,
|
||||
}
|
||||
},
|
||||
standard: {
|
||||
multiSample: {
|
||||
mode: 'off' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusionEnable: false,
|
||||
outlineEnable: false,
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
lightIntensity: 0.6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Canvas3DPreset = keyof typeof Canvas3DPresets
|
||||
|
||||
function getPreset(preset: Canvas3DPreset) {
|
||||
switch (preset) {
|
||||
case 'illustrative': return Canvas3DPresets['illustrative']
|
||||
case 'standard': return Canvas3DPresets['standard']
|
||||
case 'occlusion': return Canvas3DPresets['occlusion']
|
||||
}
|
||||
}
|
||||
|
||||
class LightingDemo {
|
||||
plugin: PluginContext;
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
|
||||
}
|
||||
});
|
||||
|
||||
this.setPreset('illustrative');
|
||||
}
|
||||
|
||||
setPreset(preset: Canvas3DPreset) {
|
||||
const props = getPreset(preset)
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
|
||||
...props,
|
||||
multiSample: {
|
||||
...this.plugin.canvas3d!.props.multiSample,
|
||||
...props.multiSample
|
||||
},
|
||||
renderer: {
|
||||
...this.plugin.canvas3d!.props.renderer,
|
||||
...props.renderer
|
||||
},
|
||||
postprocessing: {
|
||||
...this.plugin.canvas3d!.props.postprocessing,
|
||||
...props.postprocessing
|
||||
},
|
||||
}});
|
||||
}
|
||||
|
||||
private download(b: StateBuilder.To<PSO.Root>, url: string) {
|
||||
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
|
||||
}
|
||||
|
||||
private parse(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) {
|
||||
const parsed = format === 'cif'
|
||||
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
|
||||
return parsed
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
|
||||
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' });
|
||||
}
|
||||
|
||||
private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'spacefill', {}, 'illustrative'), { ref: 'seq-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick'), { ref: 'het-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = '' }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.dataState;
|
||||
|
||||
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
|
||||
loadType = 'full';
|
||||
} else if (this.loadedParams.url === url) {
|
||||
if (state.select('asm').length > 0) loadType = 'update';
|
||||
}
|
||||
|
||||
let tree: StateBuilder.Root;
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
|
||||
tree = state.build();
|
||||
this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId));
|
||||
} else {
|
||||
tree = state.build();
|
||||
tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
|
||||
this.loadedParams = { url, format, assemblyId };
|
||||
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).LightingDemo = new LightingDemo();
|
||||
@@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<title>Mol* ModelServer Query Builder</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,134 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import * as Rx from 'rxjs'
|
||||
|
||||
import { QueryDefinition, QueryList } from '../../servers/model/server/api'
|
||||
|
||||
import './index.html'
|
||||
|
||||
interface State {
|
||||
query: Rx.BehaviorSubject<QueryDefinition>,
|
||||
id: Rx.BehaviorSubject<string>,
|
||||
params: Rx.BehaviorSubject<any>,
|
||||
isBinary: Rx.BehaviorSubject<boolean>,
|
||||
models: Rx.BehaviorSubject<number[]>,
|
||||
url: Rx.Subject<string>
|
||||
}
|
||||
|
||||
class Root extends React.Component<{ state: State }, { }> {
|
||||
render() {
|
||||
return <div>
|
||||
<div>
|
||||
Query: <QuerySelect state={this.props.state} />
|
||||
</div>
|
||||
<div>
|
||||
ID: <input type='text' onChange={t => this.props.state.id.next(t.currentTarget.value)} />
|
||||
</div>
|
||||
<div>
|
||||
Params:<br/>
|
||||
<QueryParams state={this.props.state} />
|
||||
</div>
|
||||
<div>
|
||||
Model numbers (empty for all): <ModelNums state={this.props.state} />
|
||||
</div>
|
||||
<div>
|
||||
<input type='checkbox' onChange={t => this.props.state.isBinary.next(!!t.currentTarget.checked)} /> Binary
|
||||
</div>
|
||||
<div>
|
||||
Query string:
|
||||
<QueryUrl state={this.props.state} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
class QuerySelect extends React.Component<{ state: State }> {
|
||||
render() {
|
||||
return <select onChange={s => this.props.state.query.next(QueryList[+s.currentTarget.value].definition)}>
|
||||
{ QueryList.map((q, i) => <option value={i} key={i} selected={i === 1}>{q.definition.niceName}</option>) }
|
||||
</select>
|
||||
}
|
||||
}
|
||||
|
||||
class QueryParams extends React.Component<{ state: State }, { prms: string }> {
|
||||
state = { prms: '' };
|
||||
|
||||
parseParams(str: string) {
|
||||
this.setState({ prms: str });
|
||||
try {
|
||||
const params = JSON.parse(str);
|
||||
this.props.state.params.next(params);
|
||||
} catch {
|
||||
this.props.state.params.next({});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.state.query.subscribe(q => this.setState({ prms: formatParams(q) }))
|
||||
}
|
||||
|
||||
render() {
|
||||
return <textarea style={{height: '300px'}} value={this.state.prms} cols={80} onChange={t => this.parseParams(t.currentTarget.value)} />;
|
||||
}
|
||||
}
|
||||
|
||||
class QueryUrl extends React.Component<{ state: State }, { queryString: string }> {
|
||||
state = { queryString: '' };
|
||||
|
||||
componentDidMount() {
|
||||
this.props.state.url.subscribe(url => this.setState({ queryString: url }))
|
||||
}
|
||||
|
||||
render() {
|
||||
return <input type='text' value={this.state.queryString} style={{ width: '800px' }} />
|
||||
}
|
||||
}
|
||||
|
||||
class ModelNums extends React.Component<{ state: State }> {
|
||||
render() {
|
||||
return <input type='text' defaultValue='1' style={{ width: '300px' }} onChange={t =>
|
||||
this.props.state.models.next(t.currentTarget.value.split(',')
|
||||
.map(v => v.trim())
|
||||
.filter(v => !!v)
|
||||
.map(v => +v)
|
||||
)} />
|
||||
}
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
query: new Rx.BehaviorSubject(QueryList[1].definition),
|
||||
id: new Rx.BehaviorSubject('1cbs'),
|
||||
params: new Rx.BehaviorSubject({ }),
|
||||
isBinary: new Rx.BehaviorSubject<boolean>(false),
|
||||
models: new Rx.BehaviorSubject<number[]>([]),
|
||||
url: new Rx.Subject()
|
||||
}
|
||||
|
||||
function formatParams(def: QueryDefinition) {
|
||||
const prms = Object.create(null);
|
||||
for (const p of def.jsonParams) {
|
||||
prms[p.name] = p.exampleValues ? p.exampleValues[0] : void 0;
|
||||
}
|
||||
return JSON.stringify(prms, void 0, 2);
|
||||
}
|
||||
|
||||
function formatUrl() {
|
||||
const json = JSON.stringify({
|
||||
name: state.query.value.name,
|
||||
id: state.id.value,
|
||||
modelNums: state.models.value.length ? state.models.value : void 0,
|
||||
binary: state.isBinary.value,
|
||||
params: state.params.value
|
||||
});
|
||||
state.url.next(encodeURIComponent(json));
|
||||
}
|
||||
|
||||
Rx.merge(state.query, state.id, state.params, state.isBinary, state.models).subscribe(s => formatUrl());
|
||||
|
||||
ReactDOM.render(<Root state={state} />, document.getElementById('app'));
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as _ from '../../mol-plugin/state/transforms'
|
||||
import * as _ from '../../mol-plugin-state/transforms';
|
||||
import { StateTransformer, StateObject } from '../../mol-state';
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
import * as fs from 'fs';
|
||||
@@ -13,7 +13,7 @@ import { PluginContext } from '../../mol-plugin/context';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
|
||||
// force the transform to be evaluated
|
||||
_.StateTransforms.Data.Download.id
|
||||
_.StateTransforms.Data.Download.id;
|
||||
|
||||
// Empty plugin context
|
||||
const ctx = new PluginContext({
|
||||
@@ -32,7 +32,7 @@ function writeTransformer(t: StateTransformer) {
|
||||
StringBuilder.write(builder, `## <a name="${t.id.replace('.', '-')}"></a>${t.id} :: ${typeToString(t.definition.from)} -> ${typeToString(t.definition.to)}`);
|
||||
StringBuilder.newline(builder);
|
||||
if (t.definition.display.description) {
|
||||
StringBuilder.write(builder, `*${t.definition.display.description}*`)
|
||||
StringBuilder.write(builder, `*${t.definition.display.description}*`);
|
||||
StringBuilder.newline(builder);
|
||||
}
|
||||
StringBuilder.newline(builder);
|
||||
@@ -48,7 +48,7 @@ function writeTransformer(t: StateTransformer) {
|
||||
StringBuilder.write(builder, `\`\`\`js\n${JSON.stringify(ParamDefinition.getDefaultValues(params), null, 2)}\n\`\`\``);
|
||||
StringBuilder.newline(builder);
|
||||
}
|
||||
StringBuilder.write(builder, '----------------------------')
|
||||
StringBuilder.write(builder, '----------------------------');
|
||||
StringBuilder.newline(builder);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ transformers.forEach(t => {
|
||||
StringBuilder.newline(builder);
|
||||
});
|
||||
StringBuilder.newline(builder);
|
||||
StringBuilder.write(builder, '----------------------------')
|
||||
StringBuilder.write(builder, '----------------------------');
|
||||
StringBuilder.newline(builder);
|
||||
transformers.forEach(t => writeTransformer(t));
|
||||
|
||||
|
||||
@@ -19,8 +19,10 @@ function paramInfo(param: PD.Any, offset: number): string {
|
||||
case 'conditioned': return getParams(param.conditionParams, offset);
|
||||
case 'multi-select': return `Array of ${oToS(param.options)}`;
|
||||
case 'color': return 'Color as 0xrrggbb';
|
||||
case 'color-list': return `One of ${oToS(param.options)}`;
|
||||
case 'color-list': return `A list of colors as 0xrrggbb`;
|
||||
case 'vec3': return `3D vector [x, y, z]`;
|
||||
case 'mat4': return `4x4 transformation matrix`;
|
||||
case 'url': return `URL couple with unique identifier`;
|
||||
case 'file': return `JavaScript File Handle`;
|
||||
case 'file-list': return `JavaScript FileList Handle`;
|
||||
case 'select': return `One of ${oToS(param.options)}`;
|
||||
@@ -39,7 +41,7 @@ function paramInfo(param: PD.Any, offset: number): string {
|
||||
}
|
||||
}
|
||||
|
||||
function oToS(options: readonly (readonly [string, string])[]) {
|
||||
function oToS(options: readonly (readonly [string, string] | readonly [string, string, string | undefined])[]) {
|
||||
return options.map(o => `'${o[0]}'`).join(', ');
|
||||
}
|
||||
|
||||
|
||||
@@ -4,19 +4,19 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as util from 'util'
|
||||
import * as fs from 'fs'
|
||||
import fetch from 'node-fetch'
|
||||
import * as util from 'util';
|
||||
import * as fs from 'fs';
|
||||
import fetch from 'node-fetch';
|
||||
require('util.promisify').shim();
|
||||
|
||||
import { CIF } from '../../mol-io/reader/cif'
|
||||
import { Progress } from '../../mol-task'
|
||||
import { CIF } from '../../mol-io/reader/cif';
|
||||
import { Progress } from '../../mol-task';
|
||||
|
||||
const readFileAsync = util.promisify(fs.readFile);
|
||||
|
||||
async function readFile(path: string) {
|
||||
if (path.match(/\.bcif$/)) {
|
||||
const input = await readFileAsync(path)
|
||||
const input = await readFileAsync(path);
|
||||
const data = new Uint8Array(input.byteLength);
|
||||
for (let i = 0; i < input.byteLength; i++) data[i] = input[i];
|
||||
return data;
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as argparse from 'argparse'
|
||||
import * as argparse from 'argparse';
|
||||
require('util.promisify').shim();
|
||||
|
||||
import { CifFrame } from '../../mol-io/reader/cif'
|
||||
import { Model, Structure, StructureElement, Unit, StructureProperties, UnitRing } from '../../mol-model/structure'
|
||||
import { CifFrame } from '../../mol-io/reader/cif';
|
||||
import { Model, Structure, StructureElement, Unit, StructureProperties, UnitRing } from '../../mol-model/structure';
|
||||
// import { Run, Progress } from '../../mol-task'
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { openCif, downloadCif } from './helpers';
|
||||
@@ -32,30 +32,30 @@ export async function readCifFile(path: string) {
|
||||
}
|
||||
|
||||
export function atomLabel(model: Model, aI: number) {
|
||||
const { atoms, residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy
|
||||
const { label_atom_id } = atoms
|
||||
const { label_comp_id, label_seq_id } = residues
|
||||
const { label_asym_id } = chains
|
||||
const rI = residueAtomSegments.index[aI]
|
||||
const cI = chainAtomSegments.index[aI]
|
||||
return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`
|
||||
const { atoms, residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy;
|
||||
const { label_atom_id } = atoms;
|
||||
const { label_comp_id, label_seq_id } = residues;
|
||||
const { label_asym_id } = chains;
|
||||
const rI = residueAtomSegments.index[aI];
|
||||
const cI = chainAtomSegments.index[aI];
|
||||
return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`;
|
||||
}
|
||||
|
||||
export function residueLabel(model: Model, rI: number) {
|
||||
const { residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy
|
||||
const { label_comp_id, label_seq_id } = residues
|
||||
const { label_asym_id } = chains
|
||||
const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]]
|
||||
return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`
|
||||
const { residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy;
|
||||
const { label_comp_id, label_seq_id } = residues;
|
||||
const { label_asym_id } = chains;
|
||||
const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]];
|
||||
return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`;
|
||||
}
|
||||
|
||||
export function printSecStructure(model: Model) {
|
||||
console.log('\nSecondary Structure\n=============');
|
||||
const { residues } = model.atomicHierarchy;
|
||||
const secondaryStructure = ModelSecondaryStructure.Provider.get(model);
|
||||
if (!secondaryStructure) return
|
||||
if (!secondaryStructure) return;
|
||||
|
||||
const { key, elements } = secondaryStructure
|
||||
const { key, elements } = secondaryStructure;
|
||||
const count = residues._rowCount;
|
||||
let rI = 0;
|
||||
while (rI < count) {
|
||||
@@ -116,25 +116,13 @@ export function printSequence(model: Model) {
|
||||
const { byEntityKey } = model.sequence;
|
||||
for (const key of Object.keys(byEntityKey)) {
|
||||
const { sequence, entityId } = byEntityKey[+key];
|
||||
const { seqId, compId } = sequence
|
||||
const { seqId, compId } = sequence;
|
||||
console.log(`${entityId} (${sequence.kind} ${seqId.value(0)} (offset ${sequence.offset}), ${seqId.value(seqId.rowCount - 1)}) (${compId.value(0)}, ${compId.value(compId.rowCount - 1)})`);
|
||||
console.log(`${Sequence.getSequenceString(sequence)}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
export function printModRes(model: Model) {
|
||||
console.log('\nModified Residues\n=============');
|
||||
const map = model.properties.modifiedResidues.parentId;
|
||||
const { label_comp_id, _rowCount } = model.atomicHierarchy.residues;
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
const comp_id = label_comp_id.value(i);
|
||||
if (!map.has(comp_id)) continue;
|
||||
console.log(`[${i}] ${map.get(comp_id)} -> ${comp_id}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
export function printRings(structure: Structure) {
|
||||
console.log('\nRings\n=============');
|
||||
for (const unit of structure.units) {
|
||||
@@ -144,7 +132,7 @@ export function printRings(structure: Structure) {
|
||||
for (let i = 0, _i = Math.min(5, all.length); i < _i; i++) {
|
||||
fps[fps.length] = UnitRing.fingerprint(unit, all[i]);
|
||||
}
|
||||
if (all.length > 5) fps.push('...')
|
||||
if (all.length > 5) fps.push('...');
|
||||
console.log(`Unit ${unit.id}, ${all.length} ring(s), ${byFingerprint.size} different fingerprint(s).\n ${fps.join(', ')}`);
|
||||
}
|
||||
console.log();
|
||||
@@ -183,8 +171,8 @@ export function printUnits(structure: Structure) {
|
||||
|
||||
export function printSymmetryInfo(model: Model) {
|
||||
console.log('\nSymmetry Info\n=============');
|
||||
const symmetry = ModelSymmetry.Provider.get(model)
|
||||
if (!symmetry) return
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
if (!symmetry) return;
|
||||
const { size, anglesInRadians } = symmetry.spacegroup.cell;
|
||||
console.log(`Spacegroup: ${symmetry.spacegroup.name} size: ${Vec3.toString(size)} angles: ${Vec3.toString(anglesInRadians)}`);
|
||||
console.log(`Assembly names: ${symmetry.assemblies.map(a => a.id).join(', ')}`);
|
||||
@@ -221,12 +209,11 @@ async function run(frame: CifFrame, args: Args) {
|
||||
if (args.rings) printRings(structure);
|
||||
if (args.intraBonds) printBonds(structure, true, false);
|
||||
if (args.interBonds) printBonds(structure, false, true);
|
||||
if (args.mod) printModRes(models[0]);
|
||||
if (args.sec) printSecStructure(models[0]);
|
||||
}
|
||||
|
||||
async function runDL(pdb: string, args: Args) {
|
||||
const mmcif = await downloadFromPdb(pdb)
|
||||
const mmcif = await downloadFromPdb(pdb);
|
||||
run(mmcif, args);
|
||||
}
|
||||
|
||||
@@ -268,5 +255,5 @@ interface Args {
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
|
||||
if (args.download) runDL(args.download, args)
|
||||
else if (args.file) runFile(args.file, args)
|
||||
if (args.download) runDL(args.download, args);
|
||||
else if (args.file) runFile(args.file, args);
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as fs from 'fs'
|
||||
import * as argparse from 'argparse'
|
||||
import * as util from 'util'
|
||||
import * as fs from 'fs';
|
||||
import * as argparse from 'argparse';
|
||||
import * as util from 'util';
|
||||
|
||||
import { VolumeData, VolumeIsoValue } from '../../mol-model/volume'
|
||||
import { downloadCif } from './helpers'
|
||||
import { CIF } from '../../mol-io/reader/cif'
|
||||
import { VolumeData, VolumeIsoValue } from '../../mol-model/volume';
|
||||
import { downloadCif } from './helpers';
|
||||
import { CIF } from '../../mol-io/reader/cif';
|
||||
import { DensityServer_Data_Database } from '../../mol-io/reader/cif/schema/density-server';
|
||||
import { Table } from '../../mol-data/db';
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
@@ -34,9 +34,8 @@ function print(data: Volume) {
|
||||
const { volume_data_3d_info } = data.source;
|
||||
const row = Table.getRow(volume_data_3d_info, 0);
|
||||
console.log(row);
|
||||
console.log(data.volume.cell);
|
||||
if (data.volume.transform) console.log(data.volume.transform);
|
||||
console.log(data.volume.dataStats);
|
||||
console.log(data.volume.fractionalBox);
|
||||
}
|
||||
|
||||
async function doMesh(data: Volume, filename: string) {
|
||||
|
||||
@@ -1,480 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateAction } from '../../../../mol-state';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../../../mol-plugin/state/objects';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Ingredient, CellPacking, Cell } from './data';
|
||||
import { getFromPdb, getFromCellPackDB } from './util';
|
||||
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../../../mol-model/structure';
|
||||
import { trajectoryFromMmCIF, MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { trajectoryFromPDB } from '../../../../mol-model-formats/structure/pdb';
|
||||
import { Mat4, Vec3, Quat } from '../../../../mol-math/linear-algebra';
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { StructureRepresentation3DHelpers } from '../../../../mol-plugin/state/transforms/representation';
|
||||
import { StateTransforms } from '../../../../mol-plugin/state/transforms';
|
||||
import { distinctColors } from '../../../../mol-util/color/distinct';
|
||||
import { ModelIndexColorThemeProvider } from '../../../../mol-theme/color/model-index';
|
||||
import { Hcl } from '../../../../mol-util/color/spaces/hcl';
|
||||
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl } from './state';
|
||||
import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
|
||||
import { getMatFromResamplePoints } from './curve';
|
||||
import { compile } from '../../../../mol-script/runtime/query/compiler';
|
||||
import { UniformColorThemeProvider } from '../../../../mol-theme/color/uniform';
|
||||
import { ThemeRegistryContext } from '../../../../mol-theme/theme';
|
||||
import { ColorTheme } from '../../../../mol-theme/color';
|
||||
import { CifCategory, CifField } from '../../../../mol-io/reader/cif';
|
||||
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Column } from '../../../../mol-data/db';
|
||||
import { createModels } from '../../../../mol-model-formats/structure/basic/parser';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/results/${fileName}`
|
||||
}
|
||||
|
||||
async function getModel(id: string, baseUrl: string) {
|
||||
let model: Model;
|
||||
if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
|
||||
// return
|
||||
const cif = await getFromPdb(id)
|
||||
model = (await trajectoryFromMmCIF(cif).run())[0]
|
||||
} else {
|
||||
const pdb = await getFromCellPackDB(id, baseUrl)
|
||||
model = (await trajectoryFromPDB(pdb).run())[0]
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
async function getStructure(model: Model, props: { assembly?: string } = {}) {
|
||||
let structure = Structure.ofModel(model)
|
||||
const { assembly } = props
|
||||
|
||||
if (assembly) {
|
||||
structure = await StructureSymmetry.buildAssembly(structure, assembly).run()
|
||||
}
|
||||
|
||||
const query = MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer'])
|
||||
})
|
||||
])
|
||||
const compiled = compile<StructureSelection>(query)
|
||||
const result = compiled(new QueryContext(structure))
|
||||
structure = StructureSelection.unionStructure(result)
|
||||
|
||||
return structure
|
||||
}
|
||||
|
||||
function getTransform(trans: Vec3, rot: Quat) {
|
||||
const q: Quat = Quat.create(-rot[3], rot[0], rot[1], rot[2])
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q)
|
||||
Mat4.transpose(m, m)
|
||||
Mat4.scale(m, m, Vec3.create(-1.0, 1.0, -1.0))
|
||||
Mat4.setTranslation(m, trans)
|
||||
return m
|
||||
}
|
||||
|
||||
function getResultTransforms(results: Ingredient['results']) {
|
||||
return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1]))
|
||||
}
|
||||
|
||||
function getCurveTransforms(ingredient: Ingredient) {
|
||||
const n = ingredient.nbCurve || 0
|
||||
const instances: Mat4[] = []
|
||||
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const cname = `curve${i}`
|
||||
if (!(cname in ingredient)) {
|
||||
// console.warn(`Expected '${cname}' in ingredient`)
|
||||
continue
|
||||
}
|
||||
const _points = ingredient[cname] as Vec3[]
|
||||
if (_points.length <= 2) {
|
||||
// TODO handle curve with 2 or less points
|
||||
continue
|
||||
}
|
||||
const points = new Float32Array(_points.length * 3)
|
||||
for (let i = 0, il = _points.length; i < il; ++i) Vec3.toArray(_points[i], points, i * 3)
|
||||
const newInstances = getMatFromResamplePoints(points)
|
||||
instances.push(...newInstances)
|
||||
}
|
||||
|
||||
return instances
|
||||
}
|
||||
|
||||
function getAssembly(transforms: Mat4[], structure: Structure) {
|
||||
const builder = Structure.Builder()
|
||||
const { units } = structure;
|
||||
|
||||
for (let i = 0, il = transforms.length; i < il; ++i) {
|
||||
const id = `${i + 1}`
|
||||
const op = SymmetryOperator.create(id, transforms[i], { id, operList: [ id ] })
|
||||
for (const unit of units) {
|
||||
builder.addWithOperator(unit, op)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.getStructure();
|
||||
}
|
||||
|
||||
function getCifCurve(name: string, transforms: Mat4[], model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) throw new Error('mmcif source data needed')
|
||||
|
||||
const { db } = model.sourceData.data
|
||||
const d = db.atom_site
|
||||
const n = d._rowCount
|
||||
const rowCount = n * transforms.length
|
||||
|
||||
const { offsets, count } = model.atomicHierarchy.chainAtomSegments
|
||||
|
||||
const x = d.Cartn_x.toArray()
|
||||
const y = d.Cartn_y.toArray()
|
||||
const z = d.Cartn_z.toArray()
|
||||
|
||||
const Cartn_x = new Float32Array(rowCount)
|
||||
const Cartn_y = new Float32Array(rowCount)
|
||||
const Cartn_z = new Float32Array(rowCount)
|
||||
const map = new Uint32Array(rowCount)
|
||||
const seq = new Int32Array(rowCount)
|
||||
let offset = 0
|
||||
for (let c = 0; c < count; ++c) {
|
||||
const cStart = offsets[c]
|
||||
const cEnd = offsets[c + 1]
|
||||
const cLength = cEnd - cStart
|
||||
for (let t = 0, tl = transforms.length; t < tl; ++t) {
|
||||
const m = transforms[t]
|
||||
for (let j = cStart; j < cEnd; ++j) {
|
||||
const i = offset + j - cStart
|
||||
const xj = x[j], yj = y[j], zj = z[j]
|
||||
Cartn_x[i] = m[0] * xj + m[4] * yj + m[8] * zj + m[12]
|
||||
Cartn_y[i] = m[1] * xj + m[5] * yj + m[9] * zj + m[13]
|
||||
Cartn_z[i] = m[2] * xj + m[6] * yj + m[10] * zj + m[14]
|
||||
map[i] = j
|
||||
seq[i] = t + 1
|
||||
}
|
||||
offset += cLength
|
||||
}
|
||||
}
|
||||
|
||||
function multColumn<T>(column: Column<T>) {
|
||||
const array = column.toArray()
|
||||
return Column.ofLambda({
|
||||
value: row => array[map[row]],
|
||||
areValuesEqual: (rowA, rowB) => map[rowA] === map[rowB] || array[map[rowA]] === array[map[rowB]],
|
||||
rowCount, schema: column.schema
|
||||
})
|
||||
}
|
||||
|
||||
const _atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = {
|
||||
auth_asym_id: CifField.ofColumn(multColumn(d.auth_asym_id)),
|
||||
auth_atom_id: CifField.ofColumn(multColumn(d.auth_atom_id)),
|
||||
auth_comp_id: CifField.ofColumn(multColumn(d.auth_comp_id)),
|
||||
auth_seq_id: CifField.ofNumbers(seq),
|
||||
|
||||
B_iso_or_equiv: CifField.ofColumn(Column.ofConst(0, rowCount, Column.Schema.float)),
|
||||
Cartn_x: CifField.ofNumbers(Cartn_x),
|
||||
Cartn_y: CifField.ofNumbers(Cartn_y),
|
||||
Cartn_z: CifField.ofNumbers(Cartn_z),
|
||||
group_PDB: CifField.ofColumn(Column.ofConst('ATOM', rowCount, Column.Schema.str)),
|
||||
id: CifField.ofColumn(Column.ofLambda({
|
||||
value: row => row,
|
||||
areValuesEqual: (rowA, rowB) => rowA === rowB,
|
||||
rowCount, schema: d.id.schema,
|
||||
})),
|
||||
|
||||
label_alt_id: CifField.ofColumn(multColumn(d.label_alt_id)),
|
||||
|
||||
label_asym_id: CifField.ofColumn(multColumn(d.label_asym_id)),
|
||||
label_atom_id: CifField.ofColumn(multColumn(d.label_atom_id)),
|
||||
label_comp_id: CifField.ofColumn(multColumn(d.label_comp_id)),
|
||||
label_seq_id: CifField.ofNumbers(seq),
|
||||
label_entity_id: CifField.ofColumn(Column.ofConst('1', rowCount, Column.Schema.str)),
|
||||
|
||||
occupancy: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.float)),
|
||||
type_symbol: CifField.ofColumn(multColumn(d.type_symbol)),
|
||||
|
||||
pdbx_PDB_ins_code: CifField.ofColumn(Column.ofConst('', rowCount, Column.Schema.str)),
|
||||
pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.int)),
|
||||
}
|
||||
|
||||
const categories = {
|
||||
entity: CifCategory.ofTable('entity', db.entity),
|
||||
chem_comp: CifCategory.ofTable('chem_comp', db.chem_comp),
|
||||
atom_site: CifCategory.ofFields('atom_site', _atom_site)
|
||||
}
|
||||
|
||||
return {
|
||||
header: name,
|
||||
categoryNames: Object.keys(categories),
|
||||
categories
|
||||
};
|
||||
}
|
||||
|
||||
async function getCurve(name: string, transforms: Mat4[], model: Model) {
|
||||
const cif = getCifCurve(name, transforms, model)
|
||||
|
||||
const curveModelTask = Task.create('Curve Model', async ctx => {
|
||||
const format = MmcifFormat.fromFrame(cif)
|
||||
const models = await createModels(format.data.db, format, ctx)
|
||||
return models[0]
|
||||
})
|
||||
|
||||
const curveModel = await curveModelTask.run()
|
||||
return getStructure(curveModel)
|
||||
}
|
||||
|
||||
async function getIngredientStructure(ingredient: Ingredient, baseUrl: string) {
|
||||
const { name, source, results, nbCurve } = ingredient
|
||||
|
||||
// TODO can these be added to the library?
|
||||
if (name === 'HIV1_CAhex_0_1_0') return
|
||||
if (name === 'HIV1_CAhexCyclophilA_0_1_0') return
|
||||
if (name === 'iLDL') return
|
||||
if (name === 'peptides') return
|
||||
if (name === 'lypoglycane') return
|
||||
|
||||
if (source.pdb === 'None') return
|
||||
|
||||
const model = await getModel(source.pdb || name, baseUrl)
|
||||
if (!model) return
|
||||
|
||||
if (nbCurve) {
|
||||
return getCurve(name, getCurveTransforms(ingredient), model)
|
||||
} else {
|
||||
const structure = await getStructure(model, { assembly: source.biomt ? '1' : undefined })
|
||||
return getAssembly(getResultTransforms(results), structure)
|
||||
}
|
||||
}
|
||||
|
||||
export function createStructureFromCellPack(packing: CellPacking, baseUrl: string) {
|
||||
return Task.create('Create Packing Structure', async ctx => {
|
||||
const { ingredients, name } = packing
|
||||
const structures: Structure[] = []
|
||||
for (const iName in ingredients) {
|
||||
if (ctx.shouldUpdate) await ctx.update(iName)
|
||||
const s = await getIngredientStructure(ingredients[iName], baseUrl)
|
||||
if (s) structures.push(s)
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - units`)
|
||||
const builder = Structure.Builder({ label: name })
|
||||
let offsetInvariantId = 0
|
||||
for (const s of structures) {
|
||||
if (ctx.shouldUpdate) await ctx.update(`${s.label}`)
|
||||
let maxInvariantId = 0
|
||||
for (const u of s.units) {
|
||||
const invariantId = u.invariantId + offsetInvariantId
|
||||
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId
|
||||
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId)
|
||||
}
|
||||
offsetInvariantId += maxInvariantId
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`)
|
||||
const s = builder.getStructure()
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
const RepresentationOptions = PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'ellipsoid'] as const)
|
||||
type RepresentationName = (typeof RepresentationOptions)[0][0]
|
||||
|
||||
export const LoadCellPackModel = StateAction.build({
|
||||
display: { name: 'Load CellPack Model' },
|
||||
params: {
|
||||
id: PD.Select('influenza_model1.json', [
|
||||
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
||||
['influenza_model1.json', 'influenza_model1'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
['curveTest', 'Curve Test'],
|
||||
] as const),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
preset: PD.Group({
|
||||
traceOnly: PD.Boolean(false),
|
||||
representation: PD.Select('spacefill', RepresentationOptions)
|
||||
}, { isExpanded: true })
|
||||
},
|
||||
from: PSO.Root
|
||||
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
|
||||
const url = getCellPackModelUrl(params.id, params.baseUrl)
|
||||
|
||||
const root = state.build().toRoot();
|
||||
|
||||
let cellPackBuilder: any
|
||||
|
||||
if (params.id === 'curveTest') {
|
||||
const url = `${params.baseUrl}/extras/rna_allpoints.json`
|
||||
const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
|
||||
const { points } = await (new Response(data)).json() as { points: number[] }
|
||||
const curve0: Vec3[] = []
|
||||
for (let j = 0, jl = Math.min(points.length, 3 * 100); j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j))
|
||||
}
|
||||
const cell: Cell = {
|
||||
recipe: { setupfile: '', paths: [], version: '', name: 'Curve Test' },
|
||||
compartments: {
|
||||
'CurveCompartment': {
|
||||
interior: {
|
||||
ingredients: {
|
||||
'CurveIngredient': {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cellPackBuilder = root
|
||||
.apply(StateTransforms.Data.ImportJson, { data: cell }, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack)
|
||||
} else {
|
||||
cellPackBuilder = root
|
||||
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.id }, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack)
|
||||
|
||||
|
||||
}
|
||||
|
||||
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(taskCtx)
|
||||
const { packings } = cellPackObject.data
|
||||
const tree = state.build().to(cellPackBuilder.ref);
|
||||
|
||||
const isHiv = (
|
||||
params.id === 'BloodHIV1.0_mixed_fixed_nc1.cpr' ||
|
||||
params.id === 'HIV-1_0.1.6-8_mixed_radii_pdb.cpr'
|
||||
)
|
||||
|
||||
if (isHiv) {
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
|
||||
const url = `${params.baseUrl}/extras/rna_allpoints.json`
|
||||
const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
|
||||
const { points } = await (new Response(data)).json() as { points: number[] }
|
||||
|
||||
const curve0: Vec3[] = []
|
||||
for (let j = 0, jl = points.length; j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j))
|
||||
}
|
||||
packings[i].ingredients['RNA'] = {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const colors = distinctColors(packings.length)
|
||||
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
const hcl = Hcl.fromColor(Hcl(), colors[i])
|
||||
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number]
|
||||
const p = { packing: i, baseUrl: params.baseUrl }
|
||||
|
||||
let cellpackTree = tree.apply(StructureFromCellpack, p)
|
||||
if (params.preset.traceOnly) {
|
||||
const expression = MS.struct.generator.atomGroups({
|
||||
'atom-test': MS.core.logic.or([
|
||||
MS.core.rel.eq([MS.ammp('label_atom_id'), 'CA']),
|
||||
MS.core.rel.eq([MS.ammp('label_atom_id'), 'P'])
|
||||
])
|
||||
})
|
||||
cellpackTree = cellpackTree.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression }, { state: { isGhost: true } }) as any
|
||||
}
|
||||
cellpackTree
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.createParams(ctx, Structure.Empty, {
|
||||
repr: getReprParams(ctx, params.preset),
|
||||
color: getColorParams(hue)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (isHiv) {
|
||||
const url = `${params.baseUrl}/membranes/hiv_lipids.bcif`
|
||||
tree.apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.StructureFromModel, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Misc.CreateGroup, { label: 'HIV1_envelope_Membrane' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.createParams(ctx, Structure.Empty, {
|
||||
repr: getReprParams(ctx, params.preset),
|
||||
color: UniformColorThemeProvider
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
console.time('cellpack')
|
||||
await state.updateTree(tree).runInContext(taskCtx);
|
||||
console.timeEnd('cellpack')
|
||||
}));
|
||||
|
||||
function getReprParams(ctx: PluginContext, params: { representation: RepresentationName, traceOnly: boolean }) {
|
||||
const { representation, traceOnly } = params
|
||||
switch (representation) {
|
||||
case 'spacefill':
|
||||
return traceOnly
|
||||
? [
|
||||
ctx.structureRepresentation.registry.get('spacefill'),
|
||||
() => ({ sizeFactor: 2, ignoreHydrogens: true })
|
||||
] as [any, any]
|
||||
: [
|
||||
ctx.structureRepresentation.registry.get('spacefill'),
|
||||
() => ({ ignoreHydrogens: true })
|
||||
] as [any, any]
|
||||
case 'gaussian-surface':
|
||||
return [
|
||||
ctx.structureRepresentation.registry.get('gaussian-surface'),
|
||||
() => ({
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2,
|
||||
alpha: 1.0, flatShaded: false, doubleSided: false,
|
||||
ignoreHydrogens: true
|
||||
})
|
||||
] as [any, any]
|
||||
case 'point':
|
||||
return [
|
||||
ctx.structureRepresentation.registry.get('point'),
|
||||
() => ({ ignoreHydrogens: true })
|
||||
] as [any, any]
|
||||
case 'ellipsoid':
|
||||
return [
|
||||
ctx.structureRepresentation.registry.get('orientation'),
|
||||
() => ({})
|
||||
] as [any, any]
|
||||
}
|
||||
}
|
||||
|
||||
function getColorParams(hue: [number, number]) {
|
||||
return [
|
||||
ModelIndexColorThemeProvider,
|
||||
(c: ColorTheme.Provider<any>, ctx: ThemeRegistryContext) => {
|
||||
return {
|
||||
palette: {
|
||||
name: 'generate',
|
||||
params: {
|
||||
hue, chroma: [30, 80], luminance: [15, 85],
|
||||
clusteringStepCount: 50, minSampleCount: 800,
|
||||
maxCount: 75
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
] as [any, any]
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin/state/objects';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { CellPack as _CellPack, Cell, CellPacking } from './data';
|
||||
import { createStructureFromCellPack } from './model';
|
||||
|
||||
// export const DefaultCellPackBaseUrl = 'https://cdn.jsdelivr.net/gh/mesoscope/cellPACK_data@master/cellPACK_database_1.1.0/'
|
||||
export const DefaultCellPackBaseUrl = 'https://mgldev.scripps.edu/projects/autoPACK/web/cellpackproject/'
|
||||
|
||||
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
|
||||
|
||||
export { ParseCellPack }
|
||||
type ParseCellPack = typeof ParseCellPack
|
||||
const ParseCellPack = PluginStateTransform.BuiltIn({
|
||||
name: 'parse-cellpack',
|
||||
display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
|
||||
from: PSO.Format.Json,
|
||||
to: CellPack
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse CellPack', async ctx => {
|
||||
const cell = a.data as Cell
|
||||
|
||||
const packings: CellPacking[] = []
|
||||
const { compartments, cytoplasme } = cell
|
||||
if (compartments) {
|
||||
for (const name in compartments) {
|
||||
const { surface, interior } = compartments[name]
|
||||
if (surface) packings.push({ name, location: 'surface', ingredients: surface.ingredients })
|
||||
if (interior) packings.push({ name, location: 'interior', ingredients: interior.ingredients })
|
||||
}
|
||||
}
|
||||
if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients })
|
||||
|
||||
return new CellPack({ cell, packings });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export { StructureFromCellpack }
|
||||
type StructureFromCellpack = typeof ParseCellPack
|
||||
const StructureFromCellpack = PluginStateTransform.BuiltIn({
|
||||
name: 'structure-from-cellpack',
|
||||
display: { name: 'Structure from CellPack', description: 'Create Structure from CellPack Packing' },
|
||||
from: CellPack,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: a => {
|
||||
if (!a) {
|
||||
return {
|
||||
packing: PD.Numeric(0, {}, { description: 'Packing Index' }),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl)
|
||||
};
|
||||
}
|
||||
const options = a.data.packings.map((d, i) => [i, d.name] as [number, string])
|
||||
return {
|
||||
packing: PD.Select(0, options),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl)
|
||||
}
|
||||
}
|
||||
})({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Structure from CellPack', async ctx => {
|
||||
const packing = a.data.packings[params.packing]
|
||||
const structure = await createStructureFromCellPack(packing, params.baseUrl).runInContext(ctx)
|
||||
return new PSO.Molecule.Structure(structure, { label: packing.name })
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CIF } from '../../../../mol-io/reader/cif'
|
||||
import { parsePDB } from '../../../../mol-io/reader/pdb/parser';
|
||||
|
||||
async function parseCif(data: string|Uint8Array) {
|
||||
const comp = CIF.parse(data);
|
||||
const parsed = await comp.run();
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
async function parsePDBfile(data: string, id: string) {
|
||||
const comp = parsePDB(data, id);
|
||||
const parsed = await comp.run();
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
async function downloadCif(url: string, isBinary: boolean) {
|
||||
const data = await fetch(url);
|
||||
return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text());
|
||||
}
|
||||
|
||||
async function downloadPDB(url: string, id: string) {
|
||||
const data = await fetch(url);
|
||||
return parsePDBfile(await data.text(), id);
|
||||
}
|
||||
|
||||
export async function getFromPdb(id: string) {
|
||||
const parsed = await downloadCif(`https://files.rcsb.org/download/${id}.cif`, false);
|
||||
return parsed.blocks[0];
|
||||
}
|
||||
|
||||
function getCellPackDataUrl(id: string, baseUrl: string) {
|
||||
const url = `${baseUrl}/other/${id}`
|
||||
return url.endsWith('.pdb') ? url : `${url}.pdb`
|
||||
}
|
||||
|
||||
export async function getFromCellPackDB(id: string, baseUrl: string) {
|
||||
const name = id.endsWith('.pdb') ? id.substring(0, id.length - 4) : id
|
||||
const parsed = await downloadPDB(getCellPackDataUrl(id, baseUrl), name);
|
||||
return parsed;
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
// /**
|
||||
// * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
// *
|
||||
// * @author David Sehnal <david.sehnal@gmail.com>
|
||||
// */
|
||||
|
||||
// import { StateTree, StateBuilder, StateAction, State } from '../../../mol-state';
|
||||
// import { StateTransforms } from '../../../mol-plugin/state/transforms';
|
||||
// import { createModelTree } from '../../../mol-plugin/state/actions/structure';
|
||||
// import { PluginContext } from '../../../mol-plugin/context';
|
||||
// import { PluginStateObject } from '../../../mol-plugin/state/objects';
|
||||
// import { ParamDefinition } from '../../../mol-util/param-definition';
|
||||
// import { PluginCommands } from '../../../mol-plugin/command';
|
||||
// import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
// import { PluginStateSnapshotManager } from '../../../mol-plugin/state/snapshots';
|
||||
// import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
|
||||
// import { Text } from '../../../mol-geo/geometry/text/text';
|
||||
// import { UUID } from '../../../mol-util';
|
||||
// import { ColorNames } from '../../../mol-util/color/names';
|
||||
// import { Camera } from '../../../mol-canvas3d/camera';
|
||||
// import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation';
|
||||
// import { createDefaultStructureComplex } from '../../../mol-plugin/util/structure-complex-helper';
|
||||
|
||||
// export const CreateJoleculeState = StateAction.build({
|
||||
// display: { name: 'Jolecule State Import' },
|
||||
// params: { id: ParamDefinition.Text('1mbo') },
|
||||
// from: PluginStateObject.Root
|
||||
// })(async ({ ref, state, params }, plugin: PluginContext) => {
|
||||
// try {
|
||||
// const id = params.id.trim().toLowerCase();
|
||||
// const data = await plugin.runTask(plugin.fetch({ url: `https://jolecule.appspot.com/pdb/${id}.views.json`, type: 'json' })) as JoleculeSnapshot[];
|
||||
|
||||
// data.sort((a, b) => a.order - b.order);
|
||||
|
||||
// await PluginCommands.State.RemoveObject.dispatch(plugin, { state, ref });
|
||||
// plugin.state.snapshots.clear();
|
||||
|
||||
// const template = createTemplate(plugin, state, id);
|
||||
// const snapshots = data.map((e, idx) => buildSnapshot(plugin, template, { e, idx, len: data.length }));
|
||||
// for (const s of snapshots) {
|
||||
// plugin.state.snapshots.add(s);
|
||||
// }
|
||||
|
||||
// PluginCommands.State.Snapshots.Apply.dispatch(plugin, { id: snapshots[0].snapshot.id });
|
||||
// } catch (e) {
|
||||
// plugin.log.error(`Jolecule Failed: ${e}`);
|
||||
// }
|
||||
// });
|
||||
|
||||
// interface JoleculeSnapshot {
|
||||
// order: number,
|
||||
// distances: { i_atom1: number, i_atom2: number }[],
|
||||
// labels: { i_atom: number, text: string }[],
|
||||
// camera: { up: Vec3, pos: Vec3, in: Vec3, slab: { z_front: number, z_back: number, zoom: number } },
|
||||
// selected: number[],
|
||||
// text: string
|
||||
// }
|
||||
|
||||
// function createTemplate(plugin: PluginContext, state: State, id: string) {
|
||||
// const b = new StateBuilder.Root(state.tree);
|
||||
// const data = b.toRoot().apply(StateTransforms.Data.Download, { url: `https://www.ebi.ac.uk/pdbe/static/entry/${id}_updated.cif` }, { state: { isGhost: true }});
|
||||
// const model = createModelTree(data, 'cif');
|
||||
// const structure = model.apply(StateTransforms.Model.StructureFromModel);
|
||||
// createDefaultStructureComplex(plugin, structure);
|
||||
// return { tree: b.getTree(), structure: structure.ref };
|
||||
// }
|
||||
|
||||
// const labelOptions: ParamDefinition.Values<Text.Params> = {
|
||||
// ...ParamDefinition.getDefaultValues(Text.Params),
|
||||
// tether: true,
|
||||
// sizeFactor: 1.3,
|
||||
// attachment: 'bottom-right',
|
||||
// offsetZ: 10,
|
||||
// background: true,
|
||||
// backgroundMargin: 0.2,
|
||||
// backgroundColor: ColorNames.skyblue,
|
||||
// backgroundOpacity: 0.9
|
||||
// }
|
||||
|
||||
// // const distanceLabelOptions = {
|
||||
// // ...ParamDefinition.getDefaultValues(Text.Params),
|
||||
// // sizeFactor: 1,
|
||||
// // offsetX: 0,
|
||||
// // offsetY: 0,
|
||||
// // offsetZ: 10,
|
||||
// // background: true,
|
||||
// // backgroundMargin: 0.2,
|
||||
// // backgroundColor: ColorNames.snow,
|
||||
// // backgroundOpacity: 0.9
|
||||
// // }
|
||||
|
||||
// function buildSnapshot(plugin: PluginContext, template: { tree: StateTree, structure: string }, params: { e: JoleculeSnapshot, idx: number, len: number }): PluginStateSnapshotManager.Entry {
|
||||
// const b = new StateBuilder.Root(template.tree);
|
||||
|
||||
// let i = 0;
|
||||
// for (const l of params.e.labels) {
|
||||
// const expression = createExpression([l.i_atom]);
|
||||
// const group = b.to(template.structure)
|
||||
// .group(StateTransforms.Misc.CreateGroup, { label: `Label ${++i}` });
|
||||
|
||||
// group
|
||||
// .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression, label: 'Atom' })
|
||||
// .apply(StateTransforms.Representation.StructureLabels3D, {
|
||||
// target: { name: 'static-text', params: { value: l.text || '' } },
|
||||
// options: labelOptions
|
||||
// });
|
||||
|
||||
// group
|
||||
// .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: MS.struct.modifier.wholeResidues([ expression ]), label: 'Residue' })
|
||||
// .apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
// StructureRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'ball-and-stick', { }));
|
||||
// }
|
||||
// if (params.e.selected && params.e.selected.length > 0) {
|
||||
// b.to(template.structure)
|
||||
// .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: createExpression(params.e.selected), label: `Selected` })
|
||||
// .apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
// StructureRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'ball-and-stick'));
|
||||
// }
|
||||
// // TODO
|
||||
// // for (const l of params.e.distances) {
|
||||
// // b.to('structure')
|
||||
// // .apply(StateTransforms.Model.StructureSelectionFromExpression, { query: createQuery([l.i_atom1, l.i_atom2]), label: `Distance ${++i}` })
|
||||
// // .apply(StateTransforms.Representation.StructureLabels3D, {
|
||||
// // target: { name: 'static-text', params: { value: l. || '' } },
|
||||
// // options: labelOptions
|
||||
// // });
|
||||
// // }
|
||||
// return PluginStateSnapshotManager.Entry({
|
||||
// id: UUID.create22(),
|
||||
// data: { tree: StateTree.toJSON(b.getTree()) },
|
||||
// camera: {
|
||||
// current: getCameraSnapshot(params.e.camera),
|
||||
// transitionStyle: 'animate',
|
||||
// transitionDurationInMs: 350
|
||||
// }
|
||||
// }, {
|
||||
// name: params.e.text
|
||||
// });
|
||||
// }
|
||||
|
||||
// function getCameraSnapshot(e: JoleculeSnapshot['camera']): Camera.Snapshot {
|
||||
// const direction = Vec3.sub(Vec3(), e.pos, e.in);
|
||||
// Vec3.normalize(direction, direction);
|
||||
// const up = Vec3.sub(Vec3(), e.pos, e.up);
|
||||
// Vec3.normalize(up, up);
|
||||
|
||||
// const s: Camera.Snapshot = {
|
||||
// mode: 'perspective',
|
||||
// fov: Math.PI / 4,
|
||||
// position: Vec3.scaleAndAdd(Vec3(), e.pos, direction, e.slab.zoom),
|
||||
// target: e.pos,
|
||||
// radius: (e.slab.z_back - e.slab.z_front) / 2,
|
||||
// fog: 50,
|
||||
// up,
|
||||
// };
|
||||
// return s;
|
||||
// }
|
||||
|
||||
// function createExpression(atomIndices: number[]) {
|
||||
// if (atomIndices.length === 0) return MS.struct.generator.empty();
|
||||
|
||||
// return MS.struct.generator.atomGroups({
|
||||
// 'atom-test': atomIndices.length === 1
|
||||
// ? MS.core.rel.eq([MS.struct.atomProperty.core.sourceIndex(), atomIndices[0]])
|
||||
// : MS.core.set.has([MS.set.apply(null, atomIndices), MS.struct.atomProperty.core.sourceIndex()]),
|
||||
// 'group-by': 0
|
||||
// });
|
||||
// }
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -7,14 +7,18 @@
|
||||
|
||||
import '../../mol-util/polyfill';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import './favicon.ico'
|
||||
import './index.html';
|
||||
import './favicon.ico';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/command';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { LoadCellPackModel } from './extensions/cellpack/model';
|
||||
import { StructureFromCellpack } from './extensions/cellpack/state';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
import { DownloadStructure } from '../../mol-plugin-state/actions/structure';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { CellPack } from '../../extensions/cellpack';
|
||||
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
function getParam(name: string, regex: string): string {
|
||||
let r = new RegExp(`${name}=(${regex})[&]?`, 'i');
|
||||
@@ -25,13 +29,14 @@ const hideControls = getParam('hide-controls', `[^&]+`) === '1';
|
||||
|
||||
function init() {
|
||||
const spec: PluginSpec = {
|
||||
actions: [
|
||||
...DefaultPluginSpec.actions,
|
||||
// PluginSpec.Action(CreateJoleculeState),
|
||||
PluginSpec.Action(LoadCellPackModel),
|
||||
PluginSpec.Action(StructureFromCellpack),
|
||||
actions: [...DefaultPluginSpec.actions],
|
||||
behaviors: [
|
||||
...DefaultPluginSpec.behaviors,
|
||||
PluginSpec.Behavior(CellPack),
|
||||
PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
PluginSpec.Behavior(RCSBAssemblySymmetry),
|
||||
PluginSpec.Behavior(RCSBValidationReport),
|
||||
],
|
||||
behaviors: [...DefaultPluginSpec.behaviors],
|
||||
animations: [...DefaultPluginSpec.animations || []],
|
||||
customParamEditors: DefaultPluginSpec.customParamEditors,
|
||||
layout: {
|
||||
@@ -45,8 +50,10 @@ function init() {
|
||||
},
|
||||
config: DefaultPluginSpec.config
|
||||
};
|
||||
spec.config?.set(PluginConfig.Viewport.ShowExpand, false);
|
||||
const plugin = createPlugin(document.getElementById('app')!, spec);
|
||||
trySetSnapshot(plugin);
|
||||
tryLoadFromUrl(plugin);
|
||||
}
|
||||
|
||||
async function trySetSnapshot(ctx: PluginContext) {
|
||||
@@ -58,11 +65,41 @@ async function trySetSnapshot(ctx: PluginContext) {
|
||||
const url = snapshotId
|
||||
? `https://webchem.ncbr.muni.cz/molstar-state/get/${snapshotId}`
|
||||
: snapshotUrl;
|
||||
await PluginCommands.State.Snapshots.Fetch.dispatch(ctx, { url })
|
||||
await PluginCommands.State.Snapshots.Fetch(ctx, { url });
|
||||
} catch (e) {
|
||||
ctx.log.error('Failed to load snapshot.');
|
||||
console.warn('Failed to load snapshot', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function tryLoadFromUrl(ctx: PluginContext) {
|
||||
const url = getParam('loadFromURL', '[^&]+').trim();
|
||||
try {
|
||||
if (!url) return;
|
||||
|
||||
let format = 'cif', isBinary = false;
|
||||
switch (getParam('loadFromURLFormat', '[a-z]+').toLocaleLowerCase().trim()) {
|
||||
case 'pdb': format = 'pdb'; break;
|
||||
case 'mmbcif': isBinary = true; break;
|
||||
}
|
||||
|
||||
const params = DownloadStructure.createDefaultParams(void 0 as any, ctx);
|
||||
|
||||
return ctx.runTask(ctx.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'url',
|
||||
params: {
|
||||
url: Asset.Url(url),
|
||||
format: format as any,
|
||||
isBinary,
|
||||
options: params.source.params.options,
|
||||
}
|
||||
}
|
||||
}));
|
||||
} catch (e) {
|
||||
ctx.log.error(`Failed to load from URL (${url})`);
|
||||
console.warn(`Failed to load from URL (${url})`, e);
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -18,13 +18,13 @@ export const StripedResidues = CustomElementProperty.create<number>({
|
||||
for (let i = 0, _i = model.atomicHierarchy.atoms._rowCount; i < _i; i++) {
|
||||
map.set(i as ElementIndex, residueIndex[i] % 2);
|
||||
}
|
||||
return map;
|
||||
return { value: map };
|
||||
},
|
||||
coloring: {
|
||||
getColor(e) { return e === 0 ? Color(0xff0000) : Color(0x0000ff) },
|
||||
getColor(e) { return e === 0 ? Color(0xff0000) : Color(0x0000ff); },
|
||||
defaultColor: Color(0x777777)
|
||||
},
|
||||
getLabel(e) {
|
||||
return e === 0 ? 'Odd stripe' : 'Even stripe'
|
||||
return e === 0 ? 'Odd stripe' : 'Even stripe';
|
||||
}
|
||||
})
|
||||
});
|
||||
16
src/examples/basic-wrapper/controls.tsx
Normal file
16
src/examples/basic-wrapper/controls.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginUIComponent } from '../../mol-plugin-ui/base';
|
||||
import * as React from 'react';
|
||||
|
||||
export class CustomToastMessage extends PluginUIComponent {
|
||||
render() {
|
||||
return <>
|
||||
Custom <i>Toast</i> content. No timeout.
|
||||
</>;
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@
|
||||
<input type='text' id='url' placeholder='url' />
|
||||
<input type='text' id='assemblyId' placeholder='assembly id' />
|
||||
<select id='format'>
|
||||
<option value='cif' selected>CIF</option>
|
||||
<option value='mmcif' selected>mmCIF</option>
|
||||
<option value='pdb'>PDB</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
var pdbId = '1grm', assemblyId= '1';
|
||||
var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif';
|
||||
var format = 'cif';
|
||||
var format = 'mmcif';
|
||||
|
||||
$('url').value = url;
|
||||
$('url').onchange = function (e) { url = e.target.value; }
|
||||
@@ -104,6 +104,7 @@
|
||||
addHeader('Misc');
|
||||
|
||||
addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes());
|
||||
addControl('Default Coloring', () => BasicMolStarWrapper.coloring.applyDefault());
|
||||
|
||||
addHeader('Interactivity');
|
||||
addControl('Highlight seq_id=7', () => BasicMolStarWrapper.interactivity.highlightOn());
|
||||
156
src/examples/basic-wrapper/index.ts
Normal file
156
src/examples/basic-wrapper/index.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { EmptyLoci } from '../../mol-model/loci';
|
||||
import { StructureSelection } from '../../mol-model/structure';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { StripedResidues } from './coloring';
|
||||
import { CustomToastMessage } from './controls';
|
||||
import './index.html';
|
||||
import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData } from './superposition';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }
|
||||
|
||||
class BasicWrapper {
|
||||
plugin: PluginContext;
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: {
|
||||
// left: 'none'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
remoteState: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!);
|
||||
this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
|
||||
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
|
||||
}
|
||||
|
||||
async load({ url, format = 'mmcif', isBinary = false, assemblyId = '' }: LoadParams) {
|
||||
await this.plugin.clear();
|
||||
|
||||
const data = await this.plugin.builders.data.download({ url: Asset.Url(url), isBinary }, { state: { isGhost: true } });
|
||||
const trajectory = await this.plugin.builders.structure.parseTrajectory(data, format);
|
||||
|
||||
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default', {
|
||||
structure: assemblyId ? { name: 'assembly', params: { id: assemblyId } } : { name: 'deposited', params: { } },
|
||||
showUnitcell: false,
|
||||
representationPreset: 'auto'
|
||||
});
|
||||
}
|
||||
|
||||
setBackground(color: number) {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: props => { props.renderer.backgroundColor = Color(color); } });
|
||||
}
|
||||
|
||||
toggleSpin() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
settings: props => {
|
||||
props.trackball.spin = !props.trackball.spin;
|
||||
}
|
||||
});
|
||||
if (!this.plugin.canvas3d.props.trackball.spin) PluginCommands.Camera.Reset(this.plugin, {});
|
||||
}
|
||||
|
||||
animate = {
|
||||
modelIndex: {
|
||||
maxFPS: 8,
|
||||
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }); },
|
||||
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }); },
|
||||
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }); },
|
||||
stop: () => this.plugin.managers.animation.stop()
|
||||
}
|
||||
}
|
||||
|
||||
coloring = {
|
||||
applyStripes: async () => {
|
||||
this.plugin.dataTransaction(async () => {
|
||||
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(s.components, { color: StripedResidues.propertyProvider.descriptor.name as any });
|
||||
}
|
||||
});
|
||||
},
|
||||
applyDefault: async () => {
|
||||
this.plugin.dataTransaction(async () => {
|
||||
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(s.components, { color: 'default' });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interactivity = {
|
||||
highlightOn: () => {
|
||||
const seq_id = 7;
|
||||
const data = (this.plugin.state.data.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
const sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
|
||||
'residue-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), seq_id]),
|
||||
'group-by': Q.struct.atomProperty.macromolecular.residueKey()
|
||||
}), data);
|
||||
const loci = StructureSelection.toLociWithSourceUnits(sel);
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci });
|
||||
},
|
||||
clearHighlight: () => {
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
|
||||
}
|
||||
}
|
||||
|
||||
tests = {
|
||||
staticSuperposition: async () => {
|
||||
await this.plugin.clear();
|
||||
return buildStaticSuperposition(this.plugin, StaticSuperpositionTestData);
|
||||
},
|
||||
dynamicSuperposition: async () => {
|
||||
await this.plugin.clear();
|
||||
return dynamicSuperpositionTest(this.plugin, ['1tqn', '2hhb', '4hhb'], 'HEM');
|
||||
},
|
||||
toggleValidationTooltip: () => {
|
||||
return this.plugin.state.updateBehavior(PDBeStructureQualityReport, params => { params.showTooltip = !params.showTooltip; });
|
||||
},
|
||||
showToasts: () => {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Toast 1',
|
||||
message: 'This is an example text, timeout 3s',
|
||||
key: 'toast-1',
|
||||
timeoutMs: 3000
|
||||
});
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Toast 2',
|
||||
message: CustomToastMessage,
|
||||
key: 'toast-2'
|
||||
});
|
||||
},
|
||||
hideToasts: () => {
|
||||
PluginCommands.Toast.Hide(this.plugin, { key: 'toast-1' });
|
||||
PluginCommands.Toast.Hide(this.plugin, { key: 'toast-2' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).BasicMolStarWrapper = new BasicWrapper();
|
||||
119
src/examples/basic-wrapper/superposition.ts
Normal file
119
src/examples/basic-wrapper/superposition.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { QueryContext, StructureSelection } from '../../mol-model/structure';
|
||||
import { superposeStructures } from '../../mol-model/structure/structure/util/superposition';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import Expression from '../../mol-script/language/expression';
|
||||
import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
|
||||
export type SuperpositionTestInput = {
|
||||
pdbId: string,
|
||||
auth_asym_id: string,
|
||||
matrix: Mat4
|
||||
}[];
|
||||
|
||||
export function buildStaticSuperposition(plugin: PluginContext, src: SuperpositionTestInput) {
|
||||
return plugin.dataTransaction(async () => {
|
||||
for (const s of src) {
|
||||
const { structure } = await loadStructure(plugin, `https://www.ebi.ac.uk/pdbe/static/entry/${s.pdbId}_updated.cif`, 'mmcif');
|
||||
await transform(plugin, structure, s.matrix);
|
||||
const chain = await plugin.builders.structure.tryCreateComponentFromExpression(structure, chainSelection(s.auth_asym_id), `Chain ${s.auth_asym_id}`);
|
||||
if (chain) await plugin.builders.structure.representation.addRepresentation(chain, { type: 'cartoon' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const StaticSuperpositionTestData: SuperpositionTestInput = [
|
||||
{
|
||||
pdbId: '1aj5', auth_asym_id: 'A', matrix: Mat4.identity()
|
||||
},
|
||||
{
|
||||
pdbId: '1df0', auth_asym_id: 'B', matrix: Mat4.ofRows([
|
||||
[0.406, 0.879, 0.248, -200.633],
|
||||
[0.693, -0.473, 0.544, 73.403],
|
||||
[0.596, -0.049, -0.802, -14.209],
|
||||
[0, 0, 0, 1]])
|
||||
},
|
||||
{
|
||||
pdbId: '1dvi', auth_asym_id: 'A', matrix: Mat4.ofRows([
|
||||
[-0.053, -0.077, 0.996, -45.633],
|
||||
[-0.312, 0.949, 0.057, -12.255],
|
||||
[-0.949, -0.307, -0.074, 53.562],
|
||||
[0, 0, 0, 1]])
|
||||
}
|
||||
];
|
||||
|
||||
export function dynamicSuperpositionTest(plugin: PluginContext, src: string[], comp_id: string) {
|
||||
return plugin.dataTransaction(async () => {
|
||||
for (const s of src) {
|
||||
await loadStructure(plugin, `https://www.ebi.ac.uk/pdbe/static/entry/${s}_updated.cif`, 'mmcif');
|
||||
}
|
||||
|
||||
const pivot = MS.struct.filter.first([
|
||||
MS.struct.generator.atomGroups({
|
||||
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), comp_id]),
|
||||
'group-by': MS.struct.atomProperty.macromolecular.residueKey()
|
||||
})
|
||||
]);
|
||||
|
||||
const rest = MS.struct.modifier.exceptBy({
|
||||
0: MS.struct.modifier.includeSurroundings({
|
||||
0: pivot,
|
||||
radius: 5
|
||||
}),
|
||||
by: pivot
|
||||
});
|
||||
|
||||
const query = compile<StructureSelection>(pivot);
|
||||
const xs = plugin.managers.structure.hierarchy.current.structures;
|
||||
const selections = xs.map(s => StructureSelection.toLociWithCurrentUnits(query(new QueryContext(s.cell.obj!.data))));
|
||||
|
||||
const transforms = superposeStructures(selections);
|
||||
|
||||
await siteVisual(plugin, xs[0].cell, pivot, rest);
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
await transform(plugin, xs[i].cell, transforms[i - 1].bTransform);
|
||||
await siteVisual(plugin, xs[i].cell, pivot, rest);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function siteVisual(plugin: PluginContext, s: StateObjectRef<PSO.Molecule.Structure>, pivot: Expression, rest: Expression) {
|
||||
const center = await plugin.builders.structure.tryCreateComponentFromExpression(s, pivot, 'pivot');
|
||||
if (center) await plugin.builders.structure.representation.addRepresentation(center, { type: 'ball-and-stick', color: 'residue-name' });
|
||||
|
||||
const surr = await plugin.builders.structure.tryCreateComponentFromExpression(s, rest, 'rest');
|
||||
if (surr) await plugin.builders.structure.representation.addRepresentation(surr, { type: 'ball-and-stick', color: 'uniform', size: 'uniform', sizeParams: { value: 0.33 } });
|
||||
}
|
||||
|
||||
async function loadStructure(plugin: PluginContext, url: string, format: BuiltInTrajectoryFormat, assemblyId?: string) {
|
||||
const data = await plugin.builders.data.download({ url: Asset.Url(url) });
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
|
||||
const model = await plugin.builders.structure.createModel(trajectory);
|
||||
const structure = await plugin.builders.structure.createStructure(model, assemblyId ? { name: 'assembly', params: { id: assemblyId } } : void 0);
|
||||
|
||||
return { data, trajectory, model, structure };
|
||||
}
|
||||
|
||||
function chainSelection(auth_asym_id: string) {
|
||||
return MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), auth_asym_id])
|
||||
});
|
||||
}
|
||||
|
||||
function transform(plugin: PluginContext, s: StateObjectRef<PSO.Molecule.Structure>, matrix: Mat4) {
|
||||
const b = plugin.state.data.build().to(s)
|
||||
.insert(StateTransforms.Model.TransformStructureConformation, { transform: { name: 'matrix', params: { data: matrix, transpose: false } } });
|
||||
return plugin.runTask(plugin.state.data.updateTree(b));
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Table } from '../../mol-data/db'
|
||||
import { CifWriter } from '../../mol-io/writer/cif'
|
||||
import * as S from './schemas'
|
||||
import { Table } from '../../mol-data/db';
|
||||
import { CifWriter } from '../../mol-io/writer/cif';
|
||||
import * as S from './schemas';
|
||||
// import { getCategoryInstanceProvider } from './utils'
|
||||
|
||||
export default function create(allData: any) {
|
||||
@@ -4,21 +4,21 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Column } from '../../mol-data/db'
|
||||
import { Column } from '../../mol-data/db';
|
||||
|
||||
import Type = Column.Schema
|
||||
|
||||
export const Sources = {
|
||||
id: Type.str,
|
||||
count: Type.int
|
||||
}
|
||||
};
|
||||
export type Sources = typeof Sources
|
||||
|
||||
export const Base = {
|
||||
id: Type.str,
|
||||
identifier: Type.str,
|
||||
mapping_group_id: Type.int
|
||||
}
|
||||
};
|
||||
export type Base = typeof Base
|
||||
|
||||
export const mapping = {
|
||||
@@ -36,17 +36,17 @@ export const mapping = {
|
||||
end_label_seq_id: Type.int,
|
||||
end_auth_seq_id: Type.int,
|
||||
pdbx_end_PDB_ins_code: Type.str
|
||||
}
|
||||
};
|
||||
export type mapping = typeof mapping
|
||||
|
||||
export const Pfam = {
|
||||
description: Type.str
|
||||
}
|
||||
};
|
||||
export type Pfam = typeof Pfam
|
||||
|
||||
export const InterPro = {
|
||||
name: Type.str
|
||||
}
|
||||
};
|
||||
export type InterPro = typeof InterPro
|
||||
|
||||
export const CATH = {
|
||||
@@ -56,32 +56,32 @@ export const CATH = {
|
||||
identifier: Type.str,
|
||||
class: Type.str,
|
||||
topology: Type.str,
|
||||
}
|
||||
};
|
||||
export type CATH = typeof CATH
|
||||
|
||||
export const EC = {
|
||||
accepted_name: Type.str,
|
||||
reaction: Type.str,
|
||||
systematic_name: Type.str
|
||||
}
|
||||
};
|
||||
export type EC = typeof EC
|
||||
|
||||
export const UniProt = {
|
||||
name: Type.str
|
||||
}
|
||||
};
|
||||
export type UniProt = typeof UniProt
|
||||
|
||||
export const SCOP = {
|
||||
sccs: Type.str,
|
||||
description: Type.str
|
||||
}
|
||||
};
|
||||
export type SCOP = typeof SCOP
|
||||
|
||||
export const GO = {
|
||||
category: Type.str,
|
||||
definition: Type.str,
|
||||
name: Type.str
|
||||
}
|
||||
};
|
||||
export type GO = typeof GO
|
||||
|
||||
export const categories = {
|
||||
@@ -92,4 +92,4 @@ export const categories = {
|
||||
UniProt,
|
||||
SCOP,
|
||||
GO
|
||||
}
|
||||
};
|
||||
@@ -4,9 +4,9 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as express from 'express'
|
||||
import fetch from 'node-fetch'
|
||||
import createMapping from './mapping'
|
||||
import express from 'express';
|
||||
import fetch from 'node-fetch';
|
||||
import createMapping from './mapping';
|
||||
|
||||
async function getMappings(id: string) {
|
||||
const data = await fetch(`https://www.ebi.ac.uk/pdbe/api/mappings/${id}`);
|
||||
@@ -19,7 +19,7 @@ let PORT = process.env.port || 1338;
|
||||
|
||||
const app = express();
|
||||
|
||||
const PREFIX = '/'
|
||||
const PREFIX = '/';
|
||||
|
||||
app.get(`${PREFIX}/:id`, async (req, res) => {
|
||||
try {
|
||||
@@ -41,7 +41,7 @@ app.get(`${PREFIX}/:id`, async (req, res) => {
|
||||
app.get(`${PREFIX}`, (req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||
res.end('Usage: /pdb_id, e.g. /1tqn');
|
||||
})
|
||||
});
|
||||
|
||||
app.listen(PORT);
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import fetch from 'node-fetch'
|
||||
import createMapping from './mapping'
|
||||
import fetch from 'node-fetch';
|
||||
import createMapping from './mapping';
|
||||
|
||||
(async function () {
|
||||
const data = await fetch('https://www.ebi.ac.uk/pdbe/api/mappings/1tqn?pretty=true');
|
||||
118
src/examples/lighting/index.ts
Normal file
118
src/examples/lighting/index.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Canvas3DProps } from '../../mol-canvas3d/canvas3d';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import './index.html';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }
|
||||
|
||||
type _Preset = Pick<Canvas3DProps, 'multiSample' | 'postprocessing' | 'renderer'>
|
||||
type Preset = { [K in keyof _Preset]: Partial<_Preset[K]> }
|
||||
|
||||
const Canvas3DPresets = {
|
||||
illustrative: <Preset> {
|
||||
multiSample: {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } },
|
||||
outline: { name: 'on', params: { scale: 1, threshold: 0.8 } }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 1,
|
||||
lightIntensity: 0,
|
||||
}
|
||||
},
|
||||
occlusion: <Preset> {
|
||||
multiSample: {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } },
|
||||
outline: { name: 'off', params: { } }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
lightIntensity: 0.6,
|
||||
}
|
||||
},
|
||||
standard: <Preset> {
|
||||
multiSample: {
|
||||
mode: 'off' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: { name: 'off', params: { } },
|
||||
outline: { name: 'off', params: { } }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
lightIntensity: 0.6,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
type Canvas3DPreset = keyof typeof Canvas3DPresets
|
||||
|
||||
class LightingDemo {
|
||||
plugin: PluginContext;
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
|
||||
}
|
||||
});
|
||||
|
||||
this.setPreset('illustrative');
|
||||
}
|
||||
|
||||
setPreset(preset: Canvas3DPreset) {
|
||||
const props = Canvas3DPresets[preset];
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
|
||||
...props,
|
||||
multiSample: {
|
||||
...this.plugin.canvas3d!.props.multiSample,
|
||||
...props.multiSample
|
||||
},
|
||||
renderer: {
|
||||
...this.plugin.canvas3d!.props.renderer,
|
||||
...props.renderer
|
||||
},
|
||||
postprocessing: {
|
||||
...this.plugin.canvas3d!.props.postprocessing,
|
||||
...props.postprocessing
|
||||
},
|
||||
}});
|
||||
}
|
||||
|
||||
async load({ url, format = 'mmcif', isBinary = false, assemblyId = '' }: LoadParams) {
|
||||
await this.plugin.clear();
|
||||
|
||||
const data = await this.plugin.builders.data.download({ url: Asset.Url(url), isBinary }, { state: { isGhost: true } });
|
||||
const trajectory = await this.plugin.builders.structure.parseTrajectory(data, format);
|
||||
const model = await this.plugin.builders.structure.createModel(trajectory);
|
||||
const structure = await this.plugin.builders.structure.createStructure(model, assemblyId ? { name: 'assembly', params: { id: assemblyId } } : { name: 'deposited', params: { } });
|
||||
|
||||
const polymer = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'polymer');
|
||||
if (polymer) await this.plugin.builders.structure.representation.addRepresentation(polymer, { type: 'spacefill', color: 'illustrative' });
|
||||
|
||||
const ligand = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'ligand');
|
||||
if (ligand) await this.plugin.builders.structure.representation.addRepresentation(ligand, { type: 'ball-and-stick' });
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).LightingDemo = new LightingDemo();
|
||||
@@ -9,6 +9,7 @@ import { CustomElementProperty } from '../../mol-model-props/common/custom-eleme
|
||||
import { Model, ElementIndex, ResidueIndex } from '../../mol-model/structure';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
|
||||
const EvolutionaryConservationPalette: Color[] = [
|
||||
[255, 255, 129], // insufficient
|
||||
@@ -30,9 +31,9 @@ export const EvolutionaryConservation = CustomElementProperty.create<number>({
|
||||
type: 'static',
|
||||
async getData(model: Model, ctx: CustomProperty.Context) {
|
||||
const id = model.entryId.toLowerCase();
|
||||
const url = `https://proteopedia.org/cgi-bin/cnsrf?${id}`
|
||||
const json = await ctx.fetch({ url, type: 'json' }).runInContext(ctx.runtime)
|
||||
const annotations = (json && json.residueAnnotations) || [];
|
||||
const url = Asset.getUrlAsset(ctx.assetManager, `https://proteopedia.org/cgi-bin/cnsrf?${id}`);
|
||||
const json = await ctx.assetManager.resolve(url, 'json').runInContext(ctx.runtime);
|
||||
const annotations = json.data?.residueAnnotations || [];
|
||||
|
||||
const conservationMap = new Map<string, number>();
|
||||
|
||||
@@ -58,7 +59,7 @@ export const EvolutionaryConservation = CustomElementProperty.create<number>({
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
return { value: map, assets: [json] };
|
||||
},
|
||||
coloring: {
|
||||
getColor(e: number) {
|
||||
@@ -68,7 +69,7 @@ export const EvolutionaryConservation = CustomElementProperty.create<number>({
|
||||
defaultColor: EvolutionaryConservationDefaultColor
|
||||
},
|
||||
getLabel(e) {
|
||||
if (e === 10) return `Evolutionary Conservation: InsufficientData`;
|
||||
if (e === 10) return `Evolutionary Conservation: Insufficient Data`;
|
||||
return e ? `Evolutionary Conservation: ${e}` : void 0;
|
||||
}
|
||||
});
|
||||
@@ -11,58 +11,58 @@ import { Unit, StructureProperties, StructureElement, Bond } from '../../mol-mod
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { Location } from '../../mol-model/location';
|
||||
import { ColorTheme, LocationColor } from '../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition'
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ThemeDataContext } from '../../mol-theme/theme';
|
||||
import { Column } from '../../mol-data/db';
|
||||
|
||||
const Description = 'Gives every chain a color from a list based on its `asym_id` value.'
|
||||
const Description = 'Gives every chain a color from a list based on its `asym_id` value.';
|
||||
|
||||
export function createProteopediaCustomTheme(colors: number[]) {
|
||||
const ProteopediaCustomColorThemeParams = {
|
||||
colors: PD.ObjectList({ color: PD.Color(Color(0xffffff)) }, ({ color }) => Color.toHexString(color),
|
||||
{ defaultValue: colors.map(c => ({ color: Color(c) })) })
|
||||
}
|
||||
};
|
||||
type ProteopediaCustomColorThemeParams = typeof ProteopediaCustomColorThemeParams
|
||||
function getChainIdColorThemeParams(ctx: ThemeDataContext) {
|
||||
return ProteopediaCustomColorThemeParams // TODO return copy
|
||||
return ProteopediaCustomColorThemeParams; // TODO return copy
|
||||
}
|
||||
|
||||
function getAsymId(unit: Unit): StructureElement.Property<string> {
|
||||
switch (unit.kind) {
|
||||
case Unit.Kind.Atomic:
|
||||
return StructureProperties.chain.label_asym_id
|
||||
return StructureProperties.chain.label_asym_id;
|
||||
case Unit.Kind.Spheres:
|
||||
case Unit.Kind.Gaussians:
|
||||
return StructureProperties.coarse.asym_id
|
||||
return StructureProperties.coarse.asym_id;
|
||||
}
|
||||
}
|
||||
|
||||
function addAsymIds(map: Map<string, number>, data: Column<string>) {
|
||||
let j = map.size
|
||||
let j = map.size;
|
||||
for (let o = 0, ol = data.rowCount; o < ol; ++o) {
|
||||
const k = data.value(o)
|
||||
const k = data.value(o);
|
||||
if (!map.has(k)) {
|
||||
map.set(k, j)
|
||||
j += 1
|
||||
map.set(k, j);
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ProteopediaCustomColorTheme(ctx: ThemeDataContext, props: PD.Values<ProteopediaCustomColorThemeParams>): ColorTheme<ProteopediaCustomColorThemeParams> {
|
||||
let color: LocationColor
|
||||
let color: LocationColor;
|
||||
|
||||
const colors = props.colors, colorCount = colors.length, defaultColor = colors[0].color;
|
||||
|
||||
if (ctx.structure) {
|
||||
const l = StructureElement.Location.create(ctx.structure)
|
||||
const { models } = ctx.structure
|
||||
const asymIdSerialMap = new Map<string, number>()
|
||||
const l = StructureElement.Location.create(ctx.structure);
|
||||
const { models } = ctx.structure;
|
||||
const asymIdSerialMap = new Map<string, number>();
|
||||
for (let i = 0, il = models.length; i < il; ++i) {
|
||||
const m = models[i]
|
||||
addAsymIds(asymIdSerialMap, m.atomicHierarchy.chains.label_asym_id)
|
||||
const m = models[i];
|
||||
addAsymIds(asymIdSerialMap, m.atomicHierarchy.chains.label_asym_id);
|
||||
if (m.coarseHierarchy.isDefined) {
|
||||
addAsymIds(asymIdSerialMap, m.coarseHierarchy.spheres.asym_id)
|
||||
addAsymIds(asymIdSerialMap, m.coarseHierarchy.gaussians.asym_id)
|
||||
addAsymIds(asymIdSerialMap, m.coarseHierarchy.spheres.asym_id);
|
||||
addAsymIds(asymIdSerialMap, m.coarseHierarchy.gaussians.asym_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,16 +72,16 @@ export function createProteopediaCustomTheme(colors: number[]) {
|
||||
const o = asymIdSerialMap.get(asym_id(location)) || 0;
|
||||
return colors[o % colorCount].color;
|
||||
} else if (Bond.isLocation(location)) {
|
||||
const asym_id = getAsymId(location.aUnit)
|
||||
l.unit = location.aUnit
|
||||
l.element = location.aUnit.elements[location.aIndex]
|
||||
const asym_id = getAsymId(location.aUnit);
|
||||
l.unit = location.aUnit;
|
||||
l.element = location.aUnit.elements[location.aIndex];
|
||||
const o = asymIdSerialMap.get(asym_id(l)) || 0;
|
||||
return colors[o % colorCount].color;
|
||||
}
|
||||
return defaultColor
|
||||
}
|
||||
return defaultColor;
|
||||
};
|
||||
} else {
|
||||
color = () => defaultColor
|
||||
color = () => defaultColor;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -91,16 +91,16 @@ export function createProteopediaCustomTheme(colors: number[]) {
|
||||
props,
|
||||
description: Description,
|
||||
legend: undefined
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const ProteopediaCustomColorThemeProvider: ColorTheme.Provider<ProteopediaCustomColorThemeParams> = {
|
||||
return {
|
||||
name: 'proteopedia-custom',
|
||||
label: 'Proteopedia Custom',
|
||||
category: 'Custom',
|
||||
factory: ProteopediaCustomColorTheme,
|
||||
getParams: getChainIdColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(ProteopediaCustomColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
|
||||
}
|
||||
|
||||
return ProteopediaCustomColorThemeProvider;
|
||||
};
|
||||
}
|
||||
@@ -4,12 +4,11 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { ResidueIndex, Model } from '../../mol-model/structure';
|
||||
import { BuiltInStructureRepresentationsName } from '../../mol-repr/structure/registry';
|
||||
import { BuiltInColorThemeName } from '../../mol-theme/color';
|
||||
import { AminoAcidNames } from '../../mol-model/structure/model/types';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
import { Model, ResidueIndex } from '../../mol-model/structure';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StructureRepresentationRegistry } from '../../mol-repr/structure/registry';
|
||||
import { ColorTheme } from '../../mol-theme/color';
|
||||
|
||||
export interface ModelInfo {
|
||||
hetResidues: { name: string, indices: ResidueIndex[] }[],
|
||||
@@ -54,14 +53,12 @@ export namespace ModelInfo {
|
||||
const hetMap = new Map<string, ModelInfo['hetResidues'][0]>();
|
||||
|
||||
for (let rI = 0 as ResidueIndex; rI < residueCount; rI++) {
|
||||
const comp_id = model.atomicHierarchy.residues.label_comp_id.value(rI);
|
||||
if (AminoAcidNames.has(comp_id)) continue;
|
||||
const mod_parent = model.properties.modifiedResidues.parentId.get(comp_id);
|
||||
if (mod_parent && AminoAcidNames.has(mod_parent)) continue;
|
||||
|
||||
const cI = chainIndex[residueOffsets[rI]];
|
||||
const eI = model.atomicHierarchy.index.getEntityFromChain(cI);
|
||||
if (model.entities.data.type.value(eI) === 'water') continue;
|
||||
const entityType = model.entities.data.type.value(eI);
|
||||
if (entityType !== 'non-polymer' && entityType !== 'branched') continue;
|
||||
|
||||
const comp_id = model.atomicHierarchy.residues.label_comp_id.value(rI);
|
||||
|
||||
let lig = hetMap.get(comp_id);
|
||||
if (!lig) {
|
||||
@@ -73,7 +70,7 @@ export namespace ModelInfo {
|
||||
}
|
||||
|
||||
const preferredAssemblyId = await pref;
|
||||
const symmetry = ModelSymmetry.Provider.get(model)
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
|
||||
return {
|
||||
hetResidues: hetResidues,
|
||||
@@ -87,6 +84,7 @@ export type SupportedFormats = 'cif' | 'pdb'
|
||||
export interface LoadParams {
|
||||
url: string,
|
||||
format?: SupportedFormats,
|
||||
isBinary?: boolean,
|
||||
assemblyId?: string,
|
||||
representationStyle?: RepresentationStyle
|
||||
}
|
||||
@@ -99,7 +97,7 @@ export interface RepresentationStyle {
|
||||
}
|
||||
|
||||
export namespace RepresentationStyle {
|
||||
export type Entry = { hide?: boolean, kind?: BuiltInStructureRepresentationsName, coloring?: BuiltInColorThemeName }
|
||||
export type Entry = { hide?: boolean, kind?: StructureRepresentationRegistry.BuiltIn, coloring?: ColorTheme.BuiltIn }
|
||||
}
|
||||
|
||||
export enum StateElements {
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
<option value='cif' selected>CIF</option>
|
||||
<option value='pdb'>PDB</option>
|
||||
</select>
|
||||
<input type='checkbox' id='isBinary' style="display: inline-block; width: auto" /> <label for="isBinary"> Binary</label><br />
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
<div id="volume-streaming-wrapper"></div>
|
||||
@@ -74,8 +75,8 @@
|
||||
|
||||
function $(id) { return document.getElementById(id); }
|
||||
|
||||
var pdbId = '1cbs', assemblyId= 'preferred';
|
||||
var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif';
|
||||
var pdbId = '1cbs', assemblyId= 'preferred', isBinary = true;
|
||||
var url = 'https://www.ebi.ac.uk/pdbe/entry-files/download/' + pdbId + '.bcif'
|
||||
var format = 'cif';
|
||||
|
||||
$('url').value = url;
|
||||
@@ -84,13 +85,21 @@
|
||||
$('assemblyId').onchange = function (e) { assemblyId = e.target.value; }
|
||||
$('format').value = format;
|
||||
$('format').onchange = function (e) { format = e.target.value; }
|
||||
$('isBinary').checked = isBinary;
|
||||
$('isBinary').onchange = function (e) { isBinary = !!e.target.checked; };
|
||||
|
||||
// var url = 'https://www.ebi.ac.uk/pdbe/entry-files/pdb' + pdbId + '.ent';
|
||||
// var format = 'pdb';
|
||||
// var assemblyId = 'deposited';
|
||||
|
||||
function loadAndSnapshot(params) {
|
||||
PluginWrapper.load(params).then(() => {
|
||||
setTimeout(() => snapshot = PluginWrapper.plugin.state.getSnapshot({ canvas3d: false /* do not save spinning state */ }), 500);
|
||||
});
|
||||
}
|
||||
|
||||
var representationStyle = {
|
||||
sequence: { coloring: 'proteopedia-custom' }, // or just { }
|
||||
// sequence: { coloring: 'proteopedia-custom' }, // or just { }
|
||||
hetGroups: { kind: 'ball-and-stick' }, // or 'spacefill
|
||||
water: { hide: true },
|
||||
snfg3d: { hide: false }
|
||||
@@ -100,7 +109,7 @@
|
||||
customColorList: CustomColors
|
||||
});
|
||||
PluginWrapper.setBackground(0xffffff);
|
||||
PluginWrapper.load({ url: url, format: format, assemblyId: assemblyId, representationStyle: representationStyle });
|
||||
loadAndSnapshot({ url: url, format: format, isBinary: isBinary, assemblyId: assemblyId, representationStyle: representationStyle });
|
||||
PluginWrapper.toggleSpin();
|
||||
|
||||
PluginWrapper.events.modelInfo.subscribe(function (info) {
|
||||
@@ -108,8 +117,8 @@
|
||||
listHetGroups(info);
|
||||
});
|
||||
|
||||
addControl('Load Asym Unit', () => PluginWrapper.load({ url: url, format: format }));
|
||||
addControl('Load Assembly', () => PluginWrapper.load({ url: url, format: format, assemblyId: assemblyId }));
|
||||
addControl('Load Asym Unit', () => loadAndSnapshot({ url: url, format: format, isBinary }));
|
||||
addControl('Load Assembly', () => loadAndSnapshot({ url: url, format: format, isBinary, assemblyId: assemblyId }));
|
||||
|
||||
addSeparator();
|
||||
|
||||
@@ -172,16 +181,20 @@
|
||||
addHeader('State');
|
||||
|
||||
var snapshot;
|
||||
addControl('Create Snapshot', () => {
|
||||
snapshot = PluginWrapper.snapshot.get();
|
||||
// could use JSON.stringify(snapshot) and upload the data
|
||||
addControl('Set Snapshot', () => {
|
||||
// const options = { data: true, behavior: false, animation: false, interactivity: false, canvas3d: false, camera: false, cameraTransition: false };
|
||||
snapshot = PluginWrapper.plugin.state.getSnapshot(/** options */);
|
||||
// console.log(JSON.stringify(snapshot, null, 2));
|
||||
});
|
||||
addControl('Apply Snapshot', () => {
|
||||
addControl('Restore Snapshot', () => {
|
||||
if (!snapshot) return;
|
||||
PluginWrapper.snapshot.set(snapshot);
|
||||
|
||||
// or download snapshot using fetch or ajax or whatever
|
||||
// or PluginWrapper.snapshot.download(url);
|
||||
});
|
||||
addControl('Download State', () => {
|
||||
PluginWrapper.snapshot.download('molj');
|
||||
});
|
||||
addControl('Download Session', () => {
|
||||
PluginWrapper.snapshot.download('molx');
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
@@ -217,7 +230,7 @@
|
||||
var l = document.createElement('button');
|
||||
l.innerText = r.name;
|
||||
l.onclick = function () {
|
||||
PluginWrapper.hetGroups.focusFirst(r.name);
|
||||
PluginWrapper.hetGroups.focusFirst(r.name, { doNotLabelWaters: true });
|
||||
};
|
||||
div.appendChild(l);
|
||||
});
|
||||
|
||||
@@ -5,37 +5,33 @@
|
||||
*/
|
||||
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Canvas3DProps, DefaultCanvas3DParams } from '../../mol-canvas3d/canvas3d';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in';
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
import { PluginStateObject, PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { CreateVolumeStreamingInfo, InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/command';
|
||||
import { StateTransforms } from '../../mol-plugin/state/transforms';
|
||||
import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transforms/representation';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin/state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin/state/animation/built-in';
|
||||
import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
|
||||
import { EvolutionaryConservation } from './annotation';
|
||||
import { LoadParams, SupportedFormats, RepresentationStyle, ModelInfo, StateElements } from './helpers';
|
||||
import { RxEventHelper } from '../../mol-util/rx-event-helper';
|
||||
import { ControlsWrapper, volumeStreamingControls } from './ui/controls';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { Scheduler } from '../../mol-task';
|
||||
import { createProteopediaCustomTheme } from './coloring';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { BuiltInStructureRepresentations } from '../../mol-repr/structure/registry';
|
||||
import { BuiltInColorThemes } from '../../mol-theme/color';
|
||||
import { BuiltInSizeThemes } from '../../mol-theme/size';
|
||||
import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { InitVolumeStreaming, CreateVolumeStreamingInfo } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { DefaultCanvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
|
||||
// import { Vec3 } from 'mol-math/linear-algebra';
|
||||
// import { ParamDefinition } from 'mol-util/param-definition';
|
||||
// import { Text } from 'mol-geo/geometry/text/text';
|
||||
require('../../mol-plugin-ui/skin/light.scss')
|
||||
import { getFormattedTime } from '../../mol-util/date';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { RxEventHelper } from '../../mol-util/rx-event-helper';
|
||||
import { EvolutionaryConservation } from './annotation';
|
||||
import { createProteopediaCustomTheme } from './coloring';
|
||||
import { LoadParams, ModelInfo, RepresentationStyle, StateElements, SupportedFormats } from './helpers';
|
||||
import './index.html';
|
||||
import { volumeStreamingControls } from './ui/controls';
|
||||
require('../../mol-plugin-ui/skin/light.scss');
|
||||
|
||||
class MolStarProteopediaWrapper {
|
||||
static VERSION_MAJOR = 3;
|
||||
static VERSION_MAJOR = 5;
|
||||
static VERSION_MINOR = 4;
|
||||
|
||||
private _ev = RxEventHelper.create();
|
||||
@@ -58,27 +54,27 @@ class MolStarProteopediaWrapper {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: {
|
||||
right: ControlsWrapper
|
||||
}
|
||||
},
|
||||
components: {
|
||||
remoteState: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
const customColoring = createProteopediaCustomTheme((options && options.customColorList) || []);
|
||||
|
||||
this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add('proteopedia-custom', customColoring);
|
||||
this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(EvolutionaryConservation.propertyProvider.descriptor.name, EvolutionaryConservation.colorThemeProvider!);
|
||||
this.plugin.lociLabels.addProvider(EvolutionaryConservation.labelProvider!);
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(customColoring);
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(EvolutionaryConservation.colorThemeProvider!);
|
||||
this.plugin.managers.lociLabels.addProvider(EvolutionaryConservation.labelProvider!);
|
||||
this.plugin.customModelProperties.register(EvolutionaryConservation.propertyProvider, true);
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this.plugin.state.dataState;
|
||||
return this.plugin.state.data;
|
||||
}
|
||||
|
||||
private download(b: StateBuilder.To<PSO.Root>, url: string) {
|
||||
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
|
||||
private download(b: StateBuilder.To<PSO.Root>, url: string, isBinary: boolean) {
|
||||
return b.apply(StateTransforms.Data.Download, { url: Asset.Url(url), isBinary });
|
||||
}
|
||||
|
||||
private model(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats) {
|
||||
@@ -92,10 +88,15 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
private structure(assemblyId: string) {
|
||||
const model = this.state.build().to(StateElements.Model);
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
};
|
||||
|
||||
const s = model
|
||||
.apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [EvolutionaryConservation.propertyProvider.descriptor.name], properties: {} }, { ref: StateElements.ModelProps, state: { isGhost: false } })
|
||||
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: StateElements.Assembly });
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { ref: StateElements.Assembly });
|
||||
|
||||
s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: StateElements.Sequence });
|
||||
s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }, { ref: StateElements.Het });
|
||||
@@ -118,9 +119,10 @@ class MolStarProteopediaWrapper {
|
||||
root.delete(StateElements.SequenceVisual);
|
||||
} else {
|
||||
root.applyOrUpdate(StateElements.SequenceVisual, StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
|
||||
(style.sequence && style.sequence.kind) || 'cartoon',
|
||||
(style.sequence && style.sequence.coloring) || 'unit-index', structure));
|
||||
createStructureRepresentationParams(this.plugin, structure, {
|
||||
type: (style.sequence && style.sequence.kind) || 'cartoon',
|
||||
color: (style.sequence && style.sequence.coloring) || 'unit-index'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,9 +135,10 @@ class MolStarProteopediaWrapper {
|
||||
root.delete(StateElements.HetVisual);
|
||||
} else {
|
||||
root.applyOrUpdate(StateElements.HetVisual, StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
|
||||
(style.hetGroups && style.hetGroups.kind) || 'ball-and-stick',
|
||||
(style.hetGroups && style.hetGroups.coloring), structure));
|
||||
createStructureRepresentationParams(this.plugin, structure, {
|
||||
type: (style.hetGroups && style.hetGroups.kind) || 'ball-and-stick',
|
||||
color: style.hetGroups && style.hetGroups.coloring
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,7 +152,7 @@ class MolStarProteopediaWrapper {
|
||||
root.delete(StateElements.Het3DSNFG);
|
||||
} else {
|
||||
root.applyOrUpdate(StateElements.Het3DSNFG, StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, 'carbohydrate', void 0, structure));
|
||||
createStructureRepresentationParams(this.plugin, structure, { type: 'carbohydrate' }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,9 +163,11 @@ class MolStarProteopediaWrapper {
|
||||
root.delete(StateElements.WaterVisual);
|
||||
} else {
|
||||
root.applyOrUpdate(StateElements.WaterVisual, StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
|
||||
(style.water && style.water.kind) || 'ball-and-stick',
|
||||
(style.water && style.water.coloring), structure, { alpha: 0.51 }));
|
||||
createStructureRepresentationParams(this.plugin, structure, {
|
||||
type: (style.water && style.water.kind) || 'ball-and-stick',
|
||||
typeParams: { alpha: 0.51 },
|
||||
color: style.water && style.water.coloring
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,20 +185,21 @@ class MolStarProteopediaWrapper {
|
||||
const model = this.getObj<PluginStateObject.Molecule.Model>('model');
|
||||
if (!model) return;
|
||||
|
||||
const info = await ModelInfo.get(this.plugin, model, checkPreferredAssembly)
|
||||
const info = await ModelInfo.get(this.plugin, model, checkPreferredAssembly);
|
||||
this.events.modelInfo.next(info);
|
||||
return info;
|
||||
}
|
||||
|
||||
private applyState(tree: StateBuilder) {
|
||||
return PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
|
||||
return PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
}
|
||||
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = 'deposited', representationStyle }: LoadParams) {
|
||||
private emptyLoadedParams: LoadParams = { url: '', format: 'cif', isBinary: false, assemblyId: '' };
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', isBinary: false, assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = 'deposited', isBinary = false, representationStyle }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.dataState;
|
||||
const state = this.plugin.state.data;
|
||||
|
||||
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
|
||||
loadType = 'full';
|
||||
@@ -202,8 +208,8 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
|
||||
const modelTree = this.model(this.download(state.build().toRoot(), url), format);
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref });
|
||||
const modelTree = this.model(this.download(state.build().toRoot(), url, isBinary), format);
|
||||
await this.applyState(modelTree);
|
||||
const info = await this.doInfo(true);
|
||||
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
|
||||
@@ -213,39 +219,44 @@ class MolStarProteopediaWrapper {
|
||||
const tree = state.build();
|
||||
const info = await this.doInfo(true);
|
||||
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: asmId }));
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: asmId || 'deposited' }
|
||||
}
|
||||
};
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
|
||||
await this.applyState(tree);
|
||||
}
|
||||
|
||||
await this.updateStyle(representationStyle);
|
||||
|
||||
this.loadedParams = { url, format, assemblyId };
|
||||
Scheduler.setImmediate(() => PluginCommands.Camera.Reset.dispatch(this.plugin, { }));
|
||||
}
|
||||
|
||||
async updateStyle(style?: RepresentationStyle, partial?: boolean) {
|
||||
const tree = this.visual(style, partial);
|
||||
if (!tree) return;
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
}
|
||||
|
||||
setBackground(color: number) {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const renderer = this.plugin.canvas3d.props.renderer;
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
}
|
||||
|
||||
toggleSpin() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const trackball = this.plugin.canvas3d.props.trackball;
|
||||
const spinning = trackball.spin;
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
if (!spinning) PluginCommands.Camera.Reset(this.plugin, { });
|
||||
}
|
||||
|
||||
viewport = {
|
||||
setSettings: (settings?: Canvas3DProps) => {
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
settings: settings || DefaultCanvas3DParams
|
||||
});
|
||||
}
|
||||
@@ -253,36 +264,17 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
camera = {
|
||||
toggleSpin: () => this.toggleSpin(),
|
||||
resetPosition: () => PluginCommands.Camera.Reset.dispatch(this.plugin, { }),
|
||||
// setClip: (options?: { distance?: number, near?: number, far?: number }) => {
|
||||
// if (!options) {
|
||||
// PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
|
||||
// settings: {
|
||||
// cameraClipDistance: DefaultCanvas3DParams.cameraClipDistance,
|
||||
// clip: DefaultCanvas3DParams.clip
|
||||
// }
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// options = options || { };
|
||||
// const props = this.plugin.canvas3d.props;
|
||||
// const clipNear = typeof options.near === 'undefined' ? props.clip[0] : options.near;
|
||||
// const clipFar = typeof options.far === 'undefined' ? props.clip[1] : options.far;
|
||||
// PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
|
||||
// settings: { cameraClipDistance: options.distance, clip: [clipNear, clipFar] }
|
||||
// });
|
||||
// }
|
||||
resetPosition: () => PluginCommands.Camera.Reset(this.plugin, { })
|
||||
}
|
||||
|
||||
animate = {
|
||||
modelIndex: {
|
||||
maxFPS: 8,
|
||||
onceForward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }) },
|
||||
onceBackward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }) },
|
||||
palindrome: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }) },
|
||||
loop: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }) },
|
||||
stop: () => this.plugin.state.animation.stop()
|
||||
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }); },
|
||||
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }); },
|
||||
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }); },
|
||||
stop: () => this.plugin.managers.animation.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,13 +285,8 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
const state = this.state;
|
||||
|
||||
// const visuals = state.selectQ(q => q.ofType(PluginStateObject.Molecule.Structure.Representation3D).filter(c => c.transform.transformer === StateTransforms.Representation.StructureRepresentation3D));
|
||||
// for (const v of visuals) {
|
||||
// }
|
||||
|
||||
const tree = state.build();
|
||||
const colorTheme = { name: EvolutionaryConservation.propertyProvider.descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.propertyProvider.descriptor.name).defaultValues };
|
||||
const colorTheme = { name: EvolutionaryConservation.propertyProvider.descriptor.name, params: this.plugin.representation.structure.themes.colorThemeRegistry.get(EvolutionaryConservation.propertyProvider.descriptor.name).defaultValues };
|
||||
|
||||
if (!params || !!params.sequence) {
|
||||
tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
|
||||
@@ -308,7 +295,7 @@ class MolStarProteopediaWrapper {
|
||||
tree.to(StateElements.HetVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +315,7 @@ class MolStarProteopediaWrapper {
|
||||
remove: () => {
|
||||
const r = this.state.select(StateSelection.Generators.ofTransformer(CreateVolumeStreamingInfo))[0];
|
||||
if (!r) return;
|
||||
PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.state, ref: r.transform.ref });
|
||||
PluginCommands.State.RemoveObject(this.plugin, { state: this.state, ref: r.transform.ref });
|
||||
if (this.experimentalDataElement) {
|
||||
ReactDOM.unmountComponentAtNode(this.experimentalDataElement);
|
||||
this.experimentalDataElement = void 0;
|
||||
@@ -339,14 +326,12 @@ class MolStarProteopediaWrapper {
|
||||
hetGroups = {
|
||||
reset: () => {
|
||||
const update = this.state.build().delete(StateElements.HetGroupFocusGroup);
|
||||
PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update });
|
||||
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
PluginCommands.State.Update(this.plugin, { state: this.state, tree: update });
|
||||
PluginCommands.Camera.Reset(this.plugin, { });
|
||||
},
|
||||
focusFirst: async (compId: string) => {
|
||||
focusFirst: async (compId: string, options?: { hideLabels: boolean, doNotLabelWaters: boolean }) => {
|
||||
if (!this.state.transforms.has(StateElements.Assembly)) return;
|
||||
await PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
|
||||
// const asm = (this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
await PluginCommands.Camera.Reset(this.plugin, { });
|
||||
|
||||
const update = this.state.build();
|
||||
|
||||
@@ -361,65 +346,66 @@ class MolStarProteopediaWrapper {
|
||||
const surroundings = MS.struct.modifier.includeSurroundings({ 0: core, radius: 5, 'as-whole-residues': true });
|
||||
|
||||
const group = update.to(StateElements.Assembly).group(StateTransforms.Misc.CreateGroup, { label: compId }, { ref: StateElements.HetGroupFocusGroup });
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
|
||||
const coreSel = group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Core', expression: core }, { ref: StateElements.HetGroupFocus });
|
||||
|
||||
|
||||
coreSel.apply(StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'ball-and-stick'
|
||||
}));
|
||||
coreSel.apply(StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'label',
|
||||
typeParams: { level: 'element' }
|
||||
}), { tags: ['proteopedia-labels'] });
|
||||
|
||||
group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Core', expression: core }, { ref: StateElements.HetGroupFocus })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D, this.createCoreVisualParams());
|
||||
group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Surroundings', expression: surroundings })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D, this.createSurVisualParams());
|
||||
// sel.apply(StateTransforms.Representation.StructureLabels3D, {
|
||||
// target: { name: 'residues', params: { } },
|
||||
// options: {
|
||||
// ...ParamDefinition.getDefaultValues(Text.Params),
|
||||
// background: true,
|
||||
// backgroundMargin: 0.2,
|
||||
// backgroundColor: ColorNames.snow,
|
||||
// backgroundOpacity: 0.9,
|
||||
// }
|
||||
// });
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'ball-and-stick',
|
||||
color: 'uniform', colorParams: { value: ColorNames.gray },
|
||||
size: 'uniform', sizeParams: { value: 0.33 }
|
||||
}));
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update });
|
||||
if (!options?.hideLabels) {
|
||||
// Labels
|
||||
const waters = MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.entityType(), 'water']),
|
||||
});
|
||||
const exclude = options?.doNotLabelWaters ? MS.struct.combinator.merge([core, waters]) : core;
|
||||
const onlySurroundings = MS.struct.modifier.exceptBy({ 0: surroundings, by: exclude });
|
||||
|
||||
group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Surroundings (only)', expression: onlySurroundings })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'label',
|
||||
typeParams: { level: 'residue' }
|
||||
}), { tags: ['proteopedia-labels'] }); // the tag can later be used to toggle the labels
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.state, tree: update });
|
||||
|
||||
const focus = (this.state.select(StateElements.HetGroupFocus)[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
const sphere = focus.boundary.sphere;
|
||||
// const asmCenter = asm.boundary.sphere.center;
|
||||
// const position = Vec3.sub(Vec3.zero(), sphere.center, asmCenter);
|
||||
// Vec3.normalize(position, position);
|
||||
// Vec3.scaleAndAdd(position, sphere.center, position, sphere.radius);
|
||||
const radius = Math.max(sphere.radius, 5)
|
||||
const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, radius, radius);
|
||||
PluginCommands.Camera.SetSnapshot.dispatch(this.plugin, { snapshot, durationMs: 250 });
|
||||
const radius = Math.max(sphere.radius, 5);
|
||||
const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, radius);
|
||||
PluginCommands.Camera.SetSnapshot(this.plugin, { snapshot, durationMs: 250 });
|
||||
}
|
||||
}
|
||||
|
||||
private createSurVisualParams() {
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
|
||||
return StructureRepresentation3DHelpers.createParams(this.plugin, asm.data, {
|
||||
repr: BuiltInStructureRepresentations['ball-and-stick'],
|
||||
color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
|
||||
size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
|
||||
});
|
||||
}
|
||||
|
||||
private createCoreVisualParams() {
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
|
||||
return StructureRepresentation3DHelpers.createParams(this.plugin, asm.data, {
|
||||
repr: BuiltInStructureRepresentations['ball-and-stick'],
|
||||
// color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
|
||||
// size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
|
||||
});
|
||||
}
|
||||
|
||||
snapshot = {
|
||||
get: () => {
|
||||
return this.plugin.state.getSnapshot();
|
||||
get: (params?: PluginState.SnapshotParams) => {
|
||||
return this.plugin.state.getSnapshot(params);
|
||||
},
|
||||
set: (snapshot: PluginState.Snapshot) => {
|
||||
return this.plugin.state.setSnapshot(snapshot);
|
||||
},
|
||||
download: async (url: string) => {
|
||||
download: async (type: 'molj' | 'molx' = 'molj', params?: PluginState.SnapshotParams) => {
|
||||
const data = await this.plugin.managers.snapshot.serialize({ type, params });
|
||||
download(data, `mol-star_state_${(name || getFormattedTime())}.${type}`);
|
||||
},
|
||||
fetch: async (url: string, type: 'molj' | 'molx' = 'molj') => {
|
||||
try {
|
||||
const snapshot = await this.plugin.runTask(this.plugin.fetch({ url, type: 'json' }));
|
||||
await this.plugin.state.setSnapshot(snapshot);
|
||||
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'binary' }));
|
||||
this.loadedParams = { ...this.emptyLoadedParams };
|
||||
await this.plugin.managers.snapshot.open(new File([data], `state.${type}`));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
@@ -6,27 +6,13 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { CurrentObject, PluginContextContainer } from '../../../mol-plugin-ui/plugin';
|
||||
import { AnimationControls } from '../../../mol-plugin-ui/state/animation';
|
||||
import { CameraSnapshots } from '../../../mol-plugin-ui/camera';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { PluginContextContainer } from '../../../mol-plugin-ui/plugin';
|
||||
import { TransformUpdaterControl } from '../../../mol-plugin-ui/state/update-transform';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateElements } from '../helpers';
|
||||
|
||||
export class ControlsWrapper extends PluginUIComponent {
|
||||
render() {
|
||||
return <div className='msp-scrollable-container msp-right-controls'>
|
||||
<CurrentObject />
|
||||
<AnimationControls />
|
||||
<CameraSnapshots />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export function volumeStreamingControls(plugin: PluginContext, parent: Element) {
|
||||
ReactDOM.render(<PluginContextContainer plugin={plugin}>
|
||||
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
|
||||
</PluginContextContainer>,
|
||||
parent);
|
||||
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
|
||||
</PluginContextContainer>, parent);
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Task, Progress, Scheduler, MultistepTask, chunkedSubtask } from '../mol-task'
|
||||
import { Task, Progress, Scheduler, MultistepTask, chunkedSubtask } from '../mol-task';
|
||||
import { now } from '../mol-util/now';
|
||||
|
||||
export async function test1() {
|
||||
@@ -85,19 +85,19 @@ export const ms = MultistepTask('ms-task', ['step 1', 'step 2', 'step 3'], async
|
||||
await step(0);
|
||||
|
||||
const child = Task.create('chunked', async ctx => {
|
||||
const s = await chunkedSubtask(ctx, 25, { i: 0, current: 0, total: 125 }, processChunk, (ctx, s, p) => ctx.update('chunk test ' + p))
|
||||
const s = await chunkedSubtask(ctx, 25, { i: 0, current: 0, total: 125 }, processChunk, (ctx, s, p) => ctx.update('chunk test ' + p));
|
||||
return s.i;
|
||||
});
|
||||
|
||||
await child.runAsChild(ctx);
|
||||
await Scheduler.delay(250);
|
||||
await step(1);
|
||||
await chunkedSubtask(ctx, 25, { i: 0, current: 0, total: 80 }, processChunk, (ctx, s, p) => ctx.update('chunk test ' + p))
|
||||
await chunkedSubtask(ctx, 25, { i: 0, current: 0, total: 80 }, processChunk, (ctx, s, p) => ctx.update('chunk test ' + p));
|
||||
await Scheduler.delay(250);
|
||||
await step(2);
|
||||
await Scheduler.delay(250);
|
||||
return p.i + 3;
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
export function abortingObserver(p: Progress) {
|
||||
|
||||
96
src/extensions/cellpack/color.ts
Normal file
96
src/extensions/cellpack/color.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ThemeDataContext } from '../../mol-theme/theme';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { getPalette } from '../../mol-util/color/palette';
|
||||
import { ColorTheme, LocationColor } from '../../mol-theme/color';
|
||||
import { ScaleLegend, TableLegend } from '../../mol-util/legend';
|
||||
import { StructureElement, Bond } from '../../mol-model/structure';
|
||||
import { Location } from '../../mol-model/location';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
import { distinctColors } from '../../mol-util/color/distinct';
|
||||
import { Hcl } from '../../mol-util/color/spaces/hcl';
|
||||
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
const Description = 'Gives every model in a CellPack packing a unique color similar to other models in the packing.';
|
||||
|
||||
export const CellPackColorThemeParams = {};
|
||||
export type CellPackColorThemeParams = typeof CellPackColorThemeParams
|
||||
export function getCellPackColorThemeParams(ctx: ThemeDataContext) {
|
||||
return CellPackColorThemeParams; // TODO return copy
|
||||
}
|
||||
|
||||
export function CellPackColorTheme(ctx: ThemeDataContext, props: PD.Values<CellPackColorThemeParams>): ColorTheme<CellPackColorThemeParams> {
|
||||
let color: LocationColor;
|
||||
let legend: ScaleLegend | TableLegend | undefined;
|
||||
|
||||
const info = ctx.structure && CellPackInfoProvider.get(ctx.structure).value;
|
||||
|
||||
if (ctx.structure && info) {
|
||||
const colors = distinctColors(info.packingsCount);
|
||||
const hcl = Hcl.fromColor(Hcl(), colors[info.packingIndex]);
|
||||
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number];
|
||||
|
||||
const { models } = ctx.structure.root;
|
||||
|
||||
let size = 0;
|
||||
for (const m of models) size = Math.max(size, m.trajectoryInfo.size);
|
||||
|
||||
const palette = getPalette(size, { palette: {
|
||||
name: 'generate',
|
||||
params: {
|
||||
hue, chroma: [30, 80], luminance: [15, 85],
|
||||
clusteringStepCount: 50, minSampleCount: 800, maxCount: 75,
|
||||
minLabel: 'Min', maxLabel: 'Max', valueLabel: (i: number) => `${i + 1}`,
|
||||
}
|
||||
}});
|
||||
legend = palette.legend;
|
||||
const modelColor = new Map<number, Color>();
|
||||
for (let i = 0, il = models.length; i < il; ++i) {
|
||||
const idx = models[i].trajectoryInfo.index;
|
||||
modelColor.set(models[i].trajectoryInfo.index, palette.color(idx));
|
||||
}
|
||||
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
return modelColor.get(location.unit.model.trajectoryInfo.index)!;
|
||||
} else if (Bond.isLocation(location)) {
|
||||
return modelColor.get(location.aUnit.model.trajectoryInfo.index)!;
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
} else {
|
||||
color = () => DefaultColor;
|
||||
}
|
||||
|
||||
return {
|
||||
factory: CellPackColorTheme,
|
||||
granularity: 'instance',
|
||||
color,
|
||||
props,
|
||||
description: Description,
|
||||
legend
|
||||
};
|
||||
}
|
||||
|
||||
export const CellPackColorThemeProvider: ColorTheme.Provider<CellPackColorThemeParams, 'cellpack'> = {
|
||||
name: 'cellpack',
|
||||
label: 'CellPack',
|
||||
category: ColorTheme.Category.Chain,
|
||||
factory: CellPackColorTheme,
|
||||
getParams: getCellPackColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(CellPackColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => {
|
||||
return (
|
||||
!!ctx.structure && ctx.structure.elementCount > 0 &&
|
||||
ctx.structure.models[0].trajectoryInfo.size > 1 &&
|
||||
!!CellPackInfoProvider.get(ctx.structure).value
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -5,8 +5,8 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec3, Quat, Mat4 } from '../../../../mol-math/linear-algebra';
|
||||
import { NumberArray } from '../../../../mol-util/type-helpers';
|
||||
import { Vec3, Quat, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { NumberArray } from '../../mol-util/type-helpers';
|
||||
|
||||
interface Frame {
|
||||
t: Vec3,
|
||||
@@ -14,215 +14,210 @@ interface Frame {
|
||||
s: Vec3,
|
||||
}
|
||||
|
||||
const a0Tmp = Vec3()
|
||||
const a1Tmp = Vec3()
|
||||
const a2Tmp = Vec3()
|
||||
const a3Tmp = Vec3()
|
||||
const a0Tmp = Vec3();
|
||||
const a1Tmp = Vec3();
|
||||
const a2Tmp = Vec3();
|
||||
const a3Tmp = Vec3();
|
||||
function CubicInterpolate(out: Vec3, y0: Vec3, y1: Vec3, y2: Vec3, y3: Vec3, mu: number): Vec3 {
|
||||
const mu2 = mu * mu;
|
||||
Vec3.sub(a0Tmp, y3, y2)
|
||||
Vec3.sub(a0Tmp, a0Tmp, y0)
|
||||
Vec3.add(a0Tmp, a0Tmp, y1)
|
||||
Vec3.sub(a0Tmp, y3, y2);
|
||||
Vec3.sub(a0Tmp, a0Tmp, y0);
|
||||
Vec3.add(a0Tmp, a0Tmp, y1);
|
||||
|
||||
Vec3.sub(a1Tmp, y0, y1)
|
||||
Vec3.sub(a1Tmp, a1Tmp, a0Tmp)
|
||||
Vec3.sub(a1Tmp, y0, y1);
|
||||
Vec3.sub(a1Tmp, a1Tmp, a0Tmp);
|
||||
|
||||
Vec3.sub(a2Tmp, y2, y0)
|
||||
Vec3.sub(a2Tmp, y2, y0);
|
||||
|
||||
Vec3.copy(a3Tmp, y1)
|
||||
Vec3.copy(a3Tmp, y1);
|
||||
|
||||
out[0] = a0Tmp[0] * mu * mu2 + a1Tmp[0] * mu2 + a2Tmp[0] * mu + a3Tmp[0]
|
||||
out[1] = a0Tmp[1] * mu * mu2 + a1Tmp[1] * mu2 + a2Tmp[1] * mu + a3Tmp[1]
|
||||
out[2] = a0Tmp[2] * mu * mu2 + a1Tmp[2] * mu2 + a2Tmp[2] * mu + a3Tmp[2]
|
||||
out[0] = a0Tmp[0] * mu * mu2 + a1Tmp[0] * mu2 + a2Tmp[0] * mu + a3Tmp[0];
|
||||
out[1] = a0Tmp[1] * mu * mu2 + a1Tmp[1] * mu2 + a2Tmp[1] * mu + a3Tmp[1];
|
||||
out[2] = a0Tmp[2] * mu * mu2 + a1Tmp[2] * mu2 + a2Tmp[2] * mu + a3Tmp[2];
|
||||
|
||||
return out
|
||||
return out;
|
||||
}
|
||||
|
||||
const cp0 = Vec3()
|
||||
const cp1 = Vec3()
|
||||
const cp2 = Vec3()
|
||||
const cp3 = Vec3()
|
||||
const currentPosition = Vec3()
|
||||
const cp0 = Vec3();
|
||||
const cp1 = Vec3();
|
||||
const cp2 = Vec3();
|
||||
const cp3 = Vec3();
|
||||
const currentPosition = Vec3();
|
||||
function ResampleControlPoints(points: NumberArray, segmentLength: number) {
|
||||
const nP = points.length / 3
|
||||
const nP = points.length / 3;
|
||||
// insert a point at the end and at the begining
|
||||
// controlPoints.Insert(0, controlPoints[0] + (controlPoints[0] - controlPoints[1]) / 2.0f);
|
||||
// controlPoints.Add(controlPoints[nP - 1] + (controlPoints[nP - 1] - controlPoints[nP - 2]) / 2.0f);
|
||||
|
||||
let resampledControlPoints: Vec3[] = []
|
||||
let resampledControlPoints: Vec3[] = [];
|
||||
// resampledControlPoints.Add(controlPoints[0]);
|
||||
// resampledControlPoints.Add(controlPoints[1]);
|
||||
|
||||
let idx = 1
|
||||
let idx = 1;
|
||||
// const currentPosition = Vec3.create(points[idx * 3], points[idx * 3 + 1], points[idx * 3 + 2])
|
||||
Vec3.fromArray(currentPosition, points, idx * 3)
|
||||
Vec3.fromArray(currentPosition, points, idx * 3);
|
||||
|
||||
let lerpValue = 0.0
|
||||
let lerpValue = 0.0;
|
||||
|
||||
// Normalize the distance between control points
|
||||
while (true) {
|
||||
if (idx + 2 >= nP) break
|
||||
Vec3.fromArray(cp0, points, (idx - 1) * 3)
|
||||
Vec3.fromArray(cp1, points, idx * 3)
|
||||
Vec3.fromArray(cp2, points, (idx + 1) * 3)
|
||||
Vec3.fromArray(cp3, points, (idx + 2) * 3)
|
||||
if (idx + 2 >= nP) break;
|
||||
Vec3.fromArray(cp0, points, (idx - 1) * 3);
|
||||
Vec3.fromArray(cp1, points, idx * 3);
|
||||
Vec3.fromArray(cp2, points, (idx + 1) * 3);
|
||||
Vec3.fromArray(cp3, points, (idx + 2) * 3);
|
||||
// const cp0 = Vec3.create(points[(idx-1)*3], points[(idx-1)*3+1], points[(idx-1)*3+2]) // controlPoints[currentPointId - 1];
|
||||
// const cp1 = Vec3.create(points[idx*3], points[idx*3+1], points[idx*3+2]) // controlPoints[currentPointId];
|
||||
// const cp2 = Vec3.create(points[(idx+1)*3], points[(idx+1)*3+1], points[(idx+1)*3+2]) // controlPoints[currentPointId + 1];
|
||||
// const cp3 = Vec3.create(points[(idx+2)*3], points[(idx+2)*3+1], points[(idx+2)*3+2]); // controlPoints[currentPointId + 2];
|
||||
let found = false
|
||||
let found = false;
|
||||
for (; lerpValue <= 1; lerpValue += 0.01) {
|
||||
// lerp?slerp
|
||||
// let candidate:Vec3 = Vec3.lerp(Vec3.zero(), cp0, cp1, lerpValue);
|
||||
// const candidate:Vec3 = Vec3.bezier(Vec3.zero(), cp0, cp1, cp2, cp3, lerpValue);
|
||||
const candidate = CubicInterpolate(Vec3(), cp0, cp1, cp2, cp3, lerpValue)
|
||||
const candidate = CubicInterpolate(Vec3(), cp0, cp1, cp2, cp3, lerpValue);
|
||||
const d = Vec3.distance(currentPosition, candidate);
|
||||
if (d > segmentLength) {
|
||||
resampledControlPoints.push(candidate)
|
||||
Vec3.copy(currentPosition, candidate)
|
||||
found = true
|
||||
break
|
||||
resampledControlPoints.push(candidate);
|
||||
Vec3.copy(currentPosition, candidate);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
lerpValue = 0
|
||||
idx += 1
|
||||
lerpValue = 0;
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
return resampledControlPoints
|
||||
return resampledControlPoints;
|
||||
}
|
||||
|
||||
|
||||
const prevV = Vec3()
|
||||
const tmpV1 = Vec3()
|
||||
const tmpV2 = Vec3()
|
||||
const tmpV3 = Vec3()
|
||||
const prevV = Vec3();
|
||||
const tmpV1 = Vec3();
|
||||
const tmpV2 = Vec3();
|
||||
const tmpV3 = Vec3();
|
||||
|
||||
// easier to align to theses normals
|
||||
function GetSmoothNormals(points: Vec3[]) {
|
||||
const nP: number = points.length;
|
||||
const smoothNormals: Vec3[] = []
|
||||
const smoothNormals: Vec3[] = [];
|
||||
if (points.length < 3) {
|
||||
for (let i = 0; i < points.length; ++i)
|
||||
smoothNormals.push(Vec3.normalize(Vec3(), points[i]))
|
||||
smoothNormals.push(Vec3.normalize(Vec3(), points[i]));
|
||||
return smoothNormals;
|
||||
}
|
||||
let p0 = points[0]
|
||||
let p1 = points[1]
|
||||
let p2 = points[2]
|
||||
const p21 = Vec3.sub(tmpV1, p2, p1)
|
||||
const p01 = Vec3.sub(tmpV2, p0, p1)
|
||||
const p0121 = Vec3.cross(tmpV3, p01, p21)
|
||||
Vec3.normalize(prevV, p0121)
|
||||
smoothNormals.push(Vec3.clone(prevV))
|
||||
let p0 = points[0];
|
||||
let p1 = points[1];
|
||||
let p2 = points[2];
|
||||
const p21 = Vec3.sub(tmpV1, p2, p1);
|
||||
const p01 = Vec3.sub(tmpV2, p0, p1);
|
||||
const p0121 = Vec3.cross(tmpV3, p01, p21);
|
||||
Vec3.normalize(prevV, p0121);
|
||||
smoothNormals.push(Vec3.clone(prevV));
|
||||
for (let i = 1; i < points.length - 1; ++i) {
|
||||
p0 = points[i - 1]
|
||||
p1 = points[i]
|
||||
p2 = points[i + 1]
|
||||
const t = Vec3.normalize(tmpV1, Vec3.sub(tmpV1, p2 , p0))
|
||||
const b = Vec3.normalize(tmpV2, Vec3.cross(tmpV2, t, prevV))
|
||||
const n = Vec3.normalize(Vec3(), Vec3.cross(tmpV3, t, b))
|
||||
Vec3.negate(n, n)
|
||||
Vec3.copy(prevV, n)
|
||||
smoothNormals.push(n)
|
||||
p0 = points[i - 1];
|
||||
p1 = points[i];
|
||||
p2 = points[i + 1];
|
||||
const t = Vec3.normalize(tmpV1, Vec3.sub(tmpV1, p2, p0));
|
||||
const b = Vec3.normalize(tmpV2, Vec3.cross(tmpV2, t, prevV));
|
||||
const n = Vec3.normalize(Vec3(), Vec3.cross(tmpV3, t, b));
|
||||
Vec3.negate(n, n);
|
||||
Vec3.copy(prevV, n);
|
||||
smoothNormals.push(n);
|
||||
}
|
||||
const last = Vec3()
|
||||
const last = Vec3();
|
||||
Vec3.normalize(last, Vec3.cross(last,
|
||||
Vec3.sub(tmpV1, points[nP - 3], points[nP-2]),
|
||||
Vec3.sub(tmpV2, points[nP-2] , points[nP-1]))
|
||||
)
|
||||
smoothNormals.push(last)
|
||||
Vec3.sub(tmpV1, points[nP - 3], points[nP - 2]),
|
||||
Vec3.sub(tmpV2, points[nP - 2], points[nP - 1]))
|
||||
);
|
||||
smoothNormals.push(last);
|
||||
return smoothNormals;
|
||||
}
|
||||
|
||||
const frameTmpV1 = Vec3()
|
||||
const frameTmpV2 = Vec3()
|
||||
const frameTmpV3 = Vec3()
|
||||
const frameTmpV1 = Vec3();
|
||||
const frameTmpV2 = Vec3();
|
||||
const frameTmpV3 = Vec3();
|
||||
|
||||
function getFrame(reference: Vec3, tangent: Vec3) {
|
||||
const t = Vec3.normalize(Vec3(), tangent);
|
||||
// make reference vector orthogonal to tangent
|
||||
const proj_r_to_t = Vec3.scale(
|
||||
frameTmpV1, tangent, Vec3.dot(reference, tangent) / Vec3.dot(tangent, tangent)
|
||||
)
|
||||
const r = Vec3.normalize(Vec3(), Vec3.sub(frameTmpV2, reference, proj_r_to_t))
|
||||
);
|
||||
const r = Vec3.normalize(Vec3(), Vec3.sub(frameTmpV2, reference, proj_r_to_t));
|
||||
// make bitangent vector orthogonal to the others
|
||||
const s = Vec3.normalize(Vec3(), Vec3.cross(frameTmpV3, t, r))
|
||||
return { t, r, s }
|
||||
const s = Vec3.normalize(Vec3(), Vec3.cross(frameTmpV3, t, r));
|
||||
return { t, r, s };
|
||||
}
|
||||
|
||||
const mfTmpV1 = Vec3()
|
||||
const mfTmpV2 = Vec3()
|
||||
const mfTmpV3 = Vec3()
|
||||
const mfTmpV4 = Vec3()
|
||||
const mfTmpV5 = Vec3()
|
||||
const mfTmpV6 = Vec3()
|
||||
const mfTmpV7 = Vec3()
|
||||
const mfTmpV8 = Vec3()
|
||||
const mfTmpV9 = Vec3()
|
||||
const mfTmpV1 = Vec3();
|
||||
const mfTmpV2 = Vec3();
|
||||
const mfTmpV3 = Vec3();
|
||||
const mfTmpV4 = Vec3();
|
||||
const mfTmpV5 = Vec3();
|
||||
const mfTmpV6 = Vec3();
|
||||
const mfTmpV7 = Vec3();
|
||||
const mfTmpV8 = Vec3();
|
||||
const mfTmpV9 = Vec3();
|
||||
|
||||
// easier to align to theses normals
|
||||
// https://github.com/bzamecnik/gpg/blob/master/rotation-minimizing-frame/rmf.py
|
||||
function GetMiniFrame(points: Vec3[], normals: Vec3[]) {
|
||||
const frames: Frame[] = [];
|
||||
const t0 = Vec3.normalize(mfTmpV1, Vec3.sub(mfTmpV1, points[1], points[0]))
|
||||
frames.push(getFrame(normals[0], t0))
|
||||
const t0 = Vec3.normalize(mfTmpV1, Vec3.sub(mfTmpV1, points[1], points[0]));
|
||||
frames.push(getFrame(normals[0], t0));
|
||||
|
||||
for (let i = 0; i< points.length-2; ++i) {
|
||||
const t2 = Vec3.normalize(mfTmpV1, Vec3.sub(mfTmpV1, points[i+2], points[i+1]))
|
||||
const v1 = Vec3.sub(mfTmpV2, points[i + 1], points[i]) // this is tangeant
|
||||
const c1 = Vec3.dot(v1, v1)
|
||||
for (let i = 0; i < points.length - 2; ++i) {
|
||||
const t2 = Vec3.normalize(mfTmpV1, Vec3.sub(mfTmpV1, points[i + 2], points[i + 1]));
|
||||
const v1 = Vec3.sub(mfTmpV2, points[i + 1], points[i]); // this is tangeant
|
||||
const c1 = Vec3.dot(v1, v1);
|
||||
// compute r_i^L = R_1 * r_i
|
||||
const v1r = Vec3.scale(mfTmpV3, v1, (2.0 / c1) * Vec3.dot(v1, frames[i].r))
|
||||
const ref_L_i = Vec3.sub(mfTmpV4, frames[i].r, v1r)
|
||||
const v1r = Vec3.scale(mfTmpV3, v1, (2.0 / c1) * Vec3.dot(v1, frames[i].r));
|
||||
const ref_L_i = Vec3.sub(mfTmpV4, frames[i].r, v1r);
|
||||
// compute t_i^L = R_1 * t_i
|
||||
const v1t = Vec3.scale(mfTmpV5, v1, (2.0 / c1) * Vec3.dot(v1, frames[i].t))
|
||||
const tan_L_i = Vec3.sub(mfTmpV6, frames[i].t, v1t)
|
||||
const v1t = Vec3.scale(mfTmpV5, v1, (2.0 / c1) * Vec3.dot(v1, frames[i].t));
|
||||
const tan_L_i = Vec3.sub(mfTmpV6, frames[i].t, v1t);
|
||||
// # compute reflection vector of R_2
|
||||
const v2 = Vec3.sub(mfTmpV7, t2 , tan_L_i)
|
||||
const c2 = Vec3.dot(v2, v2)
|
||||
const v2 = Vec3.sub(mfTmpV7, t2, tan_L_i);
|
||||
const c2 = Vec3.dot(v2, v2);
|
||||
// compute r_(i+1) = R_2 * r_i^L
|
||||
const v2l = Vec3.scale(mfTmpV8, v1, (2.0/c2) * Vec3.dot(v2, ref_L_i))
|
||||
const ref_next = Vec3.sub(mfTmpV9, ref_L_i, v2l) // ref_L_i - (2 / c2) * v2.dot(ref_L_i) * v2
|
||||
frames.push(getFrame(ref_next, t2)) // frames.append(Frame(ref_next, tangents[i+1]))
|
||||
const v2l = Vec3.scale(mfTmpV8, v1, (2.0 / c2) * Vec3.dot(v2, ref_L_i));
|
||||
const ref_next = Vec3.sub(mfTmpV9, ref_L_i, v2l); // ref_L_i - (2 / c2) * v2.dot(ref_L_i) * v2
|
||||
frames.push(getFrame(ref_next, t2)); // frames.append(Frame(ref_next, tangents[i+1]))
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
const rpTmpVec1 = Vec3()
|
||||
const rpTmpVec1 = Vec3();
|
||||
|
||||
export function getMatFromResamplePoints(points: NumberArray) {
|
||||
const segmentLength = 3.4
|
||||
const new_points = ResampleControlPoints(points, 3.4)
|
||||
const npoints = new_points.length
|
||||
const new_normal = GetSmoothNormals(new_points)
|
||||
const frames = GetMiniFrame(new_points, new_normal)
|
||||
const limit = npoints
|
||||
const transforms: Mat4[] = []
|
||||
export function getMatFromResamplePoints(points: NumberArray, segmentLength: number) {
|
||||
const new_points = ResampleControlPoints(points, segmentLength);
|
||||
const npoints = new_points.length;
|
||||
const new_normal = GetSmoothNormals(new_points);
|
||||
const frames = GetMiniFrame(new_points, new_normal);
|
||||
const limit = npoints;
|
||||
const transforms: Mat4[] = [];
|
||||
const pti = Vec3.copy(rpTmpVec1, new_points[0]);
|
||||
// console.log(new_points.length)
|
||||
// console.log(points.length/3)
|
||||
// console.log(limit)
|
||||
// console.log(segmentLength)
|
||||
for (let i = 0; i<npoints-2; ++i) {
|
||||
const pti1: Vec3 = new_points[i+1] // Vec3.create(points[(i+1)*3],points[(i+1)*3+1],points[(i+1)*3+2]);
|
||||
const d = Vec3.distance(pti, pti1)
|
||||
for (let i = 0; i < npoints - 2; ++i) {
|
||||
const pti1: Vec3 = new_points[i + 1]; // Vec3.create(points[(i+1)*3],points[(i+1)*3+1],points[(i+1)*3+2]);
|
||||
const d = Vec3.distance(pti, pti1);
|
||||
if (d >= segmentLength) {
|
||||
// use twist or random?
|
||||
const quat = Quat.rotationTo(Quat.zero(), Vec3.create(0, 0, 1), frames[i].t) // Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),new_normal[i]);//Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),direction);new_normal
|
||||
const rq = Quat.setAxisAngle(Quat.zero(), frames[i].t, Math.random()*3.60 ) // Quat.setAxisAngle(Quat.zero(),direction, Math.random()*3.60 );//Quat.identity();//
|
||||
const m = Mat4.fromQuat(Mat4.zero(), Quat.multiply(Quat.zero(), rq, quat)) // Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),quat1,quat2));//Mat4.fromQuat(Mat4.zero(),quat);//Mat4.identity();//Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),rq,quat));
|
||||
const quat = Quat.rotationTo(Quat.zero(), Vec3.create(0, 0, 1), frames[i].t); // Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),new_normal[i]);//Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),direction);new_normal
|
||||
const rq = Quat.setAxisAngle(Quat.zero(), frames[i].t, Math.random() * 3.60 ); // Quat.setAxisAngle(Quat.zero(),direction, Math.random()*3.60 );//Quat.identity();//
|
||||
const m = Mat4.fromQuat(Mat4.zero(), Quat.multiply(Quat.zero(), rq, quat)); // Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),quat1,quat2));//Mat4.fromQuat(Mat4.zero(),quat);//Mat4.identity();//Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),rq,quat));
|
||||
// let pos:Vec3 = Vec3.add(Vec3.zero(),pti1,pti)
|
||||
// pos = Vec3.scale(pos,pos,1.0/2.0);
|
||||
// Vec3.makeRotation(Mat4.zero(),Vec3.create(0,0,1),frames[i].t);//
|
||||
Mat4.setTranslation(m, pti1)
|
||||
Mat4.setTranslation(m, pti1);
|
||||
// let m2:Mat4 = GetTubePropertiesMatrix(pti,pti1);
|
||||
// let q:Quat = Quat.rotationTo(Quat.zero(), Vec3.create(0,1,0),Vec3.create(0,0,1))
|
||||
// m2=Mat4.mul(Mat4.identity(),Mat4.fromQuat(Mat4.zero(),q),m2);
|
||||
transforms.push(m)
|
||||
Vec3.copy(pti, pti1)
|
||||
transforms.push(m);
|
||||
Vec3.copy(pti, pti1);
|
||||
}
|
||||
if (transforms.length >= limit) break
|
||||
if (transforms.length >= limit) break;
|
||||
}
|
||||
return transforms
|
||||
return transforms;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec3, Quat } from '../../../../mol-math/linear-algebra';
|
||||
import { Vec3, Quat } from '../../mol-math/linear-algebra';
|
||||
|
||||
export interface CellPack {
|
||||
cell: Cell
|
||||
@@ -27,8 +27,7 @@ export interface Cell {
|
||||
|
||||
export interface Recipe {
|
||||
setupfile: string
|
||||
/** First entry is name, secound is path: [name: string, path: string][] */
|
||||
paths: [string, string][]
|
||||
paths: [string, string][] // [name: string, path: string][]
|
||||
version: string
|
||||
name: string
|
||||
}
|
||||
@@ -42,21 +41,41 @@ export interface Packing {
|
||||
ingredients: { [key: string]: Ingredient }
|
||||
}
|
||||
|
||||
export interface Ingredient {
|
||||
source: IngredientSource
|
||||
results: [Vec3, Quat][]
|
||||
name: string
|
||||
positions?: [Vec3[]] // why wrapped in an extra array?
|
||||
radii?: [number[]] // why wrapped in an extra array?
|
||||
export interface Positions {
|
||||
coords?: Vec3[];
|
||||
}
|
||||
|
||||
export interface Radii {
|
||||
radii?: number[];
|
||||
}
|
||||
|
||||
export interface Ingredient {
|
||||
source: IngredientSource;
|
||||
results: [Vec3, Quat][];
|
||||
name: string;
|
||||
/** Vec3[]];CoarseGraind Beads coordinates LOD */
|
||||
positions?: [Positions];
|
||||
/** number[]];CoarseGraind Beads radii LOD */
|
||||
radii?: [Radii];
|
||||
/** Number of `curveX` properties in the object where `X` is a 0-indexed number */
|
||||
nbCurve?: number
|
||||
nbCurve?: number;
|
||||
/** Curve properties are Vec3[] but that is not expressable in TypeScript */
|
||||
[curveX: string]: unknown
|
||||
[curveX: string]: unknown;
|
||||
/** the orientation in the membrane */
|
||||
principalAxis?: Vec3;
|
||||
/** offset along membrane */
|
||||
offset?: Vec3;
|
||||
ingtype?: string;
|
||||
}
|
||||
|
||||
export interface IngredientSource {
|
||||
pdb: string
|
||||
transform: { center: boolean, translate?: Vec3 }
|
||||
biomt?: boolean
|
||||
}
|
||||
pdb: string;
|
||||
bu?: string; /** biological unit e.g AU,BU1,etc.. */
|
||||
selection?: string; /** NGL selection or :A or :B etc.. */
|
||||
model?: string; /** model number e.g 0,1,2... */
|
||||
transform: {
|
||||
center: boolean;
|
||||
translate?: Vec3;
|
||||
};
|
||||
biomt?: boolean;
|
||||
}
|
||||
30
src/extensions/cellpack/index.ts
Normal file
30
src/extensions/cellpack/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior';
|
||||
import { CellPackColorThemeProvider } from './color';
|
||||
import { LoadCellPackModel } from './model';
|
||||
|
||||
|
||||
export const CellPack = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
|
||||
name: 'cellpack',
|
||||
category: 'custom-props',
|
||||
display: {
|
||||
name: 'CellPack',
|
||||
description: 'CellPack Model Loading and Viewing.'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
|
||||
register(): void {
|
||||
this.ctx.state.data.actions.add(LoadCellPackModel);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(CellPackColorThemeProvider);
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.ctx.state.data.actions.remove(LoadCellPackModel);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(CellPackColorThemeProvider);
|
||||
}
|
||||
}
|
||||
});
|
||||
551
src/extensions/cellpack/model.ts
Normal file
551
src/extensions/cellpack/model.ts
Normal file
@@ -0,0 +1,551 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateAction, StateBuilder, StateTransformer, State } from '../../mol-state';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Ingredient, IngredientSource, CellPacking } from './data';
|
||||
import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean, getFromOPM } from './util';
|
||||
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../mol-model/structure';
|
||||
import { trajectoryFromMmCIF, MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { trajectoryFromPDB } from '../../mol-model-formats/structure/pdb';
|
||||
import { Mat4, Vec3, Quat } from '../../mol-math/linear-algebra';
|
||||
import { SymmetryOperator } from '../../mol-math/geometry';
|
||||
import { Task, RuntimeContext } from '../../mol-task';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl } from './state';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { getMatFromResamplePoints } from './curve';
|
||||
import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
import { CifCategory, CifField } from '../../mol-io/reader/cif';
|
||||
import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Column } from '../../mol-data/db';
|
||||
import { createModels } from '../../mol-model-formats/structure/basic/parser';
|
||||
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { readFromFile } from '../../mol-util/data-source';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/results/${fileName}`;
|
||||
}
|
||||
|
||||
class TrajectoryCache {
|
||||
private map = new Map<string, Model.Trajectory>();
|
||||
set(id: string, trajectory: Model.Trajectory) { this.map.set(id, trajectory); }
|
||||
get(id: string) { return this.map.get(id); }
|
||||
}
|
||||
|
||||
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, baseUrl: string, trajCache: TrajectoryCache, file?: Asset.File) {
|
||||
const assetManager = plugin.managers.asset;
|
||||
const modelIndex = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
|
||||
const surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
|
||||
let trajectory = trajCache.get(id);
|
||||
let assets: Asset.Wrapper[] = [];
|
||||
if (!trajectory) {
|
||||
if (file) {
|
||||
if (file.name.endsWith('.cif')) {
|
||||
const text = await plugin.runTask(assetManager.resolve(file, 'string'));
|
||||
assets.push(text);
|
||||
const cif = (await parseCif(plugin, text.data)).blocks[0];
|
||||
trajectory = await plugin.runTask(trajectoryFromMmCIF(cif));
|
||||
} else if (file.name.endsWith('.bcif')) {
|
||||
const binary = await plugin.runTask(assetManager.resolve(file, 'binary'));
|
||||
assets.push(binary);
|
||||
const cif = (await parseCif(plugin, binary.data)).blocks[0];
|
||||
trajectory = await plugin.runTask(trajectoryFromMmCIF(cif));
|
||||
} else if (file.name.endsWith('.pdb')) {
|
||||
const text = await plugin.runTask(assetManager.resolve(file, 'string'));
|
||||
assets.push(text);
|
||||
const pdb = await parsePDBfile(plugin, text.data, id);
|
||||
trajectory = await plugin.runTask(trajectoryFromPDB(pdb));
|
||||
} else {
|
||||
throw new Error(`unsupported file type '${file.name}'`);
|
||||
}
|
||||
} else if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
|
||||
if (surface){
|
||||
const data = await getFromOPM(plugin, id, assetManager);
|
||||
if (data.asset){
|
||||
assets.push(data.asset);
|
||||
trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
|
||||
} else {
|
||||
const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
|
||||
assets.push(asset);
|
||||
trajectory = await plugin.runTask(trajectoryFromMmCIF(mmcif));
|
||||
}
|
||||
} else {
|
||||
const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
|
||||
assets.push(asset);
|
||||
trajectory = await plugin.runTask(trajectoryFromMmCIF(mmcif));
|
||||
}
|
||||
} else {
|
||||
const data = await getFromCellPackDB(plugin, id, baseUrl, assetManager);
|
||||
assets.push(data.asset);
|
||||
if ('pdb' in data) {
|
||||
trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
|
||||
} else {
|
||||
trajectory = await plugin.runTask(trajectoryFromMmCIF(data.mmcif));
|
||||
}
|
||||
}
|
||||
trajCache.set(id, trajectory);
|
||||
}
|
||||
const model = trajectory[modelIndex];
|
||||
return { model, assets };
|
||||
}
|
||||
|
||||
async function getStructure(plugin: PluginContext, model: Model, source: IngredientSource, props: { assembly?: string } = {}) {
|
||||
let structure = Structure.ofModel(model);
|
||||
const { assembly } = props;
|
||||
|
||||
if (assembly) {
|
||||
structure = await plugin.runTask(StructureSymmetry.buildAssembly(structure, assembly));
|
||||
}
|
||||
let query;
|
||||
if (source.selection){
|
||||
const asymIds: string[] = source.selection.replace(' :', '').split(' or');
|
||||
query = MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
'chain-test': MS.core.set.has([MS.set(...asymIds), MS.ammp('auth_asym_id')])
|
||||
})
|
||||
]);
|
||||
} else {
|
||||
query = MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer'])
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
const compiled = compile<StructureSelection>(query);
|
||||
const result = compiled(new QueryContext(structure));
|
||||
structure = StructureSelection.unionStructure(result);
|
||||
|
||||
return structure;
|
||||
}
|
||||
|
||||
function getTransformLegacy(trans: Vec3, rot: Quat) {
|
||||
const q: Quat = Quat.create(-rot[3], rot[0], rot[1], rot[2]);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
|
||||
Mat4.transpose(m, m);
|
||||
Mat4.scale(m, m, Vec3.create(-1.0, 1.0, -1.0));
|
||||
Mat4.setTranslation(m, trans);
|
||||
return m;
|
||||
}
|
||||
|
||||
function getTransform(trans: Vec3, rot: Quat) {
|
||||
const q: Quat = Quat.create(rot[0], rot[1], rot[2], rot[3]);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
|
||||
const p: Vec3 = Vec3.create(trans[0], trans[1], trans[2]);
|
||||
Mat4.setTranslation(m, p);
|
||||
return m;
|
||||
}
|
||||
|
||||
function getResultTransforms(results: Ingredient['results'], legacy: boolean) {
|
||||
if (legacy) return results.map((r: Ingredient['results'][0]) => getTransformLegacy(r[0], r[1]));
|
||||
else return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1]));
|
||||
}
|
||||
|
||||
function getCurveTransforms(ingredient: Ingredient) {
|
||||
const n = ingredient.nbCurve || 0;
|
||||
const instances: Mat4[] = [];
|
||||
const segmentLength = ingredient.radii
|
||||
? (ingredient.radii[0].radii
|
||||
? ingredient.radii[0].radii[0] * 2.0
|
||||
: 3.4)
|
||||
: 3.4;
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const cname = `curve${i}`;
|
||||
if (!(cname in ingredient)) {
|
||||
// console.warn(`Expected '${cname}' in ingredient`)
|
||||
continue;
|
||||
}
|
||||
const _points = ingredient[cname] as Vec3[];
|
||||
if (_points.length <= 2) {
|
||||
// TODO handle curve with 2 or less points
|
||||
continue;
|
||||
}
|
||||
const points = new Float32Array(_points.length * 3);
|
||||
for (let i = 0, il = _points.length; i < il; ++i) Vec3.toArray(_points[i], points, i * 3);
|
||||
const newInstances = getMatFromResamplePoints(points, segmentLength);
|
||||
instances.push(...newInstances);
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
function getAssembly(transforms: Mat4[], structure: Structure) {
|
||||
const builder = Structure.Builder();
|
||||
const { units } = structure;
|
||||
|
||||
for (let i = 0, il = transforms.length; i < il; ++i) {
|
||||
const id = `${i + 1}`;
|
||||
const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [ id ] } });
|
||||
for (const unit of units) {
|
||||
builder.addWithOperator(unit, op);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.getStructure();
|
||||
}
|
||||
|
||||
function getCifCurve(name: string, transforms: Mat4[], model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) throw new Error('mmcif source data needed');
|
||||
|
||||
const { db } = model.sourceData.data;
|
||||
const d = db.atom_site;
|
||||
const n = d._rowCount;
|
||||
const rowCount = n * transforms.length;
|
||||
|
||||
const { offsets, count } = model.atomicHierarchy.chainAtomSegments;
|
||||
|
||||
const x = d.Cartn_x.toArray();
|
||||
const y = d.Cartn_y.toArray();
|
||||
const z = d.Cartn_z.toArray();
|
||||
|
||||
const Cartn_x = new Float32Array(rowCount);
|
||||
const Cartn_y = new Float32Array(rowCount);
|
||||
const Cartn_z = new Float32Array(rowCount);
|
||||
const map = new Uint32Array(rowCount);
|
||||
const seq = new Int32Array(rowCount);
|
||||
let offset = 0;
|
||||
for (let c = 0; c < count; ++c) {
|
||||
const cStart = offsets[c];
|
||||
const cEnd = offsets[c + 1];
|
||||
const cLength = cEnd - cStart;
|
||||
for (let t = 0, tl = transforms.length; t < tl; ++t) {
|
||||
const m = transforms[t];
|
||||
for (let j = cStart; j < cEnd; ++j) {
|
||||
const i = offset + j - cStart;
|
||||
const xj = x[j], yj = y[j], zj = z[j];
|
||||
Cartn_x[i] = m[0] * xj + m[4] * yj + m[8] * zj + m[12];
|
||||
Cartn_y[i] = m[1] * xj + m[5] * yj + m[9] * zj + m[13];
|
||||
Cartn_z[i] = m[2] * xj + m[6] * yj + m[10] * zj + m[14];
|
||||
map[i] = j;
|
||||
seq[i] = t + 1;
|
||||
}
|
||||
offset += cLength;
|
||||
}
|
||||
}
|
||||
|
||||
function multColumn<T>(column: Column<T>) {
|
||||
const array = column.toArray();
|
||||
return Column.ofLambda({
|
||||
value: row => array[map[row]],
|
||||
areValuesEqual: (rowA, rowB) => map[rowA] === map[rowB] || array[map[rowA]] === array[map[rowB]],
|
||||
rowCount, schema: column.schema
|
||||
});
|
||||
}
|
||||
|
||||
const _atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = {
|
||||
auth_asym_id: CifField.ofColumn(multColumn(d.auth_asym_id)),
|
||||
auth_atom_id: CifField.ofColumn(multColumn(d.auth_atom_id)),
|
||||
auth_comp_id: CifField.ofColumn(multColumn(d.auth_comp_id)),
|
||||
auth_seq_id: CifField.ofNumbers(seq),
|
||||
|
||||
B_iso_or_equiv: CifField.ofColumn(Column.ofConst(0, rowCount, Column.Schema.float)),
|
||||
Cartn_x: CifField.ofNumbers(Cartn_x),
|
||||
Cartn_y: CifField.ofNumbers(Cartn_y),
|
||||
Cartn_z: CifField.ofNumbers(Cartn_z),
|
||||
group_PDB: CifField.ofColumn(Column.ofConst('ATOM', rowCount, Column.Schema.str)),
|
||||
id: CifField.ofColumn(Column.ofLambda({
|
||||
value: row => row,
|
||||
areValuesEqual: (rowA, rowB) => rowA === rowB,
|
||||
rowCount, schema: d.id.schema,
|
||||
})),
|
||||
|
||||
label_alt_id: CifField.ofColumn(multColumn(d.label_alt_id)),
|
||||
|
||||
label_asym_id: CifField.ofColumn(multColumn(d.label_asym_id)),
|
||||
label_atom_id: CifField.ofColumn(multColumn(d.label_atom_id)),
|
||||
label_comp_id: CifField.ofColumn(multColumn(d.label_comp_id)),
|
||||
label_seq_id: CifField.ofNumbers(seq),
|
||||
label_entity_id: CifField.ofColumn(Column.ofConst('1', rowCount, Column.Schema.str)),
|
||||
|
||||
occupancy: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.float)),
|
||||
type_symbol: CifField.ofColumn(multColumn(d.type_symbol)),
|
||||
|
||||
pdbx_PDB_ins_code: CifField.ofColumn(Column.ofConst('', rowCount, Column.Schema.str)),
|
||||
pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.int)),
|
||||
};
|
||||
|
||||
const categories = {
|
||||
entity: CifCategory.ofTable('entity', db.entity),
|
||||
chem_comp: CifCategory.ofTable('chem_comp', db.chem_comp),
|
||||
atom_site: CifCategory.ofFields('atom_site', _atom_site)
|
||||
};
|
||||
|
||||
return {
|
||||
header: name,
|
||||
categoryNames: Object.keys(categories),
|
||||
categories
|
||||
};
|
||||
}
|
||||
|
||||
async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredient, transforms: Mat4[], model: Model) {
|
||||
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);
|
||||
return models[0];
|
||||
});
|
||||
|
||||
const curveModel = await plugin.runTask(curveModelTask);
|
||||
return getStructure(plugin, curveModel, ingredient.source);
|
||||
}
|
||||
|
||||
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache) {
|
||||
const { name, source, results, nbCurve } = ingredient;
|
||||
if (source.pdb === 'None') return;
|
||||
|
||||
const file = ingredientFiles[source.pdb];
|
||||
if (!file) {
|
||||
// TODO can these be added to the library?
|
||||
if (name === 'HIV1_CAhex_0_1_0') return;
|
||||
if (name === 'HIV1_CAhexCyclophilA_0_1_0') return;
|
||||
if (name === 'iLDL') return;
|
||||
if (name === 'peptides') return;
|
||||
if (name === 'lypoglycane') return;
|
||||
}
|
||||
|
||||
// model id in case structure is NMR
|
||||
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, file);
|
||||
if (!model) return;
|
||||
|
||||
let structure: Structure;
|
||||
if (nbCurve) {
|
||||
structure = await getCurve(plugin, name, ingredient, getCurveTransforms(ingredient), model);
|
||||
} else {
|
||||
let bu: string|undefined = source.bu ? source.bu : undefined;
|
||||
if (bu){
|
||||
if (bu === 'AU') {
|
||||
bu = undefined;
|
||||
} else {
|
||||
bu = bu.slice(2);
|
||||
}
|
||||
}
|
||||
structure = await getStructure(plugin, model, source, { assembly: bu });
|
||||
// transform with offset and pcp
|
||||
let legacy: boolean = true;
|
||||
if (ingredient.offset || ingredient.principalAxis){
|
||||
legacy = false;
|
||||
const structureMean = getStructureMean(structure);
|
||||
Vec3.negate(structureMean, structureMean);
|
||||
const m1: Mat4 = Mat4.identity();
|
||||
Mat4.setTranslation(m1, structureMean);
|
||||
structure = Structure.transform(structure, m1);
|
||||
if (ingredient.offset){
|
||||
if (!Vec3.exactEquals(ingredient.offset, Vec3.zero())){
|
||||
const m: Mat4 = Mat4.identity();
|
||||
Mat4.setTranslation(m, ingredient.offset);
|
||||
structure = Structure.transform(structure, m);
|
||||
}
|
||||
}
|
||||
if (ingredient.principalAxis){
|
||||
if (!Vec3.exactEquals(ingredient.principalAxis, Vec3.unitZ)){
|
||||
const q: Quat = Quat.identity();
|
||||
Quat.rotationTo(q, ingredient.principalAxis, Vec3.unitZ);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
|
||||
structure = Structure.transform(structure, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
structure = getAssembly(getResultTransforms(results, legacy), structure);
|
||||
}
|
||||
|
||||
return { structure, assets };
|
||||
}
|
||||
|
||||
export function createStructureFromCellPack(plugin: PluginContext, packing: CellPacking, baseUrl: string, ingredientFiles: IngredientFiles) {
|
||||
return Task.create('Create Packing Structure', async ctx => {
|
||||
const { ingredients, name } = packing;
|
||||
const assets: Asset.Wrapper[] = [];
|
||||
const trajCache = new TrajectoryCache();
|
||||
const structures: Structure[] = [];
|
||||
for (const iName in ingredients) {
|
||||
if (ctx.shouldUpdate) await ctx.update(iName);
|
||||
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache);
|
||||
if (ingredientStructure) {
|
||||
structures.push(ingredientStructure.structure);
|
||||
assets.push(...ingredientStructure.assets);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - units`);
|
||||
const builder = Structure.Builder({ label: name });
|
||||
let offsetInvariantId = 0;
|
||||
for (const s of structures) {
|
||||
if (ctx.shouldUpdate) await ctx.update(`${s.label}`);
|
||||
let maxInvariantId = 0;
|
||||
for (const u of s.units) {
|
||||
const invariantId = u.invariantId + offsetInvariantId;
|
||||
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
|
||||
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId);
|
||||
}
|
||||
offsetInvariantId += maxInvariantId + 1;
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
|
||||
const structure = builder.getStructure();
|
||||
for( let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
const { trajectoryInfo } = structure.models[i];
|
||||
trajectoryInfo.size = il;
|
||||
trajectoryInfo.index = i;
|
||||
}
|
||||
return { structure, assets };
|
||||
});
|
||||
}
|
||||
|
||||
async function handleHivRna(plugin: PluginContext, packings: CellPacking[], baseUrl: string) {
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${baseUrl}/extras/rna_allpoints.json`);
|
||||
const json = await plugin.runTask(plugin.managers.asset.resolve(url, 'json', false));
|
||||
const points = json.data.points as number[];
|
||||
|
||||
const curve0: Vec3[] = [];
|
||||
for (let j = 0, jl = points.length; j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j));
|
||||
}
|
||||
packings[i].ingredients['RNA'] = {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMembrane(plugin: PluginContext, name: string, state: State, params: LoadCellPackModelParams) {
|
||||
let file: Asset.File | undefined = undefined;
|
||||
if (params.ingredients.files !== null) {
|
||||
const fileName = `${name}.bcif`;
|
||||
for (const f of params.ingredients.files) {
|
||||
if (fileName === f.name) {
|
||||
file = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let b = state.build().toRoot();
|
||||
if (file) {
|
||||
b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } });
|
||||
} else {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
|
||||
}
|
||||
|
||||
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.StructureFromModel)
|
||||
.commit({ revertOnError: true });
|
||||
|
||||
const membraneParams = {
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
|
||||
}
|
||||
|
||||
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
|
||||
const ingredientFiles = params.ingredients.files || [];
|
||||
|
||||
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
|
||||
if (params.source.name === 'id') {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl));
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } });
|
||||
} else {
|
||||
const file = params.source.params;
|
||||
if (!file?.file) {
|
||||
plugin.log.error('No file selected');
|
||||
return;
|
||||
}
|
||||
|
||||
let jsonFile: Asset.File;
|
||||
if (file.name.toLowerCase().endsWith('.zip')) {
|
||||
const data = await readFromFile(file.file, 'zip').runInContext(runtime);
|
||||
jsonFile = Asset.File(new File([data['model.json']], 'model.json'));
|
||||
objectForEach(data, (v, k) => {
|
||||
if (k === 'model.json') return;
|
||||
ingredientFiles.push(Asset.File(new File([v], k)));
|
||||
});
|
||||
} else {
|
||||
jsonFile = file;
|
||||
}
|
||||
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.ReadFile, { file: jsonFile, isBinary: false, label: jsonFile.name }, { state: { isGhost: true } });
|
||||
}
|
||||
|
||||
const cellPackBuilder = cellPackJson
|
||||
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack);
|
||||
|
||||
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime);
|
||||
const { packings } = cellPackObject.obj!.data;
|
||||
|
||||
await handleHivRna(plugin, packings, params.baseUrl);
|
||||
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
const p = { packing: i, baseUrl: params.baseUrl, ingredientFiles };
|
||||
|
||||
const packing = await state.build()
|
||||
.to(cellPackBuilder.ref)
|
||||
.apply(StructureFromCellpack, p)
|
||||
.commit({ revertOnError: true });
|
||||
|
||||
const packingParams = {
|
||||
traceOnly: params.preset.traceOnly,
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackPackingPreset.apply(packing, packingParams, plugin);
|
||||
if ( packings[i].location === 'surface' ){
|
||||
await loadMembrane(plugin, packings[i].name, state, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const LoadCellPackModelParams = {
|
||||
source: PD.MappedStatic('id', {
|
||||
'id': PD.Select('influenza_model1.json', [
|
||||
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
||||
['hiv_lipids.bcif', 'hiv_lipids'],
|
||||
['influenza_model1.json', 'influenza_model1'],
|
||||
['ExosomeModel.json', 'ExosomeModel'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
] as const, { description: 'Download the model definition with `id` from the server at `baseUrl.`' }),
|
||||
'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.' }),
|
||||
}, { options: [['id', 'Id'], ['file', 'File']] }),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
ingredients : PD.Group({
|
||||
files: PD.FileList({ accept: '.cif,.bcif,.pdb' })
|
||||
}, { isExpanded: true }),
|
||||
preset: PD.Group({
|
||||
traceOnly: PD.Boolean(false),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation']))
|
||||
}, { isExpanded: true })
|
||||
};
|
||||
type LoadCellPackModelParams = PD.Values<typeof LoadCellPackModelParams>
|
||||
|
||||
export const LoadCellPackModel = StateAction.build({
|
||||
display: { name: 'Load CellPack', description: 'Open or download a model' },
|
||||
params: LoadCellPackModelParams,
|
||||
from: PSO.Root
|
||||
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
|
||||
if (params.source.name === 'id' && params.source.params === 'hiv_lipids.bcif') {
|
||||
await loadMembrane(ctx, 'hiv_lipids', state, params);
|
||||
} else {
|
||||
await loadPackings(ctx, taskCtx, state, params);
|
||||
}
|
||||
}));
|
||||
90
src/extensions/cellpack/preset.ts
Normal file
90
src/extensions/cellpack/preset.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { CellPackColorThemeProvider } from './color';
|
||||
|
||||
export const CellpackPackingPresetParams = {
|
||||
traceOnly: PD.Boolean(true),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'])),
|
||||
};
|
||||
export type CellpackPackingPresetParams = PD.ValuesFor<typeof CellpackPackingPresetParams>
|
||||
|
||||
export const CellpackPackingPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-cellpack-packing',
|
||||
display: { name: 'CellPack Packing' },
|
||||
params: () => CellpackPackingPresetParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
|
||||
const reprProps = {
|
||||
ignoreHydrogens: true,
|
||||
traceOnly: params.traceOnly
|
||||
};
|
||||
const components = {
|
||||
polymer: await presetStaticComponent(plugin, structureCell, 'polymer')
|
||||
};
|
||||
|
||||
if (params.representation === 'gaussian-surface') {
|
||||
Object.assign(reprProps, {
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2, doubleSided: false
|
||||
});
|
||||
} else if (params.representation === 'spacefill' && params.traceOnly) {
|
||||
Object.assign(reprProps, { sizeFactor: 2 });
|
||||
}
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
|
||||
const color = CellPackColorThemeProvider.name;
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation<any>(update, components.polymer, { type: params.representation, typeParams: { ...typeParams, ...reprProps }, color }, { tag: 'polymer' })
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
export const CellpackMembranePresetParams = {
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'])),
|
||||
};
|
||||
export type CellpackMembranePresetParams = PD.ValuesFor<typeof CellpackMembranePresetParams>
|
||||
|
||||
export const CellpackMembranePreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-cellpack-membrane',
|
||||
display: { name: 'CellPack Membrane' },
|
||||
params: () => CellpackMembranePresetParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
|
||||
const reprProps = {
|
||||
ignoreHydrogens: true,
|
||||
};
|
||||
const components = {
|
||||
membrane: await presetStaticComponent(plugin, structureCell, 'all', { label: 'Membrane' })
|
||||
};
|
||||
|
||||
if (params.representation === 'gaussian-surface') {
|
||||
Object.assign(reprProps, {
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2, doubleSided: false
|
||||
});
|
||||
}
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
|
||||
const representations = {
|
||||
membrane: builder.buildRepresentation(update, components.membrane, { type: 'gaussian-surface', typeParams: { ...typeParams, ...reprProps }, color: 'uniform', colorParams: { value: ColorNames.lightgrey } }, { tag: 'all' })
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
34
src/extensions/cellpack/property.ts
Normal file
34
src/extensions/cellpack/property.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CustomStructureProperty } from '../../mol-model-props/common/custom-structure-property';
|
||||
import { Structure, CustomPropertyDescriptor } from '../../mol-model/structure';
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
export type CellPackInfoValue = {
|
||||
packingsCount: number
|
||||
packingIndex: number
|
||||
}
|
||||
|
||||
const CellPackInfoParams = {
|
||||
info: PD.Value<CellPackInfoValue>({ packingsCount: 1, packingIndex: 0 }, { isHidden: true })
|
||||
};
|
||||
type CellPackInfoParams = PD.Values<typeof CellPackInfoParams>
|
||||
|
||||
export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellPackInfoParams, CellPackInfoValue> = CustomStructureProperty.createProvider({
|
||||
label: 'CellPack Info',
|
||||
descriptor: CustomPropertyDescriptor({ name: 'cellpack-info' }),
|
||||
type: 'root',
|
||||
defaultParams: CellPackInfoParams,
|
||||
getParams: (data: Structure) => CellPackInfoParams,
|
||||
isApplicable: (data: Structure) => true,
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: CellPackInfoParams) => {
|
||||
return {
|
||||
value: { ...CellPackInfoParams.info.defaultValue, ...props.info }
|
||||
};
|
||||
}
|
||||
});
|
||||
97
src/extensions/cellpack/state.ts
Normal file
97
src/extensions/cellpack/state.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Task } from '../../mol-task';
|
||||
import { CellPack as _CellPack, Cell, CellPacking } from './data';
|
||||
import { createStructureFromCellPack } from './model';
|
||||
import { IngredientFiles } from './util';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
|
||||
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/';
|
||||
|
||||
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
|
||||
|
||||
export { ParseCellPack };
|
||||
type ParseCellPack = typeof ParseCellPack
|
||||
const ParseCellPack = PluginStateTransform.BuiltIn({
|
||||
name: 'parse-cellpack',
|
||||
display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
|
||||
from: PSO.Format.Json,
|
||||
to: CellPack
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse CellPack', async ctx => {
|
||||
const cell = a.data as Cell;
|
||||
|
||||
const packings: CellPacking[] = [];
|
||||
const { compartments, cytoplasme } = cell;
|
||||
if (compartments) {
|
||||
for (const name in compartments) {
|
||||
const { surface, interior } = compartments[name];
|
||||
if (surface) packings.push({ name, location: 'surface', ingredients: surface.ingredients });
|
||||
if (interior) packings.push({ name, location: 'interior', ingredients: interior.ingredients });
|
||||
}
|
||||
}
|
||||
if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
|
||||
|
||||
return new CellPack({ cell, packings });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export { StructureFromCellpack };
|
||||
type StructureFromCellpack = typeof ParseCellPack
|
||||
const StructureFromCellpack = PluginStateTransform.BuiltIn({
|
||||
name: 'structure-from-cellpack',
|
||||
display: { name: 'Structure from CellPack', description: 'Create Structure from CellPack Packing' },
|
||||
from: CellPack,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: a => {
|
||||
const options = a ? a.data.packings.map((d, i) => [i, d.name] as const) : [];
|
||||
return {
|
||||
packing: PD.Select(0, options),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
ingredientFiles: PD.FileList({ accept: '.cif,.bcif,.pdb' })
|
||||
};
|
||||
}
|
||||
})({
|
||||
apply({ a, params, cache }, plugin: PluginContext) {
|
||||
return Task.create('Structure from CellPack', async ctx => {
|
||||
const packing = a.data.packings[params.packing];
|
||||
const ingredientFiles: IngredientFiles = {};
|
||||
if (params.ingredientFiles !== null) {
|
||||
for (const file of params.ingredientFiles) {
|
||||
ingredientFiles[file.name] = file;
|
||||
}
|
||||
}
|
||||
const { structure, assets } = await createStructureFromCellPack(plugin, packing, params.baseUrl, ingredientFiles).runInContext(ctx);
|
||||
|
||||
await CellPackInfoProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, structure, {
|
||||
info: { packingsCount: a.data.packings.length, packingIndex: params.packing }
|
||||
});
|
||||
|
||||
(cache as any).assets = assets;
|
||||
return new PSO.Molecule.Structure(structure, { label: packing.name });
|
||||
});
|
||||
},
|
||||
dispose({ b, cache }) {
|
||||
const assets = (cache as any).assets as Asset.Wrapper[];
|
||||
if(assets) {
|
||||
for (const a of assets) a.dispose();
|
||||
}
|
||||
|
||||
if (b) {
|
||||
b.data.customPropertyDescriptors.dispose();
|
||||
for (const m of b.data.models) {
|
||||
m.customProperties.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
77
src/extensions/cellpack/util.ts
Normal file
77
src/extensions/cellpack/util.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CIF } from '../../mol-io/reader/cif';
|
||||
import { parsePDB } from '../../mol-io/reader/pdb/parser';
|
||||
import { AssetManager, Asset } from '../../mol-util/assets';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
|
||||
export async function parseCif(plugin: PluginContext, data: string | Uint8Array) {
|
||||
const comp = CIF.parse(data);
|
||||
const parsed = await plugin.runTask(comp);
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
export async function parsePDBfile(plugin: PluginContext, data: string, id: string) {
|
||||
const comp = parsePDB(data, id);
|
||||
const parsed = await plugin.runTask(comp);
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
async function downloadCif(plugin: PluginContext, url: string, isBinary: boolean, assetManager: AssetManager) {
|
||||
const type = isBinary ? 'binary' : 'string';
|
||||
const asset = await plugin.runTask(assetManager.resolve(Asset.getUrlAsset(assetManager, url), type));
|
||||
return { cif: await parseCif(plugin, asset.data), asset };
|
||||
}
|
||||
|
||||
async function downloadPDB(plugin: PluginContext, url: string, id: string, assetManager: AssetManager) {
|
||||
const asset = await assetManager.resolve(Asset.getUrlAsset(assetManager, url), 'string').run();
|
||||
return { pdb: await parsePDBfile(plugin, asset.data, id), asset };
|
||||
}
|
||||
|
||||
export async function getFromPdb(plugin: PluginContext, pdbId: string, assetManager: AssetManager) {
|
||||
const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId.toUpperCase()}.bcif`, true, assetManager);
|
||||
return { mmcif: cif.blocks[0], asset };
|
||||
}
|
||||
|
||||
export async function getFromOPM(plugin: PluginContext, pdbId: string, assetManager: AssetManager){
|
||||
const asset = await plugin.runTask(assetManager.resolve(Asset.getUrlAsset(assetManager, `https://opm-assets.storage.googleapis.com/pdb/${pdbId.toLowerCase()}.pdb`), 'string'));
|
||||
return { pdb: await parsePDBfile(plugin, asset.data, pdbId), asset };
|
||||
}
|
||||
|
||||
export async function getFromCellPackDB(plugin: PluginContext, id: string, baseUrl: string, assetManager: AssetManager) {
|
||||
if (id.toLowerCase().endsWith('.cif') || id.toLowerCase().endsWith('.bcif')) {
|
||||
const isBinary = id.toLowerCase().endsWith('.bcif');
|
||||
const { cif, asset } = await downloadCif(plugin, `${baseUrl}/other/${id}`, isBinary, assetManager);
|
||||
return { mmcif: cif.blocks[0], asset };
|
||||
} else {
|
||||
const name = id.endsWith('.pdb') ? id.substring(0, id.length - 4) : id;
|
||||
return await downloadPDB(plugin, `${baseUrl}/other/${name}.pdb`, name, assetManager);
|
||||
}
|
||||
}
|
||||
|
||||
export type IngredientFiles = { [name: string]: Asset.File }
|
||||
|
||||
export function getStructureMean(structure: Structure) {
|
||||
let xSum = 0, ySum = 0, zSum = 0;
|
||||
for (let i = 0, il = structure.units.length; i < il; ++i) {
|
||||
const unit = structure.units[i];
|
||||
const { elements } = unit;
|
||||
const { x, y, z } = unit.conformation;
|
||||
for (let j = 0, jl = elements.length; j < jl; ++j) {
|
||||
const eI = elements[j];
|
||||
xSum += x(eI);
|
||||
ySum += y(eI);
|
||||
zSum += z(eI);
|
||||
}
|
||||
}
|
||||
const { elementCount } = structure;
|
||||
return Vec3.create(xSum / elementCount, ySum / elementCount, zSum / elementCount);
|
||||
}
|
||||
9
src/extensions/pdbe/index.ts
Normal file
9
src/extensions/pdbe/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
export { PDBeStructureQualityReport } from './structure-quality-report/behavior';
|
||||
export { PDBePreferredAssembly } from './preferred-assembly';
|
||||
export { PDBeStructRefDomain } from './struct-ref-domain';
|
||||
@@ -15,7 +15,7 @@ export namespace PDBePreferredAssembly {
|
||||
export type Property = string
|
||||
|
||||
export function getFirstFromModel(model: Model): Property {
|
||||
const symmetry = ModelSymmetry.Provider.get(model)
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
return symmetry?.assemblies.length ? symmetry.assemblies[0].id : '';
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Column, Table } from '../../mol-data/db';
|
||||
import { toTable } from '../../mol-io/reader/cif/schema';
|
||||
import { CifWriter } from '../../mol-io/writer/cif';
|
||||
import { Model, CustomPropertyDescriptor } from '../../mol-model/structure';
|
||||
import { PropertyWrapper } from '../common/wrapper';
|
||||
import { PropertyWrapper } from '../../mol-model-props/common/wrapper';
|
||||
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
|
||||
export namespace PDBeStructRefDomain {
|
||||
@@ -111,7 +111,7 @@ function fromPDBeJson(modelData: Model, data: any): PDBeStructRefDomain.Property
|
||||
beg_pdbx_PDB_ins_code: map.start.author_insertion_code,
|
||||
end_label_seq_id: map.end.residue_number,
|
||||
end_pdbx_PDB_ins_code: map.end.author_insertion_code,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/extensions/pdbe/structure-quality-report/behavior.ts
Normal file
72
src/extensions/pdbe/structure-quality-report/behavior.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { StructureQualityReport, StructureQualityReportProvider } from './prop';
|
||||
import { StructureQualityReportColorThemeProvider } from './color';
|
||||
import { Loci } from '../../../mol-model/loci';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
|
||||
|
||||
export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
|
||||
name: 'pdbe-structure-quality-report-prop',
|
||||
category: 'custom-props',
|
||||
display: {
|
||||
name: 'Structure Quality Report',
|
||||
description: 'Data from wwPDB Validation Report, obtained via PDBe.'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
|
||||
|
||||
private provider = StructureQualityReportProvider
|
||||
|
||||
private labelPDBeValidation = {
|
||||
label: (loci: Loci): string | undefined => {
|
||||
if (!this.params.showTooltip) return void 0;
|
||||
|
||||
switch (loci.kind) {
|
||||
case 'element-loci':
|
||||
if (loci.elements.length === 0) return void 0;
|
||||
const e = loci.elements[0];
|
||||
const u = e.unit;
|
||||
if (!u.model.customProperties.hasReference(StructureQualityReportProvider.descriptor)) return void 0;
|
||||
|
||||
const se = StructureElement.Location.create(loci.structure, u, u.elements[OrderedSet.getAt(e.indices, 0)]);
|
||||
const issues = StructureQualityReport.getIssues(se);
|
||||
if (issues.length === 0) return 'Validation: No Issues';
|
||||
return `Validation: ${issues.join(', ')}`;
|
||||
|
||||
default: return void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register(): void {
|
||||
this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
|
||||
this.ctx.managers.lociLabels.addProvider(this.labelPDBeValidation);
|
||||
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(StructureQualityReportColorThemeProvider);
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean, showTooltip: boolean }) {
|
||||
let updated = this.params.autoAttach !== p.autoAttach;
|
||||
this.params.autoAttach = p.autoAttach;
|
||||
this.params.showTooltip = p.showTooltip;
|
||||
this.ctx.customModelProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
|
||||
return updated;
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.ctx.customModelProperties.unregister(StructureQualityReportProvider.descriptor.name);
|
||||
this.ctx.managers.lociLabels.removeProvider(this.labelPDBeValidation);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(StructureQualityReportColorThemeProvider);
|
||||
}
|
||||
},
|
||||
params: () => ({
|
||||
autoAttach: PD.Boolean(false),
|
||||
showTooltip: PD.Boolean(true)
|
||||
})
|
||||
});
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { StructureQualityReport, StructureQualityReportProvider } from '../../../mol-model-props/pdbe/structure-quality-report';
|
||||
import { StructureQualityReport, StructureQualityReportProvider } from './prop';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
@@ -12,7 +12,7 @@ import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { TableLegend } from '../../../mol-util/legend';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
|
||||
const ValidationColors = [
|
||||
Color.fromRgb(170, 170, 170), // not applicable
|
||||
@@ -20,7 +20,7 @@ const ValidationColors = [
|
||||
Color.fromRgb(255, 255, 0), // 1
|
||||
Color.fromRgb(255, 128, 0), // 2
|
||||
Color.fromRgb(255, 0, 0), // 3 or more
|
||||
]
|
||||
];
|
||||
|
||||
const ValidationColorTable: [string, Color][] = [
|
||||
['No Issues', ValidationColors[1]],
|
||||
@@ -28,7 +28,7 @@ const ValidationColorTable: [string, Color][] = [
|
||||
['Two Issues', ValidationColors[3]],
|
||||
['Three Or More Issues', ValidationColors[4]],
|
||||
['Not Applicable', ValidationColors[9]]
|
||||
]
|
||||
];
|
||||
|
||||
export const StructureQualityReportColorThemeParams = {
|
||||
type: PD.MappedStatic('issue-count', {
|
||||
@@ -42,7 +42,7 @@ export const StructureQualityReportColorThemeParams = {
|
||||
type Params = typeof StructureQualityReportColorThemeParams
|
||||
|
||||
export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: PD.Values<Params>): ColorTheme<Params> {
|
||||
let color: LocationColor
|
||||
let color: LocationColor;
|
||||
|
||||
if (ctx.structure && !ctx.structure.isEmpty && ctx.structure.models[0].customProperties.has(StructureQualityReportProvider.descriptor)) {
|
||||
const getIssues = StructureQualityReport.getIssues;
|
||||
@@ -53,7 +53,7 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
|
||||
return ValidationColors[Math.min(3, getIssues(location).length) + 1];
|
||||
}
|
||||
return ValidationColors[0];
|
||||
}
|
||||
};
|
||||
} else {
|
||||
const issue = props.type.params.kind;
|
||||
color = (location: Location) => {
|
||||
@@ -61,7 +61,7 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
|
||||
return ValidationColors[4];
|
||||
}
|
||||
return ValidationColors[0];
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
color = () => ValidationColors[0];
|
||||
@@ -72,13 +72,15 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
|
||||
granularity: 'group',
|
||||
color: color,
|
||||
props: props,
|
||||
description: 'Assigns residue colors according to the number of issues or a specific issue in the PDBe Validation Report.',
|
||||
description: 'Assigns residue colors according to the number of quality issues or a specific quality issue. Data from wwPDB Validation Report, obtained via PDBe.',
|
||||
legend: TableLegend(ValidationColorTable)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Params> = {
|
||||
label: 'PDBe Structure Quality Report',
|
||||
export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Params, 'pdbe-structure-quality-report'> = {
|
||||
name: 'pdbe-structure-quality-report',
|
||||
label: 'Structure Quality Report',
|
||||
category: ColorTheme.Category.Validation,
|
||||
factory: StructureQualityReportColorTheme,
|
||||
getParams: ctx => {
|
||||
const issueTypes = StructureQualityReport.getIssueTypes(ctx.structure);
|
||||
@@ -101,7 +103,8 @@ export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Param
|
||||
},
|
||||
defaultValues: PD.getDefaultValues(StructureQualityReportColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => StructureQualityReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
return data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(StructureQualityReportProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -5,24 +5,25 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { toTable } from '../../mol-io/reader/cif/schema';
|
||||
import { mmCIF_residueId_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
import { CifWriter } from '../../mol-io/writer/cif';
|
||||
import { Model, CustomPropertyDescriptor, ResidueIndex, Unit, IndexedCustomProperty } from '../../mol-model/structure';
|
||||
import { residueIdFields } from '../../mol-model/structure/export/categories/atom_site';
|
||||
import { StructureElement, CifExportContext, Structure } from '../../mol-model/structure/structure';
|
||||
import { CustomPropSymbol } from '../../mol-script/language/symbol';
|
||||
import Type from '../../mol-script/language/type';
|
||||
import { QuerySymbolRuntime } from '../../mol-script/runtime/query/compiler';
|
||||
import { PropertyWrapper } from '../common/wrapper';
|
||||
import { CustomModelProperty } from '../common/custom-model-property';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition'
|
||||
import { CustomProperty } from '../common/custom-property';
|
||||
import { arraySetAdd } from '../../mol-util/array';
|
||||
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { Column, Table } from '../../../mol-data/db';
|
||||
import { toTable } from '../../../mol-io/reader/cif/schema';
|
||||
import { mmCIF_residueId_schema } from '../../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
import { CifWriter } from '../../../mol-io/writer/cif';
|
||||
import { Model, CustomPropertyDescriptor, ResidueIndex, Unit, IndexedCustomProperty } from '../../../mol-model/structure';
|
||||
import { residueIdFields } from '../../../mol-model/structure/export/categories/atom_site';
|
||||
import { StructureElement, CifExportContext, Structure } from '../../../mol-model/structure/structure';
|
||||
import { CustomPropSymbol } from '../../../mol-script/language/symbol';
|
||||
import Type from '../../../mol-script/language/type';
|
||||
import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { arraySetAdd } from '../../../mol-util/array';
|
||||
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
import { PropertyWrapper } from '../../../mol-model-props/common/wrapper';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
|
||||
import { Asset } from '../../../mol-util/assets';
|
||||
|
||||
export { StructureQualityReport }
|
||||
export { StructureQualityReport };
|
||||
|
||||
type StructureQualityReport = PropertyWrapper<{
|
||||
issues: IndexedCustomProperty.Residue<string[]>,
|
||||
@@ -30,18 +31,13 @@ type StructureQualityReport = PropertyWrapper<{
|
||||
}| undefined>
|
||||
|
||||
namespace StructureQualityReport {
|
||||
export const DefaultServerUrl = 'https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/'
|
||||
export const DefaultServerUrl = 'https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/';
|
||||
export function getEntryUrl(pdbId: string, serverUrl: string) {
|
||||
return `${serverUrl}/${pdbId.toLowerCase()}`
|
||||
return `${serverUrl}/${pdbId.toLowerCase()}`;
|
||||
}
|
||||
|
||||
export function isApplicable(model?: Model): boolean {
|
||||
return (
|
||||
!!model &&
|
||||
MmcifFormat.is(model.sourceData) &&
|
||||
(model.sourceData.data.db.database_2.database_id.isDefined ||
|
||||
model.entryId.length === 4)
|
||||
)
|
||||
return !!model && Model.isFromPdbArchive(model);
|
||||
}
|
||||
|
||||
export const Schema = {
|
||||
@@ -64,27 +60,28 @@ namespace StructureQualityReport {
|
||||
export function fromJson(model: Model, data: any) {
|
||||
const info = PropertyWrapper.createInfo();
|
||||
const issueMap = createIssueMapFromJson(model, data);
|
||||
return { info, data: issueMap }
|
||||
return { info, data: issueMap };
|
||||
}
|
||||
|
||||
export async function fromServer(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): Promise<StructureQualityReport> {
|
||||
const url = getEntryUrl(model.entryId, props.serverUrl)
|
||||
const json = await ctx.fetch({ url, type: 'json' }).runInContext(ctx.runtime)
|
||||
const data = json[model.entryId.toLowerCase()];
|
||||
export async function fromServer(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): Promise<CustomProperty.Data<StructureQualityReport>> {
|
||||
const url = Asset.getUrlAsset(ctx.assetManager, getEntryUrl(model.entryId, props.serverUrl));
|
||||
const json = await ctx.assetManager.resolve(url, 'json').runInContext(ctx.runtime);
|
||||
const data = json.data[model.entryId.toLowerCase()];
|
||||
if (!data) throw new Error('missing data');
|
||||
return fromJson(model, data)
|
||||
return { value: fromJson(model, data), assets: [json] };
|
||||
}
|
||||
|
||||
export function fromCif(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): StructureQualityReport | undefined {
|
||||
let info = PropertyWrapper.tryGetInfoFromCif('pdbe_structure_quality_report', model);
|
||||
if (!info) return
|
||||
if (!info) return;
|
||||
const data = getCifData(model);
|
||||
const issueMap = createIssueMapFromCif(model, data.residues, data.groups);
|
||||
return { info, data: issueMap }
|
||||
return { info, data: issueMap };
|
||||
}
|
||||
|
||||
export async function fromCifOrServer(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): Promise<StructureQualityReport> {
|
||||
return fromCif(ctx, model, props) || fromServer(ctx, model, props)
|
||||
export async function fromCifOrServer(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): Promise<CustomProperty.Data<StructureQualityReport>> {
|
||||
const cif = fromCif(ctx, model, props);
|
||||
return cif ? { value: cif } : fromServer(ctx, model, props);
|
||||
}
|
||||
|
||||
const _emptyArray: string[] = [];
|
||||
@@ -108,18 +105,18 @@ namespace StructureQualityReport {
|
||||
return {
|
||||
residues: toTable(Schema.pdbe_structure_quality_report_issues, model.sourceData.data.frame.categories.pdbe_structure_quality_report_issues),
|
||||
groups: toTable(Schema.pdbe_structure_quality_report_issue_types, model.sourceData.data.frame.categories.pdbe_structure_quality_report_issue_types),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const StructureQualityReportParams = {
|
||||
serverUrl: PD.Text(StructureQualityReport.DefaultServerUrl, { description: 'JSON API Server URL' })
|
||||
}
|
||||
};
|
||||
export type StructureQualityReportParams = typeof StructureQualityReportParams
|
||||
export type StructureQualityReportProps = PD.Values<StructureQualityReportParams>
|
||||
|
||||
export const StructureQualityReportProvider: CustomModelProperty.Provider<StructureQualityReportParams, StructureQualityReport> = CustomModelProperty.createProvider({
|
||||
label: 'PDBe Structure Quality Report',
|
||||
label: 'Structure Quality Report',
|
||||
descriptor: CustomPropertyDescriptor<ReportExportContext, any>({
|
||||
name: 'pdbe_structure_quality_report',
|
||||
cifExport: {
|
||||
@@ -135,7 +132,7 @@ export const StructureQualityReportProvider: CustomModelProperty.Provider<Struct
|
||||
return {
|
||||
fields: _structure_quality_report_issues_fields,
|
||||
source: ctx.models.map(data => ({ data, rowCount: data.elements.length }))
|
||||
}
|
||||
};
|
||||
}
|
||||
}, {
|
||||
name: 'pdbe_structure_quality_report_issue_types',
|
||||
@@ -155,10 +152,10 @@ export const StructureQualityReportProvider: CustomModelProperty.Provider<Struct
|
||||
getParams: (data: Model) => StructureQualityReportParams,
|
||||
isApplicable: (data: Model) => StructureQualityReport.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<StructureQualityReportProps>) => {
|
||||
const p = { ...PD.getDefaultValues(StructureQualityReportParams), ...props }
|
||||
return await StructureQualityReport.fromCifOrServer(ctx, data, p)
|
||||
const p = { ...PD.getDefaultValues(StructureQualityReportParams), ...props };
|
||||
return await StructureQualityReport.fromCifOrServer(ctx, data, p);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const _structure_quality_report_issues_fields = CifWriter.fields<number, ReportExportContext['models'][0]>()
|
||||
.index('id')
|
||||
@@ -210,7 +207,7 @@ function createExportContext(ctx: CifExportContext): ReportExportContext {
|
||||
info,
|
||||
models,
|
||||
issueTypes: Table.ofArrays(StructureQualityReport.Schema.pdbe_structure_quality_report_issue_types, { group_id, issue_type })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createIssueMapFromJson(modelData: Model, data: any): StructureQualityReport['data'] | undefined {
|
||||
@@ -266,7 +263,7 @@ function createIssueMapFromCif(modelData: Model,
|
||||
for (const t of issues) {
|
||||
arraySetAdd(issueTypes, t);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return {
|
||||
issues: IndexedCustomProperty.fromResidueMap(ret),
|
||||
4
src/extensions/rcsb/README.md
Normal file
4
src/extensions/rcsb/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
### Code generation
|
||||
**GraphQL schemas**
|
||||
|
||||
./node_modules/.bin/graphql-codegen -c ./src/extensions/rcsb/graphql/codegen.yml
|
||||
196
src/extensions/rcsb/assembly-symmetry/behavior.ts
Normal file
196
src/extensions/rcsb/assembly-symmetry/behavior.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry, AssemblySymmetryDataProvider } from './prop';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
|
||||
import { AssemblySymmetryParams, AssemblySymmetryRepresentation } from './representation';
|
||||
import { AssemblySymmetryClusterColorThemeProvider } from './color';
|
||||
import { PluginStateTransform, PluginStateObject } from '../../../mol-plugin-state/objects';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateTransformer, StateAction, StateObject, StateTransform, StateObjectRef } from '../../../mol-state';
|
||||
import { GenericRepresentationRef } from '../../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
import { AssemblySymmetryControls } from './ui';
|
||||
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
|
||||
const Tag = AssemblySymmetry.Tag;
|
||||
|
||||
export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean }>({
|
||||
name: 'rcsb-assembly-symmetry-prop',
|
||||
category: 'custom-props',
|
||||
display: {
|
||||
name: 'Assembly Symmetry',
|
||||
description: 'Assembly Symmetry data calculated with BioJava, obtained via RCSB PDB.'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> {
|
||||
private provider = AssemblySymmetryProvider
|
||||
|
||||
register(): void {
|
||||
this.ctx.state.data.actions.add(InitAssemblySymmetry3D);
|
||||
this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(AssemblySymmetryClusterColorThemeProvider);
|
||||
|
||||
this.ctx.genericRepresentationControls.set(Tag.Representation, selection => {
|
||||
const refs: GenericRepresentationRef[] = [];
|
||||
selection.structures.forEach(structure => {
|
||||
const symmRepr = structure.genericRepresentations?.filter(r => r.cell.transform.transformer.id === AssemblySymmetry3D.id)[0];
|
||||
if (symmRepr) refs.push(symmRepr);
|
||||
});
|
||||
return [refs, 'Symmetries'];
|
||||
});
|
||||
this.ctx.customStructureControls.set(Tag.Representation, AssemblySymmetryControls as any);
|
||||
this.ctx.builders.structure.representation.registerPreset(AssemblySymmetryPreset);
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean }) {
|
||||
let updated = this.params.autoAttach !== p.autoAttach;
|
||||
this.params.autoAttach = p.autoAttach;
|
||||
this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
|
||||
return updated;
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.ctx.state.data.actions.remove(InitAssemblySymmetry3D);
|
||||
this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(AssemblySymmetryClusterColorThemeProvider);
|
||||
|
||||
this.ctx.genericRepresentationControls.delete(Tag.Representation);
|
||||
this.ctx.customStructureControls.delete(Tag.Representation);
|
||||
this.ctx.builders.structure.representation.unregisterPreset(AssemblySymmetryPreset);
|
||||
}
|
||||
},
|
||||
params: () => ({
|
||||
autoAttach: PD.Boolean(false),
|
||||
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl)
|
||||
})
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
export const InitAssemblySymmetry3D = StateAction.build({
|
||||
display: {
|
||||
name: 'Assembly Symmetry',
|
||||
description: 'Initialize Assembly Symmetry axes and cage. Data calculated with BioJava, obtained via RCSB PDB.'
|
||||
},
|
||||
from: PluginStateObject.Molecule.Structure,
|
||||
isApplicable: (a) => AssemblySymmetry.isApplicable(a.data)
|
||||
})(({ a, ref, state }, plugin: PluginContext) => Task.create('Init Assembly Symmetry', async ctx => {
|
||||
try {
|
||||
const propCtx = { runtime: ctx, assetManager: plugin.managers.asset };
|
||||
await AssemblySymmetryDataProvider.attach(propCtx, a.data);
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(a.data).value;
|
||||
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
|
||||
await AssemblySymmetryProvider.attach(propCtx, a.data, { symmetryIndex });
|
||||
} catch(e) {
|
||||
plugin.log.error(`Assembly Symmetry: ${e}`);
|
||||
return;
|
||||
}
|
||||
const tree = state.build().to(ref)
|
||||
.applyOrUpdateTagged(AssemblySymmetry.Tag.Representation, AssemblySymmetry3D);
|
||||
await state.updateTree(tree).runInContext(ctx);
|
||||
}));
|
||||
|
||||
export { AssemblySymmetry3D };
|
||||
|
||||
type AssemblySymmetry3D = typeof AssemblySymmetry3D
|
||||
const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
|
||||
name: Tag.Representation,
|
||||
display: {
|
||||
name: 'Assembly Symmetry',
|
||||
description: 'Assembly Symmetry axes and cage. Data calculated with BioJava, obtained via RCSB PDB.'
|
||||
},
|
||||
from: PluginStateObject.Molecule.Structure,
|
||||
to: PluginStateObject.Shape.Representation3D,
|
||||
params: (a) => {
|
||||
return {
|
||||
...AssemblySymmetryParams,
|
||||
};
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ oldParams, newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Assembly Symmetry', async ctx => {
|
||||
await AssemblySymmetryProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(a.data).value;
|
||||
if (!assemblySymmetry || assemblySymmetry.symbol === 'C1') {
|
||||
return StateObject.Null;
|
||||
}
|
||||
const repr = AssemblySymmetryRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => AssemblySymmetryParams);
|
||||
await repr.createOrUpdate(params, a.data).runInContext(ctx);
|
||||
const { type, kind, symbol } = assemblySymmetry;
|
||||
return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: kind, description: `${type} (${symbol})` });
|
||||
});
|
||||
},
|
||||
update({ a, b, newParams }, plugin: PluginContext) {
|
||||
return Task.create('Assembly Symmetry', async ctx => {
|
||||
await AssemblySymmetryProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(a.data).value;
|
||||
if (!assemblySymmetry || assemblySymmetry.symbol === 'C1') {
|
||||
// this should NOT be StateTransformer.UpdateResult.Null
|
||||
// because that keeps the old object
|
||||
return StateTransformer.UpdateResult.Recreate;
|
||||
}
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
const { type, kind, symbol } = assemblySymmetry;
|
||||
b.label = kind;
|
||||
b.description = `${type} (${symbol})`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
isApplicable(a) {
|
||||
return AssemblySymmetry.isApplicable(a.data);
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
export const AssemblySymmetryPresetParams = {
|
||||
...StructureRepresentationPresetProvider.CommonParams,
|
||||
};
|
||||
|
||||
export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-rcsb-assembly-symmetry',
|
||||
display: {
|
||||
name: 'Assembly Symmetry', group: 'Annotation',
|
||||
description: 'Shows Assembly Symmetry axes and cage; colors structure according to assembly symmetry cluster membership. Data calculated with BioJava, obtained via RCSB PDB.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return AssemblySymmetry.isApplicable(a.data);
|
||||
},
|
||||
params: () => AssemblySymmetryPresetParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
if (!AssemblySymmetryDataProvider.get(structure).value) {
|
||||
await plugin.runTask(Task.create('Assembly Symmetry', async runtime => {
|
||||
const propCtx = { runtime, assetManager: plugin.managers.asset };
|
||||
await AssemblySymmetryDataProvider.attach(propCtx, structure);
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value;
|
||||
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
|
||||
await AssemblySymmetryProvider.attach(propCtx, structure, { symmetryIndex });
|
||||
}));
|
||||
}
|
||||
|
||||
const assemblySymmetry = await tryCreateAssemblySymmetry(plugin, structureCell);
|
||||
const globalThemeName = assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
|
||||
const preset = await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName }, plugin);
|
||||
|
||||
return { components: preset.components, representations: { ...preset.representations, assemblySymmetry } };
|
||||
}
|
||||
});
|
||||
|
||||
export function tryCreateAssemblySymmetry(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, params?: StateTransformer.Params<AssemblySymmetry3D>, initialState?: Partial<StateTransform.State>) {
|
||||
const state = plugin.state.data;
|
||||
const assemblySymmetry = state.build().to(structure)
|
||||
.applyOrUpdateTagged(AssemblySymmetry.Tag.Representation, AssemblySymmetry3D, params, { state: initialState });
|
||||
return assemblySymmetry.commit({ revertOnError: true });
|
||||
}
|
||||
@@ -6,76 +6,76 @@
|
||||
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry, getSymmetrySelectParam } from '../assembly-symmetry';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry } from './prop';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { Unit, StructureElement, StructureProperties } from '../../../mol-model/structure';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
|
||||
import { getPalette, getPaletteParams } from '../../../mol-util/color/palette';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC)
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
|
||||
function getAsymId(unit: Unit): StructureElement.Property<string> {
|
||||
switch (unit.kind) {
|
||||
case Unit.Kind.Atomic:
|
||||
return StructureProperties.chain.label_asym_id
|
||||
return StructureProperties.chain.label_asym_id;
|
||||
case Unit.Kind.Spheres:
|
||||
case Unit.Kind.Gaussians:
|
||||
return StructureProperties.coarse.asym_id
|
||||
return StructureProperties.coarse.asym_id;
|
||||
}
|
||||
}
|
||||
|
||||
function clusterMemberKey(asymId: string, operList: string[]) {
|
||||
return `${asymId}-${operList.join('|')}`
|
||||
return `${asymId}-${operList.join('|')}`;
|
||||
}
|
||||
|
||||
export const AssemblySymmetryClusterColorThemeParams = {
|
||||
...getPaletteParams({ scaleList: 'red-yellow-blue' }),
|
||||
symmetryIndex: getSymmetrySelectParam(),
|
||||
}
|
||||
...getPaletteParams({ colorList: 'red-yellow-blue' }),
|
||||
};
|
||||
export type AssemblySymmetryClusterColorThemeParams = typeof AssemblySymmetryClusterColorThemeParams
|
||||
export function getAssemblySymmetryClusterColorThemeParams(ctx: ThemeDataContext) {
|
||||
const params = PD.clone(AssemblySymmetryClusterColorThemeParams)
|
||||
params.symmetryIndex = getSymmetrySelectParam(ctx.structure)
|
||||
return params
|
||||
const params = PD.clone(AssemblySymmetryClusterColorThemeParams);
|
||||
return params;
|
||||
}
|
||||
|
||||
export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props: PD.Values<AssemblySymmetryClusterColorThemeParams>): ColorTheme<AssemblySymmetryClusterColorThemeParams> {
|
||||
let color: LocationColor = () => DefaultColor
|
||||
let legend: ScaleLegend | TableLegend | undefined
|
||||
let color: LocationColor = () => DefaultColor;
|
||||
let legend: ScaleLegend | TableLegend | undefined;
|
||||
|
||||
const { symmetryIndex } = props
|
||||
const assemblySymmetry = ctx.structure && AssemblySymmetryProvider.get(ctx.structure)
|
||||
const contextHash = assemblySymmetry?.version
|
||||
const assemblySymmetry = ctx.structure && AssemblySymmetryProvider.get(ctx.structure);
|
||||
const contextHash = assemblySymmetry?.version;
|
||||
|
||||
const clusters = assemblySymmetry?.value?.[symmetryIndex]?.clusters
|
||||
const clusters = assemblySymmetry?.value?.clusters;
|
||||
|
||||
if (clusters?.length && ctx.structure) {
|
||||
const clusterByMember = new Map<string, number>()
|
||||
const clusterByMember = new Map<string, number>();
|
||||
for (let i = 0, il = clusters.length; i < il; ++i) {
|
||||
const { members } = clusters[i]!
|
||||
const { members } = clusters[i]!;
|
||||
for (let j = 0, jl = members.length; j < jl; ++j) {
|
||||
const asymId = members[j]!.asym_id
|
||||
const operList = [...members[j]!.pdbx_struct_oper_list_ids || []] as string[]
|
||||
if (operList.length === 0) operList.push('1') // TODO hack assuming '1' is the id of the identity operator
|
||||
clusterByMember.set(clusterMemberKey(asymId, operList), i)
|
||||
const asymId = members[j]!.asym_id;
|
||||
const operList = [...members[j]!.pdbx_struct_oper_list_ids || []] as string[];
|
||||
clusterByMember.set(clusterMemberKey(asymId, operList), i);
|
||||
if (operList.length === 0) {
|
||||
operList.push('1'); // TODO hack assuming '1' is the id of the identity operator
|
||||
clusterByMember.set(clusterMemberKey(asymId, operList), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
const palette = getPalette(clusters.length, props);
|
||||
legend = palette.legend;
|
||||
|
||||
const palette = getPalette(clusters.length, props)
|
||||
legend = palette.legend
|
||||
|
||||
const _emptyList: any[] = [];
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
const { assembly } = location.unit.conformation.operator
|
||||
const asymId = getAsymId(location.unit)(location)
|
||||
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly.operList))
|
||||
return cluster !== undefined ? palette.color(cluster) : DefaultColor
|
||||
const { assembly } = location.unit.conformation.operator;
|
||||
const asymId = getAsymId(location.unit)(location);
|
||||
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly?.operList || _emptyList));
|
||||
return cluster !== undefined ? palette.color(cluster) : DefaultColor;
|
||||
}
|
||||
return DefaultColor
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -84,18 +84,21 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
|
||||
color,
|
||||
props,
|
||||
contextHash,
|
||||
description: 'Assigns chain colors according to assembly symmetry cluster membership.',
|
||||
description: 'Assigns chain colors according to assembly symmetry cluster membership calculated with BioJava and obtained via RCSB PDB.',
|
||||
legend
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<AssemblySymmetryClusterColorThemeParams> = {
|
||||
label: 'RCSB Assembly Symmetry Cluster',
|
||||
export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<AssemblySymmetryClusterColorThemeParams, AssemblySymmetry.Tag.Cluster> = {
|
||||
name: AssemblySymmetry.Tag.Cluster,
|
||||
label: 'Assembly Symmetry Cluster',
|
||||
category: ColorTheme.Category.Symmetry,
|
||||
factory: AssemblySymmetryClusterColorTheme,
|
||||
getParams: getAssemblySymmetryClusterColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(AssemblySymmetryClusterColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => AssemblySymmetry.isApplicable(ctx.structure),
|
||||
ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
return data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure) : Promise.resolve()
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(AssemblySymmetryProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
};
|
||||
201
src/extensions/rcsb/assembly-symmetry/prop.ts
Normal file
201
src/extensions/rcsb/assembly-symmetry/prop.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { AssemblySymmetryQuery, AssemblySymmetryQueryVariables } from '../graphql/types';
|
||||
import query from '../graphql/symmetry.gql';
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { CustomPropertyDescriptor, Structure, Model, StructureSelection, QueryContext } from '../../../mol-model/structure';
|
||||
import { Database as _Database, Column } from '../../../mol-data/db';
|
||||
import { GraphQLClient } from '../../../mol-util/graphql-client';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { NonNullableArray } from '../../../mol-util/type-helpers';
|
||||
import { CustomStructureProperty } from '../../../mol-model-props/common/custom-structure-property';
|
||||
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
import { ReadonlyVec3 } from '../../../mol-math/linear-algebra/3d/vec3';
|
||||
import { SetUtils } from '../../../mol-util/set';
|
||||
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
|
||||
import { compile } from '../../../mol-script/runtime/query/compiler';
|
||||
|
||||
const BiologicalAssemblyNames = new Set([
|
||||
'author_and_software_defined_assembly',
|
||||
'author_defined_assembly',
|
||||
'complete icosahedral assembly',
|
||||
'complete point assembly',
|
||||
'representative helical assembly',
|
||||
'software_defined_assembly'
|
||||
]);
|
||||
|
||||
export function isBiologicalAssembly(structure: Structure): boolean {
|
||||
if (!MmcifFormat.is(structure.models[0].sourceData)) return false;
|
||||
const mmcif = structure.models[0].sourceData.data.db;
|
||||
if (!mmcif.pdbx_struct_assembly.details.isDefined) return false;
|
||||
const id = structure.units[0].conformation.operator.assembly?.id || '';
|
||||
if (id === '' || id === 'deposited') return true;
|
||||
const indices = Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id);
|
||||
if (indices.length !== 1) return false;
|
||||
const details = mmcif.pdbx_struct_assembly.details.value(indices[0]);
|
||||
return BiologicalAssemblyNames.has(details);
|
||||
}
|
||||
|
||||
export namespace AssemblySymmetry {
|
||||
export enum Tag {
|
||||
Cluster = 'rcsb-assembly-symmetry-cluster',
|
||||
Representation = 'rcsb-assembly-symmetry-3d'
|
||||
}
|
||||
|
||||
export const DefaultServerUrl = 'https://data.rcsb.org/graphql';
|
||||
|
||||
export function isApplicable(structure?: Structure): boolean {
|
||||
return (
|
||||
!!structure && structure.models.length === 1 &&
|
||||
Model.isFromPdbArchive(structure.models[0]) &&
|
||||
isBiologicalAssembly(structure)
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
|
||||
if (!isApplicable(structure)) return { value: [] };
|
||||
|
||||
const client = new GraphQLClient(props.serverUrl, ctx.assetManager);
|
||||
const variables: AssemblySymmetryQueryVariables = {
|
||||
assembly_id: structure.units[0].conformation.operator.assembly?.id || 'deposited',
|
||||
entry_id: structure.units[0].model.entryId
|
||||
};
|
||||
const result = await client.request(ctx.runtime, query, variables);
|
||||
let value: AssemblySymmetryDataValue = [];
|
||||
|
||||
if (!result.data.assembly?.rcsb_struct_symmetry) {
|
||||
console.error('expected `rcsb_struct_symmetry` field');
|
||||
} else {
|
||||
value = result.data.assembly.rcsb_struct_symmetry as AssemblySymmetryDataValue;
|
||||
}
|
||||
return { value, assets: [result] };
|
||||
}
|
||||
|
||||
/** Returns the index of the first non C1 symmetry or -1 */
|
||||
export function firstNonC1(assemblySymmetryData: AssemblySymmetryDataValue) {
|
||||
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
|
||||
if (assemblySymmetryData[i].symbol !== 'C1') return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
export type RotationAxes = ReadonlyArray<{ order: number, start: ReadonlyVec3, end: ReadonlyVec3 }>
|
||||
export function isRotationAxes(x: AssemblySymmetryValue['rotation_axes']): x is RotationAxes {
|
||||
return !!x && x.length > 0;
|
||||
}
|
||||
|
||||
export function getAsymIds(assemblySymmetry: AssemblySymmetryValue) {
|
||||
const asymIds = new Set<string>();
|
||||
for (const c of assemblySymmetry.clusters) {
|
||||
if (!c?.members) continue;
|
||||
for (const m of c.members) {
|
||||
if (m?.asym_id) asymIds.add(m.asym_id);
|
||||
}
|
||||
}
|
||||
return SetUtils.toArray(asymIds);
|
||||
}
|
||||
|
||||
function getAsymIdsStructure(structure: Structure, asymIds: string[]) {
|
||||
const query = MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.set.has([MS.set(...asymIds), MS.ammp('label_asym_id')])
|
||||
})
|
||||
]);
|
||||
const compiled = compile<StructureSelection>(query);
|
||||
const result = compiled(new QueryContext(structure));
|
||||
return StructureSelection.unionStructure(result);
|
||||
}
|
||||
|
||||
/** Returns structure limited to all cluster member chains */
|
||||
export function getStructure(structure: Structure, assemblySymmetry: AssemblySymmetryValue) {
|
||||
const asymIds = AssemblySymmetry.getAsymIds(assemblySymmetry);
|
||||
return asymIds.length > 0 ? getAsymIdsStructure(structure, asymIds) : structure;
|
||||
}
|
||||
}
|
||||
|
||||
export function getSymmetrySelectParam(structure?: Structure) {
|
||||
const param = PD.Select<number>(0, [[0, 'First Symmetry']]);
|
||||
if (structure) {
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value;
|
||||
if (assemblySymmetryData) {
|
||||
const options: [number, string][] = [];
|
||||
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
|
||||
const { symbol, kind } = assemblySymmetryData[i];
|
||||
if (symbol !== 'C1') {
|
||||
options.push([ i, `${i + 1}: ${symbol} ${kind}` ]);
|
||||
}
|
||||
}
|
||||
if (options.length) {
|
||||
param.options = options;
|
||||
param.defaultValue = options[0][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
return param;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export const AssemblySymmetryDataParams = {
|
||||
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl, { description: 'GraphQL endpoint URL' })
|
||||
};
|
||||
export type AssemblySymmetryDataParams = typeof AssemblySymmetryDataParams
|
||||
export type AssemblySymmetryDataProps = PD.Values<AssemblySymmetryDataParams>
|
||||
|
||||
export type AssemblySymmetryDataValue = NonNullableArray<NonNullable<NonNullable<AssemblySymmetryQuery['assembly']>['rcsb_struct_symmetry']>>
|
||||
|
||||
export const AssemblySymmetryDataProvider: CustomStructureProperty.Provider<AssemblySymmetryDataParams, AssemblySymmetryDataValue> = CustomStructureProperty.createProvider({
|
||||
label: 'Assembly Symmetry Data',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'rcsb_struct_symmetry_data',
|
||||
// TODO `cifExport` and `symbol`
|
||||
}),
|
||||
type: 'root',
|
||||
defaultParams: AssemblySymmetryDataParams,
|
||||
getParams: (data: Structure) => AssemblySymmetryDataParams,
|
||||
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryDataProps>) => {
|
||||
const p = { ...PD.getDefaultValues(AssemblySymmetryDataParams), ...props };
|
||||
return await AssemblySymmetry.fetch(ctx, data, p);
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
function getAssemblySymmetryParams(data?: Structure) {
|
||||
return {
|
||||
... AssemblySymmetryDataParams,
|
||||
symmetryIndex: getSymmetrySelectParam(data)
|
||||
};
|
||||
}
|
||||
|
||||
export const AssemblySymmetryParams = getAssemblySymmetryParams();
|
||||
export type AssemblySymmetryParams = typeof AssemblySymmetryParams
|
||||
export type AssemblySymmetryProps = PD.Values<AssemblySymmetryParams>
|
||||
|
||||
export type AssemblySymmetryValue = AssemblySymmetryDataValue[0]
|
||||
|
||||
export const AssemblySymmetryProvider: CustomStructureProperty.Provider<AssemblySymmetryParams, AssemblySymmetryValue> = CustomStructureProperty.createProvider({
|
||||
label: 'Assembly Symmetry',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'rcsb_struct_symmetry',
|
||||
// TODO `cifExport` and `symbol`
|
||||
}),
|
||||
type: 'root',
|
||||
defaultParams: AssemblySymmetryParams,
|
||||
getParams: getAssemblySymmetryParams,
|
||||
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryProps>) => {
|
||||
const p = { ...PD.getDefaultValues(getAssemblySymmetryParams(data)), ...props };
|
||||
await AssemblySymmetryDataProvider.attach(ctx, data, p);
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(data).value;
|
||||
const assemblySymmetry = assemblySymmetryData?.[p.symmetryIndex];
|
||||
if (!assemblySymmetry) new Error(`No assembly symmetry found for index ${p.symmetryIndex}`);
|
||||
return { value: assemblySymmetry };
|
||||
}
|
||||
});
|
||||
@@ -5,9 +5,9 @@
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { AssemblySymmetryValue, getSymmetrySelectParam, AssemblySymmetryProvider } from '../assembly-symmetry';
|
||||
import { AssemblySymmetryValue, AssemblySymmetryProvider, AssemblySymmetry } from './prop';
|
||||
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
|
||||
import { Vec3, Mat4, Mat3 } from '../../../mol-math/linear-algebra';
|
||||
import { addCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
@@ -29,31 +29,30 @@ import { TetrahedronCage } from '../../../mol-geo/primitive/tetrahedron';
|
||||
import { IcosahedronCage } from '../../../mol-geo/primitive/icosahedron';
|
||||
import { degToRad, radToDeg } from '../../../mol-math/misc';
|
||||
import { Mutable } from '../../../mol-util/type-helpers';
|
||||
import { ReadonlyVec3 } from '../../../mol-math/linear-algebra/3d/vec3';
|
||||
import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
|
||||
import { Structure } from '../../../mol-model/structure';
|
||||
import { isInteger } from '../../../mol-util/number';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
|
||||
const OrderColors = ColorMap({
|
||||
'2': ColorNames.deepskyblue,
|
||||
'3': ColorNames.lime,
|
||||
'N': ColorNames.red,
|
||||
})
|
||||
});
|
||||
const OrderColorsLegend = TableLegend(Object.keys(OrderColors).map(name => {
|
||||
return [name, (OrderColors as any)[name] as Color] as [string, Color]
|
||||
}))
|
||||
return [name, (OrderColors as any)[name] as Color] as [string, Color];
|
||||
}));
|
||||
|
||||
function axesColorHelp(value: { name: string, params: {} }) {
|
||||
return value.name === 'byOrder'
|
||||
? { description: 'Color axes by their order', legend: OrderColorsLegend }
|
||||
: {}
|
||||
: {};
|
||||
}
|
||||
|
||||
const SharedParams = {
|
||||
...Mesh.Params,
|
||||
scale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
|
||||
symmetryIndex: getSymmetrySelectParam(),
|
||||
}
|
||||
};
|
||||
|
||||
const AxesParams = {
|
||||
...SharedParams,
|
||||
@@ -63,126 +62,122 @@ const AxesParams = {
|
||||
colorValue: PD.Color(ColorNames.orange),
|
||||
}, { isFlat: true })
|
||||
}, { help: axesColorHelp }),
|
||||
}
|
||||
};
|
||||
type AxesParams = typeof AxesParams
|
||||
|
||||
const CageParams = {
|
||||
...SharedParams,
|
||||
cageColor: PD.Color(ColorNames.orange),
|
||||
}
|
||||
};
|
||||
type CageParams = typeof CageParams
|
||||
|
||||
const AssemblySymmetryVisuals = {
|
||||
'axes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, AxesParams>) => ShapeRepresentation(getAxesShape, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
|
||||
// cage should come before 'axes' so that the representative loci uses the cage shape
|
||||
'cage': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, CageParams>) => ShapeRepresentation(getCageShape, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
|
||||
}
|
||||
'axes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, AxesParams>) => ShapeRepresentation(getAxesShape, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
|
||||
};
|
||||
|
||||
export const AssemblySymmetryParams = {
|
||||
...AxesParams,
|
||||
...CageParams,
|
||||
visuals: PD.MultiSelect(['axes', 'cage'], PD.objectToOptions(AssemblySymmetryVisuals)),
|
||||
}
|
||||
};
|
||||
export type AssemblySymmetryParams = typeof AssemblySymmetryParams
|
||||
export type AssemblySymmetryProps = PD.Values<AssemblySymmetryParams>
|
||||
|
||||
//
|
||||
|
||||
type RotationAxes = ReadonlyArray<{ order: number, start: ReadonlyVec3, end: ReadonlyVec3 }>
|
||||
function isRotationAxes(x: AssemblySymmetryValue[0]['rotation_axes']): x is RotationAxes {
|
||||
return !!x && x.length > 0
|
||||
}
|
||||
|
||||
function getAssemblyName(s: Structure) {
|
||||
const { id } = s.units[0].conformation.operator.assembly
|
||||
return isInteger(id) ? `Assembly ${id}` : id
|
||||
const id = s.units[0].conformation.operator.assembly?.id || '';
|
||||
return isInteger(id) ? `Assembly ${id}` : id;
|
||||
}
|
||||
|
||||
const t = Mat4.identity()
|
||||
const tmpV = Vec3()
|
||||
const tmpCenter = Vec3()
|
||||
const tmpScale = Vec3()
|
||||
const t = Mat4.identity();
|
||||
const tmpV = Vec3();
|
||||
const tmpCenter = Vec3();
|
||||
const tmpScale = Vec3();
|
||||
|
||||
const getOrderPrimitive = memoize1((order: number): Primitive | undefined => {
|
||||
if (order < 2) {
|
||||
return Prism(polygon(48, false))
|
||||
return Prism(polygon(48, false));
|
||||
} else if (order === 2) {
|
||||
const lens = Prism(polygon(48, false))
|
||||
const m = Mat4.identity()
|
||||
Mat4.scale(m, m, Vec3.create(1, 0.35, 1))
|
||||
transformPrimitive(lens, m)
|
||||
return lens
|
||||
const lens = Prism(polygon(48, false));
|
||||
const m = Mat4.identity();
|
||||
Mat4.scale(m, m, Vec3.create(1, 0.35, 1));
|
||||
transformPrimitive(lens, m);
|
||||
return lens;
|
||||
} else if (order === 3) {
|
||||
return Wedge()
|
||||
return Wedge();
|
||||
} else {
|
||||
return Prism(polygon(order, false))
|
||||
return Prism(polygon(order, false));
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function getAxesMesh(data: AssemblySymmetryValue, props: PD.Values<AxesParams>, mesh?: Mesh) {
|
||||
const { symmetryIndex, scale } = props
|
||||
const { scale } = props;
|
||||
|
||||
const { rotation_axes } = data[symmetryIndex]
|
||||
if (!isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
|
||||
const { rotation_axes } = data;
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh);
|
||||
|
||||
const { start, end } = rotation_axes[0]
|
||||
const radius = (Vec3.distance(start, end) / 500) * scale
|
||||
const { start, end } = rotation_axes[0];
|
||||
const radius = (Vec3.distance(start, end) / 500) * scale;
|
||||
|
||||
Vec3.set(tmpScale, radius * 7, radius * 7, radius * 0.4)
|
||||
Vec3.set(tmpScale, radius * 7, radius * 7, radius * 0.4);
|
||||
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius }
|
||||
const builderState = MeshBuilder.createState(256, 128, mesh)
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius };
|
||||
const builderState = MeshBuilder.createState(256, 128, mesh);
|
||||
|
||||
builderState.currentGroup = 0
|
||||
Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5)
|
||||
builderState.currentGroup = 0;
|
||||
Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5);
|
||||
|
||||
for (let i = 0, il = rotation_axes.length; i < il; ++i) {
|
||||
const { order, start, end } = rotation_axes[i]
|
||||
builderState.currentGroup = i
|
||||
addCylinder(builderState, start, end, 1, cylinderProps)
|
||||
const { order, start, end } = rotation_axes[i];
|
||||
builderState.currentGroup = i;
|
||||
addCylinder(builderState, start, end, 1, cylinderProps);
|
||||
|
||||
const primitive = getOrderPrimitive(order)
|
||||
const primitive = getOrderPrimitive(order);
|
||||
if (primitive) {
|
||||
Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5)
|
||||
Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5);
|
||||
if (Vec3.dot(Vec3.unitY, Vec3.sub(tmpV, start, tmpCenter)) === 0) {
|
||||
Mat4.targetTo(t, start, tmpCenter, Vec3.unitY)
|
||||
Mat4.targetTo(t, start, tmpCenter, Vec3.unitY);
|
||||
} else {
|
||||
Mat4.targetTo(t, start, tmpCenter, Vec3.unitX)
|
||||
Mat4.targetTo(t, start, tmpCenter, Vec3.unitX);
|
||||
}
|
||||
Mat4.scale(t, t, tmpScale)
|
||||
Mat4.scale(t, t, tmpScale);
|
||||
|
||||
Mat4.setTranslation(t, start)
|
||||
MeshBuilder.addPrimitive(builderState, t, primitive)
|
||||
Mat4.setTranslation(t, end)
|
||||
MeshBuilder.addPrimitive(builderState, t, primitive)
|
||||
Mat4.setTranslation(t, start);
|
||||
MeshBuilder.addPrimitive(builderState, t, primitive);
|
||||
Mat4.setTranslation(t, end);
|
||||
MeshBuilder.addPrimitive(builderState, t, primitive);
|
||||
}
|
||||
}
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
return MeshBuilder.getMesh(builderState);
|
||||
}
|
||||
|
||||
function getAxesShape(ctx: RuntimeContext, data: Structure, props: AssemblySymmetryProps, shape?: Shape<Mesh>) {
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!;
|
||||
const geo = getAxesMesh(assemblySymmetry, props, shape && shape.geometry);
|
||||
const getColor = (groupId: number) => {
|
||||
if (props.axesColor.name === 'byOrder') {
|
||||
const { rotation_axes } = assemblySymmetry[props.symmetryIndex]
|
||||
const order = rotation_axes![groupId]?.order
|
||||
if (order === 2) return OrderColors[2]
|
||||
else if (order === 3) return OrderColors[3]
|
||||
else return OrderColors.N
|
||||
const { rotation_axes } = assemblySymmetry;
|
||||
const order = rotation_axes![groupId]?.order;
|
||||
if (order === 2) return OrderColors[2];
|
||||
else if (order === 3) return OrderColors[3];
|
||||
else return OrderColors.N;
|
||||
} else {
|
||||
return props.axesColor.params.colorValue
|
||||
return props.axesColor.params.colorValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
const getLabel = (groupId: number) => {
|
||||
const { type, symbol, kind, rotation_axes } = assemblySymmetry[props.symmetryIndex]
|
||||
const order = rotation_axes![groupId]?.order
|
||||
const { type, symbol, kind, rotation_axes } = assemblySymmetry;
|
||||
const order = rotation_axes![groupId]?.order;
|
||||
return [
|
||||
`<small>${data.model.entryId}</small>`,
|
||||
`<small>${getAssemblyName(data)}</small>`,
|
||||
`Axis ${groupId + 1} with Order ${order} of ${type} ${kind} (${symbol})`
|
||||
].join(' | ')
|
||||
}
|
||||
return Shape.create('Axes', data, geo, getColor, () => 1, getLabel)
|
||||
].join(' | ');
|
||||
};
|
||||
return Shape.create('Axes', data, geo, getColor, () => 1, getLabel);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -190,144 +185,179 @@ function getAxesShape(ctx: RuntimeContext, data: Structure, props: AssemblySymme
|
||||
const getSymbolCage = memoize1((symbol: string): Cage | undefined => {
|
||||
if (symbol.startsWith('D') || symbol.startsWith('C')) {
|
||||
// z axis is prism axis, x/y axes cut through edge midpoints
|
||||
const fold = parseInt(symbol.substr(1))
|
||||
const fold = parseInt(symbol.substr(1));
|
||||
if (fold === 2) {
|
||||
return PrismCage(polygon(4, false))
|
||||
return PrismCage(polygon(4, false));
|
||||
} else if (fold === 3) {
|
||||
return WedgeCage()
|
||||
return WedgeCage();
|
||||
} else if (fold > 3) {
|
||||
return PrismCage(polygon(fold, false))
|
||||
return PrismCage(polygon(fold, false));
|
||||
}
|
||||
} else if (symbol === 'O') {
|
||||
// x/y/z axes cut through order 4 vertices
|
||||
return OctahedronCage()
|
||||
return OctahedronCage();
|
||||
} else if (symbol === 'I') {
|
||||
// z axis cut through order 5 vertex
|
||||
// x axis cut through edge midpoint
|
||||
const cage = IcosahedronCage()
|
||||
const m = Mat4.identity()
|
||||
Mat4.rotate(m, m, degToRad(31.7), Vec3.unitX)
|
||||
return transformCage(cloneCage(cage), m)
|
||||
const cage = IcosahedronCage();
|
||||
const m = Mat4.identity();
|
||||
Mat4.rotate(m, m, degToRad(31.7), Vec3.unitX);
|
||||
return transformCage(cloneCage(cage), m);
|
||||
} else if (symbol === 'T') {
|
||||
// x/y/z axes cut through edge midpoints
|
||||
return TetrahedronCage()
|
||||
return TetrahedronCage();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function getSymbolScale(symbol: string) {
|
||||
if (symbol.startsWith('D') || symbol.startsWith('C')) {
|
||||
return 0.75
|
||||
return 0.75;
|
||||
} else if (symbol === 'O') {
|
||||
return 1.2
|
||||
return 1.2;
|
||||
} else if (symbol === 'I') {
|
||||
return 0.25
|
||||
return 0.25;
|
||||
} else if (symbol === 'T') {
|
||||
return 0.8
|
||||
return 0.8;
|
||||
}
|
||||
return 1
|
||||
return 1;
|
||||
}
|
||||
|
||||
function setSymbolTransform(t: Mat4, symbol: string, axes: RotationAxes, size: number, structure: Structure) {
|
||||
const eye = Vec3()
|
||||
const target = Vec3()
|
||||
const up = Vec3()
|
||||
let pair: Mutable<RotationAxes> | undefined = undefined
|
||||
function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.RotationAxes, size: number, structure: Structure) {
|
||||
const eye = Vec3();
|
||||
const target = Vec3();
|
||||
const dir = Vec3();
|
||||
const up = Vec3();
|
||||
let pair: Mutable<AssemblySymmetry.RotationAxes> | undefined = undefined;
|
||||
|
||||
if (symbol.startsWith('C')) {
|
||||
pair = [axes[0]]
|
||||
pair = [axes[0]];
|
||||
} else if (symbol.startsWith('D')) {
|
||||
const fold = parseInt(symbol.substr(1))
|
||||
const fold = parseInt(symbol.substr(1));
|
||||
if (fold === 2) {
|
||||
pair = axes.filter(a => a.order === 2)
|
||||
pair = axes.filter(a => a.order === 2);
|
||||
} else if (fold >= 3) {
|
||||
const aN = axes.filter(a => a.order === fold)[0]
|
||||
const a2 = axes.filter(a => a.order === 2)[0]
|
||||
pair = [aN, a2]
|
||||
const aN = axes.filter(a => a.order === fold)[0];
|
||||
const a2 = axes.filter(a => a.order === 2)[0];
|
||||
pair = [aN, a2];
|
||||
}
|
||||
} else if (symbol === 'O') {
|
||||
pair = axes.filter(a => a.order === 4)
|
||||
pair = axes.filter(a => a.order === 4);
|
||||
} else if (symbol === 'I') {
|
||||
const a5 = axes.filter(a => a.order === 5)[0]
|
||||
const a5dir = Vec3.sub(Vec3(), a5.end, a5.start)
|
||||
pair = [a5]
|
||||
const a5 = axes.filter(a => a.order === 5)[0];
|
||||
const a5dir = Vec3.sub(Vec3(), a5.end, a5.start);
|
||||
pair = [a5];
|
||||
for (const a of axes.filter(a => a.order === 3)) {
|
||||
let d = radToDeg(Vec3.angle(Vec3.sub(up, a.end, a.start), a5dir))
|
||||
if (equalEps(d, 100.81, 0.1)) {
|
||||
pair[1] = a
|
||||
break
|
||||
const d = radToDeg(Vec3.angle(Vec3.sub(up, a.end, a.start), a5dir));
|
||||
if (!pair[1] && (equalEps(d, 100.81, 0.1) || equalEps(d, 79.19, 0.1))) {
|
||||
pair[1] = a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (symbol === 'T') {
|
||||
pair = axes.filter(a => a.order === 2)
|
||||
pair = axes.filter(a => a.order === 2);
|
||||
}
|
||||
|
||||
Mat4.setIdentity(t)
|
||||
Mat4.setIdentity(t);
|
||||
if (pair) {
|
||||
const [aA, aB] = pair
|
||||
Vec3.scale(eye, Vec3.add(eye, aA.end, aA.start), 0.5)
|
||||
Vec3.copy(target, aA.end)
|
||||
const [aA, aB] = pair;
|
||||
Vec3.scale(eye, Vec3.add(eye, aA.end, aA.start), 0.5);
|
||||
Vec3.copy(target, aA.end);
|
||||
if (aB) {
|
||||
Vec3.sub(up, aB.end, aB.start)
|
||||
Mat4.targetTo(t, eye, target, up)
|
||||
Mat4.scaleUniformly(t, t, size * getSymbolScale(symbol))
|
||||
Vec3.sub(up, aB.end, aB.start);
|
||||
Vec3.sub(dir, eye, target);
|
||||
if (Vec3.dot(dir, up) < 0) Vec3.negate(up, up);
|
||||
Mat4.targetTo(t, eye, target, up);
|
||||
Mat4.scaleUniformly(t, t, size * getSymbolScale(symbol));
|
||||
} else {
|
||||
if (Vec3.dot(Vec3.unitY, Vec3.sub(tmpV, aA.end, aA.start)) === 0) {
|
||||
Vec3.copy(up, Vec3.unitY)
|
||||
Vec3.copy(up, Vec3.unitY);
|
||||
} else {
|
||||
Vec3.copy(up, Vec3.unitX)
|
||||
Vec3.copy(up, Vec3.unitX);
|
||||
}
|
||||
const sizeXY = (structure.lookup3d.boundary.sphere.radius * 2) * 0.8
|
||||
Mat4.targetTo(t, eye, target, up)
|
||||
Mat4.scale(t, t, Vec3.create(sizeXY, sizeXY, size))
|
||||
Mat4.targetTo(t, eye, target, up);
|
||||
|
||||
const { sphere } = structure.lookup3d.boundary;
|
||||
let sizeXY = (sphere.radius * 2) * 0.8; // fallback for missing extrema
|
||||
if (Sphere3D.hasExtrema(sphere)) {
|
||||
const n = Mat3.directionTransform(Mat3(), t);
|
||||
const dirs = unitCircleDirections.map(d => Vec3.transformMat3(Vec3(), d, n));
|
||||
sizeXY = getMaxProjectedDistance(sphere.extrema, dirs, sphere.center);
|
||||
}
|
||||
|
||||
Mat4.scale(t, t, Vec3.create(sizeXY, sizeXY, size * 0.9));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unitCircleDirections = (function() {
|
||||
const dirs: Vec3[] = [];
|
||||
const circle = polygon(12, false, 1);
|
||||
for (let i = 0, il = circle.length; i < il; i += 3) {
|
||||
dirs.push(Vec3.fromArray(Vec3(), circle, i));
|
||||
}
|
||||
return dirs;
|
||||
})();
|
||||
const tmpProj = Vec3();
|
||||
|
||||
function getMaxProjectedDistance(points: Vec3[], directions: Vec3[], center: Vec3) {
|
||||
let maxDist = 0;
|
||||
for (const p of points) {
|
||||
for (const d of directions) {
|
||||
Vec3.projectPointOnVector(tmpProj, p, d, center);
|
||||
const dist = Vec3.distance(tmpProj, center);
|
||||
if (dist > maxDist) maxDist = dist;
|
||||
}
|
||||
}
|
||||
return maxDist;
|
||||
}
|
||||
|
||||
function getCageMesh(data: Structure, props: PD.Values<CageParams>, mesh?: Mesh) {
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!
|
||||
const { symmetryIndex, scale } = props
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!;
|
||||
const { scale } = props;
|
||||
|
||||
const { rotation_axes, symbol } = assemblySymmetry[symmetryIndex]
|
||||
if (!isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
|
||||
const { rotation_axes, symbol } = assemblySymmetry;
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh);
|
||||
|
||||
const cage = getSymbolCage(symbol)
|
||||
if (!cage) return Mesh.createEmpty(mesh)
|
||||
const structure = AssemblySymmetry.getStructure(data, assemblySymmetry);
|
||||
|
||||
const { start, end } = rotation_axes[0]
|
||||
const size = Vec3.distance(start, end)
|
||||
const radius = (size / 500) * scale
|
||||
const cage = getSymbolCage(symbol);
|
||||
if (!cage) return Mesh.createEmpty(mesh);
|
||||
|
||||
const builderState = MeshBuilder.createState(256, 128, mesh)
|
||||
builderState.currentGroup = 0
|
||||
setSymbolTransform(t, symbol, rotation_axes, size, data)
|
||||
Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5)
|
||||
Mat4.setTranslation(t, tmpCenter)
|
||||
MeshBuilder.addCage(builderState, t, cage, radius, 1, 8)
|
||||
const { start, end } = rotation_axes[0];
|
||||
const size = Vec3.distance(start, end);
|
||||
const radius = (size / 500) * scale;
|
||||
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
const builderState = MeshBuilder.createState(256, 128, mesh);
|
||||
builderState.currentGroup = 0;
|
||||
setSymbolTransform(t, symbol, rotation_axes, size, structure);
|
||||
Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5);
|
||||
Mat4.setTranslation(t, tmpCenter);
|
||||
MeshBuilder.addCage(builderState, t, cage, radius, 1, 8);
|
||||
|
||||
return MeshBuilder.getMesh(builderState);
|
||||
}
|
||||
|
||||
function getCageShape(ctx: RuntimeContext, data: Structure, props: AssemblySymmetryProps, shape?: Shape<Mesh>) {
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!;
|
||||
const geo = getCageMesh(data, props, shape && shape.geometry);
|
||||
const getColor = (groupId: number) => {
|
||||
return props.cageColor
|
||||
}
|
||||
return props.cageColor;
|
||||
};
|
||||
const getLabel = (groupId: number) => {
|
||||
const { type, symbol, kind } = assemblySymmetry[props.symmetryIndex]
|
||||
data.model.entryId
|
||||
const { type, symbol, kind } = assemblySymmetry;
|
||||
data.model.entryId;
|
||||
return [
|
||||
`<small>${data.model.entryId}</small>`,
|
||||
`<small>${getAssemblyName(data)}</small>`,
|
||||
`Cage of ${type} ${kind} (${symbol})`
|
||||
].join(' | ')
|
||||
}
|
||||
return Shape.create('Cage', data, geo, getColor, () => 1, getLabel)
|
||||
].join(' | ');
|
||||
};
|
||||
return Shape.create('Cage', data, geo, getColor, () => 1, getLabel);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export type AssemblySymmetryRepresentation = Representation<Structure, AssemblySymmetryParams>
|
||||
export function AssemblySymmetryRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, AssemblySymmetryParams>): AssemblySymmetryRepresentation {
|
||||
return Representation.createMulti('Symmetry', ctx, getParams, Representation.StateBuilder, AssemblySymmetryVisuals as unknown as Representation.Def<Structure, AssemblySymmetryParams>)
|
||||
return Representation.createMulti('Assembly Symmetry', ctx, getParams, Representation.StateBuilder, AssemblySymmetryVisuals as unknown as Representation.Def<Structure, AssemblySymmetryParams>);
|
||||
}
|
||||
158
src/extensions/rcsb/assembly-symmetry/ui.tsx
Normal file
158
src/extensions/rcsb/assembly-symmetry/ui.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { CollapsableState, CollapsableControls } from '../../../mol-plugin-ui/base';
|
||||
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
|
||||
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry } from './behavior';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetryProps, AssemblySymmetryDataProvider, AssemblySymmetry } from './prop';
|
||||
import { ParameterControls } from '../../../mol-plugin-ui/controls/parameters';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { StructureHierarchyManager } from '../../../mol-plugin-state/manager/structure/hierarchy';
|
||||
import { StateAction, StateSelection } from '../../../mol-state';
|
||||
import { PluginStateObject } from '../../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { Task } from '../../../mol-task';
|
||||
import Check from '@material-ui/icons/Check';
|
||||
import Extension from '@material-ui/icons/Extension';
|
||||
|
||||
interface AssemblySymmetryControlState extends CollapsableState {
|
||||
isBusy: boolean
|
||||
}
|
||||
|
||||
export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySymmetryControlState> {
|
||||
protected defaultState(): AssemblySymmetryControlState {
|
||||
return {
|
||||
header: 'Assembly Symmetry',
|
||||
isCollapsed: false,
|
||||
isBusy: false,
|
||||
isHidden: true,
|
||||
brand: { accent: 'cyan', svg: Extension }
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, () => {
|
||||
this.setState({
|
||||
isHidden: !this.canEnable(),
|
||||
description: StructureHierarchyManager.getSelectedStructuresDescription(this.plugin)
|
||||
});
|
||||
});
|
||||
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
|
||||
if (e.cell.transform.transformer === AssemblySymmetry3D) this.forceUpdate();
|
||||
});
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, v => this.setState({ isBusy: v }));
|
||||
}
|
||||
|
||||
get pivot() {
|
||||
return this.plugin.managers.structure.hierarchy.selection.structures[0];
|
||||
}
|
||||
|
||||
canEnable() {
|
||||
const { selection } = this.plugin.managers.structure.hierarchy;
|
||||
if (selection.structures.length !== 1) return false;
|
||||
const pivot = this.pivot.cell;
|
||||
if (!pivot.obj) return false;
|
||||
return !!InitAssemblySymmetry3D.definition.isApplicable?.(pivot.obj, pivot.transform, this.plugin);
|
||||
}
|
||||
|
||||
renderEnable() {
|
||||
const pivot = this.pivot;
|
||||
if (!pivot.cell.parent) return null;
|
||||
return <ApplyActionControl state={pivot.cell.parent} action={EnableAssemblySymmetry3D} initiallyCollapsed={true} nodeRef={pivot.cell.transform.ref} simpleApply={{ header: 'Enable', icon: Check }} />;
|
||||
}
|
||||
|
||||
renderNoSymmetries() {
|
||||
return <div className='msp-row-text'>
|
||||
<div>No Symmetries for Assembly</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
get params() {
|
||||
const structure = this.pivot.cell.obj?.data;
|
||||
const params = PD.clone(structure ? AssemblySymmetryProvider.getParams(structure) : AssemblySymmetryProvider.defaultParams);
|
||||
params.serverUrl.isHidden = true;
|
||||
params.symmetryIndex.options = [[-1, 'Off'], ...params.symmetryIndex.options];
|
||||
return params;
|
||||
}
|
||||
|
||||
get values() {
|
||||
const structure = this.pivot.cell.obj?.data;
|
||||
if (structure) {
|
||||
return AssemblySymmetryProvider.props(structure);
|
||||
} else {
|
||||
return { ...PD.getDefaultValues(AssemblySymmetryProvider.defaultParams), symmetryIndex: -1 };
|
||||
}
|
||||
}
|
||||
|
||||
async updateAssemblySymmetry(values: AssemblySymmetryProps) {
|
||||
const s = this.pivot;
|
||||
const currValues = AssemblySymmetryProvider.props(s.cell.obj!.data);
|
||||
if (PD.areEqual(AssemblySymmetryProvider.defaultParams, currValues, values)) return;
|
||||
|
||||
if (s.properties) {
|
||||
const b = this.plugin.state.data.build();
|
||||
b.to(s.properties.cell).update(old => {
|
||||
old.properties[AssemblySymmetryProvider.descriptor.name] = values;
|
||||
});
|
||||
await b.commit();
|
||||
} else {
|
||||
const pd = this.plugin.customStructureProperties.getParams(s.cell.obj?.data);
|
||||
const params = PD.getDefaultValues(pd);
|
||||
params.properties[AssemblySymmetryProvider.descriptor.name] = values;
|
||||
await this.plugin.builders.structure.insertStructureProperties(s.cell, params);
|
||||
}
|
||||
|
||||
for (const components of this.plugin.managers.structure.hierarchy.currentComponentGroups) {
|
||||
if (values.symmetryIndex === -1) {
|
||||
const name = components[0]?.representations[0]?.cell.transform.params?.colorTheme.name;
|
||||
if (name === AssemblySymmetry.Tag.Cluster) {
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: 'default' });
|
||||
}
|
||||
} else {
|
||||
tryCreateAssemblySymmetry(this.plugin, s.cell);
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: AssemblySymmetry.Tag.Cluster as any });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paramsOnChange = (options: AssemblySymmetryProps) => {
|
||||
this.updateAssemblySymmetry(options);
|
||||
}
|
||||
|
||||
get hasAssemblySymmetry3D() {
|
||||
return !this.pivot.cell.parent || !!StateSelection.findTagInSubtree(this.pivot.cell.parent.tree, this.pivot.cell.transform.ref, AssemblySymmetry.Tag.Representation);
|
||||
}
|
||||
|
||||
get enable() {
|
||||
return !this.hasAssemblySymmetry3D && this.values.symmetryIndex !== -1;
|
||||
}
|
||||
|
||||
get noSymmetries() {
|
||||
const structure = this.pivot.cell.obj?.data;
|
||||
const data = structure && AssemblySymmetryDataProvider.get(structure).value;
|
||||
return data && data.filter(sym => sym.symbol !== 'C1').length === 0;
|
||||
}
|
||||
|
||||
renderParams() {
|
||||
return <>
|
||||
<ParameterControls params={this.params} values={this.values} onChangeValues={this.paramsOnChange} />
|
||||
</>;
|
||||
}
|
||||
|
||||
renderControls() {
|
||||
if (!this.pivot) return null;
|
||||
if (this.noSymmetries) return this.renderNoSymmetries();
|
||||
if (this.enable) return this.renderEnable();
|
||||
return this.renderParams();
|
||||
}
|
||||
}
|
||||
|
||||
const EnableAssemblySymmetry3D = StateAction.build({
|
||||
from: PluginStateObject.Molecule.Structure,
|
||||
})(({ a, ref, state }, plugin: PluginContext) => Task.create('Enable Assembly Symmetry', async ctx => {
|
||||
await AssemblySymmetryPreset.apply(ref, Object.create(null), plugin);
|
||||
}));
|
||||
@@ -1,7 +1,7 @@
|
||||
schema: https://data-beta.rcsb.org/graphql
|
||||
documents: './src/mol-model-props/rcsb/graphql/symmetry.gql.ts'
|
||||
schema: https://data.rcsb.org/graphql
|
||||
documents: './src/extensions/rcsb/graphql/symmetry.gql.ts'
|
||||
generates:
|
||||
'./src/mol-model-props/rcsb/graphql/types.d.ts':
|
||||
'./src/extensions/rcsb/graphql/types.ts':
|
||||
plugins:
|
||||
- add: '/* eslint-disable */'
|
||||
- time
|
||||
@@ -22,4 +22,4 @@ query AssemblySymmetry($assembly_id: String!, $entry_id: String!) {
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`;
|
||||
2631
src/extensions/rcsb/graphql/types.ts
Normal file
2631
src/extensions/rcsb/graphql/types.ts
Normal file
File diff suppressed because it is too large
Load Diff
8
src/extensions/rcsb/index.ts
Normal file
8
src/extensions/rcsb/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
export { RCSBAssemblySymmetry } from './assembly-symmetry/behavior';
|
||||
export { RCSBValidationReport } from './validation-report/behavior';
|
||||
382
src/extensions/rcsb/validation-report/behavior.ts
Normal file
382
src/extensions/rcsb/validation-report/behavior.ts
Normal file
@@ -0,0 +1,382 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
|
||||
import { ValidationReport, ValidationReportProvider } from './prop';
|
||||
import { RandomCoilIndexColorThemeProvider } from './color/random-coil-index';
|
||||
import { GeometryQualityColorThemeProvider } from './color/geometry-quality';
|
||||
import { Loci } from '../../../mol-model/loci';
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { ClashesRepresentationProvider } from './representation';
|
||||
import { DensityFitColorThemeProvider } from './color/density-fit';
|
||||
import { cantorPairing } from '../../../mol-data/util';
|
||||
import { DefaultQueryRuntimeTable } from '../../../mol-script/runtime/query/compiler';
|
||||
import { StructureSelectionQuery, StructureSelectionCategory } from '../../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StateObjectRef } from '../../../mol-state';
|
||||
import { Model } from '../../../mol-model/structure';
|
||||
|
||||
export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
|
||||
name: 'rcsb-validation-report-prop',
|
||||
category: 'custom-props',
|
||||
display: {
|
||||
name: 'Validation Report',
|
||||
description: 'Data from wwPDB Validation Report, obtained via RCSB PDB.'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
|
||||
private provider = ValidationReportProvider
|
||||
|
||||
private labelProvider = {
|
||||
label: (loci: Loci): string | undefined => {
|
||||
if (!this.params.showTooltip) return;
|
||||
return [
|
||||
geometryQualityLabel(loci),
|
||||
densityFitLabel(loci),
|
||||
randomCoilIndexLabel(loci)
|
||||
].filter(l => !!l).join('</br>');
|
||||
}
|
||||
}
|
||||
|
||||
register(): void {
|
||||
DefaultQueryRuntimeTable.addCustomProp(this.provider.descriptor);
|
||||
|
||||
this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
|
||||
|
||||
this.ctx.managers.lociLabels.addProvider(this.labelProvider);
|
||||
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(DensityFitColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(GeometryQualityColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(RandomCoilIndexColorThemeProvider);
|
||||
|
||||
this.ctx.representation.structure.registry.add(ClashesRepresentationProvider);
|
||||
this.ctx.query.structure.registry.add(hasClash);
|
||||
|
||||
this.ctx.builders.structure.representation.registerPreset(ValidationReportGeometryQualityPreset);
|
||||
this.ctx.builders.structure.representation.registerPreset(ValidationReportDensityFitPreset);
|
||||
this.ctx.builders.structure.representation.registerPreset(ValidationReportRandomCoilIndexPreset);
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean, showTooltip: boolean }) {
|
||||
let updated = this.params.autoAttach !== p.autoAttach;
|
||||
this.params.autoAttach = p.autoAttach;
|
||||
this.params.showTooltip = p.showTooltip;
|
||||
this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
|
||||
return updated;
|
||||
}
|
||||
|
||||
unregister() {
|
||||
DefaultQueryRuntimeTable.removeCustomProp(this.provider.descriptor);
|
||||
|
||||
this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
|
||||
|
||||
this.ctx.managers.lociLabels.removeProvider(this.labelProvider);
|
||||
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(DensityFitColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(GeometryQualityColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(RandomCoilIndexColorThemeProvider);
|
||||
|
||||
this.ctx.representation.structure.registry.remove(ClashesRepresentationProvider);
|
||||
this.ctx.query.structure.registry.remove(hasClash);
|
||||
|
||||
this.ctx.builders.structure.representation.unregisterPreset(ValidationReportGeometryQualityPreset);
|
||||
this.ctx.builders.structure.representation.unregisterPreset(ValidationReportDensityFitPreset);
|
||||
this.ctx.builders.structure.representation.unregisterPreset(ValidationReportRandomCoilIndexPreset);
|
||||
}
|
||||
},
|
||||
params: () => ({
|
||||
autoAttach: PD.Boolean(false),
|
||||
showTooltip: PD.Boolean(true),
|
||||
baseUrl: PD.Text(ValidationReport.DefaultBaseUrl)
|
||||
})
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
function geometryQualityLabel(loci: Loci): string | undefined {
|
||||
if (loci.kind === 'element-loci') {
|
||||
if (loci.elements.length === 0) return;
|
||||
|
||||
if (loci.elements.length === 1 && OrderedSet.size(loci.elements[0].indices) === 1) {
|
||||
const { unit, indices } = loci.elements[0];
|
||||
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value;
|
||||
if (!validationReport) return;
|
||||
if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) return;
|
||||
|
||||
const { bondOutliers, angleOutliers } = validationReport;
|
||||
const eI = unit.elements[OrderedSet.start(indices)];
|
||||
const issues = new Set<string>();
|
||||
|
||||
const bonds = bondOutliers.index.get(eI);
|
||||
if (bonds) bonds.forEach(b => issues.add(bondOutliers.data[b].tag));
|
||||
|
||||
const angles = angleOutliers.index.get(eI);
|
||||
if (angles) angles.forEach(a => issues.add(angleOutliers.data[a].tag));
|
||||
|
||||
if (issues.size === 0) {
|
||||
return `Geometry Quality <small>(1 Atom)</small>: no issues`;
|
||||
}
|
||||
|
||||
const summary: string[] = [];
|
||||
issues.forEach(name => summary.push(name));
|
||||
return `Geometry Quality <small>(1 Atom)</small>: ${summary.join(', ')}`;
|
||||
}
|
||||
|
||||
let hasValidationReport = false;
|
||||
const seen = new Set<number>();
|
||||
const cummulativeIssues = new Map<string, number>();
|
||||
|
||||
for (const { indices, unit } of loci.elements) {
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value;
|
||||
if (!validationReport) continue;
|
||||
if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue;
|
||||
hasValidationReport = true;
|
||||
|
||||
const { geometryIssues } = validationReport;
|
||||
const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index;
|
||||
const { elements } = unit;
|
||||
|
||||
OrderedSet.forEach(indices, idx => {
|
||||
const eI = elements[idx];
|
||||
|
||||
const rI = residueIndex[eI];
|
||||
const residueKey = cantorPairing(rI, unit.id);
|
||||
if (!seen.has(residueKey)) {
|
||||
const issues = geometryIssues.get(rI);
|
||||
if (issues) {
|
||||
issues.forEach(name => {
|
||||
const count = cummulativeIssues.get(name) || 0;
|
||||
cummulativeIssues.set(name, count + 1);
|
||||
});
|
||||
}
|
||||
seen.add(residueKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasValidationReport) return;
|
||||
|
||||
const residueCount = `<small>(${seen.size} ${seen.size > 1 ? 'Residues' : 'Residue'})</small>`;
|
||||
|
||||
if (cummulativeIssues.size === 0) {
|
||||
return `Geometry Quality ${residueCount}: no issues`;
|
||||
}
|
||||
|
||||
const summary: string[] = [];
|
||||
cummulativeIssues.forEach((count, name) => {
|
||||
summary.push(`${name}${count > 1 ? ` \u00D7 ${count}` : ''}`);
|
||||
});
|
||||
return `Geometry Quality ${residueCount}: ${summary.join(', ')}`;
|
||||
}
|
||||
}
|
||||
|
||||
function densityFitLabel(loci: Loci): string | undefined {
|
||||
if (loci.kind === 'element-loci') {
|
||||
if (loci.elements.length === 0) return;
|
||||
|
||||
const seen = new Set<number>();
|
||||
const rsrzSeen = new Set<number>();
|
||||
const rsccSeen = new Set<number>();
|
||||
let rsrzSum = 0;
|
||||
let rsccSum = 0;
|
||||
|
||||
for (const { indices, unit } of loci.elements) {
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value;
|
||||
if (!validationReport) continue;
|
||||
if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue;
|
||||
|
||||
const { rsrz, rscc } = validationReport;
|
||||
const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index;
|
||||
const { elements } = unit;
|
||||
|
||||
OrderedSet.forEach(indices, idx => {
|
||||
const eI = elements[idx];
|
||||
const rI = residueIndex[eI];
|
||||
|
||||
const residueKey = cantorPairing(rI, unit.id);
|
||||
if (!seen.has(residueKey)) {
|
||||
const rsrzValue = rsrz.get(rI);
|
||||
const rsccValue = rscc.get(rI);
|
||||
if (rsrzValue !== undefined) {
|
||||
rsrzSum += rsrzValue;
|
||||
rsrzSeen.add(residueKey);
|
||||
} else if (rsccValue !== undefined) {
|
||||
rsccSum += rsccValue;
|
||||
rsccSeen.add(residueKey);
|
||||
}
|
||||
seen.add(residueKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (seen.size === 0) return;
|
||||
|
||||
const summary: string[] = [];
|
||||
|
||||
if (rsrzSeen.size) {
|
||||
const rsrzCount = `<small>(${rsrzSeen.size} ${rsrzSeen.size > 1 ? 'Residues avg.' : 'Residue'})</small>`;
|
||||
const rsrzAvg = rsrzSum / rsrzSeen.size;
|
||||
summary.push(`Real Space R ${rsrzCount}: ${rsrzAvg.toFixed(2)}`);
|
||||
}
|
||||
if (rsccSeen.size) {
|
||||
const rsccCount = `<small>(${rsccSeen.size} ${rsccSeen.size > 1 ? 'Residues avg.' : 'Residue'})</small>`;
|
||||
const rsccAvg = rsccSum / rsccSeen.size;
|
||||
summary.push(`Real Space Correlation Coefficient ${rsccCount}: ${rsccAvg.toFixed(2)}`);
|
||||
}
|
||||
|
||||
if (summary.length) {
|
||||
return summary.join('</br>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function randomCoilIndexLabel(loci: Loci): string | undefined {
|
||||
if (loci.kind === 'element-loci') {
|
||||
if (loci.elements.length === 0) return;
|
||||
|
||||
const seen = new Set<number>();
|
||||
let sum = 0;
|
||||
|
||||
for (const { indices, unit } of loci.elements) {
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value;
|
||||
if (!validationReport) continue;
|
||||
if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue;
|
||||
|
||||
const { rci } = validationReport;
|
||||
const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index;
|
||||
const { elements } = unit;
|
||||
|
||||
OrderedSet.forEach(indices, idx => {
|
||||
const eI = elements[idx];
|
||||
const rI = residueIndex[eI];
|
||||
|
||||
const residueKey = cantorPairing(rI, unit.id);
|
||||
if (!seen.has(residueKey)) {
|
||||
const rciValue = rci.get(rI);
|
||||
if (rciValue !== undefined) {
|
||||
sum += rciValue;
|
||||
seen.add(residueKey);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (seen.size === 0) return;
|
||||
|
||||
const residueCount = `<small>(${seen.size} ${seen.size > 1 ? 'Residues avg.' : 'Residue'})</small>`;
|
||||
const rciAvg = sum / seen.size;
|
||||
|
||||
return `Random Coil Index ${residueCount}: ${rciAvg.toFixed(2)}`;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const hasClash = StructureSelectionQuery('Residues with Clashes', MS.struct.modifier.union([
|
||||
MS.struct.modifier.wholeResidues([
|
||||
MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
|
||||
'atom-test': ValidationReport.symbols.hasClash.symbol(),
|
||||
})
|
||||
])
|
||||
])
|
||||
]), {
|
||||
description: 'Select residues with clashes in the wwPDB validation report.',
|
||||
category: StructureSelectionCategory.Residue,
|
||||
ensureCustomProperties: (ctx, structure) => {
|
||||
return ValidationReportProvider.attach(ctx, structure.models[0]);
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
export const ValidationReportGeometryQualityPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-rcsb-validation-report-geometry-uality',
|
||||
display: {
|
||||
name: 'Validation Report (Geometry Quality)', group: 'Annotation',
|
||||
description: 'Color structure based on geometry quality; show geometry clashes. Data from wwPDB Validation Report, obtained via RCSB PDB.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const model = structureCell?.obj?.data.model;
|
||||
if (!structureCell || !model) return {};
|
||||
|
||||
await plugin.runTask(Task.create('Validation Report', async runtime => {
|
||||
await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
|
||||
}));
|
||||
|
||||
const colorTheme = GeometryQualityColorThemeProvider.name as any;
|
||||
const { components, representations } = await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin);
|
||||
|
||||
const clashes = await plugin.builders.structure.tryCreateComponentFromExpression(structureCell, hasClash.expression, 'clashes', { label: 'Clashes' });
|
||||
|
||||
const { update, builder, typeParams, color } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
let clashesBallAndStick, clashesSnfg3d;
|
||||
if (representations) {
|
||||
clashesBallAndStick = builder.buildRepresentation(update, clashes, { type: 'ball-and-stick', typeParams, color: colorTheme }, { tag: 'clashes-ball-and-stick' });
|
||||
clashesSnfg3d = builder.buildRepresentation<any>(update, clashes, { type: ClashesRepresentationProvider.name, typeParams, color }, { tag: 'clashes-snfg-3d' });
|
||||
}
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
return { components: { ...components, clashes }, representations: { ...representations, clashesBallAndStick, clashesSnfg3d } };
|
||||
}
|
||||
});
|
||||
|
||||
export const ValidationReportDensityFitPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-rcsb-validation-report-density-fit',
|
||||
display: {
|
||||
name: 'Validation Report (Density Fit)', group: 'Annotation',
|
||||
description: 'Color structure based on density fit. Data from wwPDB Validation Report, obtained via RCSB PDB.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]) && Model.isFromXray(a.data.models[0]) && Model.probablyHasDensityMap(a.data.models[0]);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const model = structureCell?.obj?.data.model;
|
||||
if (!structureCell || !model) return {};
|
||||
|
||||
await plugin.runTask(Task.create('Validation Report', async runtime => {
|
||||
await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
|
||||
}));
|
||||
|
||||
const colorTheme = DensityFitColorThemeProvider.name as any;
|
||||
return await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin);
|
||||
}
|
||||
});
|
||||
|
||||
export const ValidationReportRandomCoilIndexPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-rcsb-validation-report-random-coil-index',
|
||||
display: {
|
||||
name: 'Validation Report (Random Coil Index)', group: 'Annotation',
|
||||
description: 'Color structure based on Random Coil Index. Data from wwPDB Validation Report, obtained via RCSB PDB.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]) && Model.isFromNmr(a.data.models[0]);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const model = structureCell?.obj?.data.model;
|
||||
if (!structureCell || !model) return {};
|
||||
|
||||
await plugin.runTask(Task.create('Validation Report', async runtime => {
|
||||
await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
|
||||
}));
|
||||
|
||||
const colorTheme = RandomCoilIndexColorThemeProvider.name as any;
|
||||
return await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin);
|
||||
}
|
||||
});
|
||||
75
src/extensions/rcsb/validation-report/color/density-fit.ts
Normal file
75
src/extensions/rcsb/validation-report/color/density-fit.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ThemeDataContext } from '../../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Color, ColorScale } from '../../../../mol-util/color';
|
||||
import { StructureElement, Model } from '../../../../mol-model/structure';
|
||||
import { Location } from '../../../../mol-model/location';
|
||||
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
|
||||
import { ValidationReportProvider, ValidationReport } from '../prop';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
|
||||
export function DensityFitColorTheme(ctx: ThemeDataContext, props: {}): ColorTheme<{}> {
|
||||
let color: LocationColor = () => DefaultColor;
|
||||
const scaleRsrz = ColorScale.create({
|
||||
minLabel: 'Poor',
|
||||
maxLabel: 'Better',
|
||||
domain: [2, 0],
|
||||
listOrName: 'red-yellow-blue',
|
||||
});
|
||||
const scaleRscc = ColorScale.create({
|
||||
minLabel: 'Poor',
|
||||
maxLabel: 'Better',
|
||||
domain: [0.678, 1.0],
|
||||
listOrName: 'red-yellow-blue',
|
||||
});
|
||||
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]);
|
||||
const contextHash = validationReport?.version;
|
||||
const model = ctx.structure?.models[0];
|
||||
|
||||
if (validationReport?.value && model) {
|
||||
const { rsrz, rscc } = validationReport.value;
|
||||
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location) && location.unit.model === model) {
|
||||
const rsrzValue = rsrz.get(residueIndex[location.element]);
|
||||
if (rsrzValue !== undefined) return scaleRsrz.color(rsrzValue);
|
||||
const rsccValue = rscc.get(residueIndex[location.element]);
|
||||
if (rsccValue !== undefined) return scaleRscc.color(rsccValue);
|
||||
return DefaultColor;
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
factory: DensityFitColorTheme,
|
||||
granularity: 'group',
|
||||
color,
|
||||
props,
|
||||
contextHash,
|
||||
description: 'Assigns residue colors according to the density fit using normalized Real Space R (RSRZ) for polymer residues and real space correlation coefficient (RSCC) for ligands. Colors range from poor (RSRZ = 2 or RSCC = 0.678) - to better (RSRZ = 0 or RSCC = 1.0). Data from wwPDB Validation Report, obtained via RCSB PDB.',
|
||||
legend: scaleRsrz.legend
|
||||
};
|
||||
}
|
||||
|
||||
export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationReport.Tag.DensityFit> = {
|
||||
name: ValidationReport.Tag.DensityFit,
|
||||
label: 'Density Fit',
|
||||
category: ColorTheme.Category.Validation,
|
||||
factory: DensityFitColorTheme,
|
||||
getParams: () => ({}),
|
||||
defaultValues: PD.getDefaultValues({}),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.isFromXray(ctx.structure.models[0]) && Model.probablyHasDensityMap(ctx.structure.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
}
|
||||
};
|
||||
115
src/extensions/rcsb/validation-report/color/geometry-quality.ts
Normal file
115
src/extensions/rcsb/validation-report/color/geometry-quality.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ThemeDataContext } from '../../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { StructureElement } from '../../../../mol-model/structure';
|
||||
import { Location } from '../../../../mol-model/location';
|
||||
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
|
||||
import { ValidationReportProvider, ValidationReport } from '../prop';
|
||||
import { TableLegend } from '../../../../mol-util/legend';
|
||||
import { PolymerType } from '../../../../mol-model/structure/model/types';
|
||||
import { SetUtils } from '../../../../mol-util/set';
|
||||
|
||||
const DefaultColor = Color(0x909090);
|
||||
|
||||
const NoIssuesColor = Color(0x2166ac);
|
||||
const OneIssueColor = Color(0xfee08b);
|
||||
const TwoIssuesColor = Color(0xf46d43);
|
||||
const ThreeOrMoreIssuesColor = Color(0xa50026);
|
||||
|
||||
const ColorLegend = TableLegend([
|
||||
['Data unavailable', DefaultColor],
|
||||
['No issues', NoIssuesColor],
|
||||
['One issue', OneIssueColor],
|
||||
['Two issues', TwoIssuesColor],
|
||||
['Three or more issues', ThreeOrMoreIssuesColor],
|
||||
]);
|
||||
|
||||
export function getGeometricQualityColorThemeParams(ctx: ThemeDataContext) {
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]).value;
|
||||
const options: [string, string][] = [];
|
||||
if (validationReport) {
|
||||
const kinds = new Set<string>();
|
||||
validationReport.geometryIssues.forEach(v => v.forEach(k => kinds.add(k)));
|
||||
kinds.forEach(k => options.push([k, k]));
|
||||
}
|
||||
return {
|
||||
ignore: PD.MultiSelect([] as string[], options)
|
||||
};
|
||||
}
|
||||
export type GeometricQualityColorThemeParams = ReturnType<typeof getGeometricQualityColorThemeParams>
|
||||
|
||||
export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Values<GeometricQualityColorThemeParams>): ColorTheme<GeometricQualityColorThemeParams> {
|
||||
let color: LocationColor = () => DefaultColor;
|
||||
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]);
|
||||
const contextHash = validationReport?.version;
|
||||
|
||||
const value = validationReport?.value;
|
||||
const model = ctx.structure?.models[0];
|
||||
|
||||
if (value && model) {
|
||||
const { geometryIssues, clashes, bondOutliers, angleOutliers } = value;
|
||||
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
|
||||
const { polymerType } = model.atomicHierarchy.derived.residue;
|
||||
const ignore = new Set(props.ignore);
|
||||
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location) && location.unit.model === model) {
|
||||
const { element } = location;
|
||||
const rI = residueIndex[element];
|
||||
|
||||
const value = geometryIssues.get(rI);
|
||||
if (value === undefined) return DefaultColor;
|
||||
|
||||
let count = SetUtils.differenceSize(value, ignore);
|
||||
|
||||
if (count > 0 && polymerType[rI] === PolymerType.NA) {
|
||||
count = 0;
|
||||
if (!ignore.has('clash') && clashes.getVertexEdgeCount(element) > 0) count += 1;
|
||||
if (!ignore.has('mog-bond-outlier') && bondOutliers.index.has(element)) count += 1;
|
||||
if (!ignore.has('mog-angle-outlier') && angleOutliers.index.has(element)) count += 1;
|
||||
}
|
||||
|
||||
switch (count) {
|
||||
case undefined: return DefaultColor;
|
||||
case 0: return NoIssuesColor;
|
||||
case 1: return OneIssueColor;
|
||||
case 2: return TwoIssuesColor;
|
||||
default: return ThreeOrMoreIssuesColor;
|
||||
}
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
factory: GeometryQualityColorTheme,
|
||||
granularity: 'group',
|
||||
color,
|
||||
props,
|
||||
contextHash,
|
||||
description: 'Assigns residue colors according to the number of (filtered) geometry issues. Data from wwPDB Validation Report, obtained via RCSB PDB.',
|
||||
legend: ColorLegend
|
||||
};
|
||||
}
|
||||
|
||||
export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQualityColorThemeParams, ValidationReport.Tag.GeometryQuality> = {
|
||||
name: ValidationReport.Tag.GeometryQuality,
|
||||
label: 'Geometry Quality',
|
||||
category: ColorTheme.Category.Validation,
|
||||
factory: GeometryQualityColorTheme,
|
||||
getParams: getGeometricQualityColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(getGeometricQualityColorThemeParams({})),
|
||||
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ThemeDataContext } from '../../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Color, ColorScale } from '../../../../mol-util/color';
|
||||
import { StructureElement, Model } from '../../../../mol-model/structure';
|
||||
import { Location } from '../../../../mol-model/location';
|
||||
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
|
||||
import { ValidationReportProvider, ValidationReport } from '../prop';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
|
||||
export function RandomCoilIndexColorTheme(ctx: ThemeDataContext, props: {}): ColorTheme<{}> {
|
||||
let color: LocationColor = () => DefaultColor;
|
||||
const scale = ColorScale.create({
|
||||
reverse: true,
|
||||
domain: [0, 0.6],
|
||||
listOrName: 'red-yellow-blue',
|
||||
});
|
||||
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]);
|
||||
const contextHash = validationReport?.version;
|
||||
|
||||
const rci = validationReport?.value?.rci;
|
||||
const model = ctx.structure?.models[0];
|
||||
|
||||
if (rci && model) {
|
||||
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location) && location.unit.model === model) {
|
||||
const value = rci.get(residueIndex[location.element]);
|
||||
return value === undefined ? DefaultColor : scale.color(value);
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
factory: RandomCoilIndexColorTheme,
|
||||
granularity: 'group',
|
||||
color,
|
||||
props,
|
||||
contextHash,
|
||||
description: 'Assigns residue colors according to the Random Coil Index value. Data from wwPDB Validation Report, obtained via RCSB PDB.',
|
||||
legend: scale.legend
|
||||
};
|
||||
}
|
||||
|
||||
export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}, ValidationReport.Tag.RandomCoilIndex> = {
|
||||
name: ValidationReport.Tag.RandomCoilIndex,
|
||||
label: 'Random Coil Index',
|
||||
category: ColorTheme.Category.Validation,
|
||||
factory: RandomCoilIndexColorTheme,
|
||||
getParams: () => ({}),
|
||||
defaultValues: PD.getDefaultValues({}),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.isFromNmr(ctx.structure.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
}
|
||||
};
|
||||
@@ -4,23 +4,25 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition'
|
||||
import { CustomPropertyDescriptor, Structure, Unit } from '../../mol-model/structure';
|
||||
import { CustomProperty } from '../common/custom-property';
|
||||
import { CustomModelProperty } from '../common/custom-model-property';
|
||||
import { Model, ElementIndex, ResidueIndex } from '../../mol-model/structure/model';
|
||||
import { IntAdjacencyGraph } from '../../mol-math/graph';
|
||||
import { readFromFile } from '../../mol-util/data-source';
|
||||
import { CustomStructureProperty } from '../common/custom-structure-property';
|
||||
import { InterUnitGraph } from '../../mol-math/graph/inter-unit-graph';
|
||||
import { UnitIndex } from '../../mol-model/structure/structure/element/element';
|
||||
import { IntMap, SortedArray } from '../../mol-data/int';
|
||||
import { arrayMax } from '../../mol-util/array';
|
||||
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { CustomPropertyDescriptor, Structure, Unit } from '../../../mol-model/structure';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
|
||||
import { Model, ElementIndex, ResidueIndex } from '../../../mol-model/structure/model';
|
||||
import { IntAdjacencyGraph } from '../../../mol-math/graph';
|
||||
import { CustomStructureProperty } from '../../../mol-model-props/common/custom-structure-property';
|
||||
import { InterUnitGraph } from '../../../mol-math/graph/inter-unit-graph';
|
||||
import { UnitIndex } from '../../../mol-model/structure/structure/element/element';
|
||||
import { IntMap, SortedArray } from '../../../mol-data/int';
|
||||
import { arrayMax } from '../../../mol-util/array';
|
||||
import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
|
||||
import { CustomPropSymbol } from '../../../mol-script/language/symbol';
|
||||
import Type from '../../../mol-script/language/type';
|
||||
import { Asset } from '../../../mol-util/assets';
|
||||
|
||||
export { ValidationReport }
|
||||
export { ValidationReport };
|
||||
|
||||
interface ValidationReport {
|
||||
/**
|
||||
@@ -82,52 +84,67 @@ namespace ValidationReport {
|
||||
Clashes = 'rcsb-clashes',
|
||||
}
|
||||
|
||||
export const DefaultBaseUrl = '//ftp.rcsb.org/pub/pdb/validation_reports'
|
||||
export const DefaultBaseUrl = '//ftp.rcsb.org/pub/pdb/validation_reports';
|
||||
export function getEntryUrl(pdbId: string, baseUrl: string) {
|
||||
const id = pdbId.toLowerCase()
|
||||
return `${baseUrl}/${id.substr(1, 2)}/${id}/${id}_validation.xml.gz`
|
||||
const id = pdbId.toLowerCase();
|
||||
return `${baseUrl}/${id.substr(1, 2)}/${id}/${id}_validation.xml.gz`;
|
||||
}
|
||||
|
||||
export function isApplicable(model?: Model): boolean {
|
||||
return (
|
||||
!!model &&
|
||||
MmcifFormat.is(model.sourceData) &&
|
||||
(model.sourceData.data.db.database_2.database_id.isDefined ||
|
||||
model.entryId.length === 4)
|
||||
)
|
||||
return !!model && Model.isFromPdbArchive(model);
|
||||
}
|
||||
|
||||
export function fromXml(xml: XMLDocument, model: Model): ValidationReport {
|
||||
return parseValidationReportXml(xml, model)
|
||||
return parseValidationReportXml(xml, model);
|
||||
}
|
||||
|
||||
export async function fetch(ctx: CustomProperty.Context, model: Model, props: ServerSourceProps): Promise<ValidationReport> {
|
||||
const url = getEntryUrl(model.entryId, props.baseUrl)
|
||||
const xml = await ctx.fetch({ url, type: 'xml' }).runInContext(ctx.runtime)
|
||||
return fromXml(xml, model)
|
||||
export async function fetch(ctx: CustomProperty.Context, model: Model, props: ServerSourceProps): Promise<CustomProperty.Data<ValidationReport>> {
|
||||
const url = Asset.getUrlAsset(ctx.assetManager, getEntryUrl(model.entryId, props.baseUrl));
|
||||
const xml = await ctx.assetManager.resolve(url, 'xml').runInContext(ctx.runtime);
|
||||
return { value: fromXml(xml.data, model), assets: [xml] };
|
||||
}
|
||||
|
||||
export async function open(ctx: CustomProperty.Context, model: Model, props: FileSourceProps): Promise<ValidationReport> {
|
||||
const xml = await readFromFile(props.input, 'xml').runInContext(ctx.runtime)
|
||||
return fromXml(xml, model)
|
||||
export async function open(ctx: CustomProperty.Context, model: Model, props: FileSourceProps): Promise<CustomProperty.Data<ValidationReport>> {
|
||||
if (props.input === null) throw new Error('No file given');
|
||||
const xml = await ctx.assetManager.resolve(props.input, 'xml').runInContext(ctx.runtime);
|
||||
return { value: fromXml(xml.data, model), assets: [xml] };
|
||||
}
|
||||
|
||||
export async function obtain(ctx: CustomProperty.Context, model: Model, props: ValidationReportProps): Promise<ValidationReport> {
|
||||
export async function obtain(ctx: CustomProperty.Context, model: Model, props: ValidationReportProps): Promise<CustomProperty.Data<ValidationReport>> {
|
||||
switch(props.source.name) {
|
||||
case 'file': return open(ctx, model, props.source.params)
|
||||
case 'server': return fetch(ctx, model, props.source.params)
|
||||
case 'file': return open(ctx, model, props.source.params);
|
||||
case 'server': return fetch(ctx, model, props.source.params);
|
||||
}
|
||||
}
|
||||
|
||||
export const symbols = {
|
||||
hasClash: QuerySymbolRuntime.Dynamic(CustomPropSymbol('rcsb', 'validation-report.has-clash', Type.Bool),
|
||||
ctx => {
|
||||
const { unit, element } = ctx.element;
|
||||
if (!Unit.isAtomic(unit)) return 0;
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value;
|
||||
return validationReport && validationReport.clashes.getVertexEdgeCount(element) > 0;
|
||||
}
|
||||
),
|
||||
issueCount: QuerySymbolRuntime.Dynamic(CustomPropSymbol('rcsb', 'validation-report.issue-count', Type.Num),
|
||||
ctx => {
|
||||
const { unit, element } = ctx.element;
|
||||
if (!Unit.isAtomic(unit)) return 0;
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value;
|
||||
return validationReport?.geometryIssues.get(unit.residueIndex[element])?.size || 0;
|
||||
}
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const FileSourceParams = {
|
||||
input: PD.File({ accept: '.xml,.gz,.zip' })
|
||||
}
|
||||
};
|
||||
type FileSourceProps = PD.Values<typeof FileSourceParams>
|
||||
|
||||
const ServerSourceParams = {
|
||||
baseUrl: PD.Text(ValidationReport.DefaultBaseUrl, { description: 'Base URL to directory tree' })
|
||||
}
|
||||
};
|
||||
type ServerSourceProps = PD.Values<typeof ServerSourceParams>
|
||||
|
||||
export const ValidationReportParams = {
|
||||
@@ -135,25 +152,25 @@ export const ValidationReportParams = {
|
||||
'file': PD.Group(FileSourceParams, { label: 'File', isFlat: true }),
|
||||
'server': PD.Group(ServerSourceParams, { label: 'Server', isFlat: true }),
|
||||
}, { options: [['file', 'File'], ['server', 'Server']] })
|
||||
}
|
||||
};
|
||||
export type ValidationReportParams = typeof ValidationReportParams
|
||||
export type ValidationReportProps = PD.Values<ValidationReportParams>
|
||||
|
||||
export const ValidationReportProvider: CustomModelProperty.Provider<ValidationReportParams, ValidationReport> = CustomModelProperty.createProvider({
|
||||
label: 'RCSB Validation Report',
|
||||
label: 'Validation Report',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'rcsb_validation_report',
|
||||
// TODO `cifExport` and `symbol`
|
||||
symbols: ValidationReport.symbols
|
||||
}),
|
||||
type: 'dynamic',
|
||||
defaultParams: ValidationReportParams,
|
||||
getParams: (data: Model) => ValidationReportParams,
|
||||
isApplicable: (data: Model) => ValidationReport.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<ValidationReportProps>) => {
|
||||
const p = { ...PD.getDefaultValues(ValidationReportParams), ...props }
|
||||
return await ValidationReport.obtain(ctx, data, p)
|
||||
const p = { ...PD.getDefaultValues(ValidationReportParams), ...props };
|
||||
return await ValidationReport.obtain(ctx, data, p);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
@@ -177,26 +194,25 @@ export interface Clashes {
|
||||
}
|
||||
|
||||
function createInterUnitClashes(structure: Structure, clashes: ValidationReport['clashes']) {
|
||||
const builder = new InterUnitGraph.Builder<Unit.Atomic, UnitIndex, InterUnitClashesProps>()
|
||||
const { a, b, edgeProps: { id, magnitude, distance } } = clashes
|
||||
const builder = new InterUnitGraph.Builder<Unit.Atomic, UnitIndex, InterUnitClashesProps>();
|
||||
const { a, b, edgeProps: { id, magnitude, distance } } = clashes;
|
||||
|
||||
const pA = Vec3()
|
||||
const pB = Vec3()
|
||||
const pA = Vec3(), pB = Vec3();
|
||||
|
||||
Structure.eachUnitPair(structure, (unitA: Unit, unitB: Unit) => {
|
||||
const elementsA = unitA.elements
|
||||
const elementsB = unitB.elements
|
||||
const elementsA = unitA.elements;
|
||||
const elementsB = unitB.elements;
|
||||
|
||||
builder.startUnitPair(unitA as Unit.Atomic, unitB as Unit.Atomic)
|
||||
builder.startUnitPair(unitA as Unit.Atomic, unitB as Unit.Atomic);
|
||||
|
||||
for (let i = 0, il = clashes.edgeCount * 2; i < il; ++i) {
|
||||
// TODO create lookup
|
||||
let indexA = SortedArray.indexOf(elementsA, a[i])
|
||||
let indexB = SortedArray.indexOf(elementsB, b[i])
|
||||
let indexA = SortedArray.indexOf(elementsA, a[i]);
|
||||
let indexB = SortedArray.indexOf(elementsB, b[i]);
|
||||
|
||||
if (indexA !== -1 && indexB !== -1) {
|
||||
unitA.conformation.position(a[i], pA)
|
||||
unitB.conformation.position(b[i], pB)
|
||||
unitA.conformation.position(a[i], pA);
|
||||
unitB.conformation.position(b[i], pB);
|
||||
|
||||
// check actual distance to avoid clashes between unrelated chain instances
|
||||
if (equalEps(distance[i], Vec3.distance(pA, pB), 0.1)) {
|
||||
@@ -204,76 +220,83 @@ function createInterUnitClashes(structure: Structure, clashes: ValidationReport[
|
||||
id: id[i],
|
||||
magnitude: magnitude[i],
|
||||
distance: distance[i],
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.finishUnitPair()
|
||||
builder.finishUnitPair();
|
||||
}, {
|
||||
maxRadius: arrayMax(clashes.edgeProps.distance),
|
||||
validUnit: (unit: Unit) => Unit.isAtomic(unit),
|
||||
validUnitPair: (unitA: Unit, unitB: Unit) => unitA.model === unitB.model
|
||||
})
|
||||
});
|
||||
|
||||
return new InterUnitGraph(builder.getMap())
|
||||
return new InterUnitGraph(builder.getMap());
|
||||
}
|
||||
|
||||
function createIntraUnitClashes(unit: Unit.Atomic, clashes: ValidationReport['clashes']): IntraUnitClashes {
|
||||
const aIndices: UnitIndex[] = []
|
||||
const bIndices: UnitIndex[] = []
|
||||
const ids: number[] = []
|
||||
const magnitudes: number[] = []
|
||||
const distances: number[] = []
|
||||
const aIndices: UnitIndex[] = [];
|
||||
const bIndices: UnitIndex[] = [];
|
||||
const ids: number[] = [];
|
||||
const magnitudes: number[] = [];
|
||||
const distances: number[] = [];
|
||||
|
||||
const { elements } = unit
|
||||
const { a, b, edgeCount, edgeProps } = clashes
|
||||
const pA = Vec3(), pB = Vec3();
|
||||
|
||||
const { elements } = unit;
|
||||
const { a, b, edgeCount, edgeProps } = clashes;
|
||||
|
||||
for (let i = 0, il = edgeCount * 2; i < il; ++i) {
|
||||
// TODO create lookup
|
||||
let indexA = SortedArray.indexOf(elements, a[i])
|
||||
let indexB = SortedArray.indexOf(elements, b[i])
|
||||
let indexA = SortedArray.indexOf(elements, a[i]);
|
||||
let indexB = SortedArray.indexOf(elements, b[i]);
|
||||
|
||||
if (indexA !== -1 && indexB !== -1) {
|
||||
aIndices.push(indexA as UnitIndex)
|
||||
bIndices.push(indexB as UnitIndex)
|
||||
ids.push(edgeProps.id[i])
|
||||
magnitudes.push(edgeProps.magnitude[i])
|
||||
distances.push(edgeProps.distance[i])
|
||||
unit.conformation.position(a[i], pA);
|
||||
unit.conformation.position(b[i], pB);
|
||||
|
||||
// check actual distance to avoid clashes between unrelated chain instances
|
||||
if (equalEps(edgeProps.distance[i], Vec3.distance(pA, pB), 0.1)) {
|
||||
aIndices.push(indexA as UnitIndex);
|
||||
bIndices.push(indexB as UnitIndex);
|
||||
ids.push(edgeProps.id[i]);
|
||||
magnitudes.push(edgeProps.magnitude[i]);
|
||||
distances.push(edgeProps.distance[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const builder = new IntAdjacencyGraph.EdgeBuilder(elements.length, aIndices, bIndices)
|
||||
const id = new Int32Array(builder.slotCount)
|
||||
const magnitude = new Float32Array(builder.slotCount)
|
||||
const distance = new Float32Array(builder.slotCount)
|
||||
const builder = new IntAdjacencyGraph.EdgeBuilder(elements.length, aIndices, bIndices);
|
||||
const id = new Int32Array(builder.slotCount);
|
||||
const magnitude = new Float32Array(builder.slotCount);
|
||||
const distance = new Float32Array(builder.slotCount);
|
||||
for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
|
||||
builder.addNextEdge()
|
||||
builder.assignProperty(id, ids[i])
|
||||
builder.assignProperty(magnitude, magnitudes[i])
|
||||
builder.assignProperty(distance, distances[i])
|
||||
builder.addNextEdge();
|
||||
builder.assignProperty(id, ids[i]);
|
||||
builder.assignProperty(magnitude, magnitudes[i]);
|
||||
builder.assignProperty(distance, distances[i]);
|
||||
}
|
||||
return builder.createGraph({ id, magnitude, distance })
|
||||
return builder.createGraph({ id, magnitude, distance });
|
||||
}
|
||||
|
||||
function createClashes(structure: Structure, clashes: ValidationReport['clashes']): Clashes {
|
||||
|
||||
const intraUnit = IntMap.Mutable<IntraUnitClashes>()
|
||||
const intraUnit = IntMap.Mutable<IntraUnitClashes>();
|
||||
|
||||
for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
|
||||
const group = structure.unitSymmetryGroups[i]
|
||||
if (!Unit.isAtomic(group.units[0])) continue
|
||||
const group = structure.unitSymmetryGroups[i];
|
||||
if (!Unit.isAtomic(group.units[0])) continue;
|
||||
|
||||
const intraClashes = createIntraUnitClashes(group.units[0], clashes)
|
||||
const intraClashes = createIntraUnitClashes(group.units[0], clashes);
|
||||
for (let j = 0, jl = group.units.length; j < jl; ++j) {
|
||||
intraUnit.set(group.units[j].id, intraClashes)
|
||||
intraUnit.set(group.units[j].id, intraClashes);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
interUnit: createInterUnitClashes(structure, clashes),
|
||||
intraUnit
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const ClashesProvider: CustomStructureProperty.Provider<{}, Clashes> = CustomStructureProperty.createProvider({
|
||||
@@ -287,22 +310,24 @@ export const ClashesProvider: CustomStructureProperty.Provider<{}, Clashes> = Cu
|
||||
getParams: (data: Structure) => ({}),
|
||||
isApplicable: (data: Structure) => true,
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure) => {
|
||||
await ValidationReportProvider.attach(ctx, data.models[0])
|
||||
const validationReport = ValidationReportProvider.get(data.models[0]).value!
|
||||
return createClashes(data, validationReport.clashes)
|
||||
await ValidationReportProvider.attach(ctx, data.models[0]);
|
||||
const validationReport = ValidationReportProvider.get(data.models[0]).value!;
|
||||
return {
|
||||
value: createClashes(data, validationReport.clashes)
|
||||
};
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
function getItem(a: NamedNodeMap, name: string) {
|
||||
const item = a.getNamedItem(name)
|
||||
return item !== null ? item.value : ''
|
||||
const item = a.getNamedItem(name);
|
||||
return item !== null ? item.value : '';
|
||||
}
|
||||
|
||||
function hasAttr(a: NamedNodeMap, name: string, value: string) {
|
||||
const item = a.getNamedItem(name)
|
||||
return item !== null && item.value === value
|
||||
const item = a.getNamedItem(name);
|
||||
return item !== null && item.value === value;
|
||||
}
|
||||
|
||||
function getMogInfo(a: NamedNodeMap) {
|
||||
@@ -311,7 +336,7 @@ function getMogInfo(a: NamedNodeMap) {
|
||||
obs: parseFloat(getItem(a, 'obsval')),
|
||||
stdev: parseFloat(getItem(a, 'stdev')),
|
||||
z: parseFloat(getItem(a, 'Zscore')),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getMolInfo(a: NamedNodeMap) {
|
||||
@@ -320,229 +345,229 @@ function getMolInfo(a: NamedNodeMap) {
|
||||
obs: parseFloat(getItem(a, 'obs')),
|
||||
stdev: parseFloat(getItem(a, 'stdev')),
|
||||
z: parseInt(getItem(a, 'z')),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function addIndex(index: number, element: ElementIndex, map: Map<ElementIndex, number[]>) {
|
||||
if (map.has(element)) map.get(element)!.push(index)
|
||||
else map.set(element, [index])
|
||||
if (map.has(element)) map.get(element)!.push(index);
|
||||
else map.set(element, [index]);
|
||||
}
|
||||
|
||||
function ClashesBuilder(elementsCount: number) {
|
||||
const aIndices: ElementIndex[] = []
|
||||
const bIndices: ElementIndex[] = []
|
||||
const ids: number[] = []
|
||||
const magnitudes: number[] = []
|
||||
const distances: number[] = []
|
||||
const aIndices: ElementIndex[] = [];
|
||||
const bIndices: ElementIndex[] = [];
|
||||
const ids: number[] = [];
|
||||
const magnitudes: number[] = [];
|
||||
const distances: number[] = [];
|
||||
|
||||
const seen = new Map<string, ElementIndex>()
|
||||
const seen = new Map<string, ElementIndex>();
|
||||
|
||||
return {
|
||||
add(element: ElementIndex, id: number, magnitude: number, distance: number, isSymop: boolean) {
|
||||
const hash = `${id}|${isSymop ? 's' : ''}`
|
||||
const other = seen.get(hash)
|
||||
const hash = `${id}|${isSymop ? 's' : ''}`;
|
||||
const other = seen.get(hash);
|
||||
if (other !== undefined) {
|
||||
aIndices[aIndices.length] = element
|
||||
bIndices[bIndices.length] = other
|
||||
ids[ids.length] = id
|
||||
magnitudes[magnitudes.length] = magnitude
|
||||
distances[distances.length] = distance
|
||||
aIndices[aIndices.length] = element;
|
||||
bIndices[bIndices.length] = other;
|
||||
ids[ids.length] = id;
|
||||
magnitudes[magnitudes.length] = magnitude;
|
||||
distances[distances.length] = distance;
|
||||
} else {
|
||||
seen.set(hash, element)
|
||||
seen.set(hash, element);
|
||||
}
|
||||
},
|
||||
get() {
|
||||
const builder = new IntAdjacencyGraph.EdgeBuilder(elementsCount, aIndices, bIndices)
|
||||
const id = new Int32Array(builder.slotCount)
|
||||
const magnitude = new Float32Array(builder.slotCount)
|
||||
const distance = new Float32Array(builder.slotCount)
|
||||
const builder = new IntAdjacencyGraph.EdgeBuilder(elementsCount, aIndices, bIndices);
|
||||
const id = new Int32Array(builder.slotCount);
|
||||
const magnitude = new Float32Array(builder.slotCount);
|
||||
const distance = new Float32Array(builder.slotCount);
|
||||
for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
|
||||
builder.addNextEdge()
|
||||
builder.assignProperty(id, ids[i])
|
||||
builder.assignProperty(magnitude, magnitudes[i])
|
||||
builder.assignProperty(distance, distances[i])
|
||||
builder.addNextEdge();
|
||||
builder.assignProperty(id, ids[i]);
|
||||
builder.assignProperty(magnitude, magnitudes[i]);
|
||||
builder.assignProperty(distance, distances[i]);
|
||||
}
|
||||
return builder.createGraph({ id, magnitude, distance })
|
||||
return builder.createGraph({ id, magnitude, distance });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function parseValidationReportXml(xml: XMLDocument, model: Model): ValidationReport {
|
||||
const rsrz = new Map<ResidueIndex, number>()
|
||||
const rscc = new Map<ResidueIndex, number>()
|
||||
const rci = new Map<ResidueIndex, number>()
|
||||
const geometryIssues = new Map<ResidueIndex, Set<string>>()
|
||||
const rsrz = new Map<ResidueIndex, number>();
|
||||
const rscc = new Map<ResidueIndex, number>();
|
||||
const rci = new Map<ResidueIndex, number>();
|
||||
const geometryIssues = new Map<ResidueIndex, Set<string>>();
|
||||
|
||||
const bondOutliers = {
|
||||
index: new Map<ElementIndex, number[]>(),
|
||||
data: [] as ValidationReport['bondOutliers']['data']
|
||||
}
|
||||
};
|
||||
const angleOutliers = {
|
||||
index: new Map<ElementIndex, number[]>(),
|
||||
data: [] as ValidationReport['angleOutliers']['data']
|
||||
}
|
||||
};
|
||||
|
||||
const clashesBuilder = ClashesBuilder(model.atomicHierarchy.atoms._rowCount)
|
||||
const clashesBuilder = ClashesBuilder(model.atomicHierarchy.atoms._rowCount);
|
||||
|
||||
const { index } = model.atomicHierarchy
|
||||
const { index } = model.atomicHierarchy;
|
||||
|
||||
const entries = xml.getElementsByTagName('Entry')
|
||||
const entries = xml.getElementsByTagName('Entry');
|
||||
if (entries.length === 1) {
|
||||
const chemicalShiftLists = entries[0].getElementsByTagName('chemical_shift_list')
|
||||
const chemicalShiftLists = entries[0].getElementsByTagName('chemical_shift_list');
|
||||
if (chemicalShiftLists.length === 1) {
|
||||
const randomCoilIndices = chemicalShiftLists[0].getElementsByTagName('random_coil_index')
|
||||
const randomCoilIndices = chemicalShiftLists[0].getElementsByTagName('random_coil_index');
|
||||
for (let j = 0, jl = randomCoilIndices.length; j < jl; ++j) {
|
||||
const { attributes } = randomCoilIndices[j]
|
||||
const value = parseFloat(getItem(attributes, 'value'))
|
||||
const auth_asym_id = getItem(attributes, 'chain')
|
||||
const auth_comp_id = getItem(attributes, 'rescode')
|
||||
const auth_seq_id = parseInt(getItem(attributes, 'resnum'))
|
||||
const rI = index.findResidueAuth({ auth_asym_id, auth_comp_id, auth_seq_id })
|
||||
if (rI !== -1) rci.set(rI, value)
|
||||
const { attributes } = randomCoilIndices[j];
|
||||
const value = parseFloat(getItem(attributes, 'value'));
|
||||
const auth_asym_id = getItem(attributes, 'chain');
|
||||
const auth_comp_id = getItem(attributes, 'rescode');
|
||||
const auth_seq_id = parseInt(getItem(attributes, 'resnum'));
|
||||
const rI = index.findResidueAuth({ auth_asym_id, auth_comp_id, auth_seq_id });
|
||||
if (rI !== -1) rci.set(rI, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const groups = xml.getElementsByTagName('ModelledSubgroup')
|
||||
const groups = xml.getElementsByTagName('ModelledSubgroup');
|
||||
for (let i = 0, il = groups.length; i < il; ++i) {
|
||||
const g = groups[ i ]
|
||||
const ga = g.attributes
|
||||
const g = groups[ i ];
|
||||
const ga = g.attributes;
|
||||
|
||||
const pdbx_PDB_model_num = parseInt(getItem(ga, 'model'))
|
||||
if (model.modelNum !== pdbx_PDB_model_num) continue
|
||||
const pdbx_PDB_model_num = parseInt(getItem(ga, 'model'));
|
||||
if (model.modelNum !== pdbx_PDB_model_num) continue;
|
||||
|
||||
const auth_asym_id = getItem(ga, 'chain')
|
||||
const auth_comp_id = getItem(ga, 'resname')
|
||||
const auth_seq_id = parseInt(getItem(ga, 'resnum'))
|
||||
const pdbx_PDB_ins_code = getItem(ga, 'icode').trim() || undefined
|
||||
const label_alt_id = getItem(ga, 'altcode').trim() || undefined
|
||||
const auth_asym_id = getItem(ga, 'chain');
|
||||
const auth_comp_id = getItem(ga, 'resname');
|
||||
const auth_seq_id = parseInt(getItem(ga, 'resnum'));
|
||||
const pdbx_PDB_ins_code = getItem(ga, 'icode').trim() || undefined;
|
||||
const label_alt_id = getItem(ga, 'altcode').trim() || undefined;
|
||||
|
||||
const rI = index.findResidueAuth({ auth_asym_id, auth_comp_id, auth_seq_id, pdbx_PDB_ins_code })
|
||||
const rI = index.findResidueAuth({ auth_asym_id, auth_comp_id, auth_seq_id, pdbx_PDB_ins_code });
|
||||
|
||||
// continue if no residue index is found
|
||||
if (rI === -1) continue
|
||||
if (rI === -1) continue;
|
||||
|
||||
if (ga.getNamedItem('rsrz') !== null) rsrz.set(rI, parseFloat(getItem(ga, 'rsrz')))
|
||||
if (ga.getNamedItem('rscc') !== null) rscc.set(rI, parseFloat(getItem(ga, 'rscc')))
|
||||
if (ga.getNamedItem('rsrz') !== null) rsrz.set(rI, parseFloat(getItem(ga, 'rsrz')));
|
||||
if (ga.getNamedItem('rscc') !== null) rscc.set(rI, parseFloat(getItem(ga, 'rscc')));
|
||||
|
||||
const isPolymer = getItem(ga, 'seq') !== '.'
|
||||
const issues = new Set<string>()
|
||||
const isPolymer = getItem(ga, 'seq') !== '.';
|
||||
const issues = new Set<string>();
|
||||
|
||||
if (isPolymer) {
|
||||
const molBondOutliers = g.getElementsByTagName('bond-outlier')
|
||||
if (molBondOutliers.length) issues.add('bond-outlier')
|
||||
const molBondOutliers = g.getElementsByTagName('bond-outlier');
|
||||
if (molBondOutliers.length) issues.add('bond-outlier');
|
||||
|
||||
for (let j = 0, jl = molBondOutliers.length; j < jl; ++j) {
|
||||
const bo = molBondOutliers[j].attributes
|
||||
const idx = bondOutliers.data.length
|
||||
const atomA = index.findAtomOnResidue(rI, getItem(bo, 'atom0'))
|
||||
const atomB = index.findAtomOnResidue(rI, getItem(bo, 'atom1'))
|
||||
addIndex(idx, atomA, bondOutliers.index)
|
||||
addIndex(idx, atomB, bondOutliers.index)
|
||||
const bo = molBondOutliers[j].attributes;
|
||||
const idx = bondOutliers.data.length;
|
||||
const atomA = index.findAtomOnResidue(rI, getItem(bo, 'atom0'));
|
||||
const atomB = index.findAtomOnResidue(rI, getItem(bo, 'atom1'));
|
||||
addIndex(idx, atomA, bondOutliers.index);
|
||||
addIndex(idx, atomB, bondOutliers.index);
|
||||
bondOutliers.data.push({
|
||||
tag: 'bond-outlier', atomA, atomB, ...getMolInfo(bo)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const molAngleOutliers = g.getElementsByTagName('angle-outlier')
|
||||
if (molAngleOutliers.length) issues.add('angle-outlier')
|
||||
const molAngleOutliers = g.getElementsByTagName('angle-outlier');
|
||||
if (molAngleOutliers.length) issues.add('angle-outlier');
|
||||
|
||||
for (let j = 0, jl = molAngleOutliers.length; j < jl; ++j) {
|
||||
const ao = molAngleOutliers[j].attributes
|
||||
const idx = bondOutliers.data.length
|
||||
const atomA = index.findAtomOnResidue(rI, getItem(ao, 'atom0'))
|
||||
const atomB = index.findAtomOnResidue(rI, getItem(ao, 'atom1'))
|
||||
const atomC = index.findAtomOnResidue(rI, getItem(ao, 'atom2'))
|
||||
addIndex(idx, atomA, angleOutliers.index)
|
||||
addIndex(idx, atomB, angleOutliers.index)
|
||||
addIndex(idx, atomC, angleOutliers.index)
|
||||
const ao = molAngleOutliers[j].attributes;
|
||||
const idx = bondOutliers.data.length;
|
||||
const atomA = index.findAtomOnResidue(rI, getItem(ao, 'atom0'));
|
||||
const atomB = index.findAtomOnResidue(rI, getItem(ao, 'atom1'));
|
||||
const atomC = index.findAtomOnResidue(rI, getItem(ao, 'atom2'));
|
||||
addIndex(idx, atomA, angleOutliers.index);
|
||||
addIndex(idx, atomB, angleOutliers.index);
|
||||
addIndex(idx, atomC, angleOutliers.index);
|
||||
angleOutliers.data.push({
|
||||
tag: 'angle-outlier', atomA, atomB, atomC, ...getMolInfo(ao)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const planeOutliers = g.getElementsByTagName('plane-outlier')
|
||||
if (planeOutliers.length) issues.add('plane-outlier')
|
||||
const planeOutliers = g.getElementsByTagName('plane-outlier');
|
||||
if (planeOutliers.length) issues.add('plane-outlier');
|
||||
|
||||
if (hasAttr(ga, 'rota', 'OUTLIER')) issues.add('rotamer-outlier')
|
||||
if (hasAttr(ga, 'rama', 'OUTLIER')) issues.add('ramachandran-outlier')
|
||||
if (hasAttr(ga, 'RNApucker', 'outlier')) issues.add('RNApucker-outlier')
|
||||
if (hasAttr(ga, 'rota', 'OUTLIER')) issues.add('rotamer-outlier');
|
||||
if (hasAttr(ga, 'rama', 'OUTLIER')) issues.add('ramachandran-outlier');
|
||||
if (hasAttr(ga, 'RNApucker', 'outlier')) issues.add('RNApucker-outlier');
|
||||
} else {
|
||||
const mogBondOutliers = g.getElementsByTagName('mog-bond-outlier')
|
||||
if (mogBondOutliers.length) issues.add('mog-bond-outlier')
|
||||
const mogBondOutliers = g.getElementsByTagName('mog-bond-outlier');
|
||||
if (mogBondOutliers.length) issues.add('mog-bond-outlier');
|
||||
|
||||
for (let j = 0, jl = mogBondOutliers.length; j < jl; ++j) {
|
||||
const mbo = mogBondOutliers[j].attributes
|
||||
const atoms = getItem(mbo, 'atoms').split(',')
|
||||
const idx = bondOutliers.data.length
|
||||
const atomA = index.findAtomOnResidue(rI, atoms[0])
|
||||
const atomB = index.findAtomOnResidue(rI, atoms[1])
|
||||
addIndex(idx, atomA, bondOutliers.index)
|
||||
addIndex(idx, atomB, bondOutliers.index)
|
||||
const mbo = mogBondOutliers[j].attributes;
|
||||
const atoms = getItem(mbo, 'atoms').split(',');
|
||||
const idx = bondOutliers.data.length;
|
||||
const atomA = index.findAtomOnResidue(rI, atoms[0]);
|
||||
const atomB = index.findAtomOnResidue(rI, atoms[1]);
|
||||
addIndex(idx, atomA, bondOutliers.index);
|
||||
addIndex(idx, atomB, bondOutliers.index);
|
||||
bondOutliers.data.push({
|
||||
tag: 'mog-bond-outlier', atomA, atomB, ...getMogInfo(mbo)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const mogAngleOutliers = g.getElementsByTagName('mog-angle-outlier')
|
||||
if (mogAngleOutliers.length) issues.add('mog-angle-outlier')
|
||||
const mogAngleOutliers = g.getElementsByTagName('mog-angle-outlier');
|
||||
if (mogAngleOutliers.length) issues.add('mog-angle-outlier');
|
||||
|
||||
for (let j = 0, jl = mogAngleOutliers.length; j < jl; ++j) {
|
||||
const mao = mogAngleOutliers[j].attributes
|
||||
const atoms = getItem(mao, 'atoms').split(',')
|
||||
const idx = angleOutliers.data.length
|
||||
const atomA = index.findAtomOnResidue(rI, atoms[0])
|
||||
const atomB = index.findAtomOnResidue(rI, atoms[1])
|
||||
const atomC = index.findAtomOnResidue(rI, atoms[2])
|
||||
addIndex(idx, atomA, angleOutliers.index)
|
||||
addIndex(idx, atomB, angleOutliers.index)
|
||||
addIndex(idx, atomC, angleOutliers.index)
|
||||
const mao = mogAngleOutliers[j].attributes;
|
||||
const atoms = getItem(mao, 'atoms').split(',');
|
||||
const idx = angleOutliers.data.length;
|
||||
const atomA = index.findAtomOnResidue(rI, atoms[0]);
|
||||
const atomB = index.findAtomOnResidue(rI, atoms[1]);
|
||||
const atomC = index.findAtomOnResidue(rI, atoms[2]);
|
||||
addIndex(idx, atomA, angleOutliers.index);
|
||||
addIndex(idx, atomB, angleOutliers.index);
|
||||
addIndex(idx, atomC, angleOutliers.index);
|
||||
angleOutliers.data.push({
|
||||
tag: 'mog-angle-outlier', atomA, atomB, atomC, ...getMogInfo(mao)
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const clashes = g.getElementsByTagName('clash')
|
||||
if (clashes.length) issues.add('clash')
|
||||
const clashes = g.getElementsByTagName('clash');
|
||||
if (clashes.length) issues.add('clash');
|
||||
|
||||
for (let j = 0, jl = clashes.length; j < jl; ++j) {
|
||||
const ca = clashes[j].attributes
|
||||
const id = parseInt(getItem(ca, 'cid'))
|
||||
const magnitude = parseFloat(getItem(ca, 'clashmag'))
|
||||
const distance = parseFloat(getItem(ca, 'dist'))
|
||||
const label_atom_id = getItem(ca, 'atom')
|
||||
const element = index.findAtomOnResidue(rI, label_atom_id, label_alt_id)
|
||||
const ca = clashes[j].attributes;
|
||||
const id = parseInt(getItem(ca, 'cid'));
|
||||
const magnitude = parseFloat(getItem(ca, 'clashmag'));
|
||||
const distance = parseFloat(getItem(ca, 'dist'));
|
||||
const label_atom_id = getItem(ca, 'atom');
|
||||
const element = index.findAtomOnResidue(rI, label_atom_id, label_alt_id);
|
||||
if (element !== -1) {
|
||||
clashesBuilder.add(element, id, magnitude, distance, false)
|
||||
clashesBuilder.add(element, id, magnitude, distance, false);
|
||||
}
|
||||
}
|
||||
|
||||
const symmClashes = g.getElementsByTagName('symm-clash')
|
||||
if (symmClashes.length) issues.add('symm-clash')
|
||||
const symmClashes = g.getElementsByTagName('symm-clash');
|
||||
if (symmClashes.length) issues.add('symm-clash');
|
||||
|
||||
for (let j = 0, jl = symmClashes.length; j < jl; ++j) {
|
||||
const sca = symmClashes[j].attributes
|
||||
const id = parseInt(getItem(sca, 'scid'))
|
||||
const magnitude = parseFloat(getItem(sca, 'clashmag'))
|
||||
const distance = parseFloat(getItem(sca, 'dist'))
|
||||
const label_atom_id = getItem(sca, 'atom')
|
||||
const element = index.findAtomOnResidue(rI, label_atom_id, label_alt_id)
|
||||
const sca = symmClashes[j].attributes;
|
||||
const id = parseInt(getItem(sca, 'scid'));
|
||||
const magnitude = parseFloat(getItem(sca, 'clashmag'));
|
||||
const distance = parseFloat(getItem(sca, 'dist'));
|
||||
const label_atom_id = getItem(sca, 'atom');
|
||||
const element = index.findAtomOnResidue(rI, label_atom_id, label_alt_id);
|
||||
if (element !== -1) {
|
||||
clashesBuilder.add(element, id, magnitude, distance, true)
|
||||
clashesBuilder.add(element, id, magnitude, distance, true);
|
||||
}
|
||||
}
|
||||
|
||||
geometryIssues.set(rI, issues)
|
||||
geometryIssues.set(rI, issues);
|
||||
}
|
||||
|
||||
const clashes = clashesBuilder.get()
|
||||
const clashes = clashesBuilder.get();
|
||||
|
||||
const validationReport = {
|
||||
rsrz, rscc, rci, geometryIssues,
|
||||
bondOutliers, angleOutliers,
|
||||
clashes
|
||||
}
|
||||
};
|
||||
|
||||
return validationReport
|
||||
return validationReport;
|
||||
}
|
||||
@@ -14,47 +14,47 @@ import { EmptyLoci, Loci, DataLoci } from '../../../mol-model/loci';
|
||||
import { Interval } from '../../../mol-data/int';
|
||||
import { RepresentationContext, RepresentationParamsGetter, Representation } from '../../../mol-repr/representation';
|
||||
import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
|
||||
import { UnitKind, UnitKindOptions } from '../../../mol-repr/structure/visual/util/common';
|
||||
import { VisualContext } from '../../../mol-repr/visual';
|
||||
import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from '../../../mol-repr/structure/visual/util/link';
|
||||
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../../../mol-repr/structure/units-visual';
|
||||
import { VisualUpdateState } from '../../../mol-repr/util';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { ClashesProvider, IntraUnitClashes, InterUnitClashes } from '../validation-report';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { ClashesProvider, IntraUnitClashes, InterUnitClashes, ValidationReport } from './prop';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../../../mol-repr/structure/complex-visual';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { MarkerActions } from '../../../mol-util/marker-action';
|
||||
import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { bondLabel } from '../../../mol-theme/label';
|
||||
import { getUnitKindsParam } from '../../../mol-repr/structure/params';
|
||||
|
||||
//
|
||||
|
||||
function createIntraUnitClashCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<IntraUnitClashParams>, mesh?: Mesh) {
|
||||
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
|
||||
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
|
||||
|
||||
const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id)
|
||||
const { edgeCount, a, b, edgeProps } = clashes
|
||||
const { magnitude } = edgeProps
|
||||
const { sizeFactor } = props
|
||||
const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id);
|
||||
const { edgeCount, a, b, edgeProps } = clashes;
|
||||
const { magnitude } = edgeProps;
|
||||
const { sizeFactor } = props;
|
||||
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh)
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh);
|
||||
|
||||
const { elements } = unit
|
||||
const pos = unit.conformation.invariantPosition
|
||||
const { elements } = unit;
|
||||
const pos = unit.conformation.invariantPosition;
|
||||
|
||||
const builderProps = {
|
||||
linkCount: edgeCount * 2,
|
||||
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
|
||||
pos(elements[a[edgeIndex]], posA)
|
||||
pos(elements[b[edgeIndex]], posB)
|
||||
pos(elements[a[edgeIndex]], posA);
|
||||
pos(elements[b[edgeIndex]], posB);
|
||||
},
|
||||
style: (edgeIndex: number) => LinkCylinderStyle.Disk,
|
||||
radius: (edgeIndex: number) => magnitude[edgeIndex] * sizeFactor,
|
||||
}
|
||||
};
|
||||
|
||||
return createLinkCylinderMesh(ctx, builderProps, props, mesh)
|
||||
return createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
}
|
||||
|
||||
export const IntraUnitClashParams = {
|
||||
@@ -62,7 +62,7 @@ export const IntraUnitClashParams = {
|
||||
...LinkCylinderParams,
|
||||
linkCap: PD.Boolean(true),
|
||||
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.01 }),
|
||||
}
|
||||
};
|
||||
export type IntraUnitClashParams = typeof IntraUnitClashParams
|
||||
|
||||
export function IntraUnitClashVisual(materialId: number): UnitsVisual<IntraUnitClashParams> {
|
||||
@@ -79,95 +79,95 @@ export function IntraUnitClashVisual(materialId: number): UnitsVisual<IntraUnitC
|
||||
newProps.linkScale !== currentProps.linkScale ||
|
||||
newProps.linkSpacing !== currentProps.linkSpacing ||
|
||||
newProps.linkCap !== currentProps.linkCap
|
||||
)
|
||||
);
|
||||
}
|
||||
}, materialId)
|
||||
}, materialId);
|
||||
}
|
||||
|
||||
function getIntraClashBoundingSphere(unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[], boundingSphere: Sphere3D) {
|
||||
return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
|
||||
unit.conformation.position(unit.elements[clashes.a[elements[i]]], pA)
|
||||
unit.conformation.position(unit.elements[clashes.b[elements[i]]], pB)
|
||||
}, boundingSphere)
|
||||
unit.conformation.position(unit.elements[clashes.a[elements[i]]], pA);
|
||||
unit.conformation.position(unit.elements[clashes.b[elements[i]]], pB);
|
||||
}, boundingSphere);
|
||||
}
|
||||
|
||||
function getIntraClashLabel(structure: Structure, unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) {
|
||||
const idx = elements[0]
|
||||
if (idx === undefined) return ''
|
||||
const { edgeProps: { id, magnitude, distance } } = clashes
|
||||
const mag = magnitude[idx].toFixed(2)
|
||||
const dist = distance[idx].toFixed(2)
|
||||
const idx = elements[0];
|
||||
if (idx === undefined) return '';
|
||||
const { edgeProps: { id, magnitude, distance } } = clashes;
|
||||
const mag = magnitude[idx].toFixed(2);
|
||||
const dist = distance[idx].toFixed(2);
|
||||
|
||||
return [
|
||||
`Clash id: ${id[idx]} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`,
|
||||
bondLabel(Bond.Location(structure, unit, clashes.a[idx], structure, unit, clashes.b[idx]))
|
||||
].join('</br>')
|
||||
].join('</br>');
|
||||
}
|
||||
|
||||
function IntraClashLoci(structure: Structure, unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) {
|
||||
return DataLoci('intra-clashes', { unit, clashes }, elements,
|
||||
(boundingSphere: Sphere3D) => getIntraClashBoundingSphere(unit, clashes, elements, boundingSphere),
|
||||
() => getIntraClashLabel(structure, unit, clashes, elements))
|
||||
() => getIntraClashLabel(structure, unit, clashes, elements));
|
||||
}
|
||||
|
||||
function getIntraClashLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
|
||||
const { objectId, instanceId, groupId } = pickingId
|
||||
const { objectId, instanceId, groupId } = pickingId;
|
||||
if (id === objectId) {
|
||||
const { structure, group } = structureGroup
|
||||
const unit = group.units[instanceId]
|
||||
const { structure, group } = structureGroup;
|
||||
const unit = group.units[instanceId];
|
||||
if (Unit.isAtomic(unit)) {
|
||||
const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id)
|
||||
return IntraClashLoci(structure, unit, clashes, [groupId])
|
||||
const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id);
|
||||
return IntraClashLoci(structure, unit, clashes, [groupId]);
|
||||
}
|
||||
}
|
||||
return EmptyLoci
|
||||
return EmptyLoci;
|
||||
}
|
||||
|
||||
function eachIntraClash(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
|
||||
let changed = false
|
||||
let changed = false;
|
||||
// TODO
|
||||
return changed
|
||||
return changed;
|
||||
}
|
||||
|
||||
function createIntraClashIterator(structureGroup: StructureGroup): LocationIterator {
|
||||
const { structure, group } = structureGroup
|
||||
const unit = group.units[0]
|
||||
const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id)
|
||||
const { a } = clashes
|
||||
const groupCount = clashes.edgeCount * 2
|
||||
const instanceCount = group.units.length
|
||||
const location = StructureElement.Location.create(structure)
|
||||
const { structure, group } = structureGroup;
|
||||
const unit = group.units[0];
|
||||
const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id);
|
||||
const { a } = clashes;
|
||||
const groupCount = clashes.edgeCount * 2;
|
||||
const instanceCount = group.units.length;
|
||||
const location = StructureElement.Location.create(structure);
|
||||
const getLocation = (groupIndex: number, instanceIndex: number) => {
|
||||
const unit = group.units[instanceIndex]
|
||||
location.unit = unit
|
||||
location.element = unit.elements[a[groupIndex]]
|
||||
return location
|
||||
}
|
||||
return LocationIterator(groupCount, instanceCount, getLocation)
|
||||
const unit = group.units[instanceIndex];
|
||||
location.unit = unit;
|
||||
location.element = unit.elements[a[groupIndex]];
|
||||
return location;
|
||||
};
|
||||
return LocationIterator(groupCount, instanceCount, getLocation);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
function createInterUnitClashCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitClashParams>, mesh?: Mesh) {
|
||||
const clashes = ClashesProvider.get(structure).value!.interUnit
|
||||
const { edges, edgeCount } = clashes
|
||||
const { sizeFactor } = props
|
||||
const clashes = ClashesProvider.get(structure).value!.interUnit;
|
||||
const { edges, edgeCount } = clashes;
|
||||
const { sizeFactor } = props;
|
||||
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh)
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh);
|
||||
|
||||
const builderProps = {
|
||||
linkCount: edgeCount,
|
||||
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
|
||||
const b = edges[edgeIndex]
|
||||
const uA = b.unitA, uB = b.unitB
|
||||
uA.conformation.position(uA.elements[b.indexA], posA)
|
||||
uB.conformation.position(uB.elements[b.indexB], posB)
|
||||
const b = edges[edgeIndex];
|
||||
const uA = b.unitA, uB = b.unitB;
|
||||
uA.conformation.position(uA.elements[b.indexA], posA);
|
||||
uB.conformation.position(uB.elements[b.indexB], posB);
|
||||
},
|
||||
style: (edgeIndex: number) => LinkCylinderStyle.Disk,
|
||||
radius: (edgeIndex: number) => edges[edgeIndex].props.magnitude * sizeFactor
|
||||
}
|
||||
};
|
||||
|
||||
return createLinkCylinderMesh(ctx, builderProps, props, mesh)
|
||||
return createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
}
|
||||
|
||||
export const InterUnitClashParams = {
|
||||
@@ -175,7 +175,7 @@ export const InterUnitClashParams = {
|
||||
...LinkCylinderParams,
|
||||
linkCap: PD.Boolean(true),
|
||||
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.01 }),
|
||||
}
|
||||
};
|
||||
export type InterUnitClashParams = typeof InterUnitClashParams
|
||||
|
||||
export function InterUnitClashVisual(materialId: number): ComplexVisual<InterUnitClashParams> {
|
||||
@@ -192,65 +192,65 @@ export function InterUnitClashVisual(materialId: number): ComplexVisual<InterUni
|
||||
newProps.linkScale !== currentProps.linkScale ||
|
||||
newProps.linkSpacing !== currentProps.linkSpacing ||
|
||||
newProps.linkCap !== currentProps.linkCap
|
||||
)
|
||||
);
|
||||
}
|
||||
}, materialId)
|
||||
}, materialId);
|
||||
}
|
||||
|
||||
function getInterClashBoundingSphere(clashes: InterUnitClashes, elements: number[], boundingSphere: Sphere3D) {
|
||||
return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
|
||||
const c = clashes.edges[elements[i]]
|
||||
c.unitA.conformation.position(c.unitA.elements[c.indexA], pA)
|
||||
c.unitB.conformation.position(c.unitB.elements[c.indexB], pB)
|
||||
}, boundingSphere)
|
||||
const c = clashes.edges[elements[i]];
|
||||
c.unitA.conformation.position(c.unitA.elements[c.indexA], pA);
|
||||
c.unitB.conformation.position(c.unitB.elements[c.indexB], pB);
|
||||
}, boundingSphere);
|
||||
}
|
||||
|
||||
function getInterClashLabel(structure: Structure, clashes: InterUnitClashes, elements: number[]) {
|
||||
const idx = elements[0]
|
||||
if (idx === undefined) return ''
|
||||
const c = clashes.edges[idx]
|
||||
const mag = c.props.magnitude.toFixed(2)
|
||||
const dist = c.props.distance.toFixed(2)
|
||||
const idx = elements[0];
|
||||
if (idx === undefined) return '';
|
||||
const c = clashes.edges[idx];
|
||||
const mag = c.props.magnitude.toFixed(2);
|
||||
const dist = c.props.distance.toFixed(2);
|
||||
|
||||
return [
|
||||
`Clash id: ${c.props.id} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`,
|
||||
bondLabel(Bond.Location(structure, c.unitA, c.indexA, structure, c.unitB, c.indexB))
|
||||
].join('</br>')
|
||||
].join('</br>');
|
||||
}
|
||||
|
||||
function InterClashLoci(structure: Structure, clashes: InterUnitClashes, elements: number[]) {
|
||||
return DataLoci('inter-clashes', clashes, elements,
|
||||
(boundingSphere: Sphere3D) => getInterClashBoundingSphere(clashes, elements, boundingSphere),
|
||||
() => getInterClashLabel(structure, clashes, elements))
|
||||
() => getInterClashLabel(structure, clashes, elements));
|
||||
}
|
||||
|
||||
function getInterClashLoci(pickingId: PickingId, structure: Structure, id: number) {
|
||||
const { objectId, groupId } = pickingId
|
||||
const { objectId, groupId } = pickingId;
|
||||
if (id === objectId) {
|
||||
const clashes = ClashesProvider.get(structure).value!.interUnit
|
||||
return InterClashLoci(structure, clashes, [groupId])
|
||||
const clashes = ClashesProvider.get(structure).value!.interUnit;
|
||||
return InterClashLoci(structure, clashes, [groupId]);
|
||||
}
|
||||
return EmptyLoci
|
||||
return EmptyLoci;
|
||||
}
|
||||
|
||||
function eachInterClash(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
|
||||
let changed = false
|
||||
let changed = false;
|
||||
// TODO
|
||||
return changed
|
||||
return changed;
|
||||
}
|
||||
|
||||
function createInterClashIterator(structure: Structure): LocationIterator {
|
||||
const clashes = ClashesProvider.get(structure).value!.interUnit
|
||||
const groupCount = clashes.edgeCount
|
||||
const instanceCount = 1
|
||||
const location = StructureElement.Location.create(structure)
|
||||
const clashes = ClashesProvider.get(structure).value!.interUnit;
|
||||
const groupCount = clashes.edgeCount;
|
||||
const instanceCount = 1;
|
||||
const location = StructureElement.Location.create(structure);
|
||||
const getLocation = (groupIndex: number) => {
|
||||
const clash = clashes.edges[groupIndex]
|
||||
location.unit = clash.unitA
|
||||
location.element = clash.unitA.elements[clash.indexA]
|
||||
return location
|
||||
}
|
||||
return LocationIterator(groupCount, instanceCount, getLocation, true)
|
||||
const clash = clashes.edges[groupIndex];
|
||||
location.unit = clash.unitA;
|
||||
location.element = clash.unitA.elements[clash.indexA];
|
||||
return location;
|
||||
};
|
||||
return LocationIterator(groupCount, instanceCount, getLocation, true);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -258,36 +258,38 @@ function createInterClashIterator(structure: Structure): LocationIterator {
|
||||
const ClashesVisuals = {
|
||||
'intra-clash': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, IntraUnitClashParams>) => UnitsRepresentation('Intra-unit clash cylinder', ctx, getParams, IntraUnitClashVisual),
|
||||
'inter-clash': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, InterUnitClashParams>) => ComplexRepresentation('Inter-unit clash cylinder', ctx, getParams, InterUnitClashVisual),
|
||||
}
|
||||
};
|
||||
|
||||
export const ClashesParams = {
|
||||
...IntraUnitClashParams,
|
||||
...InterUnitClashParams,
|
||||
unitKinds: PD.MultiSelect<UnitKind>(['atomic'], UnitKindOptions),
|
||||
unitKinds: getUnitKindsParam(['atomic']),
|
||||
visuals: PD.MultiSelect(['intra-clash', 'inter-clash'], PD.objectToOptions(ClashesVisuals))
|
||||
}
|
||||
};
|
||||
export type ClashesParams = typeof ClashesParams
|
||||
export function getClashesParams(ctx: ThemeRegistryContext, structure: Structure) {
|
||||
return PD.clone(ClashesParams)
|
||||
return PD.clone(ClashesParams);
|
||||
}
|
||||
|
||||
export type ClashesRepresentation = StructureRepresentation<ClashesParams>
|
||||
export function ClashesRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, ClashesParams>): ClashesRepresentation {
|
||||
const repr = Representation.createMulti('Clashes', ctx, getParams, StructureRepresentationStateBuilder, ClashesVisuals as unknown as Representation.Def<Structure, ClashesParams>)
|
||||
repr.setState({ markerActions: MarkerActions.Highlighting })
|
||||
return repr
|
||||
const repr = Representation.createMulti('Clashes', ctx, getParams, StructureRepresentationStateBuilder, ClashesVisuals as unknown as Representation.Def<Structure, ClashesParams>);
|
||||
repr.setState({ markerActions: MarkerActions.Highlighting });
|
||||
return repr;
|
||||
}
|
||||
|
||||
export const ClashesRepresentationProvider: StructureRepresentationProvider<ClashesParams> = {
|
||||
label: 'RCSB Clashes',
|
||||
description: 'Displays clashes between atoms as disks.',
|
||||
export const ClashesRepresentationProvider = StructureRepresentationProvider({
|
||||
name: ValidationReport.Tag.Clashes,
|
||||
label: 'Validation Clashes',
|
||||
description: 'Displays clashes between atoms as disks. Data from wwPDB Validation Report, obtained via RCSB PDB.',
|
||||
factory: ClashesRepresentation,
|
||||
getParams: getClashesParams,
|
||||
defaultValues: PD.getDefaultValues(ClashesParams),
|
||||
defaultColorTheme: { name: 'uniform', props: { value: Color(0xFA28FF) } },
|
||||
defaultSizeTheme: { name: 'physical' },
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0,
|
||||
ensureCustomProperties: (ctx: CustomProperty.Context, structure: Structure) => {
|
||||
return ClashesProvider.attach(ctx, structure)
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => ClashesProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (data) => ClashesProvider.ref(data, false)
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,15 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra'
|
||||
import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra';
|
||||
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
|
||||
import { CameraTransitionManager } from './camera/transition';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
export { Camera }
|
||||
export { Camera };
|
||||
|
||||
class Camera {
|
||||
readonly view: Mat4 = Mat4.identity();
|
||||
@@ -33,6 +34,7 @@ class Camera {
|
||||
zoom = 1
|
||||
|
||||
readonly transition: CameraTransitionManager = new CameraTransitionManager(this);
|
||||
readonly stateChanged = new BehaviorSubject<Partial<Camera.Snapshot>>(this.state);
|
||||
|
||||
get position() { return this.state.position; }
|
||||
set position(v: Vec3) { Vec3.copy(this.state.position, v); }
|
||||
@@ -45,8 +47,8 @@ class Camera {
|
||||
|
||||
private prevProjection = Mat4.identity();
|
||||
private prevView = Mat4.identity();
|
||||
private deltaDirection = Vec3.zero();
|
||||
private newPosition = Vec3.zero();
|
||||
private deltaDirection = Vec3();
|
||||
private newPosition = Vec3();
|
||||
|
||||
update() {
|
||||
const snapshot = this.state as Camera.Snapshot;
|
||||
@@ -64,8 +66,8 @@ class Camera {
|
||||
const changed = !Mat4.areEqual(this.projection, this.prevProjection, EPSILON) || !Mat4.areEqual(this.view, this.prevView, EPSILON);
|
||||
|
||||
if (changed) {
|
||||
Mat4.mul(this.projectionView, this.projection, this.view)
|
||||
Mat4.invert(this.inverseProjectionView, this.projectionView)
|
||||
Mat4.mul(this.projectionView, this.projection, this.view);
|
||||
Mat4.invert(this.inverseProjectionView, this.projectionView);
|
||||
|
||||
Mat4.copy(this.prevView, this.view);
|
||||
Mat4.copy(this.prevProjection, this.projection);
|
||||
@@ -76,53 +78,58 @@ class Camera {
|
||||
|
||||
setState(snapshot: Partial<Camera.Snapshot>, durationMs?: number) {
|
||||
this.transition.apply(snapshot, durationMs);
|
||||
this.stateChanged.next(snapshot);
|
||||
}
|
||||
|
||||
getSnapshot() {
|
||||
return Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
|
||||
}
|
||||
|
||||
getFocus(target: Vec3, radiusNear: number, radiusFar: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
|
||||
const fov = this.state.fov
|
||||
const { width, height } = this.viewport
|
||||
const aspect = width / height
|
||||
const aspectFactor = (height < width ? 1 : aspect)
|
||||
const targetDistance = Math.abs((radiusNear / aspectFactor) / Math.sin(fov / 2))
|
||||
|
||||
Vec3.sub(this.deltaDirection, this.target, this.position)
|
||||
if (dir) Vec3.matchDirection(this.deltaDirection, dir, this.deltaDirection)
|
||||
Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance)
|
||||
Vec3.sub(this.newPosition, target, this.deltaDirection)
|
||||
|
||||
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state)
|
||||
state.target = Vec3.clone(target)
|
||||
state.radiusNear = radiusNear
|
||||
state.radiusFar = radiusFar
|
||||
state.position = Vec3.clone(this.newPosition)
|
||||
if (up) Vec3.matchDirection(state.up, up, state.up)
|
||||
|
||||
return state
|
||||
getTargetDistance(radius: number) {
|
||||
const r = Math.max(radius, 0.01);
|
||||
const { fov } = this.state;
|
||||
const { width, height } = this.viewport;
|
||||
const aspect = width / height;
|
||||
const aspectFactor = (height < width ? 1 : aspect);
|
||||
return Math.abs((r / aspectFactor) / Math.sin(fov / 2));
|
||||
}
|
||||
|
||||
focus(target: Vec3, radiusNear: number, radiusFar: number, durationMs?: number, up?: Vec3, dir?: Vec3) {
|
||||
if (radiusNear > 0 && radiusFar > 0) {
|
||||
this.setState(this.getFocus(target, radiusNear, radiusFar, up, dir), durationMs);
|
||||
getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
|
||||
const r = Math.max(radius, 0.01);
|
||||
const targetDistance = this.getTargetDistance(r);
|
||||
|
||||
Vec3.sub(this.deltaDirection, this.target, this.position);
|
||||
if (dir) Vec3.matchDirection(this.deltaDirection, dir, this.deltaDirection);
|
||||
Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance);
|
||||
Vec3.sub(this.newPosition, target, this.deltaDirection);
|
||||
|
||||
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
|
||||
state.target = Vec3.clone(target);
|
||||
state.radius = r;
|
||||
state.position = Vec3.clone(this.newPosition);
|
||||
if (up) Vec3.matchDirection(state.up, up, state.up);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
focus(target: Vec3, radius: number, durationMs?: number, up?: Vec3, dir?: Vec3) {
|
||||
if (radius > 0) {
|
||||
this.setState(this.getFocus(target, radius, up, dir), durationMs);
|
||||
}
|
||||
}
|
||||
|
||||
project(out: Vec4, point: Vec3) {
|
||||
return cameraProject(out, point, this.viewport, this.projectionView)
|
||||
return cameraProject(out, point, this.viewport, this.projectionView);
|
||||
}
|
||||
|
||||
unproject(out: Vec3, point: Vec3) {
|
||||
return cameraUnproject(out, point, this.viewport, this.inverseProjectionView)
|
||||
return cameraUnproject(out, point, this.viewport, this.inverseProjectionView);
|
||||
}
|
||||
|
||||
constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) {
|
||||
this.viewport = viewport;
|
||||
Camera.copySnapshot(this.state, state);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Camera {
|
||||
@@ -144,12 +151,12 @@ namespace Camera {
|
||||
}
|
||||
|
||||
export function setViewOffset(out: ViewOffset, fullWidth: number, fullHeight: number, offsetX: number, offsetY: number, width: number, height: number) {
|
||||
out.fullWidth = fullWidth
|
||||
out.fullHeight = fullHeight
|
||||
out.offsetX = offsetX
|
||||
out.offsetY = offsetY
|
||||
out.width = width
|
||||
out.height = height
|
||||
out.fullWidth = fullWidth;
|
||||
out.fullHeight = fullHeight;
|
||||
out.offsetX = offsetX;
|
||||
out.offsetY = offsetY;
|
||||
out.width = width;
|
||||
out.height = height;
|
||||
}
|
||||
|
||||
export function createDefaultSnapshot(): Snapshot {
|
||||
@@ -161,8 +168,8 @@ namespace Camera {
|
||||
up: Vec3.create(0, 1, 0),
|
||||
target: Vec3.create(0, 0, 0),
|
||||
|
||||
radiusNear: 10,
|
||||
radiusFar: 10,
|
||||
radius: 10,
|
||||
radiusMax: 10,
|
||||
fog: 50,
|
||||
clipFar: true
|
||||
};
|
||||
@@ -176,8 +183,8 @@ namespace Camera {
|
||||
up: Vec3
|
||||
target: Vec3
|
||||
|
||||
radiusNear: number
|
||||
radiusFar: number
|
||||
radius: number
|
||||
radiusMax: number
|
||||
fog: number
|
||||
clipFar: boolean
|
||||
}
|
||||
@@ -192,8 +199,8 @@ namespace Camera {
|
||||
if (typeof source.up !== 'undefined') Vec3.copy(out.up, source.up);
|
||||
if (typeof source.target !== 'undefined') Vec3.copy(out.target, source.target);
|
||||
|
||||
if (typeof source.radiusNear !== 'undefined') out.radiusNear = source.radiusNear;
|
||||
if (typeof source.radiusFar !== 'undefined') out.radiusFar = source.radiusFar;
|
||||
if (typeof source.radius !== 'undefined') out.radius = source.radius;
|
||||
if (typeof source.radiusMax !== 'undefined') out.radiusMax = source.radiusMax;
|
||||
if (typeof source.fog !== 'undefined') out.fog = source.fog;
|
||||
if (typeof source.clipFar !== 'undefined') out.clipFar = source.clipFar;
|
||||
|
||||
@@ -202,83 +209,90 @@ namespace Camera {
|
||||
}
|
||||
|
||||
function updateOrtho(camera: Camera) {
|
||||
const { viewport, zoom, near, far, viewOffset } = camera
|
||||
const { viewport, zoom, near, far, viewOffset } = camera;
|
||||
|
||||
const fullLeft = -(viewport.width - viewport.x) / 2
|
||||
const fullRight = (viewport.width - viewport.x) / 2
|
||||
const fullTop = (viewport.height - viewport.y) / 2
|
||||
const fullBottom = -(viewport.height - viewport.y) / 2
|
||||
const fullLeft = -(viewport.width - viewport.x) / 2;
|
||||
const fullRight = (viewport.width - viewport.x) / 2;
|
||||
const fullTop = (viewport.height - viewport.y) / 2;
|
||||
const fullBottom = -(viewport.height - viewport.y) / 2;
|
||||
|
||||
const dx = (fullRight - fullLeft) / (2 * zoom)
|
||||
const dy = (fullTop - fullBottom) / (2 * zoom)
|
||||
const cx = (fullRight + fullLeft) / 2
|
||||
const cy = (fullTop + fullBottom) / 2
|
||||
const dx = (fullRight - fullLeft) / (2 * zoom);
|
||||
const dy = (fullTop - fullBottom) / (2 * zoom);
|
||||
const cx = (fullRight + fullLeft) / 2;
|
||||
const cy = (fullTop + fullBottom) / 2;
|
||||
|
||||
let left = cx - dx
|
||||
let right = cx + dx
|
||||
let top = cy + dy
|
||||
let bottom = cy - dy
|
||||
let left = cx - dx;
|
||||
let right = cx + dx;
|
||||
let top = cy + dy;
|
||||
let bottom = cy - dy;
|
||||
|
||||
if (viewOffset.enabled) {
|
||||
const zoomW = zoom / (viewOffset.width / viewOffset.fullWidth)
|
||||
const zoomH = zoom / (viewOffset.height / viewOffset.fullHeight)
|
||||
const scaleW = (fullRight - fullLeft) / viewOffset.width
|
||||
const scaleH = (fullTop - fullBottom) / viewOffset.height
|
||||
left += scaleW * (viewOffset.offsetX / zoomW)
|
||||
right = left + scaleW * (viewOffset.width / zoomW)
|
||||
top -= scaleH * (viewOffset.offsetY / zoomH)
|
||||
bottom = top - scaleH * (viewOffset.height / zoomH)
|
||||
const zoomW = zoom / (viewOffset.width / viewOffset.fullWidth);
|
||||
const zoomH = zoom / (viewOffset.height / viewOffset.fullHeight);
|
||||
const scaleW = (fullRight - fullLeft) / viewOffset.width;
|
||||
const scaleH = (fullTop - fullBottom) / viewOffset.height;
|
||||
left += scaleW * (viewOffset.offsetX / zoomW);
|
||||
right = left + scaleW * (viewOffset.width / zoomW);
|
||||
top -= scaleH * (viewOffset.offsetY / zoomH);
|
||||
bottom = top - scaleH * (viewOffset.height / zoomH);
|
||||
}
|
||||
|
||||
// build projection matrix
|
||||
Mat4.ortho(camera.projection, left, right, top, bottom, near, far)
|
||||
Mat4.ortho(camera.projection, left, right, top, bottom, near, far);
|
||||
|
||||
// build view matrix
|
||||
Mat4.lookAt(camera.view, camera.position, camera.target, camera.up)
|
||||
Mat4.lookAt(camera.view, camera.position, camera.target, camera.up);
|
||||
}
|
||||
|
||||
function updatePers(camera: Camera) {
|
||||
const aspect = camera.viewport.width / camera.viewport.height
|
||||
const aspect = camera.viewport.width / camera.viewport.height;
|
||||
|
||||
const { near, far, viewOffset } = camera
|
||||
const { near, far, viewOffset } = camera;
|
||||
|
||||
let top = near * Math.tan(0.5 * camera.state.fov)
|
||||
let height = 2 * top
|
||||
let width = aspect * height
|
||||
let left = -0.5 * width
|
||||
let top = near * Math.tan(0.5 * camera.state.fov);
|
||||
let height = 2 * top;
|
||||
let width = aspect * height;
|
||||
let left = -0.5 * width;
|
||||
|
||||
if (viewOffset.enabled) {
|
||||
left += viewOffset.offsetX * width / viewOffset.fullWidth
|
||||
top -= viewOffset.offsetY * height / viewOffset.fullHeight
|
||||
width *= viewOffset.width / viewOffset.fullWidth
|
||||
height *= viewOffset.height / viewOffset.fullHeight
|
||||
left += viewOffset.offsetX * width / viewOffset.fullWidth;
|
||||
top -= viewOffset.offsetY * height / viewOffset.fullHeight;
|
||||
width *= viewOffset.width / viewOffset.fullWidth;
|
||||
height *= viewOffset.height / viewOffset.fullHeight;
|
||||
}
|
||||
|
||||
// build projection matrix
|
||||
Mat4.perspective(camera.projection, left, left + width, top, top - height, near, far)
|
||||
Mat4.perspective(camera.projection, left, left + width, top, top - height, near, far);
|
||||
|
||||
// build view matrix
|
||||
Mat4.lookAt(camera.view, camera.position, camera.target, camera.up)
|
||||
Mat4.lookAt(camera.view, camera.position, camera.target, camera.up);
|
||||
}
|
||||
|
||||
function updateClip(camera: Camera) {
|
||||
const { radiusNear, radiusFar, mode, fog, clipFar } = camera.state
|
||||
let { radius, radiusMax, mode, fog, clipFar } = camera.state;
|
||||
if (radius < 0.01) radius = 0.01;
|
||||
|
||||
const cDist = Vec3.distance(camera.position, camera.target)
|
||||
let near = cDist - radiusNear
|
||||
let far = cDist + (clipFar ? radiusNear : radiusFar)
|
||||
const normalizedFar = clipFar ? radius : radiusMax;
|
||||
const cameraDistance = Vec3.distance(camera.position, camera.target);
|
||||
let near = cameraDistance - radius;
|
||||
let far = cameraDistance + normalizedFar;
|
||||
|
||||
const fogNearFactor = -(50 - fog) / 50
|
||||
let fogNear = cDist - (radiusNear * fogNearFactor)
|
||||
let fogFar = far
|
||||
const fogNearFactor = -(50 - fog) / 50;
|
||||
let fogNear = cameraDistance - (normalizedFar * fogNearFactor);
|
||||
let fogFar = far;
|
||||
|
||||
if (mode === 'perspective') {
|
||||
// set at least to 5 to avoid slow sphere impostor rendering
|
||||
near = Math.max(5, near)
|
||||
far = Math.max(5, far)
|
||||
near = Math.max(5, near);
|
||||
far = Math.max(5, far);
|
||||
} else {
|
||||
near = Math.max(0, near)
|
||||
far = Math.max(0, far)
|
||||
near = Math.max(0, near);
|
||||
far = Math.max(0, far);
|
||||
}
|
||||
|
||||
if (near === far) {
|
||||
// make sure near and far are not identical to avoid Infinity in the projection matrix
|
||||
far = near + 0.01;
|
||||
}
|
||||
|
||||
camera.near = near;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Camera } from '../camera';
|
||||
import { Quat, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { lerp } from '../../mol-math/interpolate';
|
||||
|
||||
export { CameraTransitionManager }
|
||||
export { CameraTransitionManager };
|
||||
|
||||
class CameraTransitionManager {
|
||||
private t = 0;
|
||||
@@ -17,24 +17,40 @@ class CameraTransitionManager {
|
||||
private start = 0;
|
||||
inTransition = false;
|
||||
private durationMs = 0;
|
||||
private source: Camera.Snapshot = Camera.createDefaultSnapshot();
|
||||
private target: Camera.Snapshot = Camera.createDefaultSnapshot();
|
||||
private current = Camera.createDefaultSnapshot();
|
||||
private _source: Camera.Snapshot = Camera.createDefaultSnapshot();
|
||||
private _target: Camera.Snapshot = Camera.createDefaultSnapshot();
|
||||
private _current = Camera.createDefaultSnapshot();
|
||||
|
||||
get source(): Readonly<Camera.Snapshot> { return this._source; }
|
||||
get target(): Readonly<Camera.Snapshot> { return this._target; }
|
||||
|
||||
apply(to: Partial<Camera.Snapshot>, durationMs: number = 0, transition?: CameraTransitionManager.TransitionFunc) {
|
||||
if (durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
|
||||
this.finish(to);
|
||||
if (!this.inTransition || durationMs > 0) {
|
||||
Camera.copySnapshot(this._source, this.camera.state);
|
||||
}
|
||||
|
||||
if (!this.inTransition) {
|
||||
Camera.copySnapshot(this._target, this.camera.state);
|
||||
}
|
||||
|
||||
Camera.copySnapshot(this._target, to);
|
||||
|
||||
if (this._target.radius > this._target.radiusMax) {
|
||||
this._target.radius = this._target.radiusMax;
|
||||
}
|
||||
|
||||
if (!this.inTransition && durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
|
||||
this.finish(this._target);
|
||||
return;
|
||||
}
|
||||
|
||||
Camera.copySnapshot(this.source, this.camera.state);
|
||||
Camera.copySnapshot(this.target, this.camera.state);
|
||||
Camera.copySnapshot(this.target, to);
|
||||
|
||||
this.inTransition = true;
|
||||
this.func = transition || CameraTransitionManager.defaultTransition;
|
||||
this.start = this.t;
|
||||
this.durationMs = durationMs;
|
||||
|
||||
if (!this.inTransition || durationMs > 0) {
|
||||
this.start = this.t;
|
||||
this.durationMs = durationMs;
|
||||
}
|
||||
}
|
||||
|
||||
tick(t: number) {
|
||||
@@ -52,12 +68,12 @@ class CameraTransitionManager {
|
||||
|
||||
const normalized = Math.min((this.t - this.start) / this.durationMs, 1);
|
||||
if (normalized === 1) {
|
||||
this.finish(this.target!);
|
||||
this.finish(this._target!);
|
||||
return;
|
||||
}
|
||||
|
||||
this.func(this.current, normalized, this.source, this.target);
|
||||
Camera.copySnapshot(this.camera.state, this.current);
|
||||
this.func(this._current, normalized, this._source, this._target);
|
||||
Camera.copySnapshot(this.camera.state, this._current);
|
||||
}
|
||||
|
||||
constructor(private camera: Camera) {
|
||||
@@ -79,9 +95,9 @@ namespace CameraTransitionManager {
|
||||
// Lerp target, position & radius
|
||||
Vec3.lerp(out.target, source.target, target.target, t);
|
||||
Vec3.lerp(out.position, source.position, target.position, t);
|
||||
out.radiusNear = lerp(source.radiusNear, target.radiusNear, t);
|
||||
out.radius = lerp(source.radius, target.radius, t);
|
||||
// TODO take change of `clipFar` into account
|
||||
out.radiusFar = lerp(source.radiusFar, target.radiusFar, t);
|
||||
out.radiusMax = lerp(source.radiusMax, target.radiusMax, t);
|
||||
|
||||
// Lerp fov & fog
|
||||
out.fov = lerp(source.fov, target.fov, t);
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mat4, Vec3, Vec4 } from '../../mol-math/linear-algebra'
|
||||
import { Mat4, Vec3, Vec4 } from '../../mol-math/linear-algebra';
|
||||
|
||||
export { Viewport }
|
||||
export { Viewport };
|
||||
|
||||
type Viewport = {
|
||||
x: number
|
||||
@@ -16,74 +16,74 @@ type Viewport = {
|
||||
}
|
||||
|
||||
function Viewport() {
|
||||
return Viewport.zero()
|
||||
return Viewport.zero();
|
||||
}
|
||||
|
||||
namespace Viewport {
|
||||
export function zero(): Viewport {
|
||||
return { x: 0, y: 0, width: 0, height: 0 }
|
||||
return { x: 0, y: 0, width: 0, height: 0 };
|
||||
}
|
||||
export function create(x: number, y: number, width: number, height: number): Viewport {
|
||||
return { x, y, width, height }
|
||||
return { x, y, width, height };
|
||||
}
|
||||
export function clone(viewport: Viewport): Viewport {
|
||||
return { ...viewport }
|
||||
return { ...viewport };
|
||||
}
|
||||
export function copy(target: Viewport, source: Viewport): Viewport {
|
||||
return Object.assign(target, source)
|
||||
return Object.assign(target, source);
|
||||
}
|
||||
export function set(viewport: Viewport, x: number, y: number, width: number, height: number): Viewport {
|
||||
viewport.x = x
|
||||
viewport.y = y
|
||||
viewport.width = width
|
||||
viewport.height = height
|
||||
return viewport
|
||||
viewport.x = x;
|
||||
viewport.y = y;
|
||||
viewport.width = width;
|
||||
viewport.height = height;
|
||||
return viewport;
|
||||
}
|
||||
|
||||
export function toVec4(v4: Vec4, viewport: Viewport): Vec4 {
|
||||
v4[0] = viewport.x
|
||||
v4[1] = viewport.y
|
||||
v4[2] = viewport.width
|
||||
v4[3] = viewport.height
|
||||
return v4
|
||||
v4[0] = viewport.x;
|
||||
v4[1] = viewport.y;
|
||||
v4[2] = viewport.width;
|
||||
v4[3] = viewport.height;
|
||||
return v4;
|
||||
}
|
||||
|
||||
export function equals(a: Viewport, b: Viewport) {
|
||||
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height
|
||||
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const NEAR_RANGE = 0
|
||||
const FAR_RANGE = 1
|
||||
const NEAR_RANGE = 0;
|
||||
const FAR_RANGE = 1;
|
||||
|
||||
const tmpVec4 = Vec4()
|
||||
const tmpVec4 = Vec4();
|
||||
|
||||
/** Transform point into 2D window coordinates. */
|
||||
export function cameraProject (out: Vec4, point: Vec3, viewport: Viewport, projectionView: Mat4) {
|
||||
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport
|
||||
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport;
|
||||
|
||||
// clip space -> NDC -> window coordinates, implicit 1.0 for w component
|
||||
Vec4.set(tmpVec4, point[0], point[1], point[2], 1.0)
|
||||
Vec4.set(tmpVec4, point[0], point[1], point[2], 1.0);
|
||||
|
||||
// transform into clip space
|
||||
Vec4.transformMat4(tmpVec4, tmpVec4, projectionView)
|
||||
Vec4.transformMat4(tmpVec4, tmpVec4, projectionView);
|
||||
|
||||
// transform into NDC
|
||||
const w = tmpVec4[3]
|
||||
const w = tmpVec4[3];
|
||||
if (w !== 0) {
|
||||
tmpVec4[0] /= w
|
||||
tmpVec4[1] /= w
|
||||
tmpVec4[2] /= w
|
||||
tmpVec4[0] /= w;
|
||||
tmpVec4[1] /= w;
|
||||
tmpVec4[2] /= w;
|
||||
}
|
||||
|
||||
// transform into window coordinates, set fourth component is (1/clip.w) as in gl_FragCoord.w
|
||||
out[0] = vX + vWidth / 2 * tmpVec4[0] + (0 + vWidth / 2)
|
||||
out[1] = vY + vHeight / 2 * tmpVec4[1] + (0 + vHeight / 2)
|
||||
out[2] = (FAR_RANGE - NEAR_RANGE) / 2 * tmpVec4[2] + (FAR_RANGE + NEAR_RANGE) / 2
|
||||
out[3] = w === 0 ? 0 : 1 / w
|
||||
return out
|
||||
out[0] = vX + vWidth / 2 * tmpVec4[0] + (0 + vWidth / 2);
|
||||
out[1] = vY + vHeight / 2 * tmpVec4[1] + (0 + vHeight / 2);
|
||||
out[2] = (FAR_RANGE - NEAR_RANGE) / 2 * tmpVec4[2] + (FAR_RANGE + NEAR_RANGE) / 2;
|
||||
out[3] = w === 0 ? 0 : 1 / w;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,14 +91,14 @@ export function cameraProject (out: Vec4, point: Vec3, viewport: Viewport, proje
|
||||
* The point must have x and y set to 2D window coordinates and z between 0 (near) and 1 (far).
|
||||
*/
|
||||
export function cameraUnproject (out: Vec3, point: Vec3, viewport: Viewport, inverseProjectionView: Mat4) {
|
||||
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport
|
||||
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport;
|
||||
|
||||
const x = point[0] - vX
|
||||
const y = (vHeight - point[1] - 1) - vY
|
||||
const z = point[2]
|
||||
const x = point[0] - vX;
|
||||
const y = (vHeight - point[1] - 1) - vY;
|
||||
const z = point[2];
|
||||
|
||||
out[0] = (2 * x) / vWidth - 1
|
||||
out[1] = (2 * y) / vHeight - 1
|
||||
out[2] = 2 * z - 1
|
||||
return Vec3.transformMat4(out, out, inverseProjectionView)
|
||||
out[0] = (2 * x) / vWidth - 1;
|
||||
out[1] = (2 * y) / vHeight - 1;
|
||||
out[2] = 2 * z - 1;
|
||||
return Vec3.transformMat4(out, out, inverseProjectionView);
|
||||
}
|
||||
@@ -7,12 +7,12 @@
|
||||
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { now } from '../mol-util/now';
|
||||
import { Vec3 } from '../mol-math/linear-algebra'
|
||||
import InputObserver, { ModifiersKeys, ButtonsType } from '../mol-util/input/input-observer'
|
||||
import Renderer, { RendererStats, RendererParams } from '../mol-gl/renderer'
|
||||
import { GraphicsRenderObject } from '../mol-gl/render-object'
|
||||
import { TrackballControls, TrackballControlsParams } from './controls/trackball'
|
||||
import { Viewport } from './camera/util'
|
||||
import { Vec3 } from '../mol-math/linear-algebra';
|
||||
import InputObserver, { ModifiersKeys, ButtonsType } from '../mol-util/input/input-observer';
|
||||
import Renderer, { RendererStats, RendererParams } from '../mol-gl/renderer';
|
||||
import { GraphicsRenderObject } from '../mol-gl/render-object';
|
||||
import { TrackballControls, TrackballControlsParams } from './controls/trackball';
|
||||
import { Viewport } from './camera/util';
|
||||
import { createContext, WebGLContext, getGLContext } from '../mol-gl/webgl/context';
|
||||
import { Representation } from '../mol-repr/representation';
|
||||
import Scene from '../mol-gl/scene';
|
||||
@@ -31,15 +31,28 @@ import { PixelData } from '../mol-util/image';
|
||||
import { readTexture } from '../mol-gl/compute/util';
|
||||
import { DrawPass } from './passes/draw';
|
||||
import { PickPass } from './passes/pick';
|
||||
import { Task } from '../mol-task';
|
||||
import { ImagePass, ImageProps } from './passes/image';
|
||||
import { Sphere3D } from '../mol-math/geometry';
|
||||
import { isDebugMode } from '../mol-util/debug';
|
||||
import { CameraHelperParams } from './helper/camera-helper';
|
||||
import { produce } from 'immer';
|
||||
|
||||
export const Canvas3DParams = {
|
||||
cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]),
|
||||
cameraFog: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
|
||||
cameraClipFar: PD.Boolean(true),
|
||||
camera: PD.Group({
|
||||
mode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']] as const, { label: 'Camera' }),
|
||||
helper: PD.Group(CameraHelperParams, { isFlat: true })
|
||||
}, { pivot: 'mode' }),
|
||||
cameraFog: PD.MappedStatic('on', {
|
||||
on: PD.Group({
|
||||
intensity: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Show fog in the distance' }),
|
||||
cameraClipping: PD.Group({
|
||||
radius: PD.Numeric(100, { min: 0, max: 99, step: 1 }, { label: 'Clipping', description: 'How much of the scene to show.' }),
|
||||
far: PD.Boolean(true, { description: 'Hide scene in the distance' }),
|
||||
}, { pivot: 'radius' }),
|
||||
|
||||
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
|
||||
transparentBackground: PD.Boolean(false),
|
||||
|
||||
@@ -48,11 +61,11 @@ export const Canvas3DParams = {
|
||||
renderer: PD.Group(RendererParams),
|
||||
trackball: PD.Group(TrackballControlsParams),
|
||||
debug: PD.Group(DebugHelperParams)
|
||||
}
|
||||
};
|
||||
export const DefaultCanvas3DParams = PD.getDefaultValues(Canvas3DParams);
|
||||
export type Canvas3DProps = PD.Values<typeof Canvas3DParams>
|
||||
|
||||
export { Canvas3D }
|
||||
export { Canvas3D };
|
||||
|
||||
interface Canvas3D {
|
||||
readonly webgl: WebGLContext,
|
||||
@@ -65,6 +78,7 @@ interface Canvas3D {
|
||||
commit(isSynchronous?: boolean): void
|
||||
update(repr?: Representation.Any, keepBoundingSphere?: boolean): void
|
||||
clear(): void
|
||||
syncVisibility(): void
|
||||
|
||||
requestDraw(force?: boolean): void
|
||||
animate(): void
|
||||
@@ -77,13 +91,12 @@ interface Canvas3D {
|
||||
|
||||
handleResize(): void
|
||||
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
|
||||
requestCameraReset(durationMs?: number): void
|
||||
requestCameraReset(options?: { durationMs?: number, snapshot?: Partial<Camera.Snapshot> }): void
|
||||
readonly camera: Camera
|
||||
readonly boundingSphere: Readonly<Sphere3D>
|
||||
downloadScreenshot(): void
|
||||
getPixelData(variant: GraphicsRenderVariant): PixelData
|
||||
setProps(props: Partial<Canvas3DProps>): void
|
||||
getImagePass(): ImagePass
|
||||
setProps(props: Partial<Canvas3DProps> | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void)): void
|
||||
getImagePass(props: Partial<ImageProps>): ImagePass
|
||||
|
||||
/** Returns a copy of the current Canvas3D instance props */
|
||||
readonly props: Readonly<Canvas3DProps>
|
||||
@@ -94,155 +107,152 @@ interface Canvas3D {
|
||||
dispose(): void
|
||||
}
|
||||
|
||||
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()))
|
||||
const DefaultRunTask = (task: Task<unknown>) => task.run()
|
||||
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()));
|
||||
|
||||
namespace Canvas3D {
|
||||
export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
|
||||
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
|
||||
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask) {
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}) {
|
||||
const gl = getGLContext(canvas, {
|
||||
alpha: true,
|
||||
antialias: true,
|
||||
depth: true,
|
||||
preserveDrawingBuffer: true,
|
||||
premultipliedAlpha: false,
|
||||
})
|
||||
if (gl === null) throw new Error('Could not create a WebGL rendering context')
|
||||
const input = InputObserver.fromElement(canvas)
|
||||
const webgl = createContext(gl)
|
||||
});
|
||||
if (gl === null) throw new Error('Could not create a WebGL rendering context');
|
||||
const input = InputObserver.fromElement(canvas);
|
||||
const webgl = createContext(gl);
|
||||
|
||||
if (isDebugMode) {
|
||||
const loseContextExt = gl.getExtension('WEBGL_lose_context')
|
||||
const loseContextExt = gl.getExtension('WEBGL_lose_context');
|
||||
if (loseContextExt) {
|
||||
canvas.addEventListener('mousedown', e => {
|
||||
if (webgl.isContextLost) return
|
||||
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return
|
||||
if (webgl.isContextLost) return;
|
||||
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return;
|
||||
|
||||
console.log('lose context')
|
||||
loseContextExt.loseContext()
|
||||
if (isDebugMode) console.log('lose context');
|
||||
loseContextExt.loseContext();
|
||||
|
||||
setTimeout(() => {
|
||||
if (!webgl.isContextLost) return
|
||||
console.log('restore context')
|
||||
loseContextExt.restoreContext()
|
||||
}, 1000)
|
||||
}, false)
|
||||
if (!webgl.isContextLost) return;
|
||||
if (isDebugMode) console.log('restore context');
|
||||
loseContextExt.restoreContext();
|
||||
}, 1000);
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.khronos.org/webgl/wiki/HandlingContextLost
|
||||
|
||||
canvas.addEventListener('webglcontextlost', e => {
|
||||
webgl.setContextLost()
|
||||
e.preventDefault()
|
||||
if (isDebugMode) console.log('context lost')
|
||||
}, false)
|
||||
webgl.setContextLost();
|
||||
e.preventDefault();
|
||||
if (isDebugMode) console.log('context lost');
|
||||
}, false);
|
||||
|
||||
canvas.addEventListener('webglcontextrestored', () => {
|
||||
if (!webgl.isContextLost) return
|
||||
webgl.handleContextRestored()
|
||||
if (isDebugMode) console.log('context restored')
|
||||
}, false)
|
||||
if (!webgl.isContextLost) return;
|
||||
webgl.handleContextRestored();
|
||||
if (isDebugMode) console.log('context restored');
|
||||
}, false);
|
||||
|
||||
return Canvas3D.create(webgl, input, props, runTask)
|
||||
return Canvas3D.create(webgl, input, props);
|
||||
}
|
||||
|
||||
export function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask): Canvas3D {
|
||||
const p = { ...DefaultCanvas3DParams, ...props }
|
||||
export function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}): Canvas3D {
|
||||
const p = { ...DefaultCanvas3DParams, ...props };
|
||||
|
||||
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>()
|
||||
const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>()
|
||||
const reprCount = new BehaviorSubject(0)
|
||||
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>();
|
||||
const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>();
|
||||
const reprCount = new BehaviorSubject(0);
|
||||
|
||||
const startTime = now()
|
||||
const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp)
|
||||
const startTime = now();
|
||||
const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
|
||||
|
||||
const { gl, contextRestored } = webgl
|
||||
const { gl, contextRestored } = webgl;
|
||||
|
||||
let width = gl.drawingBufferWidth
|
||||
let height = gl.drawingBufferHeight
|
||||
let width = gl.drawingBufferWidth;
|
||||
let height = gl.drawingBufferHeight;
|
||||
|
||||
const scene = Scene.create(webgl)
|
||||
const scene = Scene.create(webgl);
|
||||
|
||||
const camera = new Camera({
|
||||
position: Vec3.create(0, 0, 100),
|
||||
mode: p.cameraMode,
|
||||
fog: p.cameraFog,
|
||||
clipFar: p.cameraClipFar
|
||||
})
|
||||
mode: p.camera.mode,
|
||||
fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0,
|
||||
clipFar: p.cameraClipping.far
|
||||
});
|
||||
|
||||
const controls = TrackballControls.create(input, camera, p.trackball)
|
||||
const renderer = Renderer.create(webgl, p.renderer)
|
||||
const controls = TrackballControls.create(input, camera, p.trackball);
|
||||
const renderer = Renderer.create(webgl, p.renderer);
|
||||
const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
|
||||
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
|
||||
|
||||
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper)
|
||||
const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5)
|
||||
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing)
|
||||
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample)
|
||||
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, {
|
||||
cameraHelper: p.camera.helper
|
||||
});
|
||||
const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5);
|
||||
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing);
|
||||
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample);
|
||||
|
||||
const contextRestoredSub = contextRestored.subscribe(() => {
|
||||
pickPass.pickDirty = true
|
||||
draw(true)
|
||||
})
|
||||
|
||||
let drawPending = false
|
||||
let cameraResetRequested = false
|
||||
let nextCameraResetDuration: number | undefined = void 0
|
||||
let drawPending = false;
|
||||
let cameraResetRequested = false;
|
||||
let nextCameraResetDuration: number | undefined = void 0;
|
||||
let nextCameraResetSnapshot: Partial<Camera.Snapshot> | undefined = void 0;
|
||||
|
||||
function getLoci(pickingId: PickingId) {
|
||||
let loci: Loci = EmptyLoci
|
||||
let repr: Representation.Any = Representation.Empty
|
||||
let loci: Loci = EmptyLoci;
|
||||
let repr: Representation.Any = Representation.Empty;
|
||||
reprRenderObjects.forEach((_, _repr) => {
|
||||
const _loci = _repr.getLoci(pickingId)
|
||||
const _loci = _repr.getLoci(pickingId);
|
||||
if (!isEmptyLoci(_loci)) {
|
||||
if (!isEmptyLoci(loci)) {
|
||||
console.warn('found another loci, this should not happen')
|
||||
console.warn('found another loci, this should not happen');
|
||||
}
|
||||
loci = _loci
|
||||
repr = _repr
|
||||
loci = _loci;
|
||||
repr = _repr;
|
||||
}
|
||||
})
|
||||
return { loci, repr }
|
||||
});
|
||||
return { loci, repr };
|
||||
}
|
||||
|
||||
function mark(reprLoci: Representation.Loci, action: MarkerAction) {
|
||||
const { repr, loci } = reprLoci
|
||||
let changed = false
|
||||
const { repr, loci } = reprLoci;
|
||||
let changed = false;
|
||||
if (repr) {
|
||||
changed = repr.mark(loci, action)
|
||||
changed = repr.mark(loci, action);
|
||||
} else {
|
||||
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed })
|
||||
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
|
||||
}
|
||||
if (changed) {
|
||||
scene.update(void 0, true)
|
||||
const prevPickDirty = pickPass.pickDirty
|
||||
draw(true)
|
||||
pickPass.pickDirty = prevPickDirty // marking does not change picking buffers
|
||||
scene.update(void 0, true);
|
||||
const prevPickDirty = pickPass.pickDirty;
|
||||
draw(true);
|
||||
pickPass.pickDirty = prevPickDirty; // marking does not change picking buffers
|
||||
}
|
||||
}
|
||||
|
||||
function render(force: boolean) {
|
||||
if (webgl.isContextLost) return false
|
||||
if (webgl.isContextLost) return false;
|
||||
|
||||
let didRender = false
|
||||
controls.update(currentTime)
|
||||
Viewport.set(camera.viewport, 0, 0, width, height)
|
||||
const cameraChanged = camera.update()
|
||||
multiSample.update(force || cameraChanged, currentTime)
|
||||
let didRender = false;
|
||||
controls.update(currentTime);
|
||||
Viewport.set(camera.viewport, 0, 0, width, height);
|
||||
const cameraChanged = camera.update();
|
||||
multiSample.update(force || cameraChanged, currentTime);
|
||||
|
||||
if (force || cameraChanged || multiSample.enabled) {
|
||||
renderer.setViewport(0, 0, width, height)
|
||||
renderer.setViewport(0, 0, width, height);
|
||||
if (multiSample.enabled) {
|
||||
multiSample.render(true, p.transparentBackground)
|
||||
multiSample.render(true, p.transparentBackground);
|
||||
} else {
|
||||
drawPass.render(!postprocessing.enabled, p.transparentBackground)
|
||||
if (postprocessing.enabled) postprocessing.render(true)
|
||||
drawPass.render(!postprocessing.enabled, p.transparentBackground);
|
||||
if (postprocessing.enabled) postprocessing.render(true);
|
||||
}
|
||||
pickPass.pickDirty = true
|
||||
didRender = true
|
||||
pickPass.pickDirty = true;
|
||||
didRender = true;
|
||||
}
|
||||
|
||||
return didRender;
|
||||
@@ -253,34 +263,32 @@ namespace Canvas3D {
|
||||
|
||||
function draw(force?: boolean) {
|
||||
if (render(!!force || forceNextDraw)) {
|
||||
didDraw.next(now() - startTime as now.Timestamp)
|
||||
didDraw.next(now() - startTime as now.Timestamp);
|
||||
}
|
||||
forceNextDraw = false;
|
||||
drawPending = false
|
||||
drawPending = false;
|
||||
}
|
||||
|
||||
function requestDraw(force?: boolean) {
|
||||
if (drawPending) return
|
||||
drawPending = true
|
||||
if (drawPending) return;
|
||||
drawPending = true;
|
||||
forceNextDraw = !!force;
|
||||
}
|
||||
|
||||
function animate() {
|
||||
currentTime = now();
|
||||
|
||||
commit();
|
||||
|
||||
camera.transition.tick(currentTime);
|
||||
|
||||
draw(false);
|
||||
if (!camera.transition.inTransition && !webgl.isContextLost) {
|
||||
interactionHelper.tick(currentTime);
|
||||
}
|
||||
requestAnimationFrame(animate)
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
function identify(x: number, y: number): PickingId | undefined {
|
||||
return webgl.isContextLost ? undefined : pickPass.identify(x, y)
|
||||
return webgl.isContextLost ? undefined : pickPass.identify(x, y);
|
||||
}
|
||||
|
||||
function commit(isSynchronous: boolean = false) {
|
||||
@@ -291,52 +299,95 @@ namespace Canvas3D {
|
||||
|
||||
function resolveCameraReset() {
|
||||
if (!cameraResetRequested) return;
|
||||
const { center, radius } = scene.boundingSphere;
|
||||
camera.focus(center, radius, radius,
|
||||
typeof nextCameraResetDuration === 'undefined' ? p.cameraResetDurationMs : nextCameraResetDuration);
|
||||
|
||||
const { center, radius } = scene.boundingSphereVisible;
|
||||
if (radius > 0) {
|
||||
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration;
|
||||
const focus = camera.getFocus(center, radius);
|
||||
const snapshot = nextCameraResetSnapshot ? { ...focus, ...nextCameraResetSnapshot } : focus;
|
||||
camera.setState(snapshot, duration);
|
||||
}
|
||||
|
||||
nextCameraResetDuration = void 0;
|
||||
nextCameraResetSnapshot = void 0;
|
||||
cameraResetRequested = false;
|
||||
}
|
||||
|
||||
const oldBoundingSphereVisible = Sphere3D();
|
||||
const cameraSphere = Sphere3D();
|
||||
|
||||
function shouldResetCamera() {
|
||||
if (camera.state.radiusMax === 0) return true;
|
||||
|
||||
if (camera.transition.inTransition || nextCameraResetSnapshot) return false;
|
||||
|
||||
let cameraSphereOverlapsNone = true;
|
||||
Sphere3D.set(cameraSphere, camera.state.target, camera.state.radius);
|
||||
|
||||
// check if any renderable has moved outside of the old bounding sphere
|
||||
// and if no renderable is overlapping with the camera sphere
|
||||
for (const r of scene.renderables) {
|
||||
if (!r.state.visible) continue;
|
||||
|
||||
const b = r.values.boundingSphere.ref.value;
|
||||
if (!b.radius) continue;
|
||||
|
||||
if (!Sphere3D.includes(oldBoundingSphereVisible, b)) return true;
|
||||
if (Sphere3D.overlaps(cameraSphere, b)) cameraSphereOverlapsNone = false;
|
||||
}
|
||||
|
||||
return cameraSphereOverlapsNone;
|
||||
}
|
||||
|
||||
const sceneCommitTimeoutMs = 250;
|
||||
function commitScene(isSynchronous: boolean) {
|
||||
if (!scene.needsCommit) return true;
|
||||
|
||||
// snapshot the current bounding sphere of visible objects
|
||||
Sphere3D.copy(oldBoundingSphereVisible, scene.boundingSphereVisible);
|
||||
|
||||
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
|
||||
|
||||
if (debugHelper.isEnabled) debugHelper.update();
|
||||
if (reprCount.value === 0 || shouldResetCamera()) {
|
||||
cameraResetRequested = true;
|
||||
}
|
||||
if (oldBoundingSphereVisible.radius === 0) nextCameraResetDuration = 0;
|
||||
|
||||
camera.setState({ radiusMax: scene.boundingSphere.radius }, 0);
|
||||
reprCount.next(reprRenderObjects.size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function add(repr: Representation.Any) {
|
||||
registerAutoUpdate(repr);
|
||||
|
||||
const oldRO = reprRenderObjects.get(repr)
|
||||
const newRO = new Set<GraphicsRenderObject>()
|
||||
repr.renderObjects.forEach(o => newRO.add(o))
|
||||
const oldRO = reprRenderObjects.get(repr);
|
||||
const newRO = new Set<GraphicsRenderObject>();
|
||||
repr.renderObjects.forEach(o => newRO.add(o));
|
||||
|
||||
if (oldRO) {
|
||||
if (!SetUtils.areEqual(newRO, oldRO)) {
|
||||
newRO.forEach(o => { if (!oldRO.has(o)) scene.add(o) })
|
||||
oldRO.forEach(o => { if (!newRO.has(o)) scene.remove(o) })
|
||||
newRO.forEach(o => { if (!oldRO.has(o)) scene.add(o); });
|
||||
oldRO.forEach(o => { if (!newRO.has(o)) scene.remove(o); });
|
||||
}
|
||||
} else {
|
||||
repr.renderObjects.forEach(o => scene.add(o))
|
||||
repr.renderObjects.forEach(o => scene.add(o));
|
||||
}
|
||||
reprRenderObjects.set(repr, newRO)
|
||||
reprRenderObjects.set(repr, newRO);
|
||||
|
||||
scene.update(repr.renderObjects, false)
|
||||
scene.update(repr.renderObjects, false);
|
||||
}
|
||||
|
||||
function remove(repr: Representation.Any) {
|
||||
unregisterAutoUpdate(repr);
|
||||
|
||||
const renderObjects = reprRenderObjects.get(repr)
|
||||
const renderObjects = reprRenderObjects.get(repr);
|
||||
if (renderObjects) {
|
||||
renderObjects.forEach(o => scene.remove(o))
|
||||
reprRenderObjects.delete(repr)
|
||||
scene.update(repr.renderObjects, false, true)
|
||||
renderObjects.forEach(o => scene.remove(o));
|
||||
reprRenderObjects.delete(repr);
|
||||
scene.update(repr.renderObjects, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +396,7 @@ namespace Canvas3D {
|
||||
|
||||
reprUpdatedSubscriptions.set(repr, repr.updated.subscribe(_ => {
|
||||
if (!repr.state.syncManually) add(repr);
|
||||
}))
|
||||
}));
|
||||
}
|
||||
|
||||
function unregisterAutoUpdate(repr: Representation.Any) {
|
||||
@@ -356,7 +407,37 @@ namespace Canvas3D {
|
||||
}
|
||||
}
|
||||
|
||||
handleResize()
|
||||
function getProps(): Canvas3DProps {
|
||||
const radius = scene.boundingSphere.radius > 0
|
||||
? 100 - Math.round((camera.transition.target.radius / scene.boundingSphere.radius) * 100)
|
||||
: 0;
|
||||
|
||||
return {
|
||||
camera: {
|
||||
mode: camera.state.mode,
|
||||
helper: { ...drawPass.props.cameraHelper }
|
||||
},
|
||||
cameraFog: camera.state.fog > 0
|
||||
? { name: 'on' as const, params: { intensity: camera.state.fog } }
|
||||
: { name: 'off' as const, params: {} },
|
||||
cameraClipping: { far: camera.state.clipFar, radius },
|
||||
cameraResetDurationMs: p.cameraResetDurationMs,
|
||||
transparentBackground: p.transparentBackground,
|
||||
|
||||
postprocessing: { ...postprocessing.props },
|
||||
multiSample: { ...multiSample.props },
|
||||
renderer: { ...renderer.props },
|
||||
trackball: { ...controls.props },
|
||||
debug: { ...debugHelper.props }
|
||||
};
|
||||
}
|
||||
|
||||
handleResize();
|
||||
|
||||
const contextRestoredSub = contextRestored.subscribe(() => {
|
||||
pickPass.pickDirty = true;
|
||||
draw(true);
|
||||
});
|
||||
|
||||
return {
|
||||
webgl,
|
||||
@@ -369,17 +450,27 @@ namespace Canvas3D {
|
||||
if (!reprRenderObjects.has(repr)) return;
|
||||
scene.update(repr.renderObjects, !!keepSphere);
|
||||
} else {
|
||||
scene.update(void 0, !!keepSphere)
|
||||
scene.update(void 0, !!keepSphere);
|
||||
}
|
||||
},
|
||||
clear: () => {
|
||||
reprUpdatedSubscriptions.forEach(v => v.unsubscribe())
|
||||
reprUpdatedSubscriptions.clear()
|
||||
reprRenderObjects.clear()
|
||||
scene.clear()
|
||||
debugHelper.clear()
|
||||
requestDraw(true)
|
||||
reprCount.next(reprRenderObjects.size)
|
||||
reprUpdatedSubscriptions.forEach(v => v.unsubscribe());
|
||||
reprUpdatedSubscriptions.clear();
|
||||
reprRenderObjects.clear();
|
||||
scene.clear();
|
||||
debugHelper.clear();
|
||||
requestDraw(true);
|
||||
reprCount.next(reprRenderObjects.size);
|
||||
},
|
||||
syncVisibility: () => {
|
||||
if (camera.state.radiusMax === 0) {
|
||||
cameraResetRequested = true;
|
||||
nextCameraResetDuration = 0;
|
||||
}
|
||||
|
||||
if (scene.syncVisibility()) {
|
||||
if (debugHelper.isEnabled) debugHelper.update();
|
||||
}
|
||||
},
|
||||
|
||||
// draw,
|
||||
@@ -390,55 +481,81 @@ namespace Canvas3D {
|
||||
getLoci,
|
||||
|
||||
handleResize,
|
||||
requestCameraReset: (durationMs) => {
|
||||
nextCameraResetDuration = durationMs;
|
||||
requestCameraReset: options => {
|
||||
nextCameraResetDuration = options?.durationMs;
|
||||
nextCameraResetSnapshot = options?.snapshot;
|
||||
cameraResetRequested = true;
|
||||
},
|
||||
camera,
|
||||
boundingSphere: scene.boundingSphere,
|
||||
downloadScreenshot: () => {
|
||||
// TODO
|
||||
},
|
||||
getPixelData: (variant: GraphicsRenderVariant) => {
|
||||
switch (variant) {
|
||||
case 'color': return webgl.getDrawingBufferPixelData()
|
||||
case 'pickObject': return pickPass.objectPickTarget.getPixelData()
|
||||
case 'pickInstance': return pickPass.instancePickTarget.getPixelData()
|
||||
case 'pickGroup': return pickPass.groupPickTarget.getPixelData()
|
||||
case 'depth': return readTexture(webgl, drawPass.depthTexture) as PixelData
|
||||
case 'color': return webgl.getDrawingBufferPixelData();
|
||||
case 'pickObject': return pickPass.objectPickTarget.getPixelData();
|
||||
case 'pickInstance': return pickPass.instancePickTarget.getPixelData();
|
||||
case 'pickGroup': return pickPass.groupPickTarget.getPixelData();
|
||||
case 'depth': return readTexture(webgl, drawPass.depthTexture) as PixelData;
|
||||
}
|
||||
},
|
||||
didDraw,
|
||||
reprCount,
|
||||
setProps: (props: Partial<Canvas3DProps>) => {
|
||||
if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
|
||||
camera.setState({ mode: props.cameraMode })
|
||||
}
|
||||
if (props.cameraFog !== undefined && props.cameraFog !== camera.state.fog) {
|
||||
camera.setState({ fog: props.cameraFog })
|
||||
}
|
||||
if (props.cameraClipFar !== undefined && props.cameraClipFar !== camera.state.clipFar) {
|
||||
camera.setState({ clipFar: props.cameraClipFar })
|
||||
}
|
||||
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs
|
||||
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground
|
||||
setProps: (properties) => {
|
||||
const props: Partial<Canvas3DProps> = typeof properties === 'function'
|
||||
? produce(getProps(), properties)
|
||||
: properties;
|
||||
|
||||
if (props.postprocessing) postprocessing.setProps(props.postprocessing)
|
||||
if (props.multiSample) multiSample.setProps(props.multiSample)
|
||||
if (props.renderer) renderer.setProps(props.renderer)
|
||||
if (props.trackball) controls.setProps(props.trackball)
|
||||
if (props.debug) debugHelper.setProps(props.debug)
|
||||
requestDraw(true)
|
||||
const cameraState: Partial<Camera.Snapshot> = Object.create(null);
|
||||
if (props.camera && props.camera.mode !== undefined && props.camera.mode !== camera.state.mode) {
|
||||
cameraState.mode = props.camera.mode;
|
||||
}
|
||||
if (props.cameraFog !== undefined) {
|
||||
const newFog = props.cameraFog.name === 'on' ? props.cameraFog.params.intensity : 0;
|
||||
if (newFog !== camera.state.fog) cameraState.fog = newFog;
|
||||
}
|
||||
if (props.cameraClipping !== undefined) {
|
||||
if (props.cameraClipping.far !== undefined && props.cameraClipping.far !== camera.state.clipFar) {
|
||||
cameraState.clipFar = props.cameraClipping.far;
|
||||
}
|
||||
if (props.cameraClipping.radius !== undefined) {
|
||||
const radius = (scene.boundingSphere.radius / 100) * (100 - props.cameraClipping.radius);
|
||||
if (radius > 0 && radius !== cameraState.radius) {
|
||||
// if radius = 0, NaNs happen
|
||||
cameraState.radius = Math.max(radius, 0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(cameraState).length > 0) camera.setState(cameraState);
|
||||
|
||||
if (props.camera?.helper) drawPass.setProps({ cameraHelper: props.camera.helper });
|
||||
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs;
|
||||
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground;
|
||||
|
||||
if (props.postprocessing) postprocessing.setProps(props.postprocessing);
|
||||
if (props.multiSample) multiSample.setProps(props.multiSample);
|
||||
if (props.renderer) renderer.setProps(props.renderer);
|
||||
if (props.trackball) controls.setProps(props.trackball);
|
||||
if (props.debug) debugHelper.setProps(props.debug);
|
||||
|
||||
requestDraw(true);
|
||||
},
|
||||
getImagePass: (props: Partial<ImageProps> = {}) => {
|
||||
return new ImagePass(webgl, renderer, scene, camera, debugHelper, props)
|
||||
return new ImagePass(webgl, renderer, scene, camera, debugHelper, props);
|
||||
},
|
||||
|
||||
get props() {
|
||||
const radius = scene.boundingSphere.radius > 0
|
||||
? 100 - Math.round((camera.transition.target.radius / scene.boundingSphere.radius) * 100)
|
||||
: 0;
|
||||
|
||||
return {
|
||||
cameraMode: camera.state.mode,
|
||||
cameraFog: camera.state.fog,
|
||||
cameraClipFar: camera.state.clipFar,
|
||||
camera: {
|
||||
mode: camera.state.mode,
|
||||
helper: { ...drawPass.props.cameraHelper }
|
||||
},
|
||||
cameraFog: camera.state.fog > 0
|
||||
? { name: 'on' as const, params: { intensity: camera.state.fog } }
|
||||
: { name: 'off' as const, params: {} },
|
||||
cameraClipping: { far: camera.state.clipFar, radius },
|
||||
cameraResetDurationMs: p.cameraResetDurationMs,
|
||||
transparentBackground: p.transparentBackground,
|
||||
|
||||
@@ -447,43 +564,43 @@ namespace Canvas3D {
|
||||
renderer: { ...renderer.props },
|
||||
trackball: { ...controls.props },
|
||||
debug: { ...debugHelper.props }
|
||||
}
|
||||
};
|
||||
},
|
||||
get input() {
|
||||
return input
|
||||
return input;
|
||||
},
|
||||
get stats() {
|
||||
return renderer.stats
|
||||
return renderer.stats;
|
||||
},
|
||||
get interaction() {
|
||||
return interactionHelper.events
|
||||
return interactionHelper.events;
|
||||
},
|
||||
dispose: () => {
|
||||
contextRestoredSub.unsubscribe()
|
||||
contextRestoredSub.unsubscribe();
|
||||
|
||||
scene.clear()
|
||||
debugHelper.clear()
|
||||
input.dispose()
|
||||
controls.dispose()
|
||||
renderer.dispose()
|
||||
interactionHelper.dispose()
|
||||
scene.clear();
|
||||
debugHelper.clear();
|
||||
input.dispose();
|
||||
controls.dispose();
|
||||
renderer.dispose();
|
||||
interactionHelper.dispose();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function handleResize() {
|
||||
width = gl.drawingBufferWidth
|
||||
height = gl.drawingBufferHeight
|
||||
width = gl.drawingBufferWidth;
|
||||
height = gl.drawingBufferHeight;
|
||||
|
||||
renderer.setViewport(0, 0, width, height)
|
||||
Viewport.set(camera.viewport, 0, 0, width, height)
|
||||
Viewport.set(controls.viewport, 0, 0, width, height)
|
||||
renderer.setViewport(0, 0, width, height);
|
||||
Viewport.set(camera.viewport, 0, 0, width, height);
|
||||
Viewport.set(controls.viewport, 0, 0, width, height);
|
||||
|
||||
drawPass.setSize(width, height)
|
||||
pickPass.setSize(width, height)
|
||||
postprocessing.setSize(width, height)
|
||||
multiSample.setSize(width, height)
|
||||
drawPass.setSize(width, height);
|
||||
pickPass.setSize(width, height);
|
||||
postprocessing.setSize(width, height);
|
||||
multiSample.setSize(width, height);
|
||||
|
||||
requestDraw(true)
|
||||
requestDraw(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,22 +16,22 @@ import { Camera } from '../camera';
|
||||
import { absMax } from '../../mol-math/misc';
|
||||
import { Binding } from '../../mol-util/binding';
|
||||
|
||||
const B = ButtonsType
|
||||
const M = ModifiersKeys
|
||||
const Trigger = Binding.Trigger
|
||||
const B = ButtonsType;
|
||||
const M = ModifiersKeys;
|
||||
const Trigger = Binding.Trigger;
|
||||
|
||||
export const DefaultTrackballBindings = {
|
||||
dragRotate: Binding([Trigger(B.Flag.Primary, M.create())], 'Rotate the 3D scene by dragging using ${triggers}'),
|
||||
dragRotateZ: Binding([Trigger(B.Flag.Primary, M.create({ shift: true }))], 'Rotate the 3D scene around the z-axis by dragging using ${triggers}'),
|
||||
dragPan: Binding([Trigger(B.Flag.Secondary, M.create()), Trigger(B.Flag.Primary, M.create({ control: true }))], 'Pan the 3D scene by dragging using ${triggers}'),
|
||||
dragRotate: Binding([Trigger(B.Flag.Primary, M.create())], 'Rotate', 'Drag using ${triggers}'),
|
||||
dragRotateZ: Binding([Trigger(B.Flag.Primary, M.create({ shift: true }))], 'Rotate around z-axis', 'Drag using ${triggers}'),
|
||||
dragPan: Binding([Trigger(B.Flag.Secondary, M.create()), Trigger(B.Flag.Primary, M.create({ control: true }))], 'Pan', 'Drag using ${triggers}'),
|
||||
dragZoom: Binding.Empty,
|
||||
dragFocus: Binding([Trigger(B.Flag.Forth, M.create())], 'Focus the 3D scene by dragging using ${triggers}'),
|
||||
dragFocusZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Focus and zoom the 3D scene by dragging using ${triggers}'),
|
||||
dragFocus: Binding([Trigger(B.Flag.Forth, M.create())], 'Focus', 'Drag using ${triggers}'),
|
||||
dragFocusZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Focus and zoom', 'Drag using ${triggers}'),
|
||||
|
||||
scrollZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Zoom the 3D scene by scrolling using ${triggers}'),
|
||||
scrollFocus: Binding([Trigger(B.Flag.Auxilary, M.create({ shift: true }))], 'Focus the 3D scene by scrolling using ${triggers}'),
|
||||
scrollZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Zoom', 'Scroll using ${triggers}'),
|
||||
scrollFocus: Binding([Trigger(B.Flag.Auxilary, M.create({ shift: true }))], 'Clip', 'Scroll using ${triggers}'),
|
||||
scrollFocusZoom: Binding.Empty,
|
||||
}
|
||||
};
|
||||
|
||||
export const TrackballControlsParams = {
|
||||
noScroll: PD.Boolean(true, { isHidden: true }),
|
||||
@@ -41,7 +41,7 @@ export const TrackballControlsParams = {
|
||||
panSpeed: PD.Numeric(0.8, { 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: -100, max: 100, step: 1 }),
|
||||
spinSpeed: PD.Numeric(1, { min: -20, max: 20, step: 1 }),
|
||||
|
||||
staticMoving: PD.Boolean(true, { isHidden: true }),
|
||||
dynamicDampingFactor: PD.Numeric(0.2, {}, { isHidden: true }),
|
||||
@@ -50,10 +50,10 @@ export const TrackballControlsParams = {
|
||||
maxDistance: PD.Numeric(1e150, {}, { isHidden: true }),
|
||||
|
||||
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true })
|
||||
}
|
||||
};
|
||||
export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams>
|
||||
|
||||
export { TrackballControls }
|
||||
export { TrackballControls };
|
||||
interface TrackballControls {
|
||||
viewport: Viewport
|
||||
|
||||
@@ -66,48 +66,48 @@ interface TrackballControls {
|
||||
}
|
||||
namespace TrackballControls {
|
||||
export function create(input: InputObserver, camera: Camera, props: Partial<TrackballControlsProps> = {}): TrackballControls {
|
||||
const p = { ...PD.getDefaultValues(TrackballControlsParams), ...props }
|
||||
const p = { ...PD.getDefaultValues(TrackballControlsParams), ...props };
|
||||
|
||||
const viewport = Viewport()
|
||||
const viewport = Viewport();
|
||||
|
||||
let disposed = false
|
||||
let disposed = false;
|
||||
|
||||
const dragSub = input.drag.subscribe(onDrag)
|
||||
const interactionEndSub = input.interactionEnd.subscribe(onInteractionEnd)
|
||||
const wheelSub = input.wheel.subscribe(onWheel)
|
||||
const pinchSub = input.pinch.subscribe(onPinch)
|
||||
const dragSub = input.drag.subscribe(onDrag);
|
||||
const interactionEndSub = input.interactionEnd.subscribe(onInteractionEnd);
|
||||
const wheelSub = input.wheel.subscribe(onWheel);
|
||||
const pinchSub = input.pinch.subscribe(onPinch);
|
||||
|
||||
let _isInteracting = false;
|
||||
|
||||
// For internal use
|
||||
const lastPosition = Vec3()
|
||||
const lastPosition = Vec3();
|
||||
|
||||
const _eye = Vec3()
|
||||
const _eye = Vec3();
|
||||
|
||||
const _rotPrev = Vec2()
|
||||
const _rotCurr = Vec2()
|
||||
const _rotLastAxis = Vec3()
|
||||
let _rotLastAngle = 0
|
||||
const _rotPrev = Vec2();
|
||||
const _rotCurr = Vec2();
|
||||
const _rotLastAxis = Vec3();
|
||||
let _rotLastAngle = 0;
|
||||
|
||||
const _zRotPrev = Vec2()
|
||||
const _zRotCurr = Vec2()
|
||||
let _zRotLastAngle = 0
|
||||
const _zRotPrev = Vec2();
|
||||
const _zRotCurr = Vec2();
|
||||
let _zRotLastAngle = 0;
|
||||
|
||||
const _zoomStart = Vec2()
|
||||
const _zoomEnd = Vec2()
|
||||
const _zoomStart = Vec2();
|
||||
const _zoomEnd = Vec2();
|
||||
|
||||
const _focusStart = Vec2()
|
||||
const _focusEnd = Vec2()
|
||||
const _focusStart = Vec2();
|
||||
const _focusEnd = Vec2();
|
||||
|
||||
const _panStart = Vec2()
|
||||
const _panEnd = Vec2()
|
||||
const _panStart = Vec2();
|
||||
const _panEnd = Vec2();
|
||||
|
||||
// Initial values for reseting
|
||||
const target0 = Vec3.clone(camera.target)
|
||||
const position0 = Vec3.clone(camera.position)
|
||||
const up0 = Vec3.clone(camera.up)
|
||||
const target0 = Vec3.clone(camera.target);
|
||||
const position0 = Vec3.clone(camera.position);
|
||||
const up0 = Vec3.clone(camera.up);
|
||||
|
||||
const mouseOnScreenVec2 = Vec2()
|
||||
const mouseOnScreenVec2 = Vec2();
|
||||
function getMouseOnScreen(pageX: number, pageY: number) {
|
||||
return Vec2.set(
|
||||
mouseOnScreenVec2,
|
||||
@@ -116,7 +116,7 @@ namespace TrackballControls {
|
||||
);
|
||||
}
|
||||
|
||||
const mouseOnCircleVec2 = Vec2()
|
||||
const mouseOnCircleVec2 = Vec2();
|
||||
function getMouseOnCircle(pageX: number, pageY: number) {
|
||||
return Vec2.set(
|
||||
mouseOnCircleVec2,
|
||||
@@ -125,143 +125,147 @@ namespace TrackballControls {
|
||||
);
|
||||
}
|
||||
|
||||
const rotAxis = Vec3()
|
||||
const rotQuat = Quat()
|
||||
const rotEyeDir = Vec3()
|
||||
const rotObjUpDir = Vec3()
|
||||
const rotObjSideDir = Vec3()
|
||||
const rotMoveDir = Vec3()
|
||||
const rotAxis = Vec3();
|
||||
const rotQuat = Quat();
|
||||
const rotEyeDir = Vec3();
|
||||
const rotObjUpDir = Vec3();
|
||||
const rotObjSideDir = Vec3();
|
||||
const rotMoveDir = Vec3();
|
||||
|
||||
function rotateCamera() {
|
||||
const dx = _rotCurr[0] - _rotPrev[0]
|
||||
const dy = _rotCurr[1] - _rotPrev[1]
|
||||
const dx = _rotCurr[0] - _rotPrev[0];
|
||||
const dy = _rotCurr[1] - _rotPrev[1];
|
||||
Vec3.set(rotMoveDir, dx, dy, 0);
|
||||
|
||||
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed;
|
||||
|
||||
if (angle) {
|
||||
Vec3.sub(_eye, camera.position, camera.target)
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
|
||||
Vec3.normalize(rotEyeDir, _eye)
|
||||
Vec3.normalize(rotObjUpDir, camera.up)
|
||||
Vec3.normalize(rotObjSideDir, Vec3.cross(rotObjSideDir, rotObjUpDir, rotEyeDir))
|
||||
Vec3.normalize(rotEyeDir, _eye);
|
||||
Vec3.normalize(rotObjUpDir, camera.up);
|
||||
Vec3.normalize(rotObjSideDir, Vec3.cross(rotObjSideDir, rotObjUpDir, rotEyeDir));
|
||||
|
||||
Vec3.setMagnitude(rotObjUpDir, rotObjUpDir, dy)
|
||||
Vec3.setMagnitude(rotObjSideDir, rotObjSideDir, dx)
|
||||
Vec3.setMagnitude(rotObjUpDir, rotObjUpDir, dy);
|
||||
Vec3.setMagnitude(rotObjSideDir, rotObjSideDir, dx);
|
||||
|
||||
Vec3.add(rotMoveDir, rotObjUpDir, rotObjSideDir)
|
||||
Vec3.normalize(rotAxis, Vec3.cross(rotAxis, rotMoveDir, _eye))
|
||||
Quat.setAxisAngle(rotQuat, rotAxis, angle)
|
||||
Vec3.add(rotMoveDir, rotObjUpDir, rotObjSideDir);
|
||||
Vec3.normalize(rotAxis, Vec3.cross(rotAxis, rotMoveDir, _eye));
|
||||
Quat.setAxisAngle(rotQuat, rotAxis, angle);
|
||||
|
||||
Vec3.transformQuat(_eye, _eye, rotQuat)
|
||||
Vec3.transformQuat(camera.up, camera.up, rotQuat)
|
||||
Vec3.transformQuat(_eye, _eye, rotQuat);
|
||||
Vec3.transformQuat(camera.up, camera.up, rotQuat);
|
||||
|
||||
Vec3.copy(_rotLastAxis, rotAxis)
|
||||
Vec3.copy(_rotLastAxis, rotAxis);
|
||||
_rotLastAngle = angle;
|
||||
} else if (!p.staticMoving && _rotLastAngle) {
|
||||
_rotLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
|
||||
Vec3.sub(_eye, camera.position, camera.target)
|
||||
Quat.setAxisAngle(rotQuat, _rotLastAxis, _rotLastAngle)
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
Quat.setAxisAngle(rotQuat, _rotLastAxis, _rotLastAngle);
|
||||
|
||||
Vec3.transformQuat(_eye, _eye, rotQuat)
|
||||
Vec3.transformQuat(camera.up, camera.up, rotQuat)
|
||||
Vec3.transformQuat(_eye, _eye, rotQuat);
|
||||
Vec3.transformQuat(camera.up, camera.up, rotQuat);
|
||||
}
|
||||
|
||||
Vec2.copy(_rotPrev, _rotCurr)
|
||||
Vec2.copy(_rotPrev, _rotCurr);
|
||||
}
|
||||
|
||||
const zRotQuat = Quat()
|
||||
const zRotQuat = Quat();
|
||||
|
||||
function zRotateCamera() {
|
||||
const dx = _zRotCurr[0] - _zRotPrev[0]
|
||||
const dy = _zRotCurr[1] - _zRotPrev[1]
|
||||
const angle = p.rotateSpeed * (-dx + dy) * -0.05
|
||||
const dx = _zRotCurr[0] - _zRotPrev[0];
|
||||
const dy = _zRotCurr[1] - _zRotPrev[1];
|
||||
const angle = p.rotateSpeed * (-dx + dy) * -0.05;
|
||||
|
||||
if (angle) {
|
||||
Vec3.sub(_eye, camera.position, camera.target)
|
||||
Quat.setAxisAngle(zRotQuat, _eye, angle)
|
||||
Vec3.transformQuat(camera.up, camera.up, zRotQuat)
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
Quat.setAxisAngle(zRotQuat, _eye, angle);
|
||||
Vec3.transformQuat(camera.up, camera.up, zRotQuat);
|
||||
_zRotLastAngle = angle;
|
||||
} else if (!p.staticMoving && _zRotLastAngle) {
|
||||
_zRotLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
|
||||
Vec3.sub(_eye, camera.position, camera.target)
|
||||
Quat.setAxisAngle(zRotQuat, _eye, _zRotLastAngle)
|
||||
Vec3.transformQuat(camera.up, camera.up, zRotQuat)
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
Quat.setAxisAngle(zRotQuat, _eye, _zRotLastAngle);
|
||||
Vec3.transformQuat(camera.up, camera.up, zRotQuat);
|
||||
}
|
||||
|
||||
Vec2.copy(_zRotPrev, _zRotCurr)
|
||||
Vec2.copy(_zRotPrev, _zRotCurr);
|
||||
}
|
||||
|
||||
function zoomCamera() {
|
||||
const factor = 1.0 + (_zoomEnd[1] - _zoomStart[1]) * p.zoomSpeed
|
||||
const factor = 1.0 + (_zoomEnd[1] - _zoomStart[1]) * p.zoomSpeed;
|
||||
if (factor !== 1.0 && factor > 0.0) {
|
||||
Vec3.scale(_eye, _eye, factor)
|
||||
Vec3.scale(_eye, _eye, factor);
|
||||
}
|
||||
|
||||
if (p.staticMoving) {
|
||||
Vec2.copy(_zoomStart, _zoomEnd)
|
||||
Vec2.copy(_zoomStart, _zoomEnd);
|
||||
} else {
|
||||
_zoomStart[1] += (_zoomEnd[1] - _zoomStart[1]) * p.dynamicDampingFactor
|
||||
_zoomStart[1] += (_zoomEnd[1] - _zoomStart[1]) * p.dynamicDampingFactor;
|
||||
}
|
||||
}
|
||||
|
||||
function focusCamera() {
|
||||
const factor = (_focusEnd[1] - _focusStart[1]) * p.zoomSpeed
|
||||
const factor = (_focusEnd[1] - _focusStart[1]) * p.zoomSpeed;
|
||||
if (factor !== 0.0) {
|
||||
const radiusNear = Math.max(1, camera.state.radiusNear + 10 * factor)
|
||||
camera.setState({ radiusNear })
|
||||
const radius = Math.max(1, camera.state.radius + camera.state.radius * factor);
|
||||
camera.setState({ radius });
|
||||
}
|
||||
|
||||
if (p.staticMoving) {
|
||||
Vec2.copy(_focusStart, _focusEnd)
|
||||
Vec2.copy(_focusStart, _focusEnd);
|
||||
} else {
|
||||
_focusStart[1] += (_focusEnd[1] - _focusStart[1]) * p.dynamicDampingFactor
|
||||
_focusStart[1] += (_focusEnd[1] - _focusStart[1]) * p.dynamicDampingFactor;
|
||||
}
|
||||
}
|
||||
|
||||
const panMouseChange = Vec2()
|
||||
const panObjUp = Vec3()
|
||||
const panOffset = Vec3()
|
||||
const panMouseChange = Vec2();
|
||||
const panObjUp = Vec3();
|
||||
const panOffset = Vec3();
|
||||
|
||||
function panCamera() {
|
||||
Vec2.sub(panMouseChange, Vec2.copy(panMouseChange, _panEnd), _panStart)
|
||||
Vec2.sub(panMouseChange, Vec2.copy(panMouseChange, _panEnd), _panStart);
|
||||
|
||||
if (Vec2.squaredMagnitude(panMouseChange)) {
|
||||
Vec2.scale(panMouseChange, panMouseChange, Vec3.magnitude(_eye) * p.panSpeed)
|
||||
Vec2.scale(panMouseChange, panMouseChange, Vec3.magnitude(_eye) * p.panSpeed);
|
||||
|
||||
Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), camera.up)
|
||||
Vec3.setMagnitude(panOffset, panOffset, panMouseChange[0])
|
||||
Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), camera.up);
|
||||
Vec3.setMagnitude(panOffset, panOffset, panMouseChange[0]);
|
||||
|
||||
Vec3.setMagnitude(panObjUp, camera.up, panMouseChange[1])
|
||||
Vec3.add(panOffset, panOffset, panObjUp)
|
||||
Vec3.setMagnitude(panObjUp, camera.up, panMouseChange[1]);
|
||||
Vec3.add(panOffset, panOffset, panObjUp);
|
||||
|
||||
Vec3.add(camera.position, camera.position, panOffset)
|
||||
Vec3.add(camera.target, camera.target, panOffset)
|
||||
Vec3.add(camera.position, camera.position, panOffset);
|
||||
Vec3.add(camera.target, camera.target, panOffset);
|
||||
|
||||
if (p.staticMoving) {
|
||||
Vec2.copy(_panStart, _panEnd)
|
||||
Vec2.copy(_panStart, _panEnd);
|
||||
} else {
|
||||
Vec2.sub(panMouseChange, _panEnd, _panStart)
|
||||
Vec2.scale(panMouseChange, panMouseChange, p.dynamicDampingFactor)
|
||||
Vec2.add(_panStart, _panStart, panMouseChange)
|
||||
Vec2.sub(panMouseChange, _panEnd, _panStart);
|
||||
Vec2.scale(panMouseChange, panMouseChange, p.dynamicDampingFactor);
|
||||
Vec2.add(_panStart, _panStart, panMouseChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensure the distance between object and target is within the min/max distance */
|
||||
/**
|
||||
* Ensure the distance between object and target is within the min/max distance
|
||||
* and not too large compared to `camera.state.radiusMax`
|
||||
*/
|
||||
function checkDistances() {
|
||||
if (Vec3.squaredMagnitude(_eye) > p.maxDistance * p.maxDistance) {
|
||||
Vec3.setMagnitude(_eye, _eye, p.maxDistance)
|
||||
Vec3.add(camera.position, camera.target, _eye)
|
||||
Vec2.copy(_zoomStart, _zoomEnd)
|
||||
Vec2.copy(_focusStart, _focusEnd)
|
||||
const maxDistance = Math.min(Math.max(camera.state.radiusMax * 1000, 0.01), p.maxDistance);
|
||||
if (Vec3.squaredMagnitude(_eye) > maxDistance * maxDistance) {
|
||||
Vec3.setMagnitude(_eye, _eye, maxDistance);
|
||||
Vec3.add(camera.position, camera.target, _eye);
|
||||
Vec2.copy(_zoomStart, _zoomEnd);
|
||||
Vec2.copy(_focusStart, _focusEnd);
|
||||
}
|
||||
|
||||
if (Vec3.squaredMagnitude(_eye) < p.minDistance * p.minDistance) {
|
||||
Vec3.setMagnitude(_eye, _eye, p.minDistance)
|
||||
Vec3.add(camera.position, camera.target, _eye)
|
||||
Vec2.copy(_zoomStart, _zoomEnd)
|
||||
Vec2.copy(_focusStart, _focusEnd)
|
||||
Vec3.setMagnitude(_eye, _eye, p.minDistance);
|
||||
Vec3.add(camera.position, camera.target, _eye);
|
||||
Vec2.copy(_zoomStart, _zoomEnd);
|
||||
Vec2.copy(_focusStart, _focusEnd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,19 +275,19 @@ namespace TrackballControls {
|
||||
if (lastUpdated === t) return;
|
||||
if (p.spin) spin(t - lastUpdated);
|
||||
|
||||
Vec3.sub(_eye, camera.position, camera.target)
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
|
||||
rotateCamera()
|
||||
zRotateCamera()
|
||||
zoomCamera()
|
||||
focusCamera()
|
||||
panCamera()
|
||||
rotateCamera();
|
||||
zRotateCamera();
|
||||
zoomCamera();
|
||||
focusCamera();
|
||||
panCamera();
|
||||
|
||||
Vec3.add(camera.position, camera.target, _eye)
|
||||
checkDistances()
|
||||
Vec3.add(camera.position, camera.target, _eye);
|
||||
checkDistances();
|
||||
|
||||
if (Vec3.squaredDistance(lastPosition, camera.position) > EPSILON) {
|
||||
Vec3.copy(lastPosition, camera.position)
|
||||
Vec3.copy(lastPosition, camera.position);
|
||||
}
|
||||
|
||||
lastUpdated = t;
|
||||
@@ -291,12 +295,12 @@ namespace TrackballControls {
|
||||
|
||||
/** Reset object's vectors and the target vector to their initial values */
|
||||
function reset() {
|
||||
Vec3.copy(camera.target, target0)
|
||||
Vec3.copy(camera.position, position0)
|
||||
Vec3.copy(camera.up, up0)
|
||||
Vec3.copy(camera.target, target0);
|
||||
Vec3.copy(camera.position, position0);
|
||||
Vec3.copy(camera.up, up0);
|
||||
|
||||
Vec3.sub(_eye, camera.position, camera.target)
|
||||
Vec3.copy(lastPosition, camera.position)
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
Vec3.copy(lastPosition, camera.position);
|
||||
}
|
||||
|
||||
// listeners
|
||||
@@ -304,48 +308,48 @@ namespace TrackballControls {
|
||||
function onDrag({ pageX, pageY, buttons, modifiers, isStart }: DragInput) {
|
||||
_isInteracting = true;
|
||||
|
||||
const dragRotate = Binding.match(p.bindings.dragRotate, buttons, modifiers)
|
||||
const dragRotateZ = Binding.match(p.bindings.dragRotateZ, buttons, modifiers)
|
||||
const dragPan = Binding.match(p.bindings.dragPan, buttons, modifiers)
|
||||
const dragZoom = Binding.match(p.bindings.dragZoom, buttons, modifiers)
|
||||
const dragFocus = Binding.match(p.bindings.dragFocus, buttons, modifiers)
|
||||
const dragFocusZoom = Binding.match(p.bindings.dragFocusZoom, buttons, modifiers)
|
||||
const dragRotate = Binding.match(p.bindings.dragRotate, buttons, modifiers);
|
||||
const dragRotateZ = Binding.match(p.bindings.dragRotateZ, buttons, modifiers);
|
||||
const dragPan = Binding.match(p.bindings.dragPan, buttons, modifiers);
|
||||
const dragZoom = Binding.match(p.bindings.dragZoom, buttons, modifiers);
|
||||
const dragFocus = Binding.match(p.bindings.dragFocus, buttons, modifiers);
|
||||
const dragFocusZoom = Binding.match(p.bindings.dragFocusZoom, buttons, modifiers);
|
||||
|
||||
getMouseOnCircle(pageX, pageY)
|
||||
getMouseOnScreen(pageX, pageY)
|
||||
getMouseOnCircle(pageX, pageY);
|
||||
getMouseOnScreen(pageX, pageY);
|
||||
|
||||
if (isStart) {
|
||||
if (dragRotate) {
|
||||
Vec2.copy(_rotCurr, mouseOnCircleVec2)
|
||||
Vec2.copy(_rotPrev, _rotCurr)
|
||||
Vec2.copy(_rotCurr, mouseOnCircleVec2);
|
||||
Vec2.copy(_rotPrev, _rotCurr);
|
||||
}
|
||||
if (dragRotateZ) {
|
||||
Vec2.copy(_zRotCurr, mouseOnCircleVec2)
|
||||
Vec2.copy(_zRotPrev, _zRotCurr)
|
||||
Vec2.copy(_zRotCurr, mouseOnCircleVec2);
|
||||
Vec2.copy(_zRotPrev, _zRotCurr);
|
||||
}
|
||||
if (dragZoom || dragFocusZoom) {
|
||||
Vec2.copy(_zoomStart, mouseOnScreenVec2)
|
||||
Vec2.copy(_zoomEnd, _zoomStart)
|
||||
Vec2.copy(_zoomStart, mouseOnScreenVec2);
|
||||
Vec2.copy(_zoomEnd, _zoomStart);
|
||||
}
|
||||
if (dragFocus) {
|
||||
Vec2.copy(_focusStart, mouseOnScreenVec2)
|
||||
Vec2.copy(_focusEnd, _focusStart)
|
||||
Vec2.copy(_focusStart, mouseOnScreenVec2);
|
||||
Vec2.copy(_focusEnd, _focusStart);
|
||||
}
|
||||
if (dragPan) {
|
||||
Vec2.copy(_panStart, mouseOnScreenVec2)
|
||||
Vec2.copy(_panEnd, _panStart)
|
||||
Vec2.copy(_panStart, mouseOnScreenVec2);
|
||||
Vec2.copy(_panEnd, _panStart);
|
||||
}
|
||||
}
|
||||
|
||||
if (dragRotate) Vec2.copy(_rotCurr, mouseOnCircleVec2)
|
||||
if (dragRotateZ) Vec2.copy(_zRotCurr, mouseOnCircleVec2)
|
||||
if (dragZoom || dragFocusZoom) Vec2.copy(_zoomEnd, mouseOnScreenVec2)
|
||||
if (dragFocus) Vec2.copy(_focusEnd, mouseOnScreenVec2)
|
||||
if (dragRotate) Vec2.copy(_rotCurr, mouseOnCircleVec2);
|
||||
if (dragRotateZ) Vec2.copy(_zRotCurr, mouseOnCircleVec2);
|
||||
if (dragZoom || dragFocusZoom) Vec2.copy(_zoomEnd, mouseOnScreenVec2);
|
||||
if (dragFocus) Vec2.copy(_focusEnd, mouseOnScreenVec2);
|
||||
if (dragFocusZoom) {
|
||||
const dist = Vec3.distance(camera.state.position, camera.state.target);
|
||||
camera.setState({ radiusNear: dist / 5 })
|
||||
camera.setState({ radius: dist / 5 });
|
||||
}
|
||||
if (dragPan) Vec2.copy(_panEnd, mouseOnScreenVec2)
|
||||
if (dragPan) Vec2.copy(_panEnd, mouseOnScreenVec2);
|
||||
}
|
||||
|
||||
function onInteractionEnd() {
|
||||
@@ -353,30 +357,30 @@ namespace TrackballControls {
|
||||
}
|
||||
|
||||
function onWheel({ dx, dy, dz, buttons, modifiers }: WheelInput) {
|
||||
const delta = absMax(dx, dy, dz)
|
||||
const delta = absMax(dx, dy, dz);
|
||||
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
|
||||
_zoomEnd[1] += delta * 0.0001
|
||||
_zoomEnd[1] += delta * 0.0001;
|
||||
}
|
||||
if (Binding.match(p.bindings.scrollFocus, buttons, modifiers)) {
|
||||
_focusEnd[1] += delta * 0.0001
|
||||
_focusEnd[1] += delta * 0.0001;
|
||||
}
|
||||
}
|
||||
|
||||
function onPinch({ fraction, buttons, modifiers }: PinchInput) {
|
||||
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
|
||||
_isInteracting = true;
|
||||
_zoomEnd[1] += (fraction - 1) * 0.1
|
||||
_zoomEnd[1] += (fraction - 1) * 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
function dispose() {
|
||||
if (disposed) return
|
||||
disposed = true
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
|
||||
dragSub.unsubscribe()
|
||||
wheelSub.unsubscribe()
|
||||
pinchSub.unsubscribe()
|
||||
interactionEndSub.unsubscribe()
|
||||
dragSub.unsubscribe();
|
||||
wheelSub.unsubscribe();
|
||||
pinchSub.unsubscribe();
|
||||
interactionEndSub.unsubscribe();
|
||||
}
|
||||
|
||||
const _spinSpeed = Vec2.create(0.005, 0);
|
||||
@@ -392,14 +396,14 @@ namespace TrackballControls {
|
||||
return {
|
||||
viewport,
|
||||
|
||||
get props() { return p as Readonly<TrackballControlsProps> },
|
||||
get props() { return p as Readonly<TrackballControlsProps>; },
|
||||
setProps: (props: Partial<TrackballControlsProps>) => {
|
||||
Object.assign(p, props)
|
||||
Object.assign(p, props);
|
||||
},
|
||||
|
||||
update,
|
||||
reset,
|
||||
dispose
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createRenderObject, GraphicsRenderObject, getNextMaterialId } from '../../mol-gl/render-object'
|
||||
import { createRenderObject, GraphicsRenderObject, getNextMaterialId } from '../../mol-gl/render-object';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
@@ -20,10 +20,11 @@ import { ValueCell } from '../../mol-util';
|
||||
import { Geometry } from '../../mol-geo/geometry/geometry';
|
||||
|
||||
export const DebugHelperParams = {
|
||||
sceneBoundingSpheres: PD.Boolean(false, { description: 'Show scene bounding spheres.' }),
|
||||
objectBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of render objects.' }),
|
||||
instanceBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of instances.' }),
|
||||
}
|
||||
sceneBoundingSpheres: PD.Boolean(false, { description: 'Show full scene bounding spheres.' }),
|
||||
visibleSceneBoundingSpheres: PD.Boolean(false, { description: 'Show visible scene bounding spheres.' }),
|
||||
objectBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of visible render objects.' }),
|
||||
instanceBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of visible instances.' }),
|
||||
};
|
||||
export type DebugHelperParams = typeof DebugHelperParams
|
||||
export type DebugHelperProps = PD.Values<DebugHelperParams>
|
||||
|
||||
@@ -37,108 +38,126 @@ export class BoundingSphereHelper {
|
||||
private objectsData = new Map<GraphicsRenderObject, BoundingSphereData>()
|
||||
private instancesData = new Map<GraphicsRenderObject, BoundingSphereData>()
|
||||
private sceneData: BoundingSphereData | undefined
|
||||
private visibleSceneData: BoundingSphereData | undefined
|
||||
|
||||
constructor(ctx: WebGLContext, parent: Scene, props: Partial<DebugHelperProps>) {
|
||||
this.scene = Scene.create(ctx)
|
||||
this.parent = parent
|
||||
this._props = { ...PD.getDefaultValues(DebugHelperParams), ...props }
|
||||
this.scene = Scene.create(ctx);
|
||||
this.parent = parent;
|
||||
this._props = { ...PD.getDefaultValues(DebugHelperParams), ...props };
|
||||
}
|
||||
|
||||
update() {
|
||||
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.grey)
|
||||
if (newSceneData) this.sceneData = newSceneData
|
||||
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.lightgrey, sceneMaterialId);
|
||||
if (newSceneData) this.sceneData = newSceneData;
|
||||
|
||||
const newVisibleSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphereVisible, this.visibleSceneData, ColorNames.black, visibleSceneMaterialId);
|
||||
if (newVisibleSceneData) this.visibleSceneData = newVisibleSceneData;
|
||||
|
||||
this.parent.forEach((r, ro) => {
|
||||
const objectData = this.objectsData.get(ro)
|
||||
const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato)
|
||||
if (newObjectData) this.objectsData.set(ro, newObjectData)
|
||||
const objectData = this.objectsData.get(ro);
|
||||
const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato, objectMaterialId);
|
||||
if (newObjectData) this.objectsData.set(ro, newObjectData);
|
||||
|
||||
if (ro.type === 'mesh' || ro.type === 'lines' || ro.type === 'points') {
|
||||
const instanceData = this.instancesData.get(ro)
|
||||
const newInstanceData = updateBoundingSphereData(this.scene, r.values.invariantBoundingSphere.ref.value, instanceData, ColorNames.skyblue, {
|
||||
aTransform: ro.values.aTransform,
|
||||
matrix: ro.values.matrix,
|
||||
transform: ro.values.transform,
|
||||
extraTransform: ro.values.extraTransform,
|
||||
uInstanceCount: ro.values.uInstanceCount,
|
||||
instanceCount: ro.values.instanceCount,
|
||||
aInstance: ro.values.aInstance,
|
||||
})
|
||||
if (newInstanceData) this.instancesData.set(ro, newInstanceData)
|
||||
}
|
||||
})
|
||||
const instanceData = this.instancesData.get(ro);
|
||||
const newInstanceData = updateBoundingSphereData(this.scene, r.values.invariantBoundingSphere.ref.value, instanceData, ColorNames.skyblue, instanceMaterialId, {
|
||||
aTransform: ro.values.aTransform,
|
||||
matrix: ro.values.matrix,
|
||||
transform: ro.values.transform,
|
||||
extraTransform: ro.values.extraTransform,
|
||||
uInstanceCount: ro.values.uInstanceCount,
|
||||
instanceCount: ro.values.instanceCount,
|
||||
aInstance: ro.values.aInstance,
|
||||
});
|
||||
if (newInstanceData) this.instancesData.set(ro, newInstanceData);
|
||||
});
|
||||
|
||||
this.objectsData.forEach((objectData, ro) => {
|
||||
if (!this.parent.has(ro)) {
|
||||
this.scene.remove(objectData.renderObject)
|
||||
this.objectsData.delete(ro)
|
||||
this.scene.remove(objectData.renderObject);
|
||||
this.objectsData.delete(ro);
|
||||
}
|
||||
})
|
||||
});
|
||||
this.instancesData.forEach((instanceData, ro) => {
|
||||
if (!this.parent.has(ro)) {
|
||||
this.scene.remove(instanceData.renderObject)
|
||||
this.instancesData.delete(ro)
|
||||
this.scene.remove(instanceData.renderObject);
|
||||
this.instancesData.delete(ro);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.scene.update(void 0, false)
|
||||
this.scene.commit()
|
||||
this.scene.update(void 0, false);
|
||||
this.scene.commit();
|
||||
}
|
||||
|
||||
syncVisibility() {
|
||||
if (this.sceneData) {
|
||||
this.sceneData.renderObject.state.visible = this._props.sceneBoundingSpheres
|
||||
this.sceneData.renderObject.state.visible = this._props.sceneBoundingSpheres;
|
||||
}
|
||||
|
||||
if (this.visibleSceneData) {
|
||||
this.visibleSceneData.renderObject.state.visible = this._props.visibleSceneBoundingSpheres;
|
||||
}
|
||||
|
||||
this.parent.forEach((_, ro) => {
|
||||
const objectData = this.objectsData.get(ro)
|
||||
if (objectData) objectData.renderObject.state.visible = ro.state.visible && this._props.objectBoundingSpheres
|
||||
const objectData = this.objectsData.get(ro);
|
||||
if (objectData) objectData.renderObject.state.visible = ro.state.visible && this._props.objectBoundingSpheres;
|
||||
|
||||
const instanceData = this.instancesData.get(ro)
|
||||
if (instanceData) instanceData.renderObject.state.visible = ro.state.visible && this._props.instanceBoundingSpheres
|
||||
})
|
||||
const instanceData = this.instancesData.get(ro);
|
||||
if (instanceData) instanceData.renderObject.state.visible = ro.state.visible && this._props.instanceBoundingSpheres;
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.sceneData = undefined
|
||||
this.objectsData.clear()
|
||||
this.scene.clear()
|
||||
this.sceneData = undefined;
|
||||
this.objectsData.clear();
|
||||
this.scene.clear();
|
||||
}
|
||||
|
||||
get isEnabled() {
|
||||
return this._props.sceneBoundingSpheres || this._props.objectBoundingSpheres || this._props.instanceBoundingSpheres
|
||||
return (
|
||||
this._props.sceneBoundingSpheres || this._props.visibleSceneBoundingSpheres ||
|
||||
this._props.objectBoundingSpheres || this._props.instanceBoundingSpheres
|
||||
);
|
||||
}
|
||||
get props() { return this._props as Readonly<DebugHelperProps> }
|
||||
get props() { return this._props as Readonly<DebugHelperProps>; }
|
||||
|
||||
setProps (props: Partial<DebugHelperProps>) {
|
||||
Object.assign(this._props, props)
|
||||
if (this.isEnabled) this.update()
|
||||
Object.assign(this._props, props);
|
||||
if (this.isEnabled) this.update();
|
||||
}
|
||||
}
|
||||
|
||||
function updateBoundingSphereData(scene: Scene, boundingSphere: Sphere3D, data: BoundingSphereData | undefined, color: Color, transform?: TransformData) {
|
||||
function updateBoundingSphereData(scene: Scene, boundingSphere: Sphere3D, data: BoundingSphereData | undefined, color: Color, materialId: number, transform?: TransformData) {
|
||||
if (!data || !Sphere3D.equals(data.boundingSphere, boundingSphere)) {
|
||||
const mesh = createBoundingSphereMesh(boundingSphere, data && data.mesh)
|
||||
const renderObject = data ? data.renderObject : createBoundingSphereRenderObject(mesh, color, transform)
|
||||
const mesh = createBoundingSphereMesh(boundingSphere, data && data.mesh);
|
||||
const renderObject = data ? data.renderObject : createBoundingSphereRenderObject(mesh, color, materialId, transform);
|
||||
if (data) {
|
||||
ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(mesh))
|
||||
ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(mesh));
|
||||
} else {
|
||||
scene.add(renderObject)
|
||||
scene.add(renderObject);
|
||||
}
|
||||
return { boundingSphere: Sphere3D.clone(boundingSphere), renderObject, mesh }
|
||||
return { boundingSphere: Sphere3D.clone(boundingSphere), renderObject, mesh };
|
||||
}
|
||||
}
|
||||
|
||||
function createBoundingSphereMesh(boundingSphere: Sphere3D, mesh?: Mesh) {
|
||||
const detail = 2
|
||||
const vertexCount = sphereVertexCount(detail)
|
||||
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh)
|
||||
if (boundingSphere.radius) addSphere(builderState, boundingSphere.center, boundingSphere.radius, detail)
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
const detail = 2;
|
||||
const vertexCount = sphereVertexCount(detail);
|
||||
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh);
|
||||
if (boundingSphere.radius) {
|
||||
addSphere(builderState, boundingSphere.center, boundingSphere.radius, detail);
|
||||
if (Sphere3D.hasExtrema(boundingSphere)) {
|
||||
for (const e of boundingSphere.extrema) addSphere(builderState, e, 1.0, 0);
|
||||
}
|
||||
}
|
||||
return MeshBuilder.getMesh(builderState);
|
||||
}
|
||||
|
||||
const boundingSphereHelberMaterialId = getNextMaterialId()
|
||||
function createBoundingSphereRenderObject(mesh: Mesh, color: Color, transform?: TransformData) {
|
||||
const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform)
|
||||
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, opaque: false }, boundingSphereHelberMaterialId)
|
||||
const sceneMaterialId = getNextMaterialId();
|
||||
const visibleSceneMaterialId = getNextMaterialId();
|
||||
const objectMaterialId = getNextMaterialId();
|
||||
const instanceMaterialId = getNextMaterialId();
|
||||
|
||||
function createBoundingSphereRenderObject(mesh: Mesh, color: Color, materialId: number, transform?: TransformData) {
|
||||
const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform);
|
||||
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, opaque: false, writeDepth: false }, materialId);
|
||||
}
|
||||
175
src/mol-canvas3d/helper/camera-helper.ts
Normal file
175
src/mol-canvas3d/helper/camera-helper.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import Scene from '../../mol-gl/scene';
|
||||
import { Camera } from '../camera';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { Viewport } from '../camera/util';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import produce from 'immer';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
|
||||
// TODO add scale line/grid
|
||||
|
||||
const AxesParams = {
|
||||
...Mesh.Params,
|
||||
alpha: { ...Mesh.Params.alpha, defaultValue: 0.33 },
|
||||
ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
|
||||
colorX: PD.Color(ColorNames.red, { isEssential: true }),
|
||||
colorY: PD.Color(ColorNames.green, { isEssential: true }),
|
||||
colorZ: PD.Color(ColorNames.blue, { isEssential: true }),
|
||||
scale: PD.Numeric(0.33, { min: 0.1, max: 2, step: 0.1 }, { isEssential: true }),
|
||||
};
|
||||
type AxesParams = typeof AxesParams
|
||||
type AxesProps = PD.Values<AxesParams>
|
||||
|
||||
export const CameraHelperParams = {
|
||||
axes: PD.MappedStatic('on', {
|
||||
on: PD.Group(AxesParams),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Show camera orientation axes' }),
|
||||
};
|
||||
export type CameraHelperParams = typeof CameraHelperParams
|
||||
export type CameraHelperProps = PD.Values<CameraHelperParams>
|
||||
|
||||
export class CameraHelper {
|
||||
scene: Scene
|
||||
camera: Camera
|
||||
props: CameraHelperProps = {
|
||||
axes: { name: 'off', params: {} }
|
||||
}
|
||||
|
||||
private renderObject: GraphicsRenderObject | undefined
|
||||
|
||||
constructor(private webgl: WebGLContext, props: Partial<CameraHelperProps> = {}) {
|
||||
this.scene = Scene.create(webgl);
|
||||
|
||||
this.camera = new Camera();
|
||||
Vec3.set(this.camera.up, 0, 1, 0);
|
||||
Vec3.set(this.camera.target, 0, 0, 0);
|
||||
|
||||
this.setProps(props);
|
||||
}
|
||||
|
||||
setProps(props: Partial<CameraHelperProps>) {
|
||||
this.props = produce(this.props, p => {
|
||||
if (props.axes !== undefined) {
|
||||
p.axes.name = props.axes.name;
|
||||
if (props.axes.name === 'on') {
|
||||
this.scene.clear();
|
||||
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio };
|
||||
this.renderObject = createAxesRenderObject(params);
|
||||
this.scene.add(this.renderObject);
|
||||
this.scene.commit();
|
||||
|
||||
Vec3.set(this.camera.position, 0, 0, params.scale * 200);
|
||||
Mat4.lookAt(this.camera.view, this.camera.position, this.camera.target, this.camera.up);
|
||||
|
||||
p.axes.params = { ...props.axes.params };
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get isEnabled() {
|
||||
return this.props.axes.name === 'on';
|
||||
}
|
||||
|
||||
update(camera: Camera) {
|
||||
if (!this.renderObject) return;
|
||||
|
||||
updateCamera(this.camera, camera.viewport);
|
||||
|
||||
const m = this.renderObject.values.aTransform.ref.value as unknown as Mat4;
|
||||
Mat4.extractRotation(m, camera.view);
|
||||
|
||||
const r = this.renderObject.values.boundingSphere.ref.value.radius;
|
||||
Mat4.setTranslation(m, Vec3.create(
|
||||
-camera.viewport.width / 2 + r,
|
||||
-camera.viewport.height / 2 + r,
|
||||
0
|
||||
));
|
||||
|
||||
ValueCell.update(this.renderObject.values.aTransform, this.renderObject.values.aTransform.ref.value);
|
||||
this.scene.update([this.renderObject], true);
|
||||
}
|
||||
}
|
||||
|
||||
function updateCamera(camera: Camera, viewport: Viewport) {
|
||||
const { near, far } = camera;
|
||||
|
||||
const fullLeft = -(viewport.width - viewport.x) / 2;
|
||||
const fullRight = (viewport.width - viewport.x) / 2;
|
||||
const fullTop = (viewport.height - viewport.y) / 2;
|
||||
const fullBottom = -(viewport.height - viewport.y) / 2;
|
||||
|
||||
const dx = (fullRight - fullLeft) / 2;
|
||||
const dy = (fullTop - fullBottom) / 2;
|
||||
const cx = (fullRight + fullLeft) / 2;
|
||||
const cy = (fullTop + fullBottom) / 2;
|
||||
|
||||
const left = cx - dx;
|
||||
const right = cx + dx;
|
||||
const top = cy + dy;
|
||||
const bottom = cy - dy;
|
||||
|
||||
Mat4.ortho(camera.projection, left, right, top, bottom, near, far);
|
||||
}
|
||||
|
||||
function createAxesMesh(scale: number, mesh?: Mesh) {
|
||||
const state = MeshBuilder.createState(512, 256, mesh);
|
||||
const radius = 0.05 * scale;
|
||||
const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
|
||||
const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
|
||||
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
|
||||
|
||||
state.currentGroup = 0;
|
||||
addSphere(state, Vec3.origin, radius, 2);
|
||||
|
||||
state.currentGroup = 1;
|
||||
addSphere(state, x, radius, 2);
|
||||
addCylinder(state, Vec3.origin, x, 1, cylinderProps);
|
||||
|
||||
state.currentGroup = 2;
|
||||
addSphere(state, y, radius, 2);
|
||||
addCylinder(state, Vec3.origin, y, 1, cylinderProps);
|
||||
|
||||
state.currentGroup = 3;
|
||||
addSphere(state, z, radius, 2);
|
||||
addCylinder(state, Vec3.origin, z, 1, cylinderProps);
|
||||
|
||||
return MeshBuilder.getMesh(state);
|
||||
}
|
||||
|
||||
function getAxesShape(props: AxesProps, shape?: Shape<Mesh>) {
|
||||
const scale = 100 * props.scale;
|
||||
const mesh = createAxesMesh(scale, shape?.geometry);
|
||||
mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4));
|
||||
const getColor = (groupId: number) => {
|
||||
switch (groupId) {
|
||||
case 1: return props.colorX;
|
||||
case 2: return props.colorY;
|
||||
case 3: return props.colorZ;
|
||||
default: return ColorNames.grey;
|
||||
}
|
||||
};
|
||||
return Shape.create('axes', {}, mesh, getColor, () => 1, () => '');
|
||||
}
|
||||
|
||||
function createAxesRenderObject(props: AxesProps) {
|
||||
const shape = getAxesShape(props);
|
||||
return Shape.createRenderObject(shape, props);
|
||||
}
|
||||
@@ -111,7 +111,7 @@ export class Canvas3dInteractionHelper {
|
||||
this.ev.dispose();
|
||||
}
|
||||
|
||||
constructor(private canvasIdentify: Canvas3D['identify'], private getLoci: Canvas3D['getLoci'], input: InputObserver, private maxFps: number = 15) {
|
||||
constructor(private canvasIdentify: Canvas3D['identify'], private getLoci: Canvas3D['getLoci'], input: InputObserver, private maxFps: number = 30) {
|
||||
input.move.subscribe(({x, y, inside, buttons, button, modifiers }) => {
|
||||
if (!inside) return;
|
||||
this.move(x, y, buttons, button, modifiers);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -11,65 +11,93 @@ import Scene from '../../mol-gl/scene';
|
||||
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
import { Camera } from '../camera';
|
||||
import { CameraHelper, CameraHelperParams } from '../helper/camera-helper';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
export const DrawPassParams = {
|
||||
cameraHelper: PD.Group(CameraHelperParams)
|
||||
};
|
||||
export const DefaultDrawPassProps = PD.getDefaultValues(DrawPassParams);
|
||||
export type DrawPassProps = PD.Values<typeof DrawPassParams>
|
||||
|
||||
export class DrawPass {
|
||||
colorTarget: RenderTarget
|
||||
depthTexture: Texture
|
||||
packedDepth: boolean
|
||||
|
||||
cameraHelper: CameraHelper
|
||||
|
||||
private depthTarget: RenderTarget | null
|
||||
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper) {
|
||||
const { gl, extensions, resources } = webgl
|
||||
const width = gl.drawingBufferWidth
|
||||
const height = gl.drawingBufferHeight
|
||||
this.colorTarget = webgl.createRenderTarget(width, height)
|
||||
this.packedDepth = !extensions.depthTexture
|
||||
this.depthTarget = this.packedDepth ? webgl.createRenderTarget(width, height) : null
|
||||
this.depthTexture = this.depthTarget ? this.depthTarget.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest')
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, props: Partial<DrawPassProps> = {}) {
|
||||
const { gl, extensions, resources } = webgl;
|
||||
const width = gl.drawingBufferWidth;
|
||||
const height = gl.drawingBufferHeight;
|
||||
this.colorTarget = webgl.createRenderTarget(width, height);
|
||||
this.packedDepth = !extensions.depthTexture;
|
||||
this.depthTarget = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
|
||||
this.depthTexture = this.depthTarget ? this.depthTarget.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest');
|
||||
if (!this.packedDepth) {
|
||||
this.depthTexture.define(width, height)
|
||||
this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth')
|
||||
this.depthTexture.define(width, height);
|
||||
this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
|
||||
}
|
||||
|
||||
const p = { ...DefaultDrawPassProps, ...props };
|
||||
this.cameraHelper = new CameraHelper(webgl, p.cameraHelper);
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
this.colorTarget.setSize(width, height)
|
||||
this.colorTarget.setSize(width, height);
|
||||
if (this.depthTarget) {
|
||||
this.depthTarget.setSize(width, height)
|
||||
this.depthTarget.setSize(width, height);
|
||||
} else {
|
||||
this.depthTexture.define(width, height)
|
||||
this.depthTexture.define(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
setProps(props: Partial<DrawPassProps>) {
|
||||
if (props.cameraHelper) this.cameraHelper.setProps(props.cameraHelper);
|
||||
}
|
||||
|
||||
get props(): DrawPassProps {
|
||||
return {
|
||||
cameraHelper: { ...this.cameraHelper.props }
|
||||
};
|
||||
}
|
||||
|
||||
render(toDrawingBuffer: boolean, transparentBackground: boolean) {
|
||||
const { webgl, renderer, scene, camera, debugHelper, colorTarget, depthTarget } = this
|
||||
const { webgl, renderer, colorTarget, depthTarget } = this;
|
||||
if (toDrawingBuffer) {
|
||||
webgl.unbindFramebuffer()
|
||||
webgl.unbindFramebuffer();
|
||||
} else {
|
||||
colorTarget.bind()
|
||||
colorTarget.bind();
|
||||
if (!this.packedDepth) {
|
||||
// TODO unlcear why it is not enough to call `attachFramebuffer` in `Texture.reset`
|
||||
this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth')
|
||||
this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
|
||||
}
|
||||
}
|
||||
|
||||
renderer.setViewport(0, 0, colorTarget.getWidth(), colorTarget.getHeight())
|
||||
renderer.render(scene, camera, 'color', true, transparentBackground)
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility()
|
||||
renderer.render(debugHelper.scene, camera, 'color', false, transparentBackground)
|
||||
}
|
||||
renderer.setViewport(0, 0, colorTarget.getWidth(), colorTarget.getHeight());
|
||||
this.renderInternal('color', transparentBackground);
|
||||
|
||||
// do a depth pass if not rendering to drawing buffer and
|
||||
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
|
||||
if (!toDrawingBuffer && depthTarget) {
|
||||
depthTarget.bind()
|
||||
renderer.render(scene, camera, 'depth', true, transparentBackground)
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility()
|
||||
renderer.render(debugHelper.scene, camera, 'depth', false, transparentBackground)
|
||||
}
|
||||
depthTarget.bind();
|
||||
this.renderInternal('depth', transparentBackground);
|
||||
}
|
||||
}
|
||||
|
||||
private renderInternal(variant: 'color' | 'depth', transparentBackground: boolean) {
|
||||
const { renderer, scene, camera, debugHelper, cameraHelper } = this;
|
||||
renderer.render(scene, camera, variant, true, transparentBackground);
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility();
|
||||
renderer.render(debugHelper.scene, camera, variant, false, transparentBackground);
|
||||
}
|
||||
if (cameraHelper.isEnabled) {
|
||||
cameraHelper.update(camera);
|
||||
renderer.render(cameraHelper.scene, cameraHelper.camera, variant, false, transparentBackground);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -10,9 +10,9 @@ import Renderer from '../../mol-gl/renderer';
|
||||
import Scene from '../../mol-gl/scene';
|
||||
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { DrawPass } from './draw'
|
||||
import { PostprocessingPass, PostprocessingParams } from './postprocessing'
|
||||
import { MultiSamplePass, MultiSampleParams } from './multi-sample'
|
||||
import { DrawPass, DrawPassParams } from './draw';
|
||||
import { PostprocessingPass, PostprocessingParams } from './postprocessing';
|
||||
import { MultiSamplePass, MultiSampleParams } from './multi-sample';
|
||||
import { Camera } from '../camera';
|
||||
import { Viewport } from '../camera/util';
|
||||
|
||||
@@ -20,7 +20,8 @@ export const ImageParams = {
|
||||
transparentBackground: PD.Boolean(false),
|
||||
multiSample: PD.Group(MultiSampleParams),
|
||||
postprocessing: PD.Group(PostprocessingParams),
|
||||
}
|
||||
drawPass: PD.Group(DrawPassParams),
|
||||
};
|
||||
export type ImageProps = PD.Values<typeof ImageParams>
|
||||
|
||||
export class ImagePass {
|
||||
@@ -30,69 +31,79 @@ export class ImagePass {
|
||||
private _transparentBackground = false
|
||||
|
||||
private _colorTarget: RenderTarget
|
||||
get colorTarget() { return this._colorTarget }
|
||||
get colorTarget() { return this._colorTarget; }
|
||||
|
||||
readonly drawPass: DrawPass
|
||||
private readonly postprocessing: PostprocessingPass
|
||||
private readonly multiSample: MultiSamplePass
|
||||
|
||||
get width() { return this._width }
|
||||
get height() { return this._height }
|
||||
get width() { return this._width; }
|
||||
get height() { return this._height; }
|
||||
|
||||
constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, props: Partial<ImageProps>) {
|
||||
const p = { ...PD.getDefaultValues(ImageParams), ...props }
|
||||
const p = { ...PD.getDefaultValues(ImageParams), ...props };
|
||||
|
||||
this._transparentBackground = p.transparentBackground
|
||||
this._transparentBackground = p.transparentBackground;
|
||||
|
||||
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper)
|
||||
this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing)
|
||||
this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample)
|
||||
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, p.drawPass);
|
||||
this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing);
|
||||
this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample);
|
||||
|
||||
this.setSize(this._width, this._height)
|
||||
this.setSize(this._width, this._height);
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
if (width === this._width && height === this._height) return
|
||||
if (width === this._width && height === this._height) return;
|
||||
|
||||
this._width = width
|
||||
this._height = height
|
||||
this._width = width;
|
||||
this._height = height;
|
||||
|
||||
this.drawPass.setSize(width, height)
|
||||
this.postprocessing.setSize(width, height)
|
||||
this.multiSample.setSize(width, height)
|
||||
this.drawPass.setSize(width, height);
|
||||
this.postprocessing.setSize(width, height);
|
||||
this.multiSample.setSize(width, height);
|
||||
}
|
||||
|
||||
setProps(props: Partial<ImageProps> = {}) {
|
||||
if (props.transparentBackground !== undefined) this._transparentBackground = props.transparentBackground
|
||||
if (props.postprocessing) this.postprocessing.setProps(props.postprocessing)
|
||||
if (props.multiSample) this.multiSample.setProps(props.multiSample)
|
||||
if (props.transparentBackground !== undefined) this._transparentBackground = props.transparentBackground;
|
||||
if (props.postprocessing) this.postprocessing.setProps(props.postprocessing);
|
||||
if (props.multiSample) this.multiSample.setProps(props.multiSample);
|
||||
if (props.drawPass) this.drawPass.setProps(props.drawPass);
|
||||
}
|
||||
|
||||
get props(): ImageProps {
|
||||
return {
|
||||
transparentBackground: this._transparentBackground,
|
||||
postprocessing: this.postprocessing.props,
|
||||
multiSample: this.multiSample.props,
|
||||
drawPass: this.drawPass.props
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
Camera.copySnapshot(this._camera.state, this.camera.state)
|
||||
Viewport.set(this._camera.viewport, 0, 0, this._width, this._height)
|
||||
this._camera.update()
|
||||
Camera.copySnapshot(this._camera.state, this.camera.state);
|
||||
Viewport.set(this._camera.viewport, 0, 0, this._width, this._height);
|
||||
this._camera.update();
|
||||
|
||||
this.renderer.setViewport(0, 0, this._width, this._height);
|
||||
|
||||
if (this.multiSample.enabled) {
|
||||
this.multiSample.render(false, this._transparentBackground)
|
||||
this._colorTarget = this.multiSample.colorTarget
|
||||
this.multiSample.render(false, this._transparentBackground);
|
||||
this._colorTarget = this.multiSample.colorTarget;
|
||||
} else {
|
||||
this.drawPass.render(false, this._transparentBackground)
|
||||
this.drawPass.render(false, this._transparentBackground);
|
||||
if (this.postprocessing.enabled) {
|
||||
this.postprocessing.render(false)
|
||||
this._colorTarget = this.postprocessing.target
|
||||
this.postprocessing.render(false);
|
||||
this._colorTarget = this.postprocessing.target;
|
||||
} else {
|
||||
this._colorTarget = this.drawPass.colorTarget
|
||||
this._colorTarget = this.drawPass.colorTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getImageData(width: number, height: number) {
|
||||
this.setSize(width, height)
|
||||
this.render()
|
||||
const pd = this.colorTarget.getPixelData()
|
||||
return new ImageData(new Uint8ClampedArray(pd.array), pd.width, pd.height)
|
||||
this.setSize(width, height);
|
||||
this.render();
|
||||
const pd = this.colorTarget.getPixelData();
|
||||
return new ImageData(new Uint8ClampedArray(pd.array), pd.width, pd.height);
|
||||
}
|
||||
}
|
||||
@@ -19,15 +19,15 @@ import { Camera } from '../../mol-canvas3d/camera';
|
||||
import { PostprocessingPass } from './postprocessing';
|
||||
import { DrawPass } from './draw';
|
||||
|
||||
import quad_vert from '../../mol-gl/shader/quad.vert'
|
||||
import compose_frag from '../../mol-gl/shader/compose.frag'
|
||||
import quad_vert from '../../mol-gl/shader/quad.vert';
|
||||
import compose_frag from '../../mol-gl/shader/compose.frag';
|
||||
|
||||
const ComposeSchema = {
|
||||
...QuadSchema,
|
||||
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
|
||||
uTexSize: UniformSpec('v2'),
|
||||
uWeight: UniformSpec('f'),
|
||||
}
|
||||
};
|
||||
|
||||
type ComposeRenderable = ComputeRenderable<Values<typeof ComposeSchema>>
|
||||
|
||||
@@ -37,19 +37,19 @@ function getComposeRenderable(ctx: WebGLContext, colorTexture: Texture): Compose
|
||||
tColor: ValueCell.create(colorTexture),
|
||||
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
|
||||
uWeight: ValueCell.create(1.0),
|
||||
}
|
||||
};
|
||||
|
||||
const schema = { ...ComposeSchema }
|
||||
const shaderCode = ShaderCode(quad_vert, compose_frag)
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
const schema = { ...ComposeSchema };
|
||||
const shaderCode = ShaderCode('compose', quad_vert, compose_frag);
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values)
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
export const MultiSampleParams = {
|
||||
mode: PD.Select('off', [['off', 'Off'], ['on', 'On'], ['temporal', 'Temporal']]),
|
||||
sampleLevel: PD.Numeric(2, { min: 0, max: 5, step: 1 }),
|
||||
}
|
||||
};
|
||||
export type MultiSampleProps = PD.Values<typeof MultiSampleParams>
|
||||
|
||||
export class MultiSamplePass {
|
||||
@@ -65,229 +65,229 @@ export class MultiSamplePass {
|
||||
private lastRenderTime = 0
|
||||
|
||||
constructor(private webgl: WebGLContext, private camera: Camera, private drawPass: DrawPass, private postprocessing: PostprocessingPass, props: Partial<MultiSampleProps>) {
|
||||
const { gl } = webgl
|
||||
this.colorTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight)
|
||||
this.composeTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight)
|
||||
this.holdTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight)
|
||||
this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture)
|
||||
this.props = { ...PD.getDefaultValues(MultiSampleParams), ...props }
|
||||
const { gl } = webgl;
|
||||
this.colorTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
this.composeTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
this.holdTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture);
|
||||
this.props = { ...PD.getDefaultValues(MultiSampleParams), ...props };
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
if (this.props.mode === 'temporal') {
|
||||
if (this.currentTime - this.lastRenderTime > 200) {
|
||||
return this.sampleIndex !== -1
|
||||
return this.sampleIndex !== -1;
|
||||
} else {
|
||||
this.sampleIndex = 0
|
||||
return false
|
||||
this.sampleIndex = 0;
|
||||
return false;
|
||||
}
|
||||
} else if (this.props.mode === 'on') {
|
||||
return true
|
||||
return true;
|
||||
} else {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
update(changed: boolean, currentTime: number) {
|
||||
if (changed) this.lastRenderTime = currentTime
|
||||
this.currentTime = currentTime
|
||||
if (changed) this.lastRenderTime = currentTime;
|
||||
this.currentTime = currentTime;
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
this.colorTarget.setSize(width, height)
|
||||
this.composeTarget.setSize(width, height)
|
||||
this.holdTarget.setSize(width, height)
|
||||
ValueCell.update(this.compose.values.uTexSize, Vec2.set(this.compose.values.uTexSize.ref.value, width, height))
|
||||
this.colorTarget.setSize(width, height);
|
||||
this.composeTarget.setSize(width, height);
|
||||
this.holdTarget.setSize(width, height);
|
||||
ValueCell.update(this.compose.values.uTexSize, Vec2.set(this.compose.values.uTexSize.ref.value, width, height));
|
||||
}
|
||||
|
||||
setProps(props: Partial<MultiSampleProps>) {
|
||||
if (props.mode !== undefined) this.props.mode = props.mode
|
||||
if (props.sampleLevel !== undefined) this.props.sampleLevel = props.sampleLevel
|
||||
if (props.mode !== undefined) this.props.mode = props.mode;
|
||||
if (props.sampleLevel !== undefined) this.props.sampleLevel = props.sampleLevel;
|
||||
}
|
||||
|
||||
render(toDrawingBuffer: boolean, transparentBackground: boolean) {
|
||||
if (this.props.mode === 'temporal') {
|
||||
this.renderTemporalMultiSample(toDrawingBuffer, transparentBackground)
|
||||
this.renderTemporalMultiSample(toDrawingBuffer, transparentBackground);
|
||||
} else {
|
||||
this.renderMultiSample(toDrawingBuffer, transparentBackground)
|
||||
this.renderMultiSample(toDrawingBuffer, transparentBackground);
|
||||
}
|
||||
}
|
||||
|
||||
private renderMultiSample(toDrawingBuffer: boolean, transparentBackground: boolean) {
|
||||
const { camera, compose, composeTarget, drawPass, postprocessing, webgl } = this
|
||||
const { gl, state } = webgl
|
||||
const { camera, compose, composeTarget, drawPass, postprocessing, webgl } = this;
|
||||
const { gl, state } = webgl;
|
||||
|
||||
// based on the Multisample Anti-Aliasing Render Pass
|
||||
// contributed to three.js by bhouston / http://clara.io/
|
||||
//
|
||||
// This manual approach to MSAA re-renders the scene once for
|
||||
// each sample with camera jitter and accumulates the results.
|
||||
const offsetList = JitterVectors[ Math.max(0, Math.min(this.props.sampleLevel, 5)) ]
|
||||
const offsetList = JitterVectors[ Math.max(0, Math.min(this.props.sampleLevel, 5)) ];
|
||||
|
||||
const baseSampleWeight = 1.0 / offsetList.length
|
||||
const roundingRange = 1 / 32
|
||||
const baseSampleWeight = 1.0 / offsetList.length;
|
||||
const roundingRange = 1 / 32;
|
||||
|
||||
camera.viewOffset.enabled = true
|
||||
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture)
|
||||
compose.update()
|
||||
camera.viewOffset.enabled = true;
|
||||
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
|
||||
compose.update();
|
||||
|
||||
const width = drawPass.colorTarget.getWidth()
|
||||
const height = drawPass.colorTarget.getHeight()
|
||||
const width = drawPass.colorTarget.getWidth();
|
||||
const height = drawPass.colorTarget.getHeight();
|
||||
|
||||
// render the scene multiple times, each slightly jitter offset
|
||||
// from the last and accumulate the results.
|
||||
for (let i = 0; i < offsetList.length; ++i) {
|
||||
const offset = offsetList[i]
|
||||
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height)
|
||||
camera.update()
|
||||
const offset = offsetList[i];
|
||||
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height);
|
||||
camera.update();
|
||||
|
||||
// the theory is that equal weights for each sample lead to an accumulation of rounding
|
||||
// errors. The following equation varies the sampleWeight per sample so that it is uniformly
|
||||
// distributed across a range of values whose rounding errors cancel each other out.
|
||||
const uniformCenteredDistribution = -0.5 + (i + 0.5) / offsetList.length
|
||||
const sampleWeight = baseSampleWeight + roundingRange * uniformCenteredDistribution
|
||||
ValueCell.update(compose.values.uWeight, sampleWeight)
|
||||
const uniformCenteredDistribution = -0.5 + (i + 0.5) / offsetList.length;
|
||||
const sampleWeight = baseSampleWeight + roundingRange * uniformCenteredDistribution;
|
||||
ValueCell.update(compose.values.uWeight, sampleWeight);
|
||||
|
||||
// render scene and optionally postprocess
|
||||
drawPass.render(false, transparentBackground)
|
||||
if (postprocessing.enabled) postprocessing.render(false)
|
||||
drawPass.render(false, transparentBackground);
|
||||
if (postprocessing.enabled) postprocessing.render(false);
|
||||
|
||||
// compose rendered scene with compose target
|
||||
composeTarget.bind()
|
||||
gl.viewport(0, 0, width, height)
|
||||
state.enable(gl.BLEND)
|
||||
state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD)
|
||||
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE)
|
||||
state.disable(gl.DEPTH_TEST)
|
||||
state.disable(gl.SCISSOR_TEST)
|
||||
state.depthMask(false)
|
||||
composeTarget.bind();
|
||||
gl.viewport(0, 0, width, height);
|
||||
state.enable(gl.BLEND);
|
||||
state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
|
||||
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.depthMask(false);
|
||||
if (i === 0) {
|
||||
state.clearColor(0, 0, 0, 0)
|
||||
gl.clear(gl.COLOR_BUFFER_BIT)
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
compose.render()
|
||||
compose.render();
|
||||
}
|
||||
|
||||
ValueCell.update(compose.values.uWeight, 1.0)
|
||||
ValueCell.update(compose.values.tColor, composeTarget.texture)
|
||||
compose.update()
|
||||
ValueCell.update(compose.values.uWeight, 1.0);
|
||||
ValueCell.update(compose.values.tColor, composeTarget.texture);
|
||||
compose.update();
|
||||
|
||||
if (toDrawingBuffer) {
|
||||
webgl.unbindFramebuffer()
|
||||
webgl.unbindFramebuffer();
|
||||
} else {
|
||||
this.colorTarget.bind()
|
||||
this.colorTarget.bind();
|
||||
}
|
||||
gl.viewport(0, 0, width, height)
|
||||
state.disable(gl.BLEND)
|
||||
compose.render()
|
||||
gl.viewport(0, 0, width, height);
|
||||
state.disable(gl.BLEND);
|
||||
compose.render();
|
||||
|
||||
camera.viewOffset.enabled = false
|
||||
camera.update()
|
||||
camera.viewOffset.enabled = false;
|
||||
camera.update();
|
||||
}
|
||||
|
||||
private renderTemporalMultiSample(toDrawingBuffer: boolean, transparentBackground: boolean) {
|
||||
const { camera, compose, composeTarget, holdTarget, postprocessing, drawPass, webgl } = this
|
||||
const { gl, state } = webgl
|
||||
const { camera, compose, composeTarget, holdTarget, postprocessing, drawPass, webgl } = this;
|
||||
const { gl, state } = webgl;
|
||||
|
||||
// based on the Multisample Anti-Aliasing Render Pass
|
||||
// contributed to three.js by bhouston / http://clara.io/
|
||||
//
|
||||
// This manual approach to MSAA re-renders the scene once for
|
||||
// each sample with camera jitter and accumulates the results.
|
||||
const offsetList = JitterVectors[ Math.max(0, Math.min(this.props.sampleLevel, 5)) ]
|
||||
const offsetList = JitterVectors[ Math.max(0, Math.min(this.props.sampleLevel, 5)) ];
|
||||
|
||||
if (this.sampleIndex === -1) return
|
||||
if (this.sampleIndex === -1) return;
|
||||
if (this.sampleIndex >= offsetList.length) {
|
||||
this.sampleIndex = -1
|
||||
return
|
||||
this.sampleIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
const i = this.sampleIndex
|
||||
const i = this.sampleIndex;
|
||||
|
||||
if (i === 0) {
|
||||
drawPass.render(false, transparentBackground)
|
||||
if (postprocessing.enabled) postprocessing.render(false)
|
||||
ValueCell.update(compose.values.uWeight, 1.0)
|
||||
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture)
|
||||
compose.update()
|
||||
drawPass.render(false, transparentBackground);
|
||||
if (postprocessing.enabled) postprocessing.render(false);
|
||||
ValueCell.update(compose.values.uWeight, 1.0);
|
||||
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
|
||||
compose.update();
|
||||
|
||||
holdTarget.bind()
|
||||
state.disable(gl.BLEND)
|
||||
compose.render()
|
||||
holdTarget.bind();
|
||||
state.disable(gl.BLEND);
|
||||
compose.render();
|
||||
}
|
||||
|
||||
const sampleWeight = 1.0 / offsetList.length
|
||||
const sampleWeight = 1.0 / offsetList.length;
|
||||
|
||||
camera.viewOffset.enabled = true
|
||||
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture)
|
||||
ValueCell.update(compose.values.uWeight, sampleWeight)
|
||||
compose.update()
|
||||
camera.viewOffset.enabled = true;
|
||||
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
|
||||
ValueCell.update(compose.values.uWeight, sampleWeight);
|
||||
compose.update();
|
||||
|
||||
const width = drawPass.colorTarget.getWidth()
|
||||
const height = drawPass.colorTarget.getHeight()
|
||||
const width = drawPass.colorTarget.getWidth();
|
||||
const height = drawPass.colorTarget.getHeight();
|
||||
|
||||
// render the scene multiple times, each slightly jitter offset
|
||||
// from the last and accumulate the results.
|
||||
const numSamplesPerFrame = Math.pow(2, this.props.sampleLevel)
|
||||
const numSamplesPerFrame = Math.pow(2, this.props.sampleLevel);
|
||||
for (let i = 0; i < numSamplesPerFrame; ++i) {
|
||||
const offset = offsetList[this.sampleIndex]
|
||||
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height)
|
||||
camera.update()
|
||||
const offset = offsetList[this.sampleIndex];
|
||||
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height);
|
||||
camera.update();
|
||||
|
||||
// render scene and optionally postprocess
|
||||
drawPass.render(false, transparentBackground)
|
||||
if (postprocessing.enabled) postprocessing.render(false)
|
||||
drawPass.render(false, transparentBackground);
|
||||
if (postprocessing.enabled) postprocessing.render(false);
|
||||
|
||||
// compose rendered scene with compose target
|
||||
composeTarget.bind()
|
||||
state.enable(gl.BLEND)
|
||||
state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD)
|
||||
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE)
|
||||
state.disable(gl.DEPTH_TEST)
|
||||
state.disable(gl.SCISSOR_TEST)
|
||||
state.depthMask(false)
|
||||
composeTarget.bind();
|
||||
state.enable(gl.BLEND);
|
||||
state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
|
||||
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.depthMask(false);
|
||||
if (this.sampleIndex === 0) {
|
||||
state.clearColor(0, 0, 0, 0)
|
||||
gl.clear(gl.COLOR_BUFFER_BIT)
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
compose.render()
|
||||
compose.render();
|
||||
|
||||
this.sampleIndex += 1
|
||||
if (this.sampleIndex >= offsetList.length ) break
|
||||
this.sampleIndex += 1;
|
||||
if (this.sampleIndex >= offsetList.length ) break;
|
||||
}
|
||||
|
||||
const accumulationWeight = this.sampleIndex * sampleWeight
|
||||
const accumulationWeight = this.sampleIndex * sampleWeight;
|
||||
if (accumulationWeight > 0) {
|
||||
ValueCell.update(compose.values.uWeight, 1.0)
|
||||
ValueCell.update(compose.values.tColor, composeTarget.texture)
|
||||
compose.update()
|
||||
ValueCell.update(compose.values.uWeight, 1.0);
|
||||
ValueCell.update(compose.values.tColor, composeTarget.texture);
|
||||
compose.update();
|
||||
if (toDrawingBuffer) {
|
||||
webgl.unbindFramebuffer()
|
||||
webgl.unbindFramebuffer();
|
||||
} else {
|
||||
this.colorTarget.bind()
|
||||
this.colorTarget.bind();
|
||||
}
|
||||
gl.viewport(0, 0, width, height)
|
||||
state.disable(gl.BLEND)
|
||||
compose.render()
|
||||
gl.viewport(0, 0, width, height);
|
||||
state.disable(gl.BLEND);
|
||||
compose.render();
|
||||
}
|
||||
if (accumulationWeight < 1.0) {
|
||||
ValueCell.update(compose.values.uWeight, 1.0 - accumulationWeight)
|
||||
ValueCell.update(compose.values.tColor, holdTarget.texture)
|
||||
compose.update()
|
||||
ValueCell.update(compose.values.uWeight, 1.0 - accumulationWeight);
|
||||
ValueCell.update(compose.values.tColor, holdTarget.texture);
|
||||
compose.update();
|
||||
if (toDrawingBuffer) {
|
||||
webgl.unbindFramebuffer()
|
||||
webgl.unbindFramebuffer();
|
||||
} else {
|
||||
this.colorTarget.bind()
|
||||
this.colorTarget.bind();
|
||||
}
|
||||
gl.viewport(0, 0, width, height)
|
||||
if (accumulationWeight === 0) state.disable(gl.BLEND)
|
||||
else state.enable(gl.BLEND)
|
||||
compose.render()
|
||||
gl.viewport(0, 0, width, height);
|
||||
if (accumulationWeight === 0) state.disable(gl.BLEND);
|
||||
else state.enable(gl.BLEND);
|
||||
compose.render();
|
||||
}
|
||||
|
||||
camera.viewOffset.enabled = false
|
||||
camera.update()
|
||||
if (this.sampleIndex >= offsetList.length) this.sampleIndex = -1
|
||||
camera.viewOffset.enabled = false;
|
||||
camera.update();
|
||||
if (this.sampleIndex >= offsetList.length) this.sampleIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,12 +321,12 @@ const JitterVectors = [
|
||||
[ 2, 1 ], [ 6, 2 ], [ 0, 4 ], [ 4, 4 ],
|
||||
[ 2, 5 ], [ 7, 5 ], [ 5, 6 ], [ 3, 7 ]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
JitterVectors.forEach(offsetList => {
|
||||
offsetList.forEach(offset => {
|
||||
// 0.0625 = 1 / 16
|
||||
offset[0] *= 0.0625
|
||||
offset[1] *= 0.0625
|
||||
})
|
||||
})
|
||||
offset[0] *= 0.0625;
|
||||
offset[1] *= 0.0625;
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user