mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 21:34:23 +08:00
Compare commits
1309 Commits
v1.1.6
...
v3.0.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d09df55a9 | ||
|
|
ec2554537e | ||
|
|
f266dfadc6 | ||
|
|
99048eed61 | ||
|
|
fe63718b0c | ||
|
|
2c1200433c | ||
|
|
4a3252c929 | ||
|
|
5e052174ee | ||
|
|
cda0966105 | ||
|
|
c0a9716846 | ||
|
|
18c7395f9d | ||
|
|
d3da79f3dd | ||
|
|
5a215daca4 | ||
|
|
8527a3b3ef | ||
|
|
492dc1ba32 | ||
|
|
305a8ca802 | ||
|
|
6d2a35494f | ||
|
|
e76a08c73a | ||
|
|
a0fef0c20f | ||
|
|
605432ddd1 | ||
|
|
0c895071d8 | ||
|
|
79cd833ae6 | ||
|
|
0fee928e37 | ||
|
|
7f698336d7 | ||
|
|
cef04f192a | ||
|
|
4087c4c226 | ||
|
|
87bdcd2372 | ||
|
|
1dbcc0d7c8 | ||
|
|
4c93f01c64 | ||
|
|
0a18412da0 | ||
|
|
b67d16bdc4 | ||
|
|
976542d355 | ||
|
|
a2e5fda646 | ||
|
|
41b1b65d5f | ||
|
|
2ec2d1997f | ||
|
|
0bc65f3b72 | ||
|
|
9ed96b3599 | ||
|
|
b1cf9566f6 | ||
|
|
983ae4f8c2 | ||
|
|
7ce3531cc7 | ||
|
|
11f1a7fd1c | ||
|
|
47d7dd4d22 | ||
|
|
0d4f6bb5d9 | ||
|
|
b3a4e1976d | ||
|
|
8b3c0fd94e | ||
|
|
ab4a24d8ab | ||
|
|
7c93e9f834 | ||
|
|
90ea8cfebd | ||
|
|
7fc1866dac | ||
|
|
bb520ff424 | ||
|
|
f9d2e20cb9 | ||
|
|
6f13b67bf1 | ||
|
|
f37026a980 | ||
|
|
ddc4d8e867 | ||
|
|
7cded03598 | ||
|
|
744b04edc6 | ||
|
|
f2119b1d0b | ||
|
|
b4783909d7 | ||
|
|
fe5f841ab8 | ||
|
|
2c3f0dbc97 | ||
|
|
224fd1733f | ||
|
|
1f262ee422 | ||
|
|
df3bcdd05a | ||
|
|
d5d08542ed | ||
|
|
564a360c8f | ||
|
|
74f123265b | ||
|
|
ccfae65b01 | ||
|
|
bcfaef77c9 | ||
|
|
0b6243c0d1 | ||
|
|
472866d8ec | ||
|
|
471163f3d8 | ||
|
|
ab6106896d | ||
|
|
7ca624d04b | ||
|
|
bd3d18f43f | ||
|
|
21eb21b6dd | ||
|
|
fe0d4dc11e | ||
|
|
812eb0efa9 | ||
|
|
10d0bf293a | ||
|
|
ce2544b9f3 | ||
|
|
1964de1e44 | ||
|
|
20c9d2cc41 | ||
|
|
3b1513adc0 | ||
|
|
eaa60fc5cd | ||
|
|
ce1e3960a2 | ||
|
|
176f80ea9b | ||
|
|
28c9dc8286 | ||
|
|
2135f76441 | ||
|
|
5637a23153 | ||
|
|
2818389741 | ||
|
|
805f772696 | ||
|
|
308bbc1ea0 | ||
|
|
4a248b5591 | ||
|
|
704a9a111d | ||
|
|
a6befc5509 | ||
|
|
9e7aa4226d | ||
|
|
7a25699c23 | ||
|
|
901ae7f6d6 | ||
|
|
c57a4cdf6e | ||
|
|
18573e17f0 | ||
|
|
5bc7ffa8a7 | ||
|
|
430cc83259 | ||
|
|
3cd3afb775 | ||
|
|
93215b6beb | ||
|
|
694261c19c | ||
|
|
24ad38e260 | ||
|
|
53bac83dff | ||
|
|
ab1578f667 | ||
|
|
4e70301b62 | ||
|
|
76ed2e9e11 | ||
|
|
58ce1f6498 | ||
|
|
47c4353eb9 | ||
|
|
d82cd3a8fe | ||
|
|
8f06b603e6 | ||
|
|
0865c1b7fb | ||
|
|
c8c32b89c1 | ||
|
|
658c789906 | ||
|
|
c9ad7fce5b | ||
|
|
720a8a440f | ||
|
|
84fe2d2502 | ||
|
|
6f159c592f | ||
|
|
beff1ecb3e | ||
|
|
e4f630dbef | ||
|
|
ccaf18af04 | ||
|
|
2b72098f95 | ||
|
|
b32546bea7 | ||
|
|
b9b0413e9f | ||
|
|
1d29b4627f | ||
|
|
bd44c76709 | ||
|
|
06b4761f2b | ||
|
|
daa3d1dbaa | ||
|
|
5490d5ceb5 | ||
|
|
cf3c1cfcce | ||
|
|
be2607ae84 | ||
|
|
aa1f081664 | ||
|
|
e2966241e8 | ||
|
|
447792b1ef | ||
|
|
1cbcb5c530 | ||
|
|
f8d32d1d8d | ||
|
|
8c556c2849 | ||
|
|
4d0f0ceebf | ||
|
|
14abcddfcf | ||
|
|
704cc96a9f | ||
|
|
b51f610173 | ||
|
|
98050875c7 | ||
|
|
470280ea1a | ||
|
|
dafb5a8299 | ||
|
|
4a1af03744 | ||
|
|
bb176f1efb | ||
|
|
a53bcde973 | ||
|
|
1a8dc2c637 | ||
|
|
f96211ff91 | ||
|
|
77f9c02785 | ||
|
|
7910b65fdc | ||
|
|
eb4fc4588d | ||
|
|
5430674071 | ||
|
|
17e67e3b79 | ||
|
|
e87a0d72e4 | ||
|
|
67d3c65907 | ||
|
|
564a5486c9 | ||
|
|
9ce11c4c32 | ||
|
|
5e97b551a5 | ||
|
|
77536e75af | ||
|
|
6f12f714d2 | ||
|
|
1f67077400 | ||
|
|
d1c4cf69cb | ||
|
|
803c5eaa15 | ||
|
|
970fd5d9c3 | ||
|
|
7ccd4a1e0d | ||
|
|
3a6ab55266 | ||
|
|
eb41882c56 | ||
|
|
734851a810 | ||
|
|
6318717a15 | ||
|
|
d8498feaef | ||
|
|
aaec452bc2 | ||
|
|
bce959195a | ||
|
|
4287e09a9a | ||
|
|
fdc006f833 | ||
|
|
c704b7505c | ||
|
|
ceaf238322 | ||
|
|
b7224ce5c7 | ||
|
|
95654175fe | ||
|
|
de96244706 | ||
|
|
3104ee5742 | ||
|
|
73ac445a44 | ||
|
|
1a1d1d9d30 | ||
|
|
062aff76da | ||
|
|
7d0d24b66d | ||
|
|
6655672d11 | ||
|
|
6e573ae410 | ||
|
|
1c48c02473 | ||
|
|
78be3320ce | ||
|
|
c8018800cc | ||
|
|
bb795aca98 | ||
|
|
2cb1279f4c | ||
|
|
b876c6f618 | ||
|
|
3a7dfc055e | ||
|
|
928e521ac7 | ||
|
|
e5e9598e4b | ||
|
|
e6e1809592 | ||
|
|
812f97ddb7 | ||
|
|
c6b814b31b | ||
|
|
98566fa389 | ||
|
|
4318c89bdb | ||
|
|
b41a97ce6a | ||
|
|
862c384dc0 | ||
|
|
26cc7e94c2 | ||
|
|
c6fe6ddcba | ||
|
|
154984e74d | ||
|
|
72fcaf8321 | ||
|
|
0c14ca5888 | ||
|
|
a85ede5058 | ||
|
|
db49a16184 | ||
|
|
0704db2343 | ||
|
|
425dca4665 | ||
|
|
8d65ccabd2 | ||
|
|
cbd417ca13 | ||
|
|
1e4d1e45f9 | ||
|
|
1578211157 | ||
|
|
15932dc5df | ||
|
|
7db2205956 | ||
|
|
d87f0d236a | ||
|
|
16daca6008 | ||
|
|
a0d919c8db | ||
|
|
ffee2bf1c4 | ||
|
|
de77f6ac59 | ||
|
|
c8c2ebcd65 | ||
|
|
42796b984f | ||
|
|
746557bf52 | ||
|
|
a5020a9e96 | ||
|
|
2ebb0a35fd | ||
|
|
64aaa92d45 | ||
|
|
4baf391efe | ||
|
|
5afdcff6a5 | ||
|
|
339c397860 | ||
|
|
a58cbd31ef | ||
|
|
d232b01cf9 | ||
|
|
ec95270854 | ||
|
|
78cc0d960f | ||
|
|
b5ccdfdd53 | ||
|
|
d0c0d8e703 | ||
|
|
ebf64404be | ||
|
|
7f39cf0f37 | ||
|
|
e5dcc8e54f | ||
|
|
4592510a95 | ||
|
|
46d5442dc5 | ||
|
|
271cff4aba | ||
|
|
94fd5a97d6 | ||
|
|
28678e2f80 | ||
|
|
406307a432 | ||
|
|
56345b5096 | ||
|
|
3fcc42ee0e | ||
|
|
b903677f8a | ||
|
|
ef4b632a07 | ||
|
|
e9d485ca85 | ||
|
|
a149fa5929 | ||
|
|
bb3dde585b | ||
|
|
cd6bbeaa86 | ||
|
|
e3d24dae4b | ||
|
|
687a814a62 | ||
|
|
8f2e99dc51 | ||
|
|
568be030c3 | ||
|
|
97c3ab8b5a | ||
|
|
5db646d139 | ||
|
|
340f8f1af3 | ||
|
|
4484a4452c | ||
|
|
65b654a0a2 | ||
|
|
a8e0c13b0e | ||
|
|
41d67eb642 | ||
|
|
c76c8335d1 | ||
|
|
42528b7be5 | ||
|
|
b371f8c11c | ||
|
|
3d651b40f0 | ||
|
|
895a13fc0d | ||
|
|
c94acff82e | ||
|
|
2f9ac711d1 | ||
|
|
93b9953f6d | ||
|
|
dcaf6f8927 | ||
|
|
d96eb404e1 | ||
|
|
07322819f0 | ||
|
|
9be686686d | ||
|
|
3df539c9e1 | ||
|
|
903f06bab6 | ||
|
|
13f2810f90 | ||
|
|
ee8cae16d2 | ||
|
|
feaf6f7fd4 | ||
|
|
04775a2e44 | ||
|
|
bec9fec755 | ||
|
|
e840059a38 | ||
|
|
1bd0339dec | ||
|
|
d0eaf2f71e | ||
|
|
c7edf40afe | ||
|
|
b44962eb2f | ||
|
|
254c9efbf5 | ||
|
|
73e9aed98c | ||
|
|
6e60d9713a | ||
|
|
ef0593b1e2 | ||
|
|
7831fa8b33 | ||
|
|
c64851492c | ||
|
|
4a2e93e265 | ||
|
|
d4bb1a6e93 | ||
|
|
55de0aba69 | ||
|
|
ecfa7b5a99 | ||
|
|
bee3dc4595 | ||
|
|
787ca47825 | ||
|
|
b06c134b61 | ||
|
|
3436d03468 | ||
|
|
58df6f3b85 | ||
|
|
6fab6ce1f2 | ||
|
|
9fd95f1a11 | ||
|
|
69fe0901e2 | ||
|
|
ffaf008dce | ||
|
|
eb196a41b5 | ||
|
|
35baaaf594 | ||
|
|
0fc305aaea | ||
|
|
bf67546a61 | ||
|
|
8c417ef35c | ||
|
|
01691f3050 | ||
|
|
128abf3090 | ||
|
|
0c0e995256 | ||
|
|
4090498f92 | ||
|
|
24677d6931 | ||
|
|
80d54afdd0 | ||
|
|
908fff8041 | ||
|
|
aa1eb90f66 | ||
|
|
1d21787e7e | ||
|
|
42409a2bc7 | ||
|
|
b31302ba3a | ||
|
|
3840b0041b | ||
|
|
cc68f8311d | ||
|
|
3400c8e94a | ||
|
|
61c47c517b | ||
|
|
76674917ca | ||
|
|
b835eb8de7 | ||
|
|
2a74dfcf46 | ||
|
|
b0f447adde | ||
|
|
3f0d476e94 | ||
|
|
9423d56d36 | ||
|
|
0f03fe99d6 | ||
|
|
556d5bb003 | ||
|
|
8e3ea6943f | ||
|
|
599cfc1b1c | ||
|
|
84eccb5019 | ||
|
|
2dd07bb0e3 | ||
|
|
f404d23280 | ||
|
|
8b7333b470 | ||
|
|
b8628ccff1 | ||
|
|
7d26567d40 | ||
|
|
8f6dbf2192 | ||
|
|
db350ddfd3 | ||
|
|
c0144d826c | ||
|
|
de3e819b80 | ||
|
|
bbf96567b1 | ||
|
|
c9a3254bd6 | ||
|
|
bad6d030f1 | ||
|
|
e1ad67a059 | ||
|
|
4c30057edf | ||
|
|
53a4826274 | ||
|
|
8d3ac92989 | ||
|
|
d6eb334d12 | ||
|
|
c62f19623c | ||
|
|
77139afe7f | ||
|
|
54476ad85e | ||
|
|
6dd876232d | ||
|
|
ae2314d76c | ||
|
|
6667509745 | ||
|
|
5c871a5aae | ||
|
|
393fc99ed2 | ||
|
|
7c5dff1c8b | ||
|
|
2482ef92af | ||
|
|
db59303a84 | ||
|
|
fe700953ff | ||
|
|
047946e41c | ||
|
|
ce3f13431d | ||
|
|
df54766ab2 | ||
|
|
94233fbcd9 | ||
|
|
d044496eaa | ||
|
|
ca825d720e | ||
|
|
f833efae37 | ||
|
|
be4b787e66 | ||
|
|
950b1c179a | ||
|
|
2bd1a01afb | ||
|
|
2fe43eda2b | ||
|
|
45fc0c61af | ||
|
|
7e7993f5ba | ||
|
|
9d34dbff0f | ||
|
|
ba1b03f01b | ||
|
|
791f7ca3c8 | ||
|
|
c14d50e4ff | ||
|
|
3e9de449c8 | ||
|
|
0132c7ef5e | ||
|
|
aa2222c086 | ||
|
|
fc2765d376 | ||
|
|
9d85194082 | ||
|
|
abfcc60898 | ||
|
|
c688a83fa2 | ||
|
|
77376056b9 | ||
|
|
8efd943c2b | ||
|
|
b230655439 | ||
|
|
8ba792c4b0 | ||
|
|
fde8ca69e4 | ||
|
|
9ee1439299 | ||
|
|
195668760e | ||
|
|
bd64f1db9a | ||
|
|
38a5a857aa | ||
|
|
5e8cdfe3a7 | ||
|
|
504406eb22 | ||
|
|
f892917e1c | ||
|
|
738b7f4ca5 | ||
|
|
bce53d03a5 | ||
|
|
74f721ab9f | ||
|
|
f011025f16 | ||
|
|
9e44cd83fa | ||
|
|
e0e45b64ac | ||
|
|
f10b152252 | ||
|
|
23cf5c2fdd | ||
|
|
e211abd5ae | ||
|
|
7199be4d62 | ||
|
|
e1a40ded1d | ||
|
|
8999c3097d | ||
|
|
44308fa1fd | ||
|
|
f5dd2f4579 | ||
|
|
104999b7dc | ||
|
|
e5341623d3 | ||
|
|
0e9238e5ec | ||
|
|
43c292e2df | ||
|
|
fbfd1b20d8 | ||
|
|
5330df87e1 | ||
|
|
ad6b3c6fe0 | ||
|
|
b983df7eb5 | ||
|
|
add76a87d9 | ||
|
|
f9f8350d28 | ||
|
|
b71c2f365c | ||
|
|
a5443189d3 | ||
|
|
7686b61728 | ||
|
|
844c13cd35 | ||
|
|
d1c8b92fdf | ||
|
|
93d33bca80 | ||
|
|
6550e53414 | ||
|
|
96dddb0998 | ||
|
|
baa64d8109 | ||
|
|
2df145aa8f | ||
|
|
06b9c5f2de | ||
|
|
e03b689f27 | ||
|
|
e4cdcff3ee | ||
|
|
f73150d074 | ||
|
|
451dc12689 | ||
|
|
a3fb7762d8 | ||
|
|
3dfafc3202 | ||
|
|
4fcea991d3 | ||
|
|
0607ed46d1 | ||
|
|
30d6244e82 | ||
|
|
fab8c74365 | ||
|
|
1952922e4e | ||
|
|
1eb351369e | ||
|
|
701d782485 | ||
|
|
dc8457c4dc | ||
|
|
f104cd4d11 | ||
|
|
9b56a6ae65 | ||
|
|
2485ad5a2f | ||
|
|
a56716ab6a | ||
|
|
e0aaaa989e | ||
|
|
9ec0f9e736 | ||
|
|
47968eeeec | ||
|
|
9c157b70e1 | ||
|
|
6d7e4ca227 | ||
|
|
fccd08d2ec | ||
|
|
19bae202d0 | ||
|
|
4ba0ae24e4 | ||
|
|
fcf3718d75 | ||
|
|
df1dd94f1c | ||
|
|
65ba401850 | ||
|
|
a98f5e1047 | ||
|
|
e5cf97d1ea | ||
|
|
1844fc14b2 | ||
|
|
d185c0ef34 | ||
|
|
40a4211e75 | ||
|
|
daa2bbd042 | ||
|
|
ed5b4b27a8 | ||
|
|
408ccb4353 | ||
|
|
99e3cd6654 | ||
|
|
0819ace1dc | ||
|
|
987c9210bd | ||
|
|
84fb42a161 | ||
|
|
53d3480701 | ||
|
|
eb629ef337 | ||
|
|
c26111e8fb | ||
|
|
4853ff7a1a | ||
|
|
1bdebda136 | ||
|
|
fe5b847797 | ||
|
|
19ec5b226c | ||
|
|
4bb32d31dc | ||
|
|
976a469cc7 | ||
|
|
86087aa3ca | ||
|
|
c0e955d472 | ||
|
|
eca052e52e | ||
|
|
a1e05387e4 | ||
|
|
301940c8bd | ||
|
|
d96303627c | ||
|
|
051b48776e | ||
|
|
26054681d8 | ||
|
|
70fa85d7d4 | ||
|
|
5a23cd483e | ||
|
|
d759b07f1b | ||
|
|
4694da0057 | ||
|
|
f930e3dbe0 | ||
|
|
fcf45d20be | ||
|
|
ad4ba7bcf9 | ||
|
|
26644ede49 | ||
|
|
810973ff54 | ||
|
|
6ad09c60c0 | ||
|
|
dc146f5f04 | ||
|
|
e1b771bba4 | ||
|
|
e2ab3a6fd6 | ||
|
|
d1296de676 | ||
|
|
fcac1a62c6 | ||
|
|
5eafddf97a | ||
|
|
e2dcbc3d65 | ||
|
|
54a388da9c | ||
|
|
3849c341b8 | ||
|
|
31f4803c0a | ||
|
|
d6e36d4ca7 | ||
|
|
0d526fdc98 | ||
|
|
04b36170d8 | ||
|
|
db787c9ea4 | ||
|
|
e1e6f9ca48 | ||
|
|
40b5605e10 | ||
|
|
609654b689 | ||
|
|
45ef00f1d1 | ||
|
|
88380ff917 | ||
|
|
bc7bfe9788 | ||
|
|
469ca6cb41 | ||
|
|
c0be790ff1 | ||
|
|
8c1d16353e | ||
|
|
d76d475015 | ||
|
|
69024152cb | ||
|
|
4a19aedec8 | ||
|
|
df89351301 | ||
|
|
9a0c87695f | ||
|
|
a393231522 | ||
|
|
33de60d365 | ||
|
|
3cf67f7605 | ||
|
|
ffdcf798e0 | ||
|
|
397e1235e7 | ||
|
|
4e77699076 | ||
|
|
b47d046505 | ||
|
|
74aa24bfa0 | ||
|
|
30d5b0ddb1 | ||
|
|
1e35ea15eb | ||
|
|
bc998ab328 | ||
|
|
e5e245f4ee | ||
|
|
c6073b894a | ||
|
|
9b11794f22 | ||
|
|
f2b9dceaab | ||
|
|
9ccaaf6c80 | ||
|
|
ecb97e525e | ||
|
|
c36c6a6d97 | ||
|
|
60b92471f1 | ||
|
|
79e283cfbd | ||
|
|
3778dacb08 | ||
|
|
e407f7279b | ||
|
|
ea54209414 | ||
|
|
d10a36509b | ||
|
|
4af560e63a | ||
|
|
ecb8900258 | ||
|
|
7bfc1b0ebc | ||
|
|
5edae9d6f7 | ||
|
|
fe702a8c63 | ||
|
|
c8868464a5 | ||
|
|
720e65d2e6 | ||
|
|
b5123ff36a | ||
|
|
d237034e8e | ||
|
|
aab95d27e0 | ||
|
|
c68306125e | ||
|
|
3173396737 | ||
|
|
212a3eeb6c | ||
|
|
17b25354f5 | ||
|
|
9f176bd2bc | ||
|
|
4a78283ce1 | ||
|
|
81e29533dc | ||
|
|
c1a2c602a1 | ||
|
|
c436653ce9 | ||
|
|
a2b4ed7c1c | ||
|
|
83968aa408 | ||
|
|
71539cc75a | ||
|
|
881d4d2a99 | ||
|
|
ae2f2e7d0e | ||
|
|
e31f0f7660 | ||
|
|
3586207968 | ||
|
|
b575793b83 | ||
|
|
81bf653790 | ||
|
|
6186c60cd9 | ||
|
|
6ab480589a | ||
|
|
571f8187c3 | ||
|
|
d510ff00dc | ||
|
|
7a0f286fb4 | ||
|
|
fccf8d6b87 | ||
|
|
e0c08e89d0 | ||
|
|
ef9885411c | ||
|
|
7542ead360 | ||
|
|
043ab08066 | ||
|
|
ec0933d197 | ||
|
|
aef34a687d | ||
|
|
9b7192f261 | ||
|
|
18212d9ee7 | ||
|
|
8d4e0730e8 | ||
|
|
ba8e9e189f | ||
|
|
b7935de7af | ||
|
|
10ca32f9d7 | ||
|
|
bb86d83c96 | ||
|
|
907b08cc99 | ||
|
|
a07d593909 | ||
|
|
a0a3ff1969 | ||
|
|
7fac8a8f77 | ||
|
|
7266c67e32 | ||
|
|
50c8d09742 | ||
|
|
7377947975 | ||
|
|
a3c4daf30a | ||
|
|
9d7e6f1d99 | ||
|
|
9e105020e3 | ||
|
|
698f7e16bd | ||
|
|
93df548cfe | ||
|
|
a0b1593c82 | ||
|
|
fc81e08d73 | ||
|
|
5369fa5adf | ||
|
|
316a77c716 | ||
|
|
42dfa69ad7 | ||
|
|
cae4eb8b0e | ||
|
|
5514b24fdf | ||
|
|
d570bc352e | ||
|
|
8a76a3fa64 | ||
|
|
71bf4e21f5 | ||
|
|
e0d36c30d3 | ||
|
|
d653a96b25 | ||
|
|
b53debcfef | ||
|
|
d0705ac226 | ||
|
|
e01eacb3fe | ||
|
|
d4102b476b | ||
|
|
83ce17174a | ||
|
|
18023d7f26 | ||
|
|
a8541d5967 | ||
|
|
8b21818f2e | ||
|
|
0b290247dc | ||
|
|
fb5010e962 | ||
|
|
178789d327 | ||
|
|
4fae526073 | ||
|
|
05f1d8085a | ||
|
|
38bbabd742 | ||
|
|
3ab958a93c | ||
|
|
f59d589a30 | ||
|
|
11f7e54704 | ||
|
|
16ebd8266e | ||
|
|
7a796a4d3d | ||
|
|
1cbb915962 | ||
|
|
80486d58c3 | ||
|
|
81bc116c4d | ||
|
|
4249064dd1 | ||
|
|
e0a594121b | ||
|
|
028c02f50d | ||
|
|
76e97d7b59 | ||
|
|
ad1181a75b | ||
|
|
5d683462fb | ||
|
|
42422bb0ea | ||
|
|
861e5c3e97 | ||
|
|
614cffda96 | ||
|
|
2e0379d202 | ||
|
|
b5cfdcd2a3 | ||
|
|
c00de6fde0 | ||
|
|
da3a8e56f3 | ||
|
|
103d6fe775 | ||
|
|
5df55e6bf7 | ||
|
|
3b285086d4 | ||
|
|
91793bc3cc | ||
|
|
fa3828e820 | ||
|
|
31ba8212da | ||
|
|
fe27d8e134 | ||
|
|
83dcdfdc4b | ||
|
|
f9aaabc1f7 | ||
|
|
034370b44c | ||
|
|
b87666df3e | ||
|
|
c98c3228fe | ||
|
|
9419980dfc | ||
|
|
42d60420e5 | ||
|
|
5b1df333a7 | ||
|
|
0bb376706d | ||
|
|
eca7da2c72 | ||
|
|
b0bdb3ddb6 | ||
|
|
3180d7c305 | ||
|
|
2faa821c50 | ||
|
|
7f355ae501 | ||
|
|
7f79ff9ff2 | ||
|
|
02de871c59 | ||
|
|
00cb783d4c | ||
|
|
c925919ee5 | ||
|
|
324820890a | ||
|
|
2687b29d4d | ||
|
|
7084aaee1a | ||
|
|
520a2f7850 | ||
|
|
9264987817 | ||
|
|
b736ed3ea4 | ||
|
|
166d660fa7 | ||
|
|
b8249cde4d | ||
|
|
f12f5eca90 | ||
|
|
cd3798b46f | ||
|
|
0240e54737 | ||
|
|
6a735d902e | ||
|
|
57a942ecb5 | ||
|
|
f67605a398 | ||
|
|
aaafa1d5ad | ||
|
|
a1d9a77653 | ||
|
|
f2f1181af3 | ||
|
|
864befc48a | ||
|
|
73f6793bd8 | ||
|
|
87ee9d88f2 | ||
|
|
b1e245e913 | ||
|
|
78c0471f39 | ||
|
|
c57b9b9214 | ||
|
|
34f33c5bbb | ||
|
|
57da2a7ebb | ||
|
|
d45d5c0e55 | ||
|
|
42ed425e65 | ||
|
|
f752ee5094 | ||
|
|
044c796942 | ||
|
|
0aabbcfaab | ||
|
|
24274cc53b | ||
|
|
870cef2fd4 | ||
|
|
bf7b1f5bfd | ||
|
|
9c9a0312db | ||
|
|
724fa2a7cd | ||
|
|
19b36e5942 | ||
|
|
b0dd9ab026 | ||
|
|
b77f1d4dee | ||
|
|
3770fd7706 | ||
|
|
e3175c3ed1 | ||
|
|
7c5dd5b15b | ||
|
|
0872e11669 | ||
|
|
a66da4defc | ||
|
|
d4ba13a2f2 | ||
|
|
3b25e037aa | ||
|
|
189fad3d84 | ||
|
|
c3c22ee3bc | ||
|
|
8a3222005c | ||
|
|
a17da36410 | ||
|
|
80323d8122 | ||
|
|
cbd6aa0b6b | ||
|
|
3831bd9941 | ||
|
|
3d3e2c3a86 | ||
|
|
acf13fa46f | ||
|
|
bc5d796653 | ||
|
|
82dd0496c2 | ||
|
|
056742ac74 | ||
|
|
29d4cfbcca | ||
|
|
376449f7c8 | ||
|
|
bc37fad007 | ||
|
|
2e561a8de7 | ||
|
|
e6c8c69d0c | ||
|
|
d121a11e28 | ||
|
|
5484a2a72c | ||
|
|
d527609b6d | ||
|
|
e628f580a7 | ||
|
|
b662179b4d | ||
|
|
fa2b8542bf | ||
|
|
901522f500 | ||
|
|
62b63c1aa5 | ||
|
|
24b36f41da | ||
|
|
c9c890782c | ||
|
|
f2c539ebd8 | ||
|
|
feb922ca91 | ||
|
|
25127bb84b | ||
|
|
8fb01d2157 | ||
|
|
c09357ea75 | ||
|
|
9f2513dae0 | ||
|
|
11a52c0390 | ||
|
|
e955dc7e94 | ||
|
|
c8107272f6 | ||
|
|
fb08fe7545 | ||
|
|
b6f054ea28 | ||
|
|
dc7e85133c | ||
|
|
90cddf4e41 | ||
|
|
2cddbb72a6 | ||
|
|
a16faaac4e | ||
|
|
6c5224f33e | ||
|
|
77d013b775 | ||
|
|
02a466e8b9 | ||
|
|
3cb65cbe3d | ||
|
|
fe8838542c | ||
|
|
78b5c9aac4 | ||
|
|
021fa7b79b | ||
|
|
0443589b09 | ||
|
|
415288de9f | ||
|
|
ecbafb086a | ||
|
|
e5dae6c0dd | ||
|
|
16f4524bdb | ||
|
|
6b33021f43 | ||
|
|
fdf37100c2 | ||
|
|
e28674d0dc | ||
|
|
fb7456286a | ||
|
|
9d240f8928 | ||
|
|
48ef5efb21 | ||
|
|
52b2e7c144 | ||
|
|
f2d1d60f6b | ||
|
|
5a176a378a | ||
|
|
60151c2c24 | ||
|
|
a5db6350a2 | ||
|
|
0618eb18ba | ||
|
|
bffdff6aad | ||
|
|
7753a6ec56 | ||
|
|
b8aafa1d78 | ||
|
|
672875187b | ||
|
|
547d60d573 | ||
|
|
99471d2a7b | ||
|
|
45d249b71a | ||
|
|
1382edd81c | ||
|
|
89a6102f8d | ||
|
|
163929477e | ||
|
|
c10a8369e8 | ||
|
|
8fbba52de8 | ||
|
|
ca3174b2c3 | ||
|
|
b9864fba80 | ||
|
|
f8e9bc1e7f | ||
|
|
f79f1507f7 | ||
|
|
61ab205a5d | ||
|
|
2c65260a4f | ||
|
|
0597a1ef24 | ||
|
|
8d6557e51c | ||
|
|
5cff0dff3d | ||
|
|
93206e76d7 | ||
|
|
40933a8539 | ||
|
|
989800783b | ||
|
|
d83b0d2c4d | ||
|
|
5e5d5a63dc | ||
|
|
b1755604e2 | ||
|
|
e58da9b574 | ||
|
|
f5d6498601 | ||
|
|
07f351888f | ||
|
|
4588fdd5d5 | ||
|
|
c3b32baf6a | ||
|
|
b8d60cea9b | ||
|
|
25b8956712 | ||
|
|
7015309db6 | ||
|
|
aad861db37 | ||
|
|
ae7811705d | ||
|
|
7e26dac50b | ||
|
|
75f43d038c | ||
|
|
b9ba940510 | ||
|
|
35603baaaa | ||
|
|
19dc32c491 | ||
|
|
95997e6a61 | ||
|
|
03e19a2ad7 | ||
|
|
765b133369 | ||
|
|
703e729514 | ||
|
|
b0216c4ce6 | ||
|
|
6796fc1cd4 | ||
|
|
87c504f9a8 | ||
|
|
2e770cb733 | ||
|
|
9f440f68e0 | ||
|
|
40028b27ba | ||
|
|
4676ad8738 | ||
|
|
e1c7833826 | ||
|
|
dd1bca0fee | ||
|
|
c38ab2c638 | ||
|
|
459c5aa5a7 | ||
|
|
b8bf07d393 | ||
|
|
ea87ac2094 | ||
|
|
e1b830a59d | ||
|
|
41e1ac76c0 | ||
|
|
98b118fd1e | ||
|
|
5f691913e4 | ||
|
|
26e2516097 | ||
|
|
3d2e4115ed | ||
|
|
dbce1ccb3d | ||
|
|
03aa2be978 | ||
|
|
8dfc52e1ab | ||
|
|
6058179f10 | ||
|
|
ea9e25b03c | ||
|
|
d60c3ddce3 | ||
|
|
724e79bddf | ||
|
|
2de61215c4 | ||
|
|
e783d9a9f1 | ||
|
|
e9e971d4f3 | ||
|
|
96dea14cb1 | ||
|
|
04fc157340 | ||
|
|
cfc24fa99e | ||
|
|
19c1088209 | ||
|
|
ee6c2e0841 | ||
|
|
20ee659b00 | ||
|
|
b6514a4a50 | ||
|
|
07b8bdb951 | ||
|
|
afd18cabd4 | ||
|
|
1117ce05d5 | ||
|
|
fc15e952bf | ||
|
|
249e5a3e0b | ||
|
|
4bfe3f6bde | ||
|
|
75b7e0b4d9 | ||
|
|
ee4ce2fd7a | ||
|
|
db0aa12e75 | ||
|
|
6d2578d3d0 | ||
|
|
99d61f48b4 | ||
|
|
146022dc12 | ||
|
|
92730cad01 | ||
|
|
d6b68b06da | ||
|
|
b174fbf0c6 | ||
|
|
fde1557955 | ||
|
|
24a0753881 | ||
|
|
5664e1d8be | ||
|
|
4881a41256 | ||
|
|
235e41ee03 | ||
|
|
94d293a4d3 | ||
|
|
40f1ca207f | ||
|
|
926fb38c1e | ||
|
|
5a14fcabc5 | ||
|
|
560e40773f | ||
|
|
6561732f57 | ||
|
|
b45cf206fd | ||
|
|
70e07be64d | ||
|
|
f3013f0e46 | ||
|
|
2e7041bd78 | ||
|
|
5d0447c9bb | ||
|
|
9eba0b91a8 | ||
|
|
58bc6722a9 | ||
|
|
1acfed3233 | ||
|
|
8147b3aa34 | ||
|
|
b21552ff36 | ||
|
|
c683cbe962 | ||
|
|
bd270e4428 | ||
|
|
23d942d8a5 | ||
|
|
cbcd6b99d2 | ||
|
|
ee5c098a9f | ||
|
|
070a15d679 | ||
|
|
befa5174f8 | ||
|
|
d6c4366f40 | ||
|
|
181cfefa63 | ||
|
|
0e7c885961 | ||
|
|
d58e90d93f | ||
|
|
cd872b47e6 | ||
|
|
2683c5b318 | ||
|
|
c71f60a164 | ||
|
|
881cbc1947 | ||
|
|
f3e7febbd1 | ||
|
|
e68ad13031 | ||
|
|
7fbbe1e63a | ||
|
|
a5ca72af3c | ||
|
|
1ce6641eb3 | ||
|
|
5dc413ab8c | ||
|
|
50b615e86c | ||
|
|
5b4c6743e7 | ||
|
|
99a3906978 | ||
|
|
981db34736 | ||
|
|
c079a8c5a8 | ||
|
|
ad70adf6ce | ||
|
|
89110b52bd | ||
|
|
8a69f050a6 | ||
|
|
9e38a44406 | ||
|
|
3514ab23c3 | ||
|
|
b59e3c383d | ||
|
|
eeba565d78 | ||
|
|
687e54cc87 | ||
|
|
ac73939440 | ||
|
|
7a3eb8d03f | ||
|
|
3d26904e0b | ||
|
|
468e14bc35 | ||
|
|
e2dc61212e | ||
|
|
aa911ad4bc | ||
|
|
bb5494264c | ||
|
|
c0116a3baa | ||
|
|
9c7497b447 | ||
|
|
fa3a79fdeb | ||
|
|
2987240df4 | ||
|
|
17a1640da5 | ||
|
|
a86da8ee11 | ||
|
|
20e373115d | ||
|
|
531260fbc5 | ||
|
|
47a3dfcef9 | ||
|
|
c5ca51fd80 | ||
|
|
2f2e44c032 | ||
|
|
26acb37098 | ||
|
|
466308cde8 | ||
|
|
dc9af9d8b0 | ||
|
|
c17bfd65e7 | ||
|
|
6de07ab8c2 | ||
|
|
0b8aab802c | ||
|
|
13f28fbe33 | ||
|
|
2eda679966 | ||
|
|
35c778b644 | ||
|
|
96f8ba5a80 | ||
|
|
0daaa94958 | ||
|
|
e672503fda | ||
|
|
4d86c9e0ae | ||
|
|
80fbc474f6 | ||
|
|
dacdc6abfc | ||
|
|
58bc8b58de | ||
|
|
07ead670dd | ||
|
|
00fd760f71 | ||
|
|
a71186905d | ||
|
|
a7e0524d01 | ||
|
|
7d7c1241d4 | ||
|
|
1f6e928d78 | ||
|
|
9bc256bdab | ||
|
|
734096260d | ||
|
|
1b4b6f9435 | ||
|
|
54fe5c85d6 | ||
|
|
f336891bf3 | ||
|
|
d2a3c9c61f | ||
|
|
6968959fe2 | ||
|
|
7749fe5000 | ||
|
|
bf45d2df5d | ||
|
|
b2222844ae | ||
|
|
3d21f1ecc6 | ||
|
|
cde280de60 | ||
|
|
9b415ddff2 | ||
|
|
906c3ac2b6 | ||
|
|
498611d4d4 | ||
|
|
a11bc73d68 | ||
|
|
9616ae5d63 | ||
|
|
c81476d2a7 | ||
|
|
397f001352 | ||
|
|
7edf274477 | ||
|
|
3c1a26c4f5 | ||
|
|
1c695846d5 | ||
|
|
a4c6d1e0e6 | ||
|
|
e51fe83800 | ||
|
|
316076d81e | ||
|
|
4073055d8d | ||
|
|
c6e0ec1c06 | ||
|
|
49aaa48e6e | ||
|
|
0eb882883e | ||
|
|
a6c25551dd | ||
|
|
0a3f73860a | ||
|
|
1de159d65c | ||
|
|
e2c411fefe | ||
|
|
3cf1c64e12 | ||
|
|
b159752b72 | ||
|
|
0d7db59c9e | ||
|
|
a8bf90a68b | ||
|
|
96aff39272 | ||
|
|
a9ae08fc1f | ||
|
|
a24f989c01 | ||
|
|
41ff45d14c | ||
|
|
6ad80bf66b | ||
|
|
eeed48a1f7 | ||
|
|
232bc0d076 | ||
|
|
ac6b87add4 | ||
|
|
2e3bff7d48 | ||
|
|
bd223b4c39 | ||
|
|
a75dc11427 | ||
|
|
30acaffb72 | ||
|
|
2818102b8b | ||
|
|
519e5a6f92 | ||
|
|
071740e7c1 | ||
|
|
7aafb2f4c3 | ||
|
|
3c72988d77 | ||
|
|
3c01dfbd42 | ||
|
|
3a5829aa3e | ||
|
|
ffeeddb37a | ||
|
|
50945493c1 | ||
|
|
fa80c4797a | ||
|
|
650e8bf703 | ||
|
|
13d57737ae | ||
|
|
a6d1a3dfdd | ||
|
|
afffdc06e5 | ||
|
|
80f1b1c795 | ||
|
|
06111e2731 | ||
|
|
adb49371bb | ||
|
|
7b726ded20 | ||
|
|
9f85a0c840 | ||
|
|
f92755c920 | ||
|
|
d141c27765 | ||
|
|
062ac65f0f | ||
|
|
bb420d0806 | ||
|
|
0018032423 | ||
|
|
3dd48ac73c | ||
|
|
4632a6f305 | ||
|
|
eda570d4f1 | ||
|
|
b0127d746d | ||
|
|
5a66ca69c4 | ||
|
|
1c17277f03 | ||
|
|
d771bdc8ff | ||
|
|
eace3f4259 | ||
|
|
8f88da70a6 | ||
|
|
b797be9642 | ||
|
|
2395b7a10a | ||
|
|
0764795c08 | ||
|
|
ea8b7a1d56 | ||
|
|
7c6827f5f5 | ||
|
|
5a6f16ef8d | ||
|
|
8dfdcdd0b7 | ||
|
|
67a2594108 | ||
|
|
871f9635e3 | ||
|
|
25251f3546 | ||
|
|
98824f477e | ||
|
|
aae9a117e8 | ||
|
|
452639c3ce | ||
|
|
6bd45e0a9b | ||
|
|
be100a3ac6 | ||
|
|
96a8cd789c | ||
|
|
d195e1dbf5 | ||
|
|
e6a8e788f5 | ||
|
|
a755ed441e | ||
|
|
de8f294329 | ||
|
|
021171c07d | ||
|
|
013ddb72ed | ||
|
|
207c226f66 | ||
|
|
b4ff98499b | ||
|
|
8471d337a2 | ||
|
|
2f84b94227 | ||
|
|
8dcd6063b7 | ||
|
|
caefe7ba67 | ||
|
|
ad6cebc59b | ||
|
|
e8d2e6d806 | ||
|
|
39352c40d1 | ||
|
|
9994262abc | ||
|
|
5bcf923381 | ||
|
|
ab5dd0b733 | ||
|
|
5a8a6310f8 | ||
|
|
a634c7a587 | ||
|
|
353c5d6d95 | ||
|
|
92698c486c | ||
|
|
898dd1161d | ||
|
|
361f289d0e | ||
|
|
b49d036fcd | ||
|
|
cfee9d86c0 | ||
|
|
92622dfbd7 | ||
|
|
80c2876350 | ||
|
|
fb99f6db8a | ||
|
|
862f8193ef | ||
|
|
490c6679eb | ||
|
|
dd278ca964 | ||
|
|
0892bb24d0 | ||
|
|
83ce5e9422 | ||
|
|
c4ba92c7cb | ||
|
|
846bdf10b0 | ||
|
|
91a46ea7df | ||
|
|
e4ec68a86c | ||
|
|
410655052f | ||
|
|
17162e967a | ||
|
|
c119a1bc21 | ||
|
|
7f2e98f714 | ||
|
|
82f5d8be21 | ||
|
|
73fa675346 | ||
|
|
1f2812b2e3 | ||
|
|
6f05179db8 | ||
|
|
fc8848e97c | ||
|
|
b991102bfa | ||
|
|
e3768805a6 | ||
|
|
cdb65665a6 | ||
|
|
3765bc410c | ||
|
|
69bbd76f33 | ||
|
|
b61b3e1115 | ||
|
|
835f717e47 | ||
|
|
6a35a3ece0 | ||
|
|
518621a1bd | ||
|
|
51acfa1dce | ||
|
|
5be6c9176a | ||
|
|
dfa83c94f7 | ||
|
|
67aedd4770 | ||
|
|
346eb59da9 | ||
|
|
3195594ef3 | ||
|
|
489b412308 | ||
|
|
2d0e8d4ca0 | ||
|
|
27f94c81a2 | ||
|
|
1e865ecacc | ||
|
|
f293a02485 | ||
|
|
ddf00600c6 | ||
|
|
88cd639493 | ||
|
|
0a30ed45f9 | ||
|
|
b5b282c141 | ||
|
|
c4c60cb263 | ||
|
|
e3cf4e928e | ||
|
|
d8a08ef900 | ||
|
|
8b8f3bf492 | ||
|
|
3983073d6c | ||
|
|
82b22fa3f2 | ||
|
|
8a38f73771 | ||
|
|
37da82b138 | ||
|
|
dd17cb23d9 | ||
|
|
8f3afd9f7c | ||
|
|
6e39188f0b | ||
|
|
667cacea12 | ||
|
|
49f0ec981c | ||
|
|
a60d6e9223 | ||
|
|
2d111c1e25 | ||
|
|
874cde4f72 | ||
|
|
826318760e | ||
|
|
de790051aa | ||
|
|
00bf75839e | ||
|
|
b9d4501dcc | ||
|
|
46fb1789b0 | ||
|
|
a1e8bf841b | ||
|
|
6662dbfdd6 | ||
|
|
39b9710d16 | ||
|
|
4fe303da72 | ||
|
|
0662506d35 | ||
|
|
ea987f5601 | ||
|
|
bcae586122 | ||
|
|
e56f188a12 | ||
|
|
8fda9beb7b | ||
|
|
0f50a6682b | ||
|
|
524d38c450 | ||
|
|
68a2e52355 | ||
|
|
447d1f940f | ||
|
|
1cbb59b5d0 | ||
|
|
6f5bcdef90 | ||
|
|
f6f1c5a350 | ||
|
|
f6545c38be | ||
|
|
53fe73d3ee | ||
|
|
3bf5ab1ef7 | ||
|
|
b4813ff866 | ||
|
|
845269e9a5 | ||
|
|
59968d92ab | ||
|
|
d5b7cd370b | ||
|
|
b4434cce17 | ||
|
|
e73227519b | ||
|
|
f8e6d5cbfb | ||
|
|
70bde8c899 | ||
|
|
361dce2b96 | ||
|
|
c83ce28bf4 | ||
|
|
b9a3620a4c | ||
|
|
a939a57811 | ||
|
|
8388ee8f1e | ||
|
|
befa40f5a2 | ||
|
|
36257e2b0f | ||
|
|
769022e88c | ||
|
|
51c180a8f4 | ||
|
|
2ffc5dc5c0 | ||
|
|
431ba01117 | ||
|
|
c6a4350b81 | ||
|
|
9ef8d0c9f8 | ||
|
|
e7606477c2 | ||
|
|
de093b5472 | ||
|
|
88cd9184d8 | ||
|
|
055c169c1f | ||
|
|
1988275695 | ||
|
|
82451bff00 | ||
|
|
e6fd0202a6 | ||
|
|
c21d84dd62 | ||
|
|
f392ac21cd | ||
|
|
d4e5473b86 | ||
|
|
760298c6bf | ||
|
|
d949b99629 | ||
|
|
fff9719e48 | ||
|
|
5858a6eb19 | ||
|
|
46acc1b95e | ||
|
|
da4654b859 | ||
|
|
32869a9a45 | ||
|
|
4cfbccc5d6 | ||
|
|
6301196e67 | ||
|
|
0c61c2badd | ||
|
|
1207526161 | ||
|
|
405d9d524f | ||
|
|
f724717821 | ||
|
|
6247efa8b6 | ||
|
|
7045545419 | ||
|
|
17a0a2be6f | ||
|
|
0e92cfa007 | ||
|
|
04e17872d0 | ||
|
|
59255c720d | ||
|
|
eb71e2c606 | ||
|
|
db2905ba9f | ||
|
|
d097a4abd2 | ||
|
|
459cfd7ab8 | ||
|
|
317229afee | ||
|
|
1e2f16d6b3 | ||
|
|
be07c1668f | ||
|
|
90ddb3dc34 | ||
|
|
f968e86387 | ||
|
|
56639f0bda | ||
|
|
8ae40bfd7c | ||
|
|
a7901c53ce | ||
|
|
76f856fa4f | ||
|
|
63a4cda442 | ||
|
|
603aa89609 | ||
|
|
51dd388912 | ||
|
|
c4708f0260 | ||
|
|
dcfe2e3072 | ||
|
|
9d536fefff | ||
|
|
ed69d15ee1 | ||
|
|
4cd7f0575e | ||
|
|
ad1507dadf | ||
|
|
c358259437 | ||
|
|
6b961c532f | ||
|
|
bb8f872a13 | ||
|
|
93df8a65cd | ||
|
|
de9fd2fcd7 | ||
|
|
24ae8dfda8 | ||
|
|
909e4b3a9f | ||
|
|
09c46447d9 | ||
|
|
239a7cc072 | ||
|
|
f7ccff61e0 | ||
|
|
f8a7483467 | ||
|
|
0a7b3fa396 | ||
|
|
e5cf8bcc04 | ||
|
|
f93230ce44 | ||
|
|
bdde2cea31 | ||
|
|
d44bb6c908 | ||
|
|
052e30b739 | ||
|
|
48a7ac80f5 | ||
|
|
c9717c2332 | ||
|
|
aaed0a9a63 | ||
|
|
c29648ac9b | ||
|
|
cb1f52a8f4 | ||
|
|
5f759edc1b | ||
|
|
8897261836 | ||
|
|
23b1426567 | ||
|
|
329658ff54 | ||
|
|
482059cc9b | ||
|
|
4e509fc479 | ||
|
|
6b1edd9d10 | ||
|
|
d0c692fe03 | ||
|
|
36ee1089a8 | ||
|
|
723bf3e657 |
137
.eslintrc.json
137
.eslintrc.json
@@ -3,60 +3,21 @@
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": ["tsconfig.json", "tsconfig.commonjs.json"],
|
||||
"sourceType": "module"
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"impliedStrict": true
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/class-name-casing": "off",
|
||||
"indent": "off",
|
||||
"@typescript-eslint/indent": [
|
||||
"error",
|
||||
4
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"off",
|
||||
{
|
||||
"multiline": {
|
||||
"delimiter": "none",
|
||||
"requireLast": true
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/prefer-namespace-keyword": "warn",
|
||||
"@typescript-eslint/quotes": [
|
||||
"error",
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/semi": [
|
||||
"off",
|
||||
null
|
||||
],
|
||||
"@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": [
|
||||
@@ -70,6 +31,90 @@
|
||||
"no-unsafe-finally": "warn",
|
||||
"no-var": "error",
|
||||
"spaced-comment": "error",
|
||||
"semi": "warn"
|
||||
}
|
||||
"semi": "warn",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
"selector": "ExportDefaultDeclaration",
|
||||
"message": "Default exports are not allowed"
|
||||
}
|
||||
],
|
||||
"no-throw-literal": "error",
|
||||
"key-spacing": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"array-bracket-spacing": "error",
|
||||
"space-in-parens": "error",
|
||||
"computed-property-spacing": "error",
|
||||
"prefer-const": ["error", {
|
||||
"destructuring": "all",
|
||||
"ignoreReadBeforeAssign": false
|
||||
}],
|
||||
"space-before-function-paren": "off",
|
||||
"func-call-spacing": "off",
|
||||
"no-multi-spaces": "error",
|
||||
"block-spacing": "error",
|
||||
"keyword-spacing": "off",
|
||||
"space-before-blocks": "error",
|
||||
"semi-spacing": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.ts", "**/*.tsx"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": ["tsconfig.json", "tsconfig.commonjs.json"],
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/class-name-casing": "off",
|
||||
"@typescript-eslint/indent": [
|
||||
"error",
|
||||
4
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"off",
|
||||
{
|
||||
"multiline": {
|
||||
"delimiter": "none",
|
||||
"requireLast": true
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/prefer-namespace-keyword": "warn",
|
||||
"@typescript-eslint/quotes": [
|
||||
"error",
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/semi": [
|
||||
"off",
|
||||
null
|
||||
],
|
||||
"@typescript-eslint/type-annotation-spacing": "error",
|
||||
"@typescript-eslint/brace-style": [
|
||||
"error",
|
||||
"1tbs", { "allowSingleLine": true }
|
||||
],
|
||||
"@typescript-eslint/comma-spacing": "error",
|
||||
"@typescript-eslint/space-before-function-paren": ["error", {
|
||||
"anonymous": "always",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}],
|
||||
"@typescript-eslint/func-call-spacing": ["error"],
|
||||
"@typescript-eslint/keyword-spacing": ["error"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
18
.github/workflows/lint.yml
vendored
18
.github/workflows/lint.yml
vendored
@@ -1,18 +0,0 @@
|
||||
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
|
||||
20
.github/workflows/node.yml
vendored
Normal file
20
.github/workflows/node.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 17
|
||||
- run: npm ci
|
||||
- run: sudo apt-get install xvfb
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
- name: Test
|
||||
run: xvfb-run --auto-servernum npm run jest
|
||||
- name: Build
|
||||
run: npm run build
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,3 +9,5 @@ tsconfig.commonjs.tsbuildinfo
|
||||
|
||||
*.sublime-workspace
|
||||
.idea
|
||||
|
||||
.DS_Store
|
||||
@@ -1 +1,5 @@
|
||||
tsconfig.commonjs.buildinfo
|
||||
tests
|
||||
perf-tests
|
||||
_spec
|
||||
*.tsbuildinfo
|
||||
*.js.map
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -7,6 +7,8 @@
|
||||
"*.gql.ts": "graphql"
|
||||
},
|
||||
"eslint.options": {
|
||||
"ignorePattern": ["webpack.config.js", "scripts/*"],
|
||||
"overrideConfig": {
|
||||
"ignorePatterns": ["webpack.config.js", "scripts/*"],
|
||||
},
|
||||
}
|
||||
}
|
||||
305
CHANGELOG.md
Normal file
305
CHANGELOG.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# Change Log
|
||||
All notable changes to this project will be documented in this file, following the suggestions of [Keep a CHANGELOG](http://keepachangelog.com/). This project adheres to [Semantic Versioning](http://semver.org/) for its most widely used - and defacto - public interfaces.
|
||||
|
||||
Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here.
|
||||
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v3.0.0-dev.8] - 2021-12-31
|
||||
|
||||
- Add ``PluginFeatureDetection`` and disable WBOIT in Safari 15.
|
||||
- Add ``disable-wboit`` Viewer GET param
|
||||
- Add ``prefer-webgl1`` Viewer GET param
|
||||
- [Breaking] Refactor direct-volume rendering
|
||||
- Remove isosurface render-mode (use GPU MC instead)
|
||||
- Move coloring into theme (like for other geometries/renderables)
|
||||
- Add ``direct`` color type
|
||||
- Remove color from transfer-function (now only alpha)
|
||||
- Add direct-volume color theme support
|
||||
- Add volume-value color theme
|
||||
- [Breaking] Use size theme in molecular/gaussian surface & label representations
|
||||
- This is breaking because it was hardcoded to ``physical`` internally but the repr size theme default was ``uniform`` (now ``physical``)
|
||||
|
||||
## [v3.0.0-dev.7] - 2021-12-20
|
||||
|
||||
- Reduce number of created programs/shaders
|
||||
- Support specifying variants when creating graphics render-items
|
||||
- Change double-side shader param from define to uniform
|
||||
- Remove dMarkerType shader define (use uMarker as needed)
|
||||
- Support to ignore defines depending on the shader variant
|
||||
- Combine pickObject/pickInstance/pickGroup shader variants into one
|
||||
- Combine markingDepth/markingMask shader variants into one
|
||||
- Correctly set shader define flags for overpaint, transparency, substance, clipping
|
||||
- [Breaking] Add per-object clip rendering properties (variant/objects)
|
||||
- ``SimpleSettingsParams.clipping.variant/objects`` and ``RendererParams.clip`` were removed
|
||||
|
||||
## [v3.0.0-dev.6] - 2021-12-19
|
||||
|
||||
- Enable temporal multi-sampling by default
|
||||
- Fix flickering during marking with camera at rest
|
||||
- Enable ``aromaticBonds`` in structure representations by default
|
||||
- Add ``PluginConfig.Structure.DefaultRepresentationPreset``
|
||||
- Add ModelArchive support
|
||||
- schema extensions (e.g., AlphaFold uses it for the pLDDT score)
|
||||
- ModelArchive option in DownloadStructure action
|
||||
- ``model-archive`` GET parameter for Viewer app
|
||||
- ``Viewer.loadModelArchive`` method
|
||||
- Improve support for loading AlphaFold structures
|
||||
- Automatic coloring by pLDDT
|
||||
- AlphaFold DB option in DownloadStructure action
|
||||
- ``afdb`` GET parameter for Viewer app
|
||||
- ``Viewer.loadAlphaFoldDb`` method
|
||||
- Add QualityAssessment extension (using data from ma_qa_metric_local mmcif category)
|
||||
- pLDDT & qmean score: coloring, repr presets, molql symbol, loci labels (including avg for mutli-residue selections)
|
||||
- pLDDT: selection query
|
||||
- Warn about erroneous symmetry operator matrix (instead of throwing an error)
|
||||
- Added ``createPluginUI`` to ``mol-plugin-ui``
|
||||
- Support ``onBeforeUIRender`` to make sure initial UI works with custom presets and similar features.
|
||||
- [Breaking] Removed ``createPlugin`` and ``createPluginAsync`` from ``mol-plugin-ui``
|
||||
- Please use ``createPluginUI`` instead
|
||||
- Improve aromatic bonds handling
|
||||
- Don't detect aromatic bonds for rings < 5 atoms based on planarity
|
||||
- Prefer atoms in aromatic rings as bond reference positions
|
||||
|
||||
## [v3.0.0-dev.5] - 2021-12-16
|
||||
|
||||
- Fix initial camera reset not triggering for some entries.
|
||||
|
||||
## [v3.0.0-dev.4] - 2021-12-14
|
||||
|
||||
- Add ``bumpiness`` (per-object and per-group), ``bumpFrequency`` & ``bumpAmplitude`` (per-object) render parameters (#299)
|
||||
- Change ``label`` representation defaults: Use text border instead of rectangle background
|
||||
- Add outline color option to renderer
|
||||
- Fix false positives in Model.isFromPdbArchive
|
||||
- Add drag and drop support for loading any file, including multiple at once
|
||||
- If there are session files (.molx or .molj) among the dropped files, only the first session will be loaded
|
||||
- Add drag and drop overlay
|
||||
- Safari 15.1 - 15.3 WebGL 2 support workaround
|
||||
- [Breaking] Move ``react`` and ``react-dom`` to ``peerDependencies``. This might break some builds.
|
||||
|
||||
## [v3.0.0-dev.3] - 2021-12-4
|
||||
|
||||
- Fix OBJ and USDZ export
|
||||
|
||||
## [v3.0.0-dev.2] - 2021-12-1
|
||||
|
||||
- Do not include tests and source maps in NPM package
|
||||
|
||||
## [v3.0.0-dev.0] - 2021-11-28
|
||||
|
||||
- Add multiple lights support (with color, intensity, and direction parameters)
|
||||
- [Breaking] Add per-object material rendering properties
|
||||
- ``SimpleSettingsParams.lighting.renderStyle`` and ``RendererParams.style`` were removed
|
||||
- Add substance theme with per-group material rendering properties
|
||||
- ``StructureComponentManager.Options`` state saving support
|
||||
- ``ParamDefinition.Group.presets`` support
|
||||
|
||||
## [v2.4.1] - 2021-11-28
|
||||
|
||||
- Fix: allow atoms in aromatic rings to do hydrogen bonds
|
||||
|
||||
## [v2.4.0] - 2021-11-25
|
||||
|
||||
- Fix secondary-structure property handling
|
||||
- StructureElement.Property was incorrectly resolving type & key
|
||||
- StructureSelectionQuery helpers 'helix' & 'beta' were not ensuring property availability
|
||||
- Re-enable VAO with better workaround (bind null elements buffer before deleting)
|
||||
- Add ``Representation.geometryVersion`` (increments whenever the geometry of any of its visuals changes)
|
||||
- Add support for grid-based smoothing of Overpaint and Transparency visual state for surfaces
|
||||
|
||||
## [v2.3.9] - 2021-11-20
|
||||
|
||||
- Workaround: switch off VAO support for now
|
||||
|
||||
## [v2.3.8] - 2021-11-20
|
||||
|
||||
- Fix double canvas context creation (in plugin context)
|
||||
- Fix unused vertex attribute handling (track which are used, disable the rest)
|
||||
- Workaround for VAO issue in Chrome 96 (can cause WebGL to crash on geometry updates)
|
||||
|
||||
## [v2.3.7] - 2021-11-15
|
||||
|
||||
- Added ``ViewerOptions.collapseRightPanel``
|
||||
- Added ``Viewer.loadTrajectory`` to support loading "composed" trajectories (e.g. from gro + xtc)
|
||||
- Fix: handle parent in Structure.remapModel
|
||||
- Add ``rounded`` and ``square`` helix profile options to Cartoon representation (in addition to the default ``elliptical``)
|
||||
|
||||
## [v2.3.6] - 2021-11-8
|
||||
|
||||
- Add additional measurement controls: orientation (box, axes, ellipsoid) & plane (best fit)
|
||||
- Improve aromatic bond visuals (add ``aromaticScale``, ``aromaticSpacing``, ``aromaticDashCount`` params)
|
||||
- [Breaking] Change ``adjustCylinderLength`` default to ``false`` (set to true for focus representation)
|
||||
- Fix marker highlight color overriding select color
|
||||
- CellPack extension update
|
||||
- add binary model support
|
||||
- add compartment (including membrane) geometry support
|
||||
- add latest mycoplasma model example
|
||||
- Prefer WebGL1 in Safari 15.1.
|
||||
|
||||
## [v2.3.5] - 2021-10-19
|
||||
|
||||
- Fix sequence viewer for PDB files with COMPND record and multichain entities.
|
||||
- Fix index pair bonds order assignment
|
||||
|
||||
## [v2.3.4] - 2021-10-12
|
||||
|
||||
- Fix pickScale not taken into account in line/point shader
|
||||
- Add pixel-scale, pick-scale & pick-padding GET params to Viewer app
|
||||
- Fix selecting bonds not adding their atoms in selection manager
|
||||
- Add ``preferAtoms`` option to SelectLoci/HighlightLoci behaviors
|
||||
- Make the implicit atoms of bond visuals pickable
|
||||
- Add ``preferAtomPixelPadding`` to Canvas3dInteractionHelper
|
||||
- Add points & crosses visuals to Line representation
|
||||
- Add ``pickPadding`` config option (look around in case target pixel is empty)
|
||||
- Add ``multipleBonds`` param to bond visuals with options: off, symmetric, offset
|
||||
- Fix ``argparse`` config in servers.
|
||||
|
||||
## [v2.3.3] - 2021-10-01
|
||||
|
||||
- Fix direct volume shader
|
||||
|
||||
## [v2.3.2] - 2021-10-01
|
||||
|
||||
- Prefer WebGL1 on iOS devices until WebGL2 support has stabilized.
|
||||
|
||||
## [v2.3.1] - 2021-09-28
|
||||
|
||||
- Add Charmm saccharide names
|
||||
- Treat missing occupancy column as occupancy of 1
|
||||
- Fix line shader not accounting for aspect ratio
|
||||
- [Breaking] Fix point repr & shader
|
||||
- Was unusable with ``wboit``
|
||||
- Replaced ``pointFilledCircle`` & ``pointEdgeBleach`` params by ``pointStyle`` (square, circle, fuzzy)
|
||||
- Set ``pointSizeAttenuation`` to false by default
|
||||
- Set ``sizeTheme`` to ``uniform`` by default
|
||||
- Add ``markerPriority`` option to Renderer (useful in combination with edges of marking pass)
|
||||
- Add support support for ``chem_comp_bond`` and ``struct_conn`` categories (fixes ModelServer behavior where these categories should have been present)
|
||||
- Model and VolumeServer: fix argparse config
|
||||
|
||||
## [v2.3.0] - 2021-09-06
|
||||
|
||||
- Take include/exclude flags into account when displaying aromatic bonds
|
||||
- Improve marking performance
|
||||
- Avoid unnecessary draw calls/ui updates when marking
|
||||
- Check if loci is superset of visual
|
||||
- Check if loci overlaps with unit visual
|
||||
- Ensure ``Interval`` is used for ranges instead of ``SortedArray``
|
||||
- Add uniform marker type
|
||||
- Special case for reversing previous mark
|
||||
- Add optional marking pass
|
||||
- Outlines visible and hidden parts of highlighted/selected groups
|
||||
- Add highlightStrength/selectStrength renderer params
|
||||
|
||||
## [v2.2.3] - 2021-08-25
|
||||
|
||||
- Add ``invertCantorPairing`` helper function
|
||||
- Add ``Mesh`` processing helper ``.smoothEdges``
|
||||
- Smooth border of molecular-surface with ``includeParent`` enabled
|
||||
- Hide ``includeParent`` option from gaussian-surface visuals (not particularly useful)
|
||||
- Improved ``StructureElement.Loci.size`` performance (for marking large cellpack models)
|
||||
- Fix new ``TransformData`` issues (camera/bounding helper not showing up)
|
||||
- Improve marking performance (avoid superfluous calls to ``StructureElement.Loci.isWholeStructure``)
|
||||
|
||||
## [v2.2.2] - 2021-08-11
|
||||
|
||||
- Fix ``TransformData`` issues [#133](https://github.com/molstar/molstar/issues/133)
|
||||
- Fix ``mol-script`` query compiler const expression recognition.
|
||||
|
||||
## [v2.2.1] - 2021-08-02
|
||||
|
||||
- Add surrounding atoms (5 Angstrom) structure selection query
|
||||
- [Breaking] Add maxDistance prop to ``IndexPairBonds``
|
||||
- Fix coordinateSystem not handled in ``Structure.asParent``
|
||||
- Add ``dynamicBonds`` to ``Structure`` props (force re-calc on model change)
|
||||
- Expose as optional param in root structure transform helper
|
||||
- Add overpaint support to geometry exporters
|
||||
- ``InputObserver`` improvements
|
||||
- normalize wheel speed across browsers/platforms
|
||||
- support Safari gestures (used by ``TrackballControls``)
|
||||
- ``PinchInput.fractionDelta`` and use it in ``TrackballControls``
|
||||
|
||||
## [v2.2.0] - 2021-07-31
|
||||
|
||||
- Add ``tubularHelices`` parameter to Cartoon representation
|
||||
- Add ``SdfFormat`` and update SDF parser to be able to parse data headers according to spec (hopefully :)) #230
|
||||
- Fix mononucleotides detected as polymer components (#229)
|
||||
- Set default outline scale back to 1
|
||||
- Improved DCD reader cell angle handling (interpret near 0 angles as 90 deg)
|
||||
- Handle more residue/atom names commonly used in force-fields
|
||||
- Add USDZ support to ``geo-export`` extension.
|
||||
- Fix ``includeParent`` support for multi-instance bond visuals.
|
||||
- Add ``operator`` Loci granularity, selecting everything with the same operator name.
|
||||
- Prefer ``_label_seq_id`` fields in secondary structure assignment.
|
||||
- Support new EMDB API (https://www.ebi.ac.uk/emdb/api/entry/map/[EMBD-ID]) for EM volume contour levels.
|
||||
- ``Canvas3D`` tweaks:
|
||||
- Update ``forceDraw`` logic.
|
||||
- Ensure the scene is re-rendered when viewport size changes.
|
||||
- Support ``noDraw`` mode in ``PluginAnimationLoop``.
|
||||
|
||||
## [v2.1.0] - 2021-07-05
|
||||
|
||||
- Add parameter for to display aromatic bonds as dashes next to solid cylinder/line.
|
||||
- Add backbone representation
|
||||
- Fix outline in orthographic mode and set default scale to 2.
|
||||
|
||||
## [v2.0.7] - 2021-06-23
|
||||
|
||||
- Add ability to specify ``volumeIndex`` in ``Viewer.loadVolumeFromUrl`` to better support Volume Server inputs.
|
||||
- Support in-place reordering for trajectory ``Frame.x/y/z`` arrays for better memory efficiency.
|
||||
- Fixed text CIF encoder edge cases (most notably single whitespace not being escaped).
|
||||
|
||||
## [v2.0.6] - 2021-06-01
|
||||
|
||||
- Add glTF (GLB) and STL support to ``geo-export`` extension.
|
||||
- Protein crosslink improvements
|
||||
- Change O-S bond distance to allow for NOS bridges (doi:10.1038/s41586-021-03513-3)
|
||||
- Added NOS-bridges query & improved disulfide-bridges query
|
||||
- Fix #178: ``IndexPairBonds`` for non-single residue structures (bug due to atom reordering).
|
||||
- Add volumetric color smoothing for MolecularSurface and GaussianSurface representations (#173)
|
||||
- Fix nested 3d grid lookup that caused results being overwritten in non-covalent interactions computation.
|
||||
- Basic implementation of ``BestDatabaseSequenceMapping`` (parse from CIF, color theme, superposition).
|
||||
- Add atom id ranges support to Selection UI.
|
||||
|
||||
## [v2.0.5] - 2021-04-26
|
||||
|
||||
- Ability to pass ``Canvas3DContext`` to ``PluginContext.fromCanvas``.
|
||||
- Relative frame support for ``Canvas3D`` viewport.
|
||||
- Fix bug in screenshot copy UI.
|
||||
- Add ability to select residues from a list of identifiers to the Selection UI.
|
||||
- Fix SSAO bugs when used with ``Canvas3D`` viewport.
|
||||
- Support for full pausing (no draw) rendering: ``Canvas3D.pause(true)``.
|
||||
- Add ``MeshBuilder.addMesh``.
|
||||
- Add ``Torus`` primitive.
|
||||
- Lazy volume loading support.
|
||||
- [Breaking] ``Viewer.loadVolumeFromUrl`` signature change.
|
||||
- ``loadVolumeFromUrl(url, format, isBinary, isovalues, entryId)`` => ``loadVolumeFromUrl({ url, format, isBinary }, isovalues, { entryId, isLazy })``
|
||||
- Add ``TextureMesh`` support to ``geo-export`` extension.
|
||||
|
||||
## [v2.0.4] - 2021-04-20
|
||||
|
||||
- [WIP] Mesh export extension
|
||||
- ``Structure.eachAtomicHierarchyElement`` (#161)
|
||||
- Fixed reading multi-line values in SDF format
|
||||
- Fixed Measurements UI labels (#166)
|
||||
|
||||
## [v2.0.3] - 2021-04-09
|
||||
|
||||
- Add support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
|
||||
- [Breaking] The ``zip`` function is now asynchronous and expects a ``RuntimeContext``. Also added ``Zip()`` returning a ``Task``.
|
||||
- [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
|
||||
|
||||
## [v2.0.2] - 2021-03-29
|
||||
|
||||
- Add ``Canvas3D.getRenderObjects``.
|
||||
- [WIP] Animate state interpolating, including model trajectories
|
||||
- Recognise MSE, SEP, TPO, PTR and PCA as non-standard amino-acids.
|
||||
- Fix VolumeFromDensityServerCif transform label
|
||||
|
||||
## [v2.0.1] - 2021-03-23
|
||||
|
||||
- Exclude tsconfig.commonjs.tsbuildinfo from npm bundle
|
||||
|
||||
## [v2.0.0] - 2021-03-23
|
||||
|
||||
Too many changes to list as this is the start of the changelog... Notably, default exports are now forbidden.
|
||||
37
README.md
37
README.md
@@ -5,15 +5,15 @@
|
||||
|
||||
# Mol*
|
||||
|
||||
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.
|
||||
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that serves as a basis for the next-generation data delivery and analysis tools for (not only) macromolecular structure data. Mol* development was jointly initiated by PDBe and RCSB PDB to combine and build on the strengths of [LiteMol](https://litemol.org) (developed by PDBe) and [NGL](https://nglviewer.org) (developed by RCSB PDB) viewers.
|
||||
|
||||
This particular project is the implementation of this technology (still under development).
|
||||
When using Mol*, please cite:
|
||||
|
||||
*If you are looking for the "MOLeculAR structure annoTator", that package is now available on NPM as [MolArt](https://www.npmjs.com/package/molart).*
|
||||
David Sehnal, Sebastian Bittrich, Mandar Deshpande, Radka Svobodová, Karel Berka, Václav Bazgier, Sameer Velankar, Stephen K Burley, Jaroslav Koča, Alexander S Rose: [Mol* Viewer: modern web app for 3D visualization and analysis of large biomolecular structures](https://doi.org/10.1093/nar/gkab314), *Nucleic Acids Research*, 2021; https://doi.org/10.1093/nar/gkab314.
|
||||
|
||||
## Project Overview
|
||||
## Project Structure Overview
|
||||
|
||||
The core of Mol* currently consists of these modules (see under `src/`):
|
||||
The core of Mol* 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.)
|
||||
@@ -29,7 +29,6 @@ The core of Mol* currently consists of these modules (see under `src/`):
|
||||
- `mol-gl` A wrapper around WebGL.
|
||||
- `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 building UIs.
|
||||
- `mol-plugin` Allow to define modular Mol* plugin instances utilizing `mol-state` and `mol-canvas3d`.
|
||||
- `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.
|
||||
@@ -41,7 +40,7 @@ Moreover, the project contains the implementation of `servers`, including
|
||||
- `servers/volume` A tool for accessing volumetric experimental data related to molecular structures.
|
||||
- `servers/plugin-state` A basic server to store Mol* Plugin states.
|
||||
|
||||
The project also contains performance tests (`perf-tests`), `examples`, and basic proof of concept `cli` apps (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
|
||||
The project also contains performance tests (`perf-tests`), `examples`, and `cli` apps (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
|
||||
|
||||
## Previous Work
|
||||
This project builds on experience from previous solutions:
|
||||
@@ -69,6 +68,17 @@ If working on just the viewer, ``npm run watch-viewer`` will provide shorter com
|
||||
|
||||
Debug/production mode in browsers can be turned on/off during runtime by calling ``setMolStarDebugMode(true/false, true/false)`` from the dev console.
|
||||
|
||||
### Cleaning and forcing a full rebuild
|
||||
npm run clean
|
||||
|
||||
Wipes the `build` and `lib` directories and `.tsbuildinfo` files.
|
||||
|
||||
npm run rebuild
|
||||
|
||||
Runs the cleanup script prior to building the project, forcing a full rebuild of the project.
|
||||
|
||||
Use these commands to resolve occassional build failures which may arise after some dependency updates. Once done, `npm run build` should work again. Note that full rebuilds take more time to complete.
|
||||
|
||||
### Build for production:
|
||||
NODE_ENV=production npm run build
|
||||
|
||||
@@ -99,6 +109,11 @@ and navigate to `build/viewer`
|
||||
|
||||
node lib/commonjs/cli/lipid-params -o src/mol-model/structure/model/types/lipids.ts
|
||||
|
||||
**Ion names**
|
||||
|
||||
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-ions.js src/mol-model/structure/model/types/ions.ts
|
||||
|
||||
|
||||
**GraphQL schemas**
|
||||
|
||||
node node_modules//@graphql-codegen/cli/bin -c src/extensions/rcsb/graphql/codegen.yml
|
||||
@@ -118,9 +133,9 @@ and navigate to `build/viewer`
|
||||
|
||||
**Convert any CIF to BinaryCIF**
|
||||
|
||||
node lib/servers/model/preprocess -i file.cif -ob file.bcif
|
||||
node lib/commonjs/servers/model/preprocess -i file.cif -ob file.bcif
|
||||
|
||||
To see all available commands, use ``node lib/servers/model/preprocess -h``.
|
||||
To see all available commands, use ``node lib/commonjs/servers/model/preprocess -h``.
|
||||
|
||||
Or
|
||||
|
||||
@@ -164,11 +179,9 @@ To get syntax highlighting for shader and graphql files add the following to Vis
|
||||
## Contributing
|
||||
Just open an issue or make a pull request. All contributions are welcome.
|
||||
|
||||
## Roadmap
|
||||
Continually develop this prototype project. As individual modules become stable, make them into standalone libraries.
|
||||
|
||||
## Funding
|
||||
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/)
|
||||
* [EntosAI](https://www.entos.ai)
|
||||
|
||||
@@ -2,11 +2,11 @@ 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
|
||||
database_code.icsd
|
||||
database_code.mdf
|
||||
database_code.nbs
|
||||
database_code.csd
|
||||
database_code.cod
|
||||
|
||||
chemical.name_systematic
|
||||
chemical.name_common
|
||||
@@ -24,8 +24,8 @@ 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.name_h-m_full
|
||||
space_group.it_number
|
||||
space_group_symop.operation_xyz
|
||||
|
||||
cell.length_a
|
||||
@@ -35,14 +35,14 @@ cell.angle_alpha
|
||||
cell.angle_beta
|
||||
cell.angle_gamma
|
||||
cell.volume
|
||||
cell.formula_units_Z
|
||||
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.u_iso_or_equiv
|
||||
atom_site.adp_type
|
||||
atom_site.occupancy
|
||||
atom_site.calc_flag
|
||||
@@ -52,20 +52,13 @@ 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
|
||||
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
|
||||
|
||||
geom_bond.atom_site_label_1
|
||||
geom_bond.atom_site_label_2
|
||||
|
||||
|
@@ -246,6 +246,14 @@ citation_author.ordinal
|
||||
exptl.entry_id
|
||||
exptl.method
|
||||
|
||||
software.classification
|
||||
software.date
|
||||
software.description
|
||||
software.name
|
||||
software.pdbx_ordinal
|
||||
software.type
|
||||
software.version
|
||||
|
||||
struct.entry_id
|
||||
struct.title
|
||||
struct.pdbx_descriptor
|
||||
@@ -802,4 +810,58 @@ 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
|
||||
ihm_multi_state_modeling.details
|
||||
|
||||
ma_data.content_type
|
||||
ma_data.content_type_other_details
|
||||
ma_data.id
|
||||
ma_data.name
|
||||
|
||||
ma_model_list.data_id
|
||||
ma_model_list.model_group_id
|
||||
ma_model_list.model_group_name
|
||||
ma_model_list.model_id
|
||||
ma_model_list.model_name
|
||||
ma_model_list.model_type
|
||||
ma_model_list.ordinal_id
|
||||
|
||||
ma_qa_metric.id
|
||||
ma_qa_metric.mode
|
||||
ma_qa_metric.name
|
||||
ma_qa_metric.software_group_id
|
||||
ma_qa_metric.type
|
||||
|
||||
ma_qa_metric_global.metric_id
|
||||
ma_qa_metric_global.metric_value
|
||||
ma_qa_metric_global.model_id
|
||||
ma_qa_metric_global.ordinal_id
|
||||
|
||||
ma_qa_metric_local.label_asym_id
|
||||
ma_qa_metric_local.label_comp_id
|
||||
ma_qa_metric_local.label_seq_id
|
||||
ma_qa_metric_local.metric_id
|
||||
ma_qa_metric_local.metric_value
|
||||
ma_qa_metric_local.model_id
|
||||
ma_qa_metric_local.ordinal_id
|
||||
|
||||
ma_software_group.group_id
|
||||
ma_software_group.ordinal_id
|
||||
ma_software_group.software_id
|
||||
|
||||
ma_target_entity.data_id
|
||||
ma_target_entity.entity_id
|
||||
ma_target_entity.origin
|
||||
|
||||
ma_target_entity_instance.asym_id
|
||||
ma_target_entity_instance.details
|
||||
ma_target_entity_instance.entity_id
|
||||
|
||||
ma_target_ref_db_details.db_accession
|
||||
ma_target_ref_db_details.db_code
|
||||
ma_target_ref_db_details.db_name
|
||||
ma_target_ref_db_details.ncbi_taxonomy_id
|
||||
ma_target_ref_db_details.organism_scientific
|
||||
ma_target_ref_db_details.seq_db_align_begin
|
||||
ma_target_ref_db_details.seq_db_align_end
|
||||
ma_target_ref_db_details.seq_db_isoform
|
||||
ma_target_ref_db_details.target_entity_id
|
||||
|
@@ -24,4 +24,17 @@
|
||||
* Close backbone atoms but not linked (e.g. 4HIV)
|
||||
* Non-standard residues
|
||||
* Protein (1BRR, 5Z6Y)
|
||||
* DNA (5D3G)
|
||||
* DNA (5D3G)
|
||||
* Multiple models with different sets of ligands or missing ligands (1J6T, 1VRC, 2ICY, 1O2F)
|
||||
* Long linear sugar chain (4HG6)
|
||||
* Anisotropic B-factors/Ellipsoids (1EJG)
|
||||
* NOS bridges (LYS-CSO in 7B0L, 6ZWJ, 6ZWH)
|
||||
* Non-polymer components in polymer entities
|
||||
* PN2 in 1F80
|
||||
* ACE (many, e.g. 5AGU, 1E1X)
|
||||
* ACY in 7ABY
|
||||
* NH2 (many, e.g. 6Y13)
|
||||
|
||||
Assembly symmetries
|
||||
* 5M30 (Assembly 1, C3 local and pseudo)
|
||||
* 1RB8 (Assembly 1, I global)
|
||||
86
examples/ace2-hit.mol2
Normal file
86
examples/ace2-hit.mol2
Normal file
@@ -0,0 +1,86 @@
|
||||
@<TRIPOS>MOLECULE
|
||||
ace2_r_r2.top2000.poses.plain/3_Z1137565832_1_T2.pdb
|
||||
37 41 0 0 0
|
||||
SMALL
|
||||
GASTEIGER
|
||||
|
||||
@<TRIPOS>ATOM
|
||||
1 C 64.7720 85.9180 38.1090 C.3 1 LIG1 0.0799
|
||||
2 C 64.6440 84.6900 37.1570 C.3 1 LIG1 0.0306
|
||||
3 C 65.2660 83.4260 37.8080 C.3 1 LIG1 0.0927
|
||||
4 N 66.6560 83.6710 38.1790 N.pl3 1 LIG1 -0.2919
|
||||
5 C 67.7080 82.9280 37.6260 C.ar 1 LIG1 0.1520
|
||||
6 C 69.0970 83.1860 37.8250 C.ar 1 LIG1 0.0393
|
||||
7 C 70.0830 82.4100 37.1810 C.ar 1 LIG1 0.0436
|
||||
8 C 69.6450 81.3740 36.3510 C.ar 1 LIG1 0.1867
|
||||
9 N 70.3370 80.5030 35.6050 N.ar 1 LIG1 -0.1270
|
||||
10 N 69.4700 79.7480 35.0040 N.ar 1 LIG1 -0.1228
|
||||
11 C 68.2200 80.1120 35.3570 C.ar 1 LIG1 0.2583
|
||||
12 N 68.3350 81.1470 36.2040 N.ar 1 LIG1 -0.1866
|
||||
13 N 67.4230 81.8700 36.8030 N.ar 1 LIG1 -0.1473
|
||||
14 C 66.8190 84.7600 39.1350 C.3 1 LIG1 0.0927
|
||||
15 C 66.2560 86.0870 38.5570 C.3 1 LIG1 0.0306
|
||||
16 N 64.4520 87.4670 36.2150 N.am 1 LIG1 -0.2979
|
||||
17 H 64.9740 86.7650 35.7110 H 1 LIG1 0.1498
|
||||
18 C 64.2290 87.1720 37.5220 C.2 1 LIG1 0.2224
|
||||
19 O 63.5690 87.9550 38.2480 O.2 1 LIG1 -0.2751
|
||||
20 C 64.0160 88.6470 35.5420 C.3 1 LIG1 0.1559
|
||||
21 C 65.9650 88.2170 32.5700 C.ar 1 LIG1 0.1629
|
||||
22 N 65.8040 88.0390 33.8860 N.ar 1 LIG1 -0.3232
|
||||
23 H 66.4190 87.5270 34.5040 H 1 LIG1 0.1686
|
||||
24 C 64.6770 88.6690 34.2330 C.ar 1 LIG1 0.1601
|
||||
25 N 64.1890 89.2810 33.1430 N.ar 1 LIG1 -0.1318
|
||||
26 N 64.9640 89.0070 32.1450 N.ar 1 LIG1 -0.1293
|
||||
27 F 69.9260 86.0280 29.3520 F 1 LIG1 -0.2042
|
||||
28 C 68.9880 86.5420 30.0990 C.ar 1 LIG1 0.1401
|
||||
29 C 67.6590 86.0900 29.9830 C.ar 1 LIG1 0.0297
|
||||
30 C 66.6500 86.6450 30.7950 C.ar 1 LIG1 0.0053
|
||||
31 C 66.9590 87.6470 31.7450 C.ar 1 LIG1 0.0359
|
||||
32 C 68.2970 88.0990 31.8400 C.ar 1 LIG1 0.0053
|
||||
33 C 69.3030 87.5560 31.0230 C.ar 1 LIG1 0.0297
|
||||
34 C 66.9850 79.4910 34.8440 C.3 1 LIG1 0.4541
|
||||
35 F 67.3150 78.4110 34.0640 F 1 LIG1 -0.1631
|
||||
36 F 66.2460 80.3690 34.0910 F 1 LIG1 -0.1631
|
||||
37 F 66.1920 79.0650 35.8800 F 1 LIG1 -0.1631
|
||||
@<TRIPOS>BOND
|
||||
1 1 2 1
|
||||
2 1 18 1
|
||||
3 1 15 1
|
||||
4 2 3 1
|
||||
5 3 4 1
|
||||
6 4 5 1
|
||||
7 4 14 1
|
||||
8 5 13 ar
|
||||
9 5 6 ar
|
||||
10 6 7 ar
|
||||
11 7 8 ar
|
||||
12 8 9 ar
|
||||
13 8 12 ar
|
||||
14 9 10 ar
|
||||
15 10 11 ar
|
||||
16 11 34 1
|
||||
17 11 12 ar
|
||||
18 12 13 ar
|
||||
19 14 15 1
|
||||
20 16 20 1
|
||||
21 16 17 1
|
||||
22 16 18 am
|
||||
23 18 19 2
|
||||
24 20 24 1
|
||||
25 21 31 1
|
||||
26 21 26 ar
|
||||
27 21 22 ar
|
||||
28 22 24 ar
|
||||
29 22 23 1
|
||||
30 24 25 ar
|
||||
31 25 26 ar
|
||||
32 27 28 1
|
||||
33 28 29 ar
|
||||
34 28 33 ar
|
||||
35 29 30 ar
|
||||
36 30 31 ar
|
||||
37 31 32 ar
|
||||
38 32 33 ar
|
||||
39 34 35 1
|
||||
40 34 36 1
|
||||
41 34 37 1
|
||||
7628
examples/ace2.pdbqt
Normal file
7628
examples/ace2.pdbqt
Normal file
File diff suppressed because it is too large
Load Diff
38817
package-lock.json
generated
38817
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
131
package.json
131
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "1.1.6",
|
||||
"version": "3.0.0-dev.8",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -11,10 +11,13 @@
|
||||
"url": "https://github.com/molstar/molstar/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint ./**/*.{ts,tsx}",
|
||||
"lint-fix": "eslint ./**/*.{ts,tsx} --fix",
|
||||
"lint": "eslint .",
|
||||
"lint-fix": "eslint . --fix",
|
||||
"test": "npm run lint && jest",
|
||||
"jest": "jest",
|
||||
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
|
||||
"clean": "node ./scripts/clean.js",
|
||||
"rebuild": "npm run clean && npm run build",
|
||||
"build-viewer": "npm run build-tsc && npm run build-extra && npm run build-webpack-viewer",
|
||||
"build-tsc": "concurrently \"tsc --incremental\" \"tsc --build tsconfig.commonjs.json --incremental\"",
|
||||
"build-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/",
|
||||
@@ -26,16 +29,16 @@
|
||||
"watch-tsc": "tsc --watch --incremental",
|
||||
"watch-servers": "tsc --build tsconfig.commonjs.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",
|
||||
"watch-webpack": "webpack -w --mode development --stats minimal",
|
||||
"watch-webpack-viewer": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.js",
|
||||
"watch-webpack-viewer-debug": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.debug.js",
|
||||
"serve": "http-server -p 1338 -g",
|
||||
"model-server": "node lib/commonjs/servers/model/server.js",
|
||||
"model-server-watch": "nodemon --watch lib lib/commonjs/servers/model/server.js",
|
||||
"volume-server-test": "node lib/commonjs/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
|
||||
"plugin-state": "node lib/commonjs/servers/plugin-state/index.js",
|
||||
"plugin-state": "node lib/commonjs/servers/plugin-state/index.js --working-folder ./build/state --port 1339",
|
||||
"preversion": "npm run test",
|
||||
"version": "npm run build",
|
||||
"version": "npm run rebuild && cpx .npmignore lib/",
|
||||
"postversion": "git push && git push --tags"
|
||||
},
|
||||
"files": [
|
||||
@@ -80,70 +83,80 @@
|
||||
"Alexander Rose <alexander.rose@weirdbyte.de>",
|
||||
"David Sehnal <david.sehnal@gmail.com>",
|
||||
"Sebastian Bittrich <sebastian.bittrich@rcsb.org>",
|
||||
"Áron Samuel Kovács <aron.kovacs@mail.muni.cz>",
|
||||
"Ludovic Autin <autin@scripps.edu>",
|
||||
"Michal Malý <michal.maly@ibt.cas.cz>",
|
||||
"Jiří Černý <jiri.cerny@ibt.cas.cz>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^1.17.3",
|
||||
"@graphql-codegen/cli": "^1.17.3",
|
||||
"@graphql-codegen/time": "^1.17.3",
|
||||
"@graphql-codegen/typescript": "^1.17.3",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.17.3",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.17.3",
|
||||
"@graphql-codegen/typescript-operations": "^1.17.3",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@typescript-eslint/eslint-plugin": "^3.7.0",
|
||||
"@typescript-eslint/parser": "^3.7.0",
|
||||
"@graphql-codegen/add": "^3.1.0",
|
||||
"@graphql-codegen/cli": "^2.3.0",
|
||||
"@graphql-codegen/time": "^3.1.0",
|
||||
"@graphql-codegen/typescript": "^2.4.1",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.0",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.3.1",
|
||||
"@graphql-codegen/typescript-operations": "^2.2.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/gl": "^4.1.0",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.5.0",
|
||||
"@typescript-eslint/parser": "^5.5.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^5.2.0",
|
||||
"cpx2": "^2.0.0",
|
||||
"css-loader": "^3.6.0",
|
||||
"eslint": "^7.5.0",
|
||||
"concurrently": "^6.4.0",
|
||||
"cpx2": "^4.0.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.5.1",
|
||||
"eslint": "^8.3.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.0.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"graphql": "^15.3.0",
|
||||
"http-server": "^0.12.3",
|
||||
"jest": "^26.1.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.14.1",
|
||||
"raw-loader": "^4.0.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"simple-git": "^2.14.0",
|
||||
"style-loader": "^1.2.1",
|
||||
"ts-jest": "^26.1.3",
|
||||
"typescript": "^3.9.7",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-version-file-plugin": "^0.4.0"
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"graphql": "^15.7.2",
|
||||
"http-server": "^14.0.0",
|
||||
"jest": "^27.3.1",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"path-browserify": "^1.0.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass": "^1.43.5",
|
||||
"sass-loader": "^12.3.0",
|
||||
"simple-git": "^2.47.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^27.0.7",
|
||||
"typescript": "^4.5.2",
|
||||
"webpack": "^5.64.4",
|
||||
"webpack-cli": "^4.9.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
"@types/benchmark": "^1.0.33",
|
||||
"@types/compression": "1.7.0",
|
||||
"@types/express": "^4.17.7",
|
||||
"@types/jest": "^25.2.3",
|
||||
"@types/node": "^14.0.24",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/react": "^16.9.43",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/swagger-ui-dist": "3.0.5",
|
||||
"argparse": "^1.0.10",
|
||||
"@types/argparse": "^2.0.10",
|
||||
"@types/benchmark": "^2.1.1",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^16.11.10",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"@types/react": "^17.0.37",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"argparse": "^2.0.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"immer": "^7.0.5",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^9.0.7",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"rxjs": "^6.6.0",
|
||||
"swagger-ui-dist": "^3.30.1",
|
||||
"tslib": "^2.0.0",
|
||||
"util.promisify": "^1.0.1",
|
||||
"xhr2": "^0.2.0"
|
||||
"node-fetch": "^2.6.2",
|
||||
"rxjs": "^7.4.0",
|
||||
"swagger-ui-dist": "^4.1.1",
|
||||
"tslib": "^2.3.1",
|
||||
"util.promisify": "^1.1.1",
|
||||
"xhr2": "^0.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"gl": "^4.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
41
scripts/clean.js
Normal file
41
scripts/clean.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Michal Malý <malym@ibt.cas.cz>
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function removeDir(dirPath) {
|
||||
for (const ent of fs.readdirSync(dirPath)) {
|
||||
const entryPath = path.join(dirPath, ent);
|
||||
remove(entryPath);
|
||||
}
|
||||
|
||||
fs.rmdirSync(dirPath);
|
||||
}
|
||||
|
||||
function remove(entryPath) {
|
||||
const st = fs.statSync(entryPath);
|
||||
if (st.isDirectory())
|
||||
removeDir(entryPath);
|
||||
else
|
||||
fs.unlinkSync(entryPath);
|
||||
}
|
||||
|
||||
const toClean = [
|
||||
path.resolve(__dirname, '../build'),
|
||||
path.resolve(__dirname, '../lib'),
|
||||
path.resolve(__dirname, '../tsconfig.tsbuildinfo'),
|
||||
];
|
||||
|
||||
toClean.forEach(ph => {
|
||||
if (fs.existsSync(ph)) {
|
||||
try {
|
||||
remove(ph);
|
||||
} catch (err) {
|
||||
console.warn(`Cleanup failed: ${err}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,59 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
const git = require('simple-git')
|
||||
const path = require('path')
|
||||
const fs = require("fs")
|
||||
const fse = require("fs-extra")
|
||||
const git = require('simple-git');
|
||||
const path = require('path');
|
||||
const fs = require("fs");
|
||||
const fse = require("fs-extra");
|
||||
|
||||
const remoteUrl = "https://github.com/molstar/molstar.github.io.git"
|
||||
const buildDir = path.resolve(__dirname, '../build/')
|
||||
const deployDir = path.resolve(buildDir, 'deploy/')
|
||||
const localPath = path.resolve(deployDir, 'molstar.github.io/')
|
||||
const remoteUrl = "https://github.com/molstar/molstar.github.io.git";
|
||||
const buildDir = path.resolve(__dirname, '../build/');
|
||||
const deployDir = path.resolve(buildDir, 'deploy/');
|
||||
const localPath = path.resolve(deployDir, 'molstar.github.io/');
|
||||
|
||||
const analyticsTag = /<!-- __MOLSTAR_ANALYTICS__ -->/g;
|
||||
const analyticsCode = `<!-- Cloudflare Web Analytics --><script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "c414cbae2d284ea995171a81e4a3e721"}'></script><!-- End Cloudflare Web Analytics -->`;
|
||||
|
||||
function log(command, stdout, stderr) {
|
||||
if (command) {
|
||||
console.log('\n###', command)
|
||||
stdout.pipe(process.stdout)
|
||||
stderr.pipe(process.stderr)
|
||||
console.log('\n###', command);
|
||||
stdout.pipe(process.stdout);
|
||||
stderr.pipe(process.stderr);
|
||||
}
|
||||
}
|
||||
|
||||
function addAnalytics(path) {
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = data.replace(analyticsTag, analyticsCode);
|
||||
fs.writeFileSync(path, result, 'utf8');
|
||||
}
|
||||
|
||||
function copyViewer() {
|
||||
console.log('\n###', 'copy viewer files')
|
||||
const viewerBuildPath = path.resolve(buildDir, '../build/viewer/')
|
||||
const viewerDeployPath = path.resolve(localPath, 'viewer/')
|
||||
fse.copySync(viewerBuildPath, viewerDeployPath, { overwrite: true })
|
||||
console.log('\n###', 'copy viewer files');
|
||||
const viewerBuildPath = path.resolve(buildDir, '../build/viewer/');
|
||||
const viewerDeployPath = path.resolve(localPath, 'viewer/');
|
||||
fse.copySync(viewerBuildPath, viewerDeployPath, { overwrite: true });
|
||||
addAnalytics(path.resolve(viewerDeployPath, 'index.html'));
|
||||
}
|
||||
|
||||
function copyDemos() {
|
||||
console.log('\n###', 'copy demos files');
|
||||
const lightingBuildPath = path.resolve(buildDir, '../build/examples/lighting/');
|
||||
const lightingDeployPath = path.resolve(localPath, 'demos/lighting/');
|
||||
fse.copySync(lightingBuildPath, lightingDeployPath, { overwrite: true });
|
||||
addAnalytics(path.resolve(lightingDeployPath, 'index.html'));
|
||||
|
||||
const orbitalsBuildPath = path.resolve(buildDir, '../build/examples/alpha-orbitals/');
|
||||
const orbitalsDeployPath = path.resolve(localPath, 'demos/alpha-orbitals/');
|
||||
fse.copySync(orbitalsBuildPath, orbitalsDeployPath, { overwrite: true });
|
||||
addAnalytics(path.resolve(orbitalsDeployPath, 'index.html'));
|
||||
}
|
||||
|
||||
function copyFiles() {
|
||||
copyViewer();
|
||||
copyDemos();
|
||||
}
|
||||
|
||||
if (!fs.existsSync(localPath)) {
|
||||
console.log('\n###', 'create localPath')
|
||||
fs.mkdirSync(localPath, { recursive: true })
|
||||
console.log('\n###', 'create localPath');
|
||||
fs.mkdirSync(localPath, { recursive: true });
|
||||
}
|
||||
|
||||
process.chdir(localPath);
|
||||
|
||||
if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
|
||||
console.log('\n###', 'clone repository')
|
||||
console.log('\n###', 'clone repository');
|
||||
git()
|
||||
.outputHandler(log)
|
||||
.clone(remoteUrl, localPath)
|
||||
.fetch(['--all'])
|
||||
.exec(copyViewer)
|
||||
.exec(copyFiles)
|
||||
.add(['-A'])
|
||||
.commit('updated viewer')
|
||||
.push()
|
||||
.commit('updated viewer & demos')
|
||||
.push();
|
||||
} else {
|
||||
console.log('\n###', 'update repository')
|
||||
console.log('\n###', 'update repository');
|
||||
git()
|
||||
.outputHandler(log)
|
||||
.fetch(['--all'])
|
||||
.reset(['--hard', 'origin/master'])
|
||||
.exec(copyViewer)
|
||||
.exec(copyFiles)
|
||||
.add(['-A'])
|
||||
.commit('updated viewer')
|
||||
.push()
|
||||
.commit('updated viewer & demos')
|
||||
.push();
|
||||
}
|
||||
@@ -19,19 +19,19 @@
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
var viewer = new DockingViewer('app', [0x33DD22, 0x1133EE], true);
|
||||
|
||||
function getParam(name, regex) {
|
||||
var r = new RegExp(name + '=' + '(' + regex + ')[&]?', 'i');
|
||||
return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || '');
|
||||
}
|
||||
var pdbqt = getParam('pdbqt', '[^&]+').trim();
|
||||
var mol2 = getParam('mol2', '[^&]+').trim();
|
||||
var pdbqt = getParam('pdbqt', '[^&]+').trim() || '../../examples/ace2.pdbqt';
|
||||
var mol2 = getParam('mol2', '[^&]+').trim() || '../../examples/ace2-hit.mol2';
|
||||
|
||||
viewer.loadStructuresFromUrlsAndMerge([
|
||||
{ url: pdbqt, format: 'pdbqt' },
|
||||
{ url: mol2, format: 'mol2' }
|
||||
]);
|
||||
DockingViewer.create('app', [0x33DD22, 0x1133EE], true).then(viewer => {
|
||||
viewer.loadStructuresFromUrlsAndMerge([
|
||||
{ url: pdbqt, format: 'pdbqt' },
|
||||
{ url: mol2, format: 'mol2' }
|
||||
]);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,30 +5,32 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import '../../mol-util/polyfill';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { PluginStateTransform, PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Task } from '../../mol-task';
|
||||
import { StateObject } from '../../mol-state';
|
||||
import { ViewportComponent, StructurePreset, ShowButtons } from './viewport';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginBehaviors } from '../../mol-plugin/behavior';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { StateObject } from '../../mol-state';
|
||||
import { Task } from '../../mol-task';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import './index.html';
|
||||
import { ShowButtons, StructurePreset, ViewportComponent } from './viewport';
|
||||
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setProductionMode, setDebugMode } from '../../mol-util/debug';
|
||||
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
|
||||
export { Viewer as DockingViewer };
|
||||
|
||||
const DefaultViewerOptions = {
|
||||
extensions: ObjectKeys({}),
|
||||
@@ -52,9 +54,10 @@ const DefaultViewerOptions = {
|
||||
};
|
||||
|
||||
class Viewer {
|
||||
plugin: PluginContext
|
||||
constructor(public plugin: PluginUIContext) {
|
||||
}
|
||||
|
||||
constructor(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
|
||||
static async create(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
|
||||
const o = { ...DefaultViewerOptions, ...{
|
||||
layoutIsExpanded: false,
|
||||
layoutShowControls: false,
|
||||
@@ -69,9 +72,10 @@ class Viewer {
|
||||
viewportShowSelectionMode: false,
|
||||
viewportShowAnimation: false,
|
||||
} };
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const spec: PluginSpec = {
|
||||
actions: [...DefaultPluginSpec.actions],
|
||||
const spec: PluginUISpec = {
|
||||
actions: defaultSpec.actions,
|
||||
behaviors: [
|
||||
PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci, { mark: false }),
|
||||
PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
|
||||
@@ -81,23 +85,23 @@ class Viewer {
|
||||
PluginSpec.Behavior(PluginBehaviors.CustomProps.Interactions),
|
||||
PluginSpec.Behavior(PluginBehaviors.CustomProps.SecondaryStructure),
|
||||
],
|
||||
animations: [...DefaultPluginSpec.animations || []],
|
||||
customParamEditors: DefaultPluginSpec.customParamEditors,
|
||||
animations: defaultSpec.animations,
|
||||
customParamEditors: defaultSpec.customParamEditors,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: o.layoutIsExpanded,
|
||||
showControls: o.layoutShowControls,
|
||||
controlsDisplay: o.layoutControlsDisplay,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
...defaultSpec.components,
|
||||
controls: {
|
||||
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls,
|
||||
...defaultSpec.components?.controls,
|
||||
top: o.layoutShowSequence ? undefined : 'none',
|
||||
bottom: o.layoutShowLog ? undefined : 'none',
|
||||
left: o.layoutShowLeftPanel ? undefined : 'none',
|
||||
}
|
||||
},
|
||||
components: {
|
||||
...DefaultPluginSpec.components,
|
||||
},
|
||||
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
|
||||
viewport: {
|
||||
view: ViewportComponent
|
||||
@@ -122,29 +126,31 @@ class Viewer {
|
||||
? document.getElementById(elementOrId)
|
||||
: elementOrId;
|
||||
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
|
||||
this.plugin = createPlugin(element, spec);
|
||||
const plugin = await createPluginUI(element, spec);
|
||||
|
||||
(this.plugin.customState as any) = {
|
||||
(plugin.customState as any) = {
|
||||
colorPalette: {
|
||||
name: 'colors',
|
||||
params: { list: { colors } }
|
||||
}
|
||||
};
|
||||
|
||||
this.plugin.behaviors.canvas3d.initialized.subscribe(v => {
|
||||
plugin.behaviors.canvas3d.initialized.subscribe(v => {
|
||||
if (v) {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
|
||||
PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
|
||||
renderer: {
|
||||
...this.plugin.canvas3d!.props.renderer,
|
||||
...plugin.canvas3d!.props.renderer,
|
||||
backgroundColor: ColorNames.white,
|
||||
},
|
||||
camera: {
|
||||
...this.plugin.canvas3d!.props.camera,
|
||||
...plugin.canvas3d!.props.camera,
|
||||
helper: { axes: { name: 'off', params: {} } }
|
||||
}
|
||||
} });
|
||||
}
|
||||
});
|
||||
|
||||
return new Viewer(plugin);
|
||||
}
|
||||
|
||||
async loadStructuresFromUrlsAndMerge(sources: { url: string, format: BuiltInTrajectoryFormat, isBinary?: boolean }[]) {
|
||||
@@ -208,4 +214,3 @@ const MergeStructures = PluginStateTransform.BuiltIn({
|
||||
});
|
||||
|
||||
(window as any).DockingViewer = Viewer;
|
||||
export { Viewer as DockingViewer };
|
||||
@@ -4,30 +4,29 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { PluginUIComponent } from '../../mol-plugin-ui/base';
|
||||
import { Viewport, ViewportControls } from '../../mol-plugin-ui/viewport';
|
||||
import { BackgroundTaskProgress } from '../../mol-plugin-ui/task';
|
||||
import { LociLabels } from '../../mol-plugin-ui/controls';
|
||||
import { Toasts } from '../../mol-plugin-ui/toast';
|
||||
import { Button } from '../../mol-plugin-ui/controls/common';
|
||||
import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
import { StructureSelectionQueries, StructureSelectionQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { InteractionsRepresentationProvider } from '../../mol-model-props/computed/representations/interactions';
|
||||
import { InteractionTypeColorThemeProvider } from '../../mol-model-props/computed/themes/interaction-type';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { presetStaticComponent, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StructureSelectionQueries, StructureSelectionQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { StructureRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { PluginUIComponent } from '../../mol-plugin-ui/base';
|
||||
import { LociLabels } from '../../mol-plugin-ui/controls';
|
||||
import { Button } from '../../mol-plugin-ui/controls/common';
|
||||
import { BackgroundTaskProgress } from '../../mol-plugin-ui/task';
|
||||
import { Toasts } from '../../mol-plugin-ui/toast';
|
||||
import { Viewport, ViewportControls } from '../../mol-plugin-ui/viewport';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { Material } from '../../mol-util/material';
|
||||
|
||||
function shinyStyle(plugin: PluginContext) {
|
||||
return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
|
||||
renderer: {
|
||||
...plugin.canvas3d!.props.renderer,
|
||||
style: { name: 'plastic', params: {} },
|
||||
},
|
||||
postprocessing: {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
@@ -41,18 +40,19 @@ function occlusionStyle(plugin: PluginContext) {
|
||||
return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
|
||||
renderer: {
|
||||
...plugin.canvas3d!.props.renderer,
|
||||
style: { name: 'flat', params: {} }
|
||||
},
|
||||
postprocessing: {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
occlusion: { name: 'on', params: {
|
||||
kernelSize: 8,
|
||||
bias: 0.8,
|
||||
radius: 64
|
||||
samples: 64,
|
||||
radius: 8,
|
||||
bias: 1.0,
|
||||
blurKernelSize: 13
|
||||
} },
|
||||
outline: { name: 'on', params: {
|
||||
scale: 1.0,
|
||||
threshold: 0.8
|
||||
threshold: 0.33,
|
||||
color: Color(0x0000),
|
||||
} }
|
||||
}
|
||||
} });
|
||||
@@ -77,7 +77,7 @@ const PresetParams = {
|
||||
...StructureRepresentationPresetProvider.CommonParams,
|
||||
};
|
||||
|
||||
|
||||
const CustomMaterial = Material({ roughness: 0.2, metalness: 0 });
|
||||
|
||||
export const StructurePreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure',
|
||||
@@ -94,8 +94,8 @@ export const StructurePreset = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, material: CustomMaterial }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -121,8 +121,8 @@ export const IllustrativePreset = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'spacefill', typeParams: { ...typeParams }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'spacefill', typeParams: { ...typeParams }, color: 'illustrative', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'spacefill', typeParams: { ...typeParams, ignoreLight: true }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'spacefill', typeParams: { ...typeParams, ignoreLight: true }, color: 'illustrative', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -149,8 +149,8 @@ const SurfacePreset = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'molecular-surface', typeParams: { ...typeParams, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'molecular-surface', typeParams: { ...typeParams, material: CustomMaterial, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }),
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -177,8 +177,8 @@ const PocketPreset = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
surroundings: builder.buildRepresentation(update, components.surroundings, { type: 'molecular-surface', typeParams: { ...typeParams, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
surroundings: builder.buildRepresentation(update, components.surroundings, { type: 'molecular-surface', typeParams: { ...typeParams, material: CustomMaterial, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }),
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -206,10 +206,10 @@ const InteractionsPreset = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
|
||||
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
|
||||
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
|
||||
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
|
||||
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, material: CustomMaterial, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -230,7 +230,7 @@ export class ViewportComponent extends PluginUIComponent {
|
||||
|
||||
set = async (preset: StructureRepresentationPresetProvider) => {
|
||||
await this._set(this.plugin.managers.structure.hierarchy.selection.structures, preset);
|
||||
}
|
||||
};
|
||||
|
||||
structurePreset = () => this.set(StructurePreset);
|
||||
illustrativePreset = () => this.set(IllustrativePreset);
|
||||
@@ -238,7 +238,7 @@ export class ViewportComponent extends PluginUIComponent {
|
||||
pocketPreset = () => this.set(PocketPreset);
|
||||
interactionsPreset = () => this.set(InteractionsPreset);
|
||||
|
||||
get showButtons () {
|
||||
get showButtons() {
|
||||
return this.plugin.config.get(ShowButtons);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
var viewer = new molstar.Viewer('app', {
|
||||
layoutIsExpanded: false,
|
||||
molstar.Viewer.create('app', {
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: false,
|
||||
layoutShowRemoteState: false,
|
||||
layoutShowSequence: true,
|
||||
@@ -34,9 +34,11 @@
|
||||
|
||||
pdbProvider: 'rcsb',
|
||||
emdbProvider: 'rcsb',
|
||||
}).then(viewer => {
|
||||
viewer.loadPdb('7bv2');
|
||||
viewer.loadEmdb('EMD-30210', { detail: 6 });
|
||||
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
|
||||
});
|
||||
viewer.loadPdb('7bv2');
|
||||
viewer.loadEmdb('EMD-30210', { detail: 6 });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -46,38 +46,62 @@
|
||||
}
|
||||
|
||||
var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
|
||||
if (debugMode) molstar.setDebugMode(debugMode);
|
||||
if (debugMode) molstar.setDebugMode(debugMode, debugMode);
|
||||
|
||||
var hideControls = getParam('hide-controls', '[^&]+').trim() === '1';
|
||||
var collapseLeftPanel = getParam('collapse-left-panel', '[^&]+').trim() === '1';
|
||||
var pdbProvider = getParam('pdb-provider', '[^&]+').trim().toLowerCase();
|
||||
var emdbProvider = getParam('emdb-provider', '[^&]+').trim().toLowerCase();
|
||||
var viewer = new molstar.Viewer('app', {
|
||||
var mapProvider = getParam('map-provider', '[^&]+').trim().toLowerCase();
|
||||
var pixelScale = getParam('pixel-scale', '[^&]+').trim();
|
||||
var pickScale = getParam('pick-scale', '[^&]+').trim();
|
||||
var pickPadding = getParam('pick-padding', '[^&]+').trim();
|
||||
var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
|
||||
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1';
|
||||
|
||||
molstar.Viewer.create('app', {
|
||||
layoutShowControls: !hideControls,
|
||||
viewportShowExpand: false,
|
||||
collapseLeftPanel: collapseLeftPanel,
|
||||
pdbProvider: pdbProvider || 'pdbe',
|
||||
emdbProvider: emdbProvider || 'pdbe',
|
||||
volumeStreamingServer: (mapProvider || 'pdbe') === 'rcsb'
|
||||
? 'https://maps.rcsb.org'
|
||||
: 'https://www.ebi.ac.uk/pdbe/densities',
|
||||
pixelScale: parseFloat(pixelScale) || 1,
|
||||
pickScale: parseFloat(pickScale) || 0.25,
|
||||
pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
|
||||
enableWboit: !disableWboit,
|
||||
preferWebgl1: preferWebgl1,
|
||||
}).then(viewer => {
|
||||
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
|
||||
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);
|
||||
|
||||
var snapshotUrl = getParam('snapshot-url', '[^&]+').trim();
|
||||
var snapshotUrlType = getParam('snapshot-url-type', '[^&]+').toLowerCase().trim() || 'molj';
|
||||
if (snapshotUrl && snapshotUrlType) viewer.loadSnapshotFromUrl(snapshotUrl, snapshotUrlType);
|
||||
|
||||
var structureUrl = getParam('structure-url', '[^&]+').trim();
|
||||
var structureUrlFormat = getParam('structure-url-format', '[a-z]+').toLowerCase().trim();
|
||||
var structureUrlIsBinary = getParam('structure-url-is-binary', '[^&]+').trim() === '1';
|
||||
if (structureUrl) viewer.loadStructureFromUrl(structureUrl, structureUrlFormat, structureUrlIsBinary);
|
||||
|
||||
var pdb = getParam('pdb', '[^&]+').trim();
|
||||
if (pdb) viewer.loadPdb(pdb);
|
||||
|
||||
var pdbDev = getParam('pdb-dev', '[^&]+').trim();
|
||||
if (pdbDev) viewer.loadPdbDev(pdbDev);
|
||||
|
||||
var emdb = getParam('emdb', '[^&]+').trim();
|
||||
if (emdb) viewer.loadEmdb(emdb);
|
||||
|
||||
var afdb = getParam('afdb', '[^&]+').trim();
|
||||
if (afdb) viewer.loadAlphaFoldDb(afdb);
|
||||
|
||||
var modelArchive = getParam('model-archive', '[^&]+').trim();
|
||||
if (modelArchive) viewer.loadModelArchive(modelArchive);
|
||||
});
|
||||
|
||||
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
|
||||
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);
|
||||
|
||||
var snapshotUrl = getParam('snapshot-url', '[^&]+').trim();
|
||||
var snapshotUrlType = getParam('snapshot-url-type', '[^&]+').toLowerCase().trim() || 'molj';
|
||||
if (snapshotUrl && snapshotUrlType) viewer.loadSnapshotFromUrl(snapshotUrl, snapshotUrlType);
|
||||
|
||||
var structureUrl = getParam('structure-url', '[^&]+').trim();
|
||||
var structureUrlFormat = getParam('structure-url-format', '[a-z]+').toLowerCase().trim();
|
||||
var structureUrlIsBinary = getParam('structure-url-is-binary', '[^&]+').trim() === '1';
|
||||
if (structureUrl) viewer.loadStructureFromUrl(structureUrl, structureUrlFormat, structureUrlIsBinary);
|
||||
|
||||
var pdb = getParam('pdb', '[^&]+').trim();
|
||||
if (pdb) viewer.loadPdb(pdb);
|
||||
|
||||
var pdbDev = getParam('pdb-dev', '[^&]+').trim();
|
||||
if (pdbDev) viewer.loadPdbDev(pdbDev);
|
||||
|
||||
var emdb = getParam('emdb', '[^&]+').trim();
|
||||
if (emdb) viewer.loadEmdb(emdb);
|
||||
</script>
|
||||
<!-- __MOLSTAR_ANALYTICS__ -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,36 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
|
||||
import { CellPack } from '../../extensions/cellpack';
|
||||
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
|
||||
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
|
||||
import { GeometryExport } from '../../extensions/geo-export';
|
||||
import { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
|
||||
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
|
||||
import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset';
|
||||
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
import { BuildInStructureFormat } from '../../mol-plugin-state/formats/structure';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
|
||||
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { StateObjectRef, StateObjectSelector } from '../../mol-state';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import '../../mol-util/polyfill';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import './embedded.html';
|
||||
import './favicon.ico';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { DownloadStructure, PdbDownloadProvider } 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';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
|
||||
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
|
||||
import './index.html';
|
||||
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setProductionMode, setDebugMode } from '../../mol-util/debug';
|
||||
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
|
||||
|
||||
const CustomFormats = [
|
||||
['g3d', G3dProvider] as const
|
||||
];
|
||||
|
||||
const Extensions = {
|
||||
'cellpack': PluginSpec.Behavior(CellPack),
|
||||
@@ -38,10 +60,15 @@ const Extensions = {
|
||||
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
|
||||
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
|
||||
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation)
|
||||
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
|
||||
'g3d': PluginSpec.Behavior(G3DFormat),
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export),
|
||||
'geo-export': PluginSpec.Behavior(GeometryExport),
|
||||
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
|
||||
};
|
||||
|
||||
const DefaultViewerOptions = {
|
||||
customFormats: CustomFormats as [string, DataFormatProvider][],
|
||||
extensions: ObjectKeys(Extensions),
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: true,
|
||||
@@ -50,6 +77,14 @@ const DefaultViewerOptions = {
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: true,
|
||||
layoutShowLeftPanel: true,
|
||||
collapseLeftPanel: false,
|
||||
collapseRightPanel: false,
|
||||
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
|
||||
pixelScale: PluginConfig.General.PixelScale.defaultValue,
|
||||
pickScale: PluginConfig.General.PickScale.defaultValue,
|
||||
pickPadding: PluginConfig.General.PickPadding.defaultValue,
|
||||
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
|
||||
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
|
||||
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
@@ -58,43 +93,59 @@ const DefaultViewerOptions = {
|
||||
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
|
||||
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
|
||||
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
|
||||
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
|
||||
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
|
||||
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
|
||||
};
|
||||
type ViewerOptions = typeof DefaultViewerOptions;
|
||||
|
||||
export class Viewer {
|
||||
plugin: PluginContext
|
||||
constructor(public plugin: PluginUIContext) {
|
||||
}
|
||||
|
||||
constructor(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
|
||||
static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
|
||||
const o = { ...DefaultViewerOptions, ...options };
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const spec: PluginSpec = {
|
||||
actions: [...DefaultPluginSpec.actions],
|
||||
const spec: PluginUISpec = {
|
||||
actions: defaultSpec.actions,
|
||||
behaviors: [
|
||||
...DefaultPluginSpec.behaviors,
|
||||
...defaultSpec.behaviors,
|
||||
...o.extensions.map(e => Extensions[e]),
|
||||
],
|
||||
animations: [...DefaultPluginSpec.animations || []],
|
||||
customParamEditors: DefaultPluginSpec.customParamEditors,
|
||||
animations: [...defaultSpec.animations || []],
|
||||
customParamEditors: defaultSpec.customParamEditors,
|
||||
customFormats: o?.customFormats,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: o.layoutIsExpanded,
|
||||
showControls: o.layoutShowControls,
|
||||
controlsDisplay: o.layoutControlsDisplay,
|
||||
regionState: {
|
||||
bottom: 'full',
|
||||
left: o.collapseLeftPanel ? 'collapsed' : 'full',
|
||||
right: o.collapseRightPanel ? 'hidden' : 'full',
|
||||
top: 'full',
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
...defaultSpec.components,
|
||||
controls: {
|
||||
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls,
|
||||
...defaultSpec.components?.controls,
|
||||
top: o.layoutShowSequence ? undefined : 'none',
|
||||
bottom: o.layoutShowLog ? undefined : 'none',
|
||||
left: o.layoutShowLeftPanel ? undefined : 'none',
|
||||
}
|
||||
},
|
||||
components: {
|
||||
...DefaultPluginSpec.components,
|
||||
remoteState: o.layoutShowRemoteState ? 'default' : 'none'
|
||||
},
|
||||
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
|
||||
},
|
||||
config: [
|
||||
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
|
||||
[PluginConfig.General.PixelScale, o.pixelScale],
|
||||
[PluginConfig.General.PickScale, o.pickScale],
|
||||
[PluginConfig.General.PickPadding, o.pickPadding],
|
||||
[PluginConfig.General.EnableWboit, o.enableWboit],
|
||||
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
|
||||
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
|
||||
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
|
||||
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
|
||||
@@ -103,8 +154,10 @@ export class Viewer {
|
||||
[PluginConfig.State.DefaultServer, o.pluginStateServer],
|
||||
[PluginConfig.State.CurrentServer, o.pluginStateServer],
|
||||
[PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
|
||||
[PluginConfig.VolumeStreaming.Enabled, !o.volumeStreamingDisabled],
|
||||
[PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
|
||||
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider]
|
||||
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
|
||||
[PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
|
||||
]
|
||||
};
|
||||
|
||||
@@ -112,7 +165,14 @@ export class Viewer {
|
||||
? document.getElementById(elementOrId)
|
||||
: elementOrId;
|
||||
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
|
||||
this.plugin = createPlugin(element, spec);
|
||||
const plugin = await createPluginUI(element, spec, {
|
||||
onBeforeUIRender: plugin => {
|
||||
// the preset needs to be added before the UI renders otherwise
|
||||
// "Download Structure" wont be able to pick it up
|
||||
plugin.builders.structure.representation.registerPreset(ViewerAutoPreset);
|
||||
}
|
||||
});
|
||||
return new Viewer(plugin);
|
||||
}
|
||||
|
||||
setRemoteSnapshot(id: string) {
|
||||
@@ -124,7 +184,7 @@ export class Viewer {
|
||||
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
|
||||
}
|
||||
|
||||
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false) {
|
||||
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
@@ -133,19 +193,28 @@ export class Viewer {
|
||||
url: Asset.Url(url),
|
||||
format: format as any,
|
||||
isBinary,
|
||||
options: params.source.params.options,
|
||||
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async loadAllModelsOrAssemblyFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
|
||||
|
||||
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'all-models', { useDefaultIfSingleModel: true, representationPresetParams: options?.representationParams });
|
||||
}
|
||||
|
||||
async loadStructureFromData(data: string | number[], format: BuiltInTrajectoryFormat, options?: { dataLabel?: string }) {
|
||||
const _data = await this.plugin.builders.data.rawData({ data, label: options?.dataLabel });
|
||||
const trajectory = await this.plugin.builders.structure.parseTrajectory(_data, format);
|
||||
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
|
||||
}
|
||||
|
||||
loadPdb(pdb: string) {
|
||||
loadPdb(pdb: string, options?: LoadStructureOptions) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!;
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
@@ -159,7 +228,7 @@ export class Viewer {
|
||||
params: PdbDownloadProvider[provider].defaultValue as any
|
||||
}
|
||||
},
|
||||
options: params.source.params.options,
|
||||
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -196,4 +265,220 @@ export class Viewer {
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
loadAlphaFoldDb(afdb: string) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'alphafolddb' as const,
|
||||
params: {
|
||||
id: afdb,
|
||||
options: {
|
||||
...params.source.params.options,
|
||||
representation: 'preset-structure-representation-ma-quality-assessment-plddt'
|
||||
},
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
loadModelArchive(id: string) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'modelarchive' as const,
|
||||
params: {
|
||||
id,
|
||||
options: params.source.params.options,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @example Load X-ray density from volume server
|
||||
viewer.loadVolumeFromUrl({
|
||||
url: 'https://www.ebi.ac.uk/pdbe/densities/x-ray/1tqn/cell?detail=3',
|
||||
format: 'dscif',
|
||||
isBinary: true
|
||||
}, [{
|
||||
type: 'relative',
|
||||
value: 1.5,
|
||||
color: 0x3362B2
|
||||
}, {
|
||||
type: 'relative',
|
||||
value: 3,
|
||||
color: 0x33BB33,
|
||||
volumeIndex: 1
|
||||
}, {
|
||||
type: 'relative',
|
||||
value: -3,
|
||||
color: 0xBB3333,
|
||||
volumeIndex: 1
|
||||
}], {
|
||||
entryId: ['2FO-FC', 'FO-FC'],
|
||||
isLazy: true
|
||||
});
|
||||
* *********************
|
||||
* @example Load EM density from volume server
|
||||
viewer.loadVolumeFromUrl({
|
||||
url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
|
||||
format: 'dscif',
|
||||
isBinary: true
|
||||
}, [{
|
||||
type: 'relative',
|
||||
value: 1,
|
||||
color: 0x3377aa
|
||||
}], {
|
||||
entryId: 'EMD-30210',
|
||||
isLazy: true
|
||||
});
|
||||
*/
|
||||
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string | string[], isLazy?: boolean }) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
if (!plugin.dataFormats.get(format)) {
|
||||
throw new Error(`Unknown density format: ${format}`);
|
||||
}
|
||||
|
||||
if (options?.isLazy) {
|
||||
const update = this.plugin.build();
|
||||
update.toRoot().apply(StateTransforms.Data.LazyVolume, {
|
||||
url,
|
||||
format,
|
||||
entryId: options?.entryId,
|
||||
isBinary,
|
||||
isovalues: isovalues.map(v => ({ alpha: 1, volumeIndex: 0, ...v }))
|
||||
});
|
||||
return update.commit();
|
||||
}
|
||||
|
||||
return plugin.dataTransaction(async () => {
|
||||
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
|
||||
|
||||
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId });
|
||||
const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
|
||||
if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
|
||||
|
||||
const repr = plugin.build();
|
||||
for (const iso of isovalues) {
|
||||
repr
|
||||
.to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume)
|
||||
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
|
||||
type: 'isosurface',
|
||||
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
|
||||
color: 'uniform',
|
||||
colorParams: { value: iso.color }
|
||||
}));
|
||||
}
|
||||
|
||||
await repr.commit();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* viewer.loadTrajectory({
|
||||
* model: { kind: 'model-url', url: 'villin.gro', format: 'gro' },
|
||||
* coordinates: { kind: 'coordinates-url', url: 'villin.xtc', format: 'xtc', isBinary: true },
|
||||
* preset: 'all-models' // or 'default'
|
||||
* });
|
||||
*/
|
||||
async loadTrajectory(params: LoadTrajectoryParams) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
let model: StateObjectSelector, coords: StateObjectSelector;
|
||||
|
||||
if (params.model.kind === 'model-data' || params.model.kind === 'model-url') {
|
||||
const data = params.model.kind === 'model-data'
|
||||
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
|
||||
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
|
||||
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(data, params.model.format ?? 'mmcif');
|
||||
model = await plugin.builders.structure.createModel(trajectory);
|
||||
} else {
|
||||
const data = params.model.kind === 'topology-data'
|
||||
? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel })
|
||||
: await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel });
|
||||
|
||||
const provider = plugin.dataFormats.get(params.model.format);
|
||||
model = await provider!.parse(plugin, data);
|
||||
}
|
||||
|
||||
{
|
||||
const data = params.coordinates.kind === 'coordinates-data'
|
||||
? await plugin.builders.data.rawData({ data: params.coordinates.data, label: params.coordinatesLabel })
|
||||
: await plugin.builders.data.download({ url: params.coordinates.url, isBinary: params.coordinates.isBinary, label: params.coordinatesLabel });
|
||||
|
||||
const provider = plugin.dataFormats.get(params.coordinates.format);
|
||||
coords = await provider!.parse(plugin, data);
|
||||
}
|
||||
|
||||
const trajectory = await plugin.build().toRoot()
|
||||
.apply(TrajectoryFromModelAndCoordinates, {
|
||||
modelRef: model.ref,
|
||||
coordinatesRef: coords.ref
|
||||
}, { dependsOn: [model.ref, coords.ref] })
|
||||
.commit();
|
||||
|
||||
const preset = await plugin.builders.structure.hierarchy.applyPreset(trajectory, params.preset ?? 'default');
|
||||
|
||||
return { model, coords, preset };
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
this.plugin.layout.events.updated.next(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
export interface LoadStructureOptions {
|
||||
representationParams?: StructureRepresentationPresetProvider.CommonParams
|
||||
}
|
||||
|
||||
export interface VolumeIsovalueInfo {
|
||||
type: 'absolute' | 'relative',
|
||||
value: number,
|
||||
color: Color,
|
||||
alpha?: number,
|
||||
volumeIndex?: number
|
||||
}
|
||||
|
||||
export interface LoadTrajectoryParams {
|
||||
model: { kind: 'model-url', url: string, format?: BuiltInTrajectoryFormat /* mmcif */, isBinary?: boolean }
|
||||
| { kind: 'model-data', data: string | number[] | ArrayBuffer | Uint8Array, format?: BuiltInTrajectoryFormat /* mmcif */ }
|
||||
| { kind: 'topology-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
|
||||
| { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
|
||||
modelLabel?: string,
|
||||
coordinates: { kind: 'coordinates-url', url: string, format: BuildInStructureFormat, isBinary?: boolean }
|
||||
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat },
|
||||
coordinatesLabel?: string,
|
||||
preset?: keyof PresetTrajectoryHierarchy
|
||||
}
|
||||
|
||||
export const ViewerAutoPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-viewer-auto',
|
||||
display: {
|
||||
name: 'Automatic (w/ Annotation)', group: 'Annotation',
|
||||
description: 'Show standard automatic representation but colored by quality assessment (if available in the model).'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return (
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')) ||
|
||||
!!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))
|
||||
);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'))) {
|
||||
return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin);
|
||||
} else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) {
|
||||
return await QualityAssessmentQmeanPreset.apply(ref, params, plugin);
|
||||
} else {
|
||||
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
73
src/cli/chem-comp-dict/create-ions.ts
Normal file
73
src/cli/chem-comp-dict/create-ions.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Josh McMenemy <josh.mcmenemy@gmail.com>
|
||||
*/
|
||||
|
||||
import * as argparse from 'argparse';
|
||||
import * as path from 'path';
|
||||
import util from 'util';
|
||||
import fs from 'fs';
|
||||
require('util.promisify').shim();
|
||||
const writeFile = util.promisify(fs.writeFile);
|
||||
|
||||
import { DatabaseCollection } from '../../mol-data/db';
|
||||
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
|
||||
import { ensureDataAvailable, readCCD } from './util';
|
||||
|
||||
function extractIonNames(ccd: DatabaseCollection<CCD_Schema>) {
|
||||
const ionNames: string[] = [];
|
||||
for (const k in ccd) {
|
||||
const { chem_comp } = ccd[k];
|
||||
if (chem_comp.name.value(0).toUpperCase().includes(' ION')) {
|
||||
ionNames.push(chem_comp.id.value(0));
|
||||
}
|
||||
}
|
||||
// these are extra ions that don't have ION in their name
|
||||
ionNames.push('NCO', 'OHX');
|
||||
return ionNames;
|
||||
}
|
||||
|
||||
function writeIonNamesFile(filePath: string, ionNames: string[]) {
|
||||
const output = `/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated ion names params file. Names extracted from CCD components.
|
||||
*
|
||||
* @author molstar/chem-comp-dict/create-table cli
|
||||
*/
|
||||
|
||||
export const IonNames = new Set(${JSON.stringify(ionNames).replace(/"/g, "'").replace(/,/g, ', ')});
|
||||
`;
|
||||
writeFile(filePath, output);
|
||||
}
|
||||
|
||||
async function run(out: string, forceDownload = false) {
|
||||
await ensureDataAvailable(forceDownload);
|
||||
const ccd = await readCCD();
|
||||
const ionNames = extractIonNames(ccd);
|
||||
if (!fs.existsSync(path.dirname(out))) {
|
||||
fs.mkdirSync(path.dirname(out));
|
||||
}
|
||||
writeIonNamesFile(out, ionNames);
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
add_help: true,
|
||||
description: 'Extract and save IonNames from CCD.'
|
||||
});
|
||||
parser.add_argument('out', {
|
||||
help: 'Generated file output path.'
|
||||
});
|
||||
parser.add_argument('--forceDownload', '-f', {
|
||||
action: 'store_true',
|
||||
help: 'Force download of CCD and PVCD.'
|
||||
});
|
||||
interface Args {
|
||||
out: string,
|
||||
forceDownload?: boolean,
|
||||
}
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
run(args.out, args.forceDownload);
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -9,70 +9,16 @@ 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 { mmCIF_chemCompBond_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
import { ccd_chemCompAtom_schema } from '../../mol-io/reader/cif/schema/ccd-extras';
|
||||
|
||||
export async function ensureAvailable(path: string, url: string) {
|
||||
if (FORCE_DOWNLOAD || !fs.existsSync(path)) {
|
||||
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()));
|
||||
} else {
|
||||
await writeFile(path, await data.text());
|
||||
}
|
||||
console.log(`done downloading ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureDataAvailable() {
|
||||
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);
|
||||
}
|
||||
|
||||
export async function readCCD() {
|
||||
return readFileAsCollection(CCD_PATH, CCD_Schema);
|
||||
}
|
||||
|
||||
export async function readPVCD() {
|
||||
return readFileAsCollection(PVCD_PATH, CCD_Schema);
|
||||
}
|
||||
|
||||
async function parseCif(data: string | Uint8Array) {
|
||||
const comp = CIF.parse(data);
|
||||
console.time('parse cif');
|
||||
const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
|
||||
console.timeEnd('parse cif');
|
||||
if (parsed.isError) throw 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);
|
||||
return encoder.getData();
|
||||
}
|
||||
import { ensureDataAvailable, getEncodedCif, readCCD, readPVCD } from './util';
|
||||
|
||||
type CCB = Table<CCD_Schema['chem_comp_bond']>
|
||||
type CCA = Table<CCD_Schema['chem_comp_atom']>
|
||||
@@ -81,6 +27,10 @@ function ccbKey(compId: string, atomId1: string, atomId2: string) {
|
||||
return atomId1 < atomId2 ? `${compId}:${atomId1}-${atomId2}` : `${compId}:${atomId2}-${atomId1}`;
|
||||
}
|
||||
|
||||
function ccaKey(compId: string, atomId: string) {
|
||||
return `${compId}:${atomId}`;
|
||||
}
|
||||
|
||||
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)));
|
||||
@@ -90,7 +40,7 @@ function addChemCompBondToSet(set: Set<string>, ccb: CCB) {
|
||||
|
||||
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(ccaKey(cca.comp_id.value(i), cca.atom_id.value(i)));
|
||||
}
|
||||
return set;
|
||||
}
|
||||
@@ -136,11 +86,32 @@ function checkAddingBondsFromPVCD(pvcd: DatabaseCollection<CCD_Schema>) {
|
||||
}
|
||||
}
|
||||
|
||||
async function createBonds(atomsRequested: boolean) {
|
||||
await ensureDataAvailable();
|
||||
const ccd = await readCCD();
|
||||
const pvcd = await readPVCD();
|
||||
function checkAddingAtomsFromPVCD(pvcd: DatabaseCollection<CCD_Schema>) {
|
||||
const ccaSetByParent = DefaultMap<string, Set<string>>(() => new Set());
|
||||
|
||||
for (const k in pvcd) {
|
||||
const { chem_comp, chem_comp_atom } = pvcd[k];
|
||||
if (chem_comp_atom._rowCount) {
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0);
|
||||
if (parentIds.length === 0) {
|
||||
const set = ccaSetByParent.getDefault(chem_comp.id.value(0));
|
||||
addChemCompAtomToSet(set, chem_comp_atom);
|
||||
} else {
|
||||
for (let i = 0, il = parentIds.length; i < il; ++i) {
|
||||
const parentId = parentIds[i];
|
||||
const set = ccaSetByParent.getDefault(parentId);
|
||||
addChemCompAtomToSet(set, chem_comp_atom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function createBonds(
|
||||
ccd: DatabaseCollection<CCD_Schema>,
|
||||
pvcd: DatabaseCollection<CCD_Schema>,
|
||||
atomsRequested: boolean
|
||||
) {
|
||||
const ccbSet = new Set<string>();
|
||||
|
||||
const comp_id: string[] = [];
|
||||
@@ -200,16 +171,18 @@ async function createBonds(atomsRequested: boolean) {
|
||||
pdbx_aromatic_flag, pdbx_stereo_config, molstar_protonation_variant
|
||||
});
|
||||
|
||||
const bondDatabase = Database.ofTables(
|
||||
const bondDatabase = Database.ofTables(
|
||||
CCB_TABLE_NAME,
|
||||
{ chem_comp_bond: mmCIF_chemCompBond_schema },
|
||||
{ chem_comp_bond: bondTable }
|
||||
);
|
||||
|
||||
return { bonds: bondDatabase, atoms: atomsRequested ? createAtoms(ccd) : void 0 };
|
||||
return { bonds: bondDatabase, atoms: atomsRequested ? createAtoms(ccd, pvcd) : void 0 };
|
||||
}
|
||||
|
||||
function createAtoms(ccd: DatabaseCollection<CCD_Schema>) {
|
||||
function createAtoms(ccd: DatabaseCollection<CCD_Schema>, pvcd: DatabaseCollection<CCD_Schema>) {
|
||||
const ccaSet = new Set<string>();
|
||||
|
||||
const comp_id: string[] = [];
|
||||
const atom_id: string[] = [];
|
||||
const charge: number[] = [];
|
||||
@@ -217,10 +190,33 @@ function createAtoms(ccd: DatabaseCollection<CCD_Schema>) {
|
||||
|
||||
function addAtoms(compId: string, cca: CCA) {
|
||||
for (let i = 0, il = cca._rowCount; i < il; ++i) {
|
||||
atom_id.push(cca.atom_id.value(i));
|
||||
comp_id.push(compId);
|
||||
charge.push(cca.charge.value(i));
|
||||
pdbx_stereo_config.push(cca.pdbx_stereo_config.value(i));
|
||||
const atomId = cca.atom_id.value(i);
|
||||
const k = ccaKey(compId, atomId);
|
||||
if (!ccaSet.has(k)) {
|
||||
atom_id.push(atomId);
|
||||
comp_id.push(compId);
|
||||
charge.push(cca.charge.value(i));
|
||||
pdbx_stereo_config.push(cca.pdbx_stereo_config.value(i));
|
||||
ccaSet.add(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check adding atoms from PVCD
|
||||
checkAddingAtomsFromPVCD(pvcd);
|
||||
|
||||
// add atoms from PVCD
|
||||
for (const k in pvcd) {
|
||||
const { chem_comp, chem_comp_atom } = pvcd[k];
|
||||
if (chem_comp_atom._rowCount) {
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0);
|
||||
if (parentIds.length === 0) {
|
||||
addAtoms(chem_comp.id.value(0), chem_comp_atom);
|
||||
} else {
|
||||
for (let i = 0, il = parentIds.length; i < il; ++i) {
|
||||
addAtoms(parentIds[i], chem_comp_atom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,8 +239,12 @@ function createAtoms(ccd: DatabaseCollection<CCD_Schema>) {
|
||||
);
|
||||
}
|
||||
|
||||
async function run(out: string, binary = false, ccaOut?: string) {
|
||||
const { bonds, atoms } = await createBonds(!!ccaOut);
|
||||
async function run(out: string, binary = false, forceDownload = false, ccaOut?: string) {
|
||||
await ensureDataAvailable(forceDownload);
|
||||
const ccd = await readCCD();
|
||||
const pvcd = await readPVCD();
|
||||
|
||||
const { bonds, atoms } = await createBonds(ccd, pvcd, !!ccaOut);
|
||||
|
||||
const ccbCif = getEncodedCif(CCB_TABLE_NAME, bonds, binary);
|
||||
if (!fs.existsSync(path.dirname(out))) {
|
||||
@@ -264,39 +264,31 @@ async function run(out: string, binary = false, ccaOut?: string) {
|
||||
const CCB_TABLE_NAME = 'CHEM_COMP_BONDS';
|
||||
const CCA_TABLE_NAME = 'CHEM_COMP_ATOMS';
|
||||
|
||||
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,
|
||||
add_help: true,
|
||||
description: 'Create a cif file with one big table of all chem_comp_bond entries from the CCD and PVCD.'
|
||||
});
|
||||
parser.addArgument('out', {
|
||||
parser.add_argument('out', {
|
||||
help: 'Generated file output path.'
|
||||
});
|
||||
parser.addArgument([ '--forceDownload', '-f' ], {
|
||||
action: 'storeTrue',
|
||||
parser.add_argument('--forceDownload', '-f', {
|
||||
action: 'store_true',
|
||||
help: 'Force download of CCD and PVCD.'
|
||||
});
|
||||
parser.addArgument([ '--binary', '-b' ], {
|
||||
action: 'storeTrue',
|
||||
parser.add_argument('--binary', '-b', {
|
||||
action: 'store_true',
|
||||
help: 'Output as BinaryCIF.'
|
||||
});
|
||||
parser.addArgument(['--ccaOut', '-a'], {
|
||||
parser.add_argument('--ccaOut', '-a', {
|
||||
help: 'Optional generated file output path for chem_comp_atom data.',
|
||||
required: false
|
||||
});
|
||||
interface Args {
|
||||
out: string
|
||||
forceDownload?: boolean
|
||||
out: string,
|
||||
forceDownload?: boolean,
|
||||
binary?: boolean,
|
||||
ccaOut?: string
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
const FORCE_DOWNLOAD = args.forceDownload;
|
||||
|
||||
run(args.out, args.binary, args.ccaOut);
|
||||
run(args.out, args.binary, args.forceDownload, args.ccaOut);
|
||||
|
||||
75
src/cli/chem-comp-dict/util.ts
Normal file
75
src/cli/chem-comp-dict/util.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
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 } 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';
|
||||
|
||||
export async function ensureAvailable(path: string, url: string, forceDownload = false) {
|
||||
if (forceDownload || !fs.existsSync(path)) {
|
||||
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()));
|
||||
} else {
|
||||
await writeFile(path, await data.text());
|
||||
}
|
||||
console.log(`done downloading ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureDataAvailable(forceDownload = false) {
|
||||
await ensureAvailable(CCD_PATH, CCD_URL, forceDownload);
|
||||
await ensureAvailable(PVCD_PATH, PVCD_URL, forceDownload);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
export async function readCCD() {
|
||||
return readFileAsCollection(CCD_PATH, CCD_Schema);
|
||||
}
|
||||
|
||||
export async function readPVCD() {
|
||||
return readFileAsCollection(PVCD_PATH, CCD_Schema);
|
||||
}
|
||||
|
||||
async function parseCif(data: string | Uint8Array) {
|
||||
const comp = CIF.parse(data);
|
||||
console.time('parse cif');
|
||||
const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
|
||||
console.timeEnd('parse cif');
|
||||
if (parsed.isError) throw 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);
|
||||
return encoder.getData();
|
||||
}
|
||||
|
||||
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';
|
||||
@@ -70,7 +70,7 @@ function classify(name: string, field: CifField): CifWriter.Field {
|
||||
}
|
||||
}
|
||||
|
||||
export default function convert(path: string, asText = false, hints?: EncodingStrategyHint[], filter?: string) {
|
||||
export function convert(path: string, asText = false, hints?: EncodingStrategyHint[], filter?: string) {
|
||||
return Task.create<Uint8Array>('BinaryCIF', async ctx => {
|
||||
const encodingProvider: BinaryEncodingProvider = hints
|
||||
? CifWriter.createEncodingProviderFromJsonConfig(hints)
|
||||
|
||||
@@ -10,7 +10,7 @@ import * as argparse from 'argparse';
|
||||
import * as util from 'util';
|
||||
import * as fs from 'fs';
|
||||
import * as zlib from 'zlib';
|
||||
import convert from './converter';
|
||||
import { convert } from './converter';
|
||||
|
||||
require('util.promisify').shim();
|
||||
|
||||
@@ -37,20 +37,20 @@ function run(args: Args) {
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
add_help: true,
|
||||
description: 'Convert any CIF file to a BCIF file'
|
||||
});
|
||||
parser.addArgument([ 'src' ], {
|
||||
parser.add_argument('src', {
|
||||
help: 'Source CIF path'
|
||||
});
|
||||
parser.addArgument([ 'out' ], {
|
||||
parser.add_argument('out', {
|
||||
help: 'Output BCIF path'
|
||||
});
|
||||
parser.addArgument([ '-c', '--config' ], {
|
||||
parser.add_argument('-c', '--config', {
|
||||
help: 'Optional encoding strategy/precision config path',
|
||||
required: false
|
||||
});
|
||||
parser.addArgument([ '-f', '--filter' ], {
|
||||
parser.add_argument('-f', '--filter', {
|
||||
help: 'Optional filter whitelist/blacklist path',
|
||||
required: false
|
||||
});
|
||||
@@ -61,7 +61,7 @@ interface Args {
|
||||
config?: string
|
||||
filter?: string
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
if (args) {
|
||||
run(args);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -12,7 +12,7 @@ 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 { parseCifText } from '../../mol-io/reader/cif/text/parser';
|
||||
import { generateSchema } from './util/cif-dic';
|
||||
import { generate } from './util/generate';
|
||||
import { Filter, Database } from './util/schema';
|
||||
@@ -28,27 +28,23 @@ function getDicNamespace(block: CifBlock) {
|
||||
|
||||
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();
|
||||
const mmcifDic = await parseCifText(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();
|
||||
const ihmDic = await parseCifText(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;
|
||||
await ensureMaDicAvailable();
|
||||
const maDic = await parseCifText(fs.readFileSync(MA_DIC_PATH, 'utf8')).run();
|
||||
if (maDic.isError) throw maDic;
|
||||
|
||||
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 maDicVersion = getDicVersion(maDic.result.blocks[0]);
|
||||
const version = `Dictionary versions: mmCIF ${mmcifDicVersion}, IHM ${ihmDicVersion}, MA ${maDicVersion}.`;
|
||||
|
||||
const frames: CifFrame[] = [...mmcifDic.result.blocks[0].saveFrames, ...ihmDic.result.blocks[0].saveFrames, ...carbBranchDic.result.blocks[0].saveFrames, ...carbCompDic.result.blocks[0].saveFrames];
|
||||
const frames: CifFrame[] = [...mmcifDic.result.blocks[0].saveFrames, ...ihmDic.result.blocks[0].saveFrames, ...maDic.result.blocks[0].saveFrames];
|
||||
const schema = generateSchema(frames);
|
||||
|
||||
await runGenerateSchema(name, version, schema, fieldNamesPath, typescript, out, moldbImportPath, addAliases);
|
||||
@@ -56,7 +52,7 @@ async function runGenerateSchemaMmcif(name: string, fieldNamesPath: string, type
|
||||
|
||||
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();
|
||||
const cifCoreDic = await parseCifText(fs.readFileSync(CIF_CORE_DIC_PATH, 'utf8')).run();
|
||||
if (cifCoreDic.isError) throw cifCoreDic;
|
||||
|
||||
const cifCoreDicVersion = getDicVersion(cifCoreDic.result.blocks[0]);
|
||||
@@ -80,7 +76,7 @@ async function resolveImports(frames: CifFrame[], baseDir: string): Promise<Map<
|
||||
if (!file) continue;
|
||||
if (imports.has(file)) continue;
|
||||
|
||||
const dic = await parseText(fs.readFileSync(path.join(baseDir, file), 'utf8')).run();
|
||||
const dic = await parseCifText(fs.readFileSync(path.join(baseDir, file), 'utf8')).run();
|
||||
if (dic.isError) throw dic;
|
||||
|
||||
imports.set(file, [...dic.result.blocks[0].saveFrames]);
|
||||
@@ -92,7 +88,7 @@ async function resolveImports(frames: CifFrame[], baseDir: string): Promise<Map<
|
||||
}
|
||||
|
||||
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();
|
||||
const dic = await parseCifText(fs.readFileSync(dicPath, 'utf8')).run();
|
||||
if (dic.isError) throw dic;
|
||||
|
||||
const dicVersion = getDicVersion(dic.result.blocks[0]);
|
||||
@@ -124,23 +120,22 @@ async function getFieldNamesFilter(fieldNamesPath: string): Promise<Filter> {
|
||||
const csvFile = parsed.result;
|
||||
|
||||
const fieldNamesCol = csvFile.table.getColumn('0');
|
||||
if (!fieldNamesCol) throw 'error getting fields columns';
|
||||
if (!fieldNamesCol) throw new Error('error getting fields columns');
|
||||
const fieldNames = fieldNamesCol.toStringArray();
|
||||
|
||||
const filter: Filter = {};
|
||||
fieldNames.forEach((name, i) => {
|
||||
const [ category, field ] = name.split('.');
|
||||
const [category, field] = name.split('.');
|
||||
// console.log(category, field)
|
||||
if (!filter[ category ]) filter[ category ] = {};
|
||||
filter[ category ][ field ] = true;
|
||||
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 ensureMaDicAvailable() { await ensureDicAvailable(MA_DIC_PATH, MA_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);
|
||||
@@ -165,10 +160,8 @@ 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 MA_DIC_PATH = `${DIC_DIR}/ma-extension.dic`;
|
||||
const MA_DIC_URL = 'https://raw.githubusercontent.com/ihmwg/MA-dictionary/master/mmcif_ma.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';
|
||||
@@ -178,44 +171,44 @@ 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,
|
||||
add_help: true,
|
||||
description: 'Create schema from mmcif dictionary (v50 plus IHM and entity_branch extensions, downloaded from wwPDB)'
|
||||
});
|
||||
parser.addArgument([ '--preset', '-p' ], {
|
||||
defaultValue: '',
|
||||
parser.add_argument('--preset', '-p', {
|
||||
default: '',
|
||||
choices: ['', 'mmCIF', 'CCD', 'BIRD', 'CifCore'],
|
||||
help: 'Preset name'
|
||||
});
|
||||
parser.addArgument([ '--name', '-n' ], {
|
||||
defaultValue: '',
|
||||
parser.add_argument('--name', '-n', {
|
||||
default: '',
|
||||
help: 'Schema name'
|
||||
});
|
||||
parser.addArgument([ '--out', '-o' ], {
|
||||
parser.add_argument('--out', '-o', {
|
||||
help: 'Generated schema output path, if not given printed to stdout'
|
||||
});
|
||||
parser.addArgument([ '--targetFormat', '-tf' ], {
|
||||
defaultValue: 'typescript-molstar',
|
||||
parser.add_argument('--targetFormat', '-tf', {
|
||||
default: 'typescript-molstar',
|
||||
choices: ['typescript-molstar', 'json-internal'],
|
||||
help: 'Target format'
|
||||
});
|
||||
parser.addArgument([ '--dicPath', '-d' ], {
|
||||
defaultValue: '',
|
||||
parser.add_argument('--dicPath', '-d', {
|
||||
default: '',
|
||||
help: 'Path to dictionary'
|
||||
});
|
||||
parser.addArgument([ '--fieldNamesPath', '-fn' ], {
|
||||
defaultValue: '',
|
||||
parser.add_argument('--fieldNamesPath', '-fn', {
|
||||
default: '',
|
||||
help: 'Field names to include'
|
||||
});
|
||||
parser.addArgument([ '--forceDicDownload', '-f' ], {
|
||||
action: 'storeTrue',
|
||||
parser.add_argument('--forceDicDownload', '-f', {
|
||||
action: 'store_true',
|
||||
help: 'Force download of dictionaries'
|
||||
});
|
||||
parser.addArgument([ '--moldataImportPath', '-mip' ], {
|
||||
defaultValue: 'molstar/lib/mol-data',
|
||||
parser.add_argument('--moldataImportPath', '-mip', {
|
||||
default: 'molstar/lib/mol-data',
|
||||
help: 'mol-data import path (for typescript target only)'
|
||||
});
|
||||
parser.addArgument([ '--addAliases', '-aa' ], {
|
||||
action: 'storeTrue',
|
||||
parser.add_argument('--addAliases', '-aa', {
|
||||
action: 'store_true',
|
||||
help: 'Add field name/path aliases'
|
||||
});
|
||||
interface Args {
|
||||
@@ -230,7 +223,7 @@ interface Args {
|
||||
moldataImportPath: string
|
||||
addAliases: boolean
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
const FORCE_DIC_DOWNLOAD = args.forceDicDownload;
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
case 'seq-one-letter-code':
|
||||
case 'author':
|
||||
case 'orcid_id':
|
||||
case 'pdbx_PDB_obsoleted_db_id':
|
||||
case 'pdbx_related_db_id':
|
||||
case 'sequence_dep':
|
||||
case 'pdb_id':
|
||||
case 'emd_id':
|
||||
@@ -79,9 +81,10 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
case 'List(Real,Real)':
|
||||
case 'List(Real,Real,Real,Real)':
|
||||
case 'Date':
|
||||
case 'Datetime':
|
||||
case 'DateTime':
|
||||
case 'Tag':
|
||||
case 'Implied':
|
||||
case 'Word':
|
||||
return wrapContainer('str', ',', description, container);
|
||||
case 'Real':
|
||||
return wrapContainer('float', ',', description, container);
|
||||
@@ -187,7 +190,7 @@ function getContainer(d: Data.CifFrame, imports: Imports, ctx: FrameData) {
|
||||
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) ];
|
||||
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}'`);
|
||||
}
|
||||
@@ -232,29 +235,26 @@ const FORCE_INT_FIELDS = [
|
||||
'_struct_sheet_range.end_auth_seq_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* Note that name and mapped name must share a prefix. This is not always the case in
|
||||
* the cifCore dictionary, but for downstream code to work a container field with the
|
||||
* same prefix as the member fields must be given here and in the field names filter
|
||||
* list.
|
||||
*/
|
||||
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',
|
||||
'atom_site_aniso.u_11': 'u', // is matrix_u in the the dic
|
||||
'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',
|
||||
};
|
||||
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',
|
||||
'atom_site_aniso.matrix_u': [
|
||||
'atom_site_anisotrop_U',
|
||||
'atom_site_aniso.U'
|
||||
],
|
||||
};
|
||||
|
||||
@@ -317,7 +317,7 @@ export function generateSchema(frames: CifFrame[], imports: Imports = new Map())
|
||||
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;
|
||||
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;
|
||||
@@ -372,7 +372,7 @@ export function generateSchema(frames: CifFrame[], imports: Imports = new Map())
|
||||
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 childName: string = 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}`);
|
||||
|
||||
@@ -8,7 +8,7 @@ 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) {
|
||||
function header(name: string, info: string, moldataImportPath: string) {
|
||||
return `/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
@@ -22,7 +22,7 @@ import { Database, Column } from '${moldataImportPath}/db';
|
||||
import Schema = Column.Schema;`;
|
||||
}
|
||||
|
||||
function footer (name: string) {
|
||||
function footer(name: string) {
|
||||
return `
|
||||
export type ${name}_Schema = typeof ${name}_Schema;
|
||||
export interface ${name}_Database extends Database<${name}_Schema> {};`;
|
||||
@@ -89,7 +89,7 @@ function doc(description: string, spacesCount: number) {
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function generate (name: string, info: string, schema: Database, fields: Filter | undefined, moldataImportPath: string, addAliases: boolean) {
|
||||
export function generate(name: string, info: string, schema: Database, fields: Filter | undefined, moldataImportPath: string, addAliases: boolean) {
|
||||
const codeLines: string[] = [];
|
||||
|
||||
if (fields) {
|
||||
@@ -128,7 +128,7 @@ export function generate (name: string, info: string, schema: Database, fields:
|
||||
codeLines.push('');
|
||||
codeLines.push(`export const ${name}_Aliases = {`);
|
||||
Object.keys(schema.aliases).forEach(path => {
|
||||
const [ table, columnName ] = path.split('.');
|
||||
const [table, columnName] = path.split('.');
|
||||
if (fields && !fields[table]) return;
|
||||
if (fields && !fields[table][columnName]) return;
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ 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]+)/);
|
||||
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
|
||||
|
||||
@@ -51,13 +51,13 @@ export function ListCol(subType: 'int' | 'str' | 'float' | 'coord', separator: s
|
||||
|
||||
export type Filter = { [ table: string ]: { [ column: string ]: true } }
|
||||
|
||||
export function mergeFilters (...filters: Filter[]) {
|
||||
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 => {
|
||||
Object.keys(filter[category]).forEach(field => {
|
||||
const key = `${category}.${field}`;
|
||||
const value = fields.get(key) || 0;
|
||||
fields.set(key, value + 1);
|
||||
|
||||
@@ -31,6 +31,8 @@ async function ensureAvailable(path: string, url: string) {
|
||||
|
||||
async function ensureLipidsAvailable() { await ensureAvailable(MARTINI_LIPIDS_PATH, MARTINI_LIPIDS_URL); }
|
||||
|
||||
const extraLipids = ['DMPC'];
|
||||
|
||||
async function run(out: string) {
|
||||
await ensureLipidsAvailable();
|
||||
const lipidsItpStr = fs.readFileSync(MARTINI_LIPIDS_PATH, 'utf8');
|
||||
@@ -44,17 +46,20 @@ async function run(out: string) {
|
||||
UniqueArray.add(lipids, v, v);
|
||||
}
|
||||
|
||||
for (const v of extraLipids) {
|
||||
UniqueArray.add(lipids, v, v);
|
||||
}
|
||||
|
||||
const lipidNames = JSON.stringify(lipids.array);
|
||||
|
||||
if (out) {
|
||||
const output = `/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated lipid params file. Names extracted from Martini FF lipids itp.
|
||||
*
|
||||
* @author molstar/lipid-params cli
|
||||
*/
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated lipid params file. Names extracted from Martini FF lipids itp.
|
||||
*
|
||||
* @author molstar/lipid-params cli
|
||||
*/
|
||||
|
||||
export const LipidNames = new Set(${lipidNames.replace(/"/g, "'").replace(/,/g, ', ')});
|
||||
`;
|
||||
@@ -65,21 +70,21 @@ export const LipidNames = new Set(${lipidNames.replace(/"/g, "'").replace(/,/g,
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
add_help: true,
|
||||
description: 'Create lipid params (from martini lipids itp)'
|
||||
});
|
||||
parser.addArgument([ '--out', '-o' ], {
|
||||
parser.add_argument('--out', '-o', {
|
||||
help: 'Generated lipid params output path, if not given printed to stdout'
|
||||
});
|
||||
parser.addArgument([ '--forceDownload', '-f' ], {
|
||||
action: 'storeTrue',
|
||||
parser.add_argument('--forceDownload', '-f', {
|
||||
action: 'store_true',
|
||||
help: 'Force download of martini lipids itp'
|
||||
});
|
||||
interface Args {
|
||||
out: string
|
||||
forceDownload: boolean
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
const FORCE_DOWNLOAD = args.forceDownload;
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ _.StateTransforms.Data.Download.id;
|
||||
|
||||
// Empty plugin context
|
||||
const ctx = new PluginContext({
|
||||
actions: [],
|
||||
behaviors: []
|
||||
});
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ function paramInfo(param: PD.Any, offset: number): string {
|
||||
case 'file': return `JavaScript File Handle`;
|
||||
case 'file-list': return `JavaScript FileList Handle`;
|
||||
case 'select': return `One of ${oToS(param.options)}`;
|
||||
case 'value-ref': return `Reference to a runtime defined value.`;
|
||||
case 'data-ref': return `Reference to a computed data value.`;
|
||||
case 'text': return 'String';
|
||||
case 'interval': return `Interval [min, max]`;
|
||||
case 'group': return `Object with:\n${getParams(param.params, offset + 2)}`;
|
||||
|
||||
@@ -10,7 +10,7 @@ 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 { Model, Structure, StructureElement, Unit, StructureProperties, UnitRing, Trajectory } from '../../mol-model/structure';
|
||||
// import { Run, Progress } from '../../mol-task'
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { openCif, downloadCif } from './helpers';
|
||||
@@ -19,6 +19,7 @@ import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
|
||||
import { Sequence } from '../../mol-model/sequence';
|
||||
import { ModelSecondaryStructure } from '../../mol-model-formats/structure/property/secondary-structure';
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
import { Task } from '../../mol-task';
|
||||
|
||||
|
||||
async function downloadFromPdb(pdb: string) {
|
||||
@@ -62,7 +63,7 @@ export function printSecStructure(model: Model) {
|
||||
const count = residues._rowCount;
|
||||
let rI = 0;
|
||||
while (rI < count) {
|
||||
let start = rI;
|
||||
const start = rI;
|
||||
while (rI < count && key[start] === key[rI]) rI++;
|
||||
rI--;
|
||||
|
||||
@@ -98,15 +99,17 @@ export function printBonds(structure: Structure, showIntra: boolean, showInter:
|
||||
for (const unit of structure.units) {
|
||||
if (!Unit.isAtomic(unit)) continue;
|
||||
|
||||
for (const pairBonds of bonds.getConnectedUnits(unit)) {
|
||||
for (const pairBonds of bonds.getConnectedUnits(unit.id)) {
|
||||
if (!pairBonds.areUnitsOrdered || pairBonds.edgeCount === 0) continue;
|
||||
|
||||
const { unitA, unitB } = pairBonds;
|
||||
console.log(`${pairBonds.unitA.id} - ${pairBonds.unitB.id}: ${pairBonds.edgeCount} bond(s)`);
|
||||
const { unitA, unitB, edgeCount } = pairBonds;
|
||||
const uA = structure.unitMap.get(unitA);
|
||||
const uB = structure.unitMap.get(unitB);
|
||||
console.log(`${unitA} - ${unitB}: ${edgeCount} bond(s)`);
|
||||
|
||||
for (const aI of pairBonds.connectedIndices) {
|
||||
for (const bond of pairBonds.getEdges(aI)) {
|
||||
console.log(`${atomLabel(unitA.model, unitA.elements[aI])} -- ${atomLabel(unitB.model, unitB.elements[bond.indexB])}`);
|
||||
console.log(`${atomLabel(uA.model, uA.elements[aI])} -- ${atomLabel(uB.model, uB.elements[bond.indexB])}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,10 +186,11 @@ export function printSymmetryInfo(model: Model) {
|
||||
console.log(`NCS operators: ${symmetry.ncsOperators && symmetry.ncsOperators.map(a => a.name).join(', ')}`);
|
||||
}
|
||||
|
||||
export function printModelStats(models: ReadonlyArray<Model>) {
|
||||
export async function printModelStats(models: Trajectory) {
|
||||
console.log('\nModels\n=============');
|
||||
|
||||
for (const m of models) {
|
||||
for (let i = 0; i < models.frameCount; i++) {
|
||||
const m = await Task.resolveInContext(models.getFrameAtIndex(i));
|
||||
if (m.coarseHierarchy.isDefined) {
|
||||
console.log(`${m.label} ${m.modelNum}: ${m.atomicHierarchy.atoms._rowCount} atom(s), ${m.coarseHierarchy.spheres.count} sphere(s), ${m.coarseHierarchy.gaussians.count} gaussian(s)`);
|
||||
} else {
|
||||
@@ -198,7 +202,7 @@ export function printModelStats(models: ReadonlyArray<Model>) {
|
||||
|
||||
export async function getModelsAndStructure(frame: CifFrame) {
|
||||
const models = await trajectoryFromMmCIF(frame).run();
|
||||
const structure = Structure.ofModel(models[0]);
|
||||
const structure = Structure.ofModel(models.representative);
|
||||
return { models, structure };
|
||||
}
|
||||
|
||||
@@ -206,13 +210,13 @@ async function run(frame: CifFrame, args: Args) {
|
||||
const { models, structure } = await getModelsAndStructure(frame);
|
||||
|
||||
if (args.models) printModelStats(models);
|
||||
if (args.seq) printSequence(models[0]);
|
||||
if (args.seq) printSequence(models.representative);
|
||||
if (args.units) printUnits(structure);
|
||||
if (args.sym) printSymmetryInfo(models[0]);
|
||||
if (args.sym) printSymmetryInfo(models.representative);
|
||||
if (args.rings) printRings(structure);
|
||||
if (args.intraBonds) printBonds(structure, true, false);
|
||||
if (args.interBonds) printBonds(structure, false, true);
|
||||
if (args.sec) printSecStructure(models[0]);
|
||||
if (args.sec) printSecStructure(models.representative);
|
||||
}
|
||||
|
||||
async function runDL(pdb: string, args: Args) {
|
||||
@@ -226,21 +230,21 @@ async function runFile(filename: string, args: Args) {
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
add_help: true,
|
||||
description: 'Print info about a structure, mainly to test and showcase the mol-model module'
|
||||
});
|
||||
parser.addArgument(['--download', '-d'], { help: 'Pdb entry id' });
|
||||
parser.addArgument(['--file', '-f'], { help: 'filename' });
|
||||
parser.add_argument('--download', '-d', { help: 'Pdb entry id' });
|
||||
parser.add_argument('--file', '-f', { help: 'filename' });
|
||||
|
||||
parser.addArgument(['--models'], { help: 'print models info', action: 'storeTrue' });
|
||||
parser.addArgument(['--seq'], { help: 'print sequence', action: 'storeTrue' });
|
||||
parser.addArgument(['--units'], { help: 'print units', action: 'storeTrue' });
|
||||
parser.addArgument(['--sym'], { help: 'print symmetry', action: 'storeTrue' });
|
||||
parser.addArgument(['--rings'], { help: 'print rings', action: 'storeTrue' });
|
||||
parser.addArgument(['--intraBonds'], { help: 'print intra unit bonds', action: 'storeTrue' });
|
||||
parser.addArgument(['--interBonds'], { help: 'print inter unit bonds', action: 'storeTrue' });
|
||||
parser.addArgument(['--mod'], { help: 'print modified residues', action: 'storeTrue' });
|
||||
parser.addArgument(['--sec'], { help: 'print secoundary structure', action: 'storeTrue' });
|
||||
parser.add_argument('--models', { help: 'print models info', action: 'store_true' });
|
||||
parser.add_argument('--seq', { help: 'print sequence', action: 'store_true' });
|
||||
parser.add_argument('--units', { help: 'print units', action: 'store_true' });
|
||||
parser.add_argument('--sym', { help: 'print symmetry', action: 'store_true' });
|
||||
parser.add_argument('--rings', { help: 'print rings', action: 'store_true' });
|
||||
parser.add_argument('--intraBonds', { help: 'print intra unit bonds', action: 'store_true' });
|
||||
parser.add_argument('--interBonds', { help: 'print inter unit bonds', action: 'store_true' });
|
||||
parser.add_argument('--mod', { help: 'print modified residues', action: 'store_true' });
|
||||
parser.add_argument('--sec', { help: 'print secoundary structure', action: 'store_true' });
|
||||
interface Args {
|
||||
download?: string,
|
||||
file?: string,
|
||||
@@ -256,7 +260,7 @@ interface Args {
|
||||
mod?: boolean,
|
||||
sec?: boolean,
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
if (args.download) runDL(args.download, args);
|
||||
else if (args.file) runFile(args.file, args);
|
||||
|
||||
@@ -38,7 +38,7 @@ function print(volume: Volume) {
|
||||
}
|
||||
|
||||
async function doMesh(volume: Volume, filename: string) {
|
||||
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) } )).run();
|
||||
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) })).run();
|
||||
console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
|
||||
|
||||
// Export the mesh in OBJ format.
|
||||
@@ -75,13 +75,13 @@ async function run(url: string, meshFilename: string) {
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
add_help: true,
|
||||
description: 'Info about VolumeData from mol-model module'
|
||||
});
|
||||
parser.addArgument([ '--emdb', '-e' ], {
|
||||
parser.add_argument('--emdb', '-e', {
|
||||
help: 'EMDB id, for example 8116',
|
||||
});
|
||||
parser.addArgument([ '--mesh' ], {
|
||||
parser.add_argument('--mesh', {
|
||||
help: 'Mesh filename',
|
||||
required: true
|
||||
});
|
||||
@@ -89,6 +89,6 @@ interface Args {
|
||||
emdb?: string,
|
||||
mesh: string
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
run(`https://ds.litemol.org/em/emd-${args.emdb}/cell?detail=4`, args.mesh);
|
||||
24
src/examples/alpha-orbitals/controls.tsx
Normal file
24
src/examples/alpha-orbitals/controls.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { AlphaOrbitalsExample } from '.';
|
||||
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
|
||||
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
|
||||
import { PluginContextContainer } from '../../mol-plugin-ui/plugin';
|
||||
|
||||
export function mountControls(orbitals: AlphaOrbitalsExample, parent: Element) {
|
||||
ReactDOM.render(<PluginContextContainer plugin={orbitals.plugin}>
|
||||
<Controls orbitals={orbitals} />
|
||||
</PluginContextContainer>, parent);
|
||||
}
|
||||
|
||||
function Controls({ orbitals }: { orbitals: AlphaOrbitalsExample }) {
|
||||
const params = useBehavior(orbitals.params);
|
||||
const values = useBehavior(orbitals.state);
|
||||
|
||||
return <ParameterControls params={params as any} values={values} onChangeValues={(vs: any) => orbitals.state.next(vs)} />;
|
||||
}
|
||||
60420
src/examples/alpha-orbitals/example-data.ts
Normal file
60420
src/examples/alpha-orbitals/example-data.ts
Normal file
File diff suppressed because it is too large
Load Diff
62
src/examples/alpha-orbitals/index.html
Normal file
62
src/examples/alpha-orbitals/index.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<!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* Alpha Orbitals Example</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#app {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
#controls {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 8px;
|
||||
width: 300px;
|
||||
}
|
||||
#sponsor {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
bottom: 8px;
|
||||
font-family: "Helvetica Neue", "Segoe UI", Helvetica, "Source Sans Pro", Arial, sans-serif;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
#sponsor svg {
|
||||
fill: #128EA4;
|
||||
width: 100px;
|
||||
}
|
||||
#sponsor a {
|
||||
text-decoration: none;
|
||||
color: #128EA4;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id='controls'></div>
|
||||
<div id='sponsor'>
|
||||
<a href='https://www.entos.ai/envision' target="_blank" rel="noopener">
|
||||
<svg class="makeStyles-root-46" viewBox="0 0 190 36" xmlns="http://www.w3.org/2000/svg"><path d="M32.2591 28.6707C32.2591 32.3914 29.2421 35.407 25.5214 35.407C22.0752 35.407 19.2338 32.8206 18.8325 29.4831V29.4775C18.8143 29.3312 18.8018 29.1835 18.7934 29.0344C18.7934 29.0316 18.7921 29.0274 18.7921 29.0246V29.0177C18.7865 28.902 18.7837 28.7864 18.7837 28.6707C18.7837 26.2557 20.0532 24.1389 21.9609 22.9503C21.9623 22.9489 21.9651 22.9489 21.9665 22.9475C22.0933 22.8666 22.2243 22.7914 22.3581 22.7203C22.3581 22.7203 22.3595 22.7203 22.3595 22.7189C23.3029 22.2173 24.3787 21.933 25.5214 21.933C29.2421 21.933 32.2591 24.9486 32.2591 28.6707Z"></path><path d="M25.5214 14.0692C29.2421 14.0692 32.2591 11.0522 32.2591 7.33146C32.2591 3.61074 29.2421 0.59375 25.5214 0.59375C22.0529 0.59375 19.1962 3.21637 18.8255 6.58592C18.8185 6.67092 18.8116 6.75454 18.8018 6.83815C18.7893 7.00119 18.7837 7.16563 18.7837 7.33146C18.7837 9.73669 20.0434 11.8465 21.94 13.038C22.0891 13.116 22.2355 13.201 22.3776 13.2916C22.3783 13.2923 22.379 13.2926 22.3797 13.293C22.3804 13.2933 22.3811 13.2937 22.3818 13.2944C23.3196 13.7891 24.3871 14.0692 25.5214 14.0692Z"></path><path d="M19.3645 12.4113C20.2926 12.4113 21.1694 12.638 21.94 13.038C20.0434 11.8465 18.7837 9.73669 18.7837 7.33146C18.7837 7.16563 18.7893 7.00119 18.8018 6.83815C18.4688 9.76455 16.1385 12.0866 13.2065 12.4044C13.8545 13.1193 14.3785 13.9484 14.745 14.857C15.7497 13.3798 17.4443 12.4113 19.3645 12.4113Z"></path><path d="M14.7312 21.1249V21.1236C14.1279 20.2331 13.7767 19.1587 13.7767 18.0007V17.9728C13.7767 15.3084 12.2285 13.0035 9.9835 11.911C9.98141 11.9103 9.97967 11.9096 9.97793 11.9089C9.97619 11.9082 9.97444 11.9075 9.97235 11.9068C9.96817 11.904 9.96538 11.9026 9.9612 11.9012C9.95981 11.9012 9.95981 11.8998 9.95981 11.8998C9.9417 11.8915 9.92394 11.8831 9.90618 11.8747C9.8884 11.8664 9.87063 11.858 9.85251 11.8497C9.82046 11.8343 9.78701 11.819 9.75357 11.8051L9.74521 11.8009C8.91745 11.4372 8.0019 11.2351 7.03898 11.2351C3.31826 11.2351 0.30127 14.2521 0.30127 17.9728C0.30127 21.6935 3.31826 24.7105 7.03898 24.7105C7.98797 24.7105 8.89098 24.514 9.71037 24.1601C9.71246 24.1594 9.7142 24.1583 9.71594 24.1573C9.71768 24.1562 9.71943 24.1552 9.72152 24.1545C9.8107 24.1169 9.8985 24.0765 9.9849 24.0333L9.98629 24.0319C10.7625 23.6919 11.6181 23.5037 12.5197 23.5037C12.7524 23.5037 12.9824 23.5163 13.2081 23.54C13.2082 23.5399 13.2081 23.54 13.2081 23.54C15.0168 23.7365 16.5971 24.695 17.6185 26.0885C17.9195 25.1688 18.3752 24.3201 18.9563 23.5732C17.1964 23.4464 15.6635 22.5058 14.7312 21.1249Z"></path><g clip-path="url(#clip0)"><path d="M106.391 18.0021C106.391 11.3724 101.039 6 94.4389 6H88.4585C81.8581 6 76.5061 11.3724 76.5061 18.0021V30.0042H81.2845V18.0021C81.2845 14.0268 84.4941 10.8008 88.4585 10.8008H94.4347C98.395 10.8008 101.609 14.0226 101.609 18.0021V30.0042H106.391V18.0021Z"></path><path d="M149.432 6H142.258C135.653 6 130.301 11.3724 130.301 18.0021C130.301 24.6319 135.653 30.0042 142.258 30.0042H149.432C156.036 30.0042 161.388 24.6319 161.388 18.0021C161.388 11.3724 156.032 6 149.432 6ZM149.432 25.1992H142.258C138.297 25.1992 135.084 21.9774 135.084 17.9979C135.084 14.0183 138.293 10.7966 142.258 10.7966H149.432C153.392 10.7966 156.606 14.0183 156.606 17.9979C156.606 21.9774 153.392 25.1992 149.432 25.1992Z"></path><path d="M74.1151 25.1992H58.5736C55.4526 25.1992 52.804 23.1924 51.8171 20.3983H74.1151V17.9979C74.1151 17.1808 74.1868 16.3807 74.3175 15.5975H51.8171C52.804 12.8033 55.4526 10.7966 58.5736 10.7966H76.0383C77.1475 8.87458 78.6911 7.22773 80.5299 6H58.5736C51.969 6 46.6169 11.3724 46.6169 18.0021C46.6169 24.6276 51.969 30 58.5736 30H74.1151V25.1992Z"></path><path d="M120.74 6H115.958H102.369C104.212 7.22773 105.751 8.87458 106.861 10.8008H115.958V30H120.74V10.8008H129.838C130.947 8.87458 132.486 7.22773 134.329 6H120.74Z"></path><path d="M182.906 15.6017H169.756C168.436 15.6017 167.365 14.5264 167.365 13.2013C167.365 11.8762 168.436 10.8008 169.756 10.8008H188.882V6H169.756C165.796 6 162.582 9.22173 162.582 13.2013C162.582 17.1808 165.791 20.4025 169.756 20.4025H182.906C184.226 20.4025 185.297 21.4779 185.297 22.803C185.297 24.1281 184.226 25.2034 182.906 25.2034H161.852C160.743 27.1297 159.199 28.7765 157.361 30.0042H182.906C186.866 30.0042 190.08 26.7825 190.08 22.803C190.08 18.8234 186.866 15.6017 182.906 15.6017Z"></path></g><defs><clipPath id="clip0"><rect width="190" height="24" fill="white" transform="translate(0 6)"></rect></clipPath></defs></svg>
|
||||
<div>
|
||||
Entos Envision
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<script>
|
||||
AlphaOrbitalsExample.init('app')
|
||||
</script>
|
||||
<!-- __MOLSTAR_ANALYTICS__ -->
|
||||
</body>
|
||||
</html>
|
||||
221
src/examples/alpha-orbitals/index.ts
Normal file
221
src/examples/alpha-orbitals/index.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { debounceTime, skip } from 'rxjs/operators';
|
||||
import { AlphaOrbital, Basis } from '../../extensions/alpha-orbitals/data-model';
|
||||
import { SphericalBasisOrder } from '../../extensions/alpha-orbitals/spherical-functions';
|
||||
import { BasisAndOrbitals, CreateOrbitalDensityVolume, CreateOrbitalRepresentation3D, CreateOrbitalVolume, StaticBasisAndOrbitals } from '../../extensions/alpha-orbitals/transforms';
|
||||
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { StateObjectSelector, StateTransformer } from '../../mol-state';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
import { mountControls } from './controls';
|
||||
import { DemoMoleculeSDF, DemoOrbitals } from './example-data';
|
||||
import './index.html';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
interface DemoInput {
|
||||
moleculeSdf: string,
|
||||
basis: Basis,
|
||||
order: SphericalBasisOrder,
|
||||
orbitals: AlphaOrbital[]
|
||||
}
|
||||
|
||||
interface Params {
|
||||
show: { name: 'orbital', params: { index: number } } | { name: 'density', params: {} },
|
||||
isoValue: number,
|
||||
gpuSurface: boolean
|
||||
}
|
||||
|
||||
type Selectors = {
|
||||
type: 'orbital',
|
||||
volume: StateObjectSelector<PluginStateObject.Volume.Data, typeof CreateOrbitalVolume>,
|
||||
positive: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
|
||||
negative: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
|
||||
} | {
|
||||
type: 'density',
|
||||
volume: StateObjectSelector<PluginStateObject.Volume.Data, typeof CreateOrbitalDensityVolume>,
|
||||
positive: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
|
||||
}
|
||||
|
||||
export class AlphaOrbitalsExample {
|
||||
plugin: PluginUIContext;
|
||||
|
||||
async init(target: string | HTMLElement) {
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...defaultSpec,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
},
|
||||
components: {
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' },
|
||||
},
|
||||
canvas3d: {
|
||||
camera: {
|
||||
helper: { axes: { name: 'off', params: {} } }
|
||||
}
|
||||
},
|
||||
config: [
|
||||
[PluginConfig.Viewport.ShowExpand, false],
|
||||
[PluginConfig.Viewport.ShowControls, false],
|
||||
[PluginConfig.Viewport.ShowSelectionMode, false],
|
||||
[PluginConfig.Viewport.ShowAnimation, false],
|
||||
]
|
||||
});
|
||||
|
||||
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
|
||||
|
||||
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Error',
|
||||
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.load({
|
||||
moleculeSdf: DemoMoleculeSDF,
|
||||
...DemoOrbitals
|
||||
});
|
||||
|
||||
mountControls(this, document.getElementById('controls')!);
|
||||
}
|
||||
|
||||
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
|
||||
readonly state = new BehaviorSubject<Params>({ show: { name: 'orbital', params: { index: 32 } }, isoValue: 1, gpuSurface: true });
|
||||
|
||||
private selectors?: Selectors = void 0;
|
||||
private basis?: StateObjectSelector<BasisAndOrbitals> = void 0;
|
||||
|
||||
private currentParams: Params = { ...this.state.value };
|
||||
|
||||
private clearVolume() {
|
||||
if (!this.selectors) return;
|
||||
const v = this.selectors.volume;
|
||||
this.selectors = void 0;
|
||||
return this.plugin.build().delete(v).commit();
|
||||
}
|
||||
|
||||
private async syncVolume() {
|
||||
if (!this.basis?.isOk) return;
|
||||
|
||||
const state = this.state.value;
|
||||
|
||||
if (state.show.name !== this.selectors?.type) {
|
||||
await this.clearVolume();
|
||||
}
|
||||
|
||||
const update = this.plugin.build();
|
||||
if (state.show.name === 'orbital') {
|
||||
if (!this.selectors) {
|
||||
const volume = update
|
||||
.to(this.basis)
|
||||
.apply(CreateOrbitalVolume, { index: state.show.params.index });
|
||||
|
||||
const positive = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('positive', ColorNames.blue)).selector;
|
||||
const negative = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('negative', ColorNames.red)).selector;
|
||||
|
||||
this.selectors = { type: 'orbital', volume: volume.selector, positive, negative };
|
||||
} else {
|
||||
const index = state.show.params.index;
|
||||
update.to(this.selectors.volume).update(CreateOrbitalVolume, () => ({ index }));
|
||||
}
|
||||
} else {
|
||||
if (!this.selectors) {
|
||||
const volume = update
|
||||
.to(this.basis)
|
||||
.apply(CreateOrbitalDensityVolume);
|
||||
const positive = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('positive', ColorNames.blue)).selector;
|
||||
this.selectors = { type: 'density', volume: volume.selector, positive };
|
||||
}
|
||||
}
|
||||
|
||||
await update.commit();
|
||||
|
||||
if (this.currentParams.gpuSurface !== this.state.value.gpuSurface) {
|
||||
await this.setIsovalue();
|
||||
}
|
||||
|
||||
this.currentParams = this.state.value;
|
||||
}
|
||||
|
||||
private setIsovalue() {
|
||||
if (!this.selectors) return;
|
||||
|
||||
this.currentParams = this.state.value;
|
||||
const update = this.plugin.build();
|
||||
update.to(this.selectors.positive).update(this.volumeParams('positive', ColorNames.blue));
|
||||
if (this.selectors?.type === 'orbital') {
|
||||
update.to(this.selectors.negative).update(this.volumeParams('negative', ColorNames.red));
|
||||
}
|
||||
return update.commit();
|
||||
}
|
||||
|
||||
private volumeParams(kind: 'positive' | 'negative', color: Color): StateTransformer.Params<typeof CreateOrbitalRepresentation3D> {
|
||||
return {
|
||||
alpha: 0.85,
|
||||
color,
|
||||
kind,
|
||||
relativeIsovalue: this.state.value.isoValue,
|
||||
pickable: false,
|
||||
xrayShaded: true,
|
||||
tryUseGpu: true
|
||||
};
|
||||
}
|
||||
|
||||
async load(input: DemoInput) {
|
||||
await this.plugin.clear();
|
||||
|
||||
const data = await this.plugin.builders.data.rawData({ data: input.moleculeSdf }, { state: { isGhost: true } });
|
||||
const trajectory = await this.plugin.builders.structure.parseTrajectory(data, 'mol');
|
||||
const model = await this.plugin.builders.structure.createModel(trajectory);
|
||||
const structure = await this.plugin.builders.structure.createStructure(model);
|
||||
|
||||
const all = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'all');
|
||||
if (all) await this.plugin.builders.structure.representation.addRepresentation(all, { type: 'ball-and-stick', color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } });
|
||||
|
||||
|
||||
this.basis = await this.plugin.build().toRoot()
|
||||
.apply(StaticBasisAndOrbitals, { basis: input.basis, order: input.order, orbitals: input.orbitals })
|
||||
.commit();
|
||||
|
||||
await this.syncVolume();
|
||||
|
||||
this.params.next({
|
||||
show: ParamDefinition.MappedStatic('orbital', {
|
||||
'orbital': ParamDefinition.Group({
|
||||
index: ParamDefinition.Numeric(32, { min: 0, max: input.orbitals.length - 1 }, { immediateUpdate: true, isEssential: true }),
|
||||
}),
|
||||
'density': ParamDefinition.EmptyGroup()
|
||||
}, { cycle: true }),
|
||||
isoValue: ParamDefinition.Numeric(this.currentParams.isoValue, { min: 0.5, max: 3, step: 0.1 }, { immediateUpdate: true, isEssential: false }),
|
||||
gpuSurface: ParamDefinition.Boolean(this.currentParams.gpuSurface, { isHidden: true })
|
||||
});
|
||||
|
||||
this.state.pipe(skip(1), debounceTime(1000 / 24)).subscribe(async params => {
|
||||
if (params.show.name !== this.currentParams.show.name
|
||||
|| (params.show.name === 'orbital' && this.currentParams.show.name === 'orbital' && params.show.params.index !== this.currentParams.show.params.index)) {
|
||||
this.syncVolume();
|
||||
} else if (params.isoValue !== this.currentParams.isoValue || params.gpuSurface !== this.currentParams.gpuSurface) {
|
||||
this.setIsovalue();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).AlphaOrbitalsExample = new AlphaOrbitalsExample();
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
import { PluginUIComponent } from '../../mol-plugin-ui/base';
|
||||
import * as React from 'react';
|
||||
|
||||
export class CustomToastMessage extends PluginUIComponent {
|
||||
render() {
|
||||
|
||||
51
src/examples/basic-wrapper/custom-theme.ts
Normal file
51
src/examples/basic-wrapper/custom-theme.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { isPositionLocation } from '../../mol-geo/util/location-iterator';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { ColorTheme } from '../../mol-theme/color';
|
||||
import { ThemeDataContext } from '../../mol-theme/theme';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
export function CustomColorTheme(
|
||||
ctx: ThemeDataContext,
|
||||
props: PD.Values<{}>
|
||||
): ColorTheme<{}> {
|
||||
const { radius, center } = ctx.structure?.boundary.sphere!;
|
||||
const radiusSq = Math.max(radius * radius, 0.001);
|
||||
const scale = ColorTheme.PaletteScale;
|
||||
|
||||
return {
|
||||
factory: CustomColorTheme,
|
||||
granularity: 'vertex',
|
||||
color: location => {
|
||||
if (!isPositionLocation(location)) return ColorNames.black;
|
||||
const dist = Vec3.squaredDistance(location.position, center);
|
||||
const t = Math.min(dist / radiusSq, 1);
|
||||
return ((t * scale) | 0) as Color;
|
||||
},
|
||||
palette: {
|
||||
filter: 'nearest',
|
||||
colors: [
|
||||
ColorNames.red,
|
||||
ColorNames.pink,
|
||||
ColorNames.violet,
|
||||
ColorNames.orange,
|
||||
ColorNames.yellow,
|
||||
ColorNames.green,
|
||||
ColorNames.blue
|
||||
]
|
||||
},
|
||||
props: props,
|
||||
description: '',
|
||||
};
|
||||
}
|
||||
|
||||
export const CustomColorThemeProvider: ColorTheme.Provider<{}, 'basic-wrapper-custom-color-theme'> = {
|
||||
name: 'basic-wrapper-custom-color-theme',
|
||||
label: 'Custom Color Theme',
|
||||
category: ColorTheme.Category.Misc,
|
||||
factory: CustomColorTheme,
|
||||
getParams: () => ({}),
|
||||
defaultValues: { },
|
||||
isApplicable: (ctx: ThemeDataContext) => true,
|
||||
};
|
||||
@@ -69,8 +69,9 @@
|
||||
$('format').value = format;
|
||||
$('format').onchange = function (e) { format = e.target.value; }
|
||||
|
||||
BasicMolStarWrapper.init('app' /** or document.getElementById('app') */);
|
||||
BasicMolStarWrapper.setBackground(0xffffff);
|
||||
BasicMolStarWrapper.init('app' /** or document.getElementById('app') */).then(() => {
|
||||
BasicMolStarWrapper.setBackground(0xffffff);
|
||||
});
|
||||
|
||||
addControl('Load Asym Unit', () => BasicMolStarWrapper.load({ url: url, format: format }));
|
||||
addControl('Load Assembly', () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId }));
|
||||
@@ -86,7 +87,7 @@
|
||||
|
||||
// adjust this number to make the animation faster or slower
|
||||
// requires to "restart" the animation if changed
|
||||
BasicMolStarWrapper.animate.modelIndex.maxFPS = 30;
|
||||
BasicMolStarWrapper.animate.modelIndex.targetFps = 30;
|
||||
|
||||
addControl('Play To End', () => BasicMolStarWrapper.animate.modelIndex.onceForward());
|
||||
addControl('Play To Start', () => BasicMolStarWrapper.animate.modelIndex.onceBackward());
|
||||
@@ -97,6 +98,7 @@
|
||||
addHeader('Misc');
|
||||
|
||||
addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes());
|
||||
addControl('Apply Custom Theme', () => BasicMolStarWrapper.coloring.applyCustomTheme());
|
||||
addControl('Default Coloring', () => BasicMolStarWrapper.coloring.applyDefault());
|
||||
|
||||
addHeader('Interactivity');
|
||||
|
||||
@@ -4,39 +4,37 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
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 { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { StripedResidues } from './coloring';
|
||||
import { CustomToastMessage } from './controls';
|
||||
import { CustomColorThemeProvider } from './custom-theme';
|
||||
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;
|
||||
plugin: PluginUIContext;
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec,
|
||||
async init(target: string | HTMLElement) {
|
||||
this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginUISpec(),
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: {
|
||||
// left: 'none'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
@@ -45,6 +43,7 @@ class BasicWrapper {
|
||||
});
|
||||
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!);
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(CustomColorThemeProvider);
|
||||
this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
|
||||
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
|
||||
}
|
||||
@@ -61,7 +60,7 @@ class BasicWrapper {
|
||||
params: { id: assemblyId }
|
||||
} : {
|
||||
name: 'model',
|
||||
params: { }
|
||||
params: {}
|
||||
},
|
||||
showUnitcell: false,
|
||||
representationPreset: 'auto'
|
||||
@@ -83,16 +82,20 @@ class BasicWrapper {
|
||||
if (!this.plugin.canvas3d.props.trackball.spin) PluginCommands.Camera.Reset(this.plugin, {});
|
||||
}
|
||||
|
||||
private animateModelIndexTargetFps() {
|
||||
return Math.max(1, this.animate.modelIndex.targetFps | 0);
|
||||
}
|
||||
|
||||
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: {} } }); },
|
||||
targetFps: 8,
|
||||
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'forward' } } }); },
|
||||
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'backward' } } }); },
|
||||
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'palindrome', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: { direction: 'forward' } } }); },
|
||||
stop: () => this.plugin.managers.animation.stop()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
coloring = {
|
||||
applyStripes: async () => {
|
||||
@@ -102,6 +105,13 @@ class BasicWrapper {
|
||||
}
|
||||
});
|
||||
},
|
||||
applyCustomTheme: 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: CustomColorThemeProvider.name as any });
|
||||
}
|
||||
});
|
||||
},
|
||||
applyDefault: async () => {
|
||||
this.plugin.dataTransaction(async () => {
|
||||
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {
|
||||
@@ -109,12 +119,14 @@ class BasicWrapper {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
interactivity = {
|
||||
highlightOn: () => {
|
||||
const data = this.plugin.managers.structure.hierarchy.current.structures[0]?.cell.obj?.data;
|
||||
if (!data) return;
|
||||
|
||||
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()
|
||||
@@ -125,7 +137,7 @@ class BasicWrapper {
|
||||
clearHighlight: () => {
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tests = {
|
||||
staticSuperposition: async () => {
|
||||
@@ -156,7 +168,7 @@ class BasicWrapper {
|
||||
PluginCommands.Toast.Hide(this.plugin, { key: 'toast-1' });
|
||||
PluginCommands.Toast.Hide(this.plugin, { key: 'toast-2' });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
(window as any).BasicMolStarWrapper = new BasicWrapper();
|
||||
@@ -10,7 +10,7 @@ import { superpose } from '../../mol-model/structure/structure/util/superpositio
|
||||
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 { 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';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { CifWriter } from '../../mol-io/writer/cif';
|
||||
import * as S from './schemas';
|
||||
// import { getCategoryInstanceProvider } from './utils'
|
||||
|
||||
export default function create(allData: any) {
|
||||
export function createMapping(allData: any) {
|
||||
const mols = Object.keys(allData);
|
||||
const enc = CifWriter.createEncoder();
|
||||
enc.startDataBlock(mols[0]);
|
||||
@@ -38,7 +38,7 @@ type MappingRow = Table.Row<S.mapping>;
|
||||
|
||||
function writeDomain(enc: CifWriter.Encoder, domain: DomainAnnotation | undefined) {
|
||||
if (!domain) return;
|
||||
enc.writeCategory({ name: `pdbx_${domain.name}_domain_annotation`, instance: () => CifWriter.Category.ofTable(domain.domains) });
|
||||
enc.writeCategory({ name: `pdbx_${domain.name}_domain_annotation`, instance: () => CifWriter.Category.ofTable(domain.domains) });
|
||||
enc.writeCategory({ name: `pdbx_${domain.name}_domain_mapping`, instance: () => CifWriter.Category.ofTable(domain.mappings) });
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import express from 'express';
|
||||
import fetch from 'node-fetch';
|
||||
import createMapping from './mapping';
|
||||
import { createMapping } from './mapping';
|
||||
|
||||
async function getMappings(id: string) {
|
||||
const data = await fetch(`https://www.ebi.ac.uk/pdbe/api/mappings/${id}`);
|
||||
@@ -15,7 +15,7 @@ async function getMappings(id: string) {
|
||||
};
|
||||
|
||||
|
||||
let PORT = process.env.port || 1338;
|
||||
const PORT = process.env.port || 1338;
|
||||
|
||||
const app = express();
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import fetch from 'node-fetch';
|
||||
import createMapping from './mapping';
|
||||
import { createMapping } from './mapping';
|
||||
|
||||
(async function () {
|
||||
const data = await fetch('https://www.ebi.ac.uk/pdbe/api/mappings/1tqn?pretty=true');
|
||||
|
||||
@@ -12,18 +12,18 @@
|
||||
}
|
||||
#app {
|
||||
position: absolute;
|
||||
left: 160px;
|
||||
top: 100px;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
border: 1px solid #ccc;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: absolute;
|
||||
width: 150px;
|
||||
top: 100px;
|
||||
left: 780px;
|
||||
bottom: 100px;
|
||||
right: 50px;
|
||||
z-index: 10;
|
||||
font-family: sans-serif;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
#controls > button {
|
||||
@@ -45,14 +45,15 @@
|
||||
<div id='controls'></div>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
LightingDemo.init('app')
|
||||
LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' })
|
||||
LightingDemo.init('app').then(() => {
|
||||
LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3);
|
||||
});
|
||||
|
||||
addHeader('Example PDB IDs');
|
||||
addControl('1M07', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' }));
|
||||
addControl('6HY0', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6HY0.cif', assemblyId: '1' }));
|
||||
addControl('6QVK', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6QVK.cif', assemblyId: '1' }));
|
||||
addControl('1RB8', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1RB8.cif', assemblyId: '1' }));
|
||||
addControl('4KTC', () => LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3));
|
||||
addControl('5FJ5', () => LightingDemo.load({ url: 'https://models.rcsb.org/5FJ5.bcif', assemblyId: '1' }, 8, 1.8));
|
||||
addControl('1UPN', () => LightingDemo.load({ url: 'https://models.rcsb.org/1UPN.bcif', assemblyId: '1' }, 7, 1.6));
|
||||
addControl('1RB8', () => LightingDemo.load({ url: 'https://models.rcsb.org/1RB8.bcif', assemblyId: '1' }, 6, 1.3));
|
||||
|
||||
addSeparator()
|
||||
|
||||
@@ -83,5 +84,6 @@
|
||||
$('controls').appendChild(h);
|
||||
}
|
||||
</script>
|
||||
<!-- __MOLSTAR_ANALYTICS__ -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,78 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 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 { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import './index.html';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import './index.html';
|
||||
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 = Pick<Canvas3DProps, '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,
|
||||
illustrative: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
|
||||
outline: { name: 'on', params: { scale: 1, threshold: 0.33, color: Color(0x000000) } }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 1.0,
|
||||
light: []
|
||||
}
|
||||
}
|
||||
},
|
||||
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,
|
||||
occlusion: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
|
||||
outline: { name: 'off', params: {} }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
light: [{ inclination: 180, azimuth: 0, color: Color.fromNormalizedRgb(1.0, 1.0, 1.0),
|
||||
intensity: 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,
|
||||
standard: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: { name: 'off', params: {} },
|
||||
outline: { name: 'off', params: {} }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
light: [{ inclination: 180, azimuth: 0, color: Color.fromNormalizedRgb(1.0, 1.0, 1.0),
|
||||
intensity: 0.6 }]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
type Canvas3DPreset = keyof typeof Canvas3DPresets
|
||||
|
||||
class LightingDemo {
|
||||
plugin: PluginContext;
|
||||
plugin: PluginUIContext;
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec,
|
||||
private radius = 5;
|
||||
private bias = 1.1;
|
||||
private preset: Canvas3DPreset = 'illustrative';
|
||||
|
||||
async init(target: string | HTMLElement) {
|
||||
this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginUISpec(),
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
},
|
||||
components: {
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
|
||||
}
|
||||
});
|
||||
@@ -82,36 +90,42 @@ class LightingDemo {
|
||||
|
||||
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
|
||||
},
|
||||
}});
|
||||
if (props.canvas3d.postprocessing.occlusion?.name === 'on') {
|
||||
props.canvas3d.postprocessing.occlusion.params.radius = this.radius;
|
||||
props.canvas3d.postprocessing.occlusion.params.bias = this.bias;
|
||||
}
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
settings: {
|
||||
...props,
|
||||
renderer: {
|
||||
...this.plugin.canvas3d!.props.renderer,
|
||||
...props.canvas3d.renderer
|
||||
},
|
||||
postprocessing: {
|
||||
...this.plugin.canvas3d!.props.postprocessing,
|
||||
...props.canvas3d.postprocessing
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async load({ url, format = 'mmcif', isBinary = false, assemblyId = '' }: LoadParams) {
|
||||
async load({ url, format = 'mmcif', isBinary = true, assemblyId = '' }: LoadParams, radius: number, bias: number) {
|
||||
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: 'model', params: { } });
|
||||
const structure = await this.plugin.builders.structure.createStructure(model, assemblyId ? { name: 'assembly', params: { id: assemblyId } } : { name: 'model', 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' });
|
||||
if (ligand) await this.plugin.builders.structure.representation.addRepresentation(ligand, { type: 'ball-and-stick', color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } });
|
||||
|
||||
this.radius = radius;
|
||||
this.bias = bias;
|
||||
this.setPreset(this.preset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,10 +103,11 @@
|
||||
|
||||
PluginWrapper.init('app' /** or document.getElementById('app') */, {
|
||||
customColorList: CustomColors
|
||||
}).then(() => {
|
||||
PluginWrapper.setBackground(0xffffff);
|
||||
loadAndSnapshot({ url: url, format: format, isBinary: isBinary, assemblyId: assemblyId, representationStyle: representationStyle });
|
||||
PluginWrapper.toggleSpin();
|
||||
});
|
||||
PluginWrapper.setBackground(0xffffff);
|
||||
loadAndSnapshot({ url: url, format: format, isBinary: isBinary, assemblyId: assemblyId, representationStyle: representationStyle });
|
||||
PluginWrapper.toggleSpin();
|
||||
|
||||
PluginWrapper.events.modelInfo.subscribe(function (info) {
|
||||
console.log('Model Info', info);
|
||||
@@ -147,7 +148,7 @@
|
||||
|
||||
// adjust this number to make the animation faster or slower
|
||||
// requires to "restart" the animation if changed
|
||||
PluginWrapper.animate.modelIndex.maxFPS = 30;
|
||||
PluginWrapper.animate.modelIndex.targetFps = 30;
|
||||
|
||||
addControl('Play To End', () => PluginWrapper.animate.modelIndex.onceForward());
|
||||
addControl('Play To Start', () => PluginWrapper.animate.modelIndex.onceBackward());
|
||||
|
||||
@@ -6,14 +6,15 @@
|
||||
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Canvas3DProps, DefaultCanvas3DParams } from '../../mol-canvas3d/canvas3d';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in';
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index';
|
||||
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 { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { CreateVolumeStreamingInfo, InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
|
||||
@@ -40,13 +41,13 @@ class MolStarProteopediaWrapper {
|
||||
modelInfo: this._ev<ModelInfo>()
|
||||
};
|
||||
|
||||
plugin: PluginContext;
|
||||
plugin: PluginUIContext;
|
||||
|
||||
init(target: string | HTMLElement, options?: {
|
||||
async init(target: string | HTMLElement, options?: {
|
||||
customColorList?: number[]
|
||||
}) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec,
|
||||
this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginUISpec(),
|
||||
animations: [
|
||||
AnimateModelIndex
|
||||
],
|
||||
@@ -94,7 +95,7 @@ class MolStarProteopediaWrapper {
|
||||
params: { id: assemblyId }
|
||||
} : {
|
||||
name: 'model' as const,
|
||||
params: { }
|
||||
params: {}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -112,7 +113,7 @@ class MolStarProteopediaWrapper {
|
||||
const structure = this.getObj<PluginStateObject.Molecule.Structure>(StateElements.Assembly);
|
||||
if (!structure) return;
|
||||
|
||||
const style = _style || { };
|
||||
const style = _style || {};
|
||||
|
||||
const update = this.state.build();
|
||||
|
||||
@@ -228,7 +229,7 @@ class MolStarProteopediaWrapper {
|
||||
params: { id: asmId }
|
||||
} : {
|
||||
name: 'model' as const,
|
||||
params: { }
|
||||
params: {}
|
||||
}
|
||||
};
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
|
||||
@@ -249,7 +250,7 @@ class MolStarProteopediaWrapper {
|
||||
setBackground(color: number) {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const renderer = this.plugin.canvas3d.props.renderer;
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
}
|
||||
|
||||
toggleSpin() {
|
||||
@@ -268,19 +269,23 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
camera = {
|
||||
toggleSpin: () => this.toggleSpin(),
|
||||
resetPosition: () => PluginCommands.Camera.Reset(this.plugin, { })
|
||||
resetPosition: () => PluginCommands.Camera.Reset(this.plugin, {})
|
||||
};
|
||||
|
||||
private animateModelIndexTargetFps() {
|
||||
return Math.max(1, this.animate.modelIndex.targetFps | 0);
|
||||
}
|
||||
|
||||
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: {} } }); },
|
||||
targetFps: 8,
|
||||
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'forward' } } }); },
|
||||
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'backward' } } }); },
|
||||
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'palindrome', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: { direction: 'forward' } } }); },
|
||||
stop: () => this.plugin.managers.animation.stop()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
coloring = {
|
||||
evolutionaryConservation: async (params?: { sequence?: boolean, het?: boolean, keepStyle?: boolean }) => {
|
||||
@@ -301,7 +306,7 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private experimentalDataElement?: Element = void 0;
|
||||
experimentalData = {
|
||||
@@ -325,17 +330,17 @@ class MolStarProteopediaWrapper {
|
||||
this.experimentalDataElement = void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
hetGroups = {
|
||||
reset: () => {
|
||||
const update = this.state.build().delete(StateElements.HetGroupFocusGroup);
|
||||
PluginCommands.State.Update(this.plugin, { state: this.state, tree: update });
|
||||
PluginCommands.Camera.Reset(this.plugin, { });
|
||||
PluginCommands.Camera.Reset(this.plugin, {});
|
||||
},
|
||||
focusFirst: async (compId: string, options?: { hideLabels: boolean, doNotLabelWaters: boolean }) => {
|
||||
if (!this.state.transforms.has(StateElements.Assembly)) return;
|
||||
await PluginCommands.Camera.Reset(this.plugin, { });
|
||||
await PluginCommands.Camera.Reset(this.plugin, {});
|
||||
|
||||
const update = this.state.build();
|
||||
|
||||
@@ -392,7 +397,7 @@ class MolStarProteopediaWrapper {
|
||||
const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, radius);
|
||||
PluginCommands.Camera.SetSnapshot(this.plugin, { snapshot, durationMs: 250 });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
snapshot = {
|
||||
get: (params?: PluginState.SnapshotParams) => {
|
||||
@@ -403,7 +408,7 @@ class MolStarProteopediaWrapper {
|
||||
},
|
||||
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}`);
|
||||
download(data, `mol-star_state_${getFormattedTime()}.${type}`);
|
||||
},
|
||||
fetch: async (url: string, type: 'molj' | 'molx' = 'molj') => {
|
||||
try {
|
||||
@@ -415,7 +420,7 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
(window as any).MolStarProteopediaWrapper = MolStarProteopediaWrapper;
|
||||
@@ -4,14 +4,13 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { PluginUIContext } from '../../../mol-plugin-ui/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 function volumeStreamingControls(plugin: PluginContext, parent: Element) {
|
||||
export function volumeStreamingControls(plugin: PluginUIContext, parent: Element) {
|
||||
ReactDOM.render(<PluginContextContainer plugin={plugin}>
|
||||
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
|
||||
</PluginContextContainer>, parent);
|
||||
|
||||
214
src/extensions/alpha-orbitals/_spec/collocation.spec.ts
Normal file
214
src/extensions/alpha-orbitals/_spec/collocation.spec.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Box3D } from '../../../mol-math/geometry';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
import { sphericalCollocation } from '../collocation';
|
||||
import { Basis, CubeGridInfo } from '../data-model';
|
||||
|
||||
describe('alpha-orbitals-cubes', () => {
|
||||
it('water', async () => {
|
||||
const grid: CubeGridInfo = {
|
||||
params: {
|
||||
basis: _testBasis,
|
||||
cutoffThreshold: 0,
|
||||
sphericalOrder: 'cca-reverse',
|
||||
boxExpand: 0,
|
||||
gridSpacing: []
|
||||
},
|
||||
box: Box3D.create(Vec3.create(-1, -1, -1), Vec3.create(1, 1, 1)),
|
||||
delta: Vec3.create(2, 2, 2),
|
||||
dimensions: Vec3.create(2, 2, 2),
|
||||
npoints: 8,
|
||||
size: Vec3.create(2, 2, 2)
|
||||
};
|
||||
|
||||
const matrix = await sphericalCollocation(grid, {
|
||||
energy: 0,
|
||||
occupancy: 0,
|
||||
alpha: [-2.2623991420609075e-16, 0.6360205395000592, 0.6672122399886391, -0.3876927909355508, -1.6780131293332933e-16, 2.844782862661151e-16, 4.977960694176068e-19, -2.3945919908996803e-16]
|
||||
}, RuntimeContext.Synchronous);
|
||||
|
||||
const expected = [-0.1451730622877498, 0.06479453956039086, -0.2777738736440713, -0.057116584776260436, 0.05929916178822645, 0.2742903371231049, -0.07221698722165386, 0.15389180241391376];
|
||||
|
||||
expect(matrix.length).toBe(expected.length);
|
||||
|
||||
for (let i = 0; i < matrix.length; i++) {
|
||||
expect(Math.abs(matrix[i] - expected[i]) < 1e-6).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const _testBasis: Basis = {
|
||||
'atoms': [
|
||||
{
|
||||
'center': [
|
||||
0.025886090588624934,
|
||||
0.019164790004065606,
|
||||
-0.013539970104105408
|
||||
] as Vec3,
|
||||
'shells': [
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
'coefficients': [
|
||||
[
|
||||
-0.004151277818987536,
|
||||
-0.02067024147993795,
|
||||
-0.05150303336984537,
|
||||
0.33462711739899537,
|
||||
0.5621061300983125,
|
||||
0.17129946969948573
|
||||
]
|
||||
],
|
||||
'exponents': [
|
||||
152.28769660788095,
|
||||
27.928015215973073,
|
||||
7.848374792384515,
|
||||
1.1223350202705642,
|
||||
0.5093846587907856,
|
||||
0.24292266532510307
|
||||
]
|
||||
},
|
||||
{
|
||||
'angularMomentum': [1],
|
||||
'coefficients': [
|
||||
[
|
||||
0.007924233646294425,
|
||||
0.051441048251911314,
|
||||
0.18984000600705359,
|
||||
0.4049863191150474,
|
||||
0.40123628611490797,
|
||||
0.1051855189039082
|
||||
]
|
||||
],
|
||||
'exponents': [
|
||||
27.203421487167727,
|
||||
7.09409912597673,
|
||||
2.5383362605345954,
|
||||
1.0610730767843852,
|
||||
0.4851948916410433,
|
||||
0.22938302550642545
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'center': [
|
||||
0.5082729578468134,
|
||||
1.6880351220025265,
|
||||
0.4963443067810461
|
||||
] as Vec3,
|
||||
'shells': [
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
'coefficients': [
|
||||
[
|
||||
0.009163596280542963,
|
||||
0.04936149294292479,
|
||||
0.16853830490998634,
|
||||
0.37056279972195677,
|
||||
0.4164915298246781,
|
||||
0.13033408410772263
|
||||
]
|
||||
],
|
||||
'exponents': [
|
||||
33.710073211949485,
|
||||
6.180705022740464,
|
||||
1.7291385346152253,
|
||||
0.5940057549921978,
|
||||
0.2306698170449518,
|
||||
0.09500256906284119
|
||||
]
|
||||
},
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
'coefficients': [
|
||||
[
|
||||
-0.32279868167000036,
|
||||
3.209629817295221,
|
||||
2.4672629224617935,
|
||||
-0.048487066612842224,
|
||||
-0.2611850111200143,
|
||||
-0.8917817597810863,
|
||||
-1.9607480081275706,
|
||||
-2.203769342520311,
|
||||
-0.6896328935259993
|
||||
]
|
||||
],
|
||||
'exponents': [
|
||||
10.256286070314905,
|
||||
0.6227965325875392,
|
||||
0.2391007667853915,
|
||||
33.710073211949485,
|
||||
6.180705022740464,
|
||||
1.7291385346152253,
|
||||
0.5940057549921978,
|
||||
0.2306698170449518,
|
||||
0.09500256906284119
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'center': [
|
||||
1.1367367844436005,
|
||||
-0.47018519422670163,
|
||||
-1.356802622574504
|
||||
] as Vec3,
|
||||
'shells': [
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
'coefficients': [
|
||||
[
|
||||
0.009163596280542963,
|
||||
0.04936149294292479,
|
||||
0.16853830490998634,
|
||||
0.37056279972195677,
|
||||
0.4164915298246781,
|
||||
0.13033408410772263
|
||||
]
|
||||
],
|
||||
'exponents': [
|
||||
33.710073211949485,
|
||||
6.180705022740464,
|
||||
1.7291385346152253,
|
||||
0.5940057549921978,
|
||||
0.2306698170449518,
|
||||
0.09500256906284119
|
||||
]
|
||||
},
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
'coefficients': [
|
||||
[
|
||||
-0.32279868167000036,
|
||||
3.209629817295221,
|
||||
2.4672629224617935,
|
||||
-0.048487066612842224,
|
||||
-0.2611850111200143,
|
||||
-0.8917817597810863,
|
||||
-1.9607480081275706,
|
||||
-2.203769342520311,
|
||||
-0.6896328935259993
|
||||
]
|
||||
],
|
||||
'exponents': [
|
||||
10.256286070314905,
|
||||
0.6227965325875392,
|
||||
0.2391007667853915,
|
||||
33.710073211949485,
|
||||
6.180705022740464,
|
||||
1.7291385346152253,
|
||||
0.5940057549921978,
|
||||
0.2306698170449518,
|
||||
0.09500256906284119
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
162
src/extensions/alpha-orbitals/collocation.ts
Normal file
162
src/extensions/alpha-orbitals/collocation.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Inspired by https://github.com/dgasmith/gau2grid.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { arrayMin } from '../../mol-util/array';
|
||||
import { AlphaOrbital, CubeGridInfo } from './data-model';
|
||||
import { normalizeBasicOrder, SphericalFunctions } from './spherical-functions';
|
||||
|
||||
export async function sphericalCollocation(
|
||||
grid: CubeGridInfo,
|
||||
orbital: AlphaOrbital,
|
||||
taskCtx: RuntimeContext
|
||||
) {
|
||||
const { basis, sphericalOrder, cutoffThreshold } = grid.params;
|
||||
let baseCount = 0;
|
||||
|
||||
for (const atom of basis.atoms) {
|
||||
for (const shell of atom.shells) {
|
||||
for (const L of shell.angularMomentum) {
|
||||
if (L > 4) {
|
||||
// TODO: will L > 4 be required? Would need to precompute more functions in that case.
|
||||
throw new Error('Angular momentum L > 4 not supported.');
|
||||
}
|
||||
|
||||
baseCount += 2 * L + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const matrix = new Float32Array(grid.npoints);
|
||||
|
||||
let baseIndex = 0;
|
||||
for (const atom of basis.atoms) {
|
||||
for (const shell of atom.shells) {
|
||||
let amIndex = 0;
|
||||
for (const L of shell.angularMomentum) {
|
||||
const alpha = normalizeBasicOrder(
|
||||
L,
|
||||
orbital.alpha.slice(baseIndex, baseIndex + 2 * L + 1),
|
||||
sphericalOrder
|
||||
);
|
||||
baseIndex += 2 * L + 1;
|
||||
|
||||
collocationBasis(
|
||||
matrix,
|
||||
grid,
|
||||
L,
|
||||
shell.coefficients[amIndex++],
|
||||
shell.exponents,
|
||||
atom.center,
|
||||
cutoffThreshold,
|
||||
alpha
|
||||
);
|
||||
|
||||
if (taskCtx.shouldUpdate) {
|
||||
await taskCtx.update({
|
||||
message: 'Computing...',
|
||||
current: baseIndex,
|
||||
max: baseCount,
|
||||
isIndeterminate: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
function collocationBasis(
|
||||
matrix: Float32Array,
|
||||
grid: CubeGridInfo,
|
||||
L: number,
|
||||
coefficients: number[],
|
||||
exponents: number[],
|
||||
center: Vec3,
|
||||
cutoffThreshold: number,
|
||||
alpha: number[]
|
||||
) {
|
||||
const ncoeff = exponents.length;
|
||||
const sphericalFunc = SphericalFunctions[L];
|
||||
|
||||
const cx = center[0],
|
||||
cy = center[1],
|
||||
cz = center[2];
|
||||
const ny = grid.dimensions[1],
|
||||
nz = grid.dimensions[2];
|
||||
const gdx = grid.delta[0],
|
||||
gdy = grid.delta[1],
|
||||
gdz = grid.delta[2];
|
||||
const sx = grid.box.min[0],
|
||||
sy = grid.box.min[1],
|
||||
sz = grid.box.min[2];
|
||||
|
||||
const cutoffRadius =
|
||||
cutoffThreshold > 0
|
||||
? Math.sqrt(-Math.log(cutoffThreshold) / arrayMin(exponents))
|
||||
: 10000;
|
||||
const cutoffSquared = cutoffRadius * cutoffRadius;
|
||||
|
||||
const radiusBox = getRadiusBox(grid, center, cutoffRadius);
|
||||
const iMin = radiusBox[0][0],
|
||||
jMin = radiusBox[0][1],
|
||||
kMin = radiusBox[0][2];
|
||||
const iMax = radiusBox[1][0],
|
||||
jMax = radiusBox[1][1],
|
||||
kMax = radiusBox[1][2];
|
||||
|
||||
for (let i = iMin; i <= iMax; i++) {
|
||||
const x = sx + gdx * i - cx;
|
||||
const oX = i * ny * nz;
|
||||
|
||||
for (let j = jMin; j <= jMax; j++) {
|
||||
const y = sy + gdy * j - cy;
|
||||
const oY = oX + j * nz;
|
||||
for (let k = kMin; k <= kMax; k++) {
|
||||
const z = sz + gdz * k - cz;
|
||||
const R2 = x * x + y * y + z * z;
|
||||
|
||||
if (R2 > cutoffSquared) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let gaussianSum = 0;
|
||||
for (let c = 0; c < ncoeff; c++) {
|
||||
gaussianSum +=
|
||||
coefficients[c] * Math.exp(-exponents[c] * R2);
|
||||
}
|
||||
|
||||
const sphericalSum = L === 0 ? alpha[0] : sphericalFunc(alpha, x, y, z);
|
||||
|
||||
|
||||
matrix[k + oY] += gaussianSum * sphericalSum;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getRadiusBox(grid: CubeGridInfo, center: Vec3, radius: number) {
|
||||
const r = Vec3.create(radius, radius, radius);
|
||||
const min = Vec3.scaleAndAdd(Vec3(), center, r, -1);
|
||||
const max = Vec3.add(Vec3(), center, r);
|
||||
|
||||
Vec3.sub(min, min, grid.box.min);
|
||||
Vec3.sub(max, max, grid.box.min);
|
||||
|
||||
Vec3.div(min, min, grid.delta);
|
||||
Vec3.floor(min, min);
|
||||
Vec3.max(min, min, Vec3());
|
||||
|
||||
Vec3.div(max, max, grid.delta);
|
||||
Vec3.ceil(max, max);
|
||||
Vec3.min(max, max, Vec3.subScalar(Vec3(), grid.dimensions, 1));
|
||||
|
||||
return [min, max];
|
||||
}
|
||||
143
src/extensions/alpha-orbitals/data-model.ts
Normal file
143
src/extensions/alpha-orbitals/data-model.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Mat4, Tensor, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Grid } from '../../mol-model/volume';
|
||||
import { SphericalBasisOrder } from './spherical-functions';
|
||||
import { Box3D, RegularGrid3d } from '../../mol-math/geometry';
|
||||
import { arrayMin, arrayMax, arrayRms, arrayMean } from '../../mol-util/array';
|
||||
import { ModelFormat } from '../../mol-model-formats/format';
|
||||
|
||||
// Note: generally contracted gaussians are currently not supported.
|
||||
export interface SphericalElectronShell {
|
||||
exponents: number[];
|
||||
angularMomentum: number[];
|
||||
// number[] for each angular momentum
|
||||
coefficients: number[][];
|
||||
}
|
||||
|
||||
export interface Basis {
|
||||
atoms: {
|
||||
// in Bohr units!
|
||||
center: Vec3;
|
||||
shells: SphericalElectronShell[];
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface AlphaOrbital {
|
||||
energy: number;
|
||||
occupancy: number;
|
||||
alpha: number[];
|
||||
}
|
||||
|
||||
export interface CubeGridComputationParams {
|
||||
basis: Basis;
|
||||
/**
|
||||
* for each electron shell compute a cutoff radius as
|
||||
* const cutoffRadius = Math.sqrt(-Math.log(cutoffThreshold) / arrayMin(exponents));
|
||||
*/
|
||||
cutoffThreshold: number;
|
||||
sphericalOrder: SphericalBasisOrder;
|
||||
boxExpand: number;
|
||||
gridSpacing: number | [atomCountThreshold: number, spacing: number][];
|
||||
doNotComputeIsovalues?: boolean;
|
||||
}
|
||||
|
||||
export interface CubeGridInfo {
|
||||
params: CubeGridComputationParams;
|
||||
dimensions: Vec3;
|
||||
box: Box3D;
|
||||
size: Vec3;
|
||||
npoints: number;
|
||||
delta: Vec3;
|
||||
}
|
||||
|
||||
export interface CubeGrid {
|
||||
grid: Grid;
|
||||
isovalues?: { negative?: number; positive?: number };
|
||||
}
|
||||
|
||||
export type CubeGridFormat = ModelFormat<CubeGrid>;
|
||||
|
||||
// eslint-disable-next-line
|
||||
export function CubeGridFormat(grid: CubeGrid): CubeGridFormat {
|
||||
return { name: 'custom grid', kind: 'cube-grid', data: grid };
|
||||
}
|
||||
|
||||
export function isCubeGridData(f: ModelFormat): f is CubeGridFormat {
|
||||
return f.kind === 'cube-grid';
|
||||
}
|
||||
|
||||
export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
|
||||
const geometry = params.basis.atoms.map(a => a.center);
|
||||
const { gridSpacing: spacing, boxExpand: expand } = params;
|
||||
|
||||
const count = geometry.length;
|
||||
const box = Box3D.expand(
|
||||
Box3D(),
|
||||
Box3D.fromVec3Array(Box3D(), geometry),
|
||||
Vec3.create(expand, expand, expand)
|
||||
);
|
||||
const size = Box3D.size(Vec3(), box);
|
||||
|
||||
const spacingThresholds =
|
||||
typeof spacing === 'number' ? [[0, spacing]] : [...spacing];
|
||||
spacingThresholds.sort((a, b) => b[0] - a[0]);
|
||||
|
||||
let s = 0.4;
|
||||
for (let i = 0; i <= spacingThresholds.length; i++) {
|
||||
s = spacingThresholds[i][1];
|
||||
if (spacingThresholds[i][0] <= count) break;
|
||||
}
|
||||
|
||||
const dimensions = Vec3.ceil(Vec3(), Vec3.scale(Vec3(), size, 1 / s));
|
||||
|
||||
return {
|
||||
params,
|
||||
box,
|
||||
dimensions,
|
||||
size,
|
||||
npoints: dimensions[0] * dimensions[1] * dimensions[2],
|
||||
delta: Vec3.div(Vec3(), size, Vec3.subScalar(Vec3(), dimensions, 1)),
|
||||
};
|
||||
}
|
||||
|
||||
const BohrToAngstromFactor = 0.529177210859;
|
||||
|
||||
export function createGrid(gridInfo: RegularGrid3d, values: Float32Array, axisOrder: number[]) {
|
||||
const boxSize = Box3D.size(Vec3(), gridInfo.box);
|
||||
const boxOrigin = Vec3.clone(gridInfo.box.min);
|
||||
|
||||
Vec3.scale(boxSize, boxSize, BohrToAngstromFactor);
|
||||
Vec3.scale(boxOrigin, boxOrigin, BohrToAngstromFactor);
|
||||
|
||||
const scale = Mat4.fromScaling(
|
||||
Mat4(),
|
||||
Vec3.div(
|
||||
Vec3(),
|
||||
boxSize,
|
||||
Vec3.sub(Vec3(), gridInfo.dimensions, Vec3.create(1, 1, 1))
|
||||
)
|
||||
);
|
||||
const translate = Mat4.fromTranslation(Mat4(), boxOrigin);
|
||||
const matrix = Mat4.mul(Mat4(), translate, scale);
|
||||
|
||||
const grid: Grid = {
|
||||
transform: { kind: 'matrix', matrix },
|
||||
cells: Tensor.create(
|
||||
Tensor.Space(gridInfo.dimensions, axisOrder, Float32Array),
|
||||
(values as any) as Tensor.Data
|
||||
),
|
||||
stats: {
|
||||
min: arrayMin(values),
|
||||
max: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: arrayRms(values),
|
||||
},
|
||||
};
|
||||
|
||||
return grid;
|
||||
}
|
||||
124
src/extensions/alpha-orbitals/density.ts
Normal file
124
src/extensions/alpha-orbitals/density.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { sortArray } from '../../mol-data/util';
|
||||
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Task } from '../../mol-task';
|
||||
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
|
||||
import { gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
|
||||
|
||||
export function createSphericalCollocationDensityGrid(
|
||||
params: CubeGridComputationParams, orbitals: AlphaOrbital[], webgl?: WebGLContext
|
||||
): Task<CubeGrid> {
|
||||
return Task.create('Spherical Collocation Grid', async (ctx) => {
|
||||
const cubeGrid = initCubeGrid(params);
|
||||
|
||||
let matrix: Float32Array;
|
||||
if (canComputeGrid3dOnGPU(webgl)) {
|
||||
// console.time('gpu');
|
||||
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(ctx, webgl!, cubeGrid, orbitals);
|
||||
// console.timeEnd('gpu');
|
||||
} else {
|
||||
throw new Error('Missing OES_texture_float WebGL extension.');
|
||||
}
|
||||
|
||||
const grid = createGrid(cubeGrid, matrix, [0, 1, 2]);
|
||||
let isovalues: { negative?: number, positive?: number } | undefined;
|
||||
|
||||
if (!params.doNotComputeIsovalues) {
|
||||
isovalues = computeDensityIsocontourValues(matrix, 0.85);
|
||||
}
|
||||
|
||||
return { grid, isovalues };
|
||||
});
|
||||
}
|
||||
|
||||
export function computeDensityIsocontourValues(input: Float32Array, cumulativeThreshold: number) {
|
||||
let weightSum = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = Math.abs(v);
|
||||
weightSum += w;
|
||||
}
|
||||
const avgWeight = weightSum / input.length;
|
||||
let minWeight = 3 * avgWeight;
|
||||
|
||||
// do not try to identify isovalues for degenerate data
|
||||
// e.g. all values are almost same
|
||||
if (Math.abs(avgWeight - input[0] * input[0]) < 1e-5) {
|
||||
return { negative: void 0, positive: void 0 };
|
||||
}
|
||||
|
||||
let size = 0;
|
||||
while (true) {
|
||||
let csum = 0;
|
||||
size = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = Math.abs(v);
|
||||
if (w >= minWeight) {
|
||||
csum += w;
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
if (csum / weightSum > cumulativeThreshold) {
|
||||
break;
|
||||
}
|
||||
|
||||
minWeight -= avgWeight;
|
||||
}
|
||||
|
||||
const values = new Float32Array(size);
|
||||
const weights = new Float32Array(size);
|
||||
const indices = new Int32Array(size);
|
||||
|
||||
let o = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = Math.abs(v);
|
||||
if (w >= minWeight) {
|
||||
values[o] = v;
|
||||
weights[o] = w;
|
||||
indices[o] = o;
|
||||
o++;
|
||||
}
|
||||
}
|
||||
|
||||
sortArray(
|
||||
indices,
|
||||
(indices, i, j) => weights[indices[j]] - weights[indices[i]]
|
||||
);
|
||||
|
||||
let cweight = 0,
|
||||
cutoffIndex = 0;
|
||||
for (let i = 0; i < size; i++) {
|
||||
cweight += weights[indices[i]];
|
||||
|
||||
if (cweight / weightSum >= cumulativeThreshold) {
|
||||
cutoffIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let positive = Number.POSITIVE_INFINITY,
|
||||
negative = Number.NEGATIVE_INFINITY;
|
||||
|
||||
for (let i = 0; i < cutoffIndex; i++) {
|
||||
const v = values[indices[i]];
|
||||
if (v > 0) {
|
||||
if (v < positive) positive = v;
|
||||
} else if (v < 0) {
|
||||
if (v > negative) negative = v;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
negative: negative !== Number.NEGATIVE_INFINITY ? negative : void 0,
|
||||
positive: positive !== Number.POSITIVE_INFINITY ? positive : void 0,
|
||||
};
|
||||
}
|
||||
170
src/extensions/alpha-orbitals/gpu/compute.ts
Normal file
170
src/extensions/alpha-orbitals/gpu/compute.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { createGrid3dComputeRenderable } from '../../../mol-gl/compute/grid3d';
|
||||
import { TextureSpec, UnboxedValues, UniformSpec } from '../../../mol-gl/renderable/schema';
|
||||
import { WebGLContext } from '../../../mol-gl/webgl/context';
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { arrayMin } from '../../../mol-util/array';
|
||||
import { AlphaOrbital, Basis, CubeGridInfo } from '../data-model';
|
||||
import { normalizeBasicOrder, SphericalBasisOrder } from '../spherical-functions';
|
||||
import { MAIN, UTILS } from './shader.frag';
|
||||
|
||||
const Schema = {
|
||||
tCenters: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
|
||||
tInfo: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
|
||||
tCoeff: TextureSpec('image-float32', 'rgb', 'float', 'nearest'),
|
||||
tAlpha: TextureSpec('image-float32', 'alpha', 'float', 'nearest'),
|
||||
uNCenters: UniformSpec('i'),
|
||||
uNAlpha: UniformSpec('i'),
|
||||
uNCoeff: UniformSpec('i'),
|
||||
uMaxCoeffs: UniformSpec('i'),
|
||||
};
|
||||
|
||||
const Orbitals = createGrid3dComputeRenderable({
|
||||
schema: Schema,
|
||||
loopBounds: ['uNCenters', 'uMaxCoeffs'],
|
||||
mainCode: MAIN,
|
||||
utilCode: UTILS,
|
||||
returnCode: 'v',
|
||||
values(params: { grid: CubeGridInfo, orbital: AlphaOrbital }) {
|
||||
return createTextureData(params.grid, params.orbital);
|
||||
}
|
||||
});
|
||||
|
||||
const Density = createGrid3dComputeRenderable({
|
||||
schema: {
|
||||
...Schema,
|
||||
uOccupancy: UniformSpec('f'),
|
||||
},
|
||||
loopBounds: ['uNCenters', 'uMaxCoeffs'],
|
||||
mainCode: MAIN,
|
||||
utilCode: UTILS,
|
||||
returnCode: 'current + uOccupancy * v * v',
|
||||
values(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
|
||||
return {
|
||||
...createTextureData(params.grid, params.orbitals[0]),
|
||||
uOccupancy: 0
|
||||
};
|
||||
},
|
||||
cumulative: {
|
||||
states(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
|
||||
return params.orbitals.filter(o => o.occupancy !== 0);
|
||||
},
|
||||
update({ grid }, state: AlphaOrbital, values) {
|
||||
const alpha = getNormalizedAlpha(grid.params.basis, state.alpha, grid.params.sphericalOrder);
|
||||
ValueCell.updateIfChanged(values.uOccupancy, state.occupancy);
|
||||
ValueCell.update(values.tAlpha, { width: alpha.length, height: 1, array: alpha });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export function gpuComputeAlphaOrbitalsGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital) {
|
||||
return Orbitals(ctx, webgl, grid, { grid, orbital });
|
||||
}
|
||||
|
||||
export function gpuComputeAlphaOrbitalsDensityGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbitals: AlphaOrbital[]) {
|
||||
return Density(ctx, webgl, grid, { grid, orbitals });
|
||||
}
|
||||
|
||||
function getNormalizedAlpha(basis: Basis, alphaOrbitals: number[], sphericalOrder: SphericalBasisOrder) {
|
||||
const alpha = new Float32Array(alphaOrbitals.length);
|
||||
|
||||
let aO = 0;
|
||||
for (const atom of basis.atoms) {
|
||||
for (const shell of atom.shells) {
|
||||
for (const L of shell.angularMomentum) {
|
||||
const a0 = normalizeBasicOrder(L, alphaOrbitals.slice(aO, aO + 2 * L + 1), sphericalOrder);
|
||||
for (let i = 0; i < a0.length; i++) alpha[aO + i] = a0[i];
|
||||
aO += 2 * L + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return alpha;
|
||||
}
|
||||
|
||||
function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital): UnboxedValues<typeof Schema> {
|
||||
const { basis, sphericalOrder, cutoffThreshold } = grid.params;
|
||||
|
||||
let centerCount = 0;
|
||||
let baseCount = 0;
|
||||
let coeffCount = 0;
|
||||
for (const atom of basis.atoms) {
|
||||
for (const shell of atom.shells) {
|
||||
for (const L of shell.angularMomentum) {
|
||||
if (L > 4) {
|
||||
// TODO: will L > 4 be required? Would need to precompute more functions in that case.
|
||||
throw new Error('Angular momentum L > 4 not supported.');
|
||||
}
|
||||
|
||||
centerCount++;
|
||||
baseCount += 2 * L + 1;
|
||||
coeffCount += shell.exponents.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const centers = new Float32Array(4 * centerCount);
|
||||
// L, alpha_offset, coeff_offset_start, coeff_offset_end
|
||||
const info = new Float32Array(4 * centerCount);
|
||||
const alpha = new Float32Array(baseCount);
|
||||
const coeff = new Float32Array(3 * coeffCount);
|
||||
|
||||
let maxCoeffs = 0;
|
||||
let cO = 0, aO = 0, coeffO = 0;
|
||||
for (const atom of basis.atoms) {
|
||||
for (const shell of atom.shells) {
|
||||
|
||||
let amIndex = 0;
|
||||
for (const L of shell.angularMomentum) {
|
||||
const a0 = normalizeBasicOrder(L, orbital.alpha.slice(aO, aO + 2 * L + 1), sphericalOrder);
|
||||
|
||||
const cutoffRadius = cutoffThreshold > 0
|
||||
? Math.sqrt(-Math.log(cutoffThreshold) / arrayMin(shell.exponents))
|
||||
: 10000;
|
||||
|
||||
centers[4 * cO + 0] = atom.center[0];
|
||||
centers[4 * cO + 1] = atom.center[1];
|
||||
centers[4 * cO + 2] = atom.center[2];
|
||||
centers[4 * cO + 3] = cutoffRadius * cutoffRadius;
|
||||
|
||||
info[4 * cO + 0] = L;
|
||||
info[4 * cO + 1] = aO;
|
||||
info[4 * cO + 2] = coeffO;
|
||||
info[4 * cO + 3] = coeffO + shell.exponents.length;
|
||||
|
||||
for (let i = 0; i < a0.length; i++) alpha[aO + i] = a0[i];
|
||||
|
||||
const c0 = shell.coefficients[amIndex++];
|
||||
for (let i = 0; i < shell.exponents.length; i++) {
|
||||
coeff[3 * (coeffO + i) + 0] = c0[i];
|
||||
coeff[3 * (coeffO + i) + 1] = shell.exponents[i];
|
||||
}
|
||||
|
||||
if (c0.length > maxCoeffs) {
|
||||
maxCoeffs = c0.length;
|
||||
}
|
||||
|
||||
cO++;
|
||||
aO += 2 * L + 1;
|
||||
coeffO += shell.exponents.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
uNCenters: centerCount,
|
||||
uNAlpha: baseCount,
|
||||
uNCoeff: coeffCount,
|
||||
uMaxCoeffs: maxCoeffs,
|
||||
tCenters: { width: centerCount, height: 1, array: centers },
|
||||
tInfo: { width: centerCount, height: 1, array: info },
|
||||
tCoeff: { width: coeffCount, height: 1, array: coeff },
|
||||
tAlpha: { width: baseCount, height: 1, array: alpha },
|
||||
};
|
||||
}
|
||||
145
src/extensions/alpha-orbitals/gpu/shader.frag.ts
Normal file
145
src/extensions/alpha-orbitals/gpu/shader.frag.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
export const UTILS = `
|
||||
float L1(vec3 p, float a0, float a1, float a2) {
|
||||
return a0 * p.z + a1 * p.x + a2 * p.y;
|
||||
}
|
||||
|
||||
float L2(vec3 p, float a0, float a1, float a2, float a3, float a4) {
|
||||
float x = p.x, y = p.y, z = p.z;
|
||||
float xx = x * x, yy = y * y, zz = z * z;
|
||||
return (
|
||||
a0 * (-0.5 * xx - 0.5 * yy + zz) +
|
||||
a1 * (1.7320508075688772 * x * z) +
|
||||
a2 * (1.7320508075688772 * y * z) +
|
||||
a3 * (0.8660254037844386 * xx - 0.8660254037844386 * yy) +
|
||||
a4 * (1.7320508075688772 * x * y)
|
||||
);
|
||||
}
|
||||
|
||||
float L3(vec3 p, float a0, float a1, float a2, float a3, float a4, float a5, float a6) {
|
||||
float x = p.x, y = p.y, z = p.z;
|
||||
float xx = x * x, yy = y * y, zz = z * z;
|
||||
float xxx = xx * x, yyy = yy * y, zzz = zz * z;
|
||||
return (
|
||||
a0 * (-1.5 * xx * z - 1.5 * yy * z + zzz) +
|
||||
a1 * (-0.6123724356957945 * xxx - 0.6123724356957945 * x * yy + 2.449489742783178 * x * zz) +
|
||||
a2 * (-0.6123724356957945 * xx * y - 0.6123724356957945 * yyy + 2.449489742783178 * y * zz) +
|
||||
a3 * (1.9364916731037085 * xx * z - 1.9364916731037085 * yy * z) +
|
||||
a4 * (3.872983346207417 * x * y * z) +
|
||||
a5 * (0.7905694150420949 * xxx - 2.3717082451262845 * x * yy) +
|
||||
a6 * (2.3717082451262845 * xx * y - 0.7905694150420949 * yyy)
|
||||
);
|
||||
}
|
||||
|
||||
float L4(vec3 p, float a0, float a1, float a2, float a3, float a4, float a5, float a6, float a7, float a8) {
|
||||
float x = p.x, y = p.y, z = p.z;
|
||||
float xx = x * x, yy = y * y, zz = z * z;
|
||||
float xxx = xx * x, yyy = yy * y, zzz = zz * z;
|
||||
float xxxx = xxx * x, yyyy = yyy * y, zzzz = zzz * z;
|
||||
return (
|
||||
a0 * (0.375 * xxxx + 0.75 * xx * yy + 0.375 * yyyy - 3.0 * xx * zz - 3.0 * yy * zz + zzzz) +
|
||||
a1 * (-2.3717082451262845 * xxx * z - 2.3717082451262845 * x * yy * z + 3.1622776601683795 * x * zzz) +
|
||||
a2 * (-2.3717082451262845 * xx * y * z - 2.3717082451262845 * yyy * z + 3.1622776601683795 * y * zzz) +
|
||||
a3 * (-0.5590169943749475 * xxxx + 0.5590169943749475 * yyyy + 3.3541019662496847 * xx * zz - 3.3541019662496847 * yy * zz) +
|
||||
a4 * (-1.118033988749895 * xxx * y - 1.118033988749895 * x * yyy + 6.708203932499369 * x * y * zz) +
|
||||
a5 * (2.091650066335189 * xxx * z + -6.274950199005566 * x * yy * z) +
|
||||
a6 * (6.274950199005566 * xx * y * z + -2.091650066335189 * yyy * z) +
|
||||
a7 * (0.739509972887452 * xxxx - 4.437059837324712 * xx * yy + 0.739509972887452 * yyyy) +
|
||||
a8 * (2.958039891549808 * xxx * y + -2.958039891549808 * x * yyy)
|
||||
);
|
||||
}
|
||||
|
||||
float alpha(float offset, float f) {
|
||||
#ifdef WEBGL1
|
||||
// in webgl1, the value is in the alpha channel!
|
||||
return texture2D(tAlpha, vec2(offset * f, 0.5)).a;
|
||||
#else
|
||||
return texture2D(tAlpha, vec2(offset * f, 0.5)).x;
|
||||
#endif
|
||||
}
|
||||
|
||||
float Y(int L, vec3 X, float aO, float fA) {
|
||||
if (L == 0) {
|
||||
return alpha(aO, fA);
|
||||
} else if (L == 1) {
|
||||
return L1(X,
|
||||
alpha(aO, fA), alpha(aO + 1.0, fA), alpha(aO + 2.0, fA)
|
||||
);
|
||||
} else if (L == 2) {
|
||||
return L2(X,
|
||||
alpha(aO, fA), alpha(aO + 1.0, fA), alpha(aO + 2.0, fA), alpha(aO + 3.0, fA), alpha(aO + 4.0, fA)
|
||||
);
|
||||
} else if (L == 3) {
|
||||
return L3(X,
|
||||
alpha(aO, fA), alpha(aO + 1.0, fA), alpha(aO + 2.0, fA), alpha(aO + 3.0, fA), alpha(aO + 4.0, fA),
|
||||
alpha(aO + 5.0, fA), alpha(aO + 6.0, fA)
|
||||
);
|
||||
} else if (L == 4) {
|
||||
return L4(X,
|
||||
alpha(aO, fA), alpha(aO + 1.0, fA), alpha(aO + 2.0, fA), alpha(aO + 3.0, fA), alpha(aO + 4.0, fA),
|
||||
alpha(aO + 5.0, fA), alpha(aO + 6.0, fA), alpha(aO + 7.0, fA), alpha(aO + 8.0, fA)
|
||||
);
|
||||
}
|
||||
// TODO: do we need L > 4?
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
#ifndef WEBGL1
|
||||
float R(float R2, int start, int end, float fCoeff) {
|
||||
float gauss = 0.0;
|
||||
for (int i = start; i < end; i++) {
|
||||
vec2 c = texture2D(tCoeff, vec2(float(i) * fCoeff, 0.5)).xy;
|
||||
gauss += c.x * exp(-c.y * R2);
|
||||
}
|
||||
return gauss;
|
||||
}
|
||||
#else
|
||||
float R(float R2, int start, int end, float fCoeff) {
|
||||
float gauss = 0.0;
|
||||
int o = start;
|
||||
for (int i = 0; i < uMaxCoeffs; i++) {
|
||||
if (o >= end) break;
|
||||
|
||||
vec2 c = texture2D(tCoeff, vec2(float(o) * fCoeff, 0.5)).xy;
|
||||
gauss += c.x * exp(-c.y * R2);
|
||||
o++;
|
||||
}
|
||||
return gauss;
|
||||
}
|
||||
#endif
|
||||
`;
|
||||
|
||||
export const MAIN = `
|
||||
float fCenter = 1.0 / float(uNCenters - 1);
|
||||
float fCoeff = 1.0 / float(uNCoeff - 1);
|
||||
float fA = 1.0 / float(uNAlpha - 1);
|
||||
|
||||
float v = 0.0;
|
||||
|
||||
for (int i = 0; i < uNCenters; i++) {
|
||||
vec2 cCoord = vec2(float(i) * fCenter, 0.5);
|
||||
|
||||
vec4 center = texture2D(tCenters, cCoord);
|
||||
vec3 X = xyz - center.xyz;
|
||||
float R2 = dot(X, X);
|
||||
|
||||
// center.w is squared cutoff radius
|
||||
if (R2 > center.w) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vec4 info = texture2D(tInfo, cCoord);
|
||||
|
||||
int L = int(info.x);
|
||||
float aO = info.y;
|
||||
int coeffStart = int(info.z);
|
||||
int coeffEnd = int(info.w);
|
||||
|
||||
v += R(R2, coeffStart, coeffEnd, fCoeff) * Y(L, X, aO, fA);
|
||||
}
|
||||
`;
|
||||
131
src/extensions/alpha-orbitals/orbitals.ts
Normal file
131
src/extensions/alpha-orbitals/orbitals.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Inspired by https://github.com/dgasmith/gau2grid.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { sortArray } from '../../mol-data/util';
|
||||
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Task } from '../../mol-task';
|
||||
import { sphericalCollocation } from './collocation';
|
||||
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
|
||||
import { gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
|
||||
|
||||
// setDebugMode(true);
|
||||
|
||||
export function createSphericalCollocationGrid(
|
||||
params: CubeGridComputationParams, orbital: AlphaOrbital, webgl?: WebGLContext
|
||||
): Task<CubeGrid> {
|
||||
return Task.create('Spherical Collocation Grid', async (ctx) => {
|
||||
const cubeGrid = initCubeGrid(params);
|
||||
|
||||
let matrix: Float32Array;
|
||||
if (canComputeGrid3dOnGPU(webgl)) {
|
||||
// console.time('gpu');
|
||||
matrix = await gpuComputeAlphaOrbitalsGridValues(ctx, webgl!, cubeGrid, orbital);
|
||||
// console.timeEnd('gpu');
|
||||
} else {
|
||||
// console.time('cpu');
|
||||
matrix = await sphericalCollocation(cubeGrid, orbital, ctx);
|
||||
// console.timeEnd('cpu');
|
||||
}
|
||||
|
||||
const grid = createGrid(cubeGrid, matrix, [0, 1, 2]);
|
||||
let isovalues: { negative?: number, positive?: number } | undefined;
|
||||
|
||||
if (!params.doNotComputeIsovalues) {
|
||||
isovalues = computeOrbitalIsocontourValues(matrix, 0.85);
|
||||
}
|
||||
|
||||
return { grid, isovalues };
|
||||
});
|
||||
}
|
||||
|
||||
export function computeOrbitalIsocontourValues(input: Float32Array, cumulativeThreshold: number) {
|
||||
let weightSum = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = v * v;
|
||||
weightSum += w;
|
||||
}
|
||||
const avgWeight = weightSum / input.length;
|
||||
let minWeight = 3 * avgWeight;
|
||||
|
||||
// do not try to identify isovalues for degenerate data
|
||||
// e.g. all values are almost same
|
||||
if (Math.abs(avgWeight - input[0] * input[0]) < 1e-5) {
|
||||
return { negative: void 0, positive: void 0 };
|
||||
}
|
||||
|
||||
let size = 0;
|
||||
while (true) {
|
||||
let csum = 0;
|
||||
size = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = v * v;
|
||||
if (w >= minWeight) {
|
||||
csum += w;
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
if (csum / weightSum > cumulativeThreshold) {
|
||||
break;
|
||||
}
|
||||
|
||||
minWeight -= avgWeight;
|
||||
}
|
||||
|
||||
const values = new Float32Array(size);
|
||||
const weights = new Float32Array(size);
|
||||
const indices = new Int32Array(size);
|
||||
|
||||
let o = 0;
|
||||
for (let i = 0, _i = input.length; i < _i; i++) {
|
||||
const v = input[i];
|
||||
const w = v * v;
|
||||
if (w >= minWeight) {
|
||||
values[o] = v;
|
||||
weights[o] = w;
|
||||
indices[o] = o;
|
||||
o++;
|
||||
}
|
||||
}
|
||||
|
||||
sortArray(
|
||||
indices,
|
||||
(indices, i, j) => weights[indices[j]] - weights[indices[i]]
|
||||
);
|
||||
|
||||
let cweight = 0,
|
||||
cutoffIndex = 0;
|
||||
for (let i = 0; i < size; i++) {
|
||||
cweight += weights[indices[i]];
|
||||
|
||||
if (cweight / weightSum >= cumulativeThreshold) {
|
||||
cutoffIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let positive = Number.POSITIVE_INFINITY,
|
||||
negative = Number.NEGATIVE_INFINITY;
|
||||
|
||||
for (let i = 0; i < cutoffIndex; i++) {
|
||||
const v = values[indices[i]];
|
||||
if (v > 0) {
|
||||
if (v < positive) positive = v;
|
||||
} else if (v < 0) {
|
||||
if (v > negative) negative = v;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
negative: negative !== Number.NEGATIVE_INFINITY ? negative : void 0,
|
||||
positive: positive !== Number.POSITIVE_INFINITY ? positive : void 0,
|
||||
};
|
||||
}
|
||||
93
src/extensions/alpha-orbitals/spherical-functions.ts
Normal file
93
src/extensions/alpha-orbitals/spherical-functions.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Inspired by https://github.com/dgasmith/gau2grid.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
// gaussian:
|
||||
// R_0, R^+_1, R^-_1, ..., R^+_l, R^-_l
|
||||
// cca:
|
||||
// R^-_(l), R^-_(l-1), ..., R_0, ..., R^+_(l-1), R^+_l
|
||||
// cca-reverse:
|
||||
// R^+_(l), R^+_(l-1), ..., R_0, ..., R^-_(l-1), R^-_l
|
||||
export type SphericalBasisOrder = 'gaussian' | 'cca' | 'cca-reverse';
|
||||
|
||||
export function normalizeBasicOrder(
|
||||
L: number,
|
||||
alpha: number[],
|
||||
order: SphericalBasisOrder
|
||||
) {
|
||||
if (order === 'gaussian' || L === 0) return alpha;
|
||||
|
||||
const ret: number[] = [alpha[L]];
|
||||
for (let l = 0; l < L; l++) {
|
||||
const a = alpha[L - l - 1],
|
||||
b = alpha[L + l + 1];
|
||||
if (order === 'cca') ret.push(b, a);
|
||||
else ret.push(a, b);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
export type SphericalFunc = (
|
||||
alpha: number[],
|
||||
x: number,
|
||||
y: number,
|
||||
z: number
|
||||
) => number;
|
||||
|
||||
export const SphericalFunctions: SphericalFunc[] = [L0, L1, L2, L3, L4];
|
||||
|
||||
// L_i functions were auto-generated.
|
||||
|
||||
function L0(alpha: number[], x: number, y: number, z: number) {
|
||||
return alpha[0];
|
||||
}
|
||||
|
||||
function L1(alpha: number[], x: number, y: number, z: number) {
|
||||
return alpha[0] * z + alpha[1] * x + alpha[2] * y;
|
||||
}
|
||||
|
||||
function L2(alpha: number[], x: number, y: number, z: number) {
|
||||
const xx = x * x, yy = y * y, zz = z * z;
|
||||
return (
|
||||
alpha[0] * (-0.5 * xx - 0.5 * yy + zz) +
|
||||
alpha[1] * (1.7320508075688772 * x * z) +
|
||||
alpha[2] * (1.7320508075688772 * y * z) +
|
||||
alpha[3] * (0.8660254037844386 * xx - 0.8660254037844386 * yy) +
|
||||
alpha[4] * (1.7320508075688772 * x * y)
|
||||
);
|
||||
}
|
||||
|
||||
function L3(alpha: number[], x: number, y: number, z: number) {
|
||||
const xx = x * x, yy = y * y, zz = z * z;
|
||||
const xxx = xx * x, yyy = yy * y, zzz = zz * z;
|
||||
return (
|
||||
alpha[0] * (-1.5 * xx * z - 1.5 * yy * z + zzz) +
|
||||
alpha[1] * (-0.6123724356957945 * xxx - 0.6123724356957945 * x * yy + 2.449489742783178 * x * zz) +
|
||||
alpha[2] * (-0.6123724356957945 * xx * y - 0.6123724356957945 * yyy + 2.449489742783178 * y * zz) +
|
||||
alpha[3] * (1.9364916731037085 * xx * z - 1.9364916731037085 * yy * z) +
|
||||
alpha[4] * (3.872983346207417 * x * y * z) +
|
||||
alpha[5] * (0.7905694150420949 * xxx - 2.3717082451262845 * x * yy) +
|
||||
alpha[6] * (2.3717082451262845 * xx * y - 0.7905694150420949 * yyy)
|
||||
);
|
||||
}
|
||||
|
||||
function L4(alpha: number[], x: number, y: number, z: number) {
|
||||
const xx = x * x, yy = y * y, zz = z * z;
|
||||
const xxx = xx * x, yyy = yy * y, zzz = zz * z;
|
||||
const xxxx = xxx * x, yyyy = yyy * y, zzzz = zzz * z;
|
||||
return (
|
||||
alpha[0] * (0.375 * xxxx + 0.75 * xx * yy + 0.375 * yyyy - 3.0 * xx * zz - 3.0 * yy * zz + zzzz) +
|
||||
alpha[1] * (-2.3717082451262845 * xxx * z - 2.3717082451262845 * x * yy * z + 3.1622776601683795 * x * zzz) +
|
||||
alpha[2] * (-2.3717082451262845 * xx * y * z - 2.3717082451262845 * yyy * z + 3.1622776601683795 * y * zzz) +
|
||||
alpha[3] * (-0.5590169943749475 * xxxx + 0.5590169943749475 * yyyy + 3.3541019662496847 * xx * zz - 3.3541019662496847 * yy * zz) +
|
||||
alpha[4] * (-1.118033988749895 * xxx * y - 1.118033988749895 * x * yyy + 6.708203932499369 * x * y * zz) +
|
||||
alpha[5] * (2.091650066335189 * xxx * z + -6.274950199005566 * x * yy * z) +
|
||||
alpha[6] * (6.274950199005566 * xx * y * z + -2.091650066335189 * yyy * z) +
|
||||
alpha[7] * (0.739509972887452 * xxxx - 4.437059837324712 * xx * yy + 0.739509972887452 * yyyy) +
|
||||
alpha[8] * (2.958039891549808 * xxx * y + -2.958039891549808 * x * yyy)
|
||||
);
|
||||
}
|
||||
225
src/extensions/alpha-orbitals/transforms.ts
Normal file
225
src/extensions/alpha-orbitals/transforms.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginStateObject, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
import { createSphericalCollocationGrid } from './orbitals';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Task } from '../../mol-task';
|
||||
import { CustomProperties } from '../../mol-model/custom-property';
|
||||
import { SphericalBasisOrder } from './spherical-functions';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
|
||||
import { StateTransformer } from '../../mol-state';
|
||||
import { Theme } from '../../mol-theme/theme';
|
||||
import { VolumeRepresentation3DHelpers } from '../../mol-plugin-state/transforms/representation';
|
||||
import { AlphaOrbital, Basis, CubeGrid, CubeGridFormat, isCubeGridData } from './data-model';
|
||||
import { createSphericalCollocationDensityGrid } from './density';
|
||||
import { Tensor } from '../../mol-math/linear-algebra';
|
||||
|
||||
export class BasisAndOrbitals extends PluginStateObject.Create<{ basis: Basis, order: SphericalBasisOrder, orbitals: AlphaOrbital[] }>({ name: 'Basis', typeClass: 'Object' }) { }
|
||||
|
||||
export const StaticBasisAndOrbitals = PluginStateTransform.BuiltIn({
|
||||
name: 'static-basis-and-orbitals',
|
||||
display: 'Basis and Orbitals',
|
||||
from: PluginStateObject.Root,
|
||||
to: BasisAndOrbitals,
|
||||
params: {
|
||||
label: PD.Text('Orbital Data', { isHidden: true }),
|
||||
basis: PD.Value<Basis>(void 0 as any, { isHidden: true }),
|
||||
order: PD.Text<SphericalBasisOrder>('gaussian' as SphericalBasisOrder, { isHidden: true }),
|
||||
orbitals: PD.Value<AlphaOrbital[]>([], { isHidden: true })
|
||||
},
|
||||
})({
|
||||
apply({ params }) {
|
||||
return new BasisAndOrbitals({ basis: params.basis, order: params.order, orbitals: params.orbitals }, { label: params.label });
|
||||
}
|
||||
});
|
||||
|
||||
const CreateOrbitalVolumeParamBase = {
|
||||
cutoffThreshold: PD.Numeric(0.0015, { min: 0, max: 0.1, step: 0.0001 }),
|
||||
boxExpand: PD.Numeric(4.5, { min: 0, max: 7, step: 0.1 }),
|
||||
gridSpacing: PD.ObjectList({ atomCount: PD.Numeric(0), spacing: PD.Numeric(0.35, { min: 0.1, max: 2, step: 0.01 }) }, e => `Atoms ${e.atomCount}: ${e.spacing}`, {
|
||||
defaultValue: [
|
||||
{ atomCount: 55, spacing: 0.5 },
|
||||
{ atomCount: 40, spacing: 0.45 },
|
||||
{ atomCount: 25, spacing: 0.4 },
|
||||
{ atomCount: 0, spacing: 0.35 },
|
||||
]
|
||||
}),
|
||||
clampValues: PD.MappedStatic('off', {
|
||||
off: PD.EmptyGroup(),
|
||||
on: PD.Group({
|
||||
sigma: PD.Numeric(8, { min: 1, max: 20 }, { description: 'Clamp values to range [sigma * negIsoValue, sigma * posIsoValue].' })
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
function clampData(matrix: Tensor.Data, min: number, max: number) {
|
||||
for (let i = 0, _i = matrix.length; i < _i; i++) {
|
||||
const v = matrix[i];
|
||||
if (v < min) matrix[i] = min;
|
||||
else if (v > max) matrix[i] = max;
|
||||
}
|
||||
}
|
||||
|
||||
function clampGrid(data: CubeGrid, v: number) {
|
||||
const grid = data.grid;
|
||||
const min = (data.isovalues?.negative ?? data.grid.stats.min) * v;
|
||||
const max = (data.isovalues?.positive ?? data.grid.stats.max) * v;
|
||||
|
||||
// clamp values for better direct volume resolution
|
||||
// current implementation uses Byte array for values
|
||||
// if this is not enough, update mol* to use float
|
||||
// textures instead
|
||||
if (grid.stats.min < min || grid.stats.max > max) {
|
||||
clampData(data.grid.cells.data, min, max);
|
||||
if (grid.stats.min < min) {
|
||||
(grid.stats.min as number) = min;
|
||||
}
|
||||
if (grid.stats.max > max) {
|
||||
(grid.stats.max as number) = max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
|
||||
name: 'create-orbital-volume',
|
||||
display: 'Orbital Volume',
|
||||
from: BasisAndOrbitals,
|
||||
to: PluginStateObject.Volume.Data,
|
||||
params: (a) => {
|
||||
if (!a) {
|
||||
return { index: PD.Numeric(0), ...CreateOrbitalVolumeParamBase };
|
||||
}
|
||||
|
||||
return {
|
||||
index: PD.Select(0, a.data.orbitals.map((o, i) => [i, `[${i + 1}] ${o.energy.toFixed(4)}`])),
|
||||
...CreateOrbitalVolumeParamBase
|
||||
};
|
||||
}
|
||||
})({
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Orbital Volume', async ctx => {
|
||||
const data = await createSphericalCollocationGrid({
|
||||
basis: a.data.basis,
|
||||
cutoffThreshold: params.cutoffThreshold,
|
||||
sphericalOrder: a.data.order,
|
||||
boxExpand: params.boxExpand,
|
||||
gridSpacing: params.gridSpacing.map(e => [e.atomCount, e.spacing] as [number, number])
|
||||
}, a.data.orbitals[params.index], plugin.canvas3d?.webgl).runInContext(ctx);
|
||||
const volume: Volume = {
|
||||
grid: data.grid,
|
||||
sourceData: CubeGridFormat(data),
|
||||
customProperties: new CustomProperties(),
|
||||
_propertyData: Object.create(null),
|
||||
};
|
||||
|
||||
if (params.clampValues?.name === 'on') {
|
||||
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
|
||||
}
|
||||
|
||||
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const CreateOrbitalDensityVolume = PluginStateTransform.BuiltIn({
|
||||
name: 'create-orbital-density-volume',
|
||||
display: 'Orbital Density Volume',
|
||||
from: BasisAndOrbitals,
|
||||
to: PluginStateObject.Volume.Data,
|
||||
params: CreateOrbitalVolumeParamBase
|
||||
})({
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Orbital Volume', async ctx => {
|
||||
const data = await createSphericalCollocationDensityGrid({
|
||||
basis: a.data.basis,
|
||||
cutoffThreshold: params.cutoffThreshold,
|
||||
sphericalOrder: a.data.order,
|
||||
boxExpand: params.boxExpand,
|
||||
gridSpacing: params.gridSpacing.map(e => [e.atomCount, e.spacing] as [number, number])
|
||||
}, a.data.orbitals, plugin.canvas3d?.webgl).runInContext(ctx);
|
||||
const volume: Volume = {
|
||||
grid: data.grid,
|
||||
sourceData: CubeGridFormat(data),
|
||||
customProperties: new CustomProperties(),
|
||||
_propertyData: Object.create(null),
|
||||
};
|
||||
|
||||
if (params.clampValues?.name === 'on') {
|
||||
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
|
||||
}
|
||||
|
||||
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
name: 'create-orbital-representation-3d',
|
||||
display: 'Orbital Representation 3D',
|
||||
from: PluginStateObject.Volume.Data,
|
||||
to: PluginStateObject.Volume.Representation3D,
|
||||
params: {
|
||||
relativeIsovalue: PD.Numeric(1, { min: 0.01, max: 5, step: 0.01 }),
|
||||
kind: PD.Select<'positive' | 'negative'>('positive', [['positive', 'Positive'], ['negative', 'Negative']]),
|
||||
color: PD.Color(ColorNames.blue),
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
xrayShaded: PD.Boolean(false),
|
||||
pickable: PD.Boolean(true),
|
||||
tryUseGpu: PD.Boolean(true)
|
||||
}
|
||||
})({
|
||||
canAutoUpdate() {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params: srcParams }, plugin: PluginContext) {
|
||||
return Task.create('Orbitals Representation 3D', async ctx => {
|
||||
const params = volumeParams(plugin, a, srcParams);
|
||||
|
||||
const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset };
|
||||
const provider = plugin.representation.volume.registry.get(params.type.name);
|
||||
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
|
||||
const props = params.type.params || {};
|
||||
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
|
||||
repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, params));
|
||||
await repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
repr.setState({ pickable: srcParams.pickable });
|
||||
return new PluginStateObject.Volume.Representation3D({ repr, sourceData: a.data }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
|
||||
});
|
||||
},
|
||||
update({ a, b, newParams: srcParams }, plugin: PluginContext) {
|
||||
return Task.create('Orbitals Representation 3D', async ctx => {
|
||||
const newParams = volumeParams(plugin, a, srcParams);
|
||||
|
||||
const props = { ...b.data.repr.props, ...newParams.type.params };
|
||||
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, newParams));
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
b.data.repr.setState({ pickable: srcParams.pickable });
|
||||
b.description = VolumeRepresentation3DHelpers.getDescription(props);
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function volumeParams(plugin: PluginContext, volume: PluginStateObject.Volume.Data, params: StateTransformer.Params<typeof CreateOrbitalRepresentation3D>) {
|
||||
if (!isCubeGridData(volume.data.sourceData)) throw new Error('Invalid data source kind.');
|
||||
|
||||
const { isovalues } = volume.data.sourceData.data;
|
||||
if (!isovalues) throw new Error('Isovalues are not computed.');
|
||||
|
||||
const value = isovalues[params.kind];
|
||||
|
||||
return createVolumeRepresentationParams(plugin, volume.data, {
|
||||
type: 'isosurface',
|
||||
typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded, tryUseGpu: params.tryUseGpu },
|
||||
color: 'uniform',
|
||||
colorParams: { value: params.color }
|
||||
});
|
||||
}
|
||||
@@ -5,10 +5,10 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Structure, StructureElement, StructureProperties } from '../../mol-model/structure';
|
||||
import { Structure, StructureElement, StructureProperties, Unit } from '../../mol-model/structure';
|
||||
import { Task, RuntimeContext } from '../../mol-task';
|
||||
import { CentroidHelper } from '../../mol-math/geometry/centroid-helper';
|
||||
import { AccessibleSurfaceAreaProvider } from '../../mol-model-props/computed/accessible-surface-area';
|
||||
import { AccessibleSurfaceAreaParams } from '../../mol-model-props/computed/accessible-surface-area';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { getElementMoleculeType } from '../../mol-model/structure/util';
|
||||
import { MoleculeType } from '../../mol-model/structure/model/types';
|
||||
@@ -16,6 +16,10 @@ import { AccessibleSurfaceArea } from '../../mol-model-props/computed/accessible
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { MembraneOrientation } from './prop';
|
||||
|
||||
const LARGE_CA_THRESHOLD = 5000;
|
||||
const DEFAULT_UPDATE_INTERVAL = 10;
|
||||
const LARGE_CA_UPDATE_INTERVAL = 1;
|
||||
|
||||
interface ANVILContext {
|
||||
structure: Structure,
|
||||
|
||||
@@ -24,29 +28,45 @@ interface ANVILContext {
|
||||
minThickness: number,
|
||||
maxThickness: number,
|
||||
asaCutoff: number,
|
||||
adjust: number,
|
||||
|
||||
offsets: ArrayLike<number>,
|
||||
exposed: ArrayLike<boolean>,
|
||||
exposed: ArrayLike<number>,
|
||||
hydrophobic: ArrayLike<boolean>,
|
||||
centroid: Vec3,
|
||||
extent: number
|
||||
extent: number,
|
||||
large: boolean
|
||||
};
|
||||
|
||||
export const ANVILParams = {
|
||||
numberOfSpherePoints: PD.Numeric(120, { min: 35, max: 700, step: 1 }, { description: 'Number of spheres/directions to test for membrane placement. Original value is 350.' }),
|
||||
numberOfSpherePoints: PD.Numeric(175, { min: 35, max: 700, step: 1 }, { description: 'Number of spheres/directions to test for membrane placement. Original value is 350.' }),
|
||||
stepSize: PD.Numeric(1, { min: 0.25, max: 4, step: 0.25 }, { description: 'Thickness of membrane slices that will be tested' }),
|
||||
minThickness: PD.Numeric(20, { min: 10, max: 30, step: 1}, { description: 'Minimum membrane thickness used during refinement' }),
|
||||
maxThickness: PD.Numeric(40, { min: 30, max: 50, step: 1}, { description: 'Maximum membrane thickness used during refinement' }),
|
||||
asaCutoff: PD.Numeric(40, { min: 10, max: 100, step: 1 }, { description: 'Absolute ASA cutoff above which residues will be considered' })
|
||||
minThickness: PD.Numeric(20, { min: 10, max: 30, step: 1 }, { description: 'Minimum membrane thickness used during refinement' }),
|
||||
maxThickness: PD.Numeric(40, { min: 30, max: 50, step: 1 }, { description: 'Maximum membrane thickness used during refinement' }),
|
||||
asaCutoff: PD.Numeric(40, { min: 10, max: 100, step: 1 }, { description: 'Relative ASA cutoff above which residues will be considered' }),
|
||||
adjust: PD.Numeric(14, { min: 0, max: 30, step: 1 }, { description: 'Minimum length of membrane-spanning regions (original values: 14 for alpha-helices and 5 for beta sheets). Set to 0 to not optimize membrane thickness.' }),
|
||||
tmdetDefinition: PD.Boolean(false, { description: `Use TMDET's classification of membrane-favoring amino acids. TMDET's classification shows better performance on porins and other beta-barrel structures.` })
|
||||
};
|
||||
export type ANVILParams = typeof ANVILParams
|
||||
export type ANVILProps = PD.Values<ANVILParams>
|
||||
|
||||
/** ANVIL-specific (not general) definition of membrane-favoring amino acids */
|
||||
const ANVIL_DEFINITION = new Set(['ALA', 'CYS', 'GLY', 'HIS', 'ILE', 'LEU', 'MET', 'PHE', 'SER', 'TRP', 'VAL']);
|
||||
/** TMDET-specific (not general) definition of membrane-favoring amino acids */
|
||||
const TMDET_DEFINITION = new Set(['LEU', 'ILE', 'VAL', 'PHE', 'MET', 'GLY', 'TRP', 'TYR']);
|
||||
|
||||
/**
|
||||
* Implements:
|
||||
* Membrane positioning for high- and low-resolution protein structures through a binary classification approach
|
||||
* Guillaume Postic, Yassine Ghouzam, Vincent Guiraud, and Jean-Christophe Gelly
|
||||
* Protein Engineering, Design & Selection, 2015, 1–5
|
||||
* doi: 10.1093/protein/gzv063
|
||||
*
|
||||
* ANVIL is derived from TMDET, the corresponding classification of hydrophobic amino acids is provided as optional parameter:
|
||||
* Gabor E. Tusnady, Zsuzsanna Dosztanyi and Istvan Simon
|
||||
* Transmembrane proteins in the Protein Data Bank: identification and classification
|
||||
* Bioinformatics, 2004, 2964-2972
|
||||
* doi: 10.1093/bioinformatics/bth340
|
||||
*/
|
||||
export function computeANVIL(structure: Structure, props: ANVILProps) {
|
||||
return Task.create('Compute Membrane Orientation', async runtime => {
|
||||
@@ -54,22 +74,38 @@ export function computeANVIL(structure: Structure, props: ANVILProps) {
|
||||
});
|
||||
}
|
||||
|
||||
const l = StructureElement.Location.create(void 0);
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3add = Vec3.add;
|
||||
const v3clone = Vec3.clone;
|
||||
const v3create = Vec3.create;
|
||||
const v3distance = Vec3.distance;
|
||||
const v3dot = Vec3.dot;
|
||||
const v3magnitude = Vec3.magnitude;
|
||||
const v3normalize = Vec3.normalize;
|
||||
const v3scale = Vec3.scale;
|
||||
const v3scaleAndAdd = Vec3.scaleAndAdd;
|
||||
const v3set = Vec3.set;
|
||||
const v3squaredDistance = Vec3.squaredDistance;
|
||||
const v3sub = Vec3.sub;
|
||||
const v3zero = Vec3.zero;
|
||||
|
||||
const centroidHelper = new CentroidHelper();
|
||||
function initialize(structure: Structure, props: ANVILProps): ANVILContext {
|
||||
const { label_atom_id, x, y, z } = StructureProperties.atom;
|
||||
const elementCount = structure.polymerResidueCount;
|
||||
async function initialize(structure: Structure, props: ANVILProps, accessibleSurfaceArea: AccessibleSurfaceArea): Promise<ANVILContext> {
|
||||
const l = StructureElement.Location.create(structure);
|
||||
const { label_atom_id, label_comp_id, x, y, z } = StructureProperties.atom;
|
||||
const asaCutoff = props.asaCutoff / 100;
|
||||
centroidHelper.reset();
|
||||
l.structure = structure;
|
||||
|
||||
let offsets = new Int32Array(elementCount);
|
||||
let exposed = new Array<boolean>(elementCount);
|
||||
const offsets = new Array<number>();
|
||||
const exposed = new Array<number>();
|
||||
const hydrophobic = new Array<boolean>();
|
||||
const definition = props.tmdetDefinition ? TMDET_DEFINITION : ANVIL_DEFINITION;
|
||||
|
||||
const accessibleSurfaceArea = structure && AccessibleSurfaceAreaProvider.get(structure);
|
||||
const asa = accessibleSurfaceArea.value!;
|
||||
function isPartOfEntity(l: StructureElement.Location): boolean {
|
||||
return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_seq_id.valueKind(l.unit.residueIndex[l.element]) === 0;
|
||||
}
|
||||
|
||||
const vec = Vec3();
|
||||
let m = 0;
|
||||
const vec = v3zero();
|
||||
for (let i = 0, il = structure.units.length; i < il; ++i) {
|
||||
const unit = structure.units[i];
|
||||
const { elements } = unit;
|
||||
@@ -79,70 +115,88 @@ function initialize(structure: Structure, props: ANVILProps): ANVILContext {
|
||||
const eI = elements[j];
|
||||
l.element = eI;
|
||||
|
||||
// consider only amino acids
|
||||
if (getElementMoleculeType(unit, eI) !== MoleculeType.Protein) {
|
||||
// consider only amino acids in chains
|
||||
if (getElementMoleculeType(unit, eI) !== MoleculeType.Protein || !isPartOfEntity(l)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// only CA is considered for downstream operations
|
||||
if (label_atom_id(l) !== 'CA') {
|
||||
if (label_atom_id(l) !== 'CA' && label_atom_id(l) !== 'BB') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// original ANVIL only considers canonical amino acids
|
||||
if (!MaxAsa[label_comp_id(l)]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// while iterating use first pass to compute centroid
|
||||
Vec3.set(vec, x(l), y(l), z(l));
|
||||
v3set(vec, x(l), y(l), z(l));
|
||||
centroidHelper.includeStep(vec);
|
||||
|
||||
// keep track of offsets and exposed state to reuse
|
||||
offsets[m] = structure.serialMapping.getSerialIndex(l.unit, l.element);
|
||||
exposed[m] = AccessibleSurfaceArea.getValue(l, asa) > props.asaCutoff;
|
||||
|
||||
m++;
|
||||
offsets.push(structure.serialMapping.getSerialIndex(l.unit, l.element));
|
||||
if (AccessibleSurfaceArea.getValue(l, accessibleSurfaceArea) / MaxAsa[label_comp_id(l)] > asaCutoff) {
|
||||
exposed.push(structure.serialMapping.getSerialIndex(l.unit, l.element));
|
||||
hydrophobic.push(isHydrophobic(definition, label_comp_id(l)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// omit potentially empty tail1
|
||||
offsets = offsets.slice(0, m);
|
||||
exposed = exposed.slice(0, m);
|
||||
|
||||
// calculate centroid and extent
|
||||
centroidHelper.finishedIncludeStep();
|
||||
const centroid = centroidHelper.center;
|
||||
const centroid = v3clone(centroidHelper.center);
|
||||
for (let k = 0, kl = offsets.length; k < kl; k++) {
|
||||
setLocation(l, structure, offsets[k]);
|
||||
Vec3.set(vec, x(l), y(l), z(l));
|
||||
v3set(vec, x(l), y(l), z(l));
|
||||
centroidHelper.radiusStep(vec);
|
||||
}
|
||||
const extent = 1.2 * Math.sqrt(centroidHelper.radiusSq);
|
||||
|
||||
return {
|
||||
...props,
|
||||
structure: structure,
|
||||
structure,
|
||||
|
||||
offsets: offsets,
|
||||
exposed: exposed,
|
||||
centroid: centroid,
|
||||
extent: extent
|
||||
offsets,
|
||||
exposed,
|
||||
hydrophobic,
|
||||
centroid,
|
||||
extent,
|
||||
large: offsets.length > LARGE_CA_THRESHOLD
|
||||
};
|
||||
}
|
||||
|
||||
export async function calculate(runtime: RuntimeContext, structure: Structure, params: ANVILProps): Promise<MembraneOrientation> {
|
||||
const { label_comp_id } = StructureProperties.atom;
|
||||
// can't get away with the default 92 points here
|
||||
const asaProps = { ...PD.getDefaultValues(AccessibleSurfaceAreaParams), probeSize: 4.0, traceOnly: true, numberOfSpherePoints: 184 };
|
||||
const accessibleSurfaceArea = await AccessibleSurfaceArea.compute(structure, asaProps).runInContext(runtime);
|
||||
|
||||
const ctx = initialize(structure, params);
|
||||
const initialHphobHphil = HphobHphil.filtered(ctx, label_comp_id);
|
||||
const ctx = await initialize(structure, params, accessibleSurfaceArea);
|
||||
const initialHphobHphil = HphobHphil.initial(ctx);
|
||||
|
||||
const initialMembrane = findMembrane(ctx, generateSpherePoints(ctx, ctx.numberOfSpherePoints), initialHphobHphil, label_comp_id);
|
||||
const alternativeMembrane = findMembrane(ctx, findProximateAxes(ctx, initialMembrane), initialHphobHphil, label_comp_id);
|
||||
const initialMembrane = (await findMembrane(runtime, 'Placing initial membrane...', ctx, generateSpherePoints(ctx, ctx.numberOfSpherePoints), initialHphobHphil))!;
|
||||
const refinedMembrane = (await findMembrane(runtime, 'Refining membrane placement...', ctx, findProximateAxes(ctx, initialMembrane), initialHphobHphil))!;
|
||||
let membrane = initialMembrane.qmax! > refinedMembrane.qmax! ? initialMembrane : refinedMembrane;
|
||||
|
||||
const membrane = initialMembrane.qmax! > alternativeMembrane.qmax! ? initialMembrane : alternativeMembrane;
|
||||
if (ctx.adjust && !ctx.large) {
|
||||
membrane = await adjustThickness(runtime, 'Adjusting membrane thickness...', ctx, membrane, initialHphobHphil);
|
||||
}
|
||||
|
||||
const normalVector = v3zero();
|
||||
const center = v3zero();
|
||||
v3sub(normalVector, membrane.planePoint1, membrane.planePoint2);
|
||||
v3normalize(normalVector, normalVector);
|
||||
|
||||
v3add(center, membrane.planePoint1, membrane.planePoint2);
|
||||
v3scale(center, center, 0.5);
|
||||
const extent = adjustExtent(ctx, membrane, center);
|
||||
|
||||
return {
|
||||
planePoint1: membrane.planePoint1,
|
||||
planePoint2: membrane.planePoint2,
|
||||
normalVector: membrane.normalVector!,
|
||||
radius: ctx.extent,
|
||||
centroid: ctx.centroid
|
||||
normalVector,
|
||||
centroid: center,
|
||||
radius: extent
|
||||
};
|
||||
}
|
||||
|
||||
@@ -160,82 +214,79 @@ namespace MembraneCandidate {
|
||||
return {
|
||||
planePoint1: c1,
|
||||
planePoint2: c2,
|
||||
stats: stats
|
||||
stats
|
||||
};
|
||||
}
|
||||
|
||||
export function scored(spherePoint: Vec3, c1: Vec3, c2: Vec3, stats: HphobHphil, qmax: number, centroid: Vec3): MembraneCandidate {
|
||||
const diam_vect = Vec3();
|
||||
Vec3.sub(diam_vect, centroid, spherePoint);
|
||||
export function scored(spherePoint: Vec3, planePoint1: Vec3, planePoint2: Vec3, stats: HphobHphil, qmax: number, centroid: Vec3): MembraneCandidate {
|
||||
const normalVector = v3zero();
|
||||
v3sub(normalVector, centroid, spherePoint);
|
||||
return {
|
||||
planePoint1: c1,
|
||||
planePoint2: c2,
|
||||
stats: stats,
|
||||
normalVector: diam_vect,
|
||||
spherePoint: spherePoint,
|
||||
qmax: qmax
|
||||
planePoint1,
|
||||
planePoint2,
|
||||
stats,
|
||||
normalVector,
|
||||
spherePoint,
|
||||
qmax
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function findMembrane(ctx: ANVILContext, spherePoints: Vec3[], initialStats: HphobHphil, label_comp_id: StructureElement.Property<string>): MembraneCandidate {
|
||||
const { centroid, stepSize, minThickness, maxThickness } = ctx;
|
||||
async function findMembrane(runtime: RuntimeContext, message: string | undefined, ctx: ANVILContext, spherePoints: Vec3[], initialStats: HphobHphil): Promise<MembraneCandidate | undefined> {
|
||||
const { centroid, stepSize, minThickness, maxThickness, large } = ctx;
|
||||
// best performing membrane
|
||||
let membrane: MembraneCandidate;
|
||||
let membrane: MembraneCandidate | undefined;
|
||||
// score of the best performing membrane
|
||||
let qmax = 0;
|
||||
|
||||
// construct slices of thickness 1.0 along the axis connecting the centroid and the spherePoint
|
||||
const diam = Vec3();
|
||||
for (let i = 0, il = spherePoints.length; i < il; i++) {
|
||||
const spherePoint = spherePoints[i];
|
||||
Vec3.sub(diam, centroid, spherePoint);
|
||||
Vec3.scale(diam, diam, 2);
|
||||
const diamNorm = Vec3.magnitude(diam);
|
||||
const qvartemp = [];
|
||||
const diam = v3zero();
|
||||
for (let n = 0, nl = spherePoints.length; n < nl; n++) {
|
||||
if (runtime.shouldUpdate && message && (n + 1) % (large ? LARGE_CA_UPDATE_INTERVAL : DEFAULT_UPDATE_INTERVAL) === 0) {
|
||||
await runtime.update({ message, current: (n + 1), max: nl });
|
||||
}
|
||||
|
||||
const spherePoint = spherePoints[n];
|
||||
v3sub(diam, centroid, spherePoint);
|
||||
v3scale(diam, diam, 2);
|
||||
const diamNorm = v3magnitude(diam);
|
||||
|
||||
const sliceStats = HphobHphil.sliced(ctx, stepSize, spherePoint, diam, diamNorm);
|
||||
const qvartemp = [];
|
||||
for (let i = 0, il = diamNorm - stepSize; i < il; i += stepSize) {
|
||||
const c1 = Vec3();
|
||||
const c2 = Vec3();
|
||||
Vec3.scaleAndAdd(c1, spherePoint, diam, i / diamNorm);
|
||||
Vec3.scaleAndAdd(c2, spherePoint, diam, (i + stepSize) / diamNorm);
|
||||
const c1 = v3zero();
|
||||
const c2 = v3zero();
|
||||
v3scaleAndAdd(c1, spherePoint, diam, i / diamNorm);
|
||||
v3scaleAndAdd(c2, spherePoint, diam, (i + stepSize) / diamNorm);
|
||||
|
||||
// evaluate how well this membrane slice embeddeds the peculiar residues
|
||||
const stats = HphobHphil.filtered(ctx, label_comp_id, (testPoint: Vec3) => isInMembranePlane(testPoint, diam, c1, c2));
|
||||
const stats = sliceStats[Math.round(i / stepSize)];
|
||||
qvartemp.push(MembraneCandidate.initial(c1, c2, stats));
|
||||
}
|
||||
|
||||
let jmax = (minThickness / stepSize) - 1;
|
||||
|
||||
for (let width = 0, widthl = maxThickness; width < widthl;) {
|
||||
const imax = qvartemp.length - 1 - jmax;
|
||||
|
||||
for (let i = 0, il = imax; i < il; i++) {
|
||||
const c1 = qvartemp[i].planePoint1;
|
||||
const c2 = qvartemp[i + jmax].planePoint2;
|
||||
let jmax = Math.floor((minThickness / stepSize) - 1);
|
||||
|
||||
for (let width = 0, widthl = maxThickness; width <= widthl;) {
|
||||
for (let i = 0, il = qvartemp.length - 1 - jmax; i < il; i++) {
|
||||
let hphob = 0;
|
||||
let hphil = 0;
|
||||
let total = 0;
|
||||
for (let j = 0; j < jmax; j++) {
|
||||
const ij = qvartemp[i + j];
|
||||
if (j === 0 || j === jmax - 1) {
|
||||
hphob += 0.5 * ij.stats.hphob;
|
||||
hphob += Math.floor(0.5 * ij.stats.hphob);
|
||||
hphil += 0.5 * ij.stats.hphil;
|
||||
} else {
|
||||
hphob += ij.stats.hphob;
|
||||
hphil += ij.stats.hphil;
|
||||
}
|
||||
total += ij.stats.total;
|
||||
}
|
||||
|
||||
const stats = HphobHphil.of(hphob, hphil, total);
|
||||
|
||||
if (hphob !== 0) {
|
||||
const stats = { hphob, hphil };
|
||||
const qvaltest = qValue(stats, initialStats);
|
||||
if (qvaltest > qmax) {
|
||||
if (qvaltest >= qmax) {
|
||||
qmax = qvaltest;
|
||||
membrane = MembraneCandidate.scored(spherePoint, c1, c2, HphobHphil.of(hphob, hphil, total), qmax, centroid);
|
||||
membrane = MembraneCandidate.scored(spherePoint, qvartemp[i].planePoint1, qvartemp[i + jmax].planePoint2, stats, qmax, centroid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,15 +295,192 @@ function findMembrane(ctx: ANVILContext, spherePoints: Vec3[], initialStats: Hph
|
||||
}
|
||||
}
|
||||
|
||||
return membrane!;
|
||||
return membrane;
|
||||
}
|
||||
|
||||
/** Adjust membrane thickness by maximizing the number of membrane segments. */
|
||||
async function adjustThickness(runtime: RuntimeContext, message: string | undefined, ctx: ANVILContext, membrane: MembraneCandidate, initialHphobHphil: HphobHphil): Promise<MembraneCandidate> {
|
||||
const { minThickness, large } = ctx;
|
||||
const step = 0.3;
|
||||
let maxThickness = v3distance(membrane.planePoint1, membrane.planePoint2);
|
||||
|
||||
let maxNos = membraneSegments(ctx, membrane).length;
|
||||
let optimalThickness = membrane;
|
||||
|
||||
let n = 0;
|
||||
const nl = Math.ceil((maxThickness - minThickness) / step);
|
||||
while (maxThickness > minThickness) {
|
||||
n++;
|
||||
if (runtime.shouldUpdate && message && n % (large ? LARGE_CA_UPDATE_INTERVAL : DEFAULT_UPDATE_INTERVAL) === 0) {
|
||||
await runtime.update({ message, current: n, max: nl });
|
||||
}
|
||||
|
||||
const p = {
|
||||
...ctx,
|
||||
maxThickness,
|
||||
stepSize: step
|
||||
};
|
||||
const temp = await findMembrane(runtime, void 0, p, [membrane.spherePoint!], initialHphobHphil);
|
||||
if (temp) {
|
||||
const nos = membraneSegments(ctx, temp).length;
|
||||
if (nos > maxNos) {
|
||||
maxNos = nos;
|
||||
optimalThickness = temp;
|
||||
}
|
||||
}
|
||||
maxThickness -= step;
|
||||
}
|
||||
|
||||
return optimalThickness;
|
||||
}
|
||||
|
||||
/** Report auth_seq_ids for all transmembrane segments. Will reject segments that are shorter than the adjust parameter specifies. Missing residues are considered in-membrane. */
|
||||
function membraneSegments(ctx: ANVILContext, membrane: MembraneCandidate): ArrayLike<{ start: number, end: number }> {
|
||||
const { offsets, structure, adjust } = ctx;
|
||||
const { normalVector, planePoint1, planePoint2 } = membrane;
|
||||
const { units } = structure;
|
||||
const { elementIndices, unitIndices } = structure.serialMapping;
|
||||
const testPoint = v3zero();
|
||||
const { auth_seq_id } = StructureProperties.residue;
|
||||
|
||||
const d1 = -v3dot(normalVector!, planePoint1);
|
||||
const d2 = -v3dot(normalVector!, planePoint2);
|
||||
const dMin = Math.min(d1, d2);
|
||||
const dMax = Math.max(d1, d2);
|
||||
|
||||
const inMembrane: { [k: string]: Set<number> } = Object.create(null);
|
||||
const outMembrane: { [k: string]: Set<number> } = Object.create(null);
|
||||
const segments: Array<{ start: number, end: number }> = [];
|
||||
let authAsymId;
|
||||
let lastAuthAsymId = null;
|
||||
let authSeqId;
|
||||
let lastAuthSeqId = units[0].model.atomicHierarchy.residues.auth_seq_id.value((units[0] as Unit.Atomic).chainIndex[0]) - 1;
|
||||
let startOffset = 0;
|
||||
let endOffset = 0;
|
||||
|
||||
// collect all residues in membrane layer
|
||||
for (let k = 0, kl = offsets.length; k < kl; k++) {
|
||||
const unit = units[unitIndices[offsets[k]]];
|
||||
if (!Unit.isAtomic(unit)) notAtomic();
|
||||
const elementIndex = elementIndices[offsets[k]];
|
||||
|
||||
authAsymId = unit.model.atomicHierarchy.chains.auth_asym_id.value(unit.chainIndex[elementIndex]);
|
||||
if (authAsymId !== lastAuthAsymId) {
|
||||
if (!inMembrane[authAsymId]) inMembrane[authAsymId] = new Set<number>();
|
||||
if (!outMembrane[authAsymId]) outMembrane[authAsymId] = new Set<number>();
|
||||
lastAuthAsymId = authAsymId;
|
||||
}
|
||||
|
||||
authSeqId = unit.model.atomicHierarchy.residues.auth_seq_id.value(unit.residueIndex[elementIndex]);
|
||||
v3set(testPoint, unit.conformation.x(elementIndex), unit.conformation.y(elementIndex), unit.conformation.z(elementIndex));
|
||||
if (_isInMembranePlane(testPoint, normalVector!, dMin, dMax)) {
|
||||
inMembrane[authAsymId].add(authSeqId);
|
||||
} else {
|
||||
outMembrane[authAsymId].add(authSeqId);
|
||||
}
|
||||
}
|
||||
|
||||
for (let k = 0, kl = offsets.length; k < kl; k++) {
|
||||
const unit = units[unitIndices[offsets[k]]];
|
||||
if (!Unit.isAtomic(unit)) notAtomic();
|
||||
const elementIndex = elementIndices[offsets[k]];
|
||||
|
||||
authAsymId = unit.model.atomicHierarchy.chains.auth_asym_id.value(unit.chainIndex[elementIndex]);
|
||||
authSeqId = unit.model.atomicHierarchy.residues.auth_seq_id.value(unit.residueIndex[elementIndex]);
|
||||
if (inMembrane[authAsymId].has(authSeqId)) {
|
||||
// chain change
|
||||
if (authAsymId !== lastAuthAsymId) {
|
||||
segments.push({ start: startOffset, end: endOffset });
|
||||
lastAuthAsymId = authAsymId;
|
||||
startOffset = k;
|
||||
endOffset = k;
|
||||
}
|
||||
|
||||
// sequence gaps
|
||||
if (authSeqId !== lastAuthSeqId + 1) {
|
||||
if (outMembrane[authAsymId].has(lastAuthSeqId + 1)) {
|
||||
segments.push({ start: startOffset, end: endOffset });
|
||||
startOffset = k;
|
||||
}
|
||||
lastAuthSeqId = authSeqId;
|
||||
endOffset = k;
|
||||
} else {
|
||||
lastAuthSeqId++;
|
||||
endOffset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
segments.push({ start: startOffset, end: endOffset });
|
||||
|
||||
const l = StructureElement.Location.create(structure);
|
||||
let startAuth;
|
||||
let endAuth;
|
||||
const refinedSegments: Array<{ start: number, end: number }> = [];
|
||||
for (let k = 0, kl = segments.length; k < kl; k++) {
|
||||
const { start, end } = segments[k];
|
||||
if (start === 0 || end === offsets.length - 1) continue;
|
||||
|
||||
// evaluate residues 1 pos outside of membrane
|
||||
setLocation(l, structure, offsets[start - 1]);
|
||||
v3set(testPoint, l.unit.conformation.x(l.element), l.unit.conformation.y(l.element), l.unit.conformation.z(l.element));
|
||||
const d3 = -v3dot(normalVector!, testPoint);
|
||||
|
||||
setLocation(l, structure, offsets[end + 1]);
|
||||
v3set(testPoint, l.unit.conformation.x(l.element), l.unit.conformation.y(l.element), l.unit.conformation.z(l.element));
|
||||
const d4 = -v3dot(normalVector!, testPoint);
|
||||
|
||||
if (Math.min(d3, d4) < dMin && Math.max(d3, d4) > dMax) {
|
||||
// reject this refinement
|
||||
setLocation(l, structure, offsets[start]);
|
||||
startAuth = auth_seq_id(l);
|
||||
setLocation(l, structure, offsets[end]);
|
||||
endAuth = auth_seq_id(l);
|
||||
if (Math.abs(startAuth - endAuth) + 1 < adjust) {
|
||||
return [];
|
||||
}
|
||||
refinedSegments.push(segments[k]);
|
||||
}
|
||||
}
|
||||
|
||||
return refinedSegments;
|
||||
}
|
||||
|
||||
function notAtomic(): never {
|
||||
throw new Error('Property only available for atomic models.');
|
||||
}
|
||||
|
||||
/** Filter for membrane residues and calculate the final extent of the membrane layer */
|
||||
function adjustExtent(ctx: ANVILContext, membrane: MembraneCandidate, centroid: Vec3): number {
|
||||
const { offsets, structure } = ctx;
|
||||
const { normalVector, planePoint1, planePoint2 } = membrane;
|
||||
const l = StructureElement.Location.create(structure);
|
||||
const testPoint = v3zero();
|
||||
const { x, y, z } = StructureProperties.atom;
|
||||
|
||||
const d1 = -v3dot(normalVector!, planePoint1);
|
||||
const d2 = -v3dot(normalVector!, planePoint2);
|
||||
const dMin = Math.min(d1, d2);
|
||||
const dMax = Math.max(d1, d2);
|
||||
let extent = 0;
|
||||
|
||||
for (let k = 0, kl = offsets.length; k < kl; k++) {
|
||||
setLocation(l, structure, offsets[k]);
|
||||
v3set(testPoint, x(l), y(l), z(l));
|
||||
if (_isInMembranePlane(testPoint, normalVector!, dMin, dMax)) {
|
||||
const dsq = v3squaredDistance(testPoint, centroid);
|
||||
if (dsq > extent) extent = dsq;
|
||||
}
|
||||
}
|
||||
|
||||
return Math.sqrt(extent);
|
||||
}
|
||||
|
||||
function qValue(currentStats: HphobHphil, initialStats: HphobHphil): number {
|
||||
if(initialStats.hphob < 1) {
|
||||
if (initialStats.hphob < 1) {
|
||||
initialStats.hphob = 0.1;
|
||||
}
|
||||
|
||||
if(initialStats.hphil < 1) {
|
||||
if (initialStats.hphil < 1) {
|
||||
initialStats.hphil += 1;
|
||||
}
|
||||
|
||||
@@ -262,23 +490,27 @@ function qValue(currentStats: HphobHphil, initialStats: HphobHphil): number {
|
||||
}
|
||||
|
||||
export function isInMembranePlane(testPoint: Vec3, normalVector: Vec3, planePoint1: Vec3, planePoint2: Vec3): boolean {
|
||||
const d1 = -Vec3.dot(normalVector, planePoint1);
|
||||
const d2 = -Vec3.dot(normalVector, planePoint2);
|
||||
const d = -Vec3.dot(normalVector, testPoint);
|
||||
return d > Math.min(d1, d2) && d < Math.max(d1, d2);
|
||||
const d1 = -v3dot(normalVector, planePoint1);
|
||||
const d2 = -v3dot(normalVector, planePoint2);
|
||||
return _isInMembranePlane(testPoint, normalVector, Math.min(d1, d2), Math.max(d1, d2));
|
||||
}
|
||||
|
||||
// generates a defined number of points on a sphere with radius = extent around the specified centroid
|
||||
function _isInMembranePlane(testPoint: Vec3, normalVector: Vec3, min: number, max: number): boolean {
|
||||
const d = -v3dot(normalVector, testPoint);
|
||||
return d > min && d < max;
|
||||
}
|
||||
|
||||
/** Generates a defined number of points on a sphere with radius = extent around the specified centroid */
|
||||
function generateSpherePoints(ctx: ANVILContext, numberOfSpherePoints: number): Vec3[] {
|
||||
const { centroid, extent } = ctx;
|
||||
const points = [];
|
||||
let oldPhi = 0, h, theta, phi;
|
||||
for(let k = 1, kl = numberOfSpherePoints + 1; k < kl; k++) {
|
||||
h = -1 + 2 * (k - 1) / (numberOfSpherePoints - 1);
|
||||
for (let k = 1, kl = numberOfSpherePoints + 1; k < kl; k++) {
|
||||
h = -1 + 2 * (k - 1) / (2 * numberOfSpherePoints - 1);
|
||||
theta = Math.acos(h);
|
||||
phi = (k === 1 || k === numberOfSpherePoints) ? 0 : (oldPhi + 3.6 / Math.sqrt(numberOfSpherePoints * (1 - h * h))) % (2 * Math.PI);
|
||||
phi = (k === 1 || k === numberOfSpherePoints) ? 0 : (oldPhi + 3.6 / Math.sqrt(2 * numberOfSpherePoints * (1 - h * h))) % (2 * Math.PI);
|
||||
|
||||
const point = Vec3.create(
|
||||
const point = v3create(
|
||||
extent * Math.sin(phi) * Math.sin(theta) + centroid[0],
|
||||
extent * Math.cos(theta) + centroid[1],
|
||||
extent * Math.cos(phi) * Math.sin(theta) + centroid[2]
|
||||
@@ -290,18 +522,18 @@ function generateSpherePoints(ctx: ANVILContext, numberOfSpherePoints: number):
|
||||
return points;
|
||||
}
|
||||
|
||||
// generates sphere points close to that of the initial membrane
|
||||
/** Generates sphere points close to that of the initial membrane */
|
||||
function findProximateAxes(ctx: ANVILContext, membrane: MembraneCandidate): Vec3[] {
|
||||
const { numberOfSpherePoints, extent } = ctx;
|
||||
const points = generateSpherePoints(ctx, 30000);
|
||||
let j = 4;
|
||||
let sphere_pts2: Vec3[] = [];
|
||||
const s = 2 * extent / numberOfSpherePoints;
|
||||
while (sphere_pts2.length < numberOfSpherePoints) {
|
||||
const d = 2 * extent / numberOfSpherePoints + j;
|
||||
const dsq = d * d;
|
||||
const dsq = (s + j) * (s + j);
|
||||
sphere_pts2 = [];
|
||||
for (let i = 0, il = points.length; i < il; i++) {
|
||||
if (Vec3.squaredDistance(points[i], membrane.spherePoint!) < dsq) {
|
||||
if (v3squaredDistance(points[i], membrane.spherePoint!) < dsq) {
|
||||
sphere_pts2.push(points[i]);
|
||||
}
|
||||
}
|
||||
@@ -312,55 +544,78 @@ function findProximateAxes(ctx: ANVILContext, membrane: MembraneCandidate): Vec3
|
||||
|
||||
interface HphobHphil {
|
||||
hphob: number,
|
||||
hphil: number,
|
||||
total: number
|
||||
hphil: number
|
||||
}
|
||||
|
||||
namespace HphobHphil {
|
||||
export function of(hphob: number, hphil: number, total?: number) {
|
||||
return {
|
||||
hphob: hphob,
|
||||
hphil: hphil,
|
||||
total: !!total ? total : hphob + hphil
|
||||
};
|
||||
}
|
||||
|
||||
const testPoint = Vec3();
|
||||
export function filtered(ctx: ANVILContext, label_comp_id: StructureElement.Property<string>, filter?: (test: Vec3) => boolean): HphobHphil {
|
||||
const { offsets, exposed, structure } = ctx;
|
||||
const { x, y, z } = StructureProperties.atom;
|
||||
export function initial(ctx: ANVILContext): HphobHphil {
|
||||
const { exposed, hydrophobic } = ctx;
|
||||
let hphob = 0;
|
||||
let hphil = 0;
|
||||
for (let k = 0, kl = offsets.length; k < kl; k++) {
|
||||
// ignore buried residues
|
||||
if (!exposed[k]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
setLocation(l, structure, offsets[k]);
|
||||
Vec3.set(testPoint, x(l), y(l), z(l));
|
||||
|
||||
// testPoints have to be in putative membrane layer
|
||||
if (filter && !filter(testPoint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isHydrophobic(label_comp_id(l))) {
|
||||
for (let k = 0, kl = exposed.length; k < kl; k++) {
|
||||
if (hydrophobic[k]) {
|
||||
hphob++;
|
||||
} else {
|
||||
hphil++;
|
||||
}
|
||||
}
|
||||
return of(hphob, hphil);
|
||||
return { hphob, hphil };
|
||||
}
|
||||
|
||||
const testPoint = v3zero();
|
||||
export function sliced(ctx: ANVILContext, stepSize: number, spherePoint: Vec3, diam: Vec3, diamNorm: number): HphobHphil[] {
|
||||
const { exposed, hydrophobic, structure } = ctx;
|
||||
const { units, serialMapping } = structure;
|
||||
const { unitIndices, elementIndices } = serialMapping;
|
||||
const sliceStats: HphobHphil[] = [];
|
||||
for (let i = 0, il = diamNorm - stepSize; i < il; i += stepSize) {
|
||||
sliceStats[sliceStats.length] = { hphob: 0, hphil: 0 };
|
||||
}
|
||||
|
||||
for (let i = 0, il = exposed.length; i < il; i++) {
|
||||
const unit = units[unitIndices[exposed[i]]];
|
||||
const elementIndex = elementIndices[exposed[i]];
|
||||
v3set(testPoint, unit.conformation.x(elementIndex), unit.conformation.y(elementIndex), unit.conformation.z(elementIndex));
|
||||
v3sub(testPoint, testPoint, spherePoint);
|
||||
if (hydrophobic[i]) {
|
||||
sliceStats[Math.floor(v3dot(testPoint, diam) / diamNorm / stepSize)].hphob++;
|
||||
} else {
|
||||
sliceStats[Math.floor(v3dot(testPoint, diam) / diamNorm / stepSize)].hphil++;
|
||||
}
|
||||
}
|
||||
return sliceStats;
|
||||
}
|
||||
}
|
||||
|
||||
// ANVIL-specific (not general) definition of membrane-favoring amino acids
|
||||
const HYDROPHOBIC_AMINO_ACIDS = new Set(['ALA', 'CYS', 'GLY', 'HIS', 'ILE', 'LEU', 'MET', 'PHE', 'SER', 'THR', 'VAL']);
|
||||
export function isHydrophobic(label_comp_id: string): boolean {
|
||||
return HYDROPHOBIC_AMINO_ACIDS.has(label_comp_id);
|
||||
/** Returns true if the definition considers this as membrane-favoring amino acid */
|
||||
export function isHydrophobic(definition: Set<string>, label_comp_id: string): boolean {
|
||||
return definition.has(label_comp_id);
|
||||
}
|
||||
|
||||
/** Accessible surface area used for normalization. ANVIL uses 'Total-Side REL' values from NACCESS, from: Hubbard, S. J., & Thornton, J. M. (1993). naccess. Computer Program, Department of Biochemistry and Molecular Biology, University College London, 2(1). */
|
||||
export const MaxAsa: { [k: string]: number } = {
|
||||
'ALA': 69.41,
|
||||
'ARG': 201.25,
|
||||
'ASN': 106.24,
|
||||
'ASP': 102.69,
|
||||
'CYS': 96.75,
|
||||
'GLU': 134.74,
|
||||
'GLN': 140.99,
|
||||
'GLY': 32.33,
|
||||
'HIS': 147.08,
|
||||
'ILE': 137.96,
|
||||
'LEU': 141.12,
|
||||
'LYS': 163.30,
|
||||
'MET': 156.64,
|
||||
'PHE': 164.11,
|
||||
'PRO': 119.90,
|
||||
'SER': 78.11,
|
||||
'THR': 101.70,
|
||||
'TRP': 211.26,
|
||||
'TYR': 177.38,
|
||||
'VAL': 114.28
|
||||
};
|
||||
|
||||
function setLocation(l: StructureElement.Location, structure: Structure, serialIndex: number) {
|
||||
l.structure = structure;
|
||||
l.unit = structure.units[structure.serialMapping.unitIndices[serialIndex]];
|
||||
|
||||
@@ -30,7 +30,7 @@ export const ANVILMembraneOrientation = PluginBehavior.create<{ autoAttach: bool
|
||||
description: 'Data calculated with ANVIL algorithm.'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> {
|
||||
private provider = MembraneOrientationProvider
|
||||
private provider = MembraneOrientationProvider;
|
||||
|
||||
register(): void {
|
||||
DefaultQueryRuntimeTable.addCustomProp(this.provider.descriptor);
|
||||
@@ -52,7 +52,7 @@ export const ANVILMembraneOrientation = PluginBehavior.create<{ autoAttach: bool
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean }) {
|
||||
let updated = this.params.autoAttach !== p.autoAttach;
|
||||
const updated = this.params.autoAttach !== p.autoAttach;
|
||||
this.params.autoAttach = p.autoAttach;
|
||||
this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
|
||||
return updated;
|
||||
@@ -121,7 +121,7 @@ const MembraneOrientation3D = PluginStateTransform.BuiltIn({
|
||||
await MembraneOrientationProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
|
||||
const repr = MembraneOrientationRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => MembraneOrientationParams);
|
||||
await repr.createOrUpdate(params, a.data).runInContext(ctx);
|
||||
return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: 'Membrane Orientation' });
|
||||
return new PluginStateObject.Shape.Representation3D({ repr, sourceData: a.data }, { label: 'Membrane Orientation' });
|
||||
});
|
||||
},
|
||||
update({ a, b, newParams }, plugin: PluginContext) {
|
||||
@@ -129,6 +129,7 @@ const MembraneOrientation3D = PluginStateTransform.BuiltIn({
|
||||
await MembraneOrientationProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
@@ -149,7 +150,7 @@ export const MembraneOrientationPreset = StructureRepresentationPresetProvider({
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
if (!MembraneOrientationProvider.get(structure).value) {
|
||||
|
||||
@@ -11,11 +11,10 @@ import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
import { ANVILParams, ANVILProps, computeANVIL, isInMembranePlane } from './algorithm';
|
||||
import { CustomStructureProperty } from '../../mol-model-props/common/custom-structure-property';
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
import { AccessibleSurfaceAreaProvider } from '../../mol-model-props/computed/accessible-surface-area';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { QuerySymbolRuntime } from '../../mol-script/runtime/query/base';
|
||||
import { CustomPropSymbol } from '../../mol-script/language/symbol';
|
||||
import Type from '../../mol-script/language/type';
|
||||
import { Type } from '../../mol-script/language/type';
|
||||
|
||||
export const MembraneOrientationParams = {
|
||||
...ANVILParams
|
||||
@@ -76,7 +75,6 @@ export const MembraneOrientationProvider: CustomStructureProperty.Provider<Membr
|
||||
});
|
||||
|
||||
async function computeAnvil(ctx: CustomProperty.Context, data: Structure, props: Partial<ANVILProps>): Promise<MembraneOrientation> {
|
||||
await AccessibleSurfaceAreaProvider.attach(ctx, data);
|
||||
const p = { ...PD.getDefaultValues(ANVILParams), ...props };
|
||||
return await computeANVIL(data, p).runInContext(ctx.runtime);
|
||||
}
|
||||
@@ -9,8 +9,6 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../mol-repr/representation';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
|
||||
import { SpheresBuilder } from '../../mol-geo/geometry/spheres/spheres-builder';
|
||||
import { StructureRepresentationProvider, StructureRepresentation, StructureRepresentationStateBuilder } from '../../mol-repr/structure/representation';
|
||||
import { MembraneOrientation } from './prop';
|
||||
import { ThemeRegistryContext } from '../../mol-theme/theme';
|
||||
@@ -27,21 +25,13 @@ import { MembraneOrientationProvider } from './prop';
|
||||
import { MarkerActions } from '../../mol-util/marker-action';
|
||||
import { lociLabel } from '../../mol-theme/label';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
|
||||
const SharedParams = {
|
||||
color: PD.Color(ColorNames.lightgrey),
|
||||
radiusFactor: PD.Numeric(0.8333, { min: 0.1, max: 3.0, step: 0.01 }, { description: 'Scale the radius of the membrane layer' })
|
||||
radiusFactor: PD.Numeric(1.2, { min: 0.1, max: 3.0, step: 0.01 }, { description: 'Scale the radius of the membrane layer' })
|
||||
};
|
||||
|
||||
const BilayerSpheresParams = {
|
||||
...Spheres.Params,
|
||||
...SharedParams,
|
||||
sphereSize: PD.Numeric(1, { min: 0.1, max: 10, step: 0.1 }, { description: 'Size of spheres that represent membrane planes' }),
|
||||
density: PD.Numeric(1, { min: 0.25, max: 10, step: 0.25 }, { description: 'Distance between spheres'})
|
||||
};
|
||||
export type BilayerSpheresParams = typeof BilayerSpheresParams
|
||||
export type BilayerSpheresProps = PD.Values<BilayerSpheresParams>
|
||||
|
||||
const BilayerPlanesParams = {
|
||||
...Mesh.Params,
|
||||
...SharedParams,
|
||||
@@ -61,13 +51,11 @@ export type BilayerRimsParams = typeof BilayerRimsParams
|
||||
export type BilayerRimsProps = PD.Values<BilayerRimsParams>
|
||||
|
||||
const MembraneOrientationVisuals = {
|
||||
'bilayer-spheres': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerSpheresParams>) => ShapeRepresentation(getBilayerSpheres, Spheres.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
|
||||
'bilayer-planes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerPlanesParams>) => ShapeRepresentation(getBilayerPlanes, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }), modifyProps: p => ({ ...p, alpha: p.sectorOpacity, ignoreLight: true, doubleSided: false }) }),
|
||||
'bilayer-rims': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerRimsParams>) => ShapeRepresentation(getBilayerRims, Lines.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) })
|
||||
};
|
||||
|
||||
export const MembraneOrientationParams = {
|
||||
...BilayerSpheresParams,
|
||||
...BilayerPlanesParams,
|
||||
...BilayerRimsParams,
|
||||
visuals: PD.MultiSelect(['bilayer-planes', 'bilayer-rims'], PD.objectToOptions(MembraneOrientationVisuals)),
|
||||
@@ -91,9 +79,13 @@ export const MembraneOrientationRepresentationProvider = StructureRepresentation
|
||||
factory: MembraneOrientationRepresentation,
|
||||
getParams: getMembraneOrientationParams,
|
||||
defaultValues: PD.getDefaultValues(MembraneOrientationParams),
|
||||
defaultColorTheme: { name: 'uniform' },
|
||||
defaultSizeTheme: { name: 'uniform' },
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0
|
||||
defaultColorTheme: { name: 'shape-group' },
|
||||
defaultSizeTheme: { name: 'shape-group' },
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => MembraneOrientationProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (data) => MembraneOrientationProvider.ref(data, false)
|
||||
}
|
||||
});
|
||||
|
||||
function membraneLabel(data: Structure) {
|
||||
@@ -101,16 +93,16 @@ function membraneLabel(data: Structure) {
|
||||
}
|
||||
|
||||
function getBilayerRims(ctx: RuntimeContext, data: Structure, props: BilayerRimsProps, shape?: Shape<Lines>): Shape<Lines> {
|
||||
const { planePoint1: p1, planePoint2: p2, centroid, normalVector: normal, radius } = MembraneOrientationProvider.get(data).value!;
|
||||
const { planePoint1: p1, planePoint2: p2, centroid, radius } = MembraneOrientationProvider.get(data).value!;
|
||||
const scaledRadius = props.radiusFactor * radius;
|
||||
const builder = LinesBuilder.create(128, 64, shape?.geometry);
|
||||
getLayerCircle(builder, p1, centroid, normal, scaledRadius, props);
|
||||
getLayerCircle(builder, p2, centroid, normal, scaledRadius, props);
|
||||
return Shape.create(name, data, builder.getLines(), () => props.color, () => props.linesSize, () => membraneLabel(data));
|
||||
getLayerCircle(builder, p1, centroid, scaledRadius, props);
|
||||
getLayerCircle(builder, p2, centroid, scaledRadius, props);
|
||||
return Shape.create('Bilayer rims', data, builder.getLines(), () => props.color, () => props.linesSize, () => membraneLabel(data));
|
||||
}
|
||||
|
||||
function getLayerCircle(builder: LinesBuilder, p: Vec3, centroid: Vec3, normal: Vec3, radius: number, props: BilayerRimsProps, shape?: Shape<Lines>) {
|
||||
const circle = getCircle(p, centroid, normal, radius);
|
||||
function getLayerCircle(builder: LinesBuilder, p: Vec3, centroid: Vec3, radius: number, props: BilayerRimsProps, shape?: Shape<Lines>) {
|
||||
const circle = getCircle(p, centroid, radius);
|
||||
const { indices, vertices } = circle;
|
||||
for (let j = 0, jl = indices.length; j < jl; j += 3) {
|
||||
if (props.dashedLines && j % 2 === 1) continue; // draw every other segment to get dashes
|
||||
@@ -127,8 +119,13 @@ function getLayerCircle(builder: LinesBuilder, p: Vec3, centroid: Vec3, normal:
|
||||
}
|
||||
|
||||
const tmpMat = Mat4();
|
||||
function getCircle(p: Vec3, centroid: Vec3, normal: Vec3, radius: number) {
|
||||
Mat4.targetTo(tmpMat, p, centroid, normal);
|
||||
const tmpV = Vec3();
|
||||
function getCircle(p: Vec3, centroid: Vec3, radius: number) {
|
||||
if (Vec3.dot(Vec3.unitY, Vec3.sub(tmpV, p, centroid)) === 0) {
|
||||
Mat4.targetTo(tmpMat, p, centroid, Vec3.unitY);
|
||||
} else {
|
||||
Mat4.targetTo(tmpMat, p, centroid, Vec3.unitX);
|
||||
}
|
||||
Mat4.setTranslation(tmpMat, p);
|
||||
Mat4.mul(tmpMat, tmpMat, Mat4.rotX90);
|
||||
|
||||
@@ -137,42 +134,17 @@ function getCircle(p: Vec3, centroid: Vec3, normal: Vec3, radius: number) {
|
||||
}
|
||||
|
||||
function getBilayerPlanes(ctx: RuntimeContext, data: Structure, props: BilayerPlanesProps, shape?: Shape<Mesh>): Shape<Mesh> {
|
||||
const { planePoint1: p1, planePoint2: p2, centroid, normalVector: normal, radius } = MembraneOrientationProvider.get(data).value!;
|
||||
const { planePoint1: p1, planePoint2: p2, centroid, radius } = MembraneOrientationProvider.get(data).value!;
|
||||
const state = MeshBuilder.createState(128, 64, shape && shape.geometry);
|
||||
const scaledRadius = props.radiusFactor * radius;
|
||||
getLayerPlane(state, p1, centroid, normal, scaledRadius);
|
||||
getLayerPlane(state, p2, centroid, normal, scaledRadius);
|
||||
return Shape.create(name, data, MeshBuilder.getMesh(state), () => props.color, () => 1, () => membraneLabel(data));
|
||||
getLayerPlane(state, p1, centroid, scaledRadius);
|
||||
getLayerPlane(state, p2, centroid, scaledRadius);
|
||||
return Shape.create('Bilayer planes', data, MeshBuilder.getMesh(state), () => props.color, () => 1, () => membraneLabel(data));
|
||||
}
|
||||
|
||||
function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, normal: Vec3, radius: number) {
|
||||
const circle = getCircle(p, centroid, normal, radius);
|
||||
function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, radius: number) {
|
||||
const circle = getCircle(p, centroid, radius);
|
||||
state.currentGroup = 0;
|
||||
MeshBuilder.addPrimitive(state, Mat4.id, circle);
|
||||
MeshBuilder.addPrimitiveFlipped(state, Mat4.id, circle);
|
||||
}
|
||||
|
||||
function getBilayerSpheres(ctx: RuntimeContext, data: Structure, props: BilayerSpheresProps, shape?: Shape<Spheres>): Shape<Spheres> {
|
||||
const { density } = props;
|
||||
const { radius, planePoint1, planePoint2, normalVector } = MembraneOrientationProvider.get(data).value!;
|
||||
const scaledRadius = (props.radiusFactor * radius) * (props.radiusFactor * radius);
|
||||
|
||||
const spheresBuilder = SpheresBuilder.create(256, 128, shape?.geometry);
|
||||
getLayerSpheres(spheresBuilder, planePoint1, normalVector, density, scaledRadius);
|
||||
getLayerSpheres(spheresBuilder, planePoint2, normalVector, density, scaledRadius);
|
||||
return Shape.create(name, data, spheresBuilder.getSpheres(), () => props.color, () => props.sphereSize, () => membraneLabel(data));
|
||||
}
|
||||
|
||||
function getLayerSpheres(spheresBuilder: SpheresBuilder, point: Vec3, normalVector: Vec3, density: number, sqRadius: number) {
|
||||
Vec3.normalize(normalVector, normalVector);
|
||||
const d = -Vec3.dot(normalVector, point);
|
||||
const rep = Vec3();
|
||||
for (let i = -1000, il = 1000; i < il; i += density) {
|
||||
for (let j = -1000, jl = 1000; j < jl; j += density) {
|
||||
Vec3.set(rep, i, j, normalVector[2] === 0 ? 0 : -(d + i * normalVector[0] + j * normalVector[1]) / normalVector[2]);
|
||||
if (Vec3.squaredDistance(rep, point) < sqRadius) {
|
||||
spheresBuilder.add(rep[0], rep[1], rep[2], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -33,7 +33,7 @@ export function CellPackGenerateColorTheme(ctx: ThemeDataContext, props: PD.Valu
|
||||
|
||||
if (ctx.structure && info) {
|
||||
const colors = distinctColors(info.packingsCount);
|
||||
let hcl = Hcl.fromColor(Hcl(), colors[info.packingIndex]);
|
||||
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];
|
||||
|
||||
@@ -46,10 +46,9 @@ export function CellPackGenerateColorTheme(ctx: ThemeDataContext, props: PD.Valu
|
||||
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}`,
|
||||
clusteringStepCount: 50, minSampleCount: 800, maxCount: 75
|
||||
}
|
||||
}});
|
||||
} }, { minLabel: 'Min', maxLabel: 'Max' });
|
||||
legend = palette.legend;
|
||||
const modelColor = new Map<number, Color>();
|
||||
for (let i = 0, il = models.length; i < il; ++i) {
|
||||
@@ -89,7 +88,6 @@ export const CellPackGenerateColorThemeProvider: ColorTheme.Provider<CellPackGen
|
||||
isApplicable: (ctx: ThemeDataContext) => {
|
||||
return (
|
||||
!!ctx.structure && ctx.structure.elementCount > 0 &&
|
||||
Model.TrajectoryInfo.get(ctx.structure.models[0]).size > 1 &&
|
||||
!!CellPackInfoProvider.get(ctx.structure).value
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -9,7 +9,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
|
||||
import { StructureElement, Model } from '../../../mol-model/structure';
|
||||
import { StructureElement, Model, Bond } from '../../../mol-model/structure';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { CellPackInfoProvider } from '../property';
|
||||
|
||||
@@ -37,9 +37,12 @@ export function CellPackProvidedColorTheme(ctx: ThemeDataContext, props: PD.Valu
|
||||
}
|
||||
|
||||
color = (location: Location): Color => {
|
||||
return StructureElement.Location.is(location)
|
||||
? modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!
|
||||
: DefaultColor;
|
||||
if (StructureElement.Location.is(location)) {
|
||||
return modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!;
|
||||
} else if (Bond.isLocation(location)) {
|
||||
return modelColor.get(Model.TrajectoryInfo.get(location.aUnit.model).index)!;
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
} else {
|
||||
color = () => DefaultColor;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Ludovic Autin <autin@scripps.edu>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
@@ -49,7 +49,7 @@ function ResampleControlPoints(points: NumberArray, segmentLength: number) {
|
||||
// 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[] = [];
|
||||
const resampledControlPoints: Vec3[] = [];
|
||||
// resampledControlPoints.Add(controlPoints[0]);
|
||||
// resampledControlPoints.Add(controlPoints[1]);
|
||||
|
||||
@@ -111,7 +111,7 @@ function GetSmoothNormals(points: Vec3[]) {
|
||||
let p1 = points[1];
|
||||
let p2 = points[2];
|
||||
const p21 = Vec3.sub(tmpV1, p2, p1);
|
||||
const p01 = Vec3.sub(tmpV2, p0, p1);
|
||||
const p01 = Vec3.sub(tmpV2, p0, p1);
|
||||
const p0121 = Vec3.cross(tmpV3, p01, p21);
|
||||
Vec3.normalize(prevV, p0121);
|
||||
smoothNormals.push(Vec3.clone(prevV));
|
||||
@@ -179,7 +179,7 @@ function GetMiniFrame(points: Vec3[], normals: Vec3[]) {
|
||||
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 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));
|
||||
@@ -195,7 +195,7 @@ export function getMatFromResamplePoints(points: NumberArray, segmentLength: num
|
||||
let new_points: Vec3[] = [];
|
||||
if (resample) new_points = ResampleControlPoints(points, segmentLength);
|
||||
else {
|
||||
for (let idx = 0; idx < points.length / 3; ++idx){
|
||||
for (let idx = 0; idx < points.length / 3; ++idx) {
|
||||
new_points.push(Vec3.fromArray(Vec3.zero(), points, idx * 3));
|
||||
}
|
||||
}
|
||||
@@ -211,7 +211,7 @@ export function getMatFromResamplePoints(points: NumberArray, segmentLength: num
|
||||
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 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);
|
||||
|
||||
@@ -13,16 +13,27 @@ export interface CellPack {
|
||||
|
||||
export interface CellPacking {
|
||||
name: string,
|
||||
location: 'surface' | 'interior' | 'cytoplasme',
|
||||
location: 'surface' | 'interior' | 'cytoplasme'
|
||||
ingredients: Packing['ingredients']
|
||||
compartment?: CellCompartment
|
||||
}
|
||||
|
||||
//
|
||||
export interface CellCompartment {
|
||||
filename?: string
|
||||
geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
|
||||
compartment_primitives?: CompartmentPrimitives
|
||||
}
|
||||
|
||||
export interface Cell {
|
||||
recipe: Recipe
|
||||
options?: RecipeOptions
|
||||
cytoplasme?: Packing
|
||||
compartments?: { [key: string]: Compartment }
|
||||
mapping_ids?: { [key: number]: [number, string] }
|
||||
}
|
||||
|
||||
export interface RecipeOptions {
|
||||
resultfile?: string
|
||||
}
|
||||
|
||||
export interface Recipe {
|
||||
@@ -35,8 +46,29 @@ export interface Recipe {
|
||||
export interface Compartment {
|
||||
surface?: Packing
|
||||
interior?: Packing
|
||||
geom?: unknown
|
||||
geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
|
||||
mb?: CompartmentPrimitives
|
||||
}
|
||||
|
||||
// Primitives discribing a compartment
|
||||
export const enum CompartmentPrimitiveType {
|
||||
MetaBall = 0,
|
||||
Sphere = 1,
|
||||
Cube = 2,
|
||||
Cylinder = 3,
|
||||
Cone = 4,
|
||||
Plane = 5,
|
||||
None = 6
|
||||
}
|
||||
|
||||
export interface CompartmentPrimitives{
|
||||
positions?: number[];
|
||||
radii?: number[];
|
||||
types?: CompartmentPrimitiveType[];
|
||||
}
|
||||
|
||||
|
||||
export interface Packing {
|
||||
ingredients: { [key: string]: Ingredient }
|
||||
}
|
||||
@@ -64,18 +96,20 @@ export interface Ingredient {
|
||||
[curveX: string]: unknown;
|
||||
/** the orientation in the membrane */
|
||||
principalAxis?: Vec3;
|
||||
principalVector?: Vec3;
|
||||
/** offset along membrane */
|
||||
offset?: Vec3;
|
||||
ingtype?: string;
|
||||
color?: Vec3;
|
||||
confidence?: number;
|
||||
Type?: string;
|
||||
}
|
||||
|
||||
export interface IngredientSource {
|
||||
pdb: string;
|
||||
bu?: string; /** biological unit e.g AU,BU1,etc.. */
|
||||
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... */
|
||||
model?: string; /** model number e.g 0,1,2... */
|
||||
transform: {
|
||||
center: boolean;
|
||||
translate?: Vec3;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
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 { Ingredient, CellPacking, CompartmentPrimitives } from './data';
|
||||
import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean, getFromOPM } from './util';
|
||||
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../mol-model/structure';
|
||||
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit, Trajectory } 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, StructureFromAssemblies } from './state';
|
||||
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies, CreateCompartmentSphere } from './state';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { getMatFromResamplePoints } from './curve';
|
||||
import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
@@ -28,25 +29,30 @@ import { createModels } from '../../mol-model-formats/structure/basic/parser';
|
||||
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { readFromFile } from '../../mol-util/data-source';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
import { readFromFile } from '../../mol-util/data-source';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
|
||||
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); }
|
||||
private map = new Map<string, Trajectory>();
|
||||
set(id: string, trajectory: 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) {
|
||||
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient,
|
||||
baseUrl: string, trajCache: TrajectoryCache, location: string,
|
||||
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 surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
|
||||
if (location === 'surface') surface = true;
|
||||
let trajectory = trajCache.get(id);
|
||||
let assets: Asset.Wrapper[] = [];
|
||||
const assets: Asset.Wrapper[] = [];
|
||||
if (!trajectory) {
|
||||
if (file) {
|
||||
if (file.name.endsWith('.cif')) {
|
||||
@@ -68,10 +74,11 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien
|
||||
throw new Error(`unsupported file type '${file.name}'`);
|
||||
}
|
||||
} else if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
|
||||
if (surface){
|
||||
if (surface) {
|
||||
try {
|
||||
const data = await getFromOPM(plugin, id, assetManager);
|
||||
assets.push(data.asset);
|
||||
data.pdb.id! = id.toUpperCase();
|
||||
trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
|
||||
} catch (e) {
|
||||
// fallback to getFromPdb
|
||||
@@ -94,13 +101,13 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien
|
||||
trajectory = await plugin.runTask(trajectoryFromMmCIF(data.mmcif));
|
||||
}
|
||||
}
|
||||
trajCache.set(id, trajectory);
|
||||
trajCache.set(id, trajectory!);
|
||||
}
|
||||
const model = trajectory[modelIndex];
|
||||
const model = await plugin.resolveTask(trajectory?.getFrameAtIndex(modelIndex)!);
|
||||
return { model, assets };
|
||||
}
|
||||
|
||||
async function getStructure(plugin: PluginContext, model: Model, source: IngredientSource, props: { assembly?: string } = {}) {
|
||||
async function getStructure(plugin: PluginContext, model: Model, source: Ingredient, props: { assembly?: string } = {}) {
|
||||
let structure = Structure.ofModel(model);
|
||||
const { assembly } = props;
|
||||
|
||||
@@ -108,11 +115,12 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi
|
||||
structure = await plugin.runTask(StructureSymmetry.buildAssembly(structure, assembly));
|
||||
}
|
||||
let query;
|
||||
if (source.selection){
|
||||
const asymIds: string[] = source.selection.replace(' ', '').replace(':', '').split('or');
|
||||
if (source.source.selection) {
|
||||
const sel = source.source.selection;
|
||||
// selection can have the model ID as well. remove it
|
||||
const asymIds: string[] = sel.replace(/ /g, '').replace(/:/g, '').split('or').slice(1);
|
||||
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')])
|
||||
})
|
||||
]);
|
||||
@@ -123,11 +131,11 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
const compiled = compile<StructureSelection>(query);
|
||||
const result = compiled(new QueryContext(structure));
|
||||
structure = StructureSelection.unionStructure(result);
|
||||
|
||||
// change here if possible the label ?
|
||||
// structure.label = source.name;
|
||||
return structure;
|
||||
}
|
||||
|
||||
@@ -141,9 +149,9 @@ function getTransformLegacy(trans: Vec3, rot: Quat) {
|
||||
}
|
||||
|
||||
function getTransform(trans: Vec3, rot: Quat) {
|
||||
const q: Quat = Quat.create(rot[0], rot[1], rot[2], rot[3]);
|
||||
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]);
|
||||
const p: Vec3 = Vec3.create(-trans[0], trans[1], trans[2]);
|
||||
Mat4.setTranslation(m, p);
|
||||
return m;
|
||||
}
|
||||
@@ -157,9 +165,9 @@ function getCurveTransforms(ingredient: Ingredient) {
|
||||
const n = ingredient.nbCurve || 0;
|
||||
const instances: Mat4[] = [];
|
||||
let segmentLength = 3.4;
|
||||
if (ingredient.uLength){
|
||||
if (ingredient.uLength) {
|
||||
segmentLength = ingredient.uLength;
|
||||
} else if (ingredient.radii){
|
||||
} else if (ingredient.radii) {
|
||||
segmentLength = ingredient.radii[0].radii
|
||||
? ingredient.radii[0].radii[0] * 2.0
|
||||
: 3.4;
|
||||
@@ -168,7 +176,7 @@ function getCurveTransforms(ingredient: Ingredient) {
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const cname = `curve${i}`;
|
||||
if (!(cname in ingredient)) {
|
||||
// console.warn(`Expected '${cname}' in ingredient`)
|
||||
console.warn(`Expected '${cname}' in ingredient`);
|
||||
continue;
|
||||
}
|
||||
const _points = ingredient[cname] as Vec3[];
|
||||
@@ -177,9 +185,9 @@ function getCurveTransforms(ingredient: Ingredient) {
|
||||
continue;
|
||||
}
|
||||
// test for resampling
|
||||
let distance: number = Vec3.distance(_points[0], _points[1]);
|
||||
const distance: number = Vec3.distance(_points[0], _points[1]);
|
||||
if (distance >= segmentLength + 2.0) {
|
||||
console.info(distance);
|
||||
// console.info(distance);
|
||||
resampling = true;
|
||||
}
|
||||
const points = new Float32Array(_points.length * 3);
|
||||
@@ -190,13 +198,13 @@ function getCurveTransforms(ingredient: Ingredient) {
|
||||
return instances;
|
||||
}
|
||||
|
||||
function getAssembly(transforms: Mat4[], structure: Structure) {
|
||||
const builder = Structure.Builder();
|
||||
function getAssembly(name: string, transforms: Mat4[], structure: Structure) {
|
||||
const builder = Structure.Builder({ label: name });
|
||||
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 ] } });
|
||||
const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [id] } });
|
||||
for (const unit of units) {
|
||||
builder.addWithOperator(unit, op);
|
||||
}
|
||||
@@ -303,17 +311,17 @@ async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredi
|
||||
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];
|
||||
return models.representative;
|
||||
});
|
||||
|
||||
const curveModel = await plugin.runTask(curveModelTask);
|
||||
return getStructure(plugin, curveModel, ingredient.source);
|
||||
// ingredient.source.selection = undefined;
|
||||
return getStructure(plugin, curveModel, ingredient);
|
||||
}
|
||||
|
||||
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache) {
|
||||
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache, location: 'surface' | 'interior' | 'cytoplasme') {
|
||||
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?
|
||||
@@ -325,106 +333,116 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
|
||||
}
|
||||
|
||||
// model id in case structure is NMR
|
||||
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, file);
|
||||
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, location, file);
|
||||
if (!model) return;
|
||||
|
||||
let structure: Structure;
|
||||
if (nbCurve) {
|
||||
structure = await getCurve(plugin, name, ingredient, getCurveTransforms(ingredient), model);
|
||||
} else {
|
||||
if ((!results || results.length === 0)) return;
|
||||
let bu: string|undefined = source.bu ? source.bu : undefined;
|
||||
if (bu){
|
||||
if (bu) {
|
||||
if (bu === 'AU') {
|
||||
bu = undefined;
|
||||
} else {
|
||||
bu = bu.slice(2);
|
||||
}
|
||||
}
|
||||
structure = await getStructure(plugin, model, source, { assembly: bu });
|
||||
structure = await getStructure(plugin, model, ingredient, { assembly: bu });
|
||||
// transform with offset and pcp
|
||||
let legacy: boolean = true;
|
||||
if (ingredient.offset || ingredient.principalAxis){
|
||||
const pcp = ingredient.principalVector ? ingredient.principalVector : ingredient.principalAxis;
|
||||
if (pcp) {
|
||||
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())){
|
||||
if (ingredient.offset) {
|
||||
const o: Vec3 = Vec3.create(ingredient.offset[0], ingredient.offset[1], ingredient.offset[2]);
|
||||
if (!Vec3.exactEquals(o, Vec3.zero())) { // -1, 1, 4e-16 ??
|
||||
if (location !== 'surface') {
|
||||
Vec3.negate(o, o);
|
||||
}
|
||||
const m: Mat4 = Mat4.identity();
|
||||
Mat4.setTranslation(m, ingredient.offset);
|
||||
Mat4.setTranslation(m, o);
|
||||
structure = Structure.transform(structure, m);
|
||||
}
|
||||
}
|
||||
if (ingredient.principalAxis){
|
||||
if (!Vec3.exactEquals(ingredient.principalAxis, Vec3.unitZ)){
|
||||
if (pcp) {
|
||||
const p: Vec3 = Vec3.create(pcp[0], pcp[1], pcp[2]);
|
||||
if (!Vec3.exactEquals(p, Vec3.unitZ)) {
|
||||
const q: Quat = Quat.identity();
|
||||
Quat.rotationTo(q, ingredient.principalAxis, Vec3.unitZ);
|
||||
Quat.rotationTo(q, p, Vec3.unitZ);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
|
||||
structure = Structure.transform(structure, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
structure = getAssembly(getResultTransforms(results, legacy), structure);
|
||||
|
||||
structure = getAssembly(name, 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 { ingredients, location, name } = packing;
|
||||
const assets: Asset.Wrapper[] = [];
|
||||
const trajCache = new TrajectoryCache();
|
||||
const structures: Structure[] = [];
|
||||
const colors: Color[] = [];
|
||||
let skipColors: boolean = false;
|
||||
for (const iName in ingredients) {
|
||||
if (ctx.shouldUpdate) await ctx.update(iName);
|
||||
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache);
|
||||
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache, location);
|
||||
if (ingredientStructure) {
|
||||
structures.push(ingredientStructure.structure);
|
||||
assets.push(...ingredientStructure.assets);
|
||||
const c = ingredients[iName].color;
|
||||
if (c){
|
||||
if (c) {
|
||||
colors.push(Color.fromNormalizedRgb(c[0], c[1], c[2]));
|
||||
} else {
|
||||
skipColors = true;
|
||||
colors.push(Color.fromNormalizedRgb(1, 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - units`);
|
||||
const builder = Structure.Builder({ label: name });
|
||||
const units: Unit[] = [];
|
||||
let offsetInvariantId = 0;
|
||||
let offsetChainGroupId = 0;
|
||||
for (const s of structures) {
|
||||
if (ctx.shouldUpdate) await ctx.update(`${s.label}`);
|
||||
let maxInvariantId = 0;
|
||||
const maxChainGroupId = 0;
|
||||
for (const u of s.units) {
|
||||
const invariantId = u.invariantId + offsetInvariantId;
|
||||
const chainGroupId = u.chainGroupId + offsetChainGroupId;
|
||||
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
|
||||
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId);
|
||||
units.push(Unit.create(units.length, invariantId, chainGroupId, u.traits, u.kind, u.model, u.conformation.operator, u.elements, u.props));
|
||||
}
|
||||
offsetInvariantId += maxInvariantId + 1;
|
||||
offsetChainGroupId += maxChainGroupId + 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 structure = Structure.create(units, { label: name + '.' + location });
|
||||
for (let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
|
||||
}
|
||||
return { structure, assets, colors: skipColors ? undefined : colors };
|
||||
return { structure, assets, colors: colors };
|
||||
});
|
||||
}
|
||||
|
||||
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') {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0' || packings[i].name === 'HIV_capsid') {
|
||||
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));
|
||||
@@ -442,18 +460,18 @@ async function handleHivRna(plugin: PluginContext, packings: CellPacking[], base
|
||||
|
||||
async function loadMembrane(plugin: PluginContext, name: string, state: State, params: LoadCellPackModelParams) {
|
||||
let file: Asset.File | undefined = undefined;
|
||||
if (params.ingredients.files !== null) {
|
||||
if (params.ingredients !== null) {
|
||||
const fileName = `${name}.bcif`;
|
||||
for (const f of params.ingredients.files) {
|
||||
for (const f of params.ingredients) {
|
||||
if (fileName === f.name) {
|
||||
file = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!file){
|
||||
if (!file) {
|
||||
// check for cif directly
|
||||
const cifileName = `${name}.cif`;
|
||||
for (const f of params.ingredients.files) {
|
||||
for (const f of params.ingredients) {
|
||||
if (cifileName === f.name) {
|
||||
file = f;
|
||||
break;
|
||||
@@ -461,7 +479,8 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let legacy_membrane: boolean = false; // temporary variable until all membrane are converted to the new correct cif format
|
||||
let geometry_membrane: boolean = false; // membrane can be a mesh geometry
|
||||
let b = state.build().toRoot();
|
||||
if (file) {
|
||||
if (file.name.endsWith('.cif')) {
|
||||
@@ -470,27 +489,82 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
|
||||
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 } });
|
||||
if (name.toLowerCase().endsWith('.bcif')) {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
|
||||
} else if (name.toLowerCase().endsWith('.cif')) {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
|
||||
} else if (name.toLowerCase().endsWith('.ply')) {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/geometries/${name}`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
|
||||
geometry_membrane = 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 } });
|
||||
legacy_membrane = 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(StructureFromAssemblies, undefined, { state: { isGhost: true } })
|
||||
.commit({ revertOnError: true });
|
||||
|
||||
const membraneParams = {
|
||||
representation: params.preset.representation,
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: '1' }
|
||||
}
|
||||
};
|
||||
if (legacy_membrane) {
|
||||
// old membrane
|
||||
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(StructureFromAssemblies, undefined, { state: { isGhost: true } })
|
||||
.commit({ revertOnError: true });
|
||||
const membraneParams = {
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
|
||||
} else if (geometry_membrane) {
|
||||
await b.apply(StateTransforms.Data.ParsePly, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ShapeFromPly)
|
||||
.apply(StateTransforms.Representation.ShapeRepresentation3D, { xrayShaded: true,
|
||||
doubleSided: true, coloring: { name: 'uniform', params: { color: ColorNames.orange } } })
|
||||
.commit({ revertOnError: true });
|
||||
} else {
|
||||
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, props, { state: { isGhost: true } })
|
||||
.commit({ revertOnError: true });
|
||||
const membraneParams = {
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
|
||||
async function handleMembraneSpheres(state: State, primitives: CompartmentPrimitives) {
|
||||
const nSpheres = primitives.positions!.length / 3;
|
||||
// console.log('ok mb ', nSpheres);
|
||||
// TODO : take in account the type of the primitives.
|
||||
for (let j = 0; j < nSpheres; j++) {
|
||||
await state.build()
|
||||
.toRoot()
|
||||
.apply(CreateCompartmentSphere, {
|
||||
center: Vec3.create(
|
||||
primitives.positions![j * 3 + 0],
|
||||
primitives.positions![j * 3 + 1],
|
||||
primitives.positions![j * 3 + 2]
|
||||
),
|
||||
radius: primitives!.radii![j]
|
||||
})
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
|
||||
const ingredientFiles = params.ingredients.files || [];
|
||||
const ingredientFiles = params.ingredients || [];
|
||||
|
||||
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
|
||||
let resultsFile: Asset.File | null = params.results;
|
||||
if (params.source.name === 'id') {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl));
|
||||
cellPackJson = state.build().toRoot()
|
||||
@@ -502,29 +576,36 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
|
||||
return;
|
||||
}
|
||||
|
||||
let jsonFile: Asset.File;
|
||||
let modelFile: 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'));
|
||||
if (data['model.json']) {
|
||||
modelFile = Asset.File(new File([data['model.json']], 'model.json'));
|
||||
} else {
|
||||
throw new Error('model.json missing from zip file');
|
||||
}
|
||||
if (data['results.bin']) {
|
||||
resultsFile = Asset.File(new File([data['results.bin']], 'results.bin'));
|
||||
}
|
||||
objectForEach(data, (v, k) => {
|
||||
if (k === 'model.json') return;
|
||||
if (k === 'results.bin') return;
|
||||
ingredientFiles.push(Asset.File(new File([v], k)));
|
||||
});
|
||||
} else {
|
||||
jsonFile = file;
|
||||
modelFile = file;
|
||||
}
|
||||
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.ReadFile, { file: jsonFile, isBinary: false, label: jsonFile.name }, { state: { isGhost: true } });
|
||||
.apply(StateTransforms.Data.ReadFile, { file: modelFile, isBinary: false, label: modelFile.name }, { state: { isGhost: true } });
|
||||
}
|
||||
|
||||
const cellPackBuilder = cellPackJson
|
||||
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack);
|
||||
.apply(ParseCellPack, { resultsFile, baseUrl: params.baseUrl });
|
||||
|
||||
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime);
|
||||
const { packings } = cellPackObject.obj!.data;
|
||||
|
||||
const { packings } = cellPackObject.obj!.data;
|
||||
await handleHivRna(plugin, packings, params.baseUrl);
|
||||
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
@@ -540,8 +621,30 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackPackingPreset.apply(packing, packingParams, plugin);
|
||||
if ( packings[i].location === 'surface' && params.membrane){
|
||||
await loadMembrane(plugin, packings[i].name, state, params);
|
||||
if (packings[i].compartment) {
|
||||
if (params.membrane === 'lipids') {
|
||||
if (packings[i].compartment!.geom_type) {
|
||||
if (packings[i].compartment!.geom_type === 'file') {
|
||||
// TODO: load mesh files or vertex,faces data
|
||||
await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
|
||||
} else if (packings[i].compartment!.compartment_primitives) {
|
||||
await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
|
||||
}
|
||||
} else {
|
||||
// try loading membrane from repo as a bcif file or from the given list of files.
|
||||
if (params.membrane === 'lipids') {
|
||||
await loadMembrane(plugin, packings[i].name, state, params);
|
||||
}
|
||||
}
|
||||
} else if (params.membrane === 'geometry') {
|
||||
if (packings[i].compartment!.compartment_primitives) {
|
||||
await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
|
||||
} else if (packings[i].compartment!.geom_type === 'file') {
|
||||
if (packings[i].compartment!.filename!.toLowerCase().endsWith('.ply')) {
|
||||
await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -551,21 +654,19 @@ const LoadCellPackModelParams = {
|
||||
'id': PD.Select('InfluenzaModel2.json', [
|
||||
['blood_hiv_immature_inside.json', 'Blood HIV immature'],
|
||||
['HIV_immature_model.json', 'HIV immature'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'Blood HIV'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV'],
|
||||
['Blood_HIV.json', 'Blood HIV'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.json', 'HIV'],
|
||||
['influenza_model1.json', 'Influenza envelope'],
|
||||
['InfluenzaModel2.json', 'Influenza Complete'],
|
||||
['InfluenzaModel2.json', 'Influenza complete'],
|
||||
['ExosomeModel.json', 'Exosome Model'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma simple'],
|
||||
['MycoplasmaModel.json', 'Mycoplasma WholeCell model'],
|
||||
['MycoplasmaGenitalium.json', 'Mycoplasma Genitalium curated model'],
|
||||
] 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.' }),
|
||||
'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.', label: 'Recipe file' }),
|
||||
}, { options: [['id', 'Id'], ['file', 'File']] }),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
membrane: PD.Boolean(true),
|
||||
ingredients : PD.Group({
|
||||
files: PD.FileList({ accept: '.cif,.bcif,.pdb' })
|
||||
}, { isExpanded: true }),
|
||||
results: PD.File({ accept: '.bin', description: 'open results file in binary format from cellpackgpu for the specified recipe', label: 'Results file' }),
|
||||
membrane: PD.Select('lipids', PD.arrayToOptions(['lipids', 'geometry', 'none'])),
|
||||
ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredient files' }),
|
||||
preset: PD.Group({
|
||||
traceOnly: PD.Boolean(false),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation']))
|
||||
@@ -579,4 +680,4 @@ export const LoadCellPackModel = StateAction.build({
|
||||
from: PSO.Root
|
||||
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
|
||||
await loadPackings(ctx, taskCtx, state, params);
|
||||
}));
|
||||
}));
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
@@ -9,8 +10,6 @@ import { StructureRepresentationPresetProvider, presetStaticComponent } from '..
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { CellPackGenerateColorThemeProvider } from './color/generate';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
import { CellPackProvidedColorThemeProvider } from './color/provided';
|
||||
|
||||
export const CellpackPackingPresetParams = {
|
||||
traceOnly: PD.Boolean(true),
|
||||
@@ -42,8 +41,8 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
|
||||
Object.assign(reprProps, { sizeFactor: 2 });
|
||||
}
|
||||
|
||||
const info = structureCell.obj?.data && CellPackInfoProvider.get(structureCell.obj?.data).value;
|
||||
const color = info?.colors ? CellPackProvidedColorThemeProvider.name : CellPackGenerateColorThemeProvider.name;
|
||||
// default is generated
|
||||
const color = CellPackGenerateColorThemeProvider.name;
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
|
||||
const representations = {
|
||||
@@ -92,4 +91,4 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({
|
||||
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -34,4 +34,4 @@ export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellP
|
||||
value: { ...CellPackInfoParams.info.defaultValue, ...props.info }
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
70
src/extensions/cellpack/representation.ts
Normal file
70
src/extensions/cellpack/representation.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { ShapeRepresentation } from '../../mol-repr/shape/representation';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
// import { Polyhedron, DefaultPolyhedronProps } from '../../mol-geo/primitive/polyhedron';
|
||||
// import { Icosahedron } from '../../mol-geo/primitive/icosahedron';
|
||||
import { Sphere } from '../../mol-geo/primitive/sphere';
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { RepresentationParamsGetter, Representation, RepresentationContext } from '../../mol-repr/representation';
|
||||
|
||||
|
||||
interface MembraneSphereData {
|
||||
radius: number
|
||||
center: Vec3
|
||||
}
|
||||
|
||||
|
||||
const MembraneSphereParams = {
|
||||
...Mesh.Params,
|
||||
cellColor: PD.Color(ColorNames.orange),
|
||||
cellScale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
|
||||
radius: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
|
||||
center: PD.Vec3(Vec3.create(0, 0, 0)),
|
||||
quality: { ...Mesh.Params.quality, isEssential: false },
|
||||
};
|
||||
|
||||
type MeshParams = typeof MembraneSphereParams
|
||||
|
||||
const MembraneSphereVisuals = {
|
||||
'mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MeshParams>) => ShapeRepresentation(getMBShape, Mesh.Utils),
|
||||
};
|
||||
|
||||
export const MBParams = {
|
||||
...MembraneSphereParams
|
||||
};
|
||||
export type MBParams = typeof MBParams
|
||||
export type UnitcellProps = PD.Values<MBParams>
|
||||
|
||||
function getMBMesh(data: MembraneSphereData, props: UnitcellProps, mesh?: Mesh) {
|
||||
const state = MeshBuilder.createState(256, 128, mesh);
|
||||
const radius = props.radius;
|
||||
const asphere = Sphere(3);
|
||||
const trans: Mat4 = Mat4.identity();
|
||||
Mat4.fromScaling(trans, Vec3.create(radius, radius, radius));
|
||||
state.currentGroup = 1;
|
||||
MeshBuilder.addPrimitive(state, trans, asphere);
|
||||
const m = MeshBuilder.getMesh(state);
|
||||
return m;
|
||||
}
|
||||
|
||||
function getMBShape(ctx: RuntimeContext, data: MembraneSphereData, props: UnitcellProps, shape?: Shape<Mesh>) {
|
||||
const geo = getMBMesh(data, props, shape && shape.geometry);
|
||||
const label = 'mb';
|
||||
return Shape.create(label, data, geo, () => props.cellColor, () => 1, () => label);
|
||||
}
|
||||
|
||||
export type MBRepresentation = Representation<MembraneSphereData, MBParams>
|
||||
export function MBRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MBParams>): MBRepresentation {
|
||||
return Representation.createMulti('MB', ctx, getParams, Representation.StateBuilder, MembraneSphereVisuals as unknown as Representation.Def<MembraneSphereData, MBParams>);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
@@ -15,9 +16,13 @@ import { PluginContext } from '../../mol-plugin/context';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
import { Structure, StructureSymmetry, Unit, Model } from '../../mol-model/structure';
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
import { Vec3, Quat } from '../../mol-math/linear-algebra';
|
||||
import { StateTransformer } from '../../mol-state';
|
||||
import { MBRepresentation, MBParams } from './representation';
|
||||
import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
|
||||
import { getFloatValue } from './util';
|
||||
|
||||
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/';
|
||||
|
||||
export const DefaultCellPackBaseUrl = 'https://raw.githubusercontent.com/mesoscope/cellPACK_data/master/cellPACK_database_1.1.0';
|
||||
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
|
||||
|
||||
export { ParseCellPack };
|
||||
@@ -26,26 +31,173 @@ const ParseCellPack = PluginStateTransform.BuiltIn({
|
||||
name: 'parse-cellpack',
|
||||
display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
|
||||
from: PSO.Format.Json,
|
||||
to: CellPack
|
||||
to: CellPack,
|
||||
params: a => {
|
||||
return {
|
||||
resultsFile: PD.File({ accept: '.bin' }),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl)
|
||||
};
|
||||
}
|
||||
})({
|
||||
apply({ a }) {
|
||||
apply({ a, params, cache }, plugin: PluginContext) {
|
||||
return Task.create('Parse CellPack', async ctx => {
|
||||
const cell = a.data as Cell;
|
||||
|
||||
let counter_id = 0;
|
||||
let fiber_counter_id = 0;
|
||||
let comp_counter = 0;
|
||||
const packings: CellPacking[] = [];
|
||||
const { compartments, cytoplasme } = cell;
|
||||
if (!cell.mapping_ids) cell.mapping_ids = {};
|
||||
if (cytoplasme) {
|
||||
packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
|
||||
for (const iName in cytoplasme.ingredients) {
|
||||
if (cytoplasme.ingredients[iName].ingtype === 'fiber') {
|
||||
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
|
||||
if (!cytoplasme.ingredients[iName].nbCurve) cytoplasme.ingredients[iName].nbCurve = 0;
|
||||
fiber_counter_id++;
|
||||
} else {
|
||||
cell.mapping_ids[counter_id] = [comp_counter, iName];
|
||||
if (!cytoplasme.ingredients[iName].results) { cytoplasme.ingredients[iName].results = []; }
|
||||
counter_id++;
|
||||
}
|
||||
}
|
||||
comp_counter++;
|
||||
}
|
||||
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 });
|
||||
let filename = '';
|
||||
if (compartments[name].geom_type === 'file') {
|
||||
filename = (compartments[name].geom) ? compartments[name].geom as string : '';
|
||||
}
|
||||
const compartment = { filename: filename, geom_type: compartments[name].geom_type, compartment_primitives: compartments[name].mb };
|
||||
if (surface) {
|
||||
packings.push({ name, location: 'surface', ingredients: surface.ingredients, compartment: compartment });
|
||||
for (const iName in surface.ingredients) {
|
||||
if (surface.ingredients[iName].ingtype === 'fiber') {
|
||||
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
|
||||
if (!surface.ingredients[iName].nbCurve) surface.ingredients[iName].nbCurve = 0;
|
||||
fiber_counter_id++;
|
||||
} else {
|
||||
cell.mapping_ids[counter_id] = [comp_counter, iName];
|
||||
if (!surface.ingredients[iName].results) { surface.ingredients[iName].results = []; }
|
||||
counter_id++;
|
||||
}
|
||||
}
|
||||
comp_counter++;
|
||||
}
|
||||
if (interior) {
|
||||
if (!surface) packings.push({ name, location: 'interior', ingredients: interior.ingredients, compartment: compartment });
|
||||
else packings.push({ name, location: 'interior', ingredients: interior.ingredients });
|
||||
for (const iName in interior.ingredients) {
|
||||
if (interior.ingredients[iName].ingtype === 'fiber') {
|
||||
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
|
||||
if (!interior.ingredients[iName].nbCurve) interior.ingredients[iName].nbCurve = 0;
|
||||
fiber_counter_id++;
|
||||
} else {
|
||||
cell.mapping_ids[counter_id] = [comp_counter, iName];
|
||||
if (!interior.ingredients[iName].results) { interior.ingredients[iName].results = []; }
|
||||
counter_id++;
|
||||
}
|
||||
}
|
||||
comp_counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
|
||||
const { options } = cell;
|
||||
let resultsAsset: Asset.Wrapper<'binary'> | undefined;
|
||||
if (params.resultsFile) {
|
||||
resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(params.resultsFile, 'binary', true));
|
||||
} else if (options?.resultfile) {
|
||||
const url = `${params.baseUrl}/results/${options.resultfile}`;
|
||||
resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(Asset.getUrlAsset(plugin.managers.asset, url), 'binary', true));
|
||||
}
|
||||
if (resultsAsset) {
|
||||
(cache as any).asset = resultsAsset;
|
||||
const results = resultsAsset.data;
|
||||
// flip the byte order if needed
|
||||
const buffer = IsNativeEndianLittle ? results.buffer : flipByteOrder(results, 4);
|
||||
const numbers = new DataView(buffer);
|
||||
const ninst = getFloatValue(numbers, 0);
|
||||
const npoints = getFloatValue(numbers, 4);
|
||||
const ncurve = getFloatValue(numbers, 8);
|
||||
|
||||
let offset = 12;
|
||||
|
||||
if (ninst !== 0) {
|
||||
const pos = new Float32Array(buffer, offset, ninst * 4);
|
||||
offset += ninst * 4 * 4;
|
||||
const quat = new Float32Array(buffer, offset, ninst * 4);
|
||||
offset += ninst * 4 * 4;
|
||||
|
||||
for (let i = 0; i < ninst; i++) {
|
||||
const x: number = pos[i * 4 + 0];
|
||||
const y: number = pos[i * 4 + 1];
|
||||
const z: number = pos[i * 4 + 2];
|
||||
const ingr_id = pos[i * 4 + 3] as number;
|
||||
const pid = cell.mapping_ids![ingr_id];
|
||||
if (!packings[pid[0]].ingredients[pid[1]].results) {
|
||||
packings[pid[0]].ingredients[pid[1]].results = [];
|
||||
}
|
||||
packings[pid[0]].ingredients[pid[1]].results.push([Vec3.create(x, y, z),
|
||||
Quat.create(quat[i * 4 + 0], quat[i * 4 + 1], quat[i * 4 + 2], quat[i * 4 + 3])]);
|
||||
}
|
||||
}
|
||||
|
||||
if (npoints !== 0) {
|
||||
const ctr_pos = new Float32Array(buffer, offset, npoints * 4);
|
||||
offset += npoints * 4 * 4;
|
||||
offset += npoints * 4 * 4;
|
||||
const ctr_info = new Float32Array(buffer, offset, npoints * 4);
|
||||
offset += npoints * 4 * 4;
|
||||
const curve_ids = new Float32Array(buffer, offset, ncurve * 4);
|
||||
offset += ncurve * 4 * 4;
|
||||
|
||||
let counter = 0;
|
||||
let ctr_points: Vec3[] = [];
|
||||
let prev_ctype = 0;
|
||||
let prev_cid = 0;
|
||||
|
||||
for (let i = 0; i < npoints; i++) {
|
||||
const x: number = -ctr_pos[i * 4 + 0];
|
||||
const y: number = ctr_pos[i * 4 + 1];
|
||||
const z: number = ctr_pos[i * 4 + 2];
|
||||
const cid: number = ctr_info[i * 4 + 0]; // curve id
|
||||
const ctype: number = curve_ids[cid * 4 + 0]; // curve type
|
||||
// cid 148 165 -1 0
|
||||
// console.log("cid ",cid,ctype,prev_cid,prev_ctype);//165,148
|
||||
if (prev_ctype !== ctype) {
|
||||
const pid = cell.mapping_ids![-prev_ctype - 1];
|
||||
const cname = `curve${counter}`;
|
||||
packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
|
||||
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
|
||||
ctr_points = [];
|
||||
counter = 0;
|
||||
} else if (prev_cid !== cid) {
|
||||
ctr_points = [];
|
||||
const pid = cell.mapping_ids![-prev_ctype - 1];
|
||||
const cname = `curve${counter}`;
|
||||
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
|
||||
counter += 1;
|
||||
}
|
||||
ctr_points.push(Vec3.create(x, y, z));
|
||||
prev_ctype = ctype;
|
||||
prev_cid = cid;
|
||||
}
|
||||
|
||||
// do the last one
|
||||
const pid = cell.mapping_ids![-prev_ctype - 1];
|
||||
const cname = `curve${counter}`;
|
||||
packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
|
||||
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
|
||||
}
|
||||
}
|
||||
return new CellPack({ cell, packings });
|
||||
});
|
||||
}
|
||||
},
|
||||
dispose({ cache }) {
|
||||
((cache as any)?.asset as Asset.Wrapper | undefined)?.dispose();
|
||||
},
|
||||
});
|
||||
|
||||
export { StructureFromCellpack };
|
||||
@@ -77,14 +229,13 @@ const StructureFromCellpack = PluginStateTransform.BuiltIn({
|
||||
await CellPackInfoProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, structure, {
|
||||
info: { packingsCount: a.data.packings.length, packingIndex: params.packing, colors }
|
||||
});
|
||||
|
||||
(cache as any).assets = assets;
|
||||
return new PSO.Molecule.Structure(structure, { label: packing.name });
|
||||
return new PSO.Molecule.Structure(structure, { label: packing.name + '.' + packing.location });
|
||||
});
|
||||
},
|
||||
dispose({ b, cache }) {
|
||||
const assets = (cache as any).assets as Asset.Wrapper[];
|
||||
if(assets) {
|
||||
if (assets) {
|
||||
for (const a of assets) a.dispose();
|
||||
}
|
||||
|
||||
@@ -115,17 +266,17 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
|
||||
// TODO: optimze
|
||||
// TODO: think of ways how to fast-track changes to this for animations
|
||||
const model = a.data;
|
||||
let initial_structure = Structure.ofModel(model);
|
||||
const initial_structure = Structure.ofModel(model);
|
||||
const structures: Structure[] = [];
|
||||
let structure: Structure = initial_structure;
|
||||
// the list of asambly *?
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
if (symmetry && symmetry.assemblies.length !== 0){
|
||||
if (symmetry && symmetry.assemblies.length !== 0) {
|
||||
for (const a of symmetry.assemblies) {
|
||||
const s = await StructureSymmetry.buildAssembly(initial_structure, a.id).runInContext(ctx);
|
||||
structures.push(s);
|
||||
}
|
||||
const builder = Structure.Builder({ label: name });
|
||||
const builder = Structure.Builder({ label: 'Membrane' });
|
||||
let offsetInvariantId = 0;
|
||||
for (const s of structures) {
|
||||
let maxInvariantId = 0;
|
||||
@@ -137,7 +288,7 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
|
||||
offsetInvariantId += maxInvariantId + 1;
|
||||
}
|
||||
structure = builder.getStructure();
|
||||
for( let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
for (let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
|
||||
}
|
||||
}
|
||||
@@ -148,3 +299,28 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
const CreateTransformer = StateTransformer.builderFactory('cellPACK');
|
||||
export const CreateCompartmentSphere = CreateTransformer({
|
||||
name: 'create-compartment-sphere',
|
||||
display: 'CompartmentSphere',
|
||||
from: PSO.Root, // or whatever data source
|
||||
to: PSO.Shape.Representation3D,
|
||||
params: {
|
||||
center: PD.Vec3(Vec3()),
|
||||
radius: PD.Numeric(1),
|
||||
label: PD.Text(`Compartment Sphere`)
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ oldParams, newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Compartment Sphere', async ctx => {
|
||||
const data = params;
|
||||
const repr = MBRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => (MBParams));
|
||||
await repr.createOrUpdate({ ...params, quality: 'custom', xrayShaded: true, doubleSided: true }, data).runInContext(ctx);
|
||||
return new PSO.Shape.Representation3D({ repr, sourceData: a }, { label: data.label });
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { CIF } from '../../mol-io/reader/cif';
|
||||
@@ -37,11 +38,11 @@ async function downloadPDB(plugin: PluginContext, url: string, id: string, 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);
|
||||
const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId}.bcif`, true, assetManager);
|
||||
return { mmcif: cif.blocks[0], asset };
|
||||
}
|
||||
|
||||
export async function getFromOPM(plugin: PluginContext, pdbId: string, assetManager: AssetManager){
|
||||
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 };
|
||||
}
|
||||
@@ -74,4 +75,35 @@ export function getStructureMean(structure: Structure) {
|
||||
}
|
||||
const { elementCount } = structure;
|
||||
return Vec3.create(xSum / elementCount, ySum / elementCount, zSum / elementCount);
|
||||
}
|
||||
|
||||
export function getFloatValue(value: DataView, offset: number) {
|
||||
// if the last byte is a negative value (MSB is 1), the final
|
||||
// float should be too
|
||||
const negative = value.getInt8(offset + 2) >>> 31;
|
||||
|
||||
// this is how the bytes are arranged in the byte array/DataView
|
||||
// buffer
|
||||
const [b0, b1, b2, exponent] = [
|
||||
// get first three bytes as unsigned since we only care
|
||||
// about the last 8 bits of 32-bit js number returned by
|
||||
// getUint8().
|
||||
// Should be the same as: getInt8(offset) & -1 >>> 24
|
||||
value.getUint8(offset),
|
||||
value.getUint8(offset + 1),
|
||||
value.getUint8(offset + 2),
|
||||
|
||||
// get the last byte, which is the exponent, as a signed int
|
||||
// since it's already correct
|
||||
value.getInt8(offset + 3)
|
||||
];
|
||||
|
||||
let mantissa = b0 | (b1 << 8) | (b2 << 16);
|
||||
if (negative) {
|
||||
// need to set the most significant 8 bits to 1's since a js
|
||||
// number is 32 bits but our mantissa is only 24.
|
||||
mantissa |= 255 << 24;
|
||||
}
|
||||
|
||||
return mantissa * Math.pow(10, exponent);
|
||||
}
|
||||
@@ -41,10 +41,10 @@ export const DnatcoConfalPyramidsPreset = StructureRepresentationPresetProvider(
|
||||
|
||||
let pyramidsRepr;
|
||||
if (representations)
|
||||
pyramidsRepr = builder.buildRepresentation(update, pyramids, { type: ConfalPyramidsRepresentationProvider, typeParams, color: ConfalPyramidsColorThemeProvider }, { tag: 'confal-pyramdis' } );
|
||||
pyramidsRepr = builder.buildRepresentation(update, pyramids, { type: ConfalPyramidsRepresentationProvider, typeParams, color: ConfalPyramidsColorThemeProvider }, { tag: 'confal-pyramdis' });
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
return { components: { ...components, pyramids }, representations: { ...representations, pyramidsRepr } };
|
||||
return { components: { ...components, pyramids }, representations: { ...representations, pyramidsRepr } };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -66,7 +66,7 @@ export const DnatcoConfalPyramids = PluginBehavior.create<{ autoAttach: boolean,
|
||||
/* TODO: Implement this */
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
register(): void {
|
||||
this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
|
||||
|
||||
@@ -27,7 +27,7 @@ const ColorMapping: ReadonlyMap<ConformerClasses, Color> = new Map([
|
||||
['B', Color(0xC8CFFF)],
|
||||
['BII', Color(0x0059DA)],
|
||||
['miB', Color(0x3BE8FB)],
|
||||
['Z', Color(0x01F60E)],
|
||||
['Z', Color(0x01F60E)],
|
||||
['IC', Color(0xFA5CFB)],
|
||||
['OPN', Color(0xE90000)],
|
||||
['SYN', Color(0xFFFF01)],
|
||||
@@ -165,8 +165,8 @@ export function ConfalPyramidsColorTheme(ctx: ThemeDataContext, props: PD.Values
|
||||
legend: TableLegend(iterableToArray(ColorMapping.entries()).map(([conformer, color]) => {
|
||||
return [conformer, color] as [string, Color];
|
||||
}).concat([
|
||||
[ 'Error', ErrorColor ],
|
||||
[ 'Unknown', DefaultColor ]
|
||||
['Error', ErrorColor],
|
||||
['Unknown', DefaultColor]
|
||||
]))
|
||||
};
|
||||
}
|
||||
@@ -181,6 +181,6 @@ export const ConfalPyramidsColorThemeProvider: ColorTheme.Provider<ConfalPyramid
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => ConfalPyramids.isApplicable(m)),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ConfalPyramidsProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ConfalPyramidsProvider.descriptor, false)
|
||||
detach: (data) => data.structure && ConfalPyramidsProvider.ref(data.structure.models[0], false)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,10 +20,10 @@ import { Structure, StructureProperties, Unit } from '../../../mol-model/structu
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
|
||||
import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder, UnitsRepresentation } from '../../../mol-repr/structure/representation';
|
||||
import { StructureGroup, UnitsMeshParams, UnitsMeshVisual, UnitsVisual } from '../../../mol-repr/structure/units-visual';
|
||||
import { UnitsMeshParams, UnitsMeshVisual, UnitsVisual } from '../../../mol-repr/structure/units-visual';
|
||||
import { VisualUpdateState } from '../../../mol-repr/util';
|
||||
import { VisualContext } from '../../../mol-repr/visual';
|
||||
import { getAltResidueLociFromId } from '../../../mol-repr/structure/visual/util/common';
|
||||
import { getAltResidueLociFromId, StructureGroup } from '../../../mol-repr/structure/visual/util/common';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Theme, ThemeRegistryContext } from '../../../mol-theme/theme';
|
||||
import { NullLocation } from '../../../mol-model/location';
|
||||
@@ -55,7 +55,7 @@ function createConfalPyramidsIterator(structureGroup: StructureGroup): LocationI
|
||||
|
||||
const prop = ConfalPyramidsProvider.get(structure.model).value;
|
||||
if (prop === undefined || prop.data === undefined) {
|
||||
return LocationIterator(0, 1, () => NullLocation);
|
||||
return LocationIterator(0, 1, 1, () => NullLocation);
|
||||
}
|
||||
|
||||
const { locations } = prop.data;
|
||||
@@ -64,7 +64,7 @@ function createConfalPyramidsIterator(structureGroup: StructureGroup): LocationI
|
||||
if (locations.length <= groupIndex) return NullLocation;
|
||||
return locations[groupIndex];
|
||||
};
|
||||
return LocationIterator(locations.length, instanceCount, getLocation);
|
||||
return LocationIterator(locations.length, instanceCount, 1, getLocation);
|
||||
}
|
||||
|
||||
function createConfalPyramidsMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<ConfalPyramidsMeshParams>, mesh?: Mesh) {
|
||||
|
||||
@@ -95,7 +95,7 @@ export namespace ConfalPyramidsUtil {
|
||||
const first = residueInfoFromLocation(locFirst);
|
||||
const second = residueInfoFromLocation(locSecond);
|
||||
const model_id = this.hasMultipleModels ? `-m${modelNum}` : '';
|
||||
const alt_id_1 = fakeAltId_1 !== '' ? `.${fakeAltId_1}` : (first.alt_id.length ? `.${first.alt_id}` : '');
|
||||
const alt_id_1 = fakeAltId_1 !== '' ? `.${fakeAltId_1}` : (first.alt_id.length ? `.${first.alt_id}` : '');
|
||||
const alt_id_2 = fakeAltId_2 !== '' ? `.${fakeAltId_2}` : (second.alt_id.length ? `.${second.alt_id}` : '');
|
||||
const ins_code_1 = first.ins_code.length ? `.${first.ins_code}` : '';
|
||||
const ins_code_2 = second.ins_code.length ? `.${second.ins_code}` : '';
|
||||
@@ -114,7 +114,7 @@ export namespace ConfalPyramidsUtil {
|
||||
this.modelNum = unit.model.modelNum;
|
||||
}
|
||||
|
||||
protected readonly data: CPT.PyramidsData
|
||||
protected readonly data: CPT.PyramidsData;
|
||||
protected readonly hasMultipleModels: boolean;
|
||||
protected readonly entryId: string;
|
||||
protected readonly modelNum: number;
|
||||
|
||||
66
src/extensions/g3d/data.ts
Normal file
66
src/extensions/g3d/data.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { decodeMsgPack } from '../../mol-io/common/msgpack/decode';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Task } from '../../mol-task';
|
||||
import { inflate } from '../../mol-util/zip/zip';
|
||||
|
||||
export interface G3dHeader {
|
||||
magic: 'G3D',
|
||||
version: number,
|
||||
genome: string,
|
||||
name: string,
|
||||
offsets: { [resolution: string]: { offset: number, size: number } },
|
||||
resolutions: number[]
|
||||
}
|
||||
|
||||
export type G3dDataBlock = {
|
||||
header: G3dHeader,
|
||||
resolution: number,
|
||||
data: {
|
||||
[haplotype: string]: {
|
||||
[ch: string]: {
|
||||
start: number[]
|
||||
x: number[],
|
||||
y: number[],
|
||||
z: number[],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HEADER_SIZE = 64000;
|
||||
|
||||
export async function getG3dHeader(ctx: PluginContext, urlOrData: string | Uint8Array): Promise<G3dHeader> {
|
||||
const data: Uint8Array = await getRawData(ctx, urlOrData, { offset: 0, size: HEADER_SIZE });
|
||||
let last = data.length - 1;
|
||||
for (; last >= 0; last--) {
|
||||
if (data[last] !== 0) break;
|
||||
}
|
||||
const header = decodeMsgPack(data.slice(0, last + 1));
|
||||
return header;
|
||||
}
|
||||
|
||||
export async function getG3dDataBlock(ctx: PluginContext, header: G3dHeader, urlOrData: string | Uint8Array, resolution: number): Promise<G3dDataBlock> {
|
||||
if (!header.offsets[resolution]) throw new Error(`Resolution ${resolution} not available.`);
|
||||
const data = await getRawData(ctx, urlOrData, header.offsets[resolution]);
|
||||
const unzipped = await ctx.runTask(Task.create('Unzip', ctx => inflate(ctx, data)));
|
||||
|
||||
return {
|
||||
header,
|
||||
resolution,
|
||||
data: decodeMsgPack(unzipped)
|
||||
};
|
||||
}
|
||||
|
||||
async function getRawData(ctx: PluginContext, urlOrData: string | Uint8Array, range: { offset: number, size: number }) {
|
||||
if (typeof urlOrData === 'string') {
|
||||
return await ctx.runTask(ctx.fetch({ url: urlOrData, headers: [['Range', `bytes=${range.offset}-${range.offset + range.size - 1}`]], type: 'binary' }));
|
||||
} else {
|
||||
return urlOrData.slice(range.offset, range.offset + range.size);
|
||||
}
|
||||
}
|
||||
173
src/extensions/g3d/format.ts
Normal file
173
src/extensions/g3d/format.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright (c) 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 { Trajectory } from '../../mol-model/structure';
|
||||
import { TrajectoryFormatCategory, TrajectoryFormatProvider } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { DefaultQueryRuntimeTable } from '../../mol-script/runtime/query/base';
|
||||
import { StateAction, StateObjectRef } from '../../mol-state';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
import { G3dHeader, getG3dDataBlock, getG3dHeader } from './data';
|
||||
import { g3dHaplotypeQuery, G3dLabelProvider, trajectoryFromG3D, G3dSymbols, G3dInfoDataProperty } from './model';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
import { stringToWords } from '../../mol-util/string';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
|
||||
export const G3dProvider: TrajectoryFormatProvider = {
|
||||
label: 'G3D',
|
||||
description: 'G3D',
|
||||
category: TrajectoryFormatCategory,
|
||||
binaryExtensions: ['g3d'],
|
||||
parse: async (plugin, data) => {
|
||||
const trajectory = await plugin.state.data.build()
|
||||
.to(data)
|
||||
.apply(G3DHeaderFromFile, {}, { state: { isGhost: true } })
|
||||
.apply(G3DTrajectory)
|
||||
.commit();
|
||||
|
||||
return { trajectory };
|
||||
},
|
||||
visuals: defaultStructure
|
||||
};
|
||||
|
||||
async function defaultStructure(plugin: PluginContext, data: { trajectory: StateObjectRef<SO.Molecule.Trajectory> }) {
|
||||
const builder = plugin.builders.structure;
|
||||
const model = await builder.createModel(data.trajectory);
|
||||
|
||||
if (!model) return;
|
||||
const structure = await builder.createStructure(model);
|
||||
|
||||
const info = G3dInfoDataProperty.get(model.data!);
|
||||
if (!info) return;
|
||||
|
||||
const components = plugin.build().to(structure);
|
||||
|
||||
const repr = createStructureRepresentationParams(plugin, void 0, {
|
||||
type: 'cartoon',
|
||||
color: 'polymer-index',
|
||||
size: 'uniform',
|
||||
sizeParams: { value: 0.25 }
|
||||
});
|
||||
|
||||
for (const h of info.haplotypes) {
|
||||
components
|
||||
.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: g3dHaplotypeQuery(h), label: stringToWords(h) })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D, repr);
|
||||
}
|
||||
|
||||
await components.commit();
|
||||
}
|
||||
|
||||
export class G3dHeaderObject extends SO.Create<{
|
||||
header: G3dHeader,
|
||||
urlOrData: Uint8Array | string,
|
||||
cache: { [resolution: number]: Trajectory | undefined }
|
||||
}>({ name: 'G3D Header', typeClass: 'Data' }) { }
|
||||
|
||||
export type G3DHeaderFromFile = typeof G3DHeaderFromFile
|
||||
export const G3DHeaderFromFile = PluginStateTransform.BuiltIn({
|
||||
name: 'g3d-header-from-file',
|
||||
display: { name: 'G3D Header', description: 'Parse G3D Header' },
|
||||
from: SO.Data.Binary,
|
||||
to: G3dHeaderObject
|
||||
})({
|
||||
apply({ a }, plugin: PluginContext) {
|
||||
return Task.create('Parse G3D', async () => {
|
||||
const header = await getG3dHeader(plugin, a.data);
|
||||
return new G3dHeaderObject({ header, urlOrData: a.data, cache: { } }, { label: header.name, description: header.genome });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type G3DHeaderFromUrl = typeof G3DHeaderFromUrl
|
||||
export const G3DHeaderFromUrl = PluginStateTransform.BuiltIn({
|
||||
name: 'g3d-header-from-url',
|
||||
display: { name: 'G3D Header', description: 'Parse G3D Header' },
|
||||
params: { url: ParamDefinition.Text('') },
|
||||
from: SO.Root,
|
||||
to: G3dHeaderObject
|
||||
})({
|
||||
apply({ params }, plugin: PluginContext) {
|
||||
return Task.create('Parse G3D', async () => {
|
||||
const header = await getG3dHeader(plugin, params.url);
|
||||
return new G3dHeaderObject({ header, urlOrData: params.url, cache: { } }, { label: header.name, description: header.genome });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type G3DTrajectory = typeof G3DHeaderFromUrl
|
||||
export const G3DTrajectory = PluginStateTransform.BuiltIn({
|
||||
name: 'g3d-trajecotry',
|
||||
display: { name: 'G3D Trajectory', description: 'Create G3D Trajectory' },
|
||||
params: a => {
|
||||
if (!a) return { resolution: ParamDefinition.Numeric(200000) };
|
||||
const rs = a.data.header.resolutions;
|
||||
return {
|
||||
resolution: ParamDefinition.Select(rs[rs.length - 1], rs.map(r => [r, '' + r] as const))
|
||||
};
|
||||
},
|
||||
from: G3dHeaderObject,
|
||||
to: SO.Molecule.Trajectory
|
||||
})({
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('G3D Trajectory', async ctx => {
|
||||
if (a.data.cache[params.resolution]) {
|
||||
return new SO.Molecule.Trajectory(a.data.cache[params.resolution]!, { label: a.label, description: a.description });
|
||||
}
|
||||
const data = await getG3dDataBlock(plugin, a.data.header, a.data.urlOrData, params.resolution);
|
||||
const traj = await trajectoryFromG3D(data).runInContext(ctx);
|
||||
a.data.cache[params.resolution] = traj;
|
||||
return new SO.Molecule.Trajectory(traj, { label: a.label, description: a.description });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const LoadG3D = StateAction.build({
|
||||
display: { name: 'Load Genome 3D (G3D)', description: 'Load G3D file from the specified URL.' },
|
||||
from: SO.Root,
|
||||
params: { url: ParamDefinition.Text('') }
|
||||
})(({ params, state }, ctx: PluginContext) => Task.create('Genome3D', taskCtx => {
|
||||
return state.transaction(async () => {
|
||||
if (params.url.trim().length === 0) {
|
||||
throw new Error('Specify URL');
|
||||
}
|
||||
|
||||
ctx.behaviors.layout.leftPanelTabName.next('data');
|
||||
|
||||
const trajectory = await state.build().toRoot()
|
||||
.apply(G3DHeaderFromUrl, { url: params.url })
|
||||
.apply(G3DTrajectory)
|
||||
.commit();
|
||||
|
||||
await defaultStructure(ctx, { trajectory });
|
||||
}).runInContext(taskCtx);
|
||||
}));
|
||||
|
||||
export const G3DFormat = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
|
||||
name: 'g3d',
|
||||
category: 'misc',
|
||||
display: {
|
||||
name: 'G3D',
|
||||
description: 'G3D Format Support'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
|
||||
register() {
|
||||
this.ctx.state.data.actions.add(LoadG3D);
|
||||
objectForEach(G3dSymbols, s => DefaultQueryRuntimeTable.addSymbol(s));
|
||||
this.ctx.managers.lociLabels.addProvider(G3dLabelProvider);
|
||||
}
|
||||
unregister() {
|
||||
this.ctx.state.data.actions.remove(LoadG3D);
|
||||
objectForEach(G3dSymbols, s => DefaultQueryRuntimeTable.removeSymbol(s));
|
||||
this.ctx.managers.lociLabels.removeProvider(G3dLabelProvider);
|
||||
}
|
||||
}
|
||||
});
|
||||
245
src/extensions/g3d/model.ts
Normal file
245
src/extensions/g3d/model.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { createModels } from '../../mol-model-formats/structure/basic/parser';
|
||||
import { BasicSchema, createBasic } from '../../mol-model-formats/structure/basic/schema';
|
||||
import { EntityBuilder } from '../../mol-model-formats/structure/common/entity';
|
||||
import { Loci } from '../../mol-model/loci';
|
||||
import { Trajectory, Unit } from '../../mol-model/structure';
|
||||
import { MoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { LociLabelProvider } from '../../mol-plugin-state/manager/loci-label';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { CustomPropSymbol } from '../../mol-script/language/symbol';
|
||||
import { Type } from '../../mol-script/language/type';
|
||||
import { QuerySymbolRuntime } from '../../mol-script/runtime/query/base';
|
||||
import { RuntimeContext, Task } from '../../mol-task';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
import { G3dDataBlock } from './data';
|
||||
import { FormatPropertyProvider } from '../../mol-model-formats/structure/common/property';
|
||||
|
||||
interface NormalizedData {
|
||||
entity_id: string[],
|
||||
chromosome: string[],
|
||||
seq_id_begin: Int32Array,
|
||||
seq_id_end: Int32Array,
|
||||
start: Int32Array,
|
||||
x: Float32Array,
|
||||
y: Float32Array,
|
||||
z: Float32Array,
|
||||
r: Float32Array,
|
||||
haplotype: string[]
|
||||
}
|
||||
|
||||
function getColumns(block: G3dDataBlock) {
|
||||
const { data } = block;
|
||||
let size = 0;
|
||||
|
||||
objectForEach(data, h => objectForEach(h, g => size += g.start.length));
|
||||
|
||||
const normalized: NormalizedData = {
|
||||
entity_id: new Array(size),
|
||||
chromosome: new Array(size),
|
||||
seq_id_begin: new Int32Array(size),
|
||||
seq_id_end: new Int32Array(size),
|
||||
start: new Int32Array(size),
|
||||
x: new Float32Array(size),
|
||||
y: new Float32Array(size),
|
||||
z: new Float32Array(size),
|
||||
r: new Float32Array(size),
|
||||
haplotype: new Array(size)
|
||||
};
|
||||
|
||||
const p = [Vec3(), Vec3(), Vec3()];
|
||||
|
||||
let o = 0;
|
||||
objectForEach(data, (hs, h) => {
|
||||
objectForEach(hs, (chs, ch) => {
|
||||
const entity_id = `${ch}-${h}`;
|
||||
const l = chs.start.length;
|
||||
if (l === 0) return;
|
||||
|
||||
let x = chs.x[0];
|
||||
let y = chs.y[0];
|
||||
let z = chs.z[0];
|
||||
|
||||
Vec3.set(p[0], x, y, z);
|
||||
Vec3.set(p[2], x, y, z);
|
||||
|
||||
for (let i = 0; i < l; i++) {
|
||||
normalized.entity_id[o] = entity_id;
|
||||
normalized.chromosome[o] = ch;
|
||||
normalized.start[o] = chs.start[i];
|
||||
normalized.seq_id_begin[o] = o;
|
||||
normalized.seq_id_end[o] = o;
|
||||
|
||||
x = chs.x[i];
|
||||
y = chs.y[i];
|
||||
z = chs.z[i];
|
||||
|
||||
Vec3.set(p[1], x, y, z);
|
||||
if (i + 1 < l) Vec3.set(p[2], chs.x[i + 1], chs.y[i + 1], chs.z[i + 1]);
|
||||
else Vec3.set(p[2], x, y, z);
|
||||
|
||||
normalized.x[o] = x;
|
||||
normalized.y[o] = y;
|
||||
normalized.z[o] = z;
|
||||
normalized.r[o] = 2 / 3 * Math.min(Vec3.distance(p[0], p[1]), Vec3.distance(p[1], p[2]));
|
||||
normalized.haplotype[o] = h;
|
||||
|
||||
const _p = p[0];
|
||||
p[0] = p[1];
|
||||
p[1] = _p;
|
||||
o++;
|
||||
}
|
||||
|
||||
if (l === 1) {
|
||||
normalized.r[o - 1] = 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
async function getTraj(ctx: RuntimeContext, data: G3dDataBlock) {
|
||||
const normalized = getColumns(data);
|
||||
|
||||
const rowCount = normalized.seq_id_begin.length;
|
||||
const entityIds = new Array<string>(rowCount);
|
||||
const entityBuilder = new EntityBuilder();
|
||||
|
||||
const eName = { customName: '' };
|
||||
for (let i = 0; i < rowCount; ++i) {
|
||||
const e = normalized.entity_id[i];
|
||||
eName.customName = e;
|
||||
const entityId = entityBuilder.getEntityId(e, MoleculeType.DNA, e, eName);
|
||||
entityIds[i] = entityId;
|
||||
}
|
||||
|
||||
const ihm_sphere_obj_site = Table.ofPartialColumns(BasicSchema.ihm_sphere_obj_site, {
|
||||
id: Column.range(0, rowCount),
|
||||
entity_id: Column.ofStringArray(entityIds),
|
||||
seq_id_begin: Column.ofIntArray(normalized.seq_id_begin),
|
||||
seq_id_end: Column.ofIntArray(normalized.seq_id_end),
|
||||
asym_id: Column.ofStringArray(normalized.chromosome),
|
||||
|
||||
Cartn_x: Column.ofFloatArray(normalized.x),
|
||||
Cartn_y: Column.ofFloatArray(normalized.y),
|
||||
Cartn_z: Column.ofFloatArray(normalized.z),
|
||||
|
||||
object_radius: Column.ofFloatArray(normalized.r),
|
||||
rmsf: Column.ofConst(0, rowCount, Column.Schema.float),
|
||||
model_id: Column.ofConst(1, rowCount, Column.Schema.int),
|
||||
}, rowCount);
|
||||
|
||||
const basic = createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
ihm_model_list: Table.ofPartialColumns(BasicSchema.ihm_model_list, {
|
||||
model_id: Column.ofIntArray([1]),
|
||||
model_name: Column.ofStringArray(['G3D Model']),
|
||||
}, 1),
|
||||
ihm_sphere_obj_site
|
||||
});
|
||||
|
||||
const models = await createModels(basic, { kind: 'g3d', name: 'G3D', data }, ctx);
|
||||
|
||||
G3dInfoDataProperty.set(models.representative, {
|
||||
haplotypes: Object.keys(data.data),
|
||||
haplotype: normalized.haplotype,
|
||||
resolution: data.resolution,
|
||||
start: normalized.start,
|
||||
chroms: normalized.chromosome,
|
||||
});
|
||||
|
||||
return models;
|
||||
}
|
||||
|
||||
export function trajectoryFromG3D(data: G3dDataBlock): Task<Trajectory> {
|
||||
return Task.create('Parse G3D', async ctx => {
|
||||
return getTraj(ctx, data);
|
||||
});
|
||||
}
|
||||
|
||||
export const G3dSymbols = {
|
||||
haplotype: QuerySymbolRuntime.Dynamic(CustomPropSymbol('g3d', 'haplotype', Type.Str),
|
||||
ctx => {
|
||||
if (Unit.isAtomic(ctx.element.unit)) return '';
|
||||
const info = (G3dInfoDataProperty as any).get(ctx.element.unit.model);
|
||||
if (!info) return '';
|
||||
const seqId = ctx.element.unit.model.coarseHierarchy.spheres.seq_id_begin.value(ctx.element.element);
|
||||
return info.haplotype[seqId] || '';
|
||||
}
|
||||
),
|
||||
chromosome: QuerySymbolRuntime.Dynamic(CustomPropSymbol('g3d', 'chromosome', Type.Str),
|
||||
ctx => {
|
||||
if (Unit.isAtomic(ctx.element.unit)) return '';
|
||||
const { asym_id } = ctx.element.unit.model.coarseHierarchy.spheres;
|
||||
return asym_id.value(ctx.element.element) || '';
|
||||
}
|
||||
),
|
||||
region: QuerySymbolRuntime.Dynamic(CustomPropSymbol('g3d', 'region', Type.Num),
|
||||
ctx => {
|
||||
if (Unit.isAtomic(ctx.element.unit)) return '';
|
||||
const info = (G3dInfoDataProperty as any).get(ctx.element.unit.model);
|
||||
if (!info) return 0;
|
||||
const seqId = ctx.element.unit.model.coarseHierarchy.spheres.seq_id_begin.value(ctx.element.element);
|
||||
return info.start[seqId] || 0;
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
export const G3dInfoDataProperty = FormatPropertyProvider.create<G3dInfoData>({ name: 'g3d_info' });
|
||||
|
||||
export function g3dHaplotypeQuery(haplotype: string) {
|
||||
return MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([G3dSymbols.haplotype.symbol(), haplotype]),
|
||||
});
|
||||
}
|
||||
|
||||
export function g3dChromosomeQuery(chr: string) {
|
||||
return MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp('objectPrimitive'), 'sphere']),
|
||||
MS.core.rel.eq([G3dSymbols.chromosome.symbol(), chr])
|
||||
])
|
||||
});
|
||||
}
|
||||
|
||||
export function g3dRegionQuery(chr: string, start: number, end: number) {
|
||||
return MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp('objectPrimitive'), 'sphere']),
|
||||
MS.core.rel.eq([G3dSymbols.chromosome.symbol(), chr])
|
||||
]),
|
||||
'residue-test': MS.core.rel.inRange([G3dSymbols.region.symbol(), start, end])
|
||||
});
|
||||
}
|
||||
|
||||
export interface G3dInfoData {
|
||||
haplotypes: string[],
|
||||
haplotype: string[],
|
||||
start: Int32Array,
|
||||
resolution: number,
|
||||
chroms: string[]
|
||||
};
|
||||
|
||||
export const G3dLabelProvider: LociLabelProvider = {
|
||||
label: (e: Loci): string | undefined => {
|
||||
if (e.kind !== 'element-loci' || Loci.isEmpty(e)) return;
|
||||
|
||||
const first = e.elements[0];
|
||||
if (e.elements.length !== 1 || Unit.isAtomic(first.unit)) return;
|
||||
const info = G3dInfoDataProperty.get(first.unit.model);
|
||||
if (!info) return;
|
||||
|
||||
const eI = first.unit.elements[OrderedSet.getAt(first.indices, 0)];
|
||||
const seqId = first.unit.model.coarseHierarchy.spheres.seq_id_begin.value(eI);
|
||||
return `<b>Start:</b> ${info.start[seqId]} <small>| resolution ${info.resolution}<small>`;
|
||||
}
|
||||
};
|
||||
89
src/extensions/geo-export/controls.ts
Normal file
89
src/extensions/geo-export/controls.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
*/
|
||||
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { PluginComponent } from '../../mol-plugin-state/component';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Task } from '../../mol-task';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { StateSelection } from '../../mol-state';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { SetUtils } from '../../mol-util/set';
|
||||
import { GlbExporter } from './glb-exporter';
|
||||
import { ObjExporter } from './obj-exporter';
|
||||
import { StlExporter } from './stl-exporter';
|
||||
import { UsdzExporter } from './usdz-exporter';
|
||||
|
||||
export const GeometryParams = {
|
||||
format: PD.Select('glb', [
|
||||
['glb', 'glTF 2.0 Binary (.glb)'],
|
||||
['stl', 'Stl (.stl)'],
|
||||
['obj', 'Wavefront (.obj)'],
|
||||
['usdz', 'Universal Scene Description (.usdz)']
|
||||
])
|
||||
};
|
||||
|
||||
export class GeometryControls extends PluginComponent {
|
||||
readonly behaviors = {
|
||||
params: this.ev.behavior<PD.Values<typeof GeometryParams>>(PD.getDefaultValues(GeometryParams))
|
||||
};
|
||||
|
||||
private getFilename() {
|
||||
const models = this.plugin.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Model)).map(s => s.obj!.data);
|
||||
const uniqueIds = new Set<string>();
|
||||
models.forEach(m => uniqueIds.add(m.entryId.toUpperCase()));
|
||||
const idString = SetUtils.toArray(uniqueIds).join('-');
|
||||
return `${idString || 'molstar-model'}`;
|
||||
}
|
||||
|
||||
exportGeometry() {
|
||||
const task = Task.create('Export Geometry', async ctx => {
|
||||
try {
|
||||
const renderObjects = this.plugin.canvas3d?.getRenderObjects()!;
|
||||
const filename = this.getFilename();
|
||||
|
||||
const boundingSphere = this.plugin.canvas3d?.boundingSphereVisible!;
|
||||
const boundingBox = Box3D.fromSphere3D(Box3D(), boundingSphere);
|
||||
let renderObjectExporter: GlbExporter | ObjExporter | StlExporter | UsdzExporter;
|
||||
switch (this.behaviors.params.value.format) {
|
||||
case 'glb':
|
||||
renderObjectExporter = new GlbExporter(boundingBox);
|
||||
break;
|
||||
case 'obj':
|
||||
renderObjectExporter = new ObjExporter(filename, boundingBox);
|
||||
break;
|
||||
case 'stl':
|
||||
renderObjectExporter = new StlExporter(boundingBox);
|
||||
break;
|
||||
case 'usdz':
|
||||
renderObjectExporter = new UsdzExporter(boundingBox, boundingSphere.radius);
|
||||
break;
|
||||
default: throw new Error('Unsupported format.');
|
||||
}
|
||||
|
||||
for (let i = 0, il = renderObjects.length; i < il; ++i) {
|
||||
await ctx.update({ message: `Exporting object ${i}/${il}` });
|
||||
await renderObjectExporter.add(renderObjects[i], this.plugin.canvas3d?.webgl!, ctx);
|
||||
}
|
||||
|
||||
const blob = await renderObjectExporter.getBlob(ctx);
|
||||
return {
|
||||
blob,
|
||||
filename: filename + '.' + renderObjectExporter.fileExtension
|
||||
};
|
||||
} catch (e) {
|
||||
this.plugin.log.error('Error during geometry export');
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
return this.plugin.runTask(task, { useOverlay: true });
|
||||
}
|
||||
|
||||
constructor(private plugin: PluginContext) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
341
src/extensions/geo-export/glb-exporter.ts
Normal file
341
src/extensions/geo-export/glb-exporter.ts
Normal file
@@ -0,0 +1,341 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { asciiWrite } from '../../mol-io/common/ascii';
|
||||
import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { PLUGIN_VERSION } from '../../mol-plugin/version';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { Color } from '../../mol-util/color/color';
|
||||
import { fillSerial } from '../../mol-util/array';
|
||||
import { NumberArray } from '../../mol-util/type-helpers';
|
||||
import { MeshExporter, AddMeshInput, MeshGeoData } from './mesh-exporter';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3fromArray = Vec3.fromArray;
|
||||
const v3normalize = Vec3.normalize;
|
||||
const v3toArray = Vec3.toArray;
|
||||
|
||||
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
|
||||
|
||||
const UNSIGNED_BYTE = 5121;
|
||||
const UNSIGNED_INT = 5125;
|
||||
const FLOAT = 5126;
|
||||
const ARRAY_BUFFER = 34962;
|
||||
const ELEMENT_ARRAY_BUFFER = 34963;
|
||||
|
||||
const GLTF_MAGIC_BYTE = 0x46546C67;
|
||||
const JSON_CHUNK_TYPE = 0x4E4F534A;
|
||||
const BIN_CHUNK_TYPE = 0x004E4942;
|
||||
const JSON_PAD_CHAR = 0x20;
|
||||
const BIN_PAD_CHAR = 0x00;
|
||||
|
||||
export type GlbData = {
|
||||
glb: Uint8Array
|
||||
}
|
||||
|
||||
export class GlbExporter extends MeshExporter<GlbData> {
|
||||
readonly fileExtension = 'glb';
|
||||
private nodes: Record<string, any>[] = [];
|
||||
private meshes: Record<string, any>[] = [];
|
||||
private materials: Record<string, any>[] = [];
|
||||
private materialMap = new Map<string, number>();
|
||||
private accessors: Record<string, any>[] = [];
|
||||
private bufferViews: Record<string, any>[] = [];
|
||||
private binaryBuffer: ArrayBuffer[] = [];
|
||||
private byteOffset = 0;
|
||||
private centerTransform: Mat4;
|
||||
|
||||
private static vec3MinMax(a: NumberArray) {
|
||||
const min: number[] = [Infinity, Infinity, Infinity];
|
||||
const max: number[] = [-Infinity, -Infinity, -Infinity];
|
||||
for (let i = 0, il = a.length; i < il; i += 3) {
|
||||
for (let j = 0; j < 3; ++j) {
|
||||
min[j] = Math.min(a[i + j], min[j]);
|
||||
max[j] = Math.max(a[i + j], max[j]);
|
||||
}
|
||||
}
|
||||
return [min, max];
|
||||
}
|
||||
|
||||
private addBuffer(buffer: ArrayBuffer, componentType: number, type: string, count: number, target: number, min?: any, max?: any, normalized?: boolean) {
|
||||
this.binaryBuffer.push(buffer);
|
||||
|
||||
const bufferViewOffset = this.bufferViews.length;
|
||||
this.bufferViews.push({
|
||||
buffer: 0,
|
||||
byteOffset: this.byteOffset,
|
||||
byteLength: buffer.byteLength,
|
||||
target
|
||||
});
|
||||
this.byteOffset += buffer.byteLength;
|
||||
|
||||
const accessorOffset = this.accessors.length;
|
||||
this.accessors.push({
|
||||
bufferView: bufferViewOffset,
|
||||
byteOffset: 0,
|
||||
componentType,
|
||||
count,
|
||||
type,
|
||||
min,
|
||||
max,
|
||||
normalized
|
||||
});
|
||||
return accessorOffset;
|
||||
}
|
||||
|
||||
private addGeometryBuffers(vertices: Float32Array, normals: Float32Array, indices: Uint32Array | undefined, vertexCount: number, drawCount: number, isGeoTexture: boolean) {
|
||||
const tmpV = Vec3();
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
|
||||
const vertexArray = new Float32Array(vertexCount * 3);
|
||||
const normalArray = new Float32Array(vertexCount * 3);
|
||||
let indexArray: Uint32Array | undefined;
|
||||
|
||||
// position
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
v3fromArray(tmpV, vertices, i * stride);
|
||||
v3toArray(tmpV, vertexArray, i * 3);
|
||||
}
|
||||
|
||||
// normal
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
v3fromArray(tmpV, normals, i * stride);
|
||||
v3normalize(tmpV, tmpV);
|
||||
v3toArray(tmpV, normalArray, i * 3);
|
||||
}
|
||||
|
||||
// face
|
||||
if (!isGeoTexture) {
|
||||
indexArray = indices!.slice(0, drawCount);
|
||||
}
|
||||
|
||||
const [vertexMin, vertexMax] = GlbExporter.vec3MinMax(vertexArray);
|
||||
|
||||
let vertexBuffer = vertexArray.buffer;
|
||||
let normalBuffer = normalArray.buffer;
|
||||
let indexBuffer = isGeoTexture ? undefined : indexArray!.buffer;
|
||||
if (!IsNativeEndianLittle) {
|
||||
vertexBuffer = flipByteOrder(new Uint8Array(vertexBuffer), 4);
|
||||
normalBuffer = flipByteOrder(new Uint8Array(normalBuffer), 4);
|
||||
if (!isGeoTexture) indexBuffer = flipByteOrder(new Uint8Array(indexBuffer!), 4);
|
||||
}
|
||||
|
||||
return {
|
||||
vertexAccessorIndex: this.addBuffer(vertexBuffer, FLOAT, 'VEC3', vertexCount, ARRAY_BUFFER, vertexMin, vertexMax),
|
||||
normalAccessorIndex: this.addBuffer(normalBuffer, FLOAT, 'VEC3', vertexCount, ARRAY_BUFFER),
|
||||
indexAccessorIndex: isGeoTexture ? undefined : this.addBuffer(indexBuffer!, UNSIGNED_INT, 'SCALAR', drawCount, ELEMENT_ARRAY_BUFFER)
|
||||
};
|
||||
}
|
||||
|
||||
private addColorBuffer(geoData: MeshGeoData, interpolatedColors: Uint8Array | undefined, interpolatedOverpaint: Uint8Array | undefined, interpolatedTransparency: Uint8Array | undefined) {
|
||||
const { values, vertexCount } = geoData;
|
||||
const uAlpha = values.uAlpha.ref.value;
|
||||
|
||||
const colorArray = new Uint8Array(vertexCount * 4);
|
||||
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
let color = GlbExporter.getColor(i, geoData, interpolatedColors, interpolatedOverpaint);
|
||||
|
||||
const transparency = GlbExporter.getTransparency(i, geoData, interpolatedTransparency);
|
||||
const alpha = uAlpha * (1 - transparency);
|
||||
|
||||
color = Color.sRGBToLinear(color);
|
||||
Color.toArray(color, colorArray, i * 4);
|
||||
colorArray[i * 4 + 3] = Math.round(alpha * 255);
|
||||
}
|
||||
|
||||
let colorBuffer = colorArray.buffer;
|
||||
if (!IsNativeEndianLittle) {
|
||||
colorBuffer = flipByteOrder(new Uint8Array(colorBuffer), 4);
|
||||
}
|
||||
|
||||
return this.addBuffer(colorBuffer, UNSIGNED_BYTE, 'VEC4', vertexCount, ARRAY_BUFFER, undefined, undefined, true);
|
||||
}
|
||||
|
||||
private addMaterial(metalness: number, roughness: number) {
|
||||
const hash = `${metalness}|${roughness}`;
|
||||
if (!this.materialMap.has(hash)) {
|
||||
this.materialMap.set(hash, this.materials.length);
|
||||
this.materials.push({
|
||||
pbrMetallicRoughness: {
|
||||
baseColorFactor: [1, 1, 1, 1],
|
||||
metallicFactor: metalness,
|
||||
roughnessFactor: roughness
|
||||
}
|
||||
});
|
||||
}
|
||||
return this.materialMap.get(hash)!;
|
||||
}
|
||||
|
||||
protected async addMeshWithColors(input: AddMeshInput) {
|
||||
const { mesh, values, isGeoTexture, webgl, ctx } = input;
|
||||
|
||||
const t = Mat4();
|
||||
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const overpaintType = values.dOverpaintType.ref.value;
|
||||
const transparencyType = values.dTransparencyType.ref.value;
|
||||
const dTransparency = values.dTransparency.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
const metalness = values.uMetalness.ref.value;
|
||||
const roughness = values.uRoughness.ref.value;
|
||||
|
||||
const material = this.addMaterial(metalness, roughness);
|
||||
|
||||
let interpolatedColors: Uint8Array | undefined;
|
||||
if (colorType === 'volume' || colorType === 'volumeInstance') {
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
interpolatedColors = GlbExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
|
||||
}
|
||||
|
||||
let interpolatedOverpaint: Uint8Array | undefined;
|
||||
if (overpaintType === 'volumeInstance') {
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
interpolatedOverpaint = GlbExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
|
||||
}
|
||||
|
||||
let interpolatedTransparency: Uint8Array | undefined;
|
||||
if (transparencyType === 'volumeInstance') {
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
interpolatedTransparency = GlbExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
|
||||
}
|
||||
|
||||
// instancing
|
||||
const sameGeometryBuffers = mesh !== undefined;
|
||||
const sameColorBuffer = sameGeometryBuffers && colorType !== 'instance' && !colorType.endsWith('Instance') && !dTransparency;
|
||||
let vertexAccessorIndex: number;
|
||||
let normalAccessorIndex: number;
|
||||
let indexAccessorIndex: number | undefined;
|
||||
let colorAccessorIndex: number;
|
||||
let meshIndex: number;
|
||||
|
||||
await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
|
||||
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
|
||||
|
||||
// create a glTF mesh if needed
|
||||
if (instanceIndex === 0 || !sameGeometryBuffers || !sameColorBuffer) {
|
||||
const { vertices, normals, indices, groups, vertexCount, drawCount } = GlbExporter.getInstance(input, instanceIndex);
|
||||
|
||||
// create geometry buffers if needed
|
||||
if (instanceIndex === 0 || !sameGeometryBuffers) {
|
||||
const accessorIndices = this.addGeometryBuffers(vertices, normals, indices, vertexCount, drawCount, isGeoTexture);
|
||||
vertexAccessorIndex = accessorIndices.vertexAccessorIndex;
|
||||
normalAccessorIndex = accessorIndices.normalAccessorIndex;
|
||||
indexAccessorIndex = accessorIndices.indexAccessorIndex;
|
||||
}
|
||||
|
||||
// create a color buffer if needed
|
||||
if (instanceIndex === 0 || !sameColorBuffer) {
|
||||
colorAccessorIndex = this.addColorBuffer({ values, groups, vertexCount, instanceIndex, isGeoTexture }, interpolatedColors, interpolatedOverpaint, interpolatedTransparency);
|
||||
}
|
||||
|
||||
// glTF mesh
|
||||
meshIndex = this.meshes.length;
|
||||
this.meshes.push({
|
||||
primitives: [{
|
||||
attributes: {
|
||||
POSITION: vertexAccessorIndex!,
|
||||
NORMAL: normalAccessorIndex!,
|
||||
COLOR_0: colorAccessorIndex!
|
||||
},
|
||||
indices: indexAccessorIndex,
|
||||
material
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
// node
|
||||
Mat4.fromArray(t, aTransform, instanceIndex * 16);
|
||||
Mat4.mul(t, this.centerTransform, t);
|
||||
const node: Record<string, any> = {
|
||||
mesh: meshIndex!,
|
||||
matrix: t.slice()
|
||||
};
|
||||
this.nodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
async getData() {
|
||||
const binaryBufferLength = this.byteOffset;
|
||||
|
||||
const gltf = {
|
||||
asset: {
|
||||
version: '2.0',
|
||||
generator: `Mol* ${PLUGIN_VERSION}`
|
||||
},
|
||||
scenes: [{
|
||||
nodes: fillSerial(new Array(this.nodes.length) as number[])
|
||||
}],
|
||||
nodes: this.nodes,
|
||||
meshes: this.meshes,
|
||||
buffers: [{
|
||||
byteLength: binaryBufferLength,
|
||||
}],
|
||||
bufferViews: this.bufferViews,
|
||||
accessors: this.accessors,
|
||||
materials: this.materials
|
||||
};
|
||||
|
||||
const createChunk = (chunkType: number, data: ArrayBuffer[], byteLength: number, padChar: number): [ArrayBuffer[], number] => {
|
||||
let padding = null;
|
||||
if (byteLength % 4 !== 0) {
|
||||
const pad = 4 - (byteLength % 4);
|
||||
byteLength += pad;
|
||||
padding = new Uint8Array(pad);
|
||||
padding.fill(padChar);
|
||||
}
|
||||
const preamble = new ArrayBuffer(8);
|
||||
const preambleDataView = new DataView(preamble);
|
||||
preambleDataView.setUint32(0, byteLength, true);
|
||||
preambleDataView.setUint32(4, chunkType, true);
|
||||
const chunk = [preamble, ...data];
|
||||
if (padding) {
|
||||
chunk.push(padding.buffer);
|
||||
}
|
||||
return [chunk, 8 + byteLength];
|
||||
};
|
||||
const jsonString = JSON.stringify(gltf);
|
||||
const jsonBuffer = new Uint8Array(jsonString.length);
|
||||
asciiWrite(jsonBuffer, jsonString);
|
||||
|
||||
const [jsonChunk, jsonChunkLength] = createChunk(JSON_CHUNK_TYPE, [jsonBuffer.buffer], jsonBuffer.length, JSON_PAD_CHAR);
|
||||
const [binaryChunk, binaryChunkLength] = createChunk(BIN_CHUNK_TYPE, this.binaryBuffer, binaryBufferLength, BIN_PAD_CHAR);
|
||||
|
||||
const glbBufferLength = 12 + jsonChunkLength + binaryChunkLength;
|
||||
const header = new ArrayBuffer(12);
|
||||
const headerDataView = new DataView(header);
|
||||
headerDataView.setUint32(0, GLTF_MAGIC_BYTE, true); // magic number "glTF"
|
||||
headerDataView.setUint32(4, 2, true); // version
|
||||
headerDataView.setUint32(8, glbBufferLength, true); // length
|
||||
const glbBuffer = [header, ...jsonChunk, ...binaryChunk];
|
||||
|
||||
const glb = new Uint8Array(glbBufferLength);
|
||||
let offset = 0;
|
||||
for (const buffer of glbBuffer) {
|
||||
glb.set(new Uint8Array(buffer), offset);
|
||||
offset += buffer.byteLength;
|
||||
}
|
||||
return { glb };
|
||||
}
|
||||
|
||||
async getBlob(ctx: RuntimeContext) {
|
||||
return new Blob([(await this.getData()).glb], { type: 'model/gltf-binary' });
|
||||
}
|
||||
|
||||
constructor(boundingBox: Box3D) {
|
||||
super();
|
||||
const tmpV = Vec3();
|
||||
Vec3.add(tmpV, boundingBox.min, boundingBox.max);
|
||||
Vec3.scale(tmpV, tmpV, -0.5);
|
||||
this.centerTransform = Mat4.fromTranslation(Mat4(), tmpV);
|
||||
}
|
||||
}
|
||||
30
src/extensions/geo-export/index.ts
Normal file
30
src/extensions/geo-export/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
*/
|
||||
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
|
||||
import { GeometryExporterUI } from './ui';
|
||||
|
||||
export const GeometryExport = PluginBehavior.create<{ }>({
|
||||
name: 'extension-geo-export',
|
||||
category: 'misc',
|
||||
display: {
|
||||
name: 'Geometry Export'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ }> {
|
||||
register(): void {
|
||||
this.ctx.customStructureControls.set('geo-export', GeometryExporterUI as any);
|
||||
}
|
||||
|
||||
update() {
|
||||
return false;
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.ctx.customStructureControls.delete('geo-export');
|
||||
}
|
||||
},
|
||||
params: () => ({ })
|
||||
});
|
||||
485
src/extensions/geo-export/mesh-exporter.ts
Normal file
485
src/extensions/geo-export/mesh-exporter.ts
Normal file
@@ -0,0 +1,485 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { sort, arraySwap } from '../../mol-data/util';
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
import { MeshValues } from '../../mol-gl/renderable/mesh';
|
||||
import { LinesValues } from '../../mol-gl/renderable/lines';
|
||||
import { PointsValues } from '../../mol-gl/renderable/points';
|
||||
import { SpheresValues } from '../../mol-gl/renderable/spheres';
|
||||
import { CylindersValues } from '../../mol-gl/renderable/cylinders';
|
||||
import { TextureMeshValues } from '../../mol-gl/renderable/texture-mesh';
|
||||
import { BaseValues, SizeValues } from '../../mol-gl/renderable/schema';
|
||||
import { TextureImage } from '../../mol-gl/renderable/util';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { getTrilinearlyInterpolated } from '../../mol-geo/geometry/mesh/color-smoothing';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { sizeDataFactor } from '../../mol-geo/geometry/size-data';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { Color } from '../../mol-util/color/color';
|
||||
import { unpackRGBToInt } from '../../mol-util/number-packing';
|
||||
import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
|
||||
import { readAlphaTexture, readTexture } from '../../mol-gl/compute/util';
|
||||
|
||||
const GeoExportName = 'geo-export';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3fromArray = Vec3.fromArray;
|
||||
|
||||
export interface AddMeshInput {
|
||||
mesh: {
|
||||
vertices: Float32Array
|
||||
normals: Float32Array
|
||||
indices: Uint32Array | undefined
|
||||
groups: Float32Array | Uint8Array
|
||||
vertexCount: number
|
||||
drawCount: number
|
||||
} | undefined
|
||||
meshes: Mesh[] | undefined
|
||||
values: BaseValues
|
||||
isGeoTexture: boolean
|
||||
webgl: WebGLContext | undefined
|
||||
ctx: RuntimeContext
|
||||
}
|
||||
|
||||
export type MeshGeoData = {
|
||||
values: BaseValues,
|
||||
groups: Float32Array | Uint8Array,
|
||||
vertexCount: number,
|
||||
instanceIndex: number,
|
||||
isGeoTexture: boolean
|
||||
}
|
||||
|
||||
export abstract class MeshExporter<D extends RenderObjectExportData> implements RenderObjectExporter<D> {
|
||||
abstract readonly fileExtension: string;
|
||||
|
||||
private static getSizeFromTexture(tSize: TextureImage<Uint8Array>, i: number): number {
|
||||
const r = tSize.array[i * 3];
|
||||
const g = tSize.array[i * 3 + 1];
|
||||
const b = tSize.array[i * 3 + 2];
|
||||
return unpackRGBToInt(r, g, b) / sizeDataFactor;
|
||||
}
|
||||
|
||||
private static getSize(values: BaseValues & SizeValues, instanceIndex: number, group: number): number {
|
||||
const tSize = values.tSize.ref.value;
|
||||
let size = 0;
|
||||
switch (values.dSizeType.ref.value) {
|
||||
case 'uniform':
|
||||
size = values.uSize.ref.value;
|
||||
break;
|
||||
case 'instance':
|
||||
size = MeshExporter.getSizeFromTexture(tSize, instanceIndex);
|
||||
break;
|
||||
case 'group':
|
||||
size = MeshExporter.getSizeFromTexture(tSize, group);
|
||||
break;
|
||||
case 'groupInstance':
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
size = MeshExporter.getSizeFromTexture(tSize, instanceIndex * groupCount + group);
|
||||
break;
|
||||
}
|
||||
return size * values.uSizeFactor.ref.value;
|
||||
}
|
||||
|
||||
protected static getGroup(groups: Float32Array | Uint8Array, i: number): number {
|
||||
const i4 = i * 4;
|
||||
const r = groups[i4];
|
||||
const g = groups[i4 + 1];
|
||||
const b = groups[i4 + 2];
|
||||
if (groups instanceof Float32Array) {
|
||||
return unpackRGBToInt(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
|
||||
}
|
||||
return unpackRGBToInt(r, g, b);
|
||||
}
|
||||
|
||||
protected static getInterpolatedColors(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volume' | 'volumeInstance' }) {
|
||||
const { values, vertexCount, vertices, colorType, stride } = input;
|
||||
const colorGridTransform = values.uColorGridTransform.ref.value;
|
||||
const colorGridDim = values.uColorGridDim.ref.value;
|
||||
const colorTexDim = values.uColorTexDim.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
|
||||
const colorGrid = readTexture(webgl, values.tColorGrid.ref.value).array;
|
||||
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4, outputStride: 3 });
|
||||
return interpolated.array;
|
||||
}
|
||||
|
||||
protected static getInterpolatedOverpaint(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) {
|
||||
const { values, vertexCount, vertices, colorType, stride } = input;
|
||||
const overpaintGridTransform = values.uOverpaintGridTransform.ref.value;
|
||||
const overpaintGridDim = values.uOverpaintGridDim.ref.value;
|
||||
const overpaintTexDim = values.uOverpaintTexDim.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
|
||||
const overpaintGrid = readTexture(webgl, values.tOverpaintGrid.ref.value).array;
|
||||
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: overpaintGrid, gridDim: overpaintGridDim, gridTexDim: overpaintTexDim, gridTransform: overpaintGridTransform, vertexStride: stride, colorStride: 4, outputStride: 4 });
|
||||
return interpolated.array;
|
||||
}
|
||||
|
||||
protected static getInterpolatedTransparency(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) {
|
||||
const { values, vertexCount, vertices, colorType, stride } = input;
|
||||
const transparencyGridTransform = values.uTransparencyGridTransform.ref.value;
|
||||
const transparencyGridDim = values.uTransparencyGridDim.ref.value;
|
||||
const transparencyTexDim = values.uTransparencyTexDim.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
|
||||
const transparencyGrid = readAlphaTexture(webgl, values.tTransparencyGrid.ref.value).array;
|
||||
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: transparencyGrid, gridDim: transparencyGridDim, gridTexDim: transparencyTexDim, gridTransform: transparencyGridTransform, vertexStride: stride, colorStride: 4, outputStride: 1, itemOffset: 3 });
|
||||
|
||||
return interpolated.array;
|
||||
}
|
||||
|
||||
protected static quantizeColors(colorArray: Uint8Array, vertexCount: number) {
|
||||
if (vertexCount <= 1024) return;
|
||||
const rgb = Vec3();
|
||||
const min = Vec3();
|
||||
const max = Vec3();
|
||||
const sum = Vec3();
|
||||
const colorMap = new Map<Color, Color>();
|
||||
const colorComparers = [
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[0] - Color.toVec3(rgb, colors[j])[0]),
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[1] - Color.toVec3(rgb, colors[j])[1]),
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[2] - Color.toVec3(rgb, colors[j])[2]),
|
||||
];
|
||||
|
||||
const medianCut = (colors: Color[], l: number, r: number, depth: number) => {
|
||||
if (l > r) return;
|
||||
if (l === r || depth >= 10) {
|
||||
// Find the average color.
|
||||
Vec3.set(sum, 0, 0, 0);
|
||||
for (let i = l; i <= r; ++i) {
|
||||
Color.toVec3(rgb, colors[i]);
|
||||
Vec3.add(sum, sum, rgb);
|
||||
}
|
||||
Vec3.round(rgb, Vec3.scale(rgb, sum, 1 / (r - l + 1)));
|
||||
const averageColor = Color.fromArray(rgb, 0);
|
||||
for (let i = l; i <= r; ++i) colorMap.set(colors[i], averageColor);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the color channel with the greatest range.
|
||||
Vec3.set(min, 255, 255, 255);
|
||||
Vec3.set(max, 0, 0, 0);
|
||||
for (let i = l; i <= r; ++i) {
|
||||
Color.toVec3(rgb, colors[i]);
|
||||
for (let j = 0; j < 3; ++j) {
|
||||
Vec3.min(min, min, rgb);
|
||||
Vec3.max(max, max, rgb);
|
||||
}
|
||||
}
|
||||
let k = 0;
|
||||
if (max[1] - min[1] > max[k] - min[k]) k = 1;
|
||||
if (max[2] - min[2] > max[k] - min[k]) k = 2;
|
||||
|
||||
sort(colors, l, r + 1, colorComparers[k], arraySwap);
|
||||
|
||||
const m = (l + r) >> 1;
|
||||
medianCut(colors, l, m, depth + 1);
|
||||
medianCut(colors, m + 1, r, depth + 1);
|
||||
};
|
||||
|
||||
// Create an array of unique colors and use the median cut algorithm.
|
||||
const colorSet = new Set<Color>();
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
colorSet.add(Color.fromArray(colorArray, i * 3));
|
||||
}
|
||||
const colors = Array.from(colorSet);
|
||||
medianCut(colors, 0, colors.length - 1, 0);
|
||||
|
||||
// Map actual colors to quantized colors.
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
const color = colorMap.get(Color.fromArray(colorArray, i * 3));
|
||||
Color.toArray(color!, colorArray, i * 3);
|
||||
}
|
||||
}
|
||||
|
||||
protected static getInstance(input: AddMeshInput, instanceIndex: number) {
|
||||
const { mesh, meshes } = input;
|
||||
if (mesh !== undefined) {
|
||||
return mesh;
|
||||
} else {
|
||||
const mesh = meshes![instanceIndex];
|
||||
return {
|
||||
vertices: mesh.vertexBuffer.ref.value,
|
||||
normals: mesh.normalBuffer.ref.value,
|
||||
indices: mesh.indexBuffer.ref.value,
|
||||
groups: mesh.groupBuffer.ref.value,
|
||||
vertexCount: mesh.vertexCount,
|
||||
drawCount: mesh.triangleCount * 3
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected static getColor(vertexIndex: number, geoData: MeshGeoData, interpolatedColors?: Uint8Array, interpolatedOverpaint?: Uint8Array): Color {
|
||||
const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const uColor = values.uColor.ref.value;
|
||||
const tColor = values.tColor.ref.value.array;
|
||||
const overpaintType = values.dOverpaintType.ref.value;
|
||||
const dOverpaint = values.dOverpaint.ref.value;
|
||||
const tOverpaint = values.tOverpaint.ref.value.array;
|
||||
|
||||
let color: Color;
|
||||
switch (colorType) {
|
||||
case 'uniform':
|
||||
color = Color.fromNormalizedArray(uColor, 0);
|
||||
break;
|
||||
case 'instance':
|
||||
color = Color.fromArray(tColor, instanceIndex * 3);
|
||||
break;
|
||||
case 'group': {
|
||||
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
|
||||
color = Color.fromArray(tColor, group * 3);
|
||||
break;
|
||||
}
|
||||
case 'groupInstance': {
|
||||
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
|
||||
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
|
||||
break;
|
||||
}
|
||||
case 'vertex':
|
||||
color = Color.fromArray(tColor, vertexIndex * 3);
|
||||
break;
|
||||
case 'vertexInstance':
|
||||
color = Color.fromArray(tColor, (instanceIndex * vertexCount + vertexIndex) * 3);
|
||||
break;
|
||||
case 'volume':
|
||||
color = Color.fromArray(interpolatedColors!, vertexIndex * 3);
|
||||
break;
|
||||
case 'volumeInstance':
|
||||
color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + vertexIndex) * 3);
|
||||
break;
|
||||
default: throw new Error('Unsupported color type.');
|
||||
}
|
||||
|
||||
if (dOverpaint) {
|
||||
let overpaintColor: Color;
|
||||
let overpaintAlpha: number;
|
||||
switch (overpaintType) {
|
||||
case 'groupInstance': {
|
||||
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
|
||||
const idx = (instanceIndex * groupCount + group) * 4;
|
||||
overpaintColor = Color.fromArray(tOverpaint, idx);
|
||||
overpaintAlpha = tOverpaint[idx + 3] / 255;
|
||||
break;
|
||||
}
|
||||
case 'vertexInstance': {
|
||||
const idx = (instanceIndex * vertexCount + vertexIndex) * 4;
|
||||
overpaintColor = Color.fromArray(tOverpaint, idx);
|
||||
overpaintAlpha = tOverpaint[idx + 3] / 255;
|
||||
break;
|
||||
}
|
||||
case 'volumeInstance': {
|
||||
const idx = (instanceIndex * vertexCount + vertexIndex) * 4;
|
||||
overpaintColor = Color.fromArray(interpolatedOverpaint!, idx);
|
||||
overpaintAlpha = interpolatedOverpaint![idx + 3] / 255;
|
||||
break;
|
||||
}
|
||||
default: throw new Error('Unsupported overpaint type.');
|
||||
}
|
||||
// interpolate twice to avoid darkening due to empty overpaint
|
||||
overpaintColor = Color.interpolate(color, overpaintColor, overpaintAlpha);
|
||||
color = Color.interpolate(color, overpaintColor, overpaintAlpha);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
protected static getTransparency(vertexIndex: number, geoData: MeshGeoData, interpolatedTransparency?: Uint8Array): number {
|
||||
const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
const dTransparency = values.dTransparency.ref.value;
|
||||
const tTransparency = values.tTransparency.ref.value.array;
|
||||
const transparencyType = values.dTransparencyType.ref.value;
|
||||
|
||||
let transparency: number = 0;
|
||||
if (dTransparency) {
|
||||
switch (transparencyType) {
|
||||
case 'groupInstance': {
|
||||
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
|
||||
const idx = (instanceIndex * groupCount + group);
|
||||
transparency = tTransparency[idx] / 255;
|
||||
break;
|
||||
}
|
||||
case 'vertexInstance': {
|
||||
const idx = (instanceIndex * vertexCount + vertexIndex);
|
||||
transparency = tTransparency[idx] / 255;
|
||||
break;
|
||||
}
|
||||
case 'volumeInstance': {
|
||||
const idx = (instanceIndex * vertexCount + vertexIndex);
|
||||
transparency = interpolatedTransparency![idx] / 255;
|
||||
break;
|
||||
}
|
||||
default: throw new Error('Unsupported transparency type.');
|
||||
}
|
||||
}
|
||||
return transparency;
|
||||
}
|
||||
|
||||
protected abstract addMeshWithColors(input: AddMeshInput): void;
|
||||
|
||||
private async addMesh(values: MeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
|
||||
const aPosition = values.aPosition.ref.value;
|
||||
const aNormal = values.aNormal.ref.value;
|
||||
const aGroup = values.aGroup.ref.value;
|
||||
const originalData = Mesh.getOriginalData(values);
|
||||
let indices: Uint32Array;
|
||||
let vertexCount: number;
|
||||
let drawCount: number;
|
||||
if (originalData) {
|
||||
indices = originalData.indexBuffer;
|
||||
vertexCount = originalData.vertexCount;
|
||||
drawCount = originalData.triangleCount * 3;
|
||||
} else {
|
||||
indices = values.elements.ref.value;
|
||||
vertexCount = values.uVertexCount.ref.value;
|
||||
drawCount = values.drawCount.ref.value;
|
||||
}
|
||||
|
||||
await this.addMeshWithColors({ mesh: { vertices: aPosition, normals: aNormal, indices, groups: aGroup, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: false, webgl, ctx });
|
||||
}
|
||||
|
||||
private async addLines(values: LinesValues, webgl: WebGLContext, ctx: RuntimeContext) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private async addPoints(values: PointsValues, webgl: WebGLContext, ctx: RuntimeContext) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private async addSpheres(values: SpheresValues, webgl: WebGLContext, ctx: RuntimeContext) {
|
||||
const center = Vec3();
|
||||
|
||||
const aPosition = values.aPosition.ref.value;
|
||||
const aGroup = values.aGroup.ref.value;
|
||||
const instanceCount = values.instanceCount.ref.value;
|
||||
const vertexCount = values.uVertexCount.ref.value;
|
||||
const meshes: Mesh[] = [];
|
||||
|
||||
const sphereCount = vertexCount / 4 * instanceCount;
|
||||
let detail: number;
|
||||
if (sphereCount < 2000) detail = 3;
|
||||
else if (sphereCount < 20000) detail = 2;
|
||||
else detail = 1;
|
||||
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
const state = MeshBuilder.createState(512, 256);
|
||||
|
||||
for (let i = 0; i < vertexCount; i += 4) {
|
||||
v3fromArray(center, aPosition, i * 3);
|
||||
|
||||
const group = aGroup[i];
|
||||
const radius = MeshExporter.getSize(values, instanceIndex, group);
|
||||
state.currentGroup = group;
|
||||
addSphere(state, center, radius, detail);
|
||||
}
|
||||
|
||||
meshes.push(MeshBuilder.getMesh(state));
|
||||
}
|
||||
|
||||
await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, webgl, ctx });
|
||||
}
|
||||
|
||||
private async addCylinders(values: CylindersValues, webgl: WebGLContext, ctx: RuntimeContext) {
|
||||
const start = Vec3();
|
||||
const end = Vec3();
|
||||
|
||||
const aStart = values.aStart.ref.value;
|
||||
const aEnd = values.aEnd.ref.value;
|
||||
const aScale = values.aScale.ref.value;
|
||||
const aCap = values.aCap.ref.value;
|
||||
const aGroup = values.aGroup.ref.value;
|
||||
const instanceCount = values.instanceCount.ref.value;
|
||||
const vertexCount = values.uVertexCount.ref.value;
|
||||
const meshes: Mesh[] = [];
|
||||
|
||||
const cylinderCount = vertexCount / 6 * instanceCount;
|
||||
let radialSegments: number;
|
||||
if (cylinderCount < 2000) radialSegments = 36;
|
||||
else if (cylinderCount < 20000) radialSegments = 24;
|
||||
else radialSegments = 12;
|
||||
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
const state = MeshBuilder.createState(512, 256);
|
||||
|
||||
for (let i = 0; i < vertexCount; i += 6) {
|
||||
v3fromArray(start, aStart, i * 3);
|
||||
v3fromArray(end, aEnd, i * 3);
|
||||
|
||||
const group = aGroup[i];
|
||||
const radius = MeshExporter.getSize(values, instanceIndex, group) * aScale[i];
|
||||
const cap = aCap[i];
|
||||
const topCap = cap === 1 || cap === 3;
|
||||
const bottomCap = cap >= 2;
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius, topCap, bottomCap, radialSegments };
|
||||
state.currentGroup = aGroup[i];
|
||||
addCylinder(state, start, end, 1, cylinderProps);
|
||||
}
|
||||
|
||||
meshes.push(MeshBuilder.getMesh(state));
|
||||
}
|
||||
|
||||
await this.addMeshWithColors({ mesh: undefined, meshes, values, isGeoTexture: false, webgl, ctx });
|
||||
}
|
||||
|
||||
private async addTextureMesh(values: TextureMeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
|
||||
if (!webgl.namedFramebuffers[GeoExportName]) {
|
||||
webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
|
||||
}
|
||||
const framebuffer = webgl.namedFramebuffers[GeoExportName];
|
||||
|
||||
const [width, height] = values.uGeoTexDim.ref.value;
|
||||
const vertices = new Float32Array(width * height * 4);
|
||||
const normals = new Float32Array(width * height * 4);
|
||||
const groups = webgl.isWebGL2 ? new Uint8Array(width * height * 4) : new Float32Array(width * height * 4);
|
||||
|
||||
framebuffer.bind();
|
||||
values.tPosition.ref.value.attachFramebuffer(framebuffer, 0);
|
||||
webgl.readPixels(0, 0, width, height, vertices);
|
||||
values.tNormal.ref.value.attachFramebuffer(framebuffer, 0);
|
||||
webgl.readPixels(0, 0, width, height, normals);
|
||||
values.tGroup.ref.value.attachFramebuffer(framebuffer, 0);
|
||||
webgl.readPixels(0, 0, width, height, groups);
|
||||
|
||||
const vertexCount = values.uVertexCount.ref.value;
|
||||
const drawCount = values.drawCount.ref.value;
|
||||
|
||||
await this.addMeshWithColors({ mesh: { vertices, normals, indices: undefined, groups, vertexCount, drawCount }, meshes: undefined, values, isGeoTexture: true, webgl, ctx });
|
||||
}
|
||||
|
||||
add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext) {
|
||||
if (!renderObject.state.visible) return;
|
||||
|
||||
switch (renderObject.type) {
|
||||
case 'mesh':
|
||||
return this.addMesh(renderObject.values as MeshValues, webgl, ctx);
|
||||
case 'lines':
|
||||
return this.addLines(renderObject.values as LinesValues, webgl, ctx);
|
||||
case 'points':
|
||||
return this.addPoints(renderObject.values as PointsValues, webgl, ctx);
|
||||
case 'spheres':
|
||||
return this.addSpheres(renderObject.values as SpheresValues, webgl, ctx);
|
||||
case 'cylinders':
|
||||
return this.addCylinders(renderObject.values as CylindersValues, webgl, ctx);
|
||||
case 'texture-mesh':
|
||||
return this.addTextureMesh(renderObject.values as TextureMeshValues, webgl, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
abstract getData(ctx: RuntimeContext): Promise<D>;
|
||||
|
||||
abstract getBlob(ctx: RuntimeContext): Promise<Blob>;
|
||||
}
|
||||
207
src/extensions/geo-export/obj-exporter.ts
Normal file
207
src/extensions/geo-export/obj-exporter.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { asciiWrite } from '../../mol-io/common/ascii';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
import { Color } from '../../mol-util/color/color';
|
||||
import { zip } from '../../mol-util/zip/zip';
|
||||
import { MeshExporter, AddMeshInput } from './mesh-exporter';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3fromArray = Vec3.fromArray;
|
||||
const v3transformMat4 = Vec3.transformMat4;
|
||||
const v3transformMat3 = Vec3.transformMat3;
|
||||
const mat3directionTransform = Mat3.directionTransform;
|
||||
|
||||
// http://paulbourke.net/dataformats/obj/
|
||||
// http://paulbourke.net/dataformats/mtl/
|
||||
|
||||
export type ObjData = {
|
||||
obj: string
|
||||
mtl: string
|
||||
}
|
||||
|
||||
export class ObjExporter extends MeshExporter<ObjData> {
|
||||
readonly fileExtension = 'zip';
|
||||
private obj = StringBuilder.create();
|
||||
private mtl = StringBuilder.create();
|
||||
private vertexOffset = 0;
|
||||
private currentColor: Color | undefined;
|
||||
private currentAlpha: number | undefined;
|
||||
private materialSet = new Set<string>();
|
||||
private centerTransform: Mat4;
|
||||
|
||||
private updateMaterial(color: Color, alpha: number) {
|
||||
if (this.currentColor === color && this.currentAlpha === alpha) return;
|
||||
|
||||
this.currentColor = color;
|
||||
this.currentAlpha = alpha;
|
||||
const material = Color.toHexString(color) + alpha;
|
||||
StringBuilder.writeSafe(this.obj, `usemtl ${material}`);
|
||||
StringBuilder.newline(this.obj);
|
||||
if (!this.materialSet.has(material)) {
|
||||
this.materialSet.add(material);
|
||||
const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
|
||||
const mtl = this.mtl;
|
||||
StringBuilder.writeSafe(mtl, `newmtl ${material}\n`);
|
||||
StringBuilder.writeSafe(mtl, 'illum 2\n'); // illumination model
|
||||
StringBuilder.writeSafe(mtl, 'Ns 163\n'); // specular exponent
|
||||
StringBuilder.writeSafe(mtl, 'Ni 0.001\n'); // optical density a.k.a. index of refraction
|
||||
StringBuilder.writeSafe(mtl, 'Ka 0 0 0\n'); // ambient reflectivity
|
||||
StringBuilder.writeSafe(mtl, 'Kd '); // diffuse reflectivity
|
||||
StringBuilder.writeFloat(mtl, r, 1000);
|
||||
StringBuilder.whitespace1(mtl);
|
||||
StringBuilder.writeFloat(mtl, g, 1000);
|
||||
StringBuilder.whitespace1(mtl);
|
||||
StringBuilder.writeFloat(mtl, b, 1000);
|
||||
StringBuilder.newline(mtl);
|
||||
StringBuilder.writeSafe(mtl, 'Ks 0.25 0.25 0.25\n'); // specular reflectivity
|
||||
StringBuilder.writeSafe(mtl, 'd '); // dissolve
|
||||
StringBuilder.writeFloat(mtl, alpha, 1000);
|
||||
StringBuilder.newline(mtl);
|
||||
}
|
||||
}
|
||||
|
||||
protected async addMeshWithColors(input: AddMeshInput) {
|
||||
const { mesh, values, isGeoTexture, webgl, ctx } = input;
|
||||
|
||||
const obj = this.obj;
|
||||
const t = Mat4();
|
||||
const n = Mat3();
|
||||
const tmpV = Vec3();
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const overpaintType = values.dOverpaintType.ref.value;
|
||||
const transparencyType = values.dTransparencyType.ref.value;
|
||||
const uAlpha = values.uAlpha.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
|
||||
let interpolatedColors: Uint8Array | undefined;
|
||||
if (colorType === 'volume' || colorType === 'volumeInstance') {
|
||||
interpolatedColors = ObjExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
|
||||
}
|
||||
|
||||
let interpolatedOverpaint: Uint8Array | undefined;
|
||||
if (overpaintType === 'volumeInstance') {
|
||||
interpolatedOverpaint = ObjExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
|
||||
}
|
||||
|
||||
let interpolatedTransparency: Uint8Array | undefined;
|
||||
if (transparencyType === 'volumeInstance') {
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
interpolatedTransparency = ObjExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
|
||||
}
|
||||
|
||||
await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
|
||||
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
|
||||
|
||||
const { vertices, normals, indices, groups, vertexCount, drawCount } = ObjExporter.getInstance(input, instanceIndex);
|
||||
|
||||
Mat4.fromArray(t, aTransform, instanceIndex * 16);
|
||||
Mat4.mul(t, this.centerTransform, t);
|
||||
mat3directionTransform(n, t);
|
||||
|
||||
// position
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
|
||||
StringBuilder.writeSafe(obj, 'v ');
|
||||
StringBuilder.writeFloat(obj, tmpV[0], 1000);
|
||||
StringBuilder.whitespace1(obj);
|
||||
StringBuilder.writeFloat(obj, tmpV[1], 1000);
|
||||
StringBuilder.whitespace1(obj);
|
||||
StringBuilder.writeFloat(obj, tmpV[2], 1000);
|
||||
StringBuilder.newline(obj);
|
||||
}
|
||||
|
||||
// normal
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
|
||||
StringBuilder.writeSafe(obj, 'vn ');
|
||||
StringBuilder.writeFloat(obj, tmpV[0], 100);
|
||||
StringBuilder.whitespace1(obj);
|
||||
StringBuilder.writeFloat(obj, tmpV[1], 100);
|
||||
StringBuilder.whitespace1(obj);
|
||||
StringBuilder.writeFloat(obj, tmpV[2], 100);
|
||||
StringBuilder.newline(obj);
|
||||
}
|
||||
|
||||
const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
|
||||
|
||||
// color
|
||||
const quantizedColors = new Uint8Array(drawCount * 3);
|
||||
for (let i = 0; i < drawCount; i += 3) {
|
||||
const v = isGeoTexture ? i : indices![i];
|
||||
const color = ObjExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
|
||||
Color.toArray(color, quantizedColors, i);
|
||||
}
|
||||
ObjExporter.quantizeColors(quantizedColors, vertexCount);
|
||||
|
||||
// face
|
||||
for (let i = 0; i < drawCount; i += 3) {
|
||||
const color = Color.fromArray(quantizedColors, i);
|
||||
|
||||
const transparency = ObjExporter.getTransparency(i, geoData, interpolatedTransparency);
|
||||
const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
|
||||
|
||||
this.updateMaterial(color, alpha);
|
||||
|
||||
const v1 = this.vertexOffset + (isGeoTexture ? i : indices![i]) + 1;
|
||||
const v2 = this.vertexOffset + (isGeoTexture ? i + 1 : indices![i + 1]) + 1;
|
||||
const v3 = this.vertexOffset + (isGeoTexture ? i + 2 : indices![i + 2]) + 1;
|
||||
StringBuilder.writeSafe(obj, 'f ');
|
||||
StringBuilder.writeInteger(obj, v1);
|
||||
StringBuilder.writeSafe(obj, '//');
|
||||
StringBuilder.writeIntegerAndSpace(obj, v1);
|
||||
StringBuilder.writeInteger(obj, v2);
|
||||
StringBuilder.writeSafe(obj, '//');
|
||||
StringBuilder.writeIntegerAndSpace(obj, v2);
|
||||
StringBuilder.writeInteger(obj, v3);
|
||||
StringBuilder.writeSafe(obj, '//');
|
||||
StringBuilder.writeInteger(obj, v3);
|
||||
StringBuilder.newline(obj);
|
||||
}
|
||||
|
||||
this.vertexOffset += vertexCount;
|
||||
}
|
||||
}
|
||||
|
||||
async getData() {
|
||||
return {
|
||||
obj: StringBuilder.getString(this.obj),
|
||||
mtl: StringBuilder.getString(this.mtl)
|
||||
};
|
||||
}
|
||||
|
||||
async getBlob(ctx: RuntimeContext) {
|
||||
const { obj, mtl } = await this.getData();
|
||||
const objData = new Uint8Array(obj.length);
|
||||
asciiWrite(objData, obj);
|
||||
const mtlData = new Uint8Array(mtl.length);
|
||||
asciiWrite(mtlData, mtl);
|
||||
const zipDataObj = {
|
||||
[this.filename + '.obj']: objData,
|
||||
[this.filename + '.mtl']: mtlData
|
||||
};
|
||||
return new Blob([await zip(ctx, zipDataObj)], { type: 'application/zip' });
|
||||
}
|
||||
|
||||
constructor(private filename: string, boundingBox: Box3D) {
|
||||
super();
|
||||
StringBuilder.writeSafe(this.obj, `mtllib ${filename}.mtl\n`);
|
||||
const tmpV = Vec3();
|
||||
Vec3.add(tmpV, boundingBox.min, boundingBox.max);
|
||||
Vec3.scale(tmpV, tmpV, -0.5);
|
||||
this.centerTransform = Mat4.fromTranslation(Mat4(), tmpV);
|
||||
}
|
||||
}
|
||||
20
src/extensions/geo-export/render-object-exporter.ts
Normal file
20
src/extensions/geo-export/render-object-exporter.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
*/
|
||||
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
|
||||
export type RenderObjectExportData = {
|
||||
[k: string]: string | Uint8Array | ArrayBuffer | undefined
|
||||
}
|
||||
|
||||
export interface RenderObjectExporter<D extends RenderObjectExportData> {
|
||||
readonly fileExtension: string
|
||||
add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext): Promise<void> | undefined
|
||||
getData(ctx: RuntimeContext): Promise<D>
|
||||
getBlob(ctx: RuntimeContext): Promise<Blob>
|
||||
}
|
||||
119
src/extensions/geo-export/stl-exporter.ts
Normal file
119
src/extensions/geo-export/stl-exporter.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
*/
|
||||
|
||||
import { asciiWrite } from '../../mol-io/common/ascii';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { PLUGIN_VERSION } from '../../mol-plugin/version';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { MeshExporter, AddMeshInput } from './mesh-exporter';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3fromArray = Vec3.fromArray;
|
||||
const v3transformMat4 = Vec3.transformMat4;
|
||||
const v3triangleNormal = Vec3.triangleNormal;
|
||||
const v3toArray = Vec3.toArray;
|
||||
|
||||
// https://www.fabbers.com/tech/STL_Format
|
||||
|
||||
export type StlData = {
|
||||
stl: Uint8Array
|
||||
}
|
||||
|
||||
export class StlExporter extends MeshExporter<StlData> {
|
||||
readonly fileExtension = 'stl';
|
||||
private triangleBuffers: ArrayBuffer[] = [];
|
||||
private triangleCount = 0;
|
||||
private centerTransform: Mat4;
|
||||
|
||||
protected async addMeshWithColors(input: AddMeshInput) {
|
||||
const { values, isGeoTexture, ctx } = input;
|
||||
|
||||
const t = Mat4();
|
||||
const tmpV = Vec3();
|
||||
const v1 = Vec3();
|
||||
const v2 = Vec3();
|
||||
const v3 = Vec3();
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
|
||||
|
||||
const { vertices, indices, vertexCount, drawCount } = StlExporter.getInstance(input, instanceIndex);
|
||||
|
||||
Mat4.fromArray(t, aTransform, instanceIndex * 16);
|
||||
Mat4.mul(t, this.centerTransform, t);
|
||||
|
||||
// position
|
||||
const vertexArray = new Float32Array(vertexCount * 3);
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
|
||||
v3toArray(tmpV, vertexArray, i * 3);
|
||||
}
|
||||
|
||||
// face
|
||||
const triangleBuffer = new ArrayBuffer(50 * drawCount);
|
||||
const dataView = new DataView(triangleBuffer);
|
||||
for (let i = 0; i < drawCount; i += 3) {
|
||||
v3fromArray(v1, vertexArray, (isGeoTexture ? i : indices![i]) * 3);
|
||||
v3fromArray(v2, vertexArray, (isGeoTexture ? i + 1 : indices![i + 1]) * 3);
|
||||
v3fromArray(v3, vertexArray, (isGeoTexture ? i + 2 : indices![i + 2]) * 3);
|
||||
v3triangleNormal(tmpV, v1, v2, v3);
|
||||
|
||||
const byteOffset = 50 * i;
|
||||
dataView.setFloat32(byteOffset, tmpV[0], true);
|
||||
dataView.setFloat32(byteOffset + 4, tmpV[1], true);
|
||||
dataView.setFloat32(byteOffset + 8, tmpV[2], true);
|
||||
|
||||
dataView.setFloat32(byteOffset + 12, v1[0], true);
|
||||
dataView.setFloat32(byteOffset + 16, v1[1], true);
|
||||
dataView.setFloat32(byteOffset + 20, v1[2], true);
|
||||
|
||||
dataView.setFloat32(byteOffset + 24, v2[0], true);
|
||||
dataView.setFloat32(byteOffset + 28, v2[1], true);
|
||||
dataView.setFloat32(byteOffset + 32, v2[2], true);
|
||||
|
||||
dataView.setFloat32(byteOffset + 36, v3[0], true);
|
||||
dataView.setFloat32(byteOffset + 40, v3[1], true);
|
||||
dataView.setFloat32(byteOffset + 44, v3[2], true);
|
||||
}
|
||||
|
||||
this.triangleBuffers.push(triangleBuffer);
|
||||
this.triangleCount += drawCount;
|
||||
}
|
||||
}
|
||||
|
||||
async getData() {
|
||||
const stl = new Uint8Array(84 + 50 * this.triangleCount);
|
||||
|
||||
asciiWrite(stl, `Exported from Mol* ${PLUGIN_VERSION}`);
|
||||
|
||||
const dataView = new DataView(stl.buffer);
|
||||
dataView.setUint32(80, this.triangleCount, true);
|
||||
|
||||
let byteOffset = 84;
|
||||
for (const buffer of this.triangleBuffers) {
|
||||
stl.set(new Uint8Array(buffer), byteOffset);
|
||||
byteOffset += buffer.byteLength;
|
||||
}
|
||||
return { stl };
|
||||
}
|
||||
|
||||
async getBlob(ctx: RuntimeContext) {
|
||||
return new Blob([(await this.getData()).stl], { type: 'model/stl' });
|
||||
}
|
||||
|
||||
constructor(boundingBox: Box3D) {
|
||||
super();
|
||||
const tmpV = Vec3();
|
||||
Vec3.add(tmpV, boundingBox.min, boundingBox.max);
|
||||
Vec3.scale(tmpV, tmpV, -0.5);
|
||||
this.centerTransform = Mat4.fromTranslation(Mat4(), tmpV);
|
||||
}
|
||||
}
|
||||
108
src/extensions/geo-export/ui.tsx
Normal file
108
src/extensions/geo-export/ui.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
*/
|
||||
|
||||
import { merge } from 'rxjs';
|
||||
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
|
||||
import { Button } from '../../mol-plugin-ui/controls/common';
|
||||
import { GetAppSvg, CubeScanSvg, CubeSendSvg } from '../../mol-plugin-ui/controls/icons';
|
||||
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { GeometryParams, GeometryControls } from './controls';
|
||||
|
||||
interface State {
|
||||
busy?: boolean
|
||||
}
|
||||
|
||||
export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
private _controls: GeometryControls | undefined;
|
||||
private isARSupported: boolean | undefined;
|
||||
|
||||
get controls() {
|
||||
return this._controls || (this._controls = new GeometryControls(this.plugin));
|
||||
}
|
||||
|
||||
protected defaultState(): State & CollapsableState {
|
||||
return {
|
||||
header: 'Export Geometry',
|
||||
isCollapsed: true,
|
||||
brand: { accent: 'cyan', svg: CubeSendSvg }
|
||||
};
|
||||
}
|
||||
|
||||
protected renderControls(): JSX.Element {
|
||||
if (this.isARSupported === undefined) {
|
||||
this.isARSupported = !!document.createElement('a').relList?.supports?.('ar');
|
||||
}
|
||||
const ctrl = this.controls;
|
||||
return <>
|
||||
<ParameterControls
|
||||
params={GeometryParams}
|
||||
values={ctrl.behaviors.params.value}
|
||||
onChangeValues={xs => ctrl.behaviors.params.next(xs)}
|
||||
isDisabled={this.state.busy}
|
||||
/>
|
||||
<Button icon={GetAppSvg}
|
||||
onClick={this.save} style={{ marginTop: 1 }}
|
||||
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
|
||||
Save
|
||||
</Button>
|
||||
{this.isARSupported && ctrl.behaviors.params.value.format === 'usdz' &&
|
||||
<Button icon={CubeScanSvg}
|
||||
onClick={this.viewInAR} style={{ marginTop: 1 }}
|
||||
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
|
||||
View in AR
|
||||
</Button>
|
||||
}
|
||||
</>;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const merged = merge(
|
||||
this.controls.behaviors.params,
|
||||
this.plugin.canvas3d!.reprCount
|
||||
);
|
||||
|
||||
this.subscribe(merged, () => {
|
||||
if (!this.state.isCollapsed) this.forceUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._controls?.dispose();
|
||||
this._controls = void 0;
|
||||
}
|
||||
|
||||
save = async () => {
|
||||
try {
|
||||
this.setState({ busy: true });
|
||||
const data = await this.controls.exportGeometry();
|
||||
download(data.blob, data.filename);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
};
|
||||
|
||||
viewInAR = async () => {
|
||||
try {
|
||||
this.setState({ busy: true });
|
||||
const data = await this.controls.exportGeometry();
|
||||
const a = document.createElement('a');
|
||||
a.rel = 'ar';
|
||||
a.href = URL.createObjectURL(data.blob);
|
||||
// For in-place viewing of USDZ on iOS, the link must contain a single child that is either an img or picture.
|
||||
// https://webkit.org/blog/8421/viewing-augmented-reality-assets-in-safari-for-ios/
|
||||
a.appendChild(document.createElement('img'));
|
||||
setTimeout(() => URL.revokeObjectURL(a.href), 4E4); // 40s
|
||||
setTimeout(() => a.dispatchEvent(new MouseEvent('click')));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
};
|
||||
}
|
||||
247
src/extensions/geo-export/usdz-exporter.ts
Normal file
247
src/extensions/geo-export/usdz-exporter.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { asciiWrite } from '../../mol-io/common/ascii';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { PLUGIN_VERSION } from '../../mol-plugin/version';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
import { Color } from '../../mol-util/color/color';
|
||||
import { zip } from '../../mol-util/zip/zip';
|
||||
import { MeshExporter, AddMeshInput } from './mesh-exporter';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3fromArray = Vec3.fromArray;
|
||||
const v3transformMat4 = Vec3.transformMat4;
|
||||
const v3transformMat3 = Vec3.transformMat3;
|
||||
const mat3directionTransform = Mat3.directionTransform;
|
||||
|
||||
// https://graphics.pixar.com/usd/docs/index.html
|
||||
|
||||
export type UsdzData = {
|
||||
usdz: ArrayBuffer
|
||||
}
|
||||
|
||||
export class UsdzExporter extends MeshExporter<UsdzData> {
|
||||
readonly fileExtension = 'usdz';
|
||||
private meshes: string[] = [];
|
||||
private materials: string[] = [];
|
||||
private materialMap = new Map<string, number>();
|
||||
private centerTransform: Mat4;
|
||||
|
||||
private addMaterial(color: Color, alpha: number, metalness: number, roughness: number): number {
|
||||
const hash = `${color}|${alpha}|${metalness}|${roughness}`;
|
||||
if (this.materialMap.has(hash)) return this.materialMap.get(hash)!;
|
||||
|
||||
const materialKey = this.materialMap.size;
|
||||
this.materialMap.set(hash, materialKey);
|
||||
|
||||
const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
|
||||
this.materials.push(`
|
||||
def Material "material${materialKey}"
|
||||
{
|
||||
token outputs:surface.connect = </material${materialKey}/shader.outputs:surface>
|
||||
def Shader "shader"
|
||||
{
|
||||
uniform token info:id = "UsdPreviewSurface"
|
||||
color3f inputs:diffuseColor = (${r},${g},${b})
|
||||
float inputs:opacity = ${alpha}
|
||||
float inputs:metallic = ${metalness}
|
||||
float inputs:roughness = ${roughness}
|
||||
token outputs:surface
|
||||
}
|
||||
}
|
||||
`);
|
||||
return materialKey;
|
||||
}
|
||||
|
||||
protected async addMeshWithColors(input: AddMeshInput) {
|
||||
const { mesh, values, isGeoTexture, webgl, ctx } = input;
|
||||
|
||||
const t = Mat4();
|
||||
const n = Mat3();
|
||||
const tmpV = Vec3();
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const overpaintType = values.dOverpaintType.ref.value;
|
||||
const transparencyType = values.dTransparencyType.ref.value;
|
||||
const uAlpha = values.uAlpha.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
const metalness = values.uMetalness.ref.value;
|
||||
const roughness = values.uRoughness.ref.value;
|
||||
|
||||
let interpolatedColors: Uint8Array | undefined;
|
||||
if (colorType === 'volume' || colorType === 'volumeInstance') {
|
||||
interpolatedColors = UsdzExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
|
||||
}
|
||||
|
||||
let interpolatedOverpaint: Uint8Array | undefined;
|
||||
if (overpaintType === 'volumeInstance') {
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
interpolatedOverpaint = UsdzExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
|
||||
}
|
||||
|
||||
let interpolatedTransparency: Uint8Array | undefined;
|
||||
if (transparencyType === 'volumeInstance') {
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
interpolatedTransparency = UsdzExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
|
||||
}
|
||||
|
||||
await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
|
||||
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
|
||||
|
||||
const { vertices, normals, indices, groups, vertexCount, drawCount } = UsdzExporter.getInstance(input, instanceIndex);
|
||||
|
||||
Mat4.fromArray(t, aTransform, instanceIndex * 16);
|
||||
Mat4.mul(t, this.centerTransform, t);
|
||||
mat3directionTransform(n, t);
|
||||
|
||||
const vertexBuilder = StringBuilder.create();
|
||||
const normalBuilder = StringBuilder.create();
|
||||
const indexBuilder = StringBuilder.create();
|
||||
|
||||
// position
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
|
||||
StringBuilder.writeSafe(vertexBuilder, (i === 0) ? '(' : ',(');
|
||||
StringBuilder.writeFloat(vertexBuilder, tmpV[0], 10000);
|
||||
StringBuilder.writeSafe(vertexBuilder, ',');
|
||||
StringBuilder.writeFloat(vertexBuilder, tmpV[1], 10000);
|
||||
StringBuilder.writeSafe(vertexBuilder, ',');
|
||||
StringBuilder.writeFloat(vertexBuilder, tmpV[2], 10000);
|
||||
StringBuilder.writeSafe(vertexBuilder, ')');
|
||||
}
|
||||
|
||||
// normal
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
|
||||
StringBuilder.writeSafe(normalBuilder, (i === 0) ? '(' : ',(');
|
||||
StringBuilder.writeFloat(normalBuilder, tmpV[0], 100);
|
||||
StringBuilder.writeSafe(normalBuilder, ',');
|
||||
StringBuilder.writeFloat(normalBuilder, tmpV[1], 100);
|
||||
StringBuilder.writeSafe(normalBuilder, ',');
|
||||
StringBuilder.writeFloat(normalBuilder, tmpV[2], 100);
|
||||
StringBuilder.writeSafe(normalBuilder, ')');
|
||||
}
|
||||
|
||||
const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
|
||||
|
||||
// face
|
||||
for (let i = 0; i < drawCount; ++i) {
|
||||
const v = isGeoTexture ? i : indices![i];
|
||||
if (i > 0) StringBuilder.writeSafe(indexBuilder, ',');
|
||||
StringBuilder.writeInteger(indexBuilder, v);
|
||||
}
|
||||
|
||||
// color
|
||||
const quantizedColors = new Uint8Array(drawCount * 3);
|
||||
for (let i = 0; i < drawCount; i += 3) {
|
||||
const v = isGeoTexture ? i : indices![i];
|
||||
const color = UsdzExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
|
||||
Color.toArray(color, quantizedColors, i);
|
||||
}
|
||||
UsdzExporter.quantizeColors(quantizedColors, vertexCount);
|
||||
|
||||
// material
|
||||
const faceIndicesByMaterial = new Map<number, number[]>();
|
||||
for (let i = 0; i < drawCount; i += 3) {
|
||||
const color = Color.fromArray(quantizedColors, i);
|
||||
|
||||
const transparency = UsdzExporter.getTransparency(i, geoData, interpolatedTransparency);
|
||||
const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
|
||||
|
||||
const materialKey = this.addMaterial(color, alpha, metalness, roughness);
|
||||
let faceIndices = faceIndicesByMaterial.get(materialKey);
|
||||
if (faceIndices === undefined) {
|
||||
faceIndices = [];
|
||||
faceIndicesByMaterial.set(materialKey, faceIndices);
|
||||
}
|
||||
faceIndices.push(i / 3);
|
||||
}
|
||||
|
||||
// If this mesh uses only one material, bind it to the material directly.
|
||||
// Otherwise, use GeomSubsets to bind it to multiple materials.
|
||||
let materialBinding: string;
|
||||
if (faceIndicesByMaterial.size === 1) {
|
||||
const materialKey = faceIndicesByMaterial.keys().next().value;
|
||||
materialBinding = `rel material:binding = </material${materialKey}>`;
|
||||
} else {
|
||||
const geomSubsets: string[] = [];
|
||||
faceIndicesByMaterial.forEach((faceIndices: number[], materialKey: number) => {
|
||||
geomSubsets.push(`
|
||||
def GeomSubset "g${materialKey}"
|
||||
{
|
||||
uniform token elementType = "face"
|
||||
uniform token familyName = "materialBind"
|
||||
int[] indices = [${faceIndices.join(',')}]
|
||||
rel material:binding = </material${materialKey}>
|
||||
}
|
||||
`);
|
||||
});
|
||||
materialBinding = geomSubsets.join('');
|
||||
}
|
||||
|
||||
this.meshes.push(`
|
||||
def Mesh "mesh${this.meshes.length}"
|
||||
{
|
||||
int[] faceVertexCounts = [${new Array(drawCount / 3).fill(3).join(',')}]
|
||||
int[] faceVertexIndices = [${StringBuilder.getString(indexBuilder)}]
|
||||
point3f[] points = [${StringBuilder.getString(vertexBuilder)}]
|
||||
normal3f[] primvars:normals = [${StringBuilder.getString(normalBuilder)}] (
|
||||
interpolation = "vertex"
|
||||
)
|
||||
uniform token subdivisionScheme = "none"
|
||||
${materialBinding}
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
async getData(ctx: RuntimeContext) {
|
||||
const header = `#usda 1.0
|
||||
(
|
||||
customLayerData = {
|
||||
string creator = "Mol* ${PLUGIN_VERSION}"
|
||||
}
|
||||
metersPerUnit = 1
|
||||
)
|
||||
`;
|
||||
const usda = [header, ...this.materials, ...this.meshes].join('');
|
||||
const usdaData = new Uint8Array(usda.length);
|
||||
asciiWrite(usdaData, usda);
|
||||
const zipDataObj = {
|
||||
['model.usda']: usdaData
|
||||
};
|
||||
return {
|
||||
usdz: await zip(ctx, zipDataObj, true)
|
||||
};
|
||||
}
|
||||
|
||||
async getBlob(ctx: RuntimeContext) {
|
||||
const { usdz } = await this.getData(ctx);
|
||||
return new Blob([usdz], { type: 'model/vnd.usdz+zip' });
|
||||
}
|
||||
|
||||
constructor(boundingBox: Box3D, radius: number) {
|
||||
super();
|
||||
const t = Mat4();
|
||||
// scale the model so that it fits within 1 meter
|
||||
Mat4.fromUniformScaling(t, Math.min(1 / (radius * 2), 1));
|
||||
// translate the model so that it sits on the ground plane (y = 0)
|
||||
Mat4.translate(t, t, Vec3.create(
|
||||
-(boundingBox.min[0] + boundingBox.max[0]) / 2,
|
||||
-boundingBox.min[1],
|
||||
-(boundingBox.min[2] + boundingBox.max[2]) / 2
|
||||
));
|
||||
this.centerTransform = t;
|
||||
}
|
||||
}
|
||||
212
src/extensions/model-archive/quality-assessment/behavior.ts
Normal file
212
src/extensions/model-archive/quality-assessment/behavior.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* Copyright (c) 2021 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 { Loci } from '../../../mol-model/loci';
|
||||
import { DefaultQueryRuntimeTable } from '../../../mol-script/runtime/query/compiler';
|
||||
import { PLDDTConfidenceColorThemeProvider } from './color/plddt';
|
||||
import { QualityAssessment, QualityAssessmentProvider } from './prop';
|
||||
import { StructureSelectionCategory, StructureSelectionQuery } from '../../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { cantorPairing } from '../../../mol-data/util';
|
||||
import { QmeanScoreColorThemeProvider } from './color/qmean';
|
||||
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StateObjectRef } from '../../../mol-state';
|
||||
|
||||
export const MAQualityAssessment = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
|
||||
name: 'ma-quality-assessment-prop',
|
||||
category: 'custom-props',
|
||||
display: {
|
||||
name: 'Quality Assessment',
|
||||
description: 'Data included in Model Archive files.'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
|
||||
private provider = QualityAssessmentProvider;
|
||||
|
||||
private labelProvider = {
|
||||
label: (loci: Loci): string | undefined => {
|
||||
if (!this.params.showTooltip) return;
|
||||
return [
|
||||
plddtLabel(loci),
|
||||
qmeanLabel(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(PLDDTConfidenceColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(QmeanScoreColorThemeProvider);
|
||||
|
||||
this.ctx.query.structure.registry.add(confidentPLDDT);
|
||||
|
||||
this.ctx.builders.structure.representation.registerPreset(QualityAssessmentPLDDTPreset);
|
||||
this.ctx.builders.structure.representation.registerPreset(QualityAssessmentQmeanPreset);
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean, showTooltip: boolean }) {
|
||||
const 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(PLDDTConfidenceColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(QmeanScoreColorThemeProvider);
|
||||
|
||||
this.ctx.query.structure.registry.remove(confidentPLDDT);
|
||||
|
||||
this.ctx.builders.structure.representation.unregisterPreset(QualityAssessmentPLDDTPreset);
|
||||
this.ctx.builders.structure.representation.unregisterPreset(QualityAssessmentQmeanPreset);
|
||||
}
|
||||
},
|
||||
params: () => ({
|
||||
autoAttach: PD.Boolean(false),
|
||||
showTooltip: PD.Boolean(true),
|
||||
})
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
function plddtCategory(score: number) {
|
||||
if (score > 50 && score <= 70) return 'Low';
|
||||
if (score > 70 && score <= 90) return 'Confident';
|
||||
if (score > 90) return 'Very high';
|
||||
return 'Very low';
|
||||
}
|
||||
|
||||
function plddtLabel(loci: Loci): string | undefined {
|
||||
return metricLabel(loci, 'pLDDT', (scoreAvg: number, countInfo: string) => `pLDDT Score ${countInfo}: ${scoreAvg.toFixed(2)} <small>(${plddtCategory(scoreAvg)})</small>`);
|
||||
}
|
||||
|
||||
function qmeanLabel(loci: Loci): string | undefined {
|
||||
return metricLabel(loci, 'qmean', (scoreAvg: number, countInfo: string) => `QMEAN Score ${countInfo}: ${scoreAvg.toFixed(2)}`);
|
||||
}
|
||||
|
||||
function metricLabel(loci: Loci, name: 'qmean' | 'pLDDT', label: (scoreAvg: number, countInfo: string) => string): string | undefined {
|
||||
if (loci.kind === 'element-loci') {
|
||||
if (loci.elements.length === 0) return;
|
||||
|
||||
const seen = new Set<number>();
|
||||
const scoreSeen = new Set<number>();
|
||||
let scoreSum = 0;
|
||||
|
||||
for (const { indices, unit } of loci.elements) {
|
||||
const metric = QualityAssessmentProvider.get(unit.model).value?.[name];
|
||||
if (!metric) continue;
|
||||
|
||||
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 score = metric.get(residueIndex[eI]) ?? -1;
|
||||
if (score !== -1) {
|
||||
scoreSum += score;
|
||||
scoreSeen.add(residueKey);
|
||||
}
|
||||
seen.add(residueKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (seen.size === 0) return;
|
||||
|
||||
const summary: string[] = [];
|
||||
|
||||
if (scoreSeen.size) {
|
||||
const countInfo = `<small>(${scoreSeen.size} ${scoreSeen.size > 1 ? 'Residues avg.' : 'Residue'})</small>`;
|
||||
const scoreAvg = scoreSum / scoreSeen.size;
|
||||
summary.push(label(scoreAvg, countInfo));
|
||||
}
|
||||
|
||||
if (summary.length) {
|
||||
return summary.join('</br>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const confidentPLDDT = StructureSelectionQuery('Confident pLDDT (> 70)', 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']),
|
||||
'residue-test': MS.core.rel.gr([QualityAssessment.symbols.pLDDT.symbol(), 70]),
|
||||
})
|
||||
])
|
||||
])
|
||||
]), {
|
||||
description: 'Select residues with a pLDDT > 70 (confident).',
|
||||
category: StructureSelectionCategory.Validation,
|
||||
ensureCustomProperties: async (ctx, structure) => {
|
||||
for (const m of structure.models) {
|
||||
await QualityAssessmentProvider.attach(ctx, m, void 0, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
export const QualityAssessmentPLDDTPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-ma-quality-assessment-plddt',
|
||||
display: {
|
||||
name: 'Quality Assessment (pLDDT)', group: 'Annotation',
|
||||
description: 'Color structure based on pLDDT Confidence.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return !!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'));
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
const colorTheme = PLDDTConfidenceColorThemeProvider.name as any;
|
||||
return await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
|
||||
}
|
||||
});
|
||||
|
||||
export const QualityAssessmentQmeanPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-ma-quality-assessment-qmean',
|
||||
display: {
|
||||
name: 'Quality Assessment (QMEAN)', group: 'Annotation',
|
||||
description: 'Color structure based on QMEAN Score.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return !!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean'));
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
const colorTheme = QmeanScoreColorThemeProvider.name as any;
|
||||
return await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
|
||||
}
|
||||
});
|
||||
106
src/extensions/model-archive/quality-assessment/color/plddt.ts
Normal file
106
src/extensions/model-archive/quality-assessment/color/plddt.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Mandar Deshpande <mandar@ebi.ac.uk>
|
||||
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { QualityAssessment, QualityAssessmentProvider } from '../prop';
|
||||
import { Location } from '../../../../mol-model/location';
|
||||
import { Bond, StructureElement, Unit } from '../../../../mol-model/structure';
|
||||
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
|
||||
import { ThemeDataContext } from '../../../../mol-theme/theme';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
|
||||
import { TableLegend } from '../../../../mol-util/legend';
|
||||
|
||||
const DefaultColor = Color(0xaaaaaa);
|
||||
const ConfidenceColors = {
|
||||
'No Score': DefaultColor,
|
||||
'Very Low': Color(0xff7d45),
|
||||
'Low': Color(0xffdb13),
|
||||
'Confident': Color(0x65cbf3),
|
||||
'Very High': Color(0x0053d6)
|
||||
};
|
||||
|
||||
const ConfidenceColorLegend = TableLegend(Object.entries(ConfidenceColors));
|
||||
|
||||
export function getPLDDTConfidenceColorThemeParams(ctx: ThemeDataContext) {
|
||||
return {};
|
||||
}
|
||||
export type PLDDTConfidenceColorThemeParams = ReturnType<typeof getPLDDTConfidenceColorThemeParams>
|
||||
|
||||
export function PLDDTConfidenceColorTheme(ctx: ThemeDataContext, props: PD.Values<PLDDTConfidenceColorThemeParams>): ColorTheme<PLDDTConfidenceColorThemeParams> {
|
||||
let color: LocationColor = () => DefaultColor;
|
||||
|
||||
if (ctx.structure) {
|
||||
const l = StructureElement.Location.create(ctx.structure.root);
|
||||
|
||||
const getColor = (location: StructureElement.Location): Color => {
|
||||
const { unit, element } = location;
|
||||
if (!Unit.isAtomic(unit)) return DefaultColor;
|
||||
const qualityAssessment = QualityAssessmentProvider.get(unit.model).value;
|
||||
const score = qualityAssessment?.pLDDT?.get(unit.model.atomicHierarchy.residueAtomSegments.index[element]) ?? -1;
|
||||
if (score < 0) {
|
||||
return DefaultColor;
|
||||
} else if (score <= 50) {
|
||||
return Color(0xff7d45);
|
||||
} else if (score <= 70) {
|
||||
return Color(0xffdb13);
|
||||
} else if (score <= 90) {
|
||||
return Color(0x65cbf3);
|
||||
} else {
|
||||
return Color(0x0053d6);
|
||||
}
|
||||
};
|
||||
|
||||
color = (location: Location) => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
return getColor(location);
|
||||
} else if (Bond.isLocation(location)) {
|
||||
l.unit = location.aUnit;
|
||||
l.element = location.aUnit.elements[location.aIndex];
|
||||
return getColor(l);
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
factory: PLDDTConfidenceColorTheme,
|
||||
granularity: 'group',
|
||||
preferSmoothing: true,
|
||||
color,
|
||||
props,
|
||||
description: 'Assigns residue colors according to the pLDDT Confidence score.',
|
||||
legend: ConfidenceColorLegend
|
||||
};
|
||||
}
|
||||
|
||||
export const PLDDTConfidenceColorThemeProvider: ColorTheme.Provider<PLDDTConfidenceColorThemeParams, 'plddt-confidence'> = {
|
||||
name: 'plddt-confidence',
|
||||
label: 'pLDDT Confidence',
|
||||
category: ColorTheme.Category.Validation,
|
||||
factory: PLDDTConfidenceColorTheme,
|
||||
getParams: getPLDDTConfidenceColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(getPLDDTConfidenceColorThemeParams({})),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure?.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')),
|
||||
ensureCustomProperties: {
|
||||
attach: async (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
if (data.structure) {
|
||||
for (const m of data.structure.models) {
|
||||
await QualityAssessmentProvider.attach(ctx, m, void 0, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
detach: async (data: ThemeDataContext) => {
|
||||
if (data.structure) {
|
||||
for (const m of data.structure.models) {
|
||||
QualityAssessmentProvider.ref(m, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { QualityAssessment, QualityAssessmentProvider } from '../prop';
|
||||
import { Location } from '../../../../mol-model/location';
|
||||
import { Bond, StructureElement, Unit } from '../../../../mol-model/structure';
|
||||
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
|
||||
import { ThemeDataContext } from '../../../../mol-theme/theme';
|
||||
import { Color, ColorScale } from '../../../../mol-util/color';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
|
||||
|
||||
const DefaultColor = Color(0xaaaaaa);
|
||||
|
||||
export function getQmeanScoreColorThemeParams(ctx: ThemeDataContext) {
|
||||
return {};
|
||||
}
|
||||
export type QmeanScoreColorThemeParams = ReturnType<typeof getQmeanScoreColorThemeParams>
|
||||
|
||||
export function QmeanScoreColorTheme(ctx: ThemeDataContext, props: PD.Values<QmeanScoreColorThemeParams>): ColorTheme<QmeanScoreColorThemeParams> {
|
||||
let color: LocationColor = () => DefaultColor;
|
||||
|
||||
const scale = ColorScale.create({
|
||||
domain: [0, 1],
|
||||
listOrName: [
|
||||
[Color(0xFF5000), 0.5], [Color(0x025AFD), 1.0]
|
||||
]
|
||||
});
|
||||
|
||||
if (ctx.structure) {
|
||||
const l = StructureElement.Location.create(ctx.structure.root);
|
||||
|
||||
const getColor = (location: StructureElement.Location): Color => {
|
||||
const { unit, element } = location;
|
||||
if (!Unit.isAtomic(unit)) return DefaultColor;
|
||||
const qualityAssessment = QualityAssessmentProvider.get(unit.model).value;
|
||||
const score = qualityAssessment?.qmean?.get(unit.model.atomicHierarchy.residueAtomSegments.index[element]) ?? -1;
|
||||
if (score < 0) {
|
||||
return DefaultColor;
|
||||
} else {
|
||||
return scale.color(score);
|
||||
}
|
||||
};
|
||||
|
||||
color = (location: Location) => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
return getColor(location);
|
||||
} else if (Bond.isLocation(location)) {
|
||||
l.unit = location.aUnit;
|
||||
l.element = location.aUnit.elements[location.aIndex];
|
||||
return getColor(l);
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
factory: QmeanScoreColorTheme,
|
||||
granularity: 'group',
|
||||
preferSmoothing: true,
|
||||
color,
|
||||
props,
|
||||
description: 'Assigns residue colors according to the QMEAN score.',
|
||||
legend: scale.legend
|
||||
};
|
||||
}
|
||||
|
||||
export const QmeanScoreColorThemeProvider: ColorTheme.Provider<QmeanScoreColorThemeParams, 'qmean-score'> = {
|
||||
name: 'qmean-score',
|
||||
label: 'QMEAN Score',
|
||||
category: ColorTheme.Category.Validation,
|
||||
factory: QmeanScoreColorTheme,
|
||||
getParams: getQmeanScoreColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(getQmeanScoreColorThemeParams({})),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure?.models.some(m => QualityAssessment.isApplicable(m, 'qmean')),
|
||||
ensureCustomProperties: {
|
||||
attach: async (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
if (data.structure) {
|
||||
for (const m of data.structure.models) {
|
||||
await QualityAssessmentProvider.attach(ctx, m, void 0, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
detach: async (data: ThemeDataContext) => {
|
||||
if (data.structure) {
|
||||
for (const m of data.structure.models) {
|
||||
QualityAssessmentProvider.ref(m, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
131
src/extensions/model-archive/quality-assessment/prop.ts
Normal file
131
src/extensions/model-archive/quality-assessment/prop.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (c) 2021 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 { 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, ResidueIndex } from '../../../mol-model/structure/model';
|
||||
import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
|
||||
import { CustomPropSymbol } from '../../../mol-script/language/symbol';
|
||||
import { Type } from '../../../mol-script/language/type';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
|
||||
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
|
||||
export { QualityAssessment };
|
||||
|
||||
interface QualityAssessment {
|
||||
localMetrics: Map<string, Map<ResidueIndex, number>>
|
||||
pLDDT?: Map<ResidueIndex, number>
|
||||
qmean?: Map<ResidueIndex, number>
|
||||
}
|
||||
|
||||
namespace QualityAssessment {
|
||||
const Empty = {
|
||||
value: {
|
||||
localMetrics: new Map()
|
||||
}
|
||||
};
|
||||
|
||||
export function isApplicable(model?: Model, localMetricName?: 'pLDDT' | 'qmean'): boolean {
|
||||
if (!model || !MmcifFormat.is(model.sourceData)) return false;
|
||||
const { db } = model.sourceData.data;
|
||||
const hasLocalMetric = (
|
||||
db.ma_qa_metric.id.isDefined &&
|
||||
db.ma_qa_metric_local.ordinal_id.isDefined
|
||||
);
|
||||
if (localMetricName && hasLocalMetric) {
|
||||
for (let i = 0, il = db.ma_qa_metric._rowCount; i < il; i++) {
|
||||
if (db.ma_qa_metric.mode.value(i) !== 'local') continue;
|
||||
if (localMetricName === db.ma_qa_metric.name.value(i)) return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return hasLocalMetric;
|
||||
}
|
||||
}
|
||||
|
||||
export async function obtain(ctx: CustomProperty.Context, model: Model, props: QualityAssessmentProps): Promise<CustomProperty.Data<QualityAssessment>> {
|
||||
if (!model || !MmcifFormat.is(model.sourceData)) return Empty;
|
||||
const { ma_qa_metric, ma_qa_metric_local } = model.sourceData.data.db;
|
||||
const { model_id, label_asym_id, label_seq_id, metric_id, metric_value } = ma_qa_metric_local;
|
||||
const { index } = model.atomicHierarchy;
|
||||
|
||||
// for simplicity we assume names in ma_qa_metric for mode 'local' are unique
|
||||
const localMetrics = new Map<string, Map<ResidueIndex, number>>();
|
||||
const localNames = new Map<number, string>();
|
||||
|
||||
for (let i = 0, il = ma_qa_metric._rowCount; i < il; i++) {
|
||||
if (ma_qa_metric.mode.value(i) !== 'local') continue;
|
||||
|
||||
const name = ma_qa_metric.name.value(i);
|
||||
if (localMetrics.has(name)) {
|
||||
console.warn(`local ma_qa_metric with name '${name}' already added`);
|
||||
continue;
|
||||
}
|
||||
|
||||
localMetrics.set(name, new Map());
|
||||
localNames.set(ma_qa_metric.id.value(i), name);
|
||||
}
|
||||
|
||||
for (let i = 0, il = ma_qa_metric_local._rowCount; i < il; i++) {
|
||||
if (model_id.value(i) !== model.modelNum) continue;
|
||||
|
||||
const labelAsymId = label_asym_id.value(i);
|
||||
const entityIndex = index.findEntity(labelAsymId);
|
||||
const rI = index.findResidue(model.entities.data.id.value(entityIndex), labelAsymId, label_seq_id.value(i));
|
||||
const name = localNames.get(metric_id.value(i))!;
|
||||
localMetrics.get(name)!.set(rI, metric_value.value(i));
|
||||
}
|
||||
|
||||
return {
|
||||
value: {
|
||||
localMetrics,
|
||||
pLDDT: localMetrics.get('pLDDT'),
|
||||
qmean: localMetrics.get('qmean'),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const symbols = {
|
||||
pLDDT: QuerySymbolRuntime.Dynamic(CustomPropSymbol('ma', 'quality-assessment.pLDDT', Type.Num),
|
||||
ctx => {
|
||||
const { unit, element } = ctx.element;
|
||||
if (!Unit.isAtomic(unit)) return -1;
|
||||
const qualityAssessment = QualityAssessmentProvider.get(unit.model).value;
|
||||
return qualityAssessment?.pLDDT?.get(unit.model.atomicHierarchy.residueAtomSegments.index[element]) ?? -1;
|
||||
}
|
||||
),
|
||||
qmean: QuerySymbolRuntime.Dynamic(CustomPropSymbol('ma', 'quality-assessment.qmean', Type.Num),
|
||||
ctx => {
|
||||
const { unit, element } = ctx.element;
|
||||
if (!Unit.isAtomic(unit)) return -1;
|
||||
const qualityAssessment = QualityAssessmentProvider.get(unit.model).value;
|
||||
return qualityAssessment?.qmean?.get(unit.model.atomicHierarchy.residueAtomSegments.index[element]) ?? -1;
|
||||
}
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export const QualityAssessmentParams = { };
|
||||
export type QualityAssessmentParams = typeof QualityAssessmentParams
|
||||
export type QualityAssessmentProps = PD.Values<QualityAssessmentParams>
|
||||
|
||||
export const QualityAssessmentProvider: CustomModelProperty.Provider<QualityAssessmentParams, QualityAssessment> = CustomModelProperty.createProvider({
|
||||
label: 'QualityAssessment',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'ma_quality_assessment',
|
||||
symbols: QualityAssessment.symbols
|
||||
}),
|
||||
type: 'static',
|
||||
defaultParams: QualityAssessmentParams,
|
||||
getParams: (data: Model) => QualityAssessmentParams,
|
||||
isApplicable: (data: Model) => QualityAssessment.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<QualityAssessmentProps>) => {
|
||||
const p = { ...PD.getDefaultValues(QualityAssessmentParams), ...props };
|
||||
return await QualityAssessment.obtain(ctx, data, p);
|
||||
}
|
||||
});
|
||||
147
src/extensions/mp4-export/controls.ts
Normal file
147
src/extensions/mp4-export/controls.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
import { PluginStateAnimation } from '../../mol-plugin-state/animation/model';
|
||||
import { PluginComponent } from '../../mol-plugin-state/component';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { encodeMp4Animation } from './encoder';
|
||||
|
||||
export interface Mp4AnimationInfo {
|
||||
width: number,
|
||||
height: number
|
||||
}
|
||||
|
||||
export const Mp4AnimationParams = {
|
||||
quantization: PD.Numeric(18, { min: 10, max: 51 }, { description: 'Lower is better, but slower.' })
|
||||
};
|
||||
|
||||
export class Mp4Controls extends PluginComponent {
|
||||
private currentNames = new Set<string>();
|
||||
private animations: PluginStateAnimation[] = [];
|
||||
|
||||
readonly behaviors = {
|
||||
animations: this.ev.behavior<PD.Params>({ }),
|
||||
current: this.ev.behavior<{ anim: PluginStateAnimation, params: PD.Params, values: any } | undefined>(void 0),
|
||||
canApply: this.ev.behavior<PluginStateAnimation.CanApply>({ canApply: false }),
|
||||
info: this.ev.behavior<Mp4AnimationInfo>({ width: 0, height: 0 }),
|
||||
params: this.ev.behavior<PD.Values<typeof Mp4AnimationParams>>(PD.getDefaultValues(Mp4AnimationParams))
|
||||
};
|
||||
|
||||
setCurrent(name?: string) {
|
||||
const anim = this.animations.find(a => a.name === name);
|
||||
if (!anim) {
|
||||
this.behaviors.current.next(anim);
|
||||
return;
|
||||
}
|
||||
|
||||
const params = anim.params(this.plugin) as PD.Params;
|
||||
const values = PD.getDefaultValues(params);
|
||||
|
||||
this.behaviors.current.next({ anim, params, values });
|
||||
this.behaviors.canApply.next(anim.canApply?.(this.plugin) ?? { canApply: true });
|
||||
}
|
||||
|
||||
setCurrentParams(values: any) {
|
||||
this.behaviors.current.next({ ...this.behaviors.current.value!, values });
|
||||
}
|
||||
|
||||
get current() {
|
||||
return this.behaviors.current.value;
|
||||
}
|
||||
|
||||
render() {
|
||||
const task = Task.create('Export Animation', async ctx => {
|
||||
try {
|
||||
const resolution = this.plugin.helpers.viewportScreenshot?.getSizeAndViewport()!;
|
||||
const anim = this.current!;
|
||||
const movie = await encodeMp4Animation(this.plugin, ctx, {
|
||||
animation: {
|
||||
definition: anim.anim,
|
||||
params: anim.values,
|
||||
},
|
||||
...resolution,
|
||||
quantizationParameter: this.behaviors.params.value.quantization,
|
||||
pass: this.plugin.helpers.viewportScreenshot?.imagePass!,
|
||||
});
|
||||
|
||||
const filename = anim.anim.display.name.toLowerCase().replace(/\s/g, '-').replace(/[^a-z0-9_\-]/g, '');
|
||||
return { movie, filename: `${this.plugin.helpers.viewportScreenshot?.getFilename('')}_${filename}.mp4` };
|
||||
} catch (e) {
|
||||
this.plugin.log.error('Error during animation export');
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
return this.plugin.runTask(task, { useOverlay: true });
|
||||
}
|
||||
|
||||
private get manager() {
|
||||
return this.plugin.managers.animation;
|
||||
}
|
||||
|
||||
private syncInfo() {
|
||||
const helper = this.plugin.helpers.viewportScreenshot;
|
||||
const size = helper?.getSizeAndViewport();
|
||||
if (!size) return;
|
||||
|
||||
this.behaviors.info.next({ width: size.viewport.width, height: size.viewport.height });
|
||||
}
|
||||
|
||||
private sync() {
|
||||
const animations = this.manager.animations.filter(a => a.isExportable);
|
||||
|
||||
const hasAll = animations.every(a => this.currentNames.has(a.name));
|
||||
if (hasAll && this.currentNames.size === animations.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params = {
|
||||
current: PD.Select(animations[0]?.name,
|
||||
animations.map(a => [a.name, a.display.name] as [string, string]),
|
||||
{ label: 'Animation' })
|
||||
};
|
||||
|
||||
const current = this.behaviors.current.value;
|
||||
const hasCurrent = !!animations.find(a => a.name === current?.anim.name);
|
||||
|
||||
this.animations = animations;
|
||||
if (!hasCurrent) {
|
||||
this.setCurrent(animations[0]?.name);
|
||||
}
|
||||
this.behaviors.animations.next(params);
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.subscribe(this.plugin.managers.animation.events.updated.pipe(debounceTime(16)), () => {
|
||||
this.sync();
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.canvas3d?.resized!, () => this.syncInfo());
|
||||
this.subscribe(this.plugin.helpers.viewportScreenshot?.events.previewed!, () => this.syncInfo());
|
||||
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, b => this.updateCanApply(b));
|
||||
this.subscribe(this.plugin.managers.snapshot.events.changed, b => this.updateCanApply(b));
|
||||
|
||||
this.sync();
|
||||
this.syncInfo();
|
||||
}
|
||||
|
||||
private updateCanApply(b?: any) {
|
||||
const anim = this.current;
|
||||
if (!b && anim) {
|
||||
this.behaviors.canApply.next(anim.anim.canApply?.(this.plugin) ?? { canApply: true });
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private plugin: PluginContext) {
|
||||
super();
|
||||
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user