mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 21:34:23 +08:00
Compare commits
2066 Commits
v3.0.0-dev
...
v4.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16d5299fbd | ||
|
|
c85aa6f979 | ||
|
|
dfab137696 | ||
|
|
629fc65b81 | ||
|
|
f06064a0c6 | ||
|
|
fa70a0e5eb | ||
|
|
f1a2c5c4ee | ||
|
|
169d6323ca | ||
|
|
fefed0a5f0 | ||
|
|
d04311a989 | ||
|
|
0273df2c16 | ||
|
|
05494953df | ||
|
|
d6b79de86d | ||
|
|
20327871a8 | ||
|
|
dd0845038d | ||
|
|
d5d3977506 | ||
|
|
1675e18f57 | ||
|
|
d8bfe78b30 | ||
|
|
589d3465ef | ||
|
|
ebc63099b4 | ||
|
|
512a099a28 | ||
|
|
d056bf3549 | ||
|
|
e2c929fa33 | ||
|
|
a619ae6687 | ||
|
|
e40fc34a6f | ||
|
|
2734377d0a | ||
|
|
86575a7a15 | ||
|
|
31227d2050 | ||
|
|
2e73a37de2 | ||
|
|
32c3ef0801 | ||
|
|
b70f2e073d | ||
|
|
03b4aa1b33 | ||
|
|
0a4643ebe4 | ||
|
|
8dc3dae81e | ||
|
|
a1e2b0e5ae | ||
|
|
46af7d03bf | ||
|
|
60550cfea1 | ||
|
|
5de4569ad9 | ||
|
|
908fb0eba9 | ||
|
|
e7e191a907 | ||
|
|
de46c82c78 | ||
|
|
d8f3ab767e | ||
|
|
d34a9be20f | ||
|
|
78628c217f | ||
|
|
d29bf2eec2 | ||
|
|
5db60c2882 | ||
|
|
737846e093 | ||
|
|
2ad551bdc7 | ||
|
|
46fa581f07 | ||
|
|
17dbe4b60e | ||
|
|
aeaf2e799a | ||
|
|
ef16b718c4 | ||
|
|
4b4f6d34a3 | ||
|
|
ab4130d42d | ||
|
|
9e73de89fb | ||
|
|
02cec6f8e6 | ||
|
|
e97a02473f | ||
|
|
827e75ce0e | ||
|
|
22e5c9d65b | ||
|
|
10d9120d37 | ||
|
|
e6c20d35bd | ||
|
|
27bf66038d | ||
|
|
3ce6d89521 | ||
|
|
24608ac355 | ||
|
|
da034d9502 | ||
|
|
581673fb9b | ||
|
|
29cf97e6cf | ||
|
|
f65773d654 | ||
|
|
d11c8c166a | ||
|
|
ae6bd743a8 | ||
|
|
c1654574d0 | ||
|
|
6f506351cd | ||
|
|
db83b97ff9 | ||
|
|
5c818b35ad | ||
|
|
b1ce20f5f5 | ||
|
|
d1cbebf8a7 | ||
|
|
c771cfef99 | ||
|
|
6ee72d5e8c | ||
|
|
b859b3c597 | ||
|
|
8281909620 | ||
|
|
f3c30ae4b3 | ||
|
|
9ad53eb0d5 | ||
|
|
a15e9158b4 | ||
|
|
6bfedc038e | ||
|
|
966293994f | ||
|
|
09f4c857de | ||
|
|
e05bd5f0c5 | ||
|
|
a3cdc2844e | ||
|
|
a51947637c | ||
|
|
72ee428f51 | ||
|
|
4fe203d3ea | ||
|
|
2ec32641ee | ||
|
|
8d5198f15c | ||
|
|
d5154c7391 | ||
|
|
6ee750e6bd | ||
|
|
ea8aa10704 | ||
|
|
19ba76a1f1 | ||
|
|
fc28046df4 | ||
|
|
20f0416a8c | ||
|
|
1298baf60d | ||
|
|
999b86ab81 | ||
|
|
75f2acd8eb | ||
|
|
1541ba10f1 | ||
|
|
00ca25ffd7 | ||
|
|
302e1c659f | ||
|
|
8b4d987f94 | ||
|
|
c383012fd0 | ||
|
|
66b6ddc527 | ||
|
|
15d60c51a9 | ||
|
|
fe82cd4f2c | ||
|
|
b3e792cbc3 | ||
|
|
2f0230dc84 | ||
|
|
45522ad410 | ||
|
|
3fe80fe61a | ||
|
|
ede1a8da07 | ||
|
|
0199afd5f3 | ||
|
|
f8bda3617f | ||
|
|
bffd7d75e0 | ||
|
|
e1315b6642 | ||
|
|
205413e696 | ||
|
|
2c0a486531 | ||
|
|
90a4e019ac | ||
|
|
640426ae16 | ||
|
|
d1e620eed1 | ||
|
|
1a2bad7c77 | ||
|
|
b6e4d7721e | ||
|
|
e5902d15e2 | ||
|
|
4e2b7b5ffc | ||
|
|
bcbab9cb9d | ||
|
|
9a4b0116eb | ||
|
|
60d0de8d88 | ||
|
|
6a520d2877 | ||
|
|
6edbae80db | ||
|
|
aac0abed32 | ||
|
|
6805dd7947 | ||
|
|
6c9254f2b6 | ||
|
|
897d443873 | ||
|
|
f276ea2258 | ||
|
|
58d735996e | ||
|
|
72b66367f3 | ||
|
|
c592a3b93d | ||
|
|
3e6d0c8c62 | ||
|
|
60ed853c02 | ||
|
|
ba58b8e559 | ||
|
|
2c3d122820 | ||
|
|
47f4b3a051 | ||
|
|
daed91e1ea | ||
|
|
3d0b9c98e4 | ||
|
|
966e8e764f | ||
|
|
14441a4ee3 | ||
|
|
e773824fb5 | ||
|
|
cc6a990b85 | ||
|
|
ffa7d66c66 | ||
|
|
abc29ceefb | ||
|
|
e2f137a495 | ||
|
|
ebbe78fb1d | ||
|
|
5d139b6db8 | ||
|
|
8d5aad2be1 | ||
|
|
e8601d3f5e | ||
|
|
c63622677a | ||
|
|
163cee6d66 | ||
|
|
cd1d2010da | ||
|
|
8f0ab2fdf9 | ||
|
|
7dbc8ab4f1 | ||
|
|
6a730a6ce7 | ||
|
|
ca606284b3 | ||
|
|
396c530e13 | ||
|
|
4cc3072165 | ||
|
|
4598841ddc | ||
|
|
728414366d | ||
|
|
1b7b38b47e | ||
|
|
dd15b1ed29 | ||
|
|
446ea9173b | ||
|
|
70f8515e51 | ||
|
|
90fac967f2 | ||
|
|
1796dfd73a | ||
|
|
a73cb1648d | ||
|
|
b715205a04 | ||
|
|
ef17cb2cca | ||
|
|
b7b52f5c7d | ||
|
|
79ed8e7de4 | ||
|
|
60a2eb8a22 | ||
|
|
b833d41c1e | ||
|
|
85d2ed0132 | ||
|
|
2552a5218f | ||
|
|
c4a4562d82 | ||
|
|
a3b9d30c7c | ||
|
|
a3572961ac | ||
|
|
3118a46250 | ||
|
|
d238b8aee9 | ||
|
|
22a57d8f48 | ||
|
|
119e8f63eb | ||
|
|
e6a4122d1c | ||
|
|
9ba5f1f540 | ||
|
|
0a0ac7ee63 | ||
|
|
7670df04ae | ||
|
|
98744af872 | ||
|
|
f4eb509887 | ||
|
|
cbb8c0536d | ||
|
|
d7d798746c | ||
|
|
c39d092fc1 | ||
|
|
102b69961c | ||
|
|
681d64dcd9 | ||
|
|
2083407a84 | ||
|
|
a07f90a405 | ||
|
|
c7ca528778 | ||
|
|
ca36264cf8 | ||
|
|
7e5b181d92 | ||
|
|
c538e1d0c8 | ||
|
|
75e23d73b0 | ||
|
|
39f077b066 | ||
|
|
78874c0024 | ||
|
|
7069760d07 | ||
|
|
df0af2b1ca | ||
|
|
edd6419b2b | ||
|
|
c6210ae1a0 | ||
|
|
446455494e | ||
|
|
c4bc16fe5d | ||
|
|
988cee0047 | ||
|
|
39fa185f3d | ||
|
|
cb0484bca1 | ||
|
|
bf0707a2aa | ||
|
|
9a2191e1cc | ||
|
|
36ead9dda3 | ||
|
|
71e1bb849e | ||
|
|
975cceed77 | ||
|
|
82065dc5b7 | ||
|
|
39ae03ff8c | ||
|
|
45ab88f0a1 | ||
|
|
f0d649f265 | ||
|
|
44ce5df136 | ||
|
|
b00bce69fd | ||
|
|
e2e9e5f6fc | ||
|
|
36cf2853b2 | ||
|
|
efc322a375 | ||
|
|
7e792a4f90 | ||
|
|
44b3a9b121 | ||
|
|
55f72e65dc | ||
|
|
745746f243 | ||
|
|
b5f229ba6d | ||
|
|
d5a47e617a | ||
|
|
a200ca5b21 | ||
|
|
65b52c8ecd | ||
|
|
203fb2f7fe | ||
|
|
492494033f | ||
|
|
9de6d86a0f | ||
|
|
23fdbdea54 | ||
|
|
8fa9c19346 | ||
|
|
ab10007d60 | ||
|
|
7a6094298b | ||
|
|
ab34a59677 | ||
|
|
7a96cdd52d | ||
|
|
65cad5ea4d | ||
|
|
fe433fc7b0 | ||
|
|
ba173978d3 | ||
|
|
b3aed2ead7 | ||
|
|
c57774a62e | ||
|
|
199b767bbd | ||
|
|
0c3131ff56 | ||
|
|
7433fe07f1 | ||
|
|
a765ba8e3b | ||
|
|
8594ce80a9 | ||
|
|
915797c4a4 | ||
|
|
70ebdc6b80 | ||
|
|
44c69f538b | ||
|
|
b53a52b04d | ||
|
|
e4396039fd | ||
|
|
296b6902b1 | ||
|
|
0795f06f28 | ||
|
|
a01f159889 | ||
|
|
4ea2eadb78 | ||
|
|
e548a3ed85 | ||
|
|
0e326bd4de | ||
|
|
c1de7df9fb | ||
|
|
3addc567a9 | ||
|
|
4d2d127dcc | ||
|
|
fc44e66b26 | ||
|
|
98f3f5a23b | ||
|
|
f2f10d0cb5 | ||
|
|
aed1056d6c | ||
|
|
be47ac09c9 | ||
|
|
d5e7797a40 | ||
|
|
0aeac628c7 | ||
|
|
668d617cd7 | ||
|
|
62ed993f0d | ||
|
|
aa0a008a41 | ||
|
|
89f01f202d | ||
|
|
733190f7a0 | ||
|
|
50429aacfa | ||
|
|
fa541bdbd3 | ||
|
|
77d173afed | ||
|
|
1415a21c09 | ||
|
|
dd2593475a | ||
|
|
a934001ae8 | ||
|
|
e5d4606437 | ||
|
|
350dddd83a | ||
|
|
7566dd4e9a | ||
|
|
cb04c74c60 | ||
|
|
ad937f8fe5 | ||
|
|
66017cf166 | ||
|
|
898f0d6462 | ||
|
|
cf90418f89 | ||
|
|
fb16cd0070 | ||
|
|
5ca126be94 | ||
|
|
7a8de81e6f | ||
|
|
143760f7db | ||
|
|
c427549b8d | ||
|
|
310300bde8 | ||
|
|
11604b9e8f | ||
|
|
973b326157 | ||
|
|
c37c63d990 | ||
|
|
cc1bf482f2 | ||
|
|
61a351b3d4 | ||
|
|
9e91a242bf | ||
|
|
c3daa1a162 | ||
|
|
fe086fb62e | ||
|
|
c2217829a3 | ||
|
|
f30686917f | ||
|
|
7b18545e1d | ||
|
|
6333c8073f | ||
|
|
2801bcf111 | ||
|
|
8a2461e157 | ||
|
|
0a081e2a8a | ||
|
|
700a3fe95c | ||
|
|
febc634d8b | ||
|
|
0105f75bb6 | ||
|
|
4cc2073eaa | ||
|
|
b657fa15f5 | ||
|
|
72446d06ed | ||
|
|
dd7499b7f6 | ||
|
|
9ac204cb6e | ||
|
|
73378bbe9d | ||
|
|
9b5fd2595c | ||
|
|
bca2073ed0 | ||
|
|
f264e4d6b8 | ||
|
|
c86c00c958 | ||
|
|
bbce807d5e | ||
|
|
795222b5b4 | ||
|
|
25eb4450ad | ||
|
|
140df13dae | ||
|
|
8b132d599e | ||
|
|
cbaf5129f9 | ||
|
|
117e2b2a1a | ||
|
|
301b9287ea | ||
|
|
81a738c6a6 | ||
|
|
f4d15a5a31 | ||
|
|
792cd513a8 | ||
|
|
14e6172c33 | ||
|
|
17e278cefb | ||
|
|
911433e056 | ||
|
|
6b585cf0d6 | ||
|
|
86211aaf3a | ||
|
|
071623f5b6 | ||
|
|
21e514ec1e | ||
|
|
9bc0ab12e7 | ||
|
|
1d1bd05400 | ||
|
|
faa750bbf9 | ||
|
|
e92e5c5cef | ||
|
|
b49230ea1f | ||
|
|
44ebc1d39a | ||
|
|
8d8e45f4ce | ||
|
|
898d877aa1 | ||
|
|
85dba9b1a4 | ||
|
|
6b5e90c5fa | ||
|
|
e231fbf3d7 | ||
|
|
0ee8525b2d | ||
|
|
106ee614e7 | ||
|
|
34056751f9 | ||
|
|
cc737192ca | ||
|
|
1afea8a86a | ||
|
|
96d5bf2447 | ||
|
|
f9265a7049 | ||
|
|
5c57137890 | ||
|
|
4e71618d0f | ||
|
|
de660cc233 | ||
|
|
616a1dabfa | ||
|
|
46ea39703f | ||
|
|
6cf20d0c44 | ||
|
|
0737e23b70 | ||
|
|
70d0c15d28 | ||
|
|
9272c8c5ec | ||
|
|
a3349f82fc | ||
|
|
11aeb6daf4 | ||
|
|
daec7e31ff | ||
|
|
c98cdc9360 | ||
|
|
4d399edbdd | ||
|
|
64598eba96 | ||
|
|
aa25874775 | ||
|
|
dccc06d497 | ||
|
|
c000526cf8 | ||
|
|
2166ab455c | ||
|
|
4de9ce01fc | ||
|
|
f543fd5683 | ||
|
|
8535013ee5 | ||
|
|
320ab77f8e | ||
|
|
982feef0c6 | ||
|
|
bd6d04cefb | ||
|
|
250013570d | ||
|
|
5e1c351efc | ||
|
|
35739ab71b | ||
|
|
bb765968f1 | ||
|
|
eb25d66359 | ||
|
|
3db28708c3 | ||
|
|
4fb18b9afd | ||
|
|
28693177cd | ||
|
|
cd2ac77469 | ||
|
|
8777f907ee | ||
|
|
5a6ac41b28 | ||
|
|
61a294c889 | ||
|
|
71fbd6baab | ||
|
|
33430a836a | ||
|
|
2ac34f96f5 | ||
|
|
de5d60f4d2 | ||
|
|
f428e9f39e | ||
|
|
72e3a31750 | ||
|
|
5bcb0b19e5 | ||
|
|
0277581684 | ||
|
|
d508988ba4 | ||
|
|
7352799270 | ||
|
|
7b524dda7c | ||
|
|
2d26425cbe | ||
|
|
f6030aee25 | ||
|
|
609e03f7d2 | ||
|
|
ba12a8bbee | ||
|
|
947f293844 | ||
|
|
fbff0e769c | ||
|
|
3798223d39 | ||
|
|
ac9c23dc65 | ||
|
|
096f492ccb | ||
|
|
ba96da9354 | ||
|
|
6c1d17bac5 | ||
|
|
ad2ccf4e07 | ||
|
|
dc1b7b4693 | ||
|
|
59e4e2b31d | ||
|
|
d2483dc449 | ||
|
|
86dcf22728 | ||
|
|
f47823577d | ||
|
|
671a982463 | ||
|
|
4244c6f5e1 | ||
|
|
d26946e9ee | ||
|
|
cd045a6b48 | ||
|
|
2407729d27 | ||
|
|
1aa22b9fa0 | ||
|
|
b00b0827c8 | ||
|
|
fe4df71d02 | ||
|
|
8f03e07632 | ||
|
|
5958b31a6d | ||
|
|
35c9f39a69 | ||
|
|
7dd420cc18 | ||
|
|
ace991da5d | ||
|
|
5e9bc89c75 | ||
|
|
1d434c259a | ||
|
|
6d193edd68 | ||
|
|
8d0c80f270 | ||
|
|
2ae36e181d | ||
|
|
52692a405d | ||
|
|
9bf859d6ed | ||
|
|
207230d565 | ||
|
|
b7a673f38e | ||
|
|
2204e4e0d0 | ||
|
|
6276365766 | ||
|
|
505b04c92d | ||
|
|
0a91a35cf3 | ||
|
|
fc84dcb037 | ||
|
|
103b0dcebd | ||
|
|
2f29ff7314 | ||
|
|
b37f043876 | ||
|
|
f0e725f65c | ||
|
|
23a34e2df1 | ||
|
|
d11e242b70 | ||
|
|
d9af0ca068 | ||
|
|
b7f10acbf0 | ||
|
|
43749ccdbd | ||
|
|
3bf4a8f8e6 | ||
|
|
f0ae1b3347 | ||
|
|
99809d25b9 | ||
|
|
e83c0af67c | ||
|
|
2ddf94313e | ||
|
|
da5965c956 | ||
|
|
31be0af3c9 | ||
|
|
38c550b245 | ||
|
|
95a7a2cef9 | ||
|
|
5934f355c2 | ||
|
|
225d051dd6 | ||
|
|
1a1ec51736 | ||
|
|
299aae56c1 | ||
|
|
781824c961 | ||
|
|
fae4aae99f | ||
|
|
6acc87abc6 | ||
|
|
930cfa2590 | ||
|
|
35439f01aa | ||
|
|
af489e8cc5 | ||
|
|
a15f2ed456 | ||
|
|
9c921fd061 | ||
|
|
e719c54e2b | ||
|
|
71121e52af | ||
|
|
c08155717f | ||
|
|
de4164e7a4 | ||
|
|
e34d1242a9 | ||
|
|
5b82641018 | ||
|
|
de2f0c27b2 | ||
|
|
71e2afe781 | ||
|
|
3cba621fcf | ||
|
|
d79a2077c1 | ||
|
|
6925547b5f | ||
|
|
84aae8cf0a | ||
|
|
bdb42e39ec | ||
|
|
6edd54ee6d | ||
|
|
198f884d8b | ||
|
|
2c7d0a6721 | ||
|
|
7ef15ede0d | ||
|
|
3d96298b55 | ||
|
|
964f045e56 | ||
|
|
d3364ac109 | ||
|
|
a5b963c919 | ||
|
|
d6fcbbf543 | ||
|
|
f2e7e2eaf2 | ||
|
|
01c4c63114 | ||
|
|
22f9bc4ff1 | ||
|
|
c6c4350638 | ||
|
|
a17a0c4527 | ||
|
|
8e507012c1 | ||
|
|
beb4351dc9 | ||
|
|
afbb940721 | ||
|
|
f5c619a4c7 | ||
|
|
1b0401dff5 | ||
|
|
649e779100 | ||
|
|
18c4de4c0f | ||
|
|
f61e0e72a8 | ||
|
|
803f75fdde | ||
|
|
718d314eda | ||
|
|
adab6b0a6a | ||
|
|
d295ed2eca | ||
|
|
d18cbfa8cf | ||
|
|
59f881c4be | ||
|
|
0295e0ef63 | ||
|
|
dcdb95a055 | ||
|
|
e379d27722 | ||
|
|
41fbe0d2b7 | ||
|
|
1231666b06 | ||
|
|
b302bb8455 | ||
|
|
6e82405600 | ||
|
|
a678893bdb | ||
|
|
430348a3cd | ||
|
|
315401c166 | ||
|
|
b309c545f5 | ||
|
|
60b5d2d39b | ||
|
|
eb749a2a16 | ||
|
|
6db96001a3 | ||
|
|
257370ad58 | ||
|
|
a12820bab9 | ||
|
|
557bf63b55 | ||
|
|
0e32e0a785 | ||
|
|
f2c607a4b2 | ||
|
|
0d12a9e118 | ||
|
|
02f418c8c5 | ||
|
|
c59ae908b8 | ||
|
|
ba618c9e4a | ||
|
|
de5b7f31f9 | ||
|
|
e264abb57c | ||
|
|
46a84799b1 | ||
|
|
ebd3ebe7b2 | ||
|
|
af451db432 | ||
|
|
804117475b | ||
|
|
09ab8d6219 | ||
|
|
f3a5369690 | ||
|
|
8bf2fe624d | ||
|
|
5ac435c6d1 | ||
|
|
aab3759da2 | ||
|
|
db1174bd32 | ||
|
|
50c1b667c5 | ||
|
|
360031d37c | ||
|
|
9ec873e0db | ||
|
|
c830a720b0 | ||
|
|
1aa7d1e0f7 | ||
|
|
c5c8de8628 | ||
|
|
74c6d6f5a1 | ||
|
|
2bff0faff7 | ||
|
|
4df028aa77 | ||
|
|
47c2d153aa | ||
|
|
18be09e9d5 | ||
|
|
55e940e88c | ||
|
|
7a3fc2047e | ||
|
|
e246f4e5ca | ||
|
|
5e1bb4b106 | ||
|
|
0b2889bb99 | ||
|
|
2994caf411 | ||
|
|
e157993a0f | ||
|
|
6c7c9afc34 | ||
|
|
2d0b17d93c | ||
|
|
033c613c89 | ||
|
|
1985eb59dd | ||
|
|
1cf6cbf8a3 | ||
|
|
0b42379c34 | ||
|
|
414c349974 | ||
|
|
cf6d5f7194 | ||
|
|
949f5207b4 | ||
|
|
a1da374b32 | ||
|
|
5460322d4a | ||
|
|
8b2da0b787 | ||
|
|
f2e5c8a30f | ||
|
|
3eaf4dacaf | ||
|
|
d66d9b4dd7 | ||
|
|
cc52279e01 | ||
|
|
34b9a46f2d | ||
|
|
0def474f6d | ||
|
|
e0ea9a2855 | ||
|
|
2bc381fe05 | ||
|
|
95d84b9dbe | ||
|
|
1f78994ac0 | ||
|
|
fb3cd3bf52 | ||
|
|
c4414c7cc4 | ||
|
|
e2f2ceb7a9 | ||
|
|
641e7efb11 | ||
|
|
e5e02e966f | ||
|
|
11f2ef50ef | ||
|
|
869ecfaf71 | ||
|
|
cb8731815c | ||
|
|
a9177ad362 | ||
|
|
ad116df73b | ||
|
|
f30b3a410c | ||
|
|
c440ba2d4b | ||
|
|
a3267dafdb | ||
|
|
7a1e83733c | ||
|
|
7cb96ce983 | ||
|
|
a73633d0c3 | ||
|
|
b2f8e8dd4e | ||
|
|
291d7abb78 | ||
|
|
32873d787b | ||
|
|
e243d71abf | ||
|
|
2689d3f21a | ||
|
|
c1bc008114 | ||
|
|
254578460a | ||
|
|
f5467dd3b9 | ||
|
|
9eb8714e11 | ||
|
|
847678ea56 | ||
|
|
f08729a402 | ||
|
|
a7c91257a7 | ||
|
|
835369a91e | ||
|
|
62554b522f | ||
|
|
fd041cd4c3 | ||
|
|
cfbb68c8ef | ||
|
|
d7acec4f7d | ||
|
|
7da46bca8b | ||
|
|
66b4fcdc2c | ||
|
|
c480579ca8 | ||
|
|
e1a5ad16f1 | ||
|
|
512ec4f8ea | ||
|
|
bc46b07ee1 | ||
|
|
f750f1581e | ||
|
|
00ff1a1eae | ||
|
|
ae795f8ad3 | ||
|
|
9d3c071689 | ||
|
|
01cb23f566 | ||
|
|
fe8a9799ab | ||
|
|
4f18154681 | ||
|
|
2114c4a3ad | ||
|
|
2ca41b2b51 | ||
|
|
6605a2019e | ||
|
|
8b1ed5f183 | ||
|
|
f11a1b788f | ||
|
|
7928e24c54 | ||
|
|
5dbca41da6 | ||
|
|
f3fa54addf | ||
|
|
e24dc0a680 | ||
|
|
e636397f90 | ||
|
|
1f3e20704d | ||
|
|
cc9bdd4f14 | ||
|
|
6d76bf120d | ||
|
|
a50e81551f | ||
|
|
94cd5abf30 | ||
|
|
cec33fa6a7 | ||
|
|
7b14a45582 | ||
|
|
86512bcea1 | ||
|
|
f0efffceaa | ||
|
|
8b37a9509f | ||
|
|
de61956bf7 | ||
|
|
c1bc8fc262 | ||
|
|
797e6a5318 | ||
|
|
bd41f45370 | ||
|
|
7946b7403c | ||
|
|
eccf6e21e0 | ||
|
|
41c40da6c7 | ||
|
|
975f45eb01 | ||
|
|
f2399d3179 | ||
|
|
b26d62a067 | ||
|
|
926d6cbd46 | ||
|
|
7ea47d2a99 | ||
|
|
89ad8cfc15 | ||
|
|
302a309aff | ||
|
|
fbc74c0012 | ||
|
|
27a953795c | ||
|
|
c3e62bc2e5 | ||
|
|
c2ab322bd2 | ||
|
|
aeab0f235c | ||
|
|
ae2285599f | ||
|
|
104ab757d2 | ||
|
|
6ada52bc0b | ||
|
|
c526cb9f08 | ||
|
|
a1662d76fb | ||
|
|
25a4c18dce | ||
|
|
0fe4eda8ae | ||
|
|
de84a8c8c5 | ||
|
|
4fa135daf0 | ||
|
|
e244a17b57 | ||
|
|
7b61d78a9e | ||
|
|
b5540c7fe9 | ||
|
|
1c48509a17 | ||
|
|
9870cb4082 | ||
|
|
b2924761ab | ||
|
|
bf08b24991 | ||
|
|
0151ee1387 | ||
|
|
eb6d5586cd | ||
|
|
f66f1f545e | ||
|
|
5a3f245ac0 | ||
|
|
9c5f451878 | ||
|
|
6a5fb85c5a | ||
|
|
2aef5fb3e5 | ||
|
|
63a9aef5eb | ||
|
|
e36fe8c707 | ||
|
|
fc7af1f60c | ||
|
|
258cc1ef66 | ||
|
|
b3727f3774 | ||
|
|
b627d6a612 | ||
|
|
918b83133e | ||
|
|
0585f7b9ca | ||
|
|
25ab0d7799 | ||
|
|
ea313a442c | ||
|
|
6aed941fc9 | ||
|
|
57a63e0381 | ||
|
|
c525812aee | ||
|
|
e602705e6d | ||
|
|
b7d126b39b | ||
|
|
80b2864da8 | ||
|
|
36ba6f035a | ||
|
|
b22e5bb7dd | ||
|
|
eca70d7536 | ||
|
|
b84bbdace2 | ||
|
|
750b2f69fc | ||
|
|
6ffe051a8e | ||
|
|
bc3640b264 | ||
|
|
8746b6e2f4 | ||
|
|
445977d99b | ||
|
|
0f0185e18c | ||
|
|
adf8e2932f | ||
|
|
a748b1581e | ||
|
|
e35cde3d6b | ||
|
|
bbbb960b50 | ||
|
|
47a63e8906 | ||
|
|
af1e06203b | ||
|
|
e9cbd06652 | ||
|
|
356cf008ce | ||
|
|
07284e7e3d | ||
|
|
72055442b7 | ||
|
|
5fa7d84c23 | ||
|
|
de46f08bf4 | ||
|
|
fde395e2fa | ||
|
|
da1e55250e | ||
|
|
a4ab117d14 | ||
|
|
4cd7088eb8 | ||
|
|
8e1876fc25 | ||
|
|
29693ebe8c | ||
|
|
a8ee7bfcbe | ||
|
|
97fb6c044c | ||
|
|
85c5575b96 | ||
|
|
2037dad03d | ||
|
|
0a9fb603f6 | ||
|
|
0b7dadd345 | ||
|
|
a3645f5acc | ||
|
|
92f409d6fc | ||
|
|
4ada0a0d29 | ||
|
|
54714c06af | ||
|
|
c28dd8135c | ||
|
|
d3e674b135 | ||
|
|
253d872599 | ||
|
|
b8ca8a9b34 | ||
|
|
d180c26ca1 | ||
|
|
b51e50978e | ||
|
|
b897cfd028 | ||
|
|
2ce8863709 | ||
|
|
37362968d9 | ||
|
|
800393bc2b | ||
|
|
7a111c49f4 | ||
|
|
756ea140e2 | ||
|
|
f9400c2547 | ||
|
|
3baa03ccdc | ||
|
|
659e96d93c | ||
|
|
1a52c2bab4 | ||
|
|
365d7d46fd | ||
|
|
f8284508e1 | ||
|
|
e9c36c2375 | ||
|
|
5d269fd77c | ||
|
|
0064293e01 | ||
|
|
ba4b6f70d3 | ||
|
|
b4b79a6102 | ||
|
|
9d056a85ec | ||
|
|
3ed17fce6b | ||
|
|
d656dd0d18 | ||
|
|
b9054723d7 | ||
|
|
ed37890519 | ||
|
|
22dc6bb1ea | ||
|
|
0917713613 | ||
|
|
0b6be09678 | ||
|
|
27c92085c7 | ||
|
|
94e9462af8 | ||
|
|
2e52ccf5d8 | ||
|
|
b15196a284 | ||
|
|
d29cc85439 | ||
|
|
30367cf239 | ||
|
|
500fdd31d7 | ||
|
|
dcf9d1c3bb | ||
|
|
c0c2e4ce4a | ||
|
|
3557f4e738 | ||
|
|
6595c00cff | ||
|
|
ca9a566c25 | ||
|
|
c612018ba8 | ||
|
|
ec06ef2dfb | ||
|
|
2febf45700 | ||
|
|
3e6066a1a1 | ||
|
|
b61e5c76db | ||
|
|
bdee4859f2 | ||
|
|
8386bf2ec6 | ||
|
|
a50443589f | ||
|
|
fd74bfe9c2 | ||
|
|
f82693103a | ||
|
|
cff72bcb67 | ||
|
|
e3326fca29 | ||
|
|
b5e45aae95 | ||
|
|
7fd12c5622 | ||
|
|
47442be989 | ||
|
|
3a484dc41a | ||
|
|
9a2dfd7e57 | ||
|
|
c9a168a138 | ||
|
|
3726f28eeb | ||
|
|
ead25d6a9c | ||
|
|
55f4abb6be | ||
|
|
7ccf376beb | ||
|
|
8678ac301c | ||
|
|
dde6f10b22 | ||
|
|
f9f41dac6a | ||
|
|
af6ab0ec9e | ||
|
|
485e91796b | ||
|
|
2451ad3f0a | ||
|
|
b0125d139a | ||
|
|
2e013fafc8 | ||
|
|
d0069b9684 | ||
|
|
0809379f91 | ||
|
|
0d4a95f5af | ||
|
|
ed6a6f71ce | ||
|
|
2e9129a71c | ||
|
|
7384bebf4e | ||
|
|
a042e72f28 | ||
|
|
0e197b1885 | ||
|
|
54697ae0d5 | ||
|
|
7b6eb9337e | ||
|
|
a94fb84052 | ||
|
|
528aeb1873 | ||
|
|
e7b35daf45 | ||
|
|
7d10971617 | ||
|
|
6ac12fd457 | ||
|
|
0abedba665 | ||
|
|
8bc688a491 | ||
|
|
46524b19ab | ||
|
|
a9bcd4c1c6 | ||
|
|
a355be9c0f | ||
|
|
3d83211503 | ||
|
|
cd8c8fa020 | ||
|
|
4b663baa88 | ||
|
|
4c03009357 | ||
|
|
047f547863 | ||
|
|
83c62c614b | ||
|
|
c39b3569de | ||
|
|
22402280a7 | ||
|
|
7e9872871d | ||
|
|
6c595377a3 | ||
|
|
7fea62aa46 | ||
|
|
d816f510ea | ||
|
|
56ed5a784d | ||
|
|
c2064aa318 | ||
|
|
109709ce8b | ||
|
|
b200725762 | ||
|
|
9cf34bf987 | ||
|
|
be3a91bb14 | ||
|
|
fc948d8b27 | ||
|
|
8774658f4e | ||
|
|
0bf32148af | ||
|
|
97d158b615 | ||
|
|
b772dea188 | ||
|
|
5c8f0b35ec | ||
|
|
1b382653f2 | ||
|
|
c7cee63c97 | ||
|
|
fbb9f09536 | ||
|
|
d8e5b9a5c6 | ||
|
|
ba9474fa62 | ||
|
|
b7ec7ea686 | ||
|
|
74560adb08 | ||
|
|
e6a303bdda | ||
|
|
7afcf0bb68 | ||
|
|
7aae2d0616 | ||
|
|
9255505a3b | ||
|
|
6c0dfd338d | ||
|
|
c306e988c8 | ||
|
|
86f7a8b273 | ||
|
|
2428a8efab | ||
|
|
cba12e487f | ||
|
|
e98d7931ca | ||
|
|
f8e2d2e3d0 | ||
|
|
4455bab6ac | ||
|
|
b21fb27041 | ||
|
|
5402ee0019 | ||
|
|
02654ea57b | ||
|
|
9cd4aeabaa | ||
|
|
d788511a83 | ||
|
|
378b5b8096 | ||
|
|
e26eb2f751 | ||
|
|
26264b15f7 | ||
|
|
309e3dd981 | ||
|
|
4ff5ed3b5d | ||
|
|
270a757756 | ||
|
|
79e4030ab4 | ||
|
|
fd86927e04 | ||
|
|
df3a7e5037 | ||
|
|
fefc6f14c9 | ||
|
|
923fbef0e4 | ||
|
|
601bd5ba7a | ||
|
|
5798f20c41 | ||
|
|
f2e26f91a8 | ||
|
|
e2cdc45be6 | ||
|
|
96493bc75e | ||
|
|
d78ad4a78e | ||
|
|
0c54b0dd6e | ||
|
|
65310e52de | ||
|
|
285d3cd9b8 | ||
|
|
10486abb64 | ||
|
|
8c1a9fc988 | ||
|
|
b067b0eed5 | ||
|
|
7bdd0b470b | ||
|
|
b7a51f12bf | ||
|
|
e002ac5474 | ||
|
|
211d339ce0 | ||
|
|
509fb14473 | ||
|
|
ef838a1b83 | ||
|
|
f84b5b633d | ||
|
|
8ec8c1170d | ||
|
|
920b98e4d1 | ||
|
|
c80f52d4bc | ||
|
|
0b6aa42daf | ||
|
|
04dc6427ef | ||
|
|
77e366b484 | ||
|
|
add7cfa0f2 | ||
|
|
14d4fa142c | ||
|
|
7fe1617f25 | ||
|
|
cbe4cb521c | ||
|
|
d6dffe89d6 | ||
|
|
0107159b84 | ||
|
|
c106b99d5d | ||
|
|
2f695ca68f | ||
|
|
461592f6e3 | ||
|
|
ccf3b5c75d | ||
|
|
a40702cda4 | ||
|
|
7c394525c1 | ||
|
|
12ed051fea | ||
|
|
014913c635 | ||
|
|
369e45c282 | ||
|
|
2892caec0c | ||
|
|
eb609e918a | ||
|
|
9217e58845 | ||
|
|
a5ad456b52 | ||
|
|
26b633618a | ||
|
|
e3054d6ee1 | ||
|
|
0b6294f4ec | ||
|
|
7b130dc0f3 | ||
|
|
d93c128c25 | ||
|
|
e1dd74775e | ||
|
|
e61e706607 | ||
|
|
a57d46deaa | ||
|
|
60efdc0071 | ||
|
|
4dc1155a9e | ||
|
|
5cabe6fb42 | ||
|
|
42239c305f | ||
|
|
fd3c763349 | ||
|
|
daa9bbf2c9 | ||
|
|
c7dad00908 | ||
|
|
24317717e8 | ||
|
|
dd3eac6db6 | ||
|
|
1606f8517f | ||
|
|
3a98401ada | ||
|
|
4764251241 | ||
|
|
3de04b7b9d | ||
|
|
04908495e9 | ||
|
|
357243c717 | ||
|
|
23f1d57064 | ||
|
|
3c835c848e | ||
|
|
0f1788f122 | ||
|
|
3d72e700a4 | ||
|
|
a5cf41e65f | ||
|
|
7029bc41d7 | ||
|
|
b87d40c844 | ||
|
|
9e154376d3 | ||
|
|
4a9fdbce57 | ||
|
|
775e335292 | ||
|
|
e553bf4deb | ||
|
|
db0e09ec6e | ||
|
|
ba50760f92 | ||
|
|
a56a2500d4 | ||
|
|
4950bb9e0a | ||
|
|
7fc409a8d1 | ||
|
|
f52872718c | ||
|
|
abca6e3bf7 | ||
|
|
524e6d4f81 | ||
|
|
db93a669ab | ||
|
|
423f5b0502 | ||
|
|
2bc45c25fe | ||
|
|
4fedef90c7 | ||
|
|
69ca72ff86 | ||
|
|
d341463f67 | ||
|
|
19d1ecb36a | ||
|
|
ffe4047f97 | ||
|
|
15a3c29e7a | ||
|
|
6a32f85e60 | ||
|
|
542a4725ad | ||
|
|
5d8bd6f474 | ||
|
|
d8b98cc39d | ||
|
|
1d558c32d5 | ||
|
|
72c560e5a2 | ||
|
|
268246e1ef | ||
|
|
8957f8a55f | ||
|
|
cdf7a1dfe8 | ||
|
|
398d82f359 | ||
|
|
11fb1b655f | ||
|
|
5032a2538a | ||
|
|
cabc0e5344 | ||
|
|
fe1414dd6b | ||
|
|
42ff593004 | ||
|
|
5140af4e6f | ||
|
|
0f5b4c00a9 | ||
|
|
feb69f4987 | ||
|
|
84eb9a58ca | ||
|
|
fbd96f473a | ||
|
|
69228157dc | ||
|
|
6808f32b8d | ||
|
|
f29c62ec33 | ||
|
|
13cee09a1c | ||
|
|
d77981230b | ||
|
|
eb5c6bd30a | ||
|
|
e2b92c15f0 | ||
|
|
6f5e6f2fe2 | ||
|
|
fc8c932874 | ||
|
|
784523e635 | ||
|
|
56db418949 | ||
|
|
31d6e81a59 | ||
|
|
6356628c80 | ||
|
|
14614f4803 | ||
|
|
37d3489b07 | ||
|
|
f81225cc0d | ||
|
|
eb47f43940 | ||
|
|
7618a5e2c9 | ||
|
|
ab3ff842b2 | ||
|
|
82f0f92c15 | ||
|
|
545d9434d8 | ||
|
|
bbc43d5113 | ||
|
|
a6709acf65 | ||
|
|
509a027742 | ||
|
|
7244023233 | ||
|
|
c5f987d8b2 | ||
|
|
793696d4c0 | ||
|
|
305ca05f04 | ||
|
|
f4d7d1920a | ||
|
|
458aad0161 | ||
|
|
9e3132461f | ||
|
|
8301291215 | ||
|
|
daed14e228 | ||
|
|
7db82c5ba5 | ||
|
|
91d03c22c2 | ||
|
|
bc188f0d2b | ||
|
|
3981225824 | ||
|
|
1886d9d72f | ||
|
|
2a7dec8892 | ||
|
|
35d4a5b297 | ||
|
|
26345bfa50 | ||
|
|
8c9b8676dd | ||
|
|
5593c7a75f | ||
|
|
5b70c14ffe | ||
|
|
5e4d611044 | ||
|
|
7ab9d57156 | ||
|
|
9ea6f51126 | ||
|
|
649fe4f4f0 | ||
|
|
53df86c585 | ||
|
|
87c708e3c1 | ||
|
|
ba927b0490 | ||
|
|
2a09725c98 | ||
|
|
9fa0d17933 | ||
|
|
8d9f8a996a | ||
|
|
8814b60d0b | ||
|
|
541c07c53a | ||
|
|
6cbed80815 | ||
|
|
a3c1fdc0f4 | ||
|
|
ddf789b01c | ||
|
|
ab86cc0bf3 | ||
|
|
dc8fab5820 | ||
|
|
813c4f845a | ||
|
|
509e6bc2d8 | ||
|
|
6ed42e9521 | ||
|
|
fb01ba60ec | ||
|
|
ea4210ded5 | ||
|
|
75e5cf54d6 | ||
|
|
7cebc85a95 | ||
|
|
c00faafa6d | ||
|
|
c9b14f0742 | ||
|
|
9624137c0d | ||
|
|
3eb433368f | ||
|
|
58691f4f5f | ||
|
|
5e9295abd5 | ||
|
|
6ed0ae55b2 | ||
|
|
84448d0aa1 | ||
|
|
31ced24966 | ||
|
|
24681840af | ||
|
|
5d28aa4f2e | ||
|
|
7dabdf3085 | ||
|
|
d7cbd5570c | ||
|
|
80011d4aea | ||
|
|
c6fe440a01 | ||
|
|
ba8d6dc3fa | ||
|
|
378f4f8304 | ||
|
|
aa414485a5 | ||
|
|
3a35a5d66a | ||
|
|
43b0a72b09 | ||
|
|
521ac2d13f | ||
|
|
30520c50c2 | ||
|
|
819f07eba3 | ||
|
|
d8d6aa7136 | ||
|
|
0bdcfea276 | ||
|
|
718f76313f | ||
|
|
ed75a365d8 | ||
|
|
f5ff13ffe4 | ||
|
|
44c69b1716 | ||
|
|
559ca7ffb8 | ||
|
|
524f34e8c1 | ||
|
|
d749be11f0 | ||
|
|
13dc9ff3cb | ||
|
|
24b4fce326 | ||
|
|
f506210bf8 | ||
|
|
cb0cbd06ce | ||
|
|
3356239089 | ||
|
|
9a5b2edc08 | ||
|
|
2d41b4bd83 | ||
|
|
58f7758ee1 | ||
|
|
9dbb642883 | ||
|
|
c5222e4d1d | ||
|
|
a5a695a17c | ||
|
|
7d1dc86cfb | ||
|
|
03224f914a | ||
|
|
1cf1f07232 | ||
|
|
838d36a74e | ||
|
|
6c9300d01b | ||
|
|
3059f7efef | ||
|
|
fbce7d9afa | ||
|
|
1c9f3ed9fa | ||
|
|
8c47d2d400 | ||
|
|
8a18f25b5d | ||
|
|
e7ae0058ed | ||
|
|
98bf3a3e33 | ||
|
|
379fcd4494 | ||
|
|
8589777bac | ||
|
|
c10a21ecbd | ||
|
|
eddc616b14 | ||
|
|
70fc1a9579 | ||
|
|
f27ec4d6a4 | ||
|
|
1e6e956e2d | ||
|
|
0a2a3530d1 | ||
|
|
9e4c820e26 | ||
|
|
05290bfe9e | ||
|
|
607f4ce353 | ||
|
|
4b819ead1d | ||
|
|
d07d9d3f31 | ||
|
|
d08776bf19 | ||
|
|
7a61fd44fd | ||
|
|
151da1487c | ||
|
|
e3f6dfad5b | ||
|
|
32080ce918 | ||
|
|
aedb2138c8 | ||
|
|
90e6938f1c | ||
|
|
eadff35250 | ||
|
|
487450ec64 | ||
|
|
f2f730bab5 | ||
|
|
ceecee37a7 | ||
|
|
394377bea9 | ||
|
|
2b47818deb | ||
|
|
9f72465052 | ||
|
|
ac33b4a322 | ||
|
|
911c844e54 | ||
|
|
12b9655565 | ||
|
|
2935717a06 | ||
|
|
2c8d2cfa21 | ||
|
|
d67c0eb757 | ||
|
|
7bcbcd5a7f | ||
|
|
1c06e7f36e | ||
|
|
407297adc0 | ||
|
|
b082b31562 | ||
|
|
fcbeb0f82f | ||
|
|
7094f8f265 | ||
|
|
48aaa13420 | ||
|
|
d2434cf91f | ||
|
|
8dbe0d2793 | ||
|
|
7b308cf984 | ||
|
|
520af504aa | ||
|
|
4bee130599 | ||
|
|
19a9ed3e19 | ||
|
|
0dac1b93ae | ||
|
|
e824863de1 | ||
|
|
9ff8becd62 | ||
|
|
fcaa1bcfa8 | ||
|
|
39f51bcc4f | ||
|
|
1b904ee2c9 | ||
|
|
e2baafc426 | ||
|
|
6dabe73002 | ||
|
|
d9b2b99c86 | ||
|
|
bdf23a7c4e | ||
|
|
c1723e0806 | ||
|
|
93590bd482 | ||
|
|
fbaa9d9e58 | ||
|
|
ba78a8558c | ||
|
|
513be04352 | ||
|
|
15317aa11b | ||
|
|
93e107f333 | ||
|
|
655b334b0a | ||
|
|
95cc1c58a6 | ||
|
|
0511d3e599 | ||
|
|
92a41b5c46 | ||
|
|
be16837c8c | ||
|
|
bf5f26cb12 | ||
|
|
ccbcef7eff | ||
|
|
d02a97b7f0 | ||
|
|
e0ca413c54 | ||
|
|
a272fc1c05 | ||
|
|
2c3f74d4ea | ||
|
|
c65b2fc0fd | ||
|
|
fd725adf27 | ||
|
|
ceba6da91f | ||
|
|
064e7d42ee | ||
|
|
cfdbf0c614 | ||
|
|
436777fe34 | ||
|
|
f08aa46222 | ||
|
|
e099ac514a | ||
|
|
873755f619 | ||
|
|
2094b7cf83 | ||
|
|
6ffdd81bb1 | ||
|
|
a69cb17602 | ||
|
|
7c82a9fd6e | ||
|
|
10d9a6c83d | ||
|
|
e474e9b090 | ||
|
|
837f9a6c74 | ||
|
|
c357aed7bb | ||
|
|
59ffddfd8d | ||
|
|
fb3accaa36 | ||
|
|
b3e79544ad | ||
|
|
2ee0f3bf97 | ||
|
|
a56b5edc4e | ||
|
|
f2d71b6551 | ||
|
|
ef560ddc03 | ||
|
|
2e30ffe1bc | ||
|
|
325b5e9297 | ||
|
|
ae9e04b8d4 | ||
|
|
ab0010122b | ||
|
|
08d736ecdc | ||
|
|
9c362c8ffd | ||
|
|
62c8778560 | ||
|
|
2fe0665e12 | ||
|
|
14a957f517 | ||
|
|
087010d0a1 | ||
|
|
f92657310a | ||
|
|
19e91400b5 | ||
|
|
7885fb7b4f | ||
|
|
331bec11ee | ||
|
|
f6ed650ef6 | ||
|
|
df9ce6add9 | ||
|
|
28ee47d501 | ||
|
|
df2da479ad | ||
|
|
46eb9d8baf | ||
|
|
b6be871a21 | ||
|
|
ce2367fc0a | ||
|
|
f219cd6c8b | ||
|
|
7789e8cfea | ||
|
|
e697624064 | ||
|
|
92ffdeb5bf | ||
|
|
ddefe7e542 | ||
|
|
fb4019c041 | ||
|
|
46026e047e | ||
|
|
51a9effcaa | ||
|
|
fc3b953a8e | ||
|
|
a2ded3cecc | ||
|
|
ffb1390b51 | ||
|
|
3b92591c05 | ||
|
|
f173ddcf00 | ||
|
|
f78306f624 | ||
|
|
9852c9301e | ||
|
|
2e4f3de604 | ||
|
|
300dd97353 | ||
|
|
8e29ade83a | ||
|
|
971c770f6a | ||
|
|
0dfad5a757 | ||
|
|
a0495f8aae | ||
|
|
7d31a06ae4 | ||
|
|
c5319ad7b1 | ||
|
|
f8bdb28ea6 | ||
|
|
2f8806d7c2 | ||
|
|
7d0a181c12 | ||
|
|
27cb7e53ed | ||
|
|
ee5154b510 | ||
|
|
080837201a | ||
|
|
656e6c0d94 | ||
|
|
b018f61bab | ||
|
|
b03b306848 | ||
|
|
19bf5c2b3e | ||
|
|
c22a716cf9 | ||
|
|
220c65da92 | ||
|
|
675f4b86f8 | ||
|
|
d31b3522b2 | ||
|
|
4ed2bab1d7 | ||
|
|
a572872806 | ||
|
|
e3ca23db0b | ||
|
|
67eb16a53f | ||
|
|
d7421cd1a3 | ||
|
|
c2e68ced66 | ||
|
|
10cf0db050 | ||
|
|
08f1a1dcfe | ||
|
|
11bf352295 | ||
|
|
5fd560b30a | ||
|
|
e6d01ca246 | ||
|
|
1610f05b83 | ||
|
|
8202b75cda | ||
|
|
4904bae5a6 | ||
|
|
04c06db02c | ||
|
|
0ddf2fa00d | ||
|
|
8776143ec2 | ||
|
|
080c8e7af3 | ||
|
|
a96f94b676 | ||
|
|
64998e762b | ||
|
|
b508da5ccc | ||
|
|
84a492655a | ||
|
|
9b9cfe4138 | ||
|
|
f362a7086a | ||
|
|
9e9ec57a5f | ||
|
|
da6a153985 | ||
|
|
b4bde3f510 | ||
|
|
8a5cebd635 | ||
|
|
ebdfc694c2 | ||
|
|
ddf2733d3c | ||
|
|
cf65bfbcd0 | ||
|
|
03cce830bc | ||
|
|
913cf4c3e9 | ||
|
|
2ccce0beaf | ||
|
|
52239f71cd | ||
|
|
d9265af2e8 | ||
|
|
5877f6a627 | ||
|
|
7f29340797 | ||
|
|
e3e982c051 | ||
|
|
17f09ff3de | ||
|
|
7a73416c03 | ||
|
|
0f799d44ad | ||
|
|
24ebd44f87 | ||
|
|
572b10e655 | ||
|
|
60361c176b | ||
|
|
b232a2c58f | ||
|
|
2a44ac56fb | ||
|
|
d0340a3257 | ||
|
|
e708a53ddb | ||
|
|
23cdd70198 | ||
|
|
ba4bc30a78 | ||
|
|
e516ea146d | ||
|
|
61af638fe4 | ||
|
|
7a0af4142f | ||
|
|
1aa8d596a3 | ||
|
|
a457810623 | ||
|
|
4bccb7ab84 | ||
|
|
fcd5b2ce0a | ||
|
|
57a1184a16 | ||
|
|
ef176efed8 | ||
|
|
9943e0317d | ||
|
|
ae11c1c904 | ||
|
|
f937e069ca | ||
|
|
c9326da47b | ||
|
|
4698c05f9c | ||
|
|
15fd2cd5a0 | ||
|
|
193eb11095 | ||
|
|
59cc0096cd | ||
|
|
6bd7390eb8 | ||
|
|
eabeb18f34 | ||
|
|
4b6f539ba3 | ||
|
|
ea55fc5f41 | ||
|
|
94787229a4 | ||
|
|
113d0b5141 | ||
|
|
163285b0a9 | ||
|
|
9f1cf5377a | ||
|
|
c37636215b | ||
|
|
0cb162022c | ||
|
|
5ff2ca311e | ||
|
|
44b8d452a8 | ||
|
|
82ccb1ab23 | ||
|
|
feb8b94e30 | ||
|
|
5adc73e277 | ||
|
|
c4ac983fc7 | ||
|
|
5ba7ba3aac | ||
|
|
e879479b3d | ||
|
|
7b49463297 | ||
|
|
1f77b19ced | ||
|
|
9853ebf02f | ||
|
|
6e13aa0bc9 | ||
|
|
66600c3373 | ||
|
|
19401c4bc6 | ||
|
|
bfc8660c5e | ||
|
|
6a83dc56ba | ||
|
|
82df9d8cad | ||
|
|
dd30fef078 | ||
|
|
79feb5a1cc | ||
|
|
0665524b11 | ||
|
|
d45367e840 | ||
|
|
1b7f0e0f1e | ||
|
|
18cb3360b5 | ||
|
|
cb0d988efc | ||
|
|
fc0c556967 | ||
|
|
00970164db | ||
|
|
7c3d76e9fe | ||
|
|
190c1f9620 | ||
|
|
f532325147 | ||
|
|
278dcb8808 | ||
|
|
6fec598b96 | ||
|
|
309c25e10b | ||
|
|
6df728ea3e | ||
|
|
dcf4ef6d74 | ||
|
|
4de1369a5a | ||
|
|
2ccfdb1280 | ||
|
|
9fbf800639 | ||
|
|
577daf64df | ||
|
|
0b1943e9b3 | ||
|
|
30bd2dd876 | ||
|
|
cecd4d4179 | ||
|
|
364baab18d | ||
|
|
bb3d4d2171 | ||
|
|
2355faf899 | ||
|
|
858e0b24ff | ||
|
|
f7d0ed3988 | ||
|
|
40096ecdfb | ||
|
|
43061b80b8 | ||
|
|
aa3d657d42 | ||
|
|
b0ef385769 | ||
|
|
dcf24e6292 | ||
|
|
2fdd77737c | ||
|
|
31c98ef1ba | ||
|
|
ceeec2c13a | ||
|
|
cc82e0cff8 | ||
|
|
29fc6c59e9 | ||
|
|
aa931fab7b | ||
|
|
8e2585a5c0 | ||
|
|
c115047f74 | ||
|
|
0ac58cb137 | ||
|
|
492e0977c3 | ||
|
|
e8a09e81f3 | ||
|
|
4fcc2c6208 | ||
|
|
e3523dc5fe | ||
|
|
acf6c31a36 | ||
|
|
339b2e696c | ||
|
|
6417fd49d6 | ||
|
|
374fd4db65 | ||
|
|
0b70dd9e38 | ||
|
|
55b19a7922 | ||
|
|
beb1b2655e | ||
|
|
6a81e48c3a | ||
|
|
f9841dd3df | ||
|
|
b563c773c1 | ||
|
|
dcda649d9d | ||
|
|
d6cfd23ae5 | ||
|
|
b69f62c9a4 | ||
|
|
582ee7d623 | ||
|
|
9e69f5dcfa | ||
|
|
7c4202186d | ||
|
|
7c56e4c09d | ||
|
|
b10b466c61 | ||
|
|
80d1986c61 | ||
|
|
7f9e413604 | ||
|
|
4dfbc3830f | ||
|
|
46cdefa9ee | ||
|
|
f857ea6095 | ||
|
|
994920f99f | ||
|
|
409ed9ad67 | ||
|
|
130d4096d5 | ||
|
|
d5659529d7 | ||
|
|
f6e06ab16e | ||
|
|
4245eaf1b2 | ||
|
|
c41bd702e2 | ||
|
|
3911145f87 | ||
|
|
350f5c4266 | ||
|
|
ed4056bc8b | ||
|
|
0d96fa21b7 | ||
|
|
0ee8d33d36 | ||
|
|
64cedec12b | ||
|
|
a16eaca42e | ||
|
|
366b3b1b75 | ||
|
|
33963c085a | ||
|
|
f3b18ef518 | ||
|
|
bca1b45fd4 | ||
|
|
3448d5ef03 | ||
|
|
0f5a6194ff | ||
|
|
9266faec59 | ||
|
|
94347c6dde | ||
|
|
7a07701be0 | ||
|
|
42519a4f75 | ||
|
|
c898c16430 | ||
|
|
318863bd18 | ||
|
|
ce94760d02 | ||
|
|
99759b5282 | ||
|
|
d9d8562ed9 | ||
|
|
dee55ea874 | ||
|
|
da413cc9e6 | ||
|
|
c72e93a13d | ||
|
|
21f910a3ca | ||
|
|
06e78e19d0 | ||
|
|
2c51edb4c2 | ||
|
|
da2c893721 | ||
|
|
e7ce693e50 | ||
|
|
29e8fe7904 | ||
|
|
baf3a6077e | ||
|
|
e030e7a32d | ||
|
|
125566ed75 | ||
|
|
c51cb67519 | ||
|
|
57f086b530 | ||
|
|
d1e17785b8 | ||
|
|
774328a1d8 | ||
|
|
175a0f48fa | ||
|
|
60b91ff032 | ||
|
|
2b003bc5b0 | ||
|
|
029a2fcab1 | ||
|
|
aa47f7fe4a | ||
|
|
a828113d9b | ||
|
|
ab4d509eda | ||
|
|
1296f32fa8 | ||
|
|
5aa1ec9876 | ||
|
|
f2cf1ab226 | ||
|
|
2d34c2a40b | ||
|
|
a7181e865c | ||
|
|
0a71b788b3 | ||
|
|
1ed3d84043 | ||
|
|
f472b75d0d | ||
|
|
072a9d1ccd | ||
|
|
8e26d1be68 | ||
|
|
c5871e9025 | ||
|
|
f26911b358 | ||
|
|
3a595b80b5 | ||
|
|
45760ddd41 | ||
|
|
447d068bf1 | ||
|
|
7e1642a4a3 | ||
|
|
7781267e78 | ||
|
|
f824fdcfed | ||
|
|
79dd441967 | ||
|
|
9dcf9c0785 | ||
|
|
83569462c6 | ||
|
|
947e169c3a | ||
|
|
e6b36c52d1 | ||
|
|
7fed3b84fa | ||
|
|
cbc941f193 | ||
|
|
a7ef0fb85f | ||
|
|
d10c36eaf5 | ||
|
|
e4c3a66753 | ||
|
|
f58f2cdc90 | ||
|
|
8f2676e91e | ||
|
|
89d397898e | ||
|
|
2dc32be9ee | ||
|
|
769220bd82 | ||
|
|
c75aa5dd52 | ||
|
|
88f1cfd8c4 | ||
|
|
108279c1aa | ||
|
|
8150490aac | ||
|
|
9497aa6362 | ||
|
|
3769da48a1 | ||
|
|
3c21fcd53a | ||
|
|
102ef2795d | ||
|
|
0befa253c2 | ||
|
|
87189cee58 | ||
|
|
3284f13fc6 | ||
|
|
70d219b120 | ||
|
|
a5ed3a08ea | ||
|
|
3665e7e999 | ||
|
|
9b583b23ae | ||
|
|
d602415e98 | ||
|
|
2c49a423e2 | ||
|
|
8a266e70c8 | ||
|
|
0df3bcd65d | ||
|
|
f5ecf5648e | ||
|
|
821f82fc3f | ||
|
|
92305fe628 | ||
|
|
17fe57b8a5 | ||
|
|
47433a51d3 | ||
|
|
e090827ced | ||
|
|
856e6a8b74 | ||
|
|
a813b4d40e | ||
|
|
98afc27442 | ||
|
|
9d4f28a395 | ||
|
|
50266d9a56 | ||
|
|
602a532cf2 | ||
|
|
b23d610c94 | ||
|
|
119c43d527 | ||
|
|
124feeb790 | ||
|
|
2c0e7e84da | ||
|
|
0d1e105343 | ||
|
|
f040c89ab3 | ||
|
|
5e9d8298ef | ||
|
|
7766ca2793 | ||
|
|
fb2f22f120 | ||
|
|
146fed3504 | ||
|
|
0b7a6e3375 | ||
|
|
f1fbdeaca0 | ||
|
|
ee7e37f6bc | ||
|
|
861f665ab3 | ||
|
|
456de23ad4 | ||
|
|
6d3578c17e | ||
|
|
57da7267e2 | ||
|
|
578b764406 | ||
|
|
f65a38a085 | ||
|
|
d187757bbc | ||
|
|
df83b24cf4 | ||
|
|
8e31ce0f5b | ||
|
|
4f69eb7963 | ||
|
|
4b9009216b | ||
|
|
895076c837 | ||
|
|
6e398ee64a | ||
|
|
c2177272b5 | ||
|
|
4877de5839 | ||
|
|
3cb9d10126 | ||
|
|
23c53cd9fb | ||
|
|
3471743a63 | ||
|
|
1b0b1809ef | ||
|
|
0833cffead | ||
|
|
a0a8ae88b7 | ||
|
|
e415cbeca4 | ||
|
|
4c15c93381 | ||
|
|
bd19822112 | ||
|
|
b87beb4a6e | ||
|
|
62a58facb2 | ||
|
|
5fa8178df7 | ||
|
|
d6043e7d1f | ||
|
|
8e432dfbb4 | ||
|
|
324ab3744b | ||
|
|
33ee4d0418 | ||
|
|
cbb104ccba | ||
|
|
0bda5461ae | ||
|
|
4096a03de1 | ||
|
|
fbee5f83df | ||
|
|
84a1b19850 | ||
|
|
1df5bd6d03 | ||
|
|
8bd4221a85 | ||
|
|
4d97ccdfb3 | ||
|
|
ca5e57ddbf | ||
|
|
ed6511799b | ||
|
|
9b1223ec15 | ||
|
|
97210ee67a | ||
|
|
308d1003ad | ||
|
|
ce9e193958 | ||
|
|
2a83afa8c1 | ||
|
|
8891fa328b | ||
|
|
a23c06c456 | ||
|
|
34e87121e1 | ||
|
|
6e5c20f442 | ||
|
|
8ac3bec451 | ||
|
|
5128d0f405 | ||
|
|
6ae2121391 | ||
|
|
749e0c5a47 | ||
|
|
7b55ef85e1 | ||
|
|
23ec35d1f9 | ||
|
|
ff089c2b9f | ||
|
|
1ab088718a | ||
|
|
0cb2e5857a | ||
|
|
7c5ae5d7ee | ||
|
|
6e2665d98d | ||
|
|
b3b4692237 | ||
|
|
55ff1d4999 | ||
|
|
511c839237 | ||
|
|
384cd6e5d9 | ||
|
|
6fd9dcc72e | ||
|
|
12ca06fe91 | ||
|
|
652f6c651b | ||
|
|
7dd808a772 | ||
|
|
945e55f8a7 | ||
|
|
866a30abe5 | ||
|
|
f0e33e1e4e | ||
|
|
8723ca38b4 | ||
|
|
efffca0026 | ||
|
|
6c5eb3035f | ||
|
|
0bf385f2ca | ||
|
|
9d5f51f513 | ||
|
|
a1448131d8 | ||
|
|
664cacc7ac | ||
|
|
1aec37dd05 | ||
|
|
3ff2c0840e | ||
|
|
5ca3c3ac52 | ||
|
|
714ee50965 | ||
|
|
ae1df3c5aa | ||
|
|
28afb39550 | ||
|
|
3466a8a024 | ||
|
|
90db3321f5 | ||
|
|
bf4e5ed7c2 | ||
|
|
d3b2c20c26 | ||
|
|
c3afabb4b1 | ||
|
|
18cc9790d1 | ||
|
|
d5ed3aa674 | ||
|
|
cfb9c9acfe | ||
|
|
67feef0b1d | ||
|
|
e0192ab5aa | ||
|
|
eae7c11c55 | ||
|
|
b8251e1ade | ||
|
|
2ff2b9f348 | ||
|
|
164e3f3343 | ||
|
|
4901a1bd87 | ||
|
|
cd194cca65 | ||
|
|
1748efbc18 | ||
|
|
4d60b40403 | ||
|
|
6e5a41879f | ||
|
|
c5f9eb54da | ||
|
|
aebbfeb061 | ||
|
|
a0a5a6b578 | ||
|
|
6bdafb85d7 | ||
|
|
0dd7debf5d | ||
|
|
962b9ee7af | ||
|
|
15bcc5df88 | ||
|
|
8495d834c8 | ||
|
|
7282399709 | ||
|
|
780bdd6e7e | ||
|
|
ad08e7c67f | ||
|
|
ff089964ca | ||
|
|
990191529a | ||
|
|
ce1bec12b4 | ||
|
|
703ea9af53 | ||
|
|
df0227ae1e | ||
|
|
486d12b6ac | ||
|
|
9c18375ab4 | ||
|
|
e1708aed68 | ||
|
|
a42d778b84 | ||
|
|
7692b59c7c | ||
|
|
82de9b36b3 | ||
|
|
49b3c8f65f | ||
|
|
90ad32d936 | ||
|
|
2509e91f1a | ||
|
|
80dc2219e4 | ||
|
|
931cb0fa7d | ||
|
|
4383f2ea90 | ||
|
|
add79dc242 | ||
|
|
b6e142f04c | ||
|
|
105f6c3041 | ||
|
|
4babbb65c1 | ||
|
|
878159f7ed | ||
|
|
c320386019 | ||
|
|
1e7a0159f0 | ||
|
|
bbf4f1d1d3 | ||
|
|
8cd1c69c76 | ||
|
|
2372a878ac | ||
|
|
9bec644997 | ||
|
|
650d38dff8 | ||
|
|
097277e397 | ||
|
|
82a4d5eedf | ||
|
|
2a00248812 | ||
|
|
9841c773cb | ||
|
|
b21a78ad14 | ||
|
|
7329fa597d | ||
|
|
f1d8f0ecb4 | ||
|
|
1d127f2364 | ||
|
|
b244405cc3 | ||
|
|
38adfe0ca6 | ||
|
|
6f0d798847 | ||
|
|
6e58bfd2b0 | ||
|
|
bc2e8d8ac4 | ||
|
|
7be654d47f | ||
|
|
bfe46e3604 | ||
|
|
008b597fc5 | ||
|
|
c4b4f2e3b1 | ||
|
|
76ee97301b | ||
|
|
289dc09eae | ||
|
|
f23f84f0f3 | ||
|
|
62259f3295 | ||
|
|
854a430a12 | ||
|
|
d27cdb5637 | ||
|
|
7d12d9ee90 | ||
|
|
d70a4ff347 | ||
|
|
49541558d1 | ||
|
|
7b00a1227c | ||
|
|
7800603c81 | ||
|
|
fca00c8116 | ||
|
|
00fa549e44 | ||
|
|
36181b6b87 | ||
|
|
88a95162e9 | ||
|
|
9c1d59a2c8 | ||
|
|
fdd894956a | ||
|
|
eb6dc0859d | ||
|
|
b99026bba2 | ||
|
|
6fa50eb8d5 | ||
|
|
e5046f15a9 | ||
|
|
09f1c066a0 | ||
|
|
ed2f0b34c9 | ||
|
|
c7f75861de | ||
|
|
ccd04dbc9d | ||
|
|
e71f8d2c10 | ||
|
|
d9b4c60239 | ||
|
|
103c1fca21 | ||
|
|
49559bf5fb | ||
|
|
26dceabf83 | ||
|
|
abe506182e | ||
|
|
582a0e2a38 | ||
|
|
2784ccf379 | ||
|
|
0ad1d578fe | ||
|
|
31fd1c9c68 | ||
|
|
9c961297a2 | ||
|
|
b920053349 | ||
|
|
0a5c764e4a | ||
|
|
b9a71c83ff | ||
|
|
3255f207d0 | ||
|
|
e3b4ca8862 | ||
|
|
6810793015 | ||
|
|
1feb3c2095 | ||
|
|
f2da6033d0 | ||
|
|
28bc212132 | ||
|
|
1a7c62eec6 | ||
|
|
de67dbacba | ||
|
|
57223a0f9a | ||
|
|
2ad0754b90 | ||
|
|
3ecb3af57b | ||
|
|
ec4f15f549 | ||
|
|
2458ea7b92 | ||
|
|
c5e6bedf11 | ||
|
|
8528e5a666 | ||
|
|
6ed232b3d9 | ||
|
|
f8aae8cbd1 | ||
|
|
00c2517045 | ||
|
|
99b043a929 | ||
|
|
5900e27e39 | ||
|
|
1b79d34907 | ||
|
|
fc52e29c92 | ||
|
|
df23b3c0fe | ||
|
|
5e25716c98 | ||
|
|
f70a10bc56 | ||
|
|
0ccb045f4e | ||
|
|
fa18d0d852 | ||
|
|
687c4342fb | ||
|
|
9459af46b8 | ||
|
|
fc5832747a | ||
|
|
01205d244b | ||
|
|
31a555255a | ||
|
|
fbb60c9493 | ||
|
|
9f953ef51c | ||
|
|
4871f1547c | ||
|
|
d6413529f4 | ||
|
|
724cf5a0da | ||
|
|
b6847907ca | ||
|
|
fb54a1aed7 | ||
|
|
9815318daf | ||
|
|
bc13b98111 | ||
|
|
238b70c121 | ||
|
|
dcd23bc0cb | ||
|
|
4694ea85fa | ||
|
|
bdb17743d7 | ||
|
|
8b76ff2461 | ||
|
|
ea5421002b | ||
|
|
76ac55917d | ||
|
|
5cfb2376c4 | ||
|
|
6b9d3fd80e | ||
|
|
a09752b62e | ||
|
|
3134e1d9f9 | ||
|
|
e94ecf2a0b | ||
|
|
1bd4d841a1 | ||
|
|
8e349f47a5 | ||
|
|
119c0a4231 | ||
|
|
f009f533e0 | ||
|
|
3ab0c1e509 | ||
|
|
e3d264e239 | ||
|
|
9bd60f8e8e | ||
|
|
bcec1d9637 | ||
|
|
1b431b1d20 | ||
|
|
23c2dcdfd4 | ||
|
|
dd415bf802 | ||
|
|
7bc0e9db7c | ||
|
|
ca10bb01db | ||
|
|
c0f14b7c33 | ||
|
|
b096f328fc | ||
|
|
88dbd43884 | ||
|
|
ade5e4d4b8 | ||
|
|
cb76b53a1b | ||
|
|
b9423f70d4 | ||
|
|
d61e18e6f3 | ||
|
|
ca4a725a79 | ||
|
|
17a18d5fea | ||
|
|
73be238ac4 | ||
|
|
796a034fec | ||
|
|
952b320975 | ||
|
|
be0f06ff0f | ||
|
|
6294ef2db2 | ||
|
|
78b5d505bd | ||
|
|
22afdffa15 | ||
|
|
d1056eddeb | ||
|
|
8655f4d85a | ||
|
|
f9deb54352 | ||
|
|
eae3c1b33a | ||
|
|
5c5f8aa741 | ||
|
|
ba68ac2e32 | ||
|
|
239fef281e | ||
|
|
c0880b647f | ||
|
|
039dc6a76b | ||
|
|
042a7625ad | ||
|
|
41827c478d | ||
|
|
9a73180c3c | ||
|
|
333ee85fdb | ||
|
|
fa8ca45b6a | ||
|
|
c2bae1aeb7 | ||
|
|
ada7a45fe6 | ||
|
|
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 | ||
|
|
13ea97bd98 | ||
|
|
009a17a9ca | ||
|
|
ca38d9adb1 | ||
|
|
4e5a86e3db | ||
|
|
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 | ||
|
|
a736fe7989 | ||
|
|
1970b7f249 | ||
|
|
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 | ||
|
|
9a1d746170 | ||
|
|
ef87ce3507 | ||
|
|
2b9053eac4 | ||
|
|
116ef719be | ||
|
|
504406eb22 |
@@ -18,7 +18,7 @@
|
||||
],
|
||||
"brace-style": "off",
|
||||
"comma-spacing": "off",
|
||||
"space-infix-ops": "error",
|
||||
"space-infix-ops": "off",
|
||||
"comma-dangle": "off",
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
@@ -55,7 +55,8 @@
|
||||
"block-spacing": "error",
|
||||
"keyword-spacing": "off",
|
||||
"space-before-blocks": "error",
|
||||
"semi-spacing": "error"
|
||||
"semi-spacing": "error",
|
||||
"no-constant-binary-expression": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
@@ -107,6 +108,7 @@
|
||||
"1tbs", { "allowSingleLine": true }
|
||||
],
|
||||
"@typescript-eslint/comma-spacing": "error",
|
||||
"@typescript-eslint/space-infix-ops": "error",
|
||||
"@typescript-eslint/space-before-function-paren": ["error", {
|
||||
"anonymous": "always",
|
||||
"named": "never",
|
||||
|
||||
10
.github/pull_request_template.md
vendored
Normal file
10
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<!-- Thank you for contributing to Mol* -->
|
||||
|
||||
# Description
|
||||
|
||||
|
||||
## Actions
|
||||
|
||||
- [ ] Added description of changes to the `[Unreleased]` section of `CHANGELOG.md`
|
||||
- [ ] Updated headers of modified files
|
||||
- [ ] Added my name to `package.json`'s `contributors`
|
||||
6
.github/workflows/node.yml
vendored
6
.github/workflows/node.yml
vendored
@@ -1,3 +1,5 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
@@ -9,12 +11,12 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
node-version: 18
|
||||
- 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
|
||||
run: npm install --no-save "gl@^6.0.2" && xvfb-run --auto-servernum npm run jest
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
18
.travis.yml
18
.travis.yml
@@ -1,18 +0,0 @@
|
||||
language: node_js
|
||||
os: linux
|
||||
sudo: required
|
||||
dist: trusty
|
||||
before_install:
|
||||
- sudo apt-get install -y mesa-utils
|
||||
- sudo apt-get install -y xvfb
|
||||
- sudo apt-get install -y libgl1-mesa-dri
|
||||
- sudo apt-get install -y libglapi-mesa
|
||||
- sudo apt-get install -y libosmesa6
|
||||
- sudo apt-get install -y gcc-4.9
|
||||
- sudo apt-get install -y libstdc++6
|
||||
- sudo apt-get install -y libxi-dev
|
||||
node_js:
|
||||
- "12"
|
||||
- "10"
|
||||
before_script:
|
||||
- export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start
|
||||
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -6,7 +6,6 @@
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"firsttris.vscode-jest-runner",
|
||||
"msjsdiag.debugger-for-chrome",
|
||||
"slevesque.shader",
|
||||
"stpn.vscode-graphql",
|
||||
"wayou.vscode-todo-highlight"
|
||||
|
||||
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/*"],
|
||||
},
|
||||
}
|
||||
}
|
||||
841
CHANGELOG.md
841
CHANGELOG.md
@@ -3,14 +3,851 @@ All notable changes to this project will be documented in this file, following t
|
||||
|
||||
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]
|
||||
|
||||
## [v4.1.0] - 2023-03-31
|
||||
|
||||
- Add `VolumeTransform` to translate/rotate a volume like in a structure superposition
|
||||
- Fix BinaryCIF encoder edge cases caused by re-encoding an existing BinaryCIF file
|
||||
- Fix edge-case where width/height in InputObserver are not correct
|
||||
- Fix transparency rendering fallback (#1058)
|
||||
- Fix SSAO broken when `OES_texture_float_linear` is unavailable
|
||||
- Add `normalOffset` to `external-volume` color theme
|
||||
- This can give results similar to pymol's surface_ramp_above_mode=1
|
||||
- Add `rotation` parameter to skybox background
|
||||
|
||||
## [v4.0.1] - 2023-02-19
|
||||
|
||||
- Fix BinaryCIF decoder edge cases. Fixes mmCIF model export from data provided by ModelServer.
|
||||
- MolViewSpec extension: support for MVSX file format
|
||||
- Revert "require WEBGL_depth_texture extension" & "remove renderbuffer use"
|
||||
|
||||
## [v4.0.0] - 2023-02-04
|
||||
|
||||
- Add Mesoscale Explorer app for investigating large systems
|
||||
- [Breaking] Remove `cellpack` extension (superseded by Mesoscale Explorer app)
|
||||
- [Breaking] Set minimal node.js version to 18
|
||||
- [Breaking] Generalize rcsb/assembly-symmetry/ extension
|
||||
- Move to assembly-symmetry/
|
||||
- Remove RCSB specific dependencies and prefixes
|
||||
- [Breaking] Require `WEBGL_depth_texture` webgl extension
|
||||
- Remove `renderbuffer` use
|
||||
- [Breaking] Change build target to ES2018
|
||||
- Custom builds only require ES6 for dependencies like immer.js
|
||||
- [Breaking] Changed `createPluginUI`
|
||||
- The function now takes a single `options` argument
|
||||
- The caller must specify a `render` method that mounts the Mol* react component to DOM
|
||||
- A default `renderReact18` method is provided, but needs to be imported separately
|
||||
- To support React 16 and 17, `ReactDOM.render` can be passed
|
||||
- Improve `SetUtils` performance using ES6 features
|
||||
- [Breaking] Reduce memory usage of `SymmetryOperator.ArrayMapping`
|
||||
- Requires calling methods from instance
|
||||
- [Breaking] Fix `mol-model/structure/model/properties/seconday-structure.ts` file name (#938)
|
||||
- [Breaking] Add `Canvas3DContext` runtime props
|
||||
- Props: pixelScale, pickScale, transparency (blended, wboit, dpoit)
|
||||
- Replaces instantiation-time attribs
|
||||
- [Breaking] Change default compile target to ES2018
|
||||
- [Breaking] Add culling & LOD support
|
||||
- Cull per-object and per-instance
|
||||
- Cull based on frustum and camera distance
|
||||
- LOD visibility based on camera distance
|
||||
- Special LOD mode for spheres with automatic levels
|
||||
- Occlusion culling (only WebGL2)
|
||||
- Hi-Z pass
|
||||
- Cull based on previous frame's Hi-Z buffer
|
||||
- Add stochastic/dithered transparency to fade overlapping LODs in and out
|
||||
- Add "Automatic Detail" preset that shows surface/cartoon/ball & stick based on camera distance
|
||||
|
||||
## [v3.45.0] - 2023-02-03
|
||||
|
||||
- Add color interpolation to impostor cylinders
|
||||
- MolViewSpec components are applicable only when the model has been loaded from MolViewSpec
|
||||
- Add `snapshotKey` and `tooltip` params to loci `LabelRepresentation`
|
||||
- Update `FocusLoci` behavior to support `snapshotKey` param
|
||||
- Clicking a visual with `snapshotKey` will trigger that snapshot
|
||||
- Render multiline loci label tooltips as Markdown
|
||||
- `ParamDefinition.Text` updates:
|
||||
- Support `multiline` inputs
|
||||
- Support `placeholder` parameter
|
||||
- Support `disableInteractiveUpdates` to only trigger updates once the control loses focus
|
||||
- Move dependencies related to the headless context from optional deps to optional peer deps
|
||||
|
||||
## [v3.44.0] - 2023-01-06
|
||||
|
||||
- Add new `cartoon` visuals to support atomic nucleotide base with sugar
|
||||
- Add `thicknessFactor` to `cartoon` representation for scaling nucleotide block/ring/atomic-fill visuals
|
||||
- Use bonds from `_struct_conn` in mmCIF files that use `label_seq_id`
|
||||
- Fix measurement label `offsetZ` default: not needed when `scaleByRadius` is enbaled
|
||||
- Support for label rendering in HeadlessPluginContext
|
||||
- MolViewSpec extension
|
||||
- Support all X11 colors
|
||||
- Support relative URIs
|
||||
- CLI tools: mvs-validate, mvs-render, mvs-print-schema
|
||||
- Labels applied in one node
|
||||
- ModelServer SDF/MOL2 ligand export: fix atom indices when additional atoms are present
|
||||
- Avoid showing (and calculating) inter-unit bonds for huge structures
|
||||
- Fixed `DragOverlay` on WebKit/Safari browsers
|
||||
|
||||
## [v3.43.1] - 2023-12-04
|
||||
|
||||
- Fix `react-markdown` dependency
|
||||
|
||||
## [v3.43.0] - 2023-12-02
|
||||
|
||||
- Fix `State.tryGetCellData` (return type & data check)
|
||||
- Don't change camera.target unless flyMode or pointerLock are enabled
|
||||
- Handle empty CIF files
|
||||
- Snapshot improvements:
|
||||
- Add `key` property
|
||||
- Ability to existing snapshot name, key, and description
|
||||
- Support markdown in descriptions (ignores all HTML tags)
|
||||
- Ability to link to snapshots by key from descriptions
|
||||
- Separate UI control showing description of the current snapshot
|
||||
- Do not activate drag overlay for non-file content
|
||||
- Add `structure-element-sphere` visual to `spacefill` representation
|
||||
- Fix missing `await` in `HeadlessPluginContext.saveStateSnapshot`
|
||||
- Added support for providing custom sequence viewers to the plugin spec
|
||||
- MolViewSpec extension (MVS)
|
||||
- Add URL parameters `mvs-url`, `mvs-data`, `mvs-format`
|
||||
- Add drag&drop for `.mvsj` files
|
||||
- Fix `bumpiness` scaling with `ignoreLight` enabled
|
||||
- Add `transforms` & `label` params to `ShapeFromPly`
|
||||
- Optimize `LociSelectManager.selectOnly` to avoid superfluous loci set operations
|
||||
- Dispose of viewer on `unload` event to aid GC
|
||||
|
||||
## [v3.42.0] - 2023-11-05
|
||||
|
||||
- Fix handling of PDB files with insertion codes (#945)
|
||||
- Fix de-/saturate of colors with no hue
|
||||
- Improve `distinctColors` function
|
||||
- Add `sort` and `sampleCountFactor` parameters
|
||||
- Fix clustering issues
|
||||
- Add `clipPrimitive` option to spheres geometry, clipping whole spheres instead of cutting them
|
||||
- Add `DragAndDropManager`
|
||||
- Add `options` support for default bond labels
|
||||
|
||||
## [v3.41.0] - 2023-10-15
|
||||
|
||||
- Add `PluginContext.initialized` promise & support for it in the `Plugin` UI component.
|
||||
- Fix undesired interaction between settings panel and the panel on the right.
|
||||
- Add ability to customize server parameters for `RCSBAssemblySymmetry`.
|
||||
|
||||
## [v3.40.1] - 2023-09-30
|
||||
|
||||
- Do not call `updateFocusRepr` if default `StructureFocusRepresentation` isn't present.
|
||||
- Treat "tap" as a click in `InputObserver`
|
||||
- ModelServer ligand queries: fix atom count reported by SDF/MOL/MOL2 export
|
||||
- CCD extension: Make visuals for aromatic bonds configurable
|
||||
- Add optional `file?: CifFile` to `MmcifFormat.data`
|
||||
- Add support for webgl extensions
|
||||
- `WEBGL_clip_cull_distance`
|
||||
- `EXT_conservative_depth`
|
||||
- `WEBGL_stencil_texturing`
|
||||
- `EXT_clip_control`
|
||||
- Add `MultiSampleParams.reduceFlicker` (to be able to switch it off)
|
||||
- Add `alphaThickness` parameter to adjust alpha of spheres for radius
|
||||
- Ability to hide "right" panel from simplified viewport controls
|
||||
- Add `blockIndex` parameter to TrajectoryFromMmCif
|
||||
- Fix bounding sphere calculation for "element-like" visuals
|
||||
- Fix RCSB PDB validation report URL
|
||||
- Add sharpening postprocessing option
|
||||
- Take pixel-ratio into account for outline scale
|
||||
- Gracefully handle missing HTMLImageElement
|
||||
- Fix pixel-ratio changes not applied to all render passes
|
||||
|
||||
## [v3.39.0] - 2023-09-02
|
||||
|
||||
- Add some elements support for `guessElementSymbolString` function
|
||||
- Faster bounding rectangle calculation for imposter spheres
|
||||
- Allow toggling of hydrogens as part of `LabelTextVisual`
|
||||
|
||||
## [v3.38.3] - 2023-07-29
|
||||
|
||||
- Fix imposter spheres not updating, e.g. in trajectories (broke in v3.38.0)
|
||||
|
||||
## [v3.38.2] - 2023-07-24
|
||||
|
||||
- Don't rely solely on `chem_comp_atom` when detecting CCD files (#877)
|
||||
- Actually support non-physical keys in `Bindings.Trigger.code`
|
||||
|
||||
## [v3.38.1] - 2023-07-22
|
||||
|
||||
- Fix pixel-scale not updated in SSAO pass
|
||||
|
||||
## [v3.38.0] - 2023-07-18
|
||||
|
||||
- Fix display issue with SIFTS mapping
|
||||
- Support non-physical keys in `Bindings.Trigger.code`
|
||||
- Update `getStateSnapshot` to only overwrite current snapshot if it was created automatically
|
||||
- Fix distinct palette's `getSamples` infinite loop
|
||||
- Add 'NH2', 'FOR', 'FMT' to `CommonProteinCaps`
|
||||
- Add `opened` event to `PluginStateSnapshotManager`
|
||||
- Properly switch-off fog
|
||||
- Add `approximate` option for spheres rendering
|
||||
- Reduce `Spheres` memory usage
|
||||
- Derive mapping from VertexID
|
||||
- Pull position and group from texture
|
||||
- Add `Euler` math primitive
|
||||
- Add stride option to element sphere & point visuals
|
||||
- Add `disabledExtensions` field to default viewer's options
|
||||
- Add `LRUCache.remove`
|
||||
- Add 'Chain Instance' and 'Uniform' options for 'Carbon Color' param (in Color Theme: Element Symbol)
|
||||
|
||||
## [v3.37.1] - 2023-06-20
|
||||
|
||||
- Fix issues with wboit/dpoit in large scenes
|
||||
- Fix lines, text, points rendering (broken in v3.37.0)
|
||||
|
||||
## [v3.37.0] - 2023-06-17
|
||||
|
||||
- Add `inverted` option to `xrayShaded` parameter
|
||||
- Model-export extension: Add ability to set a file name for structures
|
||||
- Add `contextHash` to `SizeTheme`
|
||||
- Add mipmap-based blur for image backgrounds
|
||||
|
||||
## [v3.36.1] - 2023-06-11
|
||||
|
||||
- Allow parsing of CCD ligand files
|
||||
- Add dedicated wwPDB CCD extension to align and visualize ideal & model CCD coordinates
|
||||
- Make operators in `IndexPairBonds` a directed property
|
||||
- Remove erroneous bounding-box overlap test in `Structure.eachUnitPair`
|
||||
- Fix `EdgeBuilder.addNextEdge` for loop edges
|
||||
- Optimize inter unit bond compute
|
||||
- Ensure consistent state for volume representation (#210)
|
||||
- Improve SSAO for thin geometry (e.g. lines)
|
||||
- Add snapshot support for structure selections
|
||||
- Add `nucleicProfile` parameter to cartoon representation
|
||||
- Add `cartoon` theme with separate colorings for for mainchain and sidechain visuals
|
||||
|
||||
## [v3.35.0] - 2023-05-14
|
||||
|
||||
- Enable odd dash count (1,3,5)
|
||||
- Add principal axes spec and fix edge cases
|
||||
- Add a uniform color theme for NtC tube that still paints residue and segment dividers in a different color
|
||||
- Mesh exporter improvements
|
||||
- Support points & lines in glTF export
|
||||
- Set alphaMode and doubleSided in glTF export
|
||||
- Fix flipped cylinder caps
|
||||
- Fix bond assignments `struct_conn` records referencing waters
|
||||
- Add StructConn extension providing functions for inspecting struct_conns
|
||||
- Fix `PluginState.setSnapshot` triggering unnecessary state updates
|
||||
- Fix an edge case in the `mol-state`'s `State` when trying to apply a transform to an existing Null object
|
||||
- Add `SbNcbrPartialCharges` extension for coloring and labeling atoms and residues by partial atomic charges
|
||||
- uses custom mmcif categories `_sb_ncbr_partial_atomic_charges_meta` and `_sb_ncbr_partial_atomic_charges` (more info in [README.md](./src/extensions/sb-ncbr/README.md))
|
||||
- Parse HEADER record when reading PDB file
|
||||
- Support `ignoreHydrogens` in interactions representation
|
||||
- Add hydroxyproline (HYP) commonly present in collagen molecules to the list of amino acids
|
||||
- Fix assemblies for Archive PDB files (do not generate unique `label_asym_id` if `REMARK 350` is present)
|
||||
- Add additional functions to `core.math` in `mol-script`
|
||||
- `cantorPairing`, `sortedCantorPairing`, `invertCantorPairing`,
|
||||
- `trunc`, `sign`
|
||||
|
||||
## [v3.34.0] - 2023-04-16
|
||||
|
||||
- Avoid `renderMarkingDepth` for fully transparent renderables
|
||||
- Remove `camera.far` doubling workaround
|
||||
- Add `ModifiersKeys.areNone` helper function
|
||||
- Do not render NtC tube segments unless all required atoms are present in the structure
|
||||
- Fix rendering issues caused by VAO reuse
|
||||
- Add "Zoom All", "Orient Axes", "Reset Axes" buttons to the "Reset Camera" button
|
||||
- Improve trackball move-state handling when key bindings use modifiers
|
||||
- Fix rendering with very small viewport and SSAO enabled
|
||||
- Fix `.getAllLoci` for structure representations with `structure.child`
|
||||
- Fix `readAllLinesAsync` refering to dom length property
|
||||
- Make mol-util/file-info node compatible
|
||||
- Add `eachLocation` to representation/visual interface
|
||||
|
||||
## [v3.33.0] - 2023-04-02
|
||||
|
||||
- Handle resizes of viewer element even when window remains the same size
|
||||
- Throttle canvas resize events
|
||||
- Selection toggle buttons hidden if selection mode is off
|
||||
- Camera focus loci bindings allow reset on click-away to be overridden
|
||||
- Input/controls improvements
|
||||
- Move or fly around the scene using keys
|
||||
- Pointer lock to look around scene
|
||||
- Toggle spin/rock animation using keys
|
||||
- Apply bumpiness as lightness variation with `ignoreLight`
|
||||
- Remove `JSX` reference from `loci-labels.ts`
|
||||
- Fix overpaint/transparency/substance smoothing not updated when geometry changes
|
||||
- Fix camera project/unproject when using offset viewport
|
||||
- Add support for loading all blocks from a mmcif file as a trajectory
|
||||
- Add `Frustum3D` and `Plane3D` math primitives
|
||||
- Include `occupancy` and `B_iso_or_equiv` when creating `Conformation` from `Model`
|
||||
- Remove LazyImports (introduced in v3.31.1)
|
||||
|
||||
## [v3.32.0] - 2023-03-20
|
||||
|
||||
- Avoid rendering of fully transparent renderables
|
||||
- Add occlusion color parameter
|
||||
- Fix issue with outlines and orthographic camera
|
||||
- Reduce over-blurring occlusion at larger view distances
|
||||
- Fix occlusion artefact with non-canvas viewport and pixel-ratio > 1
|
||||
- Update nodejs-shims conditionals to handle polyfilled document object in NodeJS environment.
|
||||
- Ensure marking edges are at least one pixel wide
|
||||
- Add exposure parameter to renderer
|
||||
- Only trigger marking when mouse is directly over canvas
|
||||
- Fix blurry occlusion in screenshots
|
||||
- [Breaking] Add `setFSModule` to `mol-util/data-source` instead of trying to trick WebPack
|
||||
|
||||
## [v3.31.4] - 2023-02-24
|
||||
|
||||
- Allow link cylinder/line `dashCount` set to '0'
|
||||
- Stop animation loop when disposing `PluginContext` (thanks @gfrn for identifying the issue)
|
||||
|
||||
## [v3.31.3] - 2023-02-22
|
||||
|
||||
- Fix impostor bond visuals not correctly updating on `sizeFactor` changes
|
||||
- Fix degenerate case in PCA
|
||||
- Fix near clipping avoidance in impostor shaders
|
||||
- Update `fs` import in `data-source.ts`
|
||||
|
||||
## [v3.31.2] - 2023-02-12
|
||||
|
||||
- Fix exit code of volume pack executable (pack.ts). Now exits with non-0 status when an error happens
|
||||
- Remove pca transform from components ui focus (too distracting)
|
||||
- Fix artefacts with opaque outlines behind transparent objects
|
||||
- Fix polymer trace visual not updating
|
||||
- Fix use of `WEBGL_provoking_vertex`
|
||||
|
||||
## [v3.31.1] - 2023-02-05
|
||||
|
||||
- Improve Component camera focus based on the PCA of the structure and the following rules:
|
||||
- The first residue should be in first quadrant if there is only one chain
|
||||
- The average position of the residues of the first chain should be in the first quadrant if there is more than one chain
|
||||
- Add `HeadlessPluginContext` and `HeadlessScreenshotHelper` to be used in Node.js
|
||||
- Add example `image-renderer`
|
||||
- Fix wrong offset when rendering text with orthographic projection
|
||||
- Update camera/handle helper when `devicePixelRatio` changes
|
||||
- Add various options to customize the axes camera-helper
|
||||
- Fix issue with texture-mesh color smoothing when changing themes
|
||||
- Add fast boundary helper and corresponding unit trait
|
||||
- Add Observable for Canvas3D commits
|
||||
|
||||
## [v3.30.0] - 2023-01-29
|
||||
|
||||
- Improve `Dnatco` extension
|
||||
- Factor out common code in `Dnatco` extension
|
||||
- Add `NtC tube` visual. Applicable for structures with NtC annotation
|
||||
- [Breaking] Rename `DnatcoConfalPyramids` to `DnatcoNtCs`
|
||||
- Improve boundary calculation performance
|
||||
- Add option to create & include images in state snapshots
|
||||
- Fix SSAO artefacts with high bias values
|
||||
- Fix SSAO resolution scale parameter handling
|
||||
- Improve outlines, visually more stable at different view distances
|
||||
|
||||
## [v3.29.0] - 2023-01-15
|
||||
|
||||
- `meshes` extension: Fixed a bug in mesh visualization (show backfaces when opacity < 1)
|
||||
- Add color quick select control to Volume controls
|
||||
- Fix `dropFiles` bug
|
||||
- Fix some cyclic imports and reduce the use of const enums. This should make it easier to use the library with the `isolatedModules: true` TS config.
|
||||
- Fix `dropFiles` bug (#679)
|
||||
- Add `input type='color'` picker to `CombinedColorControl`
|
||||
- Set `ParameterMappingControl` disabled when state is updating
|
||||
- Performance tweaks
|
||||
- Update clip `defines` only when changed
|
||||
- Check for identity in structure/unit areEqual methods
|
||||
- Avoid cloning of structure representation parameters
|
||||
- Make SymmetryOperator.createMapping monomorphic
|
||||
- Improve bonding-sphere calculation
|
||||
- Defer Scene properties calculation (markerAverage, opacityAverage, hasOpaque)
|
||||
- Improve checks in in UnitsRepresentation setVisualState
|
||||
- Add StructureElement.Loci.forEachLocation
|
||||
- Add RepresentationRegistry.clear and ThemeRegistry.clear
|
||||
- Add generic Loci support for overpaint, substance, clipping themes
|
||||
- Add `.getCenter` and `.center` to `Camera`
|
||||
- Add support to dim unmarked groups
|
||||
- Add support for marker edge strength
|
||||
|
||||
## [v3.28.0] - 2022-12-20
|
||||
|
||||
- Show histogram in direct volume control point settings
|
||||
- Add `solidInterior` parameter to sphere/cylinder impostors
|
||||
- [Breaking] Tweak `ignoreHydrogens` non-polar handling (introduced in 3.27.0)
|
||||
- Add `meshes` and `volumes-and-segmentations` extensions
|
||||
- See https://molstarvolseg.ncbr.muni.cz/ for more info
|
||||
- Fix missing support for info in `ParamDefinition.Converted`
|
||||
- Add support for multi-visual volume representations
|
||||
- Improve volume isosurface bounding-sphere
|
||||
- Add basic volume segmentation support to core
|
||||
- Add `Volume.Segment` model
|
||||
- Add `Segmentation` custom volume property
|
||||
- Add `SegmentRepresentation` representation
|
||||
- Add `volume-segment` color theme
|
||||
- Fix GPU marching cubes failing for large meshes with webgl2 (due to use of float16)
|
||||
|
||||
## [v3.27.0] - 2022-12-15
|
||||
|
||||
- Add an `includeTransparent` parameter to hide/show outlines of components that are transparent
|
||||
- Fix 'once' for animations of systems with many frames
|
||||
- Better guard against issue (black fringes) with bumpiness in impostors
|
||||
- Improve impostor shaders
|
||||
- Fix sphere near-clipping with orthographic projection
|
||||
- Fix cylinder near-clipping
|
||||
- Add interior cylinder caps
|
||||
- Add per-pixel object clipping
|
||||
- Fix `QualityAssessment` assignment bug for structures with different auth vs label sequence numbering
|
||||
- Refresh `ApplyActionControl`'s param definition when toggling expanded state
|
||||
- Fix `struct_conn` bond assignment for ions
|
||||
- Ability to show only polar hydrogens
|
||||
|
||||
## [v3.26.0] - 2022-12-04
|
||||
|
||||
- Support for ``powerPreference`` webgl attribute. Add ``PluginConfig.General.PowerPreference`` and ``power-preference`` Viewer GET param.
|
||||
- Excluded common protein caps `NME` and `ACE` from the ligand selection query
|
||||
- Add screen-space shadow post-processing effect
|
||||
- Add "Structure Molecular Surface" visual
|
||||
- Add `external-volume` theme (coloring of arbitrary geometries by user-selected volume)
|
||||
|
||||
## [v3.25.1] - 2022-11-20
|
||||
|
||||
- Fix edge-case in `Structure.eachUnitPair` with single-element units
|
||||
- Fix 'auto' structure-quality for coarse models
|
||||
|
||||
## [v3.25.0] - 2022-11-16
|
||||
|
||||
- Fix handling of gzipped assets (reverts #615)
|
||||
|
||||
## [v3.24.0] - 2022-11-13
|
||||
|
||||
- Make `PluginContext.initContainer` checkered canvas background optional
|
||||
- Store URL of downloaded assets to detect zip/gzip based on extension (#615)
|
||||
- Add optional `operator.key`; can be referenced in `IndexPairBonds`
|
||||
- Add overpaint/transparency/substance theme strength to representations
|
||||
- Fix viewport color for transparent background
|
||||
|
||||
## [v3.23.0] - 2022-10-19
|
||||
|
||||
- Add `PluginContext.initContainer/mount/unmount` methods; these should make it easier to reuse a plugin context with both custom and built-in UI
|
||||
- Add `PluginContext.canvas3dInitialized`
|
||||
- `createPluginUI` now resolves after the 3d canvas has been initialized
|
||||
- Change EM Volume Streaming default from `Whole Structure` to `Auto`
|
||||
|
||||
## [v3.22.0] - 2022-10-17
|
||||
|
||||
- Replace `VolumeIsosurfaceParams.pickingGranularity` param with `Volume.PickingGranuality`
|
||||
|
||||
## [v3.21.0] - 2022-10-17
|
||||
|
||||
- Add `VolumeIsosurfaceParams.pickingGranularity` param
|
||||
- Prevent component controls collapsing when option is selected
|
||||
|
||||
## [v3.20.0] - 2022-10-16
|
||||
|
||||
- [Breaking] Rename the ``model-index`` color theme to ``trajectory-index``
|
||||
- Add a new ``model-index`` color theme that uniquely colors each loaded model
|
||||
- Add the new ``model-index`` and ``structure-index`` color themes as an option for the carbon color in the ``element-symbol`` and ``ilustrative`` color themes
|
||||
- Add ``structure-index`` color theme that uniquely colors each root structure
|
||||
- Add ``nearest`` method to ``Lookup3D``
|
||||
- Add mipmap-based blur for skybox backgrounds
|
||||
|
||||
## [v3.19.0] - 2022-10-01
|
||||
|
||||
- Fix "empty textures" error on empty canvas
|
||||
- Optimize BinaryCIF integer packing encoder
|
||||
- Fix dual depth peeling when post-processing is off or when rendering direct-volumes
|
||||
- Add ``cameraClipping.minNear`` parameter
|
||||
- Fix black artifacts on specular highlights with transparent background
|
||||
|
||||
## [v3.18.0] - 2022-09-17
|
||||
|
||||
- Integration of Dual depth peeling - OIT method
|
||||
- Stereo camera improvements
|
||||
- Fix param updates not applied
|
||||
- Better param ranges and description
|
||||
- Add timer.mark for left/right camera
|
||||
|
||||
## [v3.17.0] - 2022-09-11
|
||||
|
||||
- [Fix] Clone ``Canvas3DParams`` when creating a ``Canvas3D`` instance to prevent shared state between multiple instances
|
||||
- Add ``includeResidueTest`` option to ``alignAndSuperposeWithSIFTSMapping``
|
||||
- Add ``parentDisplay`` param for interactions representation.
|
||||
- [Experimental] Add support for PyMOL, VMD, and Jmol atom expressions in selection scripts
|
||||
- Support for ``failIfMajorPerformanceCaveat`` webgl attribute. Add ``PluginConfig.General.AllowMajorPerformanceCaveat`` and ``allow-major-performance-caveat`` Viewer GET param.
|
||||
- Fix handling of PDB TER records (#549)
|
||||
- Add support for getting multiple loci from a representation (``.getAllLoci()``)
|
||||
- Add ``key`` property to intra- and inter-bonds for referencing source data
|
||||
- Fix click event triggered after move
|
||||
|
||||
## [v3.16.0] - 2022-08-25
|
||||
|
||||
- Support ``globalColorParams`` and ``globalSymmetryParams`` in common representation params
|
||||
- Support ``label`` parameter in ``Viewer.loadStructureFromUrl``
|
||||
- Fix ``ViewportHelpContent`` Mouse Controls section
|
||||
|
||||
## [v3.15.0] - 2022-08-23
|
||||
|
||||
- Fix wboit in Safari >=15 (add missing depth renderbuffer to wboit pass)
|
||||
- Add 'Around Camera' option to Volume streaming
|
||||
- Avoid queuing more than one update in Volume streaming
|
||||
|
||||
## [v3.14.0] - 2022-08-20
|
||||
|
||||
- Expose inter-bonds compute params in structure
|
||||
- Improve performance of inter/intra-bonds compute
|
||||
- Fix defaultAttribs handling in Canvas3DContext.fromCanvas
|
||||
- Confal pyramids extension improvements
|
||||
- Add custom labels to Confal pyramids
|
||||
- Improve naming of some internal types in Confal pyramids extension coordinate
|
||||
- Add example mmCIF file with categories necessary to display Confal pyramids
|
||||
- Change the lookup logic of NtC steps from residues
|
||||
- Add support for download of gzipped files
|
||||
- Don't filter IndexPairBonds by element-based rules in MOL/SDF and MOL2 (without symmetry) models
|
||||
- Fix Glycam Saccharide Names used by default
|
||||
- Fix GPU surfaces rendering in Safari with WebGL2
|
||||
- Add ``fov`` (Field of View) Canvas3D parameter
|
||||
- Add ``sceneRadiusFactor`` Canvas3D parameter
|
||||
- Add background pass (skybox, image, horizontal/radial gradient)
|
||||
- Set simple-settings presets via ``PluginConfig.Background.Styles``
|
||||
- Example presets in new backgrounds extension
|
||||
- Load skybox/image from URL or File (saved in session)
|
||||
- Opacity, saturation, lightness controls for skybox/image
|
||||
- Coverage (viewport or canvas) controls for image/gradient
|
||||
- [Breaking] ``AssetManager`` needs to be passed to various graphics related classes
|
||||
- Fix SSAO renderable initialization
|
||||
- Reduce number of webgl state changes
|
||||
- Add ``viewport`` and ``scissor`` to state object
|
||||
- Add ``hasOpaque`` to scene object
|
||||
- Handle edge cases where some renderables would not get (correctly) rendered
|
||||
- Fix text background rendering for opaque text
|
||||
- Fix helper scenes not shown when rendering directly to draw target
|
||||
- Fix ``CustomElementProperty`` coloring not working
|
||||
|
||||
## [v3.13.0] - 2022-07-24
|
||||
|
||||
- Fix: only update camera state if manualReset is off (#494)
|
||||
- Improve handling principal axes of points in a plane
|
||||
- Add 'material' annotation support for textures
|
||||
- More effort to avoid using ``flat`` qualifier in shaders: add ``dVaryingGroup``
|
||||
- Enable ``immediateUpdate`` for iso level in isosurface and volume streaming controls
|
||||
- Add support to download CCD from configurable URL
|
||||
|
||||
## [v3.12.1] - 2022-07-20
|
||||
|
||||
- Fix plugin behavior dispose logic to correctly unsubscribe observables.
|
||||
|
||||
## [v3.12.0] - 2022-07-17
|
||||
|
||||
- Add ``colorMarker`` option to Renderer. This disables the highlight and select marker at a shader level for faster rendering of large scenes in some cases.
|
||||
- Bind shared textures only once per pass, not for each render item
|
||||
- Fix missing 'material' annotation for some uniforms, causing unnecessary uniform updates
|
||||
- Remove use of ``isnan`` in impostor shaders, not needed and causing slowdown
|
||||
- Avoid using ``flat`` qualifier in shaders, causing slowdown
|
||||
- Improve CellPack's ``adjustStyle`` option (disable ``colorMarker``, set component options, enable marking w/o ghost)
|
||||
- Scan all entities when looking for ``struct_conn`` entries (fixes issue when the same ``label_asym_id`` is used in more than one entity)
|
||||
|
||||
## [v3.11.0] - 2022-07-04
|
||||
|
||||
- Add ``instanceGranularity`` option for marker, transparency, clipping, overpaint, substance data to save memory
|
||||
- CellPack extension tweaks
|
||||
- Use instancing to create DNA/RNA curves to save memory
|
||||
- Enable ``instanceGranularity`` by default
|
||||
- Add ``adjustStyle`` option to LoadCellPackModel action (stylized, no multi-sample, no far clipping, chain picking)
|
||||
- Structure Superposition now respects pivot's coordinate system
|
||||
|
||||
## [v3.10.2] - 2022-06-26
|
||||
|
||||
- Fix superfluous shader varying
|
||||
- Improve use of gl_VertexID when possible
|
||||
|
||||
## [v3.10.1] - 2022-06-26
|
||||
|
||||
- Fix groupCount when updating TextureMesh-based visuals
|
||||
|
||||
## [v3.10.0] - 2022-06-24
|
||||
|
||||
- Add support for Glycam saccharide names
|
||||
- Add ``PluginConfig.Viewport.ShowTrajectoryControls`` config option
|
||||
|
||||
## [v3.9.1] - 2022-06-19
|
||||
|
||||
- Fix missing ``super.componentWillUnmount()`` calls (@simeonborko)
|
||||
- Fix missing ``uGroupCount`` update for visuals
|
||||
- Fix missing aromatic bond display
|
||||
|
||||
## [v3.9.0] - 2022-05-30
|
||||
|
||||
- Improve picking by using drawbuffers (when available) to reduce number of drawcalls
|
||||
- GPU timing support
|
||||
- Add ``timing-mode`` Viewer GET param
|
||||
- Add support for webgl timer queries
|
||||
- Add timer marks around GPU render & compute operations
|
||||
- Volume Server CIF: Add check that a data block contains volume data before parsing
|
||||
- Fix ``Scene.clear`` not clearing primitives & volumes arrays (@JonStargaryen)
|
||||
- Fix rendering volumes when wboit is switched off and postprocessing is enabled
|
||||
|
||||
## [v3.8.2] - 2022-05-22
|
||||
|
||||
- Fix ``Scene.opacityAverage`` not taking xray shaded into account
|
||||
|
||||
## [v3.8.1] - 2022-05-14
|
||||
|
||||
- Fix issues with marking camera/handle helper (#433)
|
||||
- Fix issues with array uniforms when running with headless-gl
|
||||
- Fix Polymer Chain Instance coloring
|
||||
- Improve performance of scene marker/opacity average calculation
|
||||
|
||||
## [v3.8.0] - 2022-04-30
|
||||
|
||||
- Add support for outlines around transparent objects
|
||||
- Improve per-group transparency when wboit is switched off
|
||||
- Improve ``ColorTheme`` typing with ``ColorType`` generic.
|
||||
- Defaults to ``ColorTypeLocation``
|
||||
- Set when using ``ColorTypeDirect`` or ``ColorTypeGrid``
|
||||
- Fix case handling of ``struct_conf`` mmCIF enumeration field (#425)
|
||||
- Fix ``allowTransparentBackfaces`` for per-group transparency
|
||||
- Fix ``FormatRegistry.isApplicable`` returning true for unregistered formats
|
||||
- Fix: handle building of ``GridLookup3D`` with zero cell size
|
||||
- Fix ``ignoreLight`` for direct-volume rendering with webgl1
|
||||
- Fix (non-black) outlines when using transparent background
|
||||
|
||||
## [v3.7.0] - 2022-04-13
|
||||
|
||||
- Fix ``xrayShaded`` for texture-mesh geometries
|
||||
- [Breaking] Change ``allowTransparentBackfaces`` to ``transparentBackfaces`` with options ``off``, ``on``, ``opaque``. This was only added in 3.6.0, so allowing a breaking change here.
|
||||
- ``off``: don't show (default)
|
||||
- ``on``: show with transparency
|
||||
- ``opaque``: show fully opaque
|
||||
- Add option to disable file drop overlay.
|
||||
|
||||
## [v3.6.2] - 2022-04-05
|
||||
|
||||
- ModelServer ligand queries: fixes for alternate locations, additional atoms & UNL ligand
|
||||
- React 18 friendly ``useBehavior`` hook.
|
||||
|
||||
## [v3.6.1] - 2022-04-03
|
||||
|
||||
- Fix React18 related UI regressions.
|
||||
|
||||
## [v3.6.0] - 2022-04-03
|
||||
|
||||
- Check that model and coordinates have same element count when creating a trajectory
|
||||
- Fix aromatic rings assignment: do not mix flags and planarity test
|
||||
- Improve bonds assignment of coarse grained models: check for IndexPairBonds and exhaustive StructConn
|
||||
- Fix unit mapping in bondedAtomicPairs MolScript query
|
||||
- Improve pdb parsing: handle non unique atom and chain names (fixes #156)
|
||||
- Fix volume streaming for entries with multiple contour lists
|
||||
- Add ``allowTransparentBackfaces`` parameter to support double-sided rendering of transparent geometries
|
||||
- Fix handling of case insensitive mmCIF enumeration fields (including entity.type)
|
||||
- Fix ``disable-wboit`` Viewer GET param
|
||||
- Add support for React 18.
|
||||
- Used by importing ``createPluginUI`` from ``mol-plugin-ui/react18``;
|
||||
- In Mol* 4.0, React 18 will become the default option.
|
||||
|
||||
## [v3.5.0] - 2022-03-25
|
||||
|
||||
- Fix issues with bounding-sphere & color-smoothing (mostly for small geometries)
|
||||
- Support BCIF => CIF conversion in ``cif2bcif`` CLI tool
|
||||
|
||||
## [v3.4.0] - 2022-03-13
|
||||
|
||||
- Fix handling of mmcif with empty ``label_*`` fields
|
||||
- Improve saccharide detection (compare against list from CCD)
|
||||
- Fix legend label of hydrophobicity color theme
|
||||
- Add ``LoadTrajectory`` action
|
||||
- Add ``CustomImportControls`` to left panel
|
||||
- Add Zenodo import extension (load structures, trajectories, volumes, and zip files)
|
||||
- Fix loading of some compressed files within sessions
|
||||
- Fix wrong element assignment for atoms with Charmm ion names
|
||||
- Fix handling of empty symmetry cell data
|
||||
- Add support for ``trr`` and ``nctraj`` coordinates files
|
||||
- Add support for ``prmtop`` and ``top`` topology files
|
||||
|
||||
## [v3.3.1] - 2022-02-27
|
||||
|
||||
- Fix issue with unit boundary reuse (do at visual level instead)
|
||||
- Add option to ignore ions for inter-unit bond computation
|
||||
|
||||
## [v3.3.0] - 2022-02-27
|
||||
|
||||
- Fix parsing contour-level from emdb v3 header files
|
||||
- Fix invalid CSS (#376)
|
||||
- Fix "texture not renderable" & "texture not bound" warnings (#319)
|
||||
- Fix visual for bonds between two aromatic rings
|
||||
- Fix visual for delocalized bonds (parsed from mmcif and mol2)
|
||||
- Fix ring computation algorithm
|
||||
- Add ``UnitResonance`` property with info about delocalized triplets
|
||||
- Resolve marking in main renderer loop to improve overall performance
|
||||
- Use ``throttleTime`` instead of ``debounceTime`` in sequence viewer for better responsiveness
|
||||
- Change line geometry default ``scaleFactor`` to 2 (3 is too big after fixing line rendering)
|
||||
- Trajectory animation performance improvements
|
||||
- Reuse ``Model.CoarseGrained`` for coordinate trajectories
|
||||
- Avoid calculating ``InterUnitBonds`` when ``Structure.parent`` ones are empty
|
||||
- Reuse unit boundary if sphere has not changed too much
|
||||
- Don't show 'inter-bond' and 'element-cross' visuals in line representations of polymerAndLigand preset
|
||||
- Fix additional mononucleotides detected as polymer components
|
||||
- Fix and improve ``canRemap`` handling in ``IntraUnitBonds``
|
||||
- Reuse occlusion for secondary passes during multi-sampling
|
||||
- Check if marking passes are needed before doing them
|
||||
- Add ``resolutionScale`` parameter to allow trading quality of occlusion for performance
|
||||
|
||||
## [v3.2.0] - 2022-02-17
|
||||
|
||||
- Rename "best database mapping" to "SIFTS Mapping"
|
||||
- Add schema and export support for ``atom_site.pdbx_sifts_xref_*`` fields
|
||||
- Add schema export support for ``atom_site.pdbx_label_index`` field
|
||||
- Add `traceOnly` parameter to chain/UniProt-based structure alignment
|
||||
- Store ``IndexPairBonds`` as a dynamic property.
|
||||
|
||||
## [v3.1.0] - 2022-02-06
|
||||
|
||||
- Fix ``xrayShaded`` & ``ignoreLight`` params not working at the same time
|
||||
- Add ``ignoreLight`` to component params
|
||||
- Tweaks for cleaner default representation style
|
||||
- Cartoon: use ``nucleotide-ring`` instead of ``nucleotide-block``
|
||||
- Focus: use ``xrayShaded`` instead of opacity; adjust target size; don't show non-covalent interactions twice
|
||||
- Fix representation preset side effects (changing post-processing parameters, see #363)
|
||||
- Add Quick Styles panel (default, illustrative, stylized)
|
||||
- Fix exported structure missing secondary-structure categories (#364)
|
||||
- Fix volume streaming error message: distinguish between missing data and server error (#364)
|
||||
|
||||
## [v3.0.2] - 2022-01-30
|
||||
|
||||
- Fix color smoothing of elongated structures (by fixing ``Sphere.expand`` for spheres with highly directional extrema)
|
||||
- Fix entity label not displayed when multiple instances of the same entity are highlighted
|
||||
- Fix empty elements created in ``StructureElement.Loci.extendToAllInstances``
|
||||
- Measurement options tweaks (allow larger ``textSize``; make ``customText`` essential)
|
||||
- Fix visual visibility sync edge case when changing state snapshots
|
||||
|
||||
## [v3.0.1] - 2022-01-27
|
||||
|
||||
- Fix marking pass not working with ``transparentBackground``
|
||||
- Fix pdbe xray maps url not https
|
||||
- Fix entity-id color theme broken for non-IHM models
|
||||
- Improve/fix marking of ``InteractionsInterUnitVisual`` (mark when all contact-feature members are given)
|
||||
- Add missing "entity-id" and "enity-source" options for carbon coloring to "element-symbol" color theme
|
||||
- Fix VolumeServer/query CLI
|
||||
- Support automatic iso-value adjustment for VolumeServer data in ``Viewer.loadVolumeFromUrl``
|
||||
- Emit drag event whenever started within viewport (not only for non-empty loci)
|
||||
|
||||
## [v3.0.0] - 2022-01-23
|
||||
|
||||
- Assembly handling tweaks:
|
||||
- Do not include suffix for "identity assembly operators"
|
||||
- Do not include assembly-related categories to export if the structure was composed from an assembly
|
||||
- Special case for ``structAsymMap`` if Mol* asym id operator mapping is present
|
||||
- Support for opening ZIP files with multiple entries
|
||||
- Add Model Export extension
|
||||
- Bugfix: Automatically treat empty string as "non-present" value in BinaryCIF writer.
|
||||
- Fix coarse model support in entity-id color theme
|
||||
- Fix marking of carbohydrate visuals (whole chain could get marked instead of single residue)
|
||||
- Add custom colors to "element-symbol", "molecule-type", "residue-name", and "secondary-structure" themes
|
||||
- Support/bugfixes for ``atom_site.pdbx_sifts_xref`` categories
|
||||
- Improve/fix marking of ``InteractionsIntraUnitVisual`` (mark when all contact-feature members are given)
|
||||
|
||||
## [v3.0.0-dev.10] - 2022-01-17
|
||||
|
||||
- Fix ``getOperatorsForIndex``
|
||||
- Pass animation info (current frame & count) to state animations
|
||||
- Fix camera stutter for "camera spin" animation
|
||||
- Add formal charge parsing support for MOL/SDF files (thanks @ptourlas)
|
||||
- [Breaking] Cleaner looking ``MembraneOrientationVisuals`` defaults
|
||||
- [Breaking] Add rock animation to trackball controls
|
||||
- Add ``animate`` to ``TrackballControlsParams``, remove ``spin`` and ``spinSpeed``
|
||||
- Add ``animate`` to ``SimpleSettingsParams``, remove ``spin``
|
||||
- Add "camera rock" state animation
|
||||
- Add support for custom colors to "molecule-type" theme
|
||||
- [Breaking] Add style parameter to "illustrative" color theme
|
||||
- Defaults to "entity-id" style instead of "chain-id"
|
||||
- Add "illustrative" representation preset
|
||||
|
||||
## [v3.0.0-dev.9] - 2022-01-09
|
||||
|
||||
- Add PDBj as a ``pdb-provider`` option
|
||||
- Move Viewer APP to a separate file to allow use without importing light theme & index.html
|
||||
- Add symmetry support for mol2 files (only spacegroup setting 1)
|
||||
- Fix mol2 files element symbol assignment
|
||||
- Improve bond assignment from ``IndexPairBonds``
|
||||
- Add ``key`` field for mapping to source data
|
||||
- Fix assignment of bonds with unphysical length
|
||||
- Fix label/stats of single atom selection in multi-chain units
|
||||
|
||||
## [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
|
||||
- ``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
|
||||
|
||||
72
CITATION.cff
Normal file
72
CITATION.cff
Normal file
@@ -0,0 +1,72 @@
|
||||
cff-version: 1.2.0
|
||||
title: >-
|
||||
Mol* library
|
||||
message: >-
|
||||
Please cite this software using the metadata from
|
||||
'preferred-citation'.
|
||||
authors:
|
||||
- given-names: Alexander S
|
||||
family-names: Rose
|
||||
orcid: 'https://orcid.org/0000-0002-0893-5551'
|
||||
- given-names: David
|
||||
family-names: Sehnal
|
||||
orcid: 'https://orcid.org/0000-0002-0682-3089'
|
||||
- given-names: Sebastian
|
||||
family-names: Bittrich
|
||||
orcid: 'https://orcid.org/0000-0003-3576-0387'
|
||||
- given-names: Áron Samuel
|
||||
family-names: Kovács
|
||||
- given-names: Ludovic
|
||||
family-names: Autin
|
||||
orcid: 'https://orcid.org/0000-0002-2197-191X'
|
||||
- given-names: Michal
|
||||
family-names: Malý
|
||||
- given-names: Jiří
|
||||
family-names: Černý
|
||||
- given-names: Panagiotis
|
||||
family-names: Tourlas
|
||||
type: software
|
||||
doi: 10.5281/zenodo.3947306
|
||||
preferred-citation:
|
||||
authors:
|
||||
- given-names: David
|
||||
family-names: Sehnal
|
||||
orcid: 'https://orcid.org/0000-0002-0682-3089'
|
||||
- given-names: Sebastian
|
||||
family-names: Bittrich
|
||||
orcid: 'https://orcid.org/0000-0003-3576-0387'
|
||||
- given-names: Mandar
|
||||
family-names: Deshpande
|
||||
orcid: 'https://orcid.org/0000-0002-9043-7665'
|
||||
- given-names: Radka
|
||||
family-names: Svobodová
|
||||
orcid: 'https://orcid.org/0000-0002-3840-8760'
|
||||
- given-names: Karel
|
||||
family-names: Berka
|
||||
orcid: 'https://orcid.org/0000-0001-9472-2589'
|
||||
- given-names: Václav
|
||||
family-names: Bazgier
|
||||
orcid: 'https://orcid.org/0000-0003-3393-3010'
|
||||
- given-names: Sameer
|
||||
family-names: Velankar
|
||||
orcid: 'https://orcid.org/0000-0002-8439-5964'
|
||||
- given-names: Stephen K
|
||||
family-names: Burley
|
||||
orcid: 'https://orcid.org/0000-0002-2487-9713'
|
||||
- given-names: Jaroslav
|
||||
family-names: Koča
|
||||
orcid: 'https://orcid.org/0000-0002-2780-4901'
|
||||
- given-names: Alexander S
|
||||
family-names: Rose
|
||||
orcid: 'https://orcid.org/0000-0002-0893-5551'
|
||||
title: >-
|
||||
Mol* Viewer: modern web app for 3D visualization
|
||||
and analysis of large biomolecular structures
|
||||
type: article
|
||||
doi: 10.1093/nar/gkab314
|
||||
journal: "Nucleic Acids Research"
|
||||
issue: W1
|
||||
volume: 49
|
||||
year: 2021
|
||||
month: 7
|
||||
pages: "W431–W437"
|
||||
26
README.md
26
README.md
@@ -1,6 +1,6 @@
|
||||
[](./LICENSE)
|
||||
[](https://www.npmjs.com/package/molstar)
|
||||
[](https://travis-ci.org/molstar/molstar)
|
||||
[](https://github.com/molstar/molstar/actions/workflows/node.yml)
|
||||
[](https://gitter.im/molstar/Lobby)
|
||||
|
||||
# Mol*
|
||||
@@ -11,6 +11,13 @@ When using Mol*, please cite:
|
||||
|
||||
David Sehnal, Sebastian Bittrich, Mandar Deshpande, Radka Svobodová, Karel Berka, Václav Bazgier, Sameer Velankar, Stephen K Burley, Jaroslav Koča, Alexander S Rose: [Mol* Viewer: modern web app for 3D visualization and analysis of large biomolecular structures](https://doi.org/10.1093/nar/gkab314), *Nucleic Acids Research*, 2021; https://doi.org/10.1093/nar/gkab314.
|
||||
|
||||
### Protein Data Bank Integrations
|
||||
|
||||
- The [pdbe-molstar](https://github.com/molstar/pdbe-molstar) library is the Mol* implementation used by EMBL-EBI data resources such as [PDBe](https://pdbe.org/), [PDBe-KB](https://pdbe-kb.org/) and [AlphaFold DB](https://alphafold.ebi.ac.uk/). This implementation can be used as a JS plugin and a Web component and supports property/attribute-based easy customisation. It provides helper methods to facilitate programmatic interactions between the web application and the 3D viewer. It also provides a superposition view for overlaying all the observed ligand molecules on representative protein conformations.
|
||||
|
||||
- [rcsb-molstar](https://github.com/molstar/rcsb-molstar) is the Mol* plugin used by [RCSB PDB](https://www.rcsb.org). The project provides additional presets for the visualization of structure alignments and structure motifs such as ligand binding sites. Furthermore, [rcsb-molstar](https://github.com/molstar/rcsb-molstar) allows to interactively add or hide of (parts of) chains, as seen in the [3D Protein Feature View](https://www.rcsb.org/3d-sequence/4hhb).
|
||||
|
||||
|
||||
## Project Structure Overview
|
||||
|
||||
The core of Mol* consists of these modules (see under `src/`):
|
||||
@@ -113,10 +120,9 @@ and navigate to `build/viewer`
|
||||
|
||||
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-ions.js src/mol-model/structure/model/types/ions.ts
|
||||
|
||||
**Saccharide names**
|
||||
|
||||
**GraphQL schemas**
|
||||
|
||||
node node_modules//@graphql-codegen/cli/bin -c src/extensions/rcsb/graphql/codegen.yml
|
||||
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-saccharides.js src/mol-model/structure/model/types/saccharides.ts
|
||||
|
||||
### Other scripts
|
||||
**Create chem comp bond table**
|
||||
@@ -131,7 +137,7 @@ and navigate to `build/viewer`
|
||||
|
||||
export NODE_PATH="lib"; node build/state-docs
|
||||
|
||||
**Convert any CIF to BinaryCIF**
|
||||
**Convert any CIF to BinaryCIF (or vice versa)**
|
||||
|
||||
node lib/commonjs/servers/model/preprocess -i file.cif -ob file.bcif
|
||||
|
||||
@@ -141,6 +147,11 @@ Or
|
||||
|
||||
node lib/commonjs/cli/cif2bcif
|
||||
|
||||
E.g.
|
||||
|
||||
node lib/commonjs/cli/cif2bcif src.cif out.bcif.gz
|
||||
node lib/commonjs/cli/cif2bcif src.bcif.gz out.cif
|
||||
|
||||
## Development
|
||||
|
||||
### Installation
|
||||
@@ -152,13 +163,12 @@ If node complains about a missing acorn peer dependency, run the following comma
|
||||
|
||||
### Editor
|
||||
|
||||
To get syntax highlighting for shader and graphql files add the following to Visual Code's settings files and make sure relevant extensions are installed in the editor.
|
||||
To get syntax highlighting for shader files add the following to Visual Code's settings files and make sure relevant extensions are installed in the editor.
|
||||
|
||||
"files.associations": {
|
||||
"*.glsl.ts": "glsl",
|
||||
"*.frag.ts": "glsl",
|
||||
"*.vert.ts": "glsl",
|
||||
"*.gql.ts": "graphql"
|
||||
"*.vert.ts": "glsl"
|
||||
},
|
||||
|
||||
## Publish
|
||||
|
||||
@@ -24,6 +24,11 @@ atom_site.auth_asym_id
|
||||
atom_site.auth_seq_id
|
||||
atom_site.pdbx_PDB_model_num
|
||||
atom_site.ihm_model_id
|
||||
atom_site.pdbx_label_index
|
||||
atom_site.pdbx_sifts_xref_db_name
|
||||
atom_site.pdbx_sifts_xref_db_acc
|
||||
atom_site.pdbx_sifts_xref_db_num
|
||||
atom_site.pdbx_sifts_xref_db_res
|
||||
|
||||
atom_site_anisotrop.id
|
||||
atom_site_anisotrop.U
|
||||
@@ -246,6 +251,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 +815,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
|
||||
|
86
docs/extensions/mvs/README.md
Normal file
86
docs/extensions/mvs/README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# MolViewSpec extension in Mol\*
|
||||
|
||||
**MolViewSpec (MVS)** is a toolkit for standardized description of reproducible molecular visualizations shareable across software applications. MolViewSpec describes a 3D molecular scene by a MolViewSpec State, a viewer-independent representation that contains all information necessary to reproduce the scene. Thanks to its nested tree-based format, complex scenes can be composed from simple building blocks.
|
||||
|
||||
MolViewSpec documentation is available from <https://molstar.org/docs/extensions/mvs/>.
|
||||
|
||||
MolViewSpec extension in Mol\* provides functionality for building, validating, and visualizing MolViewSpec States.
|
||||
|
||||
## Graphical user interface
|
||||
|
||||
- **Drag&drop support:** The easiest way to load a MVS view into Mol* Viewer is to drag a `.mvsj` or `.mvsx` file and drop it in a browser window with Mol* Viewer.
|
||||
|
||||
- **Load via menu:** Another way to load a MVS view is to use "Download File" or "Open Files" action, available in the "Home" tab in the left panel. For these actions, the "Format" parameter must be set to "MVSJ" or "MVSX" (in the "Miscellaneous" category) or "Auto".
|
||||
|
||||
- **URL parameters:** Mol\* Viewer supports `mvs-url`, `mvs-data`, and `mvs-format` URL parameters to specify a MVS view to be loaded when the viewer is initialized.
|
||||
|
||||
- `mvs-url` specifies the address from which the MVS view should be retrieved.
|
||||
- `mvs-data` specifies the MVS view data directly. Keep in mind that some characters must be escaped to be used in the URL. Also beware that URLs longer than 2000 character may not work in all browsers. Because of these limitations, the preferred method it to host the data somewhere and use `mvs-url` instead.
|
||||
- `mvs-format` specifies the format of the MVS view data from `mvs-url` or `mvs-data`. Allowed values are `mvsj` and `mvsx` (default is `mvsj`).
|
||||
|
||||
Examples of URL parameter usage:
|
||||
|
||||
- https://molstar.org/viewer/?mvs-format=mvsj&mvs-url=https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs.mvsj
|
||||
|
||||
- https://molstar.org/viewer/?mvs-format=mvsx&mvs-url=https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1h9t.mvsx
|
||||
|
||||
- https://molstar.org/viewer/?mvs-format=mvsj&mvs-data=%7B%22metadata%22%3A%7B%22title%22%3A%22Example%20MolViewSpec%20-%201cbs%20with%20labelled%20protein%20and%20ligand%22%2C%22version%22%3A%221%22%2C%22timestamp%22%3A%222023-11-24T10%3A38%3A17.483%22%7D%2C%22root%22%3A%7B%22kind%22%3A%22root%22%2C%22children%22%3A%5B%7B%22kind%22%3A%22download%22%2C%22params%22%3A%7B%22url%22%3A%22https%3A//www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22parse%22%2C%22params%22%3A%7B%22format%22%3A%22bcif%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22structure%22%2C%22params%22%3A%7B%22type%22%3A%22model%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22component%22%2C%22params%22%3A%7B%22selector%22%3A%22polymer%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22representation%22%2C%22params%22%3A%7B%22type%22%3A%22cartoon%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22color%22%2C%22params%22%3A%7B%22color%22%3A%22green%22%7D%7D%2C%7B%22kind%22%3A%22color%22%2C%22params%22%3A%7B%22selector%22%3A%7B%22label_asym_id%22%3A%22A%22%2C%22beg_label_seq_id%22%3A1%2C%22end_label_seq_id%22%3A50%7D%2C%22color%22%3A%22%236688ff%22%7D%7D%5D%7D%2C%7B%22kind%22%3A%22label%22%2C%22params%22%3A%7B%22text%22%3A%22Protein%22%7D%7D%5D%7D%2C%7B%22kind%22%3A%22component%22%2C%22params%22%3A%7B%22selector%22%3A%22ligand%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22representation%22%2C%22params%22%3A%7B%22type%22%3A%22ball_and_stick%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22color%22%2C%22params%22%3A%7B%22color%22%3A%22%23cc3399%22%7D%7D%5D%7D%2C%7B%22kind%22%3A%22label%22%2C%22params%22%3A%7B%22text%22%3A%22Retinoic%20Acid%22%7D%7D%5D%7D%5D%7D%5D%7D%5D%7D%2C%7B%22kind%22%3A%22canvas%22%2C%22params%22%3A%7B%22background_color%22%3A%22%23ffffee%22%7D%7D%2C%7B%22kind%22%3A%22camera%22%2C%22params%22%3A%7B%22target%22%3A%5B17%2C21%2C27%5D%2C%22position%22%3A%5B41%2C34%2C69%5D%2C%22up%22%3A%5B-0.129%2C0.966%2C-0.224%5D%7D%7D%5D%7D%7D
|
||||
|
||||
- https://molstar.org/viewer/?mvs-format=mvsx&mvs-data=base64,UEsDBBQAAAAIADSFPlhDx8RXYwEAAGwFAAAUABwAYW5ub3RhdGlvbnMtMWg5dC5jaWZVVAkAA8MmuWVuvbtldXgLAAEE9gEAAAQUAAAAlZJNj4IwEIbv/RVNPPSkacGPckT8TDabPe6emkq72ARbF9iD/34LFBV1FSbh7YTh4Z1hELzgjOyDgnGtTcELZXQOQGrMkQEWm8PRaKmLfJTynUwZz08HpkSrck5BCD8yU0ilwfycRXDxHoJFpUu4NqkAm/pYwWifmkwJCdaXdHtJmy6uOrtt47q0kwmry7n8uatKLZ5UY2M9Hzi1byWZ+T2WbAhtBPaiPoRoIMR0ijGyqZ1yuFNaKJ0geI5RBdAxhJ5PSgBjSmsgjE/pMDIhbFEj+wFbDnHcAP85zO8dKH3mEFWHEzQgBGPOnQP8vHp347BoA6KKBvh6ACzbwLeNeoZy/XfPW2DTF1i1AYyFqIHmH0I3wLovsO0LlJuezJygTDZ9ozeVcC2aLcBcFSVLKmA6c9IRIB5x8hrwamDipDMQOOkKzKiT14BfAh72S+nY0rzf0OMa6D60A7oPXQF9ZhiDP1BLAwQUAAAACACagEFYhgn8CJECAAAbFQAACgAcAGluZGV4Lm12c2pVVAkAAyTBu2UlwbtldXgLAAEE9gEAAAQUAAAA7VjBjtowEL3vV0S5tiSBFnXhttpKPbWq1KqXqoqMPWwsHDuynaVoxb/XNhAIiUOEtK26m5zIzLzJG8+8gfB0EwShFEKH8+DJfDZ3K8qJudtZ3+5sOKOMSODG/tNZgn10DUHEmjOByB7lfAWSKFdV9r21lMwCMq0LNY/j9XodwYJGCEflKi7IAmLgWm5GS8pAxYe88Tib6bQsCNJAIkyXYZVze/LIFrJ1wjXShqCCE8Z+1s6zFDJH9rTCPK8xOGPRxaTJpsZIaVliXcpzVt3MnFdvCnDcBAEWNiK2LQk7OLbzrHHFIi8EN81Kl1LkaSlpC+nLxF2EBZuUUYw4FxppKrga2Za7VrenrXWkMwyboXkQcpNylEONuurITYGRJsIPWDCBV2kGiIC0EDexJ/X4kQpnkCP3mAxRfonTI2IlKE/T9pFfpdBgUnkifrXaW2bEJbswJ/bydfZ0qUAhQZkjdMfhLbLPvLiow8BjJLUQ3lK9ZbkkPUqzVxeRmiSYkJfkUKF6lekir5NHBe8nkyq8IZc+Q3x82JluzIlcBl2tnSrDUUNmyCgpIZWIP4B/Kuy17fD6fe3i8SHaolsn8nWv22fYgR+/3L2G/bdAjBmtkFRpilfDGvTC/+oaHPZRhRj20S7yk2CkS/b3mZENJd4ZeUlLS5VyiXCHHIZtNWwrT/Q124qhBbD/b1P1en2s/+J2lT7vW2qPGWnr5jV9M292TNNi6Ny/7VzDdq7LesSp9+g5WHcWe+cmIsxBI4I0Ov4P+QhS2a8bwziJxod/IgkoLGmh9547HsBvlBcMgjXVWfD5x7egeRyhpjkobeIsZpJM3o+SySgZfx+P59Pb+btZNPtwO5lO3yTJPEnskWxvtn8AUEsBAh4DFAAAAAgANIU+WEPHxFdjAQAAbAUAABQAGAAAAAAAAQAAAKSBAAAAAGFubm90YXRpb25zLTFoOXQuY2lmVVQFAAPDJrlldXgLAAEE9gEAAAQUAAAAUEsBAh4DFAAAAAgAmoBBWIYJ/AiRAgAAGxUAAAoAGAAAAAAAAQAAAKSBsQEAAGluZGV4Lm12c2pVVAUAAyTBu2V1eAsAAQT2AQAABBQAAABQSwUGAAAAAAIAAgCqAAAAhgQAAAAA
|
||||
|
||||
## Programming interface
|
||||
|
||||
Most functions for manipulation of MVS data (including parsing, encoding, validating, and building) are provided by the `MVSData` object (defined in [src/extensions/mvs/mvs-data.ts](/src/extensions/mvs/mvs-data.ts)). In TypeScript, `MVSData` is also the type for a MVS view.
|
||||
|
||||
The `loadMVS` function (defined in [src/extensions/mvs/load.ts](/src/extensions/mvs/load.ts)) can be used to load MVS view data into Mol\* Viewer.
|
||||
|
||||
Example usage:
|
||||
|
||||
```ts
|
||||
// Fetch a MVS, validate, and load
|
||||
const response = await fetch('https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs.mvsj');
|
||||
const rawData = await response.text();
|
||||
const mvsData: MVSData = MVSData.fromMVSJ(rawData);
|
||||
if (!MVSData.isValid(mvsData)) throw new Error(`Oh no: ${MVSData.validationIssues(mvsData)}`);
|
||||
await loadMVS(this.plugin, mvsData, { replaceExisting: true });
|
||||
console.log('Loaded this:', MVSData.toPrettyString(mvsData));
|
||||
console.log('Loaded this:', MVSData.toMVSJ(mvsData));
|
||||
|
||||
// Build a MVS and load
|
||||
const builder = MVSData.createBuilder();
|
||||
const structure = builder.download({ url: 'https://www.ebi.ac.uk/pdbe/entry-files/download/1og2_updated.cif' }).parse({ format: 'mmcif' }).modelStructure();
|
||||
structure.component({ selector: 'polymer' }).representation({ type: 'cartoon' });
|
||||
structure.component({ selector: 'ligand' }).representation({ type: 'ball_and_stick' }).color({ color: '#aa55ff' });
|
||||
const mvsData2: MVSData = builder.getState();
|
||||
await loadMVS(this.plugin, mvsData2, { replaceExisting: false });
|
||||
```
|
||||
|
||||
When using the pre-built Mol\* plugin bundle, `MVSData` and `loadMVS` are exposed as `molstar.PluginExtensions.mvs.MVSData` and `molstar.PluginExtensions.mvs.loadMVS`. Furthermore, the `molstar.Viewer` class has `loadMvsFromUrl` and `loadMvsData` methods, providing the same functionality as `mvs-url` and `mvs-data` URL parameters. See the [integration examples](./integration-examples.html) page for a demonstration.
|
||||
|
||||
## Command-line utilities
|
||||
|
||||
The MVS extension in Mol\* provides a few command-line utilities, which can be executed via NodeJS:
|
||||
|
||||
- `mvs-validate` provides validation of MolViewSpec files
|
||||
- `mvs-render` creates images based on MolViewSpec files
|
||||
- `mvs-print-schema` prints MolViewSpec tree schema (i.e. currently supported node types and their parameters)
|
||||
|
||||
Example usage:
|
||||
|
||||
```sh
|
||||
# Clone Mol* repo, install, and build
|
||||
git clone https://github.com/molstar/molstar.git
|
||||
cd molstar/
|
||||
npm install && npm run build
|
||||
|
||||
# Validate a MolViewSpec file `examples/mvs/1cbs.mvsj`
|
||||
node lib/commonjs/cli/mvs/mvs-validate.js examples/mvs/1cbs.mvsj
|
||||
|
||||
# Render a MolViewSpec file `examples/mvs/1cbs.mvsj` to `../outputs/1cbs.png`
|
||||
node lib/commonjs/cli/mvs/mvs-render.js -i examples/mvs/1cbs.mvsj -o ../outputs/1cbs.png --size 800x600 --molj
|
||||
|
||||
# Print MolViewSpec tree schema formatted as markdown
|
||||
node lib/commonjs/cli/mvs/mvs-print-schema.js --markdown
|
||||
```
|
||||
|
||||
(An alternative to cloning the GitHub repository is to install Mol\* package from npm by `npm install molstar canvas gl jpeg-js pngjs`. Then you can type `npx mvs-validate ...` instead of `node lib/commonjs/cli/mvs/mvs-validate.js ...`)
|
||||
112
docs/extensions/mvs/integration-examples.html
Normal file
112
docs/extensions/mvs/integration-examples.html
Normal file
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<!-- Replace "latest" by the specific version you want to use, e.g. "4.0.0" -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.js"></script>
|
||||
<!-- Replace "latest" by the specific version you want to use, e.g. "4.0.0" -->
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Integration of Mol* with MolViewSpec Extension</h1>
|
||||
<p>
|
||||
This page demonstrates several methods to integrate Mol* Viewer in a web page and use MolViewSpec functionality.
|
||||
See the source HTML to see the actual code.
|
||||
</p>
|
||||
|
||||
|
||||
<h2>Method 1: Get MVS view from a server and pass to the viewer</h2>
|
||||
<p>
|
||||
The recommended method is to serve the MVS view files by your server (either as static files or generated by the
|
||||
server on-demand) and call the <code>loadMvsFromUrl</code> method to retrieve and load them.
|
||||
This example uses a MVS view file from the address specified in the <code>sourceUrl</code> variable.
|
||||
If the MVS view file contains relative references, they will be resolved as relative to <code>sourceUrl</code>.
|
||||
</p>
|
||||
|
||||
<div id="viewer1" style="position: relative; width: 500px; height: 500px;"></div>
|
||||
<script>
|
||||
const sourceUrl = 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1h9t_domain_labels.mvsj';
|
||||
molstar.Viewer.create('viewer1', { layoutIsExpanded: false, layoutShowControls: false })
|
||||
.then(viewer => viewer.loadMvsFromUrl(sourceUrl, 'mvsj'));
|
||||
</script>
|
||||
|
||||
|
||||
<p>
|
||||
A variation of this method uses <code>molstar.PluginExtensions.mvs.loadMVS</code> instead of
|
||||
<code>loadMvsFromUrl</code> and allows replacing the MVS view after it has been loaded.
|
||||
</p>
|
||||
|
||||
<div id="viewer1b" style="position: relative; width: 500px; height: 500px;"></div>
|
||||
<button onclick="loadView1();">View 1</button>
|
||||
<button onclick="loadView2();">View 2</button>
|
||||
<script>
|
||||
let theViewer;
|
||||
function load(viewer, url, replace) {
|
||||
fetch(url)
|
||||
.then(response => response.text())
|
||||
.then(text => molstar.PluginExtensions.mvs.MVSData.fromMVSJ(text))
|
||||
.then(mvsData => molstar.PluginExtensions.mvs.loadMVS(viewer.plugin, mvsData, { sourceUrl: url, sanityChecks: true, replaceExisting: replace }));
|
||||
}
|
||||
function loadView1() {
|
||||
load(theViewer, 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs.mvsj', true);
|
||||
}
|
||||
function loadView2() {
|
||||
load(theViewer, 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs-focus.mvsj', true);
|
||||
}
|
||||
molstar.Viewer.create('viewer1b', { layoutIsExpanded: false, layoutShowControls: false })
|
||||
.then(viewer => {
|
||||
theViewer = viewer;
|
||||
loadView1();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<h2>Method 2: Construct MVS view on frontend and pass to the viewer</h2>
|
||||
<p>
|
||||
Another option is to utilize the MVS builder provided by the extension to build the view on frontend and then
|
||||
pass it to the viewer. This example builds the view in plain JavaScript, directly in a <script> tag in
|
||||
HTML. However, for a better developer experience consider writing the code in TypeScript.
|
||||
If the built MVS view contains relative references, they will be resolved as relative to the URL of this HTML
|
||||
page.
|
||||
</p>
|
||||
|
||||
<div id="viewer2" style="position: relative; width: 500px; height: 500px;"></div>
|
||||
<script>
|
||||
// Build an ad-hoc MVS view
|
||||
const builder = molstar.PluginExtensions.mvs.MVSData.createBuilder();
|
||||
const structure = builder
|
||||
.download({ url: 'https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif' })
|
||||
.parse({ format: 'bcif' })
|
||||
.modelStructure({});
|
||||
structure
|
||||
.component({ selector: 'polymer' })
|
||||
.representation({ type: 'cartoon' })
|
||||
.color({ color: 'green' });
|
||||
structure
|
||||
.component({ selector: 'ligand' })
|
||||
.label({ text: 'Retinoic acid' })
|
||||
.focus({})
|
||||
.representation({ type: 'ball_and_stick' })
|
||||
.color({ color: '#cc3399' });
|
||||
const mvsData = builder.getState();
|
||||
|
||||
// Initialize viewer and load MVSJ
|
||||
const mvsj = molstar.PluginExtensions.mvs.MVSData.toMVSJ(mvsData);
|
||||
molstar.Viewer.create('viewer2', { layoutIsExpanded: false, layoutShowControls: false })
|
||||
.then(viewer => viewer.loadMvsData(mvsj, 'mvsj'));
|
||||
|
||||
// // Alternative initialization and loading (avoids encoding and again decoding the data, allows changing the view by using `replaceExisting: true`):
|
||||
// molstar.Viewer.create('viewer2', { layoutIsExpanded: false, layoutShowControls: false })
|
||||
// .then(viewer => molstar.PluginExtensions.mvs.loadMVS(viewer.plugin, mvsData, { sourceUrl: undefined, sanityChecks: true, replaceExisting: false }));
|
||||
</script>
|
||||
|
||||
|
||||
<p>
|
||||
Again, there is variation with using <code>molstar.PluginExtensions.mvs.loadMVS</code> instead of
|
||||
<code>loadMvsData</code>.
|
||||
</p>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
118
docs/extensions/struct-conn.md
Normal file
118
docs/extensions/struct-conn.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# wwPDB StructConn extension
|
||||
|
||||
The STRUCT_CONN category in the mmCIF file format contains details about the connections between portions of the structure. These can be hydrogen bonds, salt bridges, disulfide bridges and so on (see more at <https://mmcif.wwpdb.org/dictionaries/mmcif_pdbx_v40.dic/Categories/struct_conn.html>).
|
||||
|
||||
**wwPDB StructConn extension** in Mol* provides functionality to retrieve and visualize these connections.
|
||||
|
||||
The extension exposes three functions, located in `src/extensions/wwpdb/struct-conn/index.ts`.
|
||||
|
||||
- `getStructConns` - to retrieve struct_conn records from a loaded structure
|
||||
- `inspectStructConn` - to visualize a struct_conn
|
||||
- `clearStructConnInspections` - to remove visulizations created by `inspectStructConn`
|
||||
|
||||
|
||||
## Example 1
|
||||
|
||||
The following example is a minimal HTML using this functionality:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<title>Mol* Viewer</title>
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
</head>
|
||||
<body style="margin: 0px;">
|
||||
<div style="position: absolute; width: 100%; height: 10%; padding-block: 10px;">
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'disulf1');">disulf1</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'disulf2');">disulf2</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'covale1');">covale1</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'covale2');">covale2</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'covale3');">covale3</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'covale4');">covale4</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'metalc1');">metalc1</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'metalc2');">metalc2</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'metalc3');">metalc3</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'metalc4');">metalc4</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.clearStructConnInspections(molstarViewer.plugin, '5elb');">CLEAR</button>
|
||||
</div>
|
||||
<div id="app" style="position: absolute; top: 10%; width: 100%; height: 90%;"></div>
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
var molstarViewer;
|
||||
molstar.Viewer.create('app', { layoutIsExpanded: false }).then(viewer => {
|
||||
molstarViewer = viewer;
|
||||
viewer.loadPdb('5elb');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
The PDB ID (`'5elb'`) can be replaced be `undefined`, in which case the functions will apply to the first loaded structure.
|
||||
|
||||
|
||||
## Example 2
|
||||
|
||||
This is a more elaborated example, which automatically loads `5elb` (or any PDB entry given in the URL after `?pdb=`), retrieves the list of struct_conns, and creates a button for each struct_conn.
|
||||
|
||||
Be aware that some of the struct_conns may be present in the deposited model but not in the preferred assembly (default view). The presented example will raise a dialog window with error message in such cases, e.g. `disulf6` in entry `5elb`.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<title>Mol* Viewer - StructConn Extension Demo</title>
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
</head>
|
||||
<style>
|
||||
body { margin: 0px; }
|
||||
#app { position: absolute; width: 85%; height: 100%; }
|
||||
#controls { position: absolute; right: 0; width: 15%; height: 100%; display: flex; flex-direction: column; overflow-y: scroll; }
|
||||
h1 { text-align: center; margin: 12px; font-weight: bold; font-size: 120%; }
|
||||
button { margin: 4px; margin-top: 0px; }
|
||||
</style>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="controls">
|
||||
<h1 id="pdb-id">Loading...</h1>
|
||||
<button onclick="clearInspections();">CLEAR</button>
|
||||
</div>
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
var pdbId = window.location.search.match(/[?&]pdb=(\w+)/i)?.[1]?.toLowerCase() ?? '5elb';
|
||||
var molstarViewer;
|
||||
function inspect(structConnId) {
|
||||
if (molstarViewer?.plugin) {
|
||||
molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, pdbId, structConnId).then(nSelectedAtoms => {
|
||||
if (nSelectedAtoms < 2) alert('Some of the interacting atoms were not found :(\n(maybe not present in the viewed assembly)');
|
||||
});
|
||||
}
|
||||
}
|
||||
function clearInspections() {
|
||||
if (molstarViewer?.plugin) {
|
||||
molstar.PluginExtensions.wwPDBStructConn.clearStructConnInspections(molstarViewer.plugin, pdbId);
|
||||
}
|
||||
}
|
||||
molstar.Viewer.create('app', { layoutIsExpanded: false }).then(viewer => {
|
||||
molstarViewer = viewer;
|
||||
return viewer.loadPdb(pdbId);
|
||||
}).then(() => {
|
||||
const structConns = molstar.PluginExtensions.wwPDBStructConn.getStructConns(molstarViewer.plugin, pdbId);
|
||||
const controls = document.getElementById('controls');
|
||||
for (const structConnId in structConns) {
|
||||
const button = document.createElement('button');
|
||||
button.innerText = structConnId;
|
||||
button.addEventListener('click', () => inspect(structConnId));
|
||||
controls.appendChild(button);
|
||||
};
|
||||
document.getElementById('pdb-id').innerHTML = pdbId;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
44
docs/file-formats.md
Normal file
44
docs/file-formats.md
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
Support file formats and their extensions.
|
||||
|
||||
## Structure
|
||||
|
||||
- MMCIF and CIFCORE (mmCIF and coreCIF schemas): cif, bcif, mmcif, mcif
|
||||
- GRO: gro
|
||||
- MOL: mol
|
||||
- MOL2: mol2
|
||||
- PDB/PDBQT: pdb, ent, pdbqt
|
||||
- SDF: sdf, sd
|
||||
- XYZ: xyz
|
||||
|
||||
|
||||
## Topology
|
||||
|
||||
Need to be loaded together with Coordinates.
|
||||
|
||||
- PRMTOP: prmtop, parm7
|
||||
- PSF: psf
|
||||
- TOP: top
|
||||
|
||||
## Coordinates
|
||||
|
||||
Need to be loaded together with a Structure or Topology.
|
||||
|
||||
- DCD: dcd
|
||||
- NCTRAJ: nc, nctraj
|
||||
- TRR: trr
|
||||
- XTC: xtc
|
||||
|
||||
|
||||
## Volume
|
||||
|
||||
- CCP4/MRC/MAP: ccp4, mrc, map
|
||||
- CUBE (may include a Structure): cub, cube
|
||||
- DSN6/BRIX: dsn6, brix
|
||||
- DX and DXBIN: dx, dxbin
|
||||
- DSCIF (DensityServer CIF schema): cif, bcif
|
||||
|
||||
|
||||
## Shape
|
||||
|
||||
- PLY
|
||||
@@ -13,6 +13,7 @@
|
||||
* DZ has C1 instead of N1 (e.g. 6I4N)
|
||||
* DP has N5 instead of C5 and C7 instead of N7 (e.g. 6I4N)
|
||||
* Beta & Gamma peptides (e.g. 1GAC, 6PQF)
|
||||
* Helices of D-amino acids (e.g. 7QDI)
|
||||
* Mixed (heterogeneous) all-atom/trace-only RNA model (1JGQ)
|
||||
* Polymers with residues with missing trace atoms (e.g. 2QFJ)
|
||||
* Modified RNA bases (1y26, 5L4O)
|
||||
@@ -25,10 +26,24 @@
|
||||
* Non-standard residues
|
||||
* Protein (1BRR, 5Z6Y)
|
||||
* DNA (5D3G)
|
||||
* Collagen (6JEC)
|
||||
* 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)
|
||||
* Ligands with many rings
|
||||
* STU (e.g. 1U59) - many fused rings
|
||||
* HT (e.g. 127D) - rings connected by a single bond
|
||||
* J2C (e.g. 7EFJ) - rings connected by a single atom
|
||||
* RBF (e.g. 7QF2) - three linearly fused rings
|
||||
* TA1 (e.g. 1JFF) - many fused rings (incl. a 8-member rings)
|
||||
* BPA (e.g. 1JDG) - many fused rings
|
||||
* CLR (e.g. 3GKI) - four fused rings
|
||||
|
||||
Assembly symmetries
|
||||
* 5M30 (Assembly 1, C3 local and pseudo)
|
||||
|
||||
1694
examples/1bna_confal_pyramids.cif
Normal file
1694
examples/1bna_confal_pyramids.cif
Normal file
File diff suppressed because it is too large
Load Diff
75130
examples/7qpd.fw2.cif
Normal file
75130
examples/7qpd.fw2.cif
Normal file
File diff suppressed because it is too large
Load Diff
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
28000
examples/long_animation.sdf
Normal file
28000
examples/long_animation.sdf
Normal file
File diff suppressed because it is too large
Load Diff
115
examples/mvs/1cbs-focus.mvsj
Normal file
115
examples/mvs/1cbs-focus.mvsj
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "Example MolViewSpec - 1cbs with labelled and zoomed ligand",
|
||||
"version": "1",
|
||||
"timestamp": "2023-11-24T10:45:49.873Z"
|
||||
},
|
||||
"root": {
|
||||
"kind": "root",
|
||||
"children": [
|
||||
{
|
||||
"kind": "download",
|
||||
"params": {
|
||||
"url": "https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "parse",
|
||||
"params": {
|
||||
"format": "bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "structure",
|
||||
"params": {
|
||||
"type": "model"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "polymer"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "cartoon"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"color": "green"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"end_label_seq_id": 50
|
||||
},
|
||||
"color": "#6688ff"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Protein"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "ligand"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "focus",
|
||||
"params": {
|
||||
"direction": [0.5, 0, -1],
|
||||
"up": [0.365, 0.913, 0.183]
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "ball_and_stick"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"color": "#cc3399"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Retinoic Acid"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "canvas",
|
||||
"params": {
|
||||
"background_color": "#ffffee"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
117
examples/mvs/1cbs.mvsj
Normal file
117
examples/mvs/1cbs.mvsj
Normal file
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "Example MolViewSpec - 1cbs with labelled protein and ligand",
|
||||
"version": "1",
|
||||
"timestamp": "2023-11-24T10:38:17.483Z"
|
||||
},
|
||||
"root": {
|
||||
"kind": "root",
|
||||
"children": [
|
||||
{
|
||||
"kind": "download",
|
||||
"params": {
|
||||
"url": "https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "parse",
|
||||
"params": {
|
||||
"format": "bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "structure",
|
||||
"params": {
|
||||
"type": "model"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "polymer"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "cartoon"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"color": "green"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 1,
|
||||
"end_label_seq_id": 50
|
||||
},
|
||||
"color": "#6688ff"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Protein"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "ligand"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "ball_and_stick"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"color": "#cc3399"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Retinoic Acid"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "canvas",
|
||||
"params": {
|
||||
"background_color": "#ffffee"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "camera",
|
||||
"params": {
|
||||
"target": [17, 21, 27],
|
||||
"position": [41, 34, 69],
|
||||
"up": [-0.129,0.966,-0.224]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
examples/mvs/1h9t.mvsx
Normal file
BIN
examples/mvs/1h9t.mvsx
Normal file
Binary file not shown.
67
examples/mvs/1h9t_domain_colors.mvsj
Normal file
67
examples/mvs/1h9t_domain_colors.mvsj
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "Example MolViewSpec - 1h9t colored by external annotation",
|
||||
"version": "1",
|
||||
"timestamp": "2023-11-24T10:47:33.182Z"
|
||||
},
|
||||
"root": {
|
||||
"kind": "root",
|
||||
"children": [
|
||||
{
|
||||
"kind": "download",
|
||||
"params": {
|
||||
"url": "https://www.ebi.ac.uk/pdbe/entry-files/1h9t.bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "parse",
|
||||
"params": {
|
||||
"format": "bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "structure",
|
||||
"params": {
|
||||
"type": "model"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "polymer"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "cartoon"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"selector": "all",
|
||||
"color": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "color_from_uri",
|
||||
"params": {
|
||||
"uri": "./1h9t_domains.json",
|
||||
"format": "json",
|
||||
"schema": "all_atomic"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
583
examples/mvs/1h9t_domain_labels.mvsj
Normal file
583
examples/mvs/1h9t_domain_labels.mvsj
Normal file
@@ -0,0 +1,583 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "Example MolViewSpec - 1h9t colored and labelled by external annotation",
|
||||
"version": "1",
|
||||
"timestamp": "2023-11-24T10:48:28.677Z"
|
||||
},
|
||||
"root": {
|
||||
"kind": "root",
|
||||
"children": [
|
||||
{
|
||||
"kind": "download",
|
||||
"params": {
|
||||
"url": "https://www.ebi.ac.uk/pdbe/entry-files/1h9t.bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "parse",
|
||||
"params": {
|
||||
"format": "bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "structure",
|
||||
"params": {
|
||||
"type": "model"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "protein"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "cartoon"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"selector": "all",
|
||||
"color": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "color_from_uri",
|
||||
"params": {
|
||||
"uri": "./1h9t_domains.json",
|
||||
"format": "json",
|
||||
"schema": "all_atomic"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "nucleic"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "ball_and_stick"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"selector": "all",
|
||||
"color": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "color_from_uri",
|
||||
"params": {
|
||||
"uri": "./1h9t_domains.json",
|
||||
"format": "json",
|
||||
"schema": "all_atomic"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "ion"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "surface"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color_from_uri",
|
||||
"params": {
|
||||
"uri": "./1h9t_domains.json",
|
||||
"format": "json",
|
||||
"schema": "all_atomic"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 9,
|
||||
"end_label_seq_id": 83
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "DNA-binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"beg_label_seq_id": 9,
|
||||
"end_label_seq_id": 83
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "DNA-binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 84,
|
||||
"end_label_seq_id": 231
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Acyl-CoA\nbinding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"beg_label_seq_id": 84,
|
||||
"end_label_seq_id": 231
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Acyl-CoA binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "C"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "DNA X"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "D"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "DNA Y"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "D",
|
||||
"atom_id": 4016
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "DNA Y O5'"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "D",
|
||||
"atom_id": 4391
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "DNA Y O3'"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "E"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Gold"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "H"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Gold"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "F"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Chloride"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "G"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Chloride"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "I"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Chloride"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 57
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 67
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 121
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 125
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 129
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 178
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 203,
|
||||
"end_label_seq_id": 205
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 67
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 121
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 125
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 129
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 178
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"beg_label_seq_id": 203,
|
||||
"end_label_seq_id": 205
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "all"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "focus",
|
||||
"params": {
|
||||
"direction": [-0.3, -0.1, -1]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "canvas",
|
||||
"params": {
|
||||
"background_color": "#eeffee"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
155
examples/mvs/1h9t_domains.json
Normal file
155
examples/mvs/1h9t_domains.json
Normal file
@@ -0,0 +1,155 @@
|
||||
[
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 9,
|
||||
"end_label_seq_id": 83,
|
||||
"color": "#dd6600",
|
||||
"tooltip": "DNA-binding"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 84,
|
||||
"end_label_seq_id": 231,
|
||||
"color": "#008800",
|
||||
"tooltip": "Acyl-CoA binding"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"beg_label_seq_id": 9,
|
||||
"end_label_seq_id": 83,
|
||||
"color": "#cc8800",
|
||||
"tooltip": "DNA-binding"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"beg_label_seq_id": 84,
|
||||
"end_label_seq_id": 231,
|
||||
"color": "#008888",
|
||||
"tooltip": "Acyl-CoA binding"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "C",
|
||||
"color": "#1100aa",
|
||||
"tooltip": "DNA X"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "D",
|
||||
"color": "#dddddd",
|
||||
"tooltip": "DNA Y"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "D",
|
||||
"atom_id": 4016,
|
||||
"color": "#ff0044",
|
||||
"tooltip": "DNA Y - O5'"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "D",
|
||||
"atom_id": 4391,
|
||||
"color": "#4400ff",
|
||||
"tooltip": "DNA Y - O3'"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"label_asym_id": "E",
|
||||
"color": "#ffff00",
|
||||
"tooltip": "Gold"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "H",
|
||||
"color": "#ffff00",
|
||||
"tooltip": "Gold"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "F",
|
||||
"color": "#00dd00",
|
||||
"tooltip": "Chloride"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "G",
|
||||
"color": "#00dd00",
|
||||
"tooltip": "Chloride"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "I",
|
||||
"color": "#00dd00",
|
||||
"tooltip": "Chloride"
|
||||
},
|
||||
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 57,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 67,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 121,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 125,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 129,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 178,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 203,
|
||||
"end_label_seq_id": 205,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 67,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 121,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 125,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 129,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"beg_label_seq_id": 203,
|
||||
"end_label_seq_id": 205,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
}
|
||||
]
|
||||
45536
package-lock.json
generated
45536
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
148
package.json
148
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.0.0-dev.2",
|
||||
"version": "4.1.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -13,14 +13,14 @@
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"lint-fix": "eslint . --fix",
|
||||
"test": "npm run lint && jest",
|
||||
"test": "npm install --no-save \"gl@^6.0.2\" && 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/",
|
||||
"build-extra": "cpx \"src/**/*.{scss,html,ico,jpg}\" lib/",
|
||||
"build-webpack": "webpack --mode production --config ./webpack.config.production.js",
|
||||
"build-webpack-viewer": "webpack --mode production --config ./webpack.config.viewer.js",
|
||||
"watch": "concurrently -c \"green,green,gray,gray\" --names \"tsc,srv,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-servers\" \"npm:watch-extra\" \"npm:watch-webpack\"",
|
||||
@@ -28,7 +28,7 @@
|
||||
"watch-viewer-debug": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer-debug\"",
|
||||
"watch-tsc": "tsc --watch --incremental",
|
||||
"watch-servers": "tsc --build tsconfig.commonjs.json --watch --incremental",
|
||||
"watch-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/ --watch",
|
||||
"watch-extra": "cpx \"src/**/*.{scss,html,ico,jpg}\" lib/ --watch",
|
||||
"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",
|
||||
@@ -48,6 +48,9 @@
|
||||
"bin": {
|
||||
"cif2bcif": "lib/commonjs/cli/cif2bcif/index.js",
|
||||
"cifschema": "lib/commonjs/cli/cifschema/index.js",
|
||||
"mvs-validate": "lib/commonjs/cli/mvs/mvs-validate.js",
|
||||
"mvs-render": "lib/commonjs/cli/mvs/mvs-render.js",
|
||||
"mvs-print-schema": "lib/commonjs/cli/mvs/mvs-print-schema.js",
|
||||
"model-server": "lib/commonjs/servers/model/server.js",
|
||||
"model-server-query": "lib/commonjs/servers/model/query.js",
|
||||
"model-server-preprocess": "lib/commonjs/servers/model/preprocess.js",
|
||||
@@ -75,7 +78,9 @@
|
||||
"node_modules",
|
||||
"lib"
|
||||
],
|
||||
"testURL": "http://localhost/",
|
||||
"testEnvironmentOptions": {
|
||||
"url": "http://localhost/"
|
||||
},
|
||||
"testRegex": "\\.spec\\.ts$"
|
||||
},
|
||||
"author": "Mol* Contributors",
|
||||
@@ -86,75 +91,104 @@
|
||||
"Áron Samuel Kovács <aron.kovacs@mail.muni.cz>",
|
||||
"Ludovic Autin <autin@scripps.edu>",
|
||||
"Michal Malý <michal.maly@ibt.cas.cz>",
|
||||
"Jiří Černý <jiri.cerny@ibt.cas.cz>"
|
||||
"Jiří Černý <jiri.cerny@ibt.cas.cz>",
|
||||
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>",
|
||||
"Adam Midlik <midlik@gmail.com>",
|
||||
"Koya Sakuma <koya.sakuma.work@gmail.com>",
|
||||
"Gianluca Tomasello <giagitom@gmail.com>",
|
||||
"Ke Ma <mark.ma@rcsb.org>",
|
||||
"Jason Pattle <jpattle@exscientia.co.uk>",
|
||||
"David Williams <dwilliams@nobiastx.com>",
|
||||
"Zhenyu Zhang <jump2cn@gmail.com>",
|
||||
"Russell Parker <russell@benchling.com>",
|
||||
"Dominik Tichy <tichydominik451@gmail.com>",
|
||||
"Yana Rose <yana.v.rose@gmail.com>",
|
||||
"Yakov Pechersky <ffxen158@gmail.com>",
|
||||
"Christian Dominguez <christian.99dominguez@gmail.com>",
|
||||
"Cai Huiyu <szmun.caihy@gmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^3.1.0",
|
||||
"@graphql-codegen/cli": "^2.3.0",
|
||||
"@graphql-codegen/time": "^3.1.0",
|
||||
"@graphql-codegen/typescript": "^2.4.1",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.0",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.3.1",
|
||||
"@graphql-codegen/typescript-operations": "^2.2.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/gl": "^4.1.0",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||
"@typescript-eslint/parser": "^5.4.0",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/gl": "^6.0.5",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "^18.2.73",
|
||||
"@types/react-dom": "^18.2.23",
|
||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||
"@typescript-eslint/parser": "^7.4.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^6.4.0",
|
||||
"cpx2": "^4.0.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"cpx2": "^7.0.1",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.5.1",
|
||||
"eslint": "^8.3.0",
|
||||
"css-loader": "^6.10.0",
|
||||
"eslint": "^8.57.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"graphql": "^15.7.2",
|
||||
"http-server": "^14.0.0",
|
||||
"jest": "^27.3.1",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"fs-extra": "^11.2.0",
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "^29.7.0",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"mini-css-extract-plugin": "^2.8.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass": "^1.43.5",
|
||||
"sass-loader": "^12.3.0",
|
||||
"simple-git": "^2.47.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sass": "^1.72.0",
|
||||
"sass-loader": "^14.1.1",
|
||||
"simple-git": "^3.24.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"
|
||||
"style-loader": "^3.3.4",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^5.4.3",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^2.0.10",
|
||||
"@types/benchmark": "^2.1.1",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^16.11.10",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"@types/react": "^17.0.37",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"@types/argparse": "^2.0.16",
|
||||
"@types/benchmark": "^2.1.5",
|
||||
"@types/compression": "1.7.5",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^18.19.28",
|
||||
"@types/node-fetch": "^2.6.11",
|
||||
"@types/swagger-ui-dist": "3.30.4",
|
||||
"argparse": "^2.0.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"express": "^4.19.2",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^9.0.7",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"rxjs": "^7.4.0",
|
||||
"swagger-ui-dist": "^4.1.1",
|
||||
"tslib": "^2.3.1",
|
||||
"util.promisify": "^1.1.1",
|
||||
"immer": "^10.0.4",
|
||||
"immutable": "^4.3.5",
|
||||
"io-ts": "^2.2.21",
|
||||
"node-fetch": "^2.7.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"swagger-ui-dist": "^5.13.0",
|
||||
"tslib": "^2.6.2",
|
||||
"util.promisify": "^1.1.2",
|
||||
"xhr2": "^0.2.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"gl": "^4.9.2"
|
||||
"peerDependencies": {
|
||||
"canvas": "^2.11.2",
|
||||
"gl": "^6.0.2",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"pngjs": "^6.0.0",
|
||||
"react": "^18.1.0 || ^17.0.2 || ^16.14.0",
|
||||
"react-dom": "^18.1.0 || ^17.0.2 || ^16.14.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"canvas": {
|
||||
"optional": true
|
||||
},
|
||||
"gl": {
|
||||
"optional": true
|
||||
},
|
||||
"jpeg-js": {
|
||||
"optional": true
|
||||
},
|
||||
"pngjs": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,23 @@
|
||||
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'),
|
||||
@@ -14,6 +31,11 @@ const toClean = [
|
||||
];
|
||||
|
||||
toClean.forEach(ph => {
|
||||
if (fs.existsSync(ph))
|
||||
fs.rm(ph, { recursive: true }, err => { if (err) console.warn(`Failed to delete ${ph}: ${err}`); });
|
||||
if (fs.existsSync(ph)) {
|
||||
try {
|
||||
remove(ph);
|
||||
} catch (err) {
|
||||
console.warn(`Cleanup failed: ${err}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -14,6 +14,9 @@ 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 --><iframe src="https://web3dsurvey.com/collector-iframe.html" style="width: 1px; height: 1px;"></iframe>`;
|
||||
|
||||
function log(command, stdout, stderr) {
|
||||
if (command) {
|
||||
console.log('\n###', command);
|
||||
@@ -22,11 +25,45 @@ function log(command, stdout, 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 });
|
||||
addAnalytics(path.resolve(viewerDeployPath, 'index.html'));
|
||||
}
|
||||
|
||||
function copyMe() {
|
||||
console.log('\n###', 'copy me files');
|
||||
const meBuildPath = path.resolve(buildDir, '../build/mesoscale-explorer/');
|
||||
const meDeployPath = path.resolve(localPath, 'me/');
|
||||
fse.copySync(meBuildPath, meDeployPath, { overwrite: true });
|
||||
addAnalytics(path.resolve(meDeployPath, '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();
|
||||
copyMe();
|
||||
copyDemos();
|
||||
}
|
||||
|
||||
if (!fs.existsSync(localPath)) {
|
||||
@@ -42,9 +79,9 @@ if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
|
||||
.outputHandler(log)
|
||||
.clone(remoteUrl, localPath)
|
||||
.fetch(['--all'])
|
||||
.exec(copyViewer)
|
||||
.exec(copyFiles)
|
||||
.add(['-A'])
|
||||
.commit('updated viewer')
|
||||
.commit('updated viewer & demos')
|
||||
.push();
|
||||
} else {
|
||||
console.log('\n###', 'update repository');
|
||||
@@ -52,8 +89,8 @@ if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
|
||||
.outputHandler(log)
|
||||
.fetch(['--all'])
|
||||
.reset(['--hard', 'origin/master'])
|
||||
.exec(copyViewer)
|
||||
.exec(copyFiles)
|
||||
.add(['-A'])
|
||||
.commit('updated viewer')
|
||||
.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>
|
||||
@@ -8,7 +8,8 @@
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
import { createPlugin } from '../../mol-plugin-ui';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { renderReact18 } from '../../mol-plugin-ui/react18';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
@@ -54,23 +55,26 @@ const DefaultViewerOptions = {
|
||||
};
|
||||
|
||||
class Viewer {
|
||||
plugin: PluginUIContext
|
||||
constructor(public plugin: PluginUIContext) {
|
||||
}
|
||||
|
||||
constructor(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
|
||||
const o = { ...DefaultViewerOptions, ...{
|
||||
layoutIsExpanded: false,
|
||||
layoutShowControls: false,
|
||||
layoutShowRemoteState: false,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: false,
|
||||
layoutShowLeftPanel: true,
|
||||
static async create(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
|
||||
const o = {
|
||||
...DefaultViewerOptions, ...{
|
||||
layoutIsExpanded: false,
|
||||
layoutShowControls: false,
|
||||
layoutShowRemoteState: false,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: false,
|
||||
layoutShowLeftPanel: true,
|
||||
|
||||
viewportShowExpand: true,
|
||||
viewportShowControls: false,
|
||||
viewportShowSettings: false,
|
||||
viewportShowSelectionMode: false,
|
||||
viewportShowAnimation: false,
|
||||
} };
|
||||
viewportShowExpand: true,
|
||||
viewportShowControls: false,
|
||||
viewportShowSettings: false,
|
||||
viewportShowSelectionMode: false,
|
||||
viewportShowAnimation: false,
|
||||
}
|
||||
};
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const spec: PluginUISpec = {
|
||||
@@ -125,29 +129,29 @@ 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({ target: element, spec, render: renderReact18 });
|
||||
|
||||
(this.plugin.customState as any) = {
|
||||
(plugin.customState as any) = {
|
||||
colorPalette: {
|
||||
name: 'colors',
|
||||
params: { list: { colors } }
|
||||
}
|
||||
};
|
||||
|
||||
this.plugin.behaviors.canvas3d.initialized.subscribe(v => {
|
||||
if (v) {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
|
||||
renderer: {
|
||||
...this.plugin.canvas3d!.props.renderer,
|
||||
backgroundColor: ColorNames.white,
|
||||
},
|
||||
camera: {
|
||||
...this.plugin.canvas3d!.props.camera,
|
||||
helper: { axes: { name: 'off', params: {} } }
|
||||
}
|
||||
} });
|
||||
PluginCommands.Canvas3D.SetSettings(plugin, {
|
||||
settings: {
|
||||
renderer: {
|
||||
...plugin.canvas3d!.props.renderer,
|
||||
backgroundColor: ColorNames.white,
|
||||
},
|
||||
camera: {
|
||||
...plugin.canvas3d!.props.camera,
|
||||
helper: { axes: { name: 'off', params: {} } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new Viewer(plugin);
|
||||
}
|
||||
|
||||
async loadStructuresFromUrlsAndMerge(sources: { url: string, format: BuiltInTrajectoryFormat, isBinary?: boolean }[]) {
|
||||
@@ -163,7 +167,7 @@ class Viewer {
|
||||
structures.push({ ref: structureProperties?.ref || structure.ref });
|
||||
}
|
||||
|
||||
// remove current structuresfrom hierarchy as they will be merged
|
||||
// remove current structures from hierarchy as they will be merged
|
||||
// TODO only works with using loadStructuresFromUrlsAndMerge once
|
||||
// need some more API metho to work with the hierarchy
|
||||
this.plugin.managers.structure.hierarchy.updateCurrent(this.plugin.managers.structure.hierarchy.current.structures, 'remove');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -31,7 +31,8 @@ function shinyStyle(plugin: PluginContext) {
|
||||
postprocessing: {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
occlusion: { name: 'off', params: {} },
|
||||
outline: { name: 'off', params: {} }
|
||||
shadow: { name: 'off', params: {} },
|
||||
outline: { name: 'off', params: {} },
|
||||
}
|
||||
} });
|
||||
}
|
||||
@@ -44,15 +45,21 @@ function occlusionStyle(plugin: PluginContext) {
|
||||
postprocessing: {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
occlusion: { name: 'on', params: {
|
||||
samples: 64,
|
||||
radius: 8,
|
||||
bias: 1.0,
|
||||
blurKernelSize: 13
|
||||
blurKernelSize: 15,
|
||||
multiScale: { name: 'off', params: {} },
|
||||
radius: 5,
|
||||
bias: 0.8,
|
||||
samples: 32,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
} },
|
||||
outline: { name: 'on', params: {
|
||||
scale: 1.0,
|
||||
threshold: 0.33
|
||||
} }
|
||||
threshold: 0.33,
|
||||
color: Color(0x0000),
|
||||
includeTransparent: true,
|
||||
} },
|
||||
shadow: { name: 'off', params: {} },
|
||||
}
|
||||
} });
|
||||
}
|
||||
@@ -200,14 +207,14 @@ const InteractionsPreset = StructureRepresentationPresetProvider({
|
||||
const components = {
|
||||
ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
|
||||
surroundings: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandSurroundings, `surroundings`),
|
||||
interactions: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandPlusSurroundings, `interactions`)
|
||||
interactions: await presetStaticComponent(plugin, structureCell, 'ligand'),
|
||||
};
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
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' }),
|
||||
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial, includeParent: true, parentDisplay: 'between' }, 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' }),
|
||||
};
|
||||
|
||||
@@ -229,7 +236,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);
|
||||
|
||||
280
src/apps/mesoscale-explorer/app.ts
Normal file
280
src/apps/mesoscale-explorer/app.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { renderReact18 } from '../../mol-plugin-ui/react18';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants';
|
||||
import { Backgrounds } from '../../extensions/backgrounds';
|
||||
import { LeftPanel, RightPanel } from './ui/panels';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { SpacefillRepresentationProvider } from '../../mol-repr/structure/representation/spacefill';
|
||||
import { PluginBehaviors } from '../../mol-plugin/behavior';
|
||||
import { MesoFocusLoci } from './behavior/camera';
|
||||
import { GraphicsMode, MesoscaleState } from './data/state';
|
||||
import { MesoSelectLoci } from './behavior/select';
|
||||
import { Transparency } from '../../mol-gl/webgl/render-item';
|
||||
import { LoadModel, loadExampleEntry, loadPdb, loadPdbDev, loadUrl, openState } from './ui/states';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { AnimateCameraSpin } from '../../mol-plugin-state/animation/built-in/camera-spin';
|
||||
import { AnimateCameraRock } from '../../mol-plugin-state/animation/built-in/camera-rock';
|
||||
import { AnimateStateSnapshots } from '../../mol-plugin-state/animation/built-in/state-snapshots';
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setDebugMode, setProductionMode, setTimingMode, consoleStats } from '../../mol-util/debug';
|
||||
|
||||
export type ExampleEntry = {
|
||||
id: string,
|
||||
label: string,
|
||||
url: string,
|
||||
type: 'molx' | 'molj' | 'cif' | 'bcif',
|
||||
description?: string,
|
||||
link?: string,
|
||||
}
|
||||
|
||||
export type MesoscaleExplorerState = {
|
||||
examples?: ExampleEntry[],
|
||||
graphicsMode: GraphicsMode,
|
||||
stateRef?: string,
|
||||
stateCache: { [k: string]: any },
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const Extensions = {
|
||||
'backgrounds': PluginSpec.Behavior(Backgrounds),
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export),
|
||||
};
|
||||
|
||||
const DefaultMesoscaleExplorerOptions = {
|
||||
customFormats: [] as [string, DataFormatProvider][],
|
||||
extensions: ObjectKeys(Extensions),
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: true,
|
||||
layoutShowRemoteState: true,
|
||||
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
|
||||
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,
|
||||
transparency: 'blended' as Transparency,
|
||||
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
|
||||
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
|
||||
powerPreference: PluginConfig.General.PowerPreference.defaultValue,
|
||||
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
|
||||
viewportShowSelectionMode: false,
|
||||
viewportShowAnimation: false,
|
||||
viewportShowTrajectoryControls: false,
|
||||
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,
|
||||
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
|
||||
|
||||
graphicsMode: 'quality' as GraphicsMode
|
||||
};
|
||||
type MesoscaleExplorerOptions = typeof DefaultMesoscaleExplorerOptions;
|
||||
|
||||
export class MesoscaleExplorer {
|
||||
constructor(public plugin: PluginUIContext) {
|
||||
}
|
||||
|
||||
async loadExample(id: string) {
|
||||
const entries = (this.plugin.customState as MesoscaleExplorerState).examples || [];
|
||||
const entry = entries.find(e => e.id === id);
|
||||
if (entry !== undefined) {
|
||||
await loadExampleEntry(this.plugin, entry);
|
||||
}
|
||||
}
|
||||
|
||||
async loadUrl(url: string, type: 'molx' | 'molj' | 'cif' | 'bcif') {
|
||||
await loadUrl(this.plugin, url, type);
|
||||
}
|
||||
|
||||
async loadPdb(id: string) {
|
||||
await loadPdb(this.plugin, id);
|
||||
}
|
||||
|
||||
async loadPdbDev(id: string) {
|
||||
await loadPdbDev(this.plugin, id);
|
||||
}
|
||||
|
||||
static async create(elementOrId: string | HTMLElement, options: Partial<MesoscaleExplorerOptions> = {}) {
|
||||
const definedOptions = {} as any;
|
||||
// filter for defined properies only so the default values
|
||||
// are property applied
|
||||
for (const p of Object.keys(options) as (keyof MesoscaleExplorerOptions)[]) {
|
||||
if (options[p] !== void 0) definedOptions[p] = options[p];
|
||||
}
|
||||
|
||||
const o: MesoscaleExplorerOptions = { ...DefaultMesoscaleExplorerOptions, ...definedOptions };
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const spec: PluginUISpec = {
|
||||
actions: defaultSpec.actions,
|
||||
behaviors: [
|
||||
PluginSpec.Behavior(PluginBehaviors.Camera.CameraAxisHelper),
|
||||
PluginSpec.Behavior(PluginBehaviors.Camera.CameraControls),
|
||||
|
||||
PluginSpec.Behavior(MesoFocusLoci),
|
||||
PluginSpec.Behavior(MesoSelectLoci),
|
||||
|
||||
...o.extensions.map(e => Extensions[e]),
|
||||
],
|
||||
animations: [
|
||||
AnimateCameraSpin,
|
||||
AnimateCameraRock,
|
||||
AnimateStateSnapshots,
|
||||
],
|
||||
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: {
|
||||
...defaultSpec.components?.controls,
|
||||
top: 'none',
|
||||
bottom: 'none',
|
||||
left: LeftPanel,
|
||||
right: RightPanel,
|
||||
},
|
||||
remoteState: 'none',
|
||||
},
|
||||
config: [
|
||||
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
|
||||
[PluginConfig.General.PixelScale, o.pixelScale],
|
||||
[PluginConfig.General.PickScale, o.pickScale],
|
||||
[PluginConfig.General.Transparency, o.transparency],
|
||||
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
|
||||
[PluginConfig.General.AllowMajorPerformanceCaveat, o.allowMajorPerformanceCaveat],
|
||||
[PluginConfig.General.PowerPreference, o.powerPreference],
|
||||
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
|
||||
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
|
||||
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
|
||||
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
|
||||
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
|
||||
[PluginConfig.Viewport.ShowTrajectoryControls, o.viewportShowTrajectoryControls],
|
||||
[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.Structure.SaccharideCompIdMapType, o.saccharideCompIdMapType],
|
||||
]
|
||||
};
|
||||
|
||||
const element = typeof elementOrId === 'string'
|
||||
? document.getElementById(elementOrId)
|
||||
: elementOrId;
|
||||
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
|
||||
|
||||
const plugin = await createPluginUI({
|
||||
target: element,
|
||||
spec,
|
||||
render: renderReact18,
|
||||
onBeforeUIRender: async plugin => {
|
||||
let examples: MesoscaleExplorerState['examples'] = undefined;
|
||||
try {
|
||||
examples = await plugin.fetch({ url: './examples/list.json', type: 'json' }).run();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
(plugin.customState as MesoscaleExplorerState) = {
|
||||
examples,
|
||||
graphicsMode: o.graphicsMode,
|
||||
stateCache: {},
|
||||
};
|
||||
|
||||
await MesoscaleState.init(plugin);
|
||||
}
|
||||
});
|
||||
|
||||
plugin.canvas3d?.setProps({
|
||||
renderer: {
|
||||
backgroundColor: Color(0x101010),
|
||||
},
|
||||
cameraFog: { name: 'off', params: {} },
|
||||
hiZ: { enabled: true },
|
||||
});
|
||||
|
||||
plugin.representation.structure.registry.clear();
|
||||
plugin.representation.structure.registry.add(SpacefillRepresentationProvider);
|
||||
|
||||
plugin.state.setSnapshotParams({
|
||||
image: true,
|
||||
componentManager: false,
|
||||
structureSelection: true,
|
||||
behavior: true,
|
||||
});
|
||||
|
||||
plugin.managers.lociLabels.clearProviders();
|
||||
|
||||
plugin.managers.dragAndDrop.addHandler('mesoscale-explorer', (files) => {
|
||||
const sessions = files.filter(f => {
|
||||
const fn = f.name.toLowerCase();
|
||||
return fn.endsWith('.molx') || fn.endsWith('.molj');
|
||||
});
|
||||
|
||||
if (sessions.length > 0) {
|
||||
openState(plugin, sessions[0]);
|
||||
} else {
|
||||
plugin.runTask(plugin.state.data.applyAction(LoadModel, {
|
||||
files: files.map(f => Asset.File(f)),
|
||||
}));
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
plugin.state.events.object.created.subscribe(e => {
|
||||
(plugin.customState as MesoscaleExplorerState).stateCache = {};
|
||||
});
|
||||
|
||||
plugin.state.events.object.removed.subscribe(e => {
|
||||
(plugin.customState as MesoscaleExplorerState).stateCache = {};
|
||||
});
|
||||
|
||||
return new MesoscaleExplorer(plugin);
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
this.plugin.layout.events.updated.next(void 0);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.plugin.dispose();
|
||||
}
|
||||
}
|
||||
75
src/apps/mesoscale-explorer/behavior/camera.ts
Normal file
75
src/apps/mesoscale-explorer/behavior/camera.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Loci } from '../../../mol-model/loci';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior';
|
||||
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
|
||||
import { Binding } from '../../../mol-util/binding';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
|
||||
const B = ButtonsType;
|
||||
const M = ModifiersKeys;
|
||||
const Trigger = Binding.Trigger;
|
||||
|
||||
const DefaultMesoFocusLociBindings = {
|
||||
clickCenter: Binding([
|
||||
Trigger(B.Flag.Primary, M.create()),
|
||||
], 'Camera center', 'Click element using ${triggers}'),
|
||||
clickCenterFocus: Binding([
|
||||
Trigger(B.Flag.Secondary, M.create()),
|
||||
], 'Camera center and focus', 'Click element using ${triggers}'),
|
||||
};
|
||||
const MesoFocusLociParams = {
|
||||
minRadius: PD.Numeric(8, { min: 1, max: 50, step: 1 }),
|
||||
extraRadius: PD.Numeric(4, { min: 1, max: 50, step: 1 }, { description: 'Value added to the bounding-sphere radius of the Loci' }),
|
||||
durationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'Camera transition duration' }),
|
||||
centerOnly: PD.Boolean(true, { description: 'Keep current camera distance' }),
|
||||
|
||||
bindings: PD.Value(DefaultMesoFocusLociBindings, { isHidden: true }),
|
||||
};
|
||||
type MesoFocusLociProps = PD.Values<typeof MesoFocusLociParams>
|
||||
|
||||
export const MesoFocusLoci = PluginBehavior.create<MesoFocusLociProps>({
|
||||
name: 'camera-meso-focus-loci',
|
||||
category: 'interaction',
|
||||
ctor: class extends PluginBehavior.Handler<MesoFocusLociProps> {
|
||||
register(): void {
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, button, modifiers }) => {
|
||||
const { canvas3d } = this.ctx;
|
||||
if (!canvas3d) return;
|
||||
|
||||
const loci = Loci.normalize(current.loci, this.ctx.managers.interactivity.props.granularity);
|
||||
const sphere = Loci.getBoundingSphere(loci) || Sphere3D();
|
||||
|
||||
const { clickCenter, clickCenterFocus } = this.params.bindings;
|
||||
const { durationMs, extraRadius, minRadius } = this.params;
|
||||
const radius = Math.max(sphere.radius + extraRadius, minRadius);
|
||||
|
||||
if (Binding.match(clickCenter, button, modifiers)) {
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
PluginCommands.Camera.Reset(this.ctx, { });
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshot = canvas3d.camera.getCenter(sphere.center);
|
||||
canvas3d.requestCameraReset({ durationMs, snapshot });
|
||||
} else if (Binding.match(clickCenterFocus, button, modifiers)) {
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
PluginCommands.Camera.Reset(this.ctx, { });
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshot = canvas3d.camera.getCenter(sphere.center, radius);
|
||||
canvas3d.requestCameraReset({ durationMs, snapshot });
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
params: () => MesoFocusLociParams,
|
||||
display: { name: 'Camera Meso Focus Loci on Canvas' }
|
||||
});
|
||||
164
src/apps/mesoscale-explorer/behavior/select.ts
Normal file
164
src/apps/mesoscale-explorer/behavior/select.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { EveryLoci, Loci } from '../../../mol-model/loci';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior';
|
||||
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
|
||||
import { Binding } from '../../../mol-util/binding';
|
||||
import { PluginStateObject as SO } from '../../../mol-plugin-state/objects';
|
||||
import { Structure, StructureElement } from '../../../mol-model/structure';
|
||||
import { StateSelection } from '../../../mol-state';
|
||||
import { StateTreeSpine } from '../../../mol-state/tree/spine';
|
||||
import { Representation } from '../../../mol-repr/representation';
|
||||
import { MarkerAction } from '../../../mol-util/marker-action';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
|
||||
const B = ButtonsType;
|
||||
const M = ModifiersKeys;
|
||||
const Trigger = Binding.Trigger;
|
||||
|
||||
const DefaultMesoSelectLociBindings = {
|
||||
clickToggleSelect: Binding([
|
||||
Trigger(B.Flag.Primary, M.create({ shift: true })),
|
||||
Trigger(B.Flag.Primary, M.create({ control: true })),
|
||||
], 'Toggle select', 'Click element using ${triggers}'),
|
||||
hoverHighlightOnly: Binding([
|
||||
Trigger(B.Flag.None, M.create({ shift: true })),
|
||||
Trigger(B.Flag.None, M.create({ control: true })),
|
||||
], 'Highlight', 'Hover element using ${triggers}'),
|
||||
};
|
||||
const MesoSelectLociParams = {
|
||||
bindings: PD.Value(DefaultMesoSelectLociBindings, { isHidden: true }),
|
||||
};
|
||||
type MesoSelectLociProps = PD.Values<typeof MesoSelectLociParams>
|
||||
|
||||
export const MesoSelectLoci = PluginBehavior.create<MesoSelectLociProps>({
|
||||
name: 'camera-meso-select-loci',
|
||||
category: 'interaction',
|
||||
ctor: class extends PluginBehavior.Handler<MesoSelectLociProps> {
|
||||
private spine: StateTreeSpine.Impl;
|
||||
private lociMarkProvider = (interactionLoci: Representation.Loci, action: MarkerAction) => {
|
||||
if (!this.ctx.canvas3d) return;
|
||||
this.ctx.canvas3d.mark(interactionLoci, action);
|
||||
};
|
||||
private applySelectMark(ref: string, clear?: boolean) {
|
||||
const cell = this.ctx.state.data.cells.get(ref);
|
||||
if (cell && SO.isRepresentation3D(cell.obj)) {
|
||||
this.spine.current = cell;
|
||||
const so = this.spine.getRootOfType(SO.Molecule.Structure);
|
||||
if (so) {
|
||||
if (clear) {
|
||||
this.lociMarkProvider({ loci: Structure.Loci(so.data) }, MarkerAction.Deselect);
|
||||
}
|
||||
const loci = this.ctx.managers.structure.selection.getLoci(so.data);
|
||||
this.lociMarkProvider({ loci }, MarkerAction.Select);
|
||||
}
|
||||
}
|
||||
}
|
||||
register(): void {
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, button, modifiers }) => {
|
||||
if (!this.ctx.canvas3d || this.ctx.isBusy) return;
|
||||
|
||||
const { clickToggleSelect } = this.params.bindings;
|
||||
if (Binding.match(clickToggleSelect, button, modifiers)) {
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
this.ctx.managers.interactivity.lociSelects.deselectAll();
|
||||
return;
|
||||
}
|
||||
|
||||
const loci = Loci.normalize(current.loci, modifiers.control ? 'entity' : 'chain');
|
||||
this.ctx.managers.interactivity.lociSelects.toggle({ loci }, false);
|
||||
}
|
||||
});
|
||||
this.ctx.managers.interactivity.lociSelects.addProvider(this.lociMarkProvider);
|
||||
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.hover, ({ current, button, modifiers }) => {
|
||||
if (!this.ctx.canvas3d || this.ctx.isBusy) return;
|
||||
|
||||
const pointerLock = !!this.ctx.canvas3dContext?.input.pointerLock;
|
||||
const { hoverHighlightOnly } = this.params.bindings;
|
||||
|
||||
if (!pointerLock && Binding.match(hoverHighlightOnly, button, modifiers)) {
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
this.ctx.managers.interactivity.lociHighlights.clearHighlights();
|
||||
return;
|
||||
}
|
||||
|
||||
if (modifiers.control) {
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci: EveryLoci }, false);
|
||||
} else {
|
||||
const loci = Loci.normalize(current.loci, 'chain');
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci }, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
this.ctx.behaviors.labels.highlight.next({ labels: [] });
|
||||
} else {
|
||||
const labels: string[] = [];
|
||||
if (StructureElement.Loci.is(current.loci)) {
|
||||
const cell = this.ctx.helpers.substructureParent.get(current.loci.structure);
|
||||
labels.push(cell?.obj?.label || 'Unknown');
|
||||
}
|
||||
this.ctx.behaviors.labels.highlight.next({ labels });
|
||||
}
|
||||
});
|
||||
this.ctx.managers.interactivity.lociHighlights.addProvider(this.lociMarkProvider);
|
||||
|
||||
let dimDisabled = false;
|
||||
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.keyReleased, ({ code, modifiers }) => {
|
||||
if (!this.ctx.canvas3d) return;
|
||||
|
||||
if ((code.startsWith('Shift') && !modifiers.control) || (code.startsWith('Control') && !modifiers.shift)) {
|
||||
if (dimDisabled) {
|
||||
dimDisabled = false;
|
||||
this.ctx.canvas3d?.setProps({ renderer: { dimStrength: 1 } }, true);
|
||||
}
|
||||
this.ctx.managers.interactivity.lociHighlights.clearHighlights();
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.key, ({ modifiers }) => {
|
||||
if (!this.ctx.canvas3d) return;
|
||||
|
||||
if (!dimDisabled && modifiers.control && modifiers.shift) {
|
||||
dimDisabled = true;
|
||||
this.ctx.canvas3d?.setProps({ renderer: { dimStrength: 0 } });
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribeObservable(this.ctx.state.events.object.created, ({ ref }) => this.applySelectMark(ref));
|
||||
|
||||
// re-apply select-mark to all representation of an updated structure
|
||||
this.subscribeObservable(this.ctx.state.events.object.updated, ({ ref, obj, oldObj, oldData, action }) => {
|
||||
const cell = this.ctx.state.data.cells.get(ref);
|
||||
if (cell && SO.Molecule.Structure.is(cell.obj)) {
|
||||
const structure: Structure = obj.data;
|
||||
const oldStructure: Structure | undefined = action === 'recreate' ? oldObj?.data :
|
||||
action === 'in-place' ? oldData : undefined;
|
||||
if (oldStructure &&
|
||||
Structure.areEquivalent(structure, oldStructure) &&
|
||||
Structure.areHierarchiesEqual(structure, oldStructure)) return;
|
||||
|
||||
const reprs = this.ctx.state.data.select(StateSelection.children(ref).ofType(SO.Molecule.Structure.Representation3D));
|
||||
for (const repr of reprs) this.applySelectMark(repr.transform.ref, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
unregister() {
|
||||
this.ctx.managers.interactivity.lociSelects.removeProvider(this.lociMarkProvider);
|
||||
this.ctx.managers.interactivity.lociHighlights.removeProvider(this.lociMarkProvider);
|
||||
}
|
||||
constructor(ctx: PluginContext, params: MesoSelectLociProps) {
|
||||
super(ctx, params);
|
||||
this.spine = new StateTreeSpine.Impl(ctx.state.data.cells);
|
||||
}
|
||||
},
|
||||
params: () => MesoSelectLociParams,
|
||||
display: { name: 'Camera Meso Select Loci on Canvas' }
|
||||
});
|
||||
178
src/apps/mesoscale-explorer/data/cellpack/model.ts
Normal file
178
src/apps/mesoscale-explorer/data/cellpack/model.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2023 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>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { SortedArray } from '../../../../mol-data/int';
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { Mat4 } from '../../../../mol-math/linear-algebra';
|
||||
import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry';
|
||||
import { CustomStructureProperty } from '../../../../mol-model-props/common/custom-structure-property';
|
||||
import { ElementIndex, EntityIndex, Model, Structure, Unit } from '../../../../mol-model/structure';
|
||||
import { Assembly, Symmetry } from '../../../../mol-model/structure/model/properties/symmetry';
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
|
||||
function createModelChainMap(model: Model) {
|
||||
const builder = new Structure.StructureBuilder();
|
||||
const units = new Map<string, Unit>();
|
||||
|
||||
const { label_asym_id, _rowCount } = model.atomicHierarchy.chains;
|
||||
const { offsets } = model.atomicHierarchy.chainAtomSegments;
|
||||
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
const elements = SortedArray.ofBounds(offsets[i] as ElementIndex, offsets[i + 1] as ElementIndex);
|
||||
const unit = builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements, Unit.Trait.FastBoundary);
|
||||
units.set(label_asym_id.value(i), unit);
|
||||
}
|
||||
|
||||
return units;
|
||||
}
|
||||
|
||||
function buildCellpackAssembly(model: Model, assembly: Assembly) {
|
||||
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } });
|
||||
const assembler = Structure.Builder({
|
||||
coordinateSystem,
|
||||
label: model.label,
|
||||
});
|
||||
|
||||
const units = createModelChainMap(model);
|
||||
|
||||
for (const g of assembly.operatorGroups) {
|
||||
for (const oper of g.operators) {
|
||||
for (const id of g.asymIds!) {
|
||||
const u = units.get(id);
|
||||
if (u) {
|
||||
assembler.addWithOperator(u, oper);
|
||||
} else {
|
||||
console.log(`missing asymId '${id}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return assembler.getStructure();
|
||||
}
|
||||
|
||||
export { CellpackAssembly };
|
||||
type CellpackAssembly = typeof CellpackAssembly
|
||||
const CellpackAssembly = PluginStateTransform.BuiltIn({
|
||||
name: 'cellpack-assembly',
|
||||
display: { name: 'Cellpack Assembly' },
|
||||
from: PSO.Molecule.Model,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: {
|
||||
id: PD.Text('', { label: 'Asm Id', description: 'Assembly Id (use empty for the 1st assembly)' }),
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
const model = a.data;
|
||||
|
||||
let id = params.id;
|
||||
let asm: Assembly | undefined = void 0;
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
|
||||
// if no id is specified, use the 1st assembly.
|
||||
if (!id && symmetry && symmetry.assemblies.length !== 0) {
|
||||
id = symmetry.assemblies[0].id;
|
||||
}
|
||||
|
||||
if (!symmetry || symmetry.assemblies.length === 0) {
|
||||
plugin.log.warn(`Model '${model.entryId}' has no assembly, returning model structure.`);
|
||||
} else {
|
||||
asm = Symmetry.findAssembly(model, id || '');
|
||||
if (!asm) {
|
||||
plugin.log.warn(`Model '${model.entryId}' has no assembly called '${id}', returning model structure.`);
|
||||
}
|
||||
}
|
||||
|
||||
const base = Structure.ofModel(model);
|
||||
if (!asm) {
|
||||
const label = { label: 'Model', description: Structure.elementDescription(base) };
|
||||
return new PSO.Molecule.Structure(base, label);
|
||||
}
|
||||
|
||||
const s = buildCellpackAssembly(model, asm);
|
||||
|
||||
const objProps = { label: `Assembly ${id}`, description: Structure.elementDescription(s) };
|
||||
return new PSO.Molecule.Structure(s, objProps);
|
||||
});
|
||||
},
|
||||
dispose({ b }) {
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
type UnitsByEntity = Map<EntityIndex, Unit[]>;
|
||||
const UnitsByEntity = CustomStructureProperty.createSimple<UnitsByEntity>('units_by_entity', 'root');
|
||||
|
||||
function getUnitsByEntity(structure: Structure): UnitsByEntity {
|
||||
if (UnitsByEntity.get(structure).value) {
|
||||
return UnitsByEntity.get(structure).value!;
|
||||
}
|
||||
|
||||
const atomicIndex = structure.model.atomicHierarchy.index;
|
||||
const map: UnitsByEntity = new Map();
|
||||
for (const ug of structure.unitSymmetryGroups) {
|
||||
const u = ug.units[0] as Unit.Atomic;
|
||||
const e = atomicIndex.getEntityFromChain(u.chainIndex[u.elements[0]]);
|
||||
|
||||
if (!map.has(e)) map.set(e, []);
|
||||
const entityUnits = map.get(e)!;
|
||||
|
||||
for (let i = 0, il = ug.units.length; i < il; ++i) {
|
||||
entityUnits.push(ug.units[i]);
|
||||
}
|
||||
}
|
||||
|
||||
UnitsByEntity.set(structure, { value: map }, map);
|
||||
return map;
|
||||
}
|
||||
|
||||
export { CellpackStructure };
|
||||
type CellpackStructure = typeof CellpackStructure
|
||||
const CellpackStructure = PluginStateTransform.BuiltIn({
|
||||
name: 'cellpack-structure',
|
||||
display: { name: 'Cellpack Structure' },
|
||||
from: PSO.Root,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: {
|
||||
structureRef: PD.Text(''),
|
||||
entityId: PD.Text('')
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params, dependencies }) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
const parent = dependencies![params.structureRef].data as Structure;
|
||||
const { entities } = parent.model;
|
||||
const idx = entities.getEntityIndex(params.entityId);
|
||||
|
||||
const unitsByEntity = getUnitsByEntity(parent);
|
||||
const units = unitsByEntity.get(idx) || [];
|
||||
// if (!unitsByEntity.get(idx)) {
|
||||
// console.log(entities.data.pdbx_description.value(idx));
|
||||
// }
|
||||
const structure = Structure.create(units);
|
||||
|
||||
const description = entities.data.pdbx_description.value(idx)[0] || 'model';
|
||||
const label = description.split('.').at(-1) || a.label;
|
||||
|
||||
return new PSO.Molecule.Structure(structure, { label, description: `${a.description}` });
|
||||
});
|
||||
},
|
||||
dispose({ b }) {
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
227
src/apps/mesoscale-explorer/data/cellpack/preset.ts
Normal file
227
src/apps/mesoscale-explorer/data/cellpack/preset.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2023 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 } from '../../../../mol-plugin-state/objects';
|
||||
import { StructureRepresentation3D } from '../../../../mol-plugin-state/transforms/representation';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/representation/spacefill';
|
||||
import { StateObjectRef, StateObjectSelector, StateBuilder } from '../../../../mol-state';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { ColorNames } from '../../../../mol-util/color/names';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getDistinctGroupColors, getGraphicsModeProps, getMesoscaleGroupParams } from '../state';
|
||||
import { CellpackAssembly, CellpackStructure } from './model';
|
||||
|
||||
function getSpacefillParams(color: Color, sizeFactor: number, graphics: GraphicsMode, merge?: boolean) {
|
||||
const gmp = getGraphicsModeProps(graphics === 'custom' ? 'quality' : graphics);
|
||||
return {
|
||||
type: {
|
||||
name: 'spacefill',
|
||||
params: {
|
||||
...SpacefillRepresentationProvider.defaultValues,
|
||||
ignoreHydrogens: false,
|
||||
instanceGranularity: true,
|
||||
ignoreLight: true,
|
||||
lodLevels: gmp.lodLevels,
|
||||
quality: 'lowest', // avoid 'auto', triggers boundary calc
|
||||
sizeFactor,
|
||||
clip: {
|
||||
variant: merge ? 'pixel' : 'instance',
|
||||
objects: [],
|
||||
},
|
||||
clipPrimitive: true,
|
||||
approximate: gmp.approximate,
|
||||
alphaThickness: gmp.alphaThickness,
|
||||
visuals: [merge ? 'structure-element-sphere' : 'element-sphere'],
|
||||
},
|
||||
},
|
||||
colorTheme: {
|
||||
name: 'uniform',
|
||||
params: {
|
||||
value: color,
|
||||
saturation: 0,
|
||||
lightness: 0,
|
||||
}
|
||||
},
|
||||
sizeTheme: {
|
||||
name: 'physical',
|
||||
params: {
|
||||
value: 1,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getSizeFactor(name: string): number {
|
||||
switch (name) {
|
||||
case 'dLDL':
|
||||
return 2.5;
|
||||
case 'iLDL':
|
||||
return 5;
|
||||
case 'NP_CA':
|
||||
case 'POL_CA':
|
||||
case 'FactorH1':
|
||||
case 'iIgM_Antibody_5mer':
|
||||
// case 'MG_271_272_273_274_192MER': // has a coarse and an atomic part
|
||||
return 2;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createCellpackHierarchy(plugin: PluginContext, trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory>) {
|
||||
const builder = plugin.builders.structure;
|
||||
const state = plugin.state.data;
|
||||
|
||||
const model = await builder.createModel(trajectory, { modelIndex: 0 });
|
||||
const entities = model.data!.entities.data;
|
||||
|
||||
const compGroups = new Map<string, StateObjectSelector>();
|
||||
const compIds = new Map<string, { idx: number, members: Map<string, number> }>();
|
||||
const compColors = new Map<string, Color[]>();
|
||||
|
||||
const funcGroups = new Map<string, StateObjectSelector>();
|
||||
const funcIds = new Map<string, { idx: number, size: number }>();
|
||||
const funcColors = new Map<string, Color[]>();
|
||||
|
||||
const graphicsMode = MesoscaleState.get(plugin).graphics;
|
||||
const groupParams = getMesoscaleGroupParams(graphicsMode);
|
||||
|
||||
const base = await state.build()
|
||||
.to(model)
|
||||
.apply(CellpackAssembly, { id: '' })
|
||||
.commit();
|
||||
|
||||
const compRoot = await state.build()
|
||||
.toRoot()
|
||||
.applyOrUpdateTagged('group:comp:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `comp:`, label: 'compartment', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: 'group:comp:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
const funcRoot = await state.build()
|
||||
.toRoot()
|
||||
.applyOrUpdateTagged('group:func:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `func:`, label: 'function', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: 'group:func:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
if (entities._rowCount > 1) {
|
||||
for (let i = 0; i < entities._rowCount; i++) {
|
||||
const description = entities.pdbx_description.value(i)[0] || 'unknown compartment';
|
||||
const d = description.split('.');
|
||||
const n = d.slice(0, -1).join('.');
|
||||
const l = d.at(-1)!;
|
||||
if (!compIds.has(n)) {
|
||||
compIds.set(n, { idx: compIds.size, members: new Map() });
|
||||
}
|
||||
const cm = compIds.get(n)!;
|
||||
cm.members.set(l, cm.members.size);
|
||||
|
||||
const f = entities.details.value(i) || 'unknown function';
|
||||
if (!funcIds.has(f)) {
|
||||
funcIds.set(f, { idx: funcIds.size, size: 0 });
|
||||
}
|
||||
funcIds.get(f)!.size += 1;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const baseCompColors = getDistinctBaseColors(compIds.size, 0);
|
||||
const compIdEntries = Array.from(compIds.entries());
|
||||
for (let i = 0; i < compIdEntries.length; ++i) {
|
||||
const [n, m] = compIdEntries[i];
|
||||
const groupColors = getDistinctGroupColors(m.members.size, baseCompColors[i], 20, 0);
|
||||
compColors.set(n, groupColors);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const baseFuncColors = getDistinctBaseColors(funcIds.size, 0);
|
||||
const funcIdEntries = Array.from(funcIds.entries());
|
||||
for (let i = 0; i < funcIdEntries.length; ++i) {
|
||||
const [n, m] = funcIdEntries[i];
|
||||
const groupColors = getDistinctGroupColors(m.size, baseFuncColors[i], 20, 0);
|
||||
funcColors.set(n, groupColors);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
for (let i = 0; i < entities._rowCount; i++) {
|
||||
const description = entities.pdbx_description.value(i)[0] || 'unknown compartment';
|
||||
const nodes = description.split('.');
|
||||
for (let j = 0, jl = nodes.length - 1; j < jl; ++j) {
|
||||
const n = nodes.slice(0, j + 1).join('.');
|
||||
const p = nodes.slice(0, j).join('.');
|
||||
if (!compGroups.has(n)) {
|
||||
const colorIdx = compIds.get(n)?.idx;
|
||||
const color = colorIdx !== undefined ? baseCompColors[colorIdx] : ColorNames.white;
|
||||
const label = nodes[j];
|
||||
const parent = compGroups.get(p) ?? compRoot;
|
||||
parent.cell!.state.isCollapsed = false;
|
||||
const group = await state.build()
|
||||
.to(parent)
|
||||
.applyOrUpdateTagged(`group:comp:${n}`, MesoscaleGroup, { ...groupParams, root: parent === compRoot, index: colorIdx, tag: `comp:${n}`, label, color: { type: 'generate', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: `comp:${p}`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit({ revertOnError: true });
|
||||
compGroups.set(n, group);
|
||||
}
|
||||
}
|
||||
|
||||
const f = entities.details.value(i) || 'unknown function';
|
||||
if (!funcGroups.has(f)) {
|
||||
const colorIdx = funcIds.get(f)?.idx;
|
||||
const color = colorIdx !== undefined ? baseFuncColors[colorIdx] : ColorNames.white;
|
||||
const group = await state.build()
|
||||
.to(funcRoot)
|
||||
.applyOrUpdateTagged(`group:func:${f}`, MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `func:${f}`, label: f, color: { type: 'custom', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: 'func:', state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit({ revertOnError: true });
|
||||
funcGroups.set(f, group);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
await state.transaction(async () => {
|
||||
try {
|
||||
const dependsOn = [base.ref];
|
||||
plugin.animationLoop.stop({ noDraw: true });
|
||||
let build: StateBuilder.Root | StateBuilder.To<any> = state.build();
|
||||
for (let i = 0; i < entities._rowCount; i++) {
|
||||
const description = entities.pdbx_description.value(i)[0] || 'model';
|
||||
const d = description.split('.');
|
||||
const n = d.slice(0, -1).join('.');
|
||||
const l = d.at(-1)!;
|
||||
|
||||
const f = entities.details.value(i) || 'unknown function';
|
||||
|
||||
const color = compColors.get(n)![compIds.get(n)!.members.get(l)!];
|
||||
const sizeFactor = getSizeFactor(l);
|
||||
|
||||
build = build
|
||||
.toRoot()
|
||||
.apply(CellpackStructure, { structureRef: base.ref, entityId: entities.id.value(i) }, { dependsOn })
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(color, sizeFactor, graphicsMode), { tags: [`comp:${n}`, `func:${f}`] });
|
||||
}
|
||||
await build.commit();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(e);
|
||||
} finally {
|
||||
plugin.animationLoop.start();
|
||||
}
|
||||
}).run();
|
||||
} else {
|
||||
const dependsOn = [base.ref];
|
||||
|
||||
const merge = (
|
||||
base.data &&
|
||||
base.data.model.entities.data._rowCount === 1 &&
|
||||
base.data.unitSymmetryGroups.length > 100 &&
|
||||
base.data.unitSymmetryGroups.some(usg => usg.units.length > 1)
|
||||
);
|
||||
|
||||
await state.build()
|
||||
.toRoot()
|
||||
.apply(CellpackStructure, { structureRef: base.ref, entityId: entities.id.value(0) }, { dependsOn })
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(ColorNames.lightgray, 1, graphicsMode, merge), { tags: [`comp:`, `func:`] })
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
139
src/apps/mesoscale-explorer/data/generic/model.ts
Normal file
139
src/apps/mesoscale-explorer/data/generic/model.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mat4 } from '../../../../mol-math/linear-algebra/3d/mat4';
|
||||
import { ElementIndex, Model, Structure, Unit } from '../../../../mol-model/structure';
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { StateObject, StateTransformer } from '../../../../mol-state';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { mergeUnits, partitionUnits } from '../util';
|
||||
import { Assembly, Symmetry } from '../../../../mol-model/structure/model/properties/symmetry';
|
||||
import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry';
|
||||
import { SortedArray } from '../../../../mol-data/int';
|
||||
import { GenericInstances, getTransforms } from './preset';
|
||||
import { Asset } from '../../../../mol-util/assets';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { deepEqual } from '../../../../mol-util';
|
||||
|
||||
function createModelChainMap(model: Model) {
|
||||
const builder = new Structure.StructureBuilder();
|
||||
const units = new Map<string, Unit>();
|
||||
|
||||
const { label_asym_id, _rowCount } = model.atomicHierarchy.chains;
|
||||
const { offsets } = model.atomicHierarchy.chainAtomSegments;
|
||||
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
const elements = SortedArray.ofBounds(offsets[i] as ElementIndex, offsets[i + 1] as ElementIndex);
|
||||
const unit = builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements, Unit.Trait.FastBoundary);
|
||||
units.set(label_asym_id.value(i), unit);
|
||||
}
|
||||
|
||||
return units;
|
||||
}
|
||||
|
||||
function buildAssembly(model: Model, assembly: Assembly) {
|
||||
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } });
|
||||
const assembler = Structure.Builder({
|
||||
coordinateSystem,
|
||||
label: model.label,
|
||||
});
|
||||
|
||||
const units = createModelChainMap(model);
|
||||
|
||||
for (const g of assembly.operatorGroups) {
|
||||
for (const oper of g.operators) {
|
||||
for (const id of g.asymIds!) {
|
||||
const u = units.get(id);
|
||||
if (u) {
|
||||
assembler.addWithOperator(u, oper);
|
||||
} else {
|
||||
console.log(`missing asymId '${id}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return assembler.getStructure();
|
||||
}
|
||||
|
||||
const EmptyInstances: GenericInstances<Asset> = {
|
||||
positions: { data: [] },
|
||||
rotations: { variant: 'euler', data: [] }
|
||||
};
|
||||
|
||||
export { StructureFromGeneric };
|
||||
type StructureFromGeneric = typeof StructureFromGeneric
|
||||
const StructureFromGeneric = PluginStateTransform.BuiltIn({
|
||||
name: 'structure-from-generic',
|
||||
display: { name: 'Structure from Generic', description: 'Create a molecular structure from Generic models.' },
|
||||
from: SO.Molecule.Model,
|
||||
to: SO.Molecule.Structure,
|
||||
params: {
|
||||
instances: PD.Value<GenericInstances<Asset>>(EmptyInstances),
|
||||
label: PD.Optional(PD.Text('')),
|
||||
cellSize: PD.Numeric(500, { min: 0, max: 10000, step: 100 }),
|
||||
}
|
||||
})({
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
const transforms = await getTransforms(plugin, params.instances);
|
||||
if (transforms.length === 0) return StateObject.Null;
|
||||
|
||||
const model = a.data;
|
||||
const label = params.label || model.label;
|
||||
|
||||
const base = Structure.ofModel(a.data);
|
||||
|
||||
let structure: Structure;
|
||||
if (transforms.length === 1 && Mat4.isIdentity(transforms[0])) {
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
const id = symmetry?.assemblies[0]?.id;
|
||||
const asm = Symmetry.findAssembly(model, id || '');
|
||||
if (asm) {
|
||||
structure = buildAssembly(model, asm);
|
||||
} else {
|
||||
const mergedUnits = partitionUnits(base.units, params.cellSize);
|
||||
structure = Structure.create(mergedUnits, { label });
|
||||
}
|
||||
} else {
|
||||
const assembler = Structure.Builder({ label });
|
||||
const unit = mergeUnits(base.units, 0);
|
||||
for (let i = 0, il = transforms.length; i < il; ++i) {
|
||||
const t = transforms[i];
|
||||
const op = SymmetryOperator.create(`op-${i}`, t);
|
||||
assembler.addWithOperator(unit, op);
|
||||
}
|
||||
structure = assembler.getStructure();
|
||||
}
|
||||
|
||||
const props = { label, description: Structure.elementDescription(structure) };
|
||||
return new SO.Molecule.Structure(structure, props);
|
||||
});
|
||||
},
|
||||
update({ newParams, oldParams }, plugin: PluginContext) {
|
||||
if (deepEqual(newParams, oldParams)) {
|
||||
return StateTransformer.UpdateResult.Unchanged;
|
||||
}
|
||||
|
||||
if (oldParams.instances) releaseInstances(plugin, oldParams.instances);
|
||||
return StateTransformer.UpdateResult.Recreate;
|
||||
},
|
||||
dispose({ b, params }, plugin: PluginContext) {
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
if (params?.instances) releaseInstances(plugin, params.instances);
|
||||
}
|
||||
});
|
||||
|
||||
function releaseInstances(plugin: PluginContext, instances: GenericInstances<Asset>) {
|
||||
if (!Array.isArray(instances.positions.data)) {
|
||||
plugin.managers.asset.release(instances.positions.data.file);
|
||||
}
|
||||
if (!Array.isArray(instances.rotations.data)) {
|
||||
plugin.managers.asset.release(instances.rotations.data.file);
|
||||
}
|
||||
}
|
||||
581
src/apps/mesoscale-explorer/data/generic/preset.ts
Normal file
581
src/apps/mesoscale-explorer/data/generic/preset.ts
Normal file
@@ -0,0 +1,581 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mat4 } from '../../../../mol-math/linear-algebra/3d/mat4';
|
||||
import { StateBuilder, StateObjectSelector } from '../../../../mol-state';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/representation/spacefill';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { utf8Read } from '../../../../mol-io/common/utf8';
|
||||
import { Mat3, Quat, Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getGraphicsModeProps, getMesoscaleGroupParams, updateColors } from '../state';
|
||||
import { ColorNames } from '../../../../mol-util/color/names';
|
||||
import { ShapeRepresentation3D, StructureRepresentation3D } from '../../../../mol-plugin-state/transforms/representation';
|
||||
import { ParseCif, ParsePly, ReadFile } from '../../../../mol-plugin-state/transforms/data';
|
||||
import { ModelFromTrajectory, ShapeFromPly, TrajectoryFromGRO, TrajectoryFromMOL, TrajectoryFromMOL2, TrajectoryFromMmCif, TrajectoryFromPDB, TrajectoryFromSDF, TrajectoryFromXYZ } from '../../../../mol-plugin-state/transforms/model';
|
||||
import { Euler } from '../../../../mol-math/linear-algebra/3d/euler';
|
||||
import { Asset } from '../../../../mol-util/assets';
|
||||
import { Clip } from '../../../../mol-util/clip';
|
||||
import { StructureFromGeneric } from './model';
|
||||
import { getFileNameInfo } from '../../../../mol-util/file-info';
|
||||
import { NumberArray } from '../../../../mol-util/type-helpers';
|
||||
import { BaseGeometry } from '../../../../mol-geo/geometry/base';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
|
||||
function getSpacefillParams(color: Color, sizeFactor: number, graphics: GraphicsMode, clipVariant: Clip.Variant) {
|
||||
const gmp = getGraphicsModeProps(graphics === 'custom' ? 'quality' : graphics);
|
||||
return {
|
||||
type: {
|
||||
name: 'spacefill',
|
||||
params: {
|
||||
...SpacefillRepresentationProvider.defaultValues,
|
||||
ignoreHydrogens: true,
|
||||
instanceGranularity: true,
|
||||
ignoreLight: true,
|
||||
lodLevels: gmp.lodLevels.map(l => {
|
||||
return {
|
||||
...l,
|
||||
stride: Math.max(1, Math.round(l.stride / Math.pow(sizeFactor, l.scaleBias)))
|
||||
};
|
||||
}),
|
||||
quality: 'lowest', // avoid 'auto', triggers boundary calc
|
||||
sizeFactor,
|
||||
clip: {
|
||||
variant: clipVariant,
|
||||
objects: [],
|
||||
},
|
||||
clipPrimitive: true,
|
||||
approximate: gmp.approximate,
|
||||
alphaThickness: gmp.alphaThickness,
|
||||
},
|
||||
},
|
||||
colorTheme: {
|
||||
name: 'uniform',
|
||||
params: {
|
||||
value: color,
|
||||
saturation: 0,
|
||||
lightness: 0,
|
||||
}
|
||||
},
|
||||
sizeTheme: {
|
||||
name: 'physical',
|
||||
params: {
|
||||
scale: 1,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getPlyShapeParams(color: Color, clipVariant: Clip.Variant) {
|
||||
return {
|
||||
...PD.getDefaultValues(BaseGeometry.Params),
|
||||
instanceGranularity: true,
|
||||
ignoreLight: true,
|
||||
clip: {
|
||||
variant: clipVariant,
|
||||
objects: [],
|
||||
},
|
||||
quality: 'custom',
|
||||
doubleSided: true,
|
||||
coloring: {
|
||||
name: 'uniform',
|
||||
params: { color }
|
||||
},
|
||||
grouping: {
|
||||
name: 'none',
|
||||
params: {}
|
||||
},
|
||||
material: {
|
||||
metalness: 0.0,
|
||||
roughness: 1.0,
|
||||
bumpiness: 1.0,
|
||||
},
|
||||
bumpAmplitude: 0.1,
|
||||
bumpFrequency: 0.1 / 10,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createGenericHierarchy(plugin: PluginContext, file: Asset.File) {
|
||||
const asset = await plugin.runTask(plugin.managers.asset.resolve(file, 'zip'));
|
||||
let manifest: GenericManifest;
|
||||
// TODO: remove special handling for martini prototype
|
||||
if (asset.data['instanced_structure.json']) {
|
||||
const d = asset.data['instanced_structure.json'];
|
||||
const t = utf8Read(d, 0, d.length);
|
||||
const martini = JSON.parse(t) as { model: string, positions: Vec3[], rotations: Vec3[], function: string }[];
|
||||
console.log(martini);
|
||||
manifest = martiniToGeneric(martini);
|
||||
} else if (asset.data['manifest.json']) {
|
||||
const d = asset.data['manifest.json'];
|
||||
const t = utf8Read(d, 0, d.length);
|
||||
manifest = JSON.parse(t) as GenericManifest;
|
||||
} else {
|
||||
throw new Error('no manifest found');
|
||||
}
|
||||
console.log(manifest);
|
||||
|
||||
const state = plugin.state.data;
|
||||
const graphicsMode = MesoscaleState.get(plugin).graphics;
|
||||
const groupParams = getMesoscaleGroupParams(graphicsMode);
|
||||
|
||||
async function addGroup(g: GenericGroup, cell: StateObjectSelector, parent: string) {
|
||||
const group = await state.build()
|
||||
.to(cell)
|
||||
.apply(MesoscaleGroup, { ...groupParams, index: undefined, tag: `${g.root}:${g.id}`, label: g.label || g.id, color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: [`group:${g.root}:${g.id}`, g.root === parent ? `${g.root}:` : `${g.root}:${parent}`], state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
if (g.children) {
|
||||
for (const c of g.children) {
|
||||
await addGroup(c, group, g.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const r of manifest.roots) {
|
||||
const root = await state.build()
|
||||
.toRoot()
|
||||
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `${r.id}:`, label: r.label || r.id, color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: `group:${r.id}:`, state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
if (r.children) {
|
||||
for (const c of r.children!) {
|
||||
await addGroup(c, root, r.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const transformAssets = new Map<string, Asset>();
|
||||
const getTransformAsset = (file: string) => {
|
||||
if (!transformAssets.has(file)) {
|
||||
const d = asset.data[file];
|
||||
transformAssets.set(file, Asset.File(new File([d], file)));
|
||||
}
|
||||
return transformAssets.get(file)!;
|
||||
};
|
||||
|
||||
const getAssetInstances = (instances: GenericInstances<string>): GenericInstances<Asset> => {
|
||||
return {
|
||||
positions: {
|
||||
data: Array.isArray(instances.positions.data)
|
||||
? instances.positions.data
|
||||
: {
|
||||
file: getTransformAsset(instances.positions.data.file),
|
||||
view: instances.positions.data.view,
|
||||
},
|
||||
type: instances.positions.type,
|
||||
},
|
||||
rotations: {
|
||||
data: Array.isArray(instances.rotations.data)
|
||||
? instances.rotations.data
|
||||
: {
|
||||
file: getTransformAsset(instances.rotations.data.file),
|
||||
view: instances.rotations.data.view,
|
||||
},
|
||||
variant: instances.rotations.variant,
|
||||
type: instances.rotations.type,
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
await state.transaction(async () => {
|
||||
try {
|
||||
plugin.animationLoop.stop({ noDraw: true });
|
||||
let build: StateBuilder.Root | StateBuilder.To<any> = state.build();
|
||||
for (const ent of manifest.entities) {
|
||||
const d = asset.data[ent.file];
|
||||
const info = getFileNameInfo(ent.file);
|
||||
const isBinary = ['bcif'].includes(info.ext);
|
||||
|
||||
const t = isBinary ? d : utf8Read(d, 0, d.length);
|
||||
const file = Asset.File(new File([t], ent.file));
|
||||
|
||||
const color = ColorNames.skyblue;
|
||||
const label = ent.label || ent.file.split('.')[0];
|
||||
const sizeFactor = ent.sizeFactor || 1;
|
||||
const tags = ent.groups.map(({ id, root }) => `${root}:${id}`);
|
||||
const instances = ent.instances && getAssetInstances(ent.instances);
|
||||
|
||||
build = build
|
||||
.toRoot()
|
||||
.apply(ReadFile, { file, label, isBinary });
|
||||
|
||||
if (['gro', 'cif', 'mmcif', 'mcif', 'bcif', 'pdb', 'ent', 'xyz', 'mol', 'sdf', 'sd', 'mol2'].includes(info.ext)) {
|
||||
if (['gro'].includes(info.ext)) {
|
||||
build = build.apply(TrajectoryFromGRO);
|
||||
} else if (['cif', 'mmcif', 'mcif', 'bcif'].includes(info.ext)) {
|
||||
build = build.apply(ParseCif).apply(TrajectoryFromMmCif);
|
||||
} else if (['pdb', 'ent'].includes(info.ext)) {
|
||||
build = build.apply(TrajectoryFromPDB);
|
||||
} else if (['xyz'].includes(info.ext)) {
|
||||
build = build.apply(TrajectoryFromXYZ);
|
||||
} else if (['mol'].includes(info.ext)) {
|
||||
build = build.apply(TrajectoryFromMOL);
|
||||
} else if (['sdf', 'sd'].includes(info.ext)) {
|
||||
build = build.apply(TrajectoryFromSDF);
|
||||
} else if (['mol2'].includes(info.ext)) {
|
||||
build = build.apply(TrajectoryFromMOL2);
|
||||
}
|
||||
|
||||
let clipVariant: Clip.Variant = 'pixel';
|
||||
if (ent.instances) {
|
||||
if (Array.isArray(ent.instances.positions.data)) {
|
||||
clipVariant = ent.instances.positions.data.length <= 3 ? 'pixel' : 'instance';
|
||||
} else {
|
||||
const byteLength = ent.instances.positions.data.view
|
||||
? ent.instances.positions.data.view.byteLength
|
||||
: asset.data[ent.instances.positions.data.file].length;
|
||||
clipVariant = byteLength <= 3 * 4 ? 'pixel' : 'instance';
|
||||
}
|
||||
}
|
||||
|
||||
build = build
|
||||
.apply(ModelFromTrajectory, { modelIndex: 0 })
|
||||
.apply(StructureFromGeneric, { instances, label })
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(color, sizeFactor, graphicsMode, clipVariant), { tags });
|
||||
} else if (['ply'].includes(info.ext)) {
|
||||
if (['ply'].includes(info.ext)) {
|
||||
const transforms = await getTransforms(plugin, instances);
|
||||
const clipVariant = transforms.length === 1 ? 'pixel' : 'instance';
|
||||
build = build
|
||||
.apply(ParsePly)
|
||||
.apply(ShapeFromPly, { label, transforms })
|
||||
.apply(ShapeRepresentation3D, getPlyShapeParams(color, clipVariant), { tags });
|
||||
}
|
||||
} else {
|
||||
console.warn(`unknown file format '${info.ext}'`);
|
||||
}
|
||||
}
|
||||
await build.commit();
|
||||
|
||||
const rootId = `${manifest.roots[0].id}:`;
|
||||
const values = { type: 'group-generate', value: ColorNames.white, lightness: 0, alpha: 1 };
|
||||
await updateColors(plugin, values, rootId, '');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(e);
|
||||
} finally {
|
||||
plugin.animationLoop.start();
|
||||
}
|
||||
}).run();
|
||||
|
||||
asset.dispose();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type GenericRoot = {
|
||||
id: string
|
||||
label?: string
|
||||
description?: string
|
||||
children: GenericGroup[]
|
||||
}
|
||||
|
||||
type GenericGroup = {
|
||||
id: string
|
||||
/** reference to `${GenericRoot.id}` */
|
||||
root: string
|
||||
label?: string
|
||||
description?: string
|
||||
children?: GenericGroup[]
|
||||
}
|
||||
|
||||
type GenericEntity = {
|
||||
/**
|
||||
* the entity file name
|
||||
*
|
||||
* the following extensions/formats are supported
|
||||
*
|
||||
* structures
|
||||
* - gro
|
||||
* - cif, mmcif, mcif, bcif
|
||||
* - pdb, ent
|
||||
* - xyz
|
||||
* - mol
|
||||
* - sdf, sd
|
||||
* - mol2
|
||||
*
|
||||
* meshes
|
||||
* - ply
|
||||
*/
|
||||
file: string
|
||||
label?: string
|
||||
description?: string
|
||||
groups: {
|
||||
/** reference to `${GenericGroup.id}` */
|
||||
id: string,
|
||||
/** reference to `${GenericGroup.root}` */
|
||||
root: string
|
||||
}[]
|
||||
/**
|
||||
* defaults to a single, untransformed instance
|
||||
*/
|
||||
instances?: GenericInstances<string>
|
||||
/**
|
||||
* defaults to 1 (assuming fully atomic structures)
|
||||
* for C-alpha only structures set to 2
|
||||
* for Martini coarse-grained set to 1.5
|
||||
*/
|
||||
sizeFactor?: number
|
||||
}
|
||||
|
||||
type BinaryData<T extends string | Asset> = {
|
||||
file: T,
|
||||
view?: {
|
||||
byteOffset: number,
|
||||
byteLength: number
|
||||
}
|
||||
}
|
||||
|
||||
export type GenericInstances<T extends string | Asset> = {
|
||||
/**
|
||||
* translation vectors in Angstrom
|
||||
* [x0, y0, z0, ..., xn, yn, zn] with n = count - 1
|
||||
*/
|
||||
positions: {
|
||||
/**
|
||||
* either the data itself or a pointer to binary data
|
||||
*/
|
||||
data: number[] | BinaryData<T>
|
||||
/**
|
||||
* how to interpret the data
|
||||
* defaults to `{ kind: 'Array', type: 'Float32' }`
|
||||
*/
|
||||
type?: { kind: 'Array', type: 'Float32' }
|
||||
// TODO: maybe worthwhile in the future, mirroring encoders from BinaryCIF
|
||||
// | { kind: 'IntegerPackedFixedPoint', byteCount: number, srcSize: number, factor: number, srcType: 'Float32' }
|
||||
}
|
||||
/**
|
||||
* euler angles in XYZ order
|
||||
* [x0, y0, z0, ..., xn, yn, zn] with n = count - 1
|
||||
*
|
||||
* quaternion rotations in XYZW order
|
||||
* [x0, y0, z0, w0, ..., xn, yn, zn, wn] with n = count - 1
|
||||
*
|
||||
* rotation matrices in row-major order
|
||||
* [m00_0, m01_0, m02_0, ..., m20_n, m21_n, m22_n] with n = count - 1
|
||||
*/
|
||||
rotations: {
|
||||
variant: 'euler' | 'quaternion' | 'matrix',
|
||||
/**
|
||||
* either the data itself or a pointer to binary data
|
||||
*/
|
||||
data: number[] | BinaryData<T>
|
||||
/**
|
||||
* how to interpret the data
|
||||
* defaults to `{ kind: 'Array', type: 'Float32' }`
|
||||
*/
|
||||
type?: { kind: 'Array', type: 'Float32' }
|
||||
}
|
||||
}
|
||||
|
||||
type GenericFrame = {
|
||||
time: number
|
||||
entities: {
|
||||
file: string
|
||||
instances: GenericInstances<string>
|
||||
}[]
|
||||
}
|
||||
|
||||
type GenericTrajectory = {
|
||||
label?: string
|
||||
description?: string
|
||||
frames: GenericFrame[]
|
||||
}
|
||||
|
||||
type GenericManifest = {
|
||||
label?: string
|
||||
description?: string
|
||||
roots: GenericRoot[]
|
||||
entities: GenericEntity[]
|
||||
trajectories?: GenericTrajectory[]
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const p = Vec3();
|
||||
const q = Quat();
|
||||
const m = Mat3();
|
||||
const e = Euler();
|
||||
|
||||
async function getPositions(plugin: PluginContext, p: GenericInstances<Asset>['positions']): Promise<NumberArray> {
|
||||
if (Array.isArray(p.data)) {
|
||||
return p.data;
|
||||
} else {
|
||||
const a = await plugin.runTask(plugin.managers.asset.resolve(p.data.file, 'binary'));
|
||||
const o = p.data.view?.byteOffset || 0;
|
||||
const l = p.data.view?.byteLength || a.data.byteLength;
|
||||
return new Float32Array(a.data.buffer, o + a.data.byteOffset, l / 4);
|
||||
}
|
||||
};
|
||||
|
||||
async function getRotations(plugin: PluginContext, r: GenericInstances<Asset>['rotations']): Promise<NumberArray> {
|
||||
if (Array.isArray(r.data)) {
|
||||
return r.data;
|
||||
} else {
|
||||
const a = await plugin.runTask(plugin.managers.asset.resolve(r.data.file, 'binary'));
|
||||
const o = r.data.view?.byteOffset || 0;
|
||||
const l = r.data.view?.byteLength || a.data.byteLength;
|
||||
return new Float32Array(a.data.buffer, o + a.data.byteOffset, l / 4);
|
||||
}
|
||||
};
|
||||
|
||||
export async function getTransforms(plugin: PluginContext, instances?: GenericInstances<Asset>) {
|
||||
const transforms: Mat4[] = [];
|
||||
if (instances) {
|
||||
const positions = await getPositions(plugin, instances.positions);
|
||||
const rotations = await getRotations(plugin, instances.rotations);
|
||||
|
||||
for (let i = 0, il = positions.length / 3; i < il; ++i) {
|
||||
Vec3.fromArray(p, positions, i * 3);
|
||||
if (instances.rotations.variant === 'matrix') {
|
||||
Mat3.fromArray(m, rotations, i * 9);
|
||||
const t = Mat4.fromMat3(Mat4(), m);
|
||||
Mat4.setTranslation(t, p);
|
||||
transforms.push(t);
|
||||
} else if (instances.rotations.variant === 'quaternion') {
|
||||
Quat.fromArray(q, rotations, i * 4);
|
||||
const t = Mat4.fromQuat(Mat4(), q);
|
||||
Mat4.setTranslation(t, p);
|
||||
transforms.push(t);
|
||||
} else if (instances.rotations.variant === 'euler') {
|
||||
Euler.fromArray(e, rotations, i * 3);
|
||||
Quat.fromEuler(q, e, 'XYZ');
|
||||
const t = Mat4.fromQuat(Mat4(), q);
|
||||
Mat4.setTranslation(t, p);
|
||||
transforms.push(t);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
transforms.push(Mat4.identity());
|
||||
}
|
||||
|
||||
return transforms;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type MartiniManifest = {
|
||||
model: string,
|
||||
positions: Vec3[],
|
||||
rotations: Vec3[],
|
||||
function: string
|
||||
}[]
|
||||
|
||||
function martiniToGeneric(martini: MartiniManifest): GenericManifest {
|
||||
const functionRoot: GenericRoot = {
|
||||
id: 'function',
|
||||
label: 'Function',
|
||||
description: 'Functional classification',
|
||||
children: [],
|
||||
};
|
||||
const entities: GenericEntity[] = [];
|
||||
|
||||
const seenGroups = new Set<string>();
|
||||
|
||||
const membraneGroup = {
|
||||
id: 'membane',
|
||||
root: 'function',
|
||||
label: 'Membrane',
|
||||
children: [] as GenericGroup[],
|
||||
};
|
||||
functionRoot.children!.push(membraneGroup);
|
||||
seenGroups.add(membraneGroup.id);
|
||||
|
||||
const lipidsGroup = {
|
||||
id: 'lipid',
|
||||
root: 'function',
|
||||
label: 'Lipid',
|
||||
children: [] as GenericGroup[],
|
||||
};
|
||||
membraneGroup.children!.push(lipidsGroup);
|
||||
seenGroups.add(lipidsGroup.id);
|
||||
|
||||
const upperGroup = {
|
||||
id: 'upper',
|
||||
root: 'function',
|
||||
label: 'Upper Leaflet',
|
||||
};
|
||||
lipidsGroup.children!.push(upperGroup);
|
||||
seenGroups.add(upperGroup.id);
|
||||
|
||||
const lowerGroup = {
|
||||
id: 'lower',
|
||||
root: 'function',
|
||||
label: 'Lower Leaflet',
|
||||
};
|
||||
lipidsGroup.children!.push(lowerGroup);
|
||||
seenGroups.add(lowerGroup.id);
|
||||
|
||||
const memprotGroup = {
|
||||
id: 'memprot',
|
||||
root: 'function',
|
||||
label: 'Transmembrane Protein',
|
||||
};
|
||||
membraneGroup.children!.push(memprotGroup);
|
||||
seenGroups.add(memprotGroup.id);
|
||||
|
||||
for (const e of martini) {
|
||||
const label = e.model.split('.')[0];
|
||||
const group = e.function || 'Metabolite';
|
||||
|
||||
const positions = {
|
||||
data: e.positions.flat().map(x => Math.round((x * 10) * 100) / 100)
|
||||
};
|
||||
const rotations = {
|
||||
data: e.rotations.flat().map(x => Math.round(x * 100) / 100),
|
||||
variant: 'euler' as const,
|
||||
};
|
||||
|
||||
if (group.includes('lower leaflet')) {
|
||||
entities.push({
|
||||
file: e.model,
|
||||
label: label.substring(15),
|
||||
groups: [{ root: 'function', id: 'lower' }],
|
||||
instances: { positions, rotations },
|
||||
sizeFactor: 1.5,
|
||||
});
|
||||
} else if (group.includes('upper leaflet')) {
|
||||
entities.push({
|
||||
file: e.model,
|
||||
label: label.substring(15),
|
||||
groups: [{ root: 'function', id: 'upper' }],
|
||||
instances: { positions, rotations },
|
||||
sizeFactor: 1.5,
|
||||
});
|
||||
} else if (group.length === 4) {
|
||||
entities.push({
|
||||
file: e.model,
|
||||
label: label.substring(17),
|
||||
groups: [{ root: 'function', id: 'memprot' }],
|
||||
instances: { positions, rotations },
|
||||
sizeFactor: 1.5,
|
||||
});
|
||||
} else {
|
||||
if (!seenGroups.has(group)) {
|
||||
functionRoot.children!.push({
|
||||
id: group,
|
||||
root: 'function',
|
||||
label: group,
|
||||
});
|
||||
seenGroups.add(group);
|
||||
}
|
||||
entities.push({
|
||||
file: e.model,
|
||||
label,
|
||||
groups: [{ root: 'function', id: group }],
|
||||
instances: { positions, rotations },
|
||||
sizeFactor: 1.5,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'Martini',
|
||||
description: 'Martini coarse-grained model',
|
||||
roots: [functionRoot],
|
||||
entities,
|
||||
};
|
||||
}
|
||||
202
src/apps/mesoscale-explorer/data/mmcif/model.ts
Normal file
202
src/apps/mesoscale-explorer/data/mmcif/model.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { SortedArray } from '../../../../mol-data/int';
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { Mat4 } from '../../../../mol-math/linear-algebra';
|
||||
import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry';
|
||||
import { CustomStructureProperty } from '../../../../mol-model-props/common/custom-structure-property';
|
||||
import { ElementIndex, EntityIndex, Model, Structure, Unit } from '../../../../mol-model/structure';
|
||||
import { Assembly, Symmetry } from '../../../../mol-model/structure/model/properties/symmetry';
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { StateTransformer } from '../../../../mol-state/transformer';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { deepEqual } from '../../../../mol-util';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { partitionUnits } from '../util';
|
||||
|
||||
function createModelChainMap(model: Model) {
|
||||
const builder = new Structure.StructureBuilder();
|
||||
const units = new Map<string, Unit>();
|
||||
|
||||
const { label_asym_id, _rowCount } = model.atomicHierarchy.chains;
|
||||
const { offsets } = model.atomicHierarchy.chainAtomSegments;
|
||||
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
const elements = SortedArray.ofBounds(offsets[i] as ElementIndex, offsets[i + 1] as ElementIndex);
|
||||
const unit = builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements, Unit.Trait.FastBoundary);
|
||||
units.set(label_asym_id.value(i), unit);
|
||||
}
|
||||
|
||||
return units;
|
||||
}
|
||||
|
||||
function buildAssembly(model: Model, assembly: Assembly) {
|
||||
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } });
|
||||
const assembler = Structure.Builder({
|
||||
coordinateSystem,
|
||||
label: model.label,
|
||||
});
|
||||
|
||||
const units = createModelChainMap(model);
|
||||
|
||||
for (const g of assembly.operatorGroups) {
|
||||
for (const oper of g.operators) {
|
||||
for (const id of g.asymIds!) {
|
||||
const u = units.get(id);
|
||||
if (u) {
|
||||
assembler.addWithOperator(u, oper);
|
||||
} else {
|
||||
console.log(`missing asymId '${id}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return assembler.getStructure();
|
||||
}
|
||||
|
||||
export { MmcifAssembly };
|
||||
type MmcifAssembly = typeof MmcifAssembly
|
||||
const MmcifAssembly = PluginStateTransform.BuiltIn({
|
||||
name: 'mmcif-assembly',
|
||||
display: { name: 'Mmcif Assembly' },
|
||||
from: PSO.Molecule.Model,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: {
|
||||
id: PD.Text('', { label: 'Asm Id', description: 'Assembly Id (use empty for the 1st assembly)' }),
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
const model = a.data;
|
||||
|
||||
let id = params.id;
|
||||
let asm: Assembly | undefined = void 0;
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
|
||||
// if no id is specified, use the 1st assembly.
|
||||
if (!id && symmetry && symmetry.assemblies.length !== 0) {
|
||||
id = symmetry.assemblies[0].id;
|
||||
}
|
||||
|
||||
if (!symmetry || symmetry.assemblies.length === 0) {
|
||||
plugin.log.warn(`Model '${model.entryId}' has no assembly, returning model structure.`);
|
||||
} else {
|
||||
asm = Symmetry.findAssembly(model, id || '');
|
||||
if (!asm) {
|
||||
plugin.log.warn(`Model '${model.entryId}' has no assembly called '${id}', returning model structure.`);
|
||||
}
|
||||
}
|
||||
|
||||
const base = Structure.ofModel(model);
|
||||
if (!asm) {
|
||||
const label = { label: 'Model', description: Structure.elementDescription(base) };
|
||||
return new PSO.Molecule.Structure(base, label);
|
||||
}
|
||||
|
||||
const s = buildAssembly(model, asm);
|
||||
|
||||
const objProps = { label: `Assembly ${id}`, description: Structure.elementDescription(s) };
|
||||
return new PSO.Molecule.Structure(s, objProps);
|
||||
});
|
||||
},
|
||||
update({ newParams, oldParams }) {
|
||||
return deepEqual(newParams, oldParams)
|
||||
? StateTransformer.UpdateResult.Unchanged
|
||||
: StateTransformer.UpdateResult.Recreate;
|
||||
},
|
||||
dispose({ b }) {
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
type UnitsByEntity = Map<EntityIndex, Unit[]>;
|
||||
const UnitsByEntity = CustomStructureProperty.createSimple<UnitsByEntity>('units_by_entity', 'root');
|
||||
|
||||
function getUnitsByEntity(structure: Structure): UnitsByEntity {
|
||||
if (UnitsByEntity.get(structure).value) {
|
||||
return UnitsByEntity.get(structure).value!;
|
||||
}
|
||||
|
||||
const atomicIndex = structure.model.atomicHierarchy.index;
|
||||
const spheresIndex = structure.model.coarseHierarchy.spheres;
|
||||
const map: UnitsByEntity = new Map();
|
||||
for (const ug of structure.unitSymmetryGroups) {
|
||||
const u = ug.units[0];
|
||||
let e: EntityIndex;
|
||||
if (Unit.isAtomic(u)) {
|
||||
e = atomicIndex.getEntityFromChain(u.chainIndex[u.elements[0]]);
|
||||
} else if (Unit.isSpheres(u)) {
|
||||
e = spheresIndex.getEntityFromChain(u.coarseElements.chainElementSegments.index[u.elements[0]]);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!map.has(e)) map.set(e, []);
|
||||
const entityUnits = map.get(e)!;
|
||||
|
||||
for (let i = 0, il = ug.units.length; i < il; ++i) {
|
||||
entityUnits.push(ug.units[i]);
|
||||
}
|
||||
}
|
||||
|
||||
UnitsByEntity.set(structure, { value: map }, map);
|
||||
return map;
|
||||
}
|
||||
|
||||
export { MmcifStructure };
|
||||
type MmcifStructure = typeof MmcifStructure
|
||||
const MmcifStructure = PluginStateTransform.BuiltIn({
|
||||
name: 'mmcif-structure',
|
||||
display: { name: 'Mmcif Structure' },
|
||||
from: PSO.Root,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: {
|
||||
structureRef: PD.Text(''),
|
||||
entityId: PD.Text(''),
|
||||
cellSize: PD.Numeric(500, { min: 0, max: 10000, step: 100 }),
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params, dependencies }) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
const parent = dependencies![params.structureRef].data as Structure;
|
||||
const { entities } = parent.model;
|
||||
const idx = entities.getEntityIndex(params.entityId);
|
||||
|
||||
const unitsByEntity = getUnitsByEntity(parent);
|
||||
const units = unitsByEntity.get(idx) || [];
|
||||
const unitCount = units.length;
|
||||
|
||||
let structure: Structure;
|
||||
if (unitCount > 1 && units.every(u => u.conformation.operator.isIdentity)) {
|
||||
const mergedUnits = partitionUnits(units, params.cellSize);
|
||||
structure = Structure.create(mergedUnits);
|
||||
} else {
|
||||
structure = Structure.create(units);
|
||||
}
|
||||
|
||||
const label = entities.data.pdbx_description.value(idx).join(', ') || 'model';
|
||||
return new PSO.Molecule.Structure(structure, { label, description: `${a.description}` });
|
||||
});
|
||||
},
|
||||
update({ newParams, oldParams }) {
|
||||
return deepEqual(newParams, oldParams)
|
||||
? StateTransformer.UpdateResult.Unchanged
|
||||
: StateTransformer.UpdateResult.Recreate;
|
||||
},
|
||||
dispose({ b }) {
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
182
src/apps/mesoscale-explorer/data/mmcif/preset.ts
Normal file
182
src/apps/mesoscale-explorer/data/mmcif/preset.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { PluginStateObject } from '../../../../mol-plugin-state/objects';
|
||||
import { StructureRepresentation3D } from '../../../../mol-plugin-state/transforms/representation';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/representation/spacefill';
|
||||
import { StateObjectRef, StateObjectSelector, StateBuilder } from '../../../../mol-state';
|
||||
import { Clip } from '../../../../mol-util/clip';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { ColorNames } from '../../../../mol-util/color/names';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getDistinctGroupColors, getGraphicsModeProps, getMesoscaleGroupParams } from '../state';
|
||||
import { MmcifAssembly, MmcifStructure } from './model';
|
||||
|
||||
function getSpacefillParams(color: Color, scaleFactor: number, graphics: GraphicsMode, clipVariant: Clip.Variant) {
|
||||
const gmp = getGraphicsModeProps(graphics === 'custom' ? 'quality' : graphics);
|
||||
return {
|
||||
type: {
|
||||
name: 'spacefill',
|
||||
params: {
|
||||
...SpacefillRepresentationProvider.defaultValues,
|
||||
ignoreHydrogens: false,
|
||||
instanceGranularity: false,
|
||||
ignoreLight: true,
|
||||
lodLevels: gmp.lodLevels.map(l => {
|
||||
return {
|
||||
...l,
|
||||
stride: Math.max(1, Math.round(l.stride / Math.pow(scaleFactor, l.scaleBias)))
|
||||
};
|
||||
}),
|
||||
quality: 'lowest', // avoid 'auto', triggers boundary calc
|
||||
clip: {
|
||||
variant: clipVariant,
|
||||
objects: [],
|
||||
},
|
||||
clipPrimitive: true,
|
||||
approximate: gmp.approximate,
|
||||
alphaThickness: gmp.alphaThickness,
|
||||
},
|
||||
},
|
||||
colorTheme: {
|
||||
name: 'uniform',
|
||||
params: {
|
||||
value: color,
|
||||
saturation: 0,
|
||||
lightness: 0,
|
||||
}
|
||||
},
|
||||
sizeTheme: {
|
||||
name: 'physical',
|
||||
params: {
|
||||
value: 1,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function createMmcifHierarchy(plugin: PluginContext, trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory>) {
|
||||
const builder = plugin.builders.structure;
|
||||
const state = plugin.state.data;
|
||||
|
||||
const model = await builder.createModel(trajectory, { modelIndex: 0 });
|
||||
const { data: entities, subtype } = model.data!.entities;
|
||||
|
||||
const sd = model.data?.sourceData;
|
||||
if (MmcifFormat.is(sd)) {
|
||||
const pdbId = sd.data.db.struct.entry_id.value(0);
|
||||
MesoscaleState.set(plugin, {
|
||||
description: sd.data.db.struct.title.value(0),
|
||||
link: pdbId ? `https://www.rcsb.org/structure/${pdbId}` : ''
|
||||
});
|
||||
}
|
||||
|
||||
const spheresAvgRadius = new Map<string, number>();
|
||||
if (model.data!.coarseHierarchy.isDefined) {
|
||||
const spheresCount = new Map<string, number>();
|
||||
const spheresEntity_id = model.data!.coarseHierarchy.spheres.entity_id;
|
||||
const spheresRadius = model.data!.coarseConformation.spheres.radius;
|
||||
for (let i = 0, il = spheresEntity_id.rowCount; i < il; ++i) {
|
||||
const entitiId = spheresEntity_id.value(i);
|
||||
const radius = spheresRadius[i];
|
||||
if (!spheresCount.has(entitiId)) {
|
||||
spheresCount.set(entitiId, 1);
|
||||
spheresAvgRadius.set(entitiId, radius);
|
||||
} else {
|
||||
spheresCount.set(entitiId, spheresCount.get(entitiId)! + 1);
|
||||
spheresAvgRadius.set(entitiId, spheresAvgRadius.get(entitiId)! + radius);
|
||||
}
|
||||
}
|
||||
spheresAvgRadius.forEach((v, k) => {
|
||||
spheresAvgRadius.set(k, v / spheresCount.get(k)!);
|
||||
});
|
||||
}
|
||||
|
||||
const entGroups = new Map<string, StateObjectSelector>();
|
||||
const entIds = new Map<string, { idx: number, members: Map<number, number> }>();
|
||||
const entColors = new Map<string, Color[]>();
|
||||
|
||||
const graphicsMode = MesoscaleState.get(plugin).graphics;
|
||||
const groupParams = getMesoscaleGroupParams(graphicsMode);
|
||||
|
||||
const base = await state.build()
|
||||
.to(model)
|
||||
.apply(MmcifAssembly, { id: '' })
|
||||
.commit();
|
||||
|
||||
const units = base.data!.units;
|
||||
const willBeMerged = units.length > 1 && units.every(u => u.conformation.operator.isIdentity);
|
||||
const clipVariant = willBeMerged ? 'pixel' : 'instance';
|
||||
|
||||
const entRoot = await state.build()
|
||||
.toRoot()
|
||||
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
const getEntityType = (i: number) => {
|
||||
if (entities.type.value(i) === 'water') return 'water' as const;
|
||||
return subtype.value(i) || 'unknown type';
|
||||
};
|
||||
|
||||
for (let i = 0; i < entities._rowCount; i++) {
|
||||
const t = getEntityType(i);
|
||||
if (!entIds.has(t)) {
|
||||
entIds.set(t, { idx: entIds.size, members: new Map() });
|
||||
}
|
||||
const cm = entIds.get(t)!;
|
||||
cm.members.set(i, cm.members.size);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const baseEntColors = getDistinctBaseColors(entIds.size, 0);
|
||||
const entIdEntries = Array.from(entIds.entries());
|
||||
for (let i = 0; i < entIdEntries.length; ++i) {
|
||||
const [t, m] = entIdEntries[i];
|
||||
const groupColors = getDistinctGroupColors(m.members.size, baseEntColors[i], 20, 0);
|
||||
entColors.set(t, groupColors);
|
||||
}
|
||||
|
||||
for (let i = 0; i < entities._rowCount; i++) {
|
||||
const t = getEntityType(i);
|
||||
if (!entGroups.has(t)) {
|
||||
const colorIdx = entIds.get(t)?.idx;
|
||||
const color = colorIdx !== undefined ? baseEntColors[colorIdx] : ColorNames.white;
|
||||
const group = await state.build()
|
||||
.to(entRoot)
|
||||
.applyOrUpdateTagged(`group:ent:${t}`, MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `ent:${t}`, label: t, color: { type: 'generate', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit({ revertOnError: true });
|
||||
entGroups.set(t, group);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
await state.transaction(async () => {
|
||||
try {
|
||||
const dependsOn = [base.ref];
|
||||
plugin.animationLoop.stop({ noDraw: true });
|
||||
let build: StateBuilder.Root | StateBuilder.To<any> = state.build();
|
||||
for (let i = 0; i < entities._rowCount; i++) {
|
||||
const t = getEntityType(i);
|
||||
const color = entColors.get(t)![entIds.get(t)!.members.get(i)!];
|
||||
const scaleFactor = spheresAvgRadius.get(entities.id.value(i)) || 1;
|
||||
|
||||
build = build
|
||||
.toRoot()
|
||||
.apply(MmcifStructure, { structureRef: base.ref, entityId: entities.id.value(i) }, { dependsOn })
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(color, scaleFactor, graphicsMode, clipVariant), { tags: [`ent:${t}`] });
|
||||
}
|
||||
await build.commit();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(e);
|
||||
} finally {
|
||||
plugin.animationLoop.start();
|
||||
}
|
||||
}).run();
|
||||
}
|
||||
138
src/apps/mesoscale-explorer/data/petworld/model.ts
Normal file
138
src/apps/mesoscale-explorer/data/petworld/model.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mat4 } from '../../../../mol-math/linear-algebra/3d/mat4';
|
||||
import { getMatrices, operatorGroupsProvider } from '../../../../mol-model-formats/structure/property/assembly';
|
||||
import { Structure, StructureElement, StructureProperties, Trajectory, Unit } from '../../../../mol-model/structure';
|
||||
import { Assembly } from '../../../../mol-model/structure/model/properties/symmetry';
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { Table } from '../../../../mol-data/db';
|
||||
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { arrayFind } from '../../../../mol-data/util';
|
||||
import { StateObject, StateTransformer } from '../../../../mol-state';
|
||||
import { CifField } from '../../../../mol-io/reader/cif';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { mergeUnits } from '../util';
|
||||
import { deepEqual } from '../../../../mol-util';
|
||||
|
||||
export { StructureFromPetworld };
|
||||
type StructureFromPetworld = typeof StructureFromPetworld
|
||||
const StructureFromPetworld = PluginStateTransform.BuiltIn({
|
||||
name: 'structure-from-petworld',
|
||||
display: { name: 'Structure from PetWorld', description: 'Create a molecular structure from PetWorld models.' },
|
||||
from: SO.Molecule.Trajectory,
|
||||
to: SO.Molecule.Structure,
|
||||
params: {
|
||||
modelIndex: PD.Numeric(0),
|
||||
entityIds: PD.Value<string[]>([]),
|
||||
}
|
||||
})({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
const s = await buildModelsAssembly(a.data, '1', params.modelIndex, params.entityIds).runInContext(ctx);
|
||||
if (!s || !MmcifFormat.is(s.model.sourceData)) return StateObject.Null;
|
||||
|
||||
const { frame } = s.model.sourceData.data;
|
||||
const pdbx_model = frame.categories.pdbx_model.getField('name')!;
|
||||
const label = pdbx_model.str(params.modelIndex);
|
||||
const props = { label, description: Structure.elementDescription(s) };
|
||||
return new SO.Molecule.Structure(s, props);
|
||||
});
|
||||
},
|
||||
update({ newParams, oldParams }) {
|
||||
return deepEqual(newParams, oldParams)
|
||||
? StateTransformer.UpdateResult.Unchanged
|
||||
: StateTransformer.UpdateResult.Recreate;
|
||||
},
|
||||
dispose({ b }) {
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
function buildModelsAssembly(trajectory: Trajectory, asmName: string, modelIndex: number, entitiyIds: string[]) {
|
||||
return Task.create('Build Models Assembly', async ctx => {
|
||||
const model = await Task.resolveInContext(trajectory.getFrameAtIndex(modelIndex), ctx);
|
||||
if (!MmcifFormat.is(model.sourceData)) return;
|
||||
|
||||
const { db, frame } = model.sourceData.data;
|
||||
const PDB_model_num = frame.categories.pdbx_struct_assembly_gen.getField('PDB_model_num')!;
|
||||
|
||||
// hack to cache models assemblies
|
||||
if (!(trajectory as any).__modelsAssemblies) {
|
||||
(trajectory as any).__modelsAssemblies = createModelsAssemblies(db.pdbx_struct_assembly, db.pdbx_struct_assembly_gen as StructAssemblyGen, db.pdbx_struct_oper_list, PDB_model_num);
|
||||
}
|
||||
const modelsAssemblies = (trajectory as any).__modelsAssemblies as ModelsAssembly[];
|
||||
|
||||
const modelsAssembly = arrayFind(modelsAssemblies, ma => ma.assembly.id.toLowerCase() === asmName);
|
||||
if (!modelsAssembly) throw new Error(`Models Assembly '${asmName}' is not defined.`);
|
||||
|
||||
const { assembly } = modelsAssembly;
|
||||
const assembler = Structure.Builder();
|
||||
const g = assembly.operatorGroups[modelIndex];
|
||||
|
||||
const structure = Structure.ofModel(model);
|
||||
const l = StructureElement.Location.create(structure);
|
||||
const units = structure.units.filter(u => {
|
||||
l.unit = u;
|
||||
l.element = u.elements[0];
|
||||
return entitiyIds.includes(StructureProperties.entity.id(l));
|
||||
});
|
||||
const unit = mergeUnits(units, 0);
|
||||
|
||||
for (const oper of g.operators) {
|
||||
assembler.addUnit(unit.kind, unit.model, oper, unit.elements, unit.traits | Unit.Trait.FastBoundary, unit.invariantId);
|
||||
}
|
||||
|
||||
return assembler.getStructure();
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type StructAssembly = Table<mmCIF_Schema['pdbx_struct_assembly']>
|
||||
type StructAssemblyGen = Table<mmCIF_Schema['pdbx_struct_assembly_gen']>
|
||||
type StructOperList = Table<mmCIF_Schema['pdbx_struct_oper_list']>
|
||||
|
||||
type ModelsAssembly = { assembly: Assembly, modelNums: number[] };
|
||||
|
||||
function createModelsAssemblies(pdbx_struct_assembly: StructAssembly, pdbx_struct_assembly_gen: StructAssemblyGen, pdbx_struct_oper_list: StructOperList, PDB_model_num: CifField): ReadonlyArray<ModelsAssembly> {
|
||||
if (!pdbx_struct_assembly._rowCount) return [];
|
||||
|
||||
const matrices = getMatrices(pdbx_struct_oper_list);
|
||||
const assemblies: ModelsAssembly[] = [];
|
||||
for (let i = 0; i < pdbx_struct_assembly._rowCount; i++) {
|
||||
assemblies[assemblies.length] = createModelsAssembly(pdbx_struct_assembly, pdbx_struct_assembly_gen, i, matrices, PDB_model_num);
|
||||
}
|
||||
return assemblies;
|
||||
}
|
||||
|
||||
type Matrices = Map<string, Mat4>
|
||||
type Generator = { assemblyId: string, expression: string, asymIds: string[] }
|
||||
|
||||
function createModelsAssembly(pdbx_struct_assembly: StructAssembly, pdbx_struct_assembly_gen: StructAssemblyGen, index: number, matrices: Matrices, PDB_model_num: CifField): ModelsAssembly {
|
||||
const id = pdbx_struct_assembly.id.value(index);
|
||||
const details = pdbx_struct_assembly.details.value(index);
|
||||
const generators: Generator[] = [];
|
||||
const modelNums: number[] = [];
|
||||
|
||||
const { assembly_id, oper_expression, asym_id_list } = pdbx_struct_assembly_gen;
|
||||
|
||||
for (let i = 0, _i = pdbx_struct_assembly_gen._rowCount; i < _i; i++) {
|
||||
if (assembly_id.value(i) !== id) continue;
|
||||
generators[generators.length] = {
|
||||
assemblyId: id,
|
||||
expression: oper_expression.value(i),
|
||||
asymIds: asym_id_list.value(i)
|
||||
};
|
||||
modelNums[modelNums.length] = PDB_model_num.int(i);
|
||||
}
|
||||
|
||||
const assembly = Assembly.create(id, details, operatorGroupsProvider(generators, matrices));
|
||||
|
||||
return { assembly, modelNums };
|
||||
}
|
||||
134
src/apps/mesoscale-explorer/data/petworld/preset.ts
Normal file
134
src/apps/mesoscale-explorer/data/petworld/preset.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateBuilder, StateObjectRef } from '../../../../mol-state';
|
||||
import { StructureFromPetworld } from './model';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/representation/spacefill';
|
||||
import { StructureRepresentation3D } from '../../../../mol-plugin-state/transforms/representation';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { PluginStateObject } from '../../../../mol-plugin-state/objects';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getGraphicsModeProps, getMesoscaleGroupParams } from '../state';
|
||||
import { ColorNames } from '../../../../mol-util/color/names';
|
||||
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { Task } from '../../../../mol-task';
|
||||
|
||||
function getSpacefillParams(color: Color, graphics: GraphicsMode) {
|
||||
const gmp = getGraphicsModeProps(graphics === 'custom' ? 'quality' : graphics);
|
||||
return {
|
||||
type: {
|
||||
name: 'spacefill',
|
||||
params: {
|
||||
...SpacefillRepresentationProvider.defaultValues,
|
||||
ignoreHydrogens: true,
|
||||
instanceGranularity: true,
|
||||
ignoreLight: true,
|
||||
lodLevels: gmp.lodLevels,
|
||||
quality: 'lowest', // avoid 'auto', triggers boundary calc
|
||||
clip: {
|
||||
variant: 'instance',
|
||||
objects: [],
|
||||
},
|
||||
clipPrimitive: true,
|
||||
approximate: gmp.approximate,
|
||||
alphaThickness: gmp.alphaThickness,
|
||||
},
|
||||
},
|
||||
colorTheme: {
|
||||
name: 'uniform',
|
||||
params: {
|
||||
value: color,
|
||||
saturation: 0,
|
||||
lightness: 0,
|
||||
}
|
||||
},
|
||||
sizeTheme: {
|
||||
name: 'physical',
|
||||
params: {
|
||||
scale: 1,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function createPetworldHierarchy(plugin: PluginContext, trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory>) {
|
||||
const cell = StateObjectRef.resolveAndCheck(plugin.state.data, trajectory);
|
||||
const tr = cell?.obj?.data;
|
||||
if (!cell || !tr) return;
|
||||
|
||||
if (!MmcifFormat.is(tr.representative.sourceData)) return;
|
||||
|
||||
const membrane: { modelIndex: number, entityIds: string[] }[] = [];
|
||||
const other: { modelIndex: number, entityIds: string[] }[] = [];
|
||||
for (let i = 0; i < tr.frameCount; ++i) {
|
||||
const m = await Task.resolveInContext(tr.getFrameAtIndex(i));
|
||||
// cannot use m.properties.structAsymMap because petworld models
|
||||
// may assign the same asymId to multiple entities
|
||||
const { label_asym_id, label_entity_id, _rowCount } = m.atomicHierarchy.chains;
|
||||
const membraneIds: string[] = [];
|
||||
const otherIds: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (let i = 0; i < _rowCount; i ++) {
|
||||
const entityId = label_entity_id.value(i);
|
||||
if (seen.has(entityId)) continue;
|
||||
|
||||
const asymId = label_asym_id.value(i);
|
||||
if (asymId.startsWith('MEM')) {
|
||||
membraneIds.push(entityId);
|
||||
} else {
|
||||
otherIds.push(entityId);
|
||||
}
|
||||
seen.add(entityId);
|
||||
}
|
||||
if (membraneIds.length) {
|
||||
membrane.push({ modelIndex: i, entityIds: membraneIds });
|
||||
}
|
||||
if (otherIds.length) {
|
||||
other.push({ modelIndex: i, entityIds: otherIds });
|
||||
}
|
||||
}
|
||||
|
||||
const state = plugin.state.data;
|
||||
const graphicsMode = MesoscaleState.get(plugin).graphics;
|
||||
const groupParams = getMesoscaleGroupParams(graphicsMode);
|
||||
|
||||
const group = await state.build()
|
||||
.toRoot()
|
||||
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'generate', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit({ revertOnError: true });
|
||||
|
||||
await state.build()
|
||||
.to(group)
|
||||
.applyOrUpdateTagged(`group:ent:mem`, MesoscaleGroup, { ...groupParams, index: undefined, tag: `ent:mem`, label: 'Membrane', color: { type: 'uniform', value: ColorNames.lightgrey, variability: 20, shift: 0, lightness: 0, alpha: 1 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
const colors = getDistinctBaseColors(other.length, 0);
|
||||
|
||||
await state.transaction(async () => {
|
||||
try {
|
||||
plugin.animationLoop.stop({ noDraw: true });
|
||||
let build: StateBuilder.Root | StateBuilder.To<any> = state.build();
|
||||
for (let i = 0, il = membrane.length; i < il; ++i) {
|
||||
build = build
|
||||
.to(cell)
|
||||
.apply(StructureFromPetworld, membrane[i])
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(ColorNames.lightgrey, graphicsMode), { tags: [`ent:mem`] });
|
||||
}
|
||||
for (let i = 0, il = other.length; i < il; ++i) {
|
||||
build = build
|
||||
.to(cell)
|
||||
.apply(StructureFromPetworld, other[i])
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(colors[i], graphicsMode), { tags: [`ent:`] });
|
||||
}
|
||||
await build.commit();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(e);
|
||||
} finally {
|
||||
plugin.animationLoop.start();
|
||||
}
|
||||
}).run();
|
||||
}
|
||||
592
src/apps/mesoscale-explorer/data/state.ts
Normal file
592
src/apps/mesoscale-explorer/data/state.ts
Normal file
@@ -0,0 +1,592 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { Spheres } from '../../../mol-geo/geometry/spheres/spheres';
|
||||
import { Clip } from '../../../mol-util/clip';
|
||||
import { escapeRegExp, stringToWords } from '../../../mol-util/string';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { ParamMapping } from '../../../mol-util/param-mapping';
|
||||
import { EntityNode } from '../ui/entities';
|
||||
import { DistinctColorsProps, distinctColors } from '../../../mol-util/color/distinct';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { Hcl } from '../../../mol-util/color/spaces/hcl';
|
||||
import { StateObjectCell, StateObjectRef, StateSelection } from '../../../mol-state';
|
||||
import { ShapeRepresentation3D, StructureRepresentation3D } from '../../../mol-plugin-state/transforms/representation';
|
||||
import { SpacefillRepresentationProvider } from '../../../mol-repr/structure/representation/spacefill';
|
||||
import { assertUnreachable } from '../../../mol-util/type-helpers';
|
||||
import { MesoscaleExplorerState } from '../app';
|
||||
import { saturate } from '../../../mol-math/interpolate';
|
||||
|
||||
function getHueRange(hue: number, variability: number) {
|
||||
let min = hue - variability;
|
||||
const minOverflow = (min < 0 ? -min : 0);
|
||||
let max = hue + variability;
|
||||
if (max > 360) min -= max - 360;
|
||||
max += minOverflow;
|
||||
return [Math.max(0, min), Math.min(360, max)] as [number, number];
|
||||
}
|
||||
|
||||
function getGrayscaleColors(count: number, luminance: number, variability: number) {
|
||||
const out: Color[] = [];
|
||||
for (let i = 0; i < count; ++ i) {
|
||||
const l = saturate(luminance / 100);
|
||||
const v = saturate(variability / 180) * Math.random();
|
||||
const s = Math.random() > 0.5 ? 1 : -1;
|
||||
const d = Math.abs(l + s * v) % 1;
|
||||
out[i] = Color.fromNormalizedRgb(d, d, d);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function getDistinctGroupColors(count: number, color: Color, variability: number, shift: number, props?: Partial<DistinctColorsProps>) {
|
||||
const hcl = Hcl.fromColor(Hcl(), color);
|
||||
if (isNaN(hcl[0])) {
|
||||
return getGrayscaleColors(count, hcl[2], variability);
|
||||
}
|
||||
|
||||
if (count === 1) {
|
||||
hcl[1] = 65;
|
||||
hcl[2] = 55;
|
||||
return [Hcl.toColor(hcl)];
|
||||
}
|
||||
|
||||
const colors = distinctColors(count, {
|
||||
hue: getHueRange(hcl[0], variability),
|
||||
chroma: [30, 100],
|
||||
luminance: [50, 100],
|
||||
clusteringStepCount: 0,
|
||||
minSampleCount: 1000,
|
||||
sampleCountFactor: 100,
|
||||
sort: 'none',
|
||||
...props,
|
||||
});
|
||||
|
||||
if (shift !== 0) {
|
||||
const offset = Math.floor(shift / 100 * count);
|
||||
return [...colors.slice(offset), ...colors.slice(0, offset)];
|
||||
} else {
|
||||
return colors;
|
||||
}
|
||||
}
|
||||
|
||||
const Colors = [0x377eb8, 0xe41a1c, 0x4daf4a, 0x984ea3, 0xff7f00, 0xffff33, 0xa65628, 0xf781bf] as Color[];
|
||||
|
||||
export function getDistinctBaseColors(count: number, shift: number, props?: Partial<DistinctColorsProps>): Color[] {
|
||||
let colors: Color[];
|
||||
if (count <= Colors.length) {
|
||||
colors = Colors.slice(0, count).map(e => Array.isArray(e) ? e[0] : e);
|
||||
} else {
|
||||
colors = distinctColors(count, {
|
||||
hue: [1, 360],
|
||||
chroma: [25, 100],
|
||||
luminance: [30, 100],
|
||||
clusteringStepCount: 0,
|
||||
minSampleCount: 1000,
|
||||
sampleCountFactor: 100,
|
||||
sort: 'none',
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
if (shift !== 0) {
|
||||
const offset = Math.floor(shift / 100 * count);
|
||||
return [...colors.slice(offset), ...colors.slice(0, offset)];
|
||||
} else {
|
||||
return colors;
|
||||
}
|
||||
}
|
||||
|
||||
export const ColorParams = {
|
||||
type: PD.Select('generate', PD.arrayToOptions(['generate', 'uniform', 'custom'])),
|
||||
value: PD.Color(Color(0xFFFFFF), { hideIf: p => p.type === 'custom' }),
|
||||
variability: PD.Numeric(20, { min: 1, max: 180, step: 1 }, { hideIf: p => p.type !== 'generate' }),
|
||||
shift: PD.Numeric(0, { min: 0, max: 100, step: 1 }, { hideIf: p => !p.type.includes('generate') }),
|
||||
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }, { hideIf: p => p.type === 'custom' }),
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
|
||||
};
|
||||
export type ColorProps = PD.Values<typeof ColorParams>
|
||||
|
||||
export const ColorValueParam = PD.Color(Color(0xFFFFFF));
|
||||
|
||||
export const RootParams = {
|
||||
type: PD.Select('custom', PD.arrayToOptions(['group-generate', 'group-uniform', 'generate', 'uniform', 'custom'])),
|
||||
value: PD.Color(Color(0xFFFFFF), { hideIf: p => p.type !== 'uniform' }),
|
||||
variability: PD.Numeric(20, { min: 1, max: 180, step: 1 }, { hideIf: p => p.type !== 'group-generate' }),
|
||||
shift: PD.Numeric(0, { min: 0, max: 100, step: 1 }, { hideIf: p => !p.type.includes('generate') }),
|
||||
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }, { hideIf: p => p.type === 'custom' }),
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
|
||||
};
|
||||
|
||||
export const LightnessParams = {
|
||||
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
|
||||
};
|
||||
export const DimLightness = 6;
|
||||
|
||||
export const OpacityParams = {
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
};
|
||||
|
||||
export const PatternParams = {
|
||||
frequency: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
amplitude: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
};
|
||||
|
||||
export const LodParams = {
|
||||
lodLevels: Spheres.Params.lodLevels,
|
||||
cellSize: Spheres.Params.cellSize,
|
||||
batchSize: Spheres.Params.batchSize,
|
||||
approximate: Spheres.Params.approximate,
|
||||
};
|
||||
|
||||
export const SimpleClipParams = {
|
||||
type: PD.Select('none', PD.objectToOptions(Clip.Type, t => stringToWords(t))),
|
||||
invert: PD.Boolean(false),
|
||||
position: PD.Group({
|
||||
x: PD.Numeric(0, { min: -100, max: 100, step: 1 }, { immediateUpdate: true }),
|
||||
y: PD.Numeric(0, { min: -100, max: 100, step: 1 }, { immediateUpdate: true }),
|
||||
z: PD.Numeric(0, { min: -100, max: 100, step: 1 }, { immediateUpdate: true }),
|
||||
}, { hideIf: g => g.type === 'none', isExpanded: true }),
|
||||
rotation: PD.Group({
|
||||
axis: PD.Vec3(Vec3.create(1, 0, 0)),
|
||||
angle: PD.Numeric(0, { min: -180, max: 180, step: 1 }, { immediateUpdate: true }),
|
||||
}, { hideIf: g => g.type === 'none', isExpanded: true }),
|
||||
scale: PD.Group({
|
||||
x: PD.Numeric(100, { min: 0, max: 100, step: 1 }, { immediateUpdate: true }),
|
||||
y: PD.Numeric(100, { min: 0, max: 100, step: 1 }, { immediateUpdate: true }),
|
||||
z: PD.Numeric(100, { min: 0, max: 100, step: 1 }, { immediateUpdate: true }),
|
||||
}, { hideIf: g => ['none', 'plane'].includes(g.type), isExpanded: true }),
|
||||
};
|
||||
export type SimpleClipParams = typeof SimpleClipParams
|
||||
export type SimpleClipProps = PD.Values<SimpleClipParams>
|
||||
|
||||
export function getClipObjects(values: SimpleClipProps, boundingSphere: Sphere3D): Clip.Props['objects'] {
|
||||
const { center, radius } = boundingSphere;
|
||||
|
||||
const position = Vec3.clone(center);
|
||||
Vec3.add(position, position, Vec3.create(
|
||||
radius * values.position.x / 100,
|
||||
radius * values.position.y / 100,
|
||||
radius * values.position.z / 100
|
||||
));
|
||||
|
||||
const scale = Vec3.create(values.scale.x, values.scale.y, values.scale.z);
|
||||
Vec3.scale(scale, scale, 2 * radius / 100);
|
||||
|
||||
return [{
|
||||
type: values.type,
|
||||
invert: values.invert,
|
||||
position,
|
||||
scale,
|
||||
rotation: values.rotation
|
||||
}];
|
||||
}
|
||||
|
||||
export function createClipMapping(node: EntityNode) {
|
||||
return ParamMapping({
|
||||
params: SimpleClipParams,
|
||||
target: (ctx: PluginContext) => {
|
||||
return node.clipValue;
|
||||
}
|
||||
})({
|
||||
values(props, ctx) {
|
||||
if (!props || props.objects.length === 0) {
|
||||
return {
|
||||
type: 'none',
|
||||
invert: false,
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
rotation: { axis: Vec3.create(1, 0, 0), angle: 0 },
|
||||
scale: { x: 100, y: 100, z: 100 },
|
||||
};
|
||||
}
|
||||
|
||||
const { center, radius } = node.plugin.canvas3d!.boundingSphere;
|
||||
const { invert, position, scale, rotation, type } = props.objects[0];
|
||||
|
||||
const p = Vec3.clone(position);
|
||||
Vec3.sub(p, p, center);
|
||||
Vec3.scale(p, p, 100 / radius);
|
||||
Vec3.round(p, p);
|
||||
|
||||
const s = Vec3.clone(scale);
|
||||
Vec3.scale(s, s, 100 / radius / 2);
|
||||
Vec3.round(s, s);
|
||||
|
||||
return {
|
||||
type,
|
||||
invert,
|
||||
position: { x: p[0], y: p[1], z: p[2] },
|
||||
rotation,
|
||||
scale: { x: s[0], y: s[1], z: s[2] },
|
||||
};
|
||||
},
|
||||
update: (s, props) => {
|
||||
if (!props) return;
|
||||
|
||||
const clipObjects = getClipObjects(s, node.plugin.canvas3d!.boundingSphere);
|
||||
props.objects = clipObjects;
|
||||
},
|
||||
apply: async (props, ctx) => {
|
||||
if (props) node.updateClip(props);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const MesoscaleGroupParams = {
|
||||
root: PD.Value<boolean>(false, { isHidden: true }),
|
||||
index: PD.Value<number>(-1, { isHidden: true }),
|
||||
tag: PD.Value<string>('', { isHidden: true }),
|
||||
label: PD.Value<string>('', { isHidden: true }),
|
||||
hidden: PD.Boolean(false),
|
||||
color: PD.Group(RootParams),
|
||||
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
lod: PD.Group(LodParams),
|
||||
clip: PD.Group(SimpleClipParams),
|
||||
};
|
||||
export type MesoscaleGroupProps = PD.Values<typeof MesoscaleGroupParams>;
|
||||
|
||||
export class MesoscaleGroupObject extends PSO.Create({ name: 'Mesoscale Group', typeClass: 'Object' }) { }
|
||||
|
||||
export const MesoscaleGroup = PluginStateTransform.BuiltIn({
|
||||
name: 'mesoscale-group',
|
||||
display: { name: 'Mesoscale Group' },
|
||||
from: [PSO.Root, MesoscaleGroupObject],
|
||||
to: MesoscaleGroupObject,
|
||||
params: MesoscaleGroupParams,
|
||||
})({
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Apply Mesoscale Group', async () => {
|
||||
return new MesoscaleGroupObject({}, { label: params.label });
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export function getMesoscaleGroupParams(graphicsMode: GraphicsMode): MesoscaleGroupProps {
|
||||
const groupParams = PD.getDefaultValues(MesoscaleGroupParams);
|
||||
if (graphicsMode === 'custom') return groupParams;
|
||||
|
||||
return {
|
||||
...groupParams,
|
||||
lod: {
|
||||
...groupParams.lod,
|
||||
...getGraphicsModeProps(graphicsMode),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export type LodLevels = typeof SpacefillRepresentationProvider.defaultValues['lodLevels']
|
||||
|
||||
export function getLodLevels(graphicsMode: Exclude<GraphicsMode, 'custom'>): LodLevels {
|
||||
switch (graphicsMode) {
|
||||
case 'performance':
|
||||
return [
|
||||
{ minDistance: 1, maxDistance: 300, overlap: 0, stride: 1, scaleBias: 1 },
|
||||
{ minDistance: 300, maxDistance: 2000, overlap: 0, stride: 40, scaleBias: 3 },
|
||||
{ minDistance: 2000, maxDistance: 6000, overlap: 0, stride: 150, scaleBias: 3 },
|
||||
{ minDistance: 6000, maxDistance: 10000000, overlap: 0, stride: 300, scaleBias: 2.5 },
|
||||
];
|
||||
case 'balanced':
|
||||
return [
|
||||
{ minDistance: 1, maxDistance: 500, overlap: 0, stride: 1, scaleBias: 1 },
|
||||
{ minDistance: 500, maxDistance: 2000, overlap: 0, stride: 15, scaleBias: 3 },
|
||||
{ minDistance: 2000, maxDistance: 6000, overlap: 0, stride: 70, scaleBias: 2.7 },
|
||||
{ minDistance: 6000, maxDistance: 10000000, overlap: 0, stride: 200, scaleBias: 2.5 },
|
||||
];
|
||||
case 'quality':
|
||||
return [
|
||||
{ minDistance: 1, maxDistance: 1000, overlap: 0, stride: 1, scaleBias: 1 },
|
||||
{ minDistance: 1000, maxDistance: 4000, overlap: 0, stride: 10, scaleBias: 3 },
|
||||
{ minDistance: 4000, maxDistance: 10000, overlap: 0, stride: 50, scaleBias: 2.7 },
|
||||
{ minDistance: 10000, maxDistance: 10000000, overlap: 0, stride: 200, scaleBias: 2.3 },
|
||||
];
|
||||
case 'ultra':
|
||||
return [
|
||||
{ minDistance: 1, maxDistance: 2000, overlap: 0, stride: 1, scaleBias: 1 },
|
||||
{ minDistance: 2000, maxDistance: 8000, overlap: 0, stride: 10, scaleBias: 3 },
|
||||
{ minDistance: 8000, maxDistance: 20000, overlap: 0, stride: 50, scaleBias: 2.5 },
|
||||
{ minDistance: 20000, maxDistance: 10000000, overlap: 0, stride: 200, scaleBias: 2 },
|
||||
];
|
||||
default:
|
||||
assertUnreachable(graphicsMode);
|
||||
}
|
||||
}
|
||||
|
||||
export type GraphicsMode = 'ultra' | 'quality' | 'balanced' | 'performance' | 'custom';
|
||||
|
||||
export function getGraphicsModeProps(graphicsMode: Exclude<GraphicsMode, 'custom'>) {
|
||||
return {
|
||||
lodLevels: getLodLevels(graphicsMode),
|
||||
approximate: graphicsMode !== 'quality' && graphicsMode !== 'ultra',
|
||||
alphaThickness: graphicsMode === 'performance' ? 15 : 12,
|
||||
};
|
||||
}
|
||||
|
||||
export function setGraphicsCanvas3DProps(ctx: PluginContext, graphics: GraphicsMode) {
|
||||
const pixelScale = graphics === 'balanced' ? 0.75
|
||||
: graphics === 'performance' ? 0.5 : 1;
|
||||
|
||||
ctx.canvas3dContext?.setProps({ pixelScale });
|
||||
|
||||
ctx.canvas3d?.setProps({
|
||||
postprocessing: {
|
||||
sharpening: pixelScale < 1 ? {
|
||||
name: 'on',
|
||||
params: { sharpness: 0.5, denoise: true }
|
||||
} : { name: 'off', params: {} }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export const MesoscaleStateParams = {
|
||||
filter: PD.Value<string>('', { isHidden: true }),
|
||||
graphics: PD.Select('quality', PD.arrayToOptions(['ultra', 'quality', 'balanced', 'performance', 'custom'] as GraphicsMode[])),
|
||||
description: PD.Value<string>('', { isHidden: true }),
|
||||
link: PD.Value<string>('', { isHidden: true }),
|
||||
};
|
||||
|
||||
export class MesoscaleStateObject extends PSO.Create<MesoscaleState>({ name: 'Mesoscale State', typeClass: 'Object' }) { }
|
||||
|
||||
const MesoscaleStateTransform = PluginStateTransform.BuiltIn({
|
||||
name: 'mesoscale-state',
|
||||
display: { name: 'Mesoscale State' },
|
||||
from: PSO.Root,
|
||||
to: MesoscaleStateObject,
|
||||
params: MesoscaleStateParams,
|
||||
})({
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Apply Mesoscale State', async () => {
|
||||
return new MesoscaleStateObject(params);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export { MesoscaleState };
|
||||
type MesoscaleState = PD.Values<typeof MesoscaleStateParams>;
|
||||
const MesoscaleState = {
|
||||
async init(ctx: PluginContext) {
|
||||
const cell = ctx.state.data.selectQ(q => q.ofType(MesoscaleStateObject))[0];
|
||||
if (cell) throw new Error('MesoscaleState already initialized');
|
||||
|
||||
const customState = ctx.customState as MesoscaleExplorerState;
|
||||
const state = await ctx.state.data.build().toRoot().apply(MesoscaleStateTransform, {
|
||||
filter: '',
|
||||
graphics: customState.graphicsMode,
|
||||
}).commit();
|
||||
customState.stateRef = state.ref;
|
||||
},
|
||||
get(ctx: PluginContext): MesoscaleState {
|
||||
const ref = this.ref(ctx);
|
||||
return ctx.state.data.tryGetCellData<MesoscaleStateObject>(ref);
|
||||
},
|
||||
async set(ctx: PluginContext, props: Partial<MesoscaleState>) {
|
||||
const ref = this.ref(ctx);
|
||||
await ctx.state.data.build().to(ref).update(MesoscaleStateTransform, old => Object.assign(old, props)).commit();
|
||||
},
|
||||
ref(ctx: PluginContext): string {
|
||||
const ref = (ctx.customState as MesoscaleExplorerState).stateRef;
|
||||
if (!ref) throw new Error('MesoscaleState not initialized');
|
||||
return ref;
|
||||
},
|
||||
has(ctx: PluginContext): boolean {
|
||||
const ref = (ctx.customState as MesoscaleExplorerState).stateRef || '';
|
||||
return ctx.state.data.cells.has(ref) ? true : false;
|
||||
},
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
export function getRoots(plugin: PluginContext): StateSelection.CellSeq<StateObjectCell<MesoscaleGroupObject>> {
|
||||
const s = plugin.customState as MesoscaleExplorerState;
|
||||
if (!s.stateCache.roots) {
|
||||
s.stateCache.roots = plugin.state.data.select(StateSelection.Generators.rootsOfType(MesoscaleGroupObject));
|
||||
}
|
||||
return s.stateCache.roots;
|
||||
}
|
||||
|
||||
export function getGroups(plugin: PluginContext, tag?: string): StateSelection.CellSeq<StateObjectCell<MesoscaleGroupObject>> {
|
||||
const s = plugin.customState as MesoscaleExplorerState;
|
||||
const k = `groups-${tag || ''}`;
|
||||
if (!s.stateCache[k]) {
|
||||
const selector = tag !== undefined
|
||||
? StateSelection.Generators.ofTransformer(MesoscaleGroup).withTag(tag)
|
||||
: StateSelection.Generators.ofTransformer(MesoscaleGroup);
|
||||
s.stateCache[k] = plugin.state.data.select(selector);
|
||||
}
|
||||
return s.stateCache[k];
|
||||
}
|
||||
|
||||
function _getAllGroups(plugin: PluginContext, tag: string | undefined, list: StateObjectCell[]) {
|
||||
const groups = getGroups(plugin, tag);
|
||||
list.push(...groups);
|
||||
for (const g of groups) {
|
||||
_getAllGroups(plugin, g.params?.values.tag, list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
export function getAllGroups(plugin: PluginContext, tag?: string) {
|
||||
return _getAllGroups(plugin, tag, []);
|
||||
}
|
||||
|
||||
export function getAllLeafGroups(plugin: PluginContext, tag: string) {
|
||||
const allGroups = getAllGroups(plugin, tag);
|
||||
allGroups.sort((a, b) => a.params?.values.index - b.params?.values.index);
|
||||
return allGroups.filter(g => {
|
||||
return getEntities(plugin, g.params?.values.tag).length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
type EntityCells = StateSelection.CellSeq<StateObjectCell<PSO.Molecule.Structure.Representation3D | PSO.Shape.Representation3D>>
|
||||
|
||||
export function getEntities(plugin: PluginContext, tag?: string): EntityCells {
|
||||
const s = plugin.customState as MesoscaleExplorerState;
|
||||
const k = `entities-${tag || ''}`;
|
||||
if (!s.stateCache[k]) {
|
||||
const structureSelector = tag !== undefined
|
||||
? StateSelection.Generators.ofTransformer(StructureRepresentation3D).withTag(tag)
|
||||
: StateSelection.Generators.ofTransformer(StructureRepresentation3D);
|
||||
const shapeSelector = tag !== undefined
|
||||
? StateSelection.Generators.ofTransformer(ShapeRepresentation3D).withTag(tag)
|
||||
: StateSelection.Generators.ofTransformer(ShapeRepresentation3D);
|
||||
s.stateCache[k] = [
|
||||
...plugin.state.data.select(structureSelector).filter(c => c.obj!.data.sourceData.elementCount > 0),
|
||||
...plugin.state.data.select(shapeSelector),
|
||||
];
|
||||
}
|
||||
return s.stateCache[k];
|
||||
}
|
||||
|
||||
function getFilterMatcher(filter: string) {
|
||||
return filter.startsWith('"') && filter.endsWith('"')
|
||||
? new RegExp(`^${escapeRegExp(filter.substring(1, filter.length - 1))}$`, 'g')
|
||||
: new RegExp(escapeRegExp(filter), 'gi');
|
||||
}
|
||||
|
||||
export function getFilteredEntities(plugin: PluginContext, tag: string, filter: string) {
|
||||
const matcher = getFilterMatcher(filter);
|
||||
return getEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
|
||||
}
|
||||
|
||||
function _getAllEntities(plugin: PluginContext, tag: string | undefined, list: EntityCells) {
|
||||
list.push(...getEntities(plugin, tag));
|
||||
for (const g of getGroups(plugin, tag)) {
|
||||
_getAllEntities(plugin, g.params?.values.tag, list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
export function getAllEntities(plugin: PluginContext, tag?: string) {
|
||||
return _getAllEntities(plugin, tag, []);
|
||||
}
|
||||
|
||||
export function getAllFilteredEntities(plugin: PluginContext, tag: string, filter: string) {
|
||||
const matcher = getFilterMatcher(filter);
|
||||
return getAllEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
|
||||
}
|
||||
|
||||
export function getEntityLabel(plugin: PluginContext, cell: StateObjectCell) {
|
||||
return StateObjectRef.resolve(plugin.state.data, cell.transform.parent)?.obj?.label || 'Entity';
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export async function updateColors(plugin: PluginContext, values: PD.Values, tag: string, filter: string) {
|
||||
const update = plugin.state.data.build();
|
||||
const { type, value, shift, lightness, alpha } = values;
|
||||
|
||||
if (type === 'group-generate' || type === 'group-uniform') {
|
||||
const groups = getAllLeafGroups(plugin, tag);
|
||||
const baseColors = getDistinctBaseColors(groups.length, shift);
|
||||
|
||||
for (let i = 0; i < groups.length; ++i) {
|
||||
const g = groups[i];
|
||||
const entities = getFilteredEntities(plugin, g.params?.values.tag, filter);
|
||||
let groupColors: Color[] = [];
|
||||
|
||||
if (type === 'group-generate') {
|
||||
const c = g.params?.values.color;
|
||||
groupColors = getDistinctGroupColors(entities.length, baseColors[i], c.variability, c.shift);
|
||||
}
|
||||
|
||||
for (let j = 0; j < entities.length; ++j) {
|
||||
const c = type === 'group-generate' ? groupColors[j] : baseColors[i];
|
||||
update.to(entities[j]).update(old => {
|
||||
if (old.type) {
|
||||
old.colorTheme.params.value = c;
|
||||
old.colorTheme.params.lightness = lightness;
|
||||
old.type.params.alpha = alpha;
|
||||
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
|
||||
} else if (old.coloring) {
|
||||
old.coloring.params.color = c;
|
||||
old.coloring.params.lightness = lightness;
|
||||
old.alpha = alpha;
|
||||
old.xrayShaded = alpha < 1 ? true : false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update.to(g.transform.ref).update(old => {
|
||||
old.color.type = type === 'group-generate' ? 'generate' : 'uniform';
|
||||
old.color.value = baseColors[i];
|
||||
old.color.lightness = lightness;
|
||||
old.color.alpha = alpha;
|
||||
});
|
||||
}
|
||||
} else if (type === 'generate' || type === 'uniform') {
|
||||
const entities = getAllFilteredEntities(plugin, tag, filter);
|
||||
let groupColors: Color[] = [];
|
||||
|
||||
if (type === 'generate') {
|
||||
groupColors = getDistinctBaseColors(entities.length, shift);
|
||||
}
|
||||
|
||||
for (let j = 0; j < entities.length; ++j) {
|
||||
const c = type === 'generate' ? groupColors[j] : value;
|
||||
update.to(entities[j]).update(old => {
|
||||
if (old.type) {
|
||||
old.colorTheme.params.value = c;
|
||||
old.colorTheme.params.lightness = lightness;
|
||||
old.type.params.alpha = alpha;
|
||||
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
|
||||
} else if (old.coloring) {
|
||||
old.coloring.params.color = c;
|
||||
old.coloring.params.lightness = lightness;
|
||||
old.alpha = alpha;
|
||||
old.xrayShaded = alpha < 1 ? true : false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const others = getAllLeafGroups(plugin, tag);
|
||||
for (const o of others) {
|
||||
update.to(o).update(old => {
|
||||
old.color.type = type === 'generate' ? 'custom' : 'uniform';
|
||||
old.color.value = value;
|
||||
old.color.lightness = lightness;
|
||||
old.color.alpha = alpha;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await update.commit();
|
||||
};
|
||||
|
||||
export function expandAllGroups(plugin: PluginContext) {
|
||||
for (const g of getAllGroups(plugin)) {
|
||||
if (g.state.isCollapsed) {
|
||||
plugin.state.data.updateCellState(g.transform.ref, { isCollapsed: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
87
src/apps/mesoscale-explorer/data/util.ts
Normal file
87
src/apps/mesoscale-explorer/data/util.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { OrderedSet, SortedArray } from '../../../mol-data/int';
|
||||
import { Box3D, GridLookup3D, PositionData, Sphere3D } from '../../../mol-math/geometry';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { ElementIndex, Unit } from '../../../mol-model/structure';
|
||||
|
||||
export function mergeUnits(units: readonly Unit[], id: number): Unit {
|
||||
const u = units[0];
|
||||
|
||||
let start = -1 as ElementIndex, end = -1 as ElementIndex;
|
||||
let elements = SortedArray.Empty as SortedArray<ElementIndex>;
|
||||
|
||||
for (let i = 0, il = units.length; i < il; ++i) {
|
||||
const e = units[i].elements;
|
||||
if (SortedArray.isRange(e)) {
|
||||
if (end !== -1 && e[0] === end + 1) {
|
||||
// extend range
|
||||
end = e[e.length - 1];
|
||||
} else {
|
||||
if (end !== -1) {
|
||||
// pending range
|
||||
elements = SortedArray.union(elements, SortedArray.ofRange(start, end));
|
||||
}
|
||||
// new range
|
||||
start = e[0];
|
||||
end = e[e.length - 1];
|
||||
}
|
||||
} else {
|
||||
if (end !== -1) {
|
||||
// pending range
|
||||
elements = SortedArray.union(elements, SortedArray.ofRange(start, end));
|
||||
start = -1 as ElementIndex, end = -1 as ElementIndex;
|
||||
}
|
||||
elements = SortedArray.union(elements, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (end !== -1) {
|
||||
// pending range
|
||||
elements = SortedArray.union(elements, SortedArray.ofRange(start, end));
|
||||
}
|
||||
|
||||
return Unit.create(id, id, 0, u.traits | Unit.Trait.MultiChain, u.kind, u.model, u.conformation.operator, elements);
|
||||
}
|
||||
|
||||
export function partitionUnits(units: readonly Unit[], cellSize: number) {
|
||||
const unitCount = units.length;
|
||||
const mergedUnits: Unit[] = [];
|
||||
|
||||
const box = Box3D.setEmpty(Box3D());
|
||||
const x = new Float32Array(unitCount);
|
||||
const y = new Float32Array(unitCount);
|
||||
const z = new Float32Array(unitCount);
|
||||
const indices = OrderedSet.ofBounds(0, unitCount);
|
||||
|
||||
for (let i = 0, il = unitCount; i < il; ++i) {
|
||||
const v = units[i].boundary.sphere.center;
|
||||
x[i] = v[0];
|
||||
y[i] = v[1];
|
||||
z[i] = v[2];
|
||||
Box3D.add(box, v);
|
||||
}
|
||||
Box3D.expand(box, box, Vec3.create(1, 1, 1));
|
||||
|
||||
const positionData: PositionData = { x, y, z, indices };
|
||||
const boundary = { box, sphere: Sphere3D.fromBox3D(Sphere3D(), box) };
|
||||
const lookup = GridLookup3D(positionData, boundary, Vec3.create(cellSize, cellSize, cellSize));
|
||||
|
||||
const { array, offset, count } = lookup.buckets;
|
||||
|
||||
for (let i = 0, il = offset.length; i < il; ++i) {
|
||||
const start = offset[i];
|
||||
const size = count[i];
|
||||
const cellUnits: Unit[] = [];
|
||||
for (let j = start, jl = start + size; j < jl; ++j) {
|
||||
cellUnits.push(units[array[j]]);
|
||||
}
|
||||
mergedUnits.push(mergeUnits(cellUnits, i));
|
||||
}
|
||||
|
||||
return mergedUnits;
|
||||
}
|
||||
BIN
src/apps/mesoscale-explorer/favicon.ico
Normal file
BIN
src/apps/mesoscale-explorer/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
101
src/apps/mesoscale-explorer/index.html
Normal file
101
src/apps/mesoscale-explorer/index.html
Normal file
@@ -0,0 +1,101 @@
|
||||
<!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">
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<title>Mol* Mesoscale Explorer</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
hr {
|
||||
margin: 10px;
|
||||
}
|
||||
h1, h2, h3, h4, h5 {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
button {
|
||||
padding: 2px;
|
||||
}
|
||||
#app {
|
||||
position: absolute;
|
||||
left: 100px;
|
||||
top: 100px;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
function getParam(name, regex) {
|
||||
var r = new RegExp(name + '=' + '(' + regex + ')[&]?', 'i');
|
||||
return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || '');
|
||||
}
|
||||
|
||||
var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
|
||||
if (debugMode) molstar.setDebugMode(debugMode);
|
||||
|
||||
var timingMode = getParam('timing-mode', '[^&]+').trim() === '1';
|
||||
if (timingMode) molstar.setTimingMode(timingMode);
|
||||
|
||||
var hideControls = getParam('hide-controls', '[^&]+').trim() === '1';
|
||||
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1' || void 0;
|
||||
var allowMajorPerformanceCaveat = getParam('allow-major-performance-caveat', '[^&]+').trim() === '1';
|
||||
var powerPreference = getParam('power-preference', '[^&]+').trim().toLowerCase();
|
||||
var graphicsMode = getParam('graphics-mode', '[^&]+').trim().toLowerCase();
|
||||
|
||||
molstar.MesoscaleExplorer.create('app', {
|
||||
layoutShowControls: !hideControls,
|
||||
viewportShowExpand: false,
|
||||
preferWebgl1: preferWebgl1,
|
||||
allowMajorPerformanceCaveat: allowMajorPerformanceCaveat,
|
||||
powerPreference: powerPreference || 'high-performance',
|
||||
graphicsMode: graphicsMode || 'quality',
|
||||
}).then(me => {
|
||||
var example = getParam('example', '[^&]+').trim();
|
||||
if (example) {
|
||||
me.loadExample(example);
|
||||
return;
|
||||
}
|
||||
|
||||
var url = getParam('url', '[^&]+').trim();
|
||||
var type = getParam('type', '[^&]+').trim();
|
||||
if (url && type) {
|
||||
me.loadUrl(url, type);
|
||||
return;
|
||||
}
|
||||
|
||||
var pdb = getParam('pdb', '[^&]+').trim();
|
||||
if (pdb) {
|
||||
me.loadPdb(pdb);
|
||||
return;
|
||||
}
|
||||
|
||||
var pdbdev = getParam('pdbdev', '[^&]+').trim();
|
||||
if (pdbdev) {
|
||||
me.loadPdbDev(pdbdev);
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
// to aid GC
|
||||
me.dispose();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- __MOLSTAR_ANALYTICS__ -->
|
||||
</body>
|
||||
</html>
|
||||
10
src/apps/mesoscale-explorer/index.ts
Normal file
10
src/apps/mesoscale-explorer/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import './favicon.ico';
|
||||
import './index.html';
|
||||
require('./style.scss');
|
||||
export * from './app';
|
||||
33
src/apps/mesoscale-explorer/style.scss
Normal file
33
src/apps/mesoscale-explorer/style.scss
Normal file
@@ -0,0 +1,33 @@
|
||||
$default-background: #2D3E50;
|
||||
$font-color: #EDF1F2;
|
||||
$hover-font-color: #3B9AD9;
|
||||
$entity-current-font-color: #FFFFFF;
|
||||
$msp-btn-remove-background: #BF3A31;
|
||||
$msp-btn-remove-hover-font-color:#ffffff;
|
||||
$msp-btn-commit-on-font-color: #ffffff;
|
||||
$entity-badge-font-color: #ccd4e0;
|
||||
|
||||
// used in LOG
|
||||
$log-message: #0CCA5D;
|
||||
$log-info: #5E3673;
|
||||
$log-warning: #FCC937;
|
||||
$log-error: #FD354B;
|
||||
|
||||
$logo-background: rgba(0,0,0,0.75);
|
||||
|
||||
@function color-lower-contrast($color, $amount) {
|
||||
@return darken($color, $amount);
|
||||
}
|
||||
|
||||
@function color-increase-contrast($color, $amount) {
|
||||
@return lighten($color, $amount);
|
||||
}
|
||||
|
||||
@import 'mol-plugin-ui/skin/base/base';
|
||||
|
||||
a {
|
||||
color: $font-color;
|
||||
&:hover {
|
||||
color: $hover-font-color;
|
||||
}
|
||||
}
|
||||
953
src/apps/mesoscale-explorer/ui/entities.tsx
Normal file
953
src/apps/mesoscale-explorer/ui/entities.tsx
Normal file
@@ -0,0 +1,953 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { Button, ControlGroup, IconButton } from '../../../mol-plugin-ui/controls/common';
|
||||
import { ArrowDropDownSvg, ArrowRightSvg, CloseSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg, ContentCutSvg, BrushSvg, SearchSvg } from '../../../mol-plugin-ui/controls/icons';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { State, StateObjectCell, StateSelection, StateTransformer } from '../../../mol-state';
|
||||
import { ParameterControls, ParameterMappingControl, ParamOnChange, SelectControl } from '../../../mol-plugin-ui/controls/parameters';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Clip } from '../../../mol-util/clip';
|
||||
import { StructureRepresentation3D } from '../../../mol-plugin-state/transforms/representation';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { CombinedColorControl } from '../../../mol-plugin-ui/controls/color';
|
||||
import { MarkerAction } from '../../../mol-util/marker-action';
|
||||
import { EveryLoci, Loci } from '../../../mol-model/loci';
|
||||
import { deepEqual } from '../../../mol-util';
|
||||
import { ColorValueParam, ColorParams, ColorProps, DimLightness, LightnessParams, LodParams, MesoscaleGroup, MesoscaleGroupProps, OpacityParams, SimpleClipParams, SimpleClipProps, createClipMapping, getClipObjects, getDistinctGroupColors, RootParams, MesoscaleState, getRoots, getAllGroups, getAllLeafGroups, getFilteredEntities, getAllFilteredEntities, getGroups, getEntities, getAllEntities, getEntityLabel, updateColors, getGraphicsModeProps, GraphicsMode, MesoscaleStateParams, setGraphicsCanvas3DProps, PatternParams, expandAllGroups } from '../data/state';
|
||||
import React from 'react';
|
||||
import { MesoscaleExplorerState } from '../app';
|
||||
import { StructureElement } from '../../../mol-model/structure/structure/element';
|
||||
import { PluginStateObject as PSO } from '../../../mol-plugin-state/objects';
|
||||
import { Structure } from '../../../mol-model/structure';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
|
||||
function centerLoci(plugin: PluginContext, loci: Loci, durationMs = 250) {
|
||||
const { canvas3d } = plugin;
|
||||
if (!canvas3d) return;
|
||||
|
||||
const sphere = Loci.getBoundingSphere(loci) || Sphere3D();
|
||||
const snapshot = canvas3d.camera.getCenter(sphere.center);
|
||||
canvas3d.requestCameraReset({ durationMs, snapshot });
|
||||
}
|
||||
|
||||
export class ModelInfo extends PluginUIComponent<{}, { isDisabled: boolean }> {
|
||||
state = {
|
||||
isDisabled: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
|
||||
this.setState({ isDisabled: v });
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
|
||||
if (!this.state.isDisabled && MesoscaleState.has(this.plugin) && MesoscaleState.ref(this.plugin) === e.ref) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get info() {
|
||||
if (!MesoscaleState.has(this.plugin)) return;
|
||||
|
||||
const state = MesoscaleState.get(this.plugin);
|
||||
if (!state.description && !state.link) return;
|
||||
|
||||
return {
|
||||
description: state.description,
|
||||
link: state.link,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const info = this.info;
|
||||
return info && <>
|
||||
<div className='msp-help-text'>
|
||||
<div>{info.description}</div>
|
||||
<div><a href={info.link} target='_blank'>Source</a></div>
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
const SelectionStyleParam = PD.Select('color+outline', PD.objectToOptions({
|
||||
'color+outline': 'Color & Outline',
|
||||
'color': 'Color',
|
||||
'outline': 'Outline'
|
||||
} as const));
|
||||
type SelectionStyle = typeof SelectionStyleParam['defaultValue']
|
||||
|
||||
export class SelectionInfo extends PluginUIComponent<{}, { isDisabled: boolean }> {
|
||||
state = {
|
||||
isDisabled: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
|
||||
this.setState({ isDisabled: v });
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.managers.structure.selection.events.changed, e => {
|
||||
if (!this.state.isDisabled) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get info() {
|
||||
const info: { label: string, key: string }[] = [];
|
||||
this.plugin.managers.structure.selection.entries.forEach((e, k) => {
|
||||
if (StructureElement.Loci.is(e.selection) && !StructureElement.Loci.isEmpty(e.selection)) {
|
||||
const cell = this.plugin.helpers.substructureParent.get(e.selection.structure);
|
||||
info.push({
|
||||
label: cell?.obj?.label || 'Unknown',
|
||||
key: k,
|
||||
});
|
||||
}
|
||||
});
|
||||
return info;
|
||||
}
|
||||
|
||||
find(label: string) {
|
||||
MesoscaleState.set(this.plugin, { filter: `"${label}"` });
|
||||
if (label) expandAllGroups(this.plugin);
|
||||
};
|
||||
|
||||
remove(key: string) {
|
||||
const e = this.plugin.managers.structure.selection.entries.get(key);
|
||||
if (!e) return;
|
||||
|
||||
const loci = Structure.toStructureElementLoci(e.selection.structure);
|
||||
this.plugin.managers.interactivity.lociSelects.deselect({ loci }, false);
|
||||
}
|
||||
|
||||
center(key: string) {
|
||||
const e = this.plugin.managers.structure.selection.entries.get(key);
|
||||
if (!e) return;
|
||||
|
||||
const loci = Structure.toStructureElementLoci(e.selection.structure);
|
||||
centerLoci(this.plugin, loci);
|
||||
}
|
||||
|
||||
get selection() {
|
||||
const info = this.info;
|
||||
if (!info.length) return <>
|
||||
<div className='msp-help-text'>
|
||||
<div>Use <i>ctrl+left click</i> to select entities, either on the 3D canvas or in the tree below</div>
|
||||
</div>
|
||||
</>;
|
||||
|
||||
return <>
|
||||
{info.map((entry, index) => {
|
||||
const label = <Button className={`msp-btn-tree-label`} noOverflow disabled={this.state.isDisabled}
|
||||
onClick={() => this.center(entry.key)}
|
||||
>
|
||||
<span title={entry.label}>{entry.label}</span>
|
||||
</Button>;
|
||||
const find = <IconButton svg={SearchSvg} toggleState={false} disabled={this.state.isDisabled} small onClick={() => this.find(entry.label)} />;
|
||||
const remove = <IconButton svg={CloseSvg} toggleState={false} disabled={this.state.isDisabled} onClick={() => this.remove(entry.key)} />;
|
||||
return <div key={index} className={`msp-flex-row`} style={{ margin: `1px 5px 1px ${1 * 10 + 5}px` }}>
|
||||
{label}
|
||||
{find}
|
||||
{remove}
|
||||
</div>;
|
||||
})}
|
||||
</>;
|
||||
}
|
||||
|
||||
get style() {
|
||||
const p = this.plugin.canvas3d?.props;
|
||||
if (!p) return;
|
||||
|
||||
if (p.renderer.dimStrength === 1 && p.marking.enabled) return 'color+outline';
|
||||
if (p.renderer.dimStrength === 1) return 'color';
|
||||
if (p.marking.enabled) return 'outline';
|
||||
}
|
||||
|
||||
setStyle(value: SelectionStyle) {
|
||||
if (value.includes('color') && value.includes('outline')) {
|
||||
this.plugin.canvas3d?.setProps({
|
||||
renderer: {
|
||||
dimStrength: 1,
|
||||
},
|
||||
marking: {
|
||||
enabled: true
|
||||
}
|
||||
});
|
||||
} else if (value.includes('color')) {
|
||||
this.plugin.canvas3d?.setProps({
|
||||
renderer: {
|
||||
dimStrength: 1,
|
||||
},
|
||||
marking: {
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
} else if (value.includes('outline')) {
|
||||
this.plugin.canvas3d?.setProps({
|
||||
renderer: {
|
||||
dimStrength: 0,
|
||||
selectStrength: 0.3,
|
||||
},
|
||||
marking: {
|
||||
enabled: true
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.plugin.canvas3d?.setProps({
|
||||
renderer: {
|
||||
dimStrength: 0,
|
||||
selectStrength: 0,
|
||||
},
|
||||
marking: {
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
renderStyle() {
|
||||
const style = this.style || '';
|
||||
return <div style={{ margin: '5px', marginBottom: '10px' }}>
|
||||
<SelectControl name={'Style'} param={SelectionStyleParam} value={style} onChange={(e) => { this.setStyle(e.value); }} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <>
|
||||
{this.renderStyle()}
|
||||
{this.selection}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
export class EntityControls extends PluginUIComponent<{}, { isDisabled: boolean }> {
|
||||
filterRef = React.createRef<HTMLInputElement>();
|
||||
prevFilter = '';
|
||||
filterFocus = false;
|
||||
|
||||
state = {
|
||||
isDisabled: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.state.events.object.created, e => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.state.events.object.removed, e => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
|
||||
this.setState({ isDisabled: v });
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
|
||||
if (!this.state.isDisabled && this.roots.some(r => e.cell === r) || (MesoscaleState.has(this.plugin) && MesoscaleState.ref(this.plugin) === e.ref)) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(): void {
|
||||
const filter = this.filter;
|
||||
if (this.filterFocus) {
|
||||
this.filterRef.current?.focus();
|
||||
this.prevFilter = filter;
|
||||
}
|
||||
}
|
||||
|
||||
get roots() {
|
||||
return getRoots(this.plugin);
|
||||
}
|
||||
|
||||
setGroupBy = (value: number) => {
|
||||
this.roots.forEach((c, i) => {
|
||||
if (c.state.isHidden && value === i || !c.state.isHidden && value !== i) {
|
||||
PluginCommands.State.ToggleVisibility(this.plugin, { state: c.parent!, ref: c.transform.ref });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
get groupBy() {
|
||||
const roots = this.roots;
|
||||
for (let i = 0, il = roots.length; i < il; ++i) {
|
||||
if (!roots[i].state.isHidden) return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
setFilter = (value: string) => {
|
||||
this.filterFocus = true;
|
||||
const filter = value.trim().replace(/\s+/gi, ' ');
|
||||
MesoscaleState.set(this.plugin, { filter });
|
||||
if (filter) expandAllGroups(this.plugin);
|
||||
};
|
||||
|
||||
get filter() {
|
||||
return MesoscaleState.has(this.plugin) ? MesoscaleState.get(this.plugin).filter : '';
|
||||
}
|
||||
|
||||
setGraphics = (graphics: GraphicsMode) => {
|
||||
MesoscaleState.set(this.plugin, { graphics });
|
||||
(this.plugin.customState as MesoscaleExplorerState).graphicsMode = graphics;
|
||||
|
||||
if (graphics === 'custom') return;
|
||||
|
||||
const update = this.plugin.state.data.build();
|
||||
|
||||
const { lodLevels, approximate, alphaThickness } = getGraphicsModeProps(graphics);
|
||||
|
||||
for (const r of getAllEntities(this.plugin)) {
|
||||
update.to(r).update(old => {
|
||||
if (old.type) {
|
||||
old.type.params.lodLevels = lodLevels;
|
||||
old.type.params.approximate = approximate;
|
||||
old.type.params.alphaThickness = alphaThickness;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const g of getAllGroups(this.plugin)) {
|
||||
update.to(g).update(old => {
|
||||
old.lod.lodLevels = lodLevels;
|
||||
old.lod.approximate = approximate;
|
||||
});
|
||||
}
|
||||
|
||||
update.commit();
|
||||
|
||||
setGraphicsCanvas3DProps(this.plugin, graphics);
|
||||
};
|
||||
|
||||
get graphics() {
|
||||
const customState = this.plugin.customState as MesoscaleExplorerState;
|
||||
return MesoscaleState.has(this.plugin) ? MesoscaleState.get(this.plugin).graphics : customState.graphicsMode;
|
||||
}
|
||||
|
||||
renderGraphics() {
|
||||
const graphics = this.graphics;
|
||||
return <div style={{ margin: '5px', marginBottom: '10px' }}>
|
||||
<SelectControl name={'Graphics'} param={MesoscaleStateParams.graphics} value={`${graphics}`} onChange={(e) => { this.setGraphics(e.value); }} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const roots = this.roots;
|
||||
if (roots.length === 0 || !MesoscaleState.has(this.plugin)) {
|
||||
return <>
|
||||
{this.renderGraphics()}
|
||||
</>;
|
||||
}
|
||||
|
||||
const disabled = this.state.isDisabled;
|
||||
const groupBy = this.groupBy;
|
||||
|
||||
const options: [string, string][] = [];
|
||||
roots.forEach((c, i) => {
|
||||
options.push([`${i}`, c.obj!.label]);
|
||||
});
|
||||
const groupParam = PD.Select(options[0][0], options);
|
||||
const root = roots.length === 1 ? roots[0] : roots[groupBy];
|
||||
|
||||
const filter = this.filter;
|
||||
|
||||
return <>
|
||||
{this.renderGraphics()}
|
||||
<div className={`msp-flex-row msp-control-row`} style={{ margin: '5px', marginBottom: '10px' }}>
|
||||
<input type='text' ref={this.filterRef}
|
||||
value={filter}
|
||||
placeholder='Search'
|
||||
onChange={e => this.setFilter(e.target.value)}
|
||||
disabled={disabled}
|
||||
onBlur={() => this.filterFocus = false}
|
||||
/>
|
||||
<IconButton svg={CloseSvg} toggleState={false} disabled={disabled} onClick={() => this.setFilter('')} />
|
||||
</div>
|
||||
{options.length > 1 && <div style={{ margin: '5px', marginBottom: '10px' }}>
|
||||
<SelectControl name={'Group By'} param={groupParam} value={`${groupBy}`} onChange={(e) => { this.setGroupBy(parseInt(e.value)); }} />
|
||||
</div>}
|
||||
<GroupNode filter={filter} cell={root} depth={0} />
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
class Node<P extends {}, S extends { isDisabled: boolean }> extends PluginUIComponent<P & { cell: StateObjectCell, depth: number }, S> {
|
||||
|
||||
is(e: State.ObjectEvent) {
|
||||
return e.ref === this.ref && e.state === this.props.cell.parent;
|
||||
}
|
||||
|
||||
get ref() {
|
||||
return this.props.cell.transform.ref;
|
||||
}
|
||||
|
||||
get cell() {
|
||||
return this.props.cell;
|
||||
}
|
||||
|
||||
get roots() {
|
||||
return getRoots(this.plugin);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
|
||||
this.setState({ isDisabled: v });
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
|
||||
if (!this.state.isDisabled && this.is(e)) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupNode extends Node<{ filter: string }, { isCollapsed: boolean, action?: 'color' | 'clip' | 'root', isDisabled: boolean }> {
|
||||
state = {
|
||||
isCollapsed: !!this.props.cell.state.isCollapsed,
|
||||
action: undefined,
|
||||
isDisabled: false,
|
||||
};
|
||||
|
||||
toggleExpanded = (e: React.MouseEvent<HTMLElement>) => {
|
||||
PluginCommands.State.ToggleExpanded(this.plugin, { state: this.cell.parent!, ref: this.ref });
|
||||
};
|
||||
|
||||
toggleColor = (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
this.setState({ action: this.state.action === 'color' ? undefined : 'color' });
|
||||
};
|
||||
|
||||
toggleClip = () => {
|
||||
this.setState({ action: this.state.action === 'clip' ? undefined : 'clip' });
|
||||
};
|
||||
|
||||
toggleRoot = () => {
|
||||
this.setState({ action: this.state.action === 'root' ? undefined : 'root' });
|
||||
};
|
||||
|
||||
highlight = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
this.plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
|
||||
for (const r of this.allFilteredEntities) {
|
||||
const repr = r.obj?.data.repr;
|
||||
if (repr) {
|
||||
this.plugin.canvas3d?.mark({ repr, loci: EveryLoci }, MarkerAction.Highlight);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
clearHighlight = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
this.plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
|
||||
e.currentTarget.blur();
|
||||
};
|
||||
|
||||
get groups() {
|
||||
return getGroups(this.plugin, this.cell.params?.values.tag);
|
||||
}
|
||||
|
||||
get allGroups() {
|
||||
const allGroups = getAllGroups(this.plugin, this.cell.params?.values.tag);
|
||||
allGroups.push(this.cell);
|
||||
return allGroups;
|
||||
}
|
||||
|
||||
get entities() {
|
||||
return getEntities(this.plugin, this.cell.params?.values.tag);
|
||||
}
|
||||
|
||||
get filteredEntities() {
|
||||
return getFilteredEntities(this.plugin, this.cell.params?.values.tag, this.props.filter);
|
||||
}
|
||||
|
||||
get allEntities() {
|
||||
return getAllEntities(this.plugin, this.cell.params?.values.tag);
|
||||
}
|
||||
|
||||
get allFilteredEntities() {
|
||||
return getAllFilteredEntities(this.plugin, this.cell.params?.values.tag, this.props.filter);
|
||||
}
|
||||
|
||||
toggleVisible = (e: React.MouseEvent<HTMLElement>) => {
|
||||
PluginCommands.State.ToggleVisibility(this.plugin, { state: this.cell.parent!, ref: this.ref });
|
||||
const isHidden = this.cell.state.isHidden;
|
||||
|
||||
for (const r of this.allFilteredEntities) {
|
||||
this.plugin.state.data.updateCellState(r.transform.ref, { isHidden });
|
||||
}
|
||||
|
||||
this.plugin.build().to(this.ref).update(old => {
|
||||
old.hidden = isHidden;
|
||||
}).commit();
|
||||
};
|
||||
|
||||
updateColor = (values: ColorProps) => {
|
||||
const update = this.plugin.state.data.build();
|
||||
const { value, type, lightness, alpha } = values;
|
||||
|
||||
const entities = this.filteredEntities;
|
||||
|
||||
let groupColors: Color[] = [];
|
||||
|
||||
if (type === 'generate') {
|
||||
groupColors = getDistinctGroupColors(entities.length, value, values.variability, values.shift);
|
||||
}
|
||||
|
||||
for (let i = 0; i < entities.length; ++i) {
|
||||
const c = type === 'generate' ? groupColors[i] : value;
|
||||
update.to(entities[i]).update(old => {
|
||||
if (old.type) {
|
||||
old.colorTheme.params.value = c;
|
||||
old.colorTheme.params.lightness = lightness;
|
||||
old.type.params.alpha = alpha;
|
||||
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
|
||||
} else {
|
||||
old.coloring.params.color = c;
|
||||
old.coloring.params.lightness = lightness;
|
||||
old.alpha = alpha;
|
||||
old.xrayShaded = alpha < 1 ? true : false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update.to(this.ref).update(old => {
|
||||
old.color = values;
|
||||
});
|
||||
|
||||
for (const r of this.roots) {
|
||||
update.to(r).update(old => {
|
||||
old.color.type = 'custom';
|
||||
});
|
||||
}
|
||||
|
||||
update.commit();
|
||||
};
|
||||
|
||||
updateRoot = async (values: PD.Values) => {
|
||||
await updateColors(this.plugin, values, this.cell.params?.values.tag, this.props.filter);
|
||||
|
||||
const update = this.plugin.state.data.build();
|
||||
|
||||
for (const r of this.roots) {
|
||||
if (r !== this.cell) {
|
||||
update.to(r).update(old => {
|
||||
old.color.type = 'custom';
|
||||
});
|
||||
const others = getAllLeafGroups(this.plugin, r.params?.values.tag);
|
||||
for (const o of others) {
|
||||
update.to(o).update(old => {
|
||||
old.color.type = 'custom';
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update.to(this.ref).update(old => {
|
||||
old.color = values;
|
||||
});
|
||||
|
||||
update.commit();
|
||||
};
|
||||
|
||||
updateClip = (values: PD.Values) => {
|
||||
const update = this.plugin.state.data.build();
|
||||
const clipObjects = getClipObjects(values as SimpleClipProps, this.plugin.canvas3d!.boundingSphere);
|
||||
|
||||
for (const r of this.allFilteredEntities) {
|
||||
update.to(r).update(old => {
|
||||
if (old.type) {
|
||||
old.type.params.clip.objects = clipObjects;
|
||||
} else {
|
||||
old.clip.objects = clipObjects;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const g of this.allGroups) {
|
||||
update.to(g).update(old => {
|
||||
old.clip = values;
|
||||
});
|
||||
}
|
||||
|
||||
update.commit();
|
||||
};
|
||||
|
||||
updateLod = (values: PD.Values) => {
|
||||
MesoscaleState.set(this.plugin, { graphics: 'custom' });
|
||||
(this.plugin.customState as MesoscaleExplorerState).graphicsMode = 'custom';
|
||||
|
||||
const update = this.plugin.state.data.build();
|
||||
|
||||
for (const r of this.allFilteredEntities) {
|
||||
update.to(r).update(old => {
|
||||
if (old.type) {
|
||||
old.type.params.lodLevels = values.lodLevels;
|
||||
old.type.params.cellSize = values.cellSize;
|
||||
old.type.params.batchSize = values.batchSize;
|
||||
old.type.params.approximate = values.approximate;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const g of this.allGroups) {
|
||||
update.to(g).update(old => {
|
||||
old.lod = values;
|
||||
});
|
||||
}
|
||||
|
||||
update.commit();
|
||||
};
|
||||
|
||||
update = (props: MesoscaleGroupProps) => {
|
||||
this.plugin.state.data.build().to(this.ref).update(props);
|
||||
};
|
||||
|
||||
renderColor() {
|
||||
const color = this.cell.params?.values.color;
|
||||
if (this.cell.params?.values.color.type === 'uniform') {
|
||||
const style = {
|
||||
backgroundColor: Color.toStyle(color.value),
|
||||
minWidth: 32,
|
||||
width: 32,
|
||||
borderRight: `6px solid ${Color.toStyle(Color.lighten(color.value, color.lightness))}`
|
||||
};
|
||||
return <Button style={style} onClick={this.toggleColor} />;
|
||||
} else if (this.cell.params?.values.color.type === 'generate') {
|
||||
const style = {
|
||||
minWidth: 32,
|
||||
width: 32,
|
||||
borderRight: `6px solid ${Color.toStyle(Color.lighten(color.value, color.lightness))}`
|
||||
};
|
||||
return <IconButton style={style} svg={BrushSvg} toggleState={false} small onClick={this.toggleColor} />;
|
||||
} else {
|
||||
return <IconButton svg={BrushSvg} toggleState={false} small onClick={this.toggleColor} />;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.allFilteredEntities.length === 0) return;
|
||||
|
||||
const state = this.cell.state;
|
||||
const disabled = false;
|
||||
const groupLabel = this.cell.obj!.label;
|
||||
const depth = this.props.depth;
|
||||
const colorValue = this.cell.params?.values.color;
|
||||
const rootValue = this.cell.params?.values.color;
|
||||
const clipValue = this.cell.params?.values.clip;
|
||||
const lodValue = this.cell.params?.values.lod;
|
||||
const isRoot = this.cell.params?.values.root;
|
||||
|
||||
const groups = this.groups;
|
||||
const entities = this.entities;
|
||||
|
||||
const label = <Button className={`msp-btn-tree-label`} noOverflow disabled={disabled}
|
||||
onMouseEnter={this.highlight}
|
||||
onMouseLeave={this.clearHighlight}
|
||||
>
|
||||
<span title={groupLabel}>{groupLabel}</span>
|
||||
</Button>;
|
||||
|
||||
const expand = <IconButton svg={state.isCollapsed ? ArrowRightSvg : ArrowDropDownSvg} flex='20px' disabled={disabled} onClick={this.toggleExpanded} transparent className='msp-no-hover-outline' style={{ visibility: groups.length > 0 || entities.length > 0 ? 'visible' : 'hidden' }} />;
|
||||
const color = (entities.length > 0 && !isRoot) && this.renderColor();
|
||||
const root = (isRoot && this.allGroups.length > 1) && <IconButton svg={BrushSvg} toggleState={false} disabled={disabled} small onClick={this.toggleRoot} />;
|
||||
const clip = <IconButton svg={ContentCutSvg} toggleState={false} disabled={disabled} small onClick={this.toggleClip} />;
|
||||
const visibility = <IconButton svg={state.isHidden ? VisibilityOffOutlinedSvg : VisibilityOutlinedSvg} toggleState={false} disabled={disabled} small onClick={this.toggleVisible} />;
|
||||
|
||||
return <>
|
||||
<div className={`msp-flex-row`} style={{ margin: `1px 5px 1px ${depth * 10 + 5}px` }}>
|
||||
{expand}
|
||||
{label}
|
||||
{root || color}
|
||||
{clip}
|
||||
{visibility}
|
||||
</div>
|
||||
{this.state.action === 'color' && <div style={{ marginRight: 5 }} className='msp-accent-offset'>
|
||||
<ControlGroup header='Color' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleColor}
|
||||
topRightIcon={CloseSvg} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
|
||||
<ParameterControls params={ColorParams} values={colorValue} onChangeValues={this.updateColor} />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
{this.state.action === 'clip' && <div style={{ marginRight: 5 }} className='msp-accent-offset'>
|
||||
<ControlGroup header='Clip' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleClip}
|
||||
topRightIcon={CloseSvg} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
|
||||
<ParameterControls params={SimpleClipParams} values={clipValue} onChangeValues={this.updateClip} />
|
||||
<ParameterControls params={LodParams} values={lodValue} onChangeValues={this.updateLod} />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
{this.state.action === 'root' && <div style={{ marginRight: 5 }} className='msp-accent-offset'>
|
||||
<ControlGroup header='Color' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleRoot}
|
||||
topRightIcon={CloseSvg} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
|
||||
<ParameterControls params={RootParams} values={rootValue} onChangeValues={this.updateRoot} />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
{(!state.isCollapsed) && <>
|
||||
{groups.map(c => {
|
||||
return <GroupNode filter={this.props.filter} cell={c} depth={depth + 1} key={c.transform.ref} />;
|
||||
})}
|
||||
{this.filteredEntities.map(c => {
|
||||
return <EntityNode cell={c} depth={depth + 1} key={c.transform.ref} />;
|
||||
})}
|
||||
</>}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
export class EntityNode extends Node<{}, { action?: 'color' | 'clip', isDisabled: boolean }> {
|
||||
state = {
|
||||
action: undefined,
|
||||
isDisabled: false,
|
||||
};
|
||||
|
||||
clipMapping = createClipMapping(this);
|
||||
|
||||
get groups() {
|
||||
return this.plugin.state.data.select(StateSelection.Generators.ofTransformer(MesoscaleGroup)
|
||||
.filter(c => !!this.cell.transform.tags?.includes(c.params?.values.tag)));
|
||||
}
|
||||
|
||||
toggleVisible = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
PluginCommands.State.ToggleVisibility(this.plugin, { state: this.props.cell.parent!, ref: this.ref });
|
||||
e.currentTarget.blur();
|
||||
};
|
||||
|
||||
toggleColor = (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
if (e?.ctrlKey) {
|
||||
this.updateLightness({ lightness: this.lightnessValue?.lightness ? 0 : DimLightness });
|
||||
e.preventDefault();
|
||||
} else {
|
||||
this.setState({ action: this.state.action === 'color' ? undefined : 'color' });
|
||||
}
|
||||
};
|
||||
|
||||
toggleClip = () => {
|
||||
this.setState({ action: this.state.action === 'clip' ? undefined : 'clip' });
|
||||
};
|
||||
|
||||
highlight = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
this.plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
|
||||
const repr = this.cell?.obj?.data.repr;
|
||||
if (repr) {
|
||||
this.plugin.canvas3d?.mark({ repr, loci: EveryLoci }, MarkerAction.Highlight);
|
||||
}
|
||||
e.currentTarget.blur();
|
||||
};
|
||||
|
||||
clearHighlight = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
this.plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
|
||||
e.currentTarget.blur();
|
||||
};
|
||||
|
||||
toggleSelect = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
const cell = this.cell as StateObjectCell<PSO.Molecule.Structure.Representation3D | PSO.Shape.Representation3D> | undefined;
|
||||
if (!(cell?.obj?.data.sourceData instanceof Structure)) return;
|
||||
|
||||
const loci = Structure.toStructureElementLoci(cell.obj.data.sourceData);
|
||||
this.plugin.managers.interactivity.lociSelects.toggle({ loci }, false);
|
||||
};
|
||||
|
||||
center = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
const cell = this.cell as StateObjectCell<PSO.Molecule.Structure.Representation3D | PSO.Shape.Representation3D> | undefined;
|
||||
if (!(cell?.obj?.data.sourceData instanceof Structure)) return;
|
||||
|
||||
const loci = Structure.toStructureElementLoci(cell.obj.data.sourceData);
|
||||
centerLoci(this.plugin, loci);
|
||||
};
|
||||
|
||||
handleClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||
if (e.ctrlKey) {
|
||||
this.toggleSelect(e);
|
||||
} else {
|
||||
this.center(e);
|
||||
}
|
||||
};
|
||||
|
||||
get colorValue(): Color | undefined {
|
||||
return this.cell.transform.params?.colorTheme?.params.value ?? this.cell.transform.params?.coloring?.params.color;
|
||||
}
|
||||
|
||||
get lightnessValue(): { lightness: number } | undefined {
|
||||
return {
|
||||
lightness: this.cell.transform.params?.colorTheme?.params.lightness ?? this.cell.transform.params?.coloring?.params.lightness ?? 0
|
||||
};
|
||||
}
|
||||
|
||||
get opacityValue(): { alpha: number } | undefined {
|
||||
return {
|
||||
alpha: this.cell.transform.params?.type?.params.alpha ?? this.cell.transform.params?.alpha ?? 1
|
||||
};
|
||||
}
|
||||
|
||||
get clipValue(): Clip.Props | undefined {
|
||||
return this.cell.transform.params.type?.params.clip ?? this.cell.transform.params.clip;
|
||||
}
|
||||
|
||||
get lodValue(): PD.Values<typeof LodParams> | undefined {
|
||||
const p = this.cell.transform.params?.type?.params;
|
||||
if (!p) return;
|
||||
return {
|
||||
lodLevels: p.lodLevels,
|
||||
cellSize: p.cellSize,
|
||||
batchSize: p.batchSize,
|
||||
approximate: p.approximate,
|
||||
};
|
||||
}
|
||||
|
||||
get patternValue(): { amplitude: number, frequency: number } | undefined {
|
||||
const p = this.cell.transform.params;
|
||||
if (p.type) return;
|
||||
return {
|
||||
amplitude: p.bumpAmplitude,
|
||||
frequency: p.bumpFrequency * 10,
|
||||
};
|
||||
}
|
||||
|
||||
updateColor: ParamOnChange = ({ value }) => {
|
||||
const update = this.plugin.state.data.build();
|
||||
for (const g of this.groups) {
|
||||
update.to(g.transform.ref).update(old => {
|
||||
old.color.type = 'custom';
|
||||
});
|
||||
}
|
||||
for (const r of this.roots) {
|
||||
update.to(r).update(old => {
|
||||
old.color.type = 'custom';
|
||||
});
|
||||
}
|
||||
update.to(this.ref).update(old => {
|
||||
if (old.colorTheme) {
|
||||
old.colorTheme.params.value = value;
|
||||
} else if (old.coloring) {
|
||||
old.coloring.params.color = value;
|
||||
}
|
||||
});
|
||||
update.commit();
|
||||
};
|
||||
|
||||
updateLightness = (values: PD.Values) => {
|
||||
return this.plugin.build().to(this.ref).update(old => {
|
||||
if (old.colorTheme) {
|
||||
old.colorTheme.params.lightness = values.lightness;
|
||||
} else if (old.coloring) {
|
||||
old.coloring.params.lightness = values.lightness;
|
||||
}
|
||||
}).commit();
|
||||
};
|
||||
|
||||
updateOpacity = (values: PD.Values) => {
|
||||
return this.plugin.build().to(this.ref).update(old => {
|
||||
if (old.type) {
|
||||
old.type.params.alpha = values.alpha;
|
||||
old.type.params.xrayShaded = values.alpha < 1 ? 'inverted' : false;
|
||||
} else {
|
||||
old.alpha = values.alpha;
|
||||
old.xrayShaded = values.alpha < 1 ? true : false;
|
||||
}
|
||||
}).commit();
|
||||
};
|
||||
|
||||
updateClip = (props: Clip.Props) => {
|
||||
const params = this.cell.transform.params;
|
||||
const clip = params.type ? params.type.params.clip : params.clip;
|
||||
if (!PD.areEqual(Clip.Params, clip, props)) {
|
||||
this.plugin.build().to(this.ref).update(old => {
|
||||
if (old.type) {
|
||||
old.type.params.clip = props;
|
||||
} else {
|
||||
old.clip = props;
|
||||
}
|
||||
}).commit();
|
||||
}
|
||||
};
|
||||
|
||||
updateLod = (values: PD.Values) => {
|
||||
const params = this.cell.transform.params as StateTransformer.Params<StructureRepresentation3D>;
|
||||
if (!params.type) return;
|
||||
|
||||
MesoscaleState.set(this.plugin, { graphics: 'custom' });
|
||||
(this.plugin.customState as MesoscaleExplorerState).graphicsMode = 'custom';
|
||||
|
||||
if (!deepEqual(params.type.params.lodLevels, values.lodLevels) || params.type.params.cellSize !== values.cellSize || params.type.params.batchSize !== values.batchSize || params.type.params.approximate !== values.approximate) {
|
||||
this.plugin.build().to(this.ref).update(old => {
|
||||
old.type.params.lodLevels = values.lodLevels;
|
||||
old.type.params.cellSize = values.cellSize;
|
||||
old.type.params.batchSize = values.batchSize;
|
||||
old.type.params.approximate = values.approximate;
|
||||
}).commit();
|
||||
}
|
||||
};
|
||||
|
||||
updatePattern = (values: PD.Values) => {
|
||||
return this.plugin.build().to(this.ref).update(old => {
|
||||
if (!old.type) {
|
||||
old.bumpAmplitude = values.amplitude;
|
||||
old.bumpFrequency = values.frequency / 10;
|
||||
}
|
||||
}).commit();
|
||||
};
|
||||
|
||||
render() {
|
||||
const cellState = this.cell.state;
|
||||
const disabled = this.cell.status !== 'error' && this.cell.status !== 'ok';
|
||||
const depth = this.props.depth;
|
||||
const colorValue = this.colorValue;
|
||||
const lightnessValue = this.lightnessValue;
|
||||
const opacityValue = this.opacityValue;
|
||||
const lodValue = this.lodValue;
|
||||
const patternValue = this.patternValue;
|
||||
|
||||
const l = getEntityLabel(this.plugin, this.cell);
|
||||
const label = <Button className={`msp-btn-tree-label msp-type-class-${this.cell.obj!.type.typeClass}`} noOverflow disabled={disabled}
|
||||
onClick={this.handleClick}
|
||||
onMouseEnter={this.highlight}
|
||||
onMouseLeave={this.clearHighlight}
|
||||
>
|
||||
<span title={l}>{l}</span>
|
||||
</Button>;
|
||||
|
||||
const color = colorValue !== undefined && <Button style={{ backgroundColor: Color.toStyle(colorValue), minWidth: 32, width: 32, borderRight: `6px solid ${Color.toStyle(Color.lighten(colorValue, lightnessValue?.lightness || 0))}` }} onClick={this.toggleColor} />;
|
||||
const clip = <IconButton svg={ContentCutSvg} toggleState={false} disabled={disabled} small onClick={this.toggleClip} />;
|
||||
const visibility = <IconButton svg={cellState.isHidden ? VisibilityOffOutlinedSvg : VisibilityOutlinedSvg} toggleState={false} disabled={disabled} small onClick={this.toggleVisible} />;
|
||||
|
||||
return <>
|
||||
<div className={`msp-flex-row`} style={{ margin: `1px 5px 1px ${depth * 10 + 5}px` }}>
|
||||
{label}
|
||||
{color}
|
||||
{clip}
|
||||
{visibility}
|
||||
</div>
|
||||
{this.state.action === 'color' && colorValue !== void 0 && <div style={{ marginRight: 5 }} className='msp-accent-offset'>
|
||||
<ControlGroup header='Color' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleColor}
|
||||
topRightIcon={CloseSvg} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
|
||||
<CombinedColorControl param={ColorValueParam} value={colorValue ?? Color(0xFFFFFF)} onChange={this.updateColor} name='color' hideNameRow />
|
||||
<ParameterControls params={LightnessParams} values={lightnessValue} onChangeValues={this.updateLightness} />
|
||||
<ParameterControls params={OpacityParams} values={opacityValue} onChangeValues={this.updateOpacity} />
|
||||
{patternValue && <ParameterControls params={PatternParams} values={patternValue} onChangeValues={this.updatePattern} />}
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
{this.state.action === 'clip' && <div style={{ marginRight: 5 }} className='msp-accent-offset'>
|
||||
<ControlGroup header='Clip' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleClip}
|
||||
topRightIcon={CloseSvg} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
|
||||
<ParameterMappingControl mapping={this.clipMapping} />
|
||||
{lodValue && <ParameterControls params={LodParams} values={lodValue} onChangeValues={this.updateLod} />}
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
98
src/apps/mesoscale-explorer/ui/panels.tsx
Normal file
98
src/apps/mesoscale-explorer/ui/panels.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mp4EncoderUI } from '../../../extensions/mp4-export/ui';
|
||||
import { PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { SectionHeader } from '../../../mol-plugin-ui/controls/common';
|
||||
import { MesoscaleExplorerState } from '../app';
|
||||
import { MesoscaleState } from '../data/state';
|
||||
import { EntityControls, ModelInfo, SelectionInfo } from './entities';
|
||||
import { LoaderControls, ExampleControls, SessionControls, SnapshotControls, DatabaseControls } from './states';
|
||||
|
||||
const Spacer = () => <div style={{ height: '2em' }} />;
|
||||
|
||||
export class LeftPanel extends PluginUIComponent {
|
||||
render() {
|
||||
const customState = this.plugin.customState as MesoscaleExplorerState;
|
||||
|
||||
return <div className='msp-scrollable-container'>
|
||||
<SectionHeader title='Database' />
|
||||
<DatabaseControls />
|
||||
<Spacer />
|
||||
|
||||
<SectionHeader title='Open' />
|
||||
<LoaderControls />
|
||||
<Spacer />
|
||||
|
||||
{customState.examples?.length && <>
|
||||
<SectionHeader title='Example' />
|
||||
<ExampleControls />
|
||||
<Spacer />
|
||||
</>}
|
||||
|
||||
<SectionHeader title='Session' />
|
||||
<SessionControls />
|
||||
<Spacer />
|
||||
|
||||
<SectionHeader title='Snapshots' />
|
||||
<SnapshotControls />
|
||||
<Spacer />
|
||||
|
||||
<Mp4EncoderUI />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class RightPanel extends PluginUIComponent<{}, { isDisabled: boolean }> {
|
||||
state = {
|
||||
isDisabled: false,
|
||||
};
|
||||
|
||||
get hasModelInfo() {
|
||||
return (
|
||||
MesoscaleState.has(this.plugin) &&
|
||||
!!(MesoscaleState.get(this.plugin).description ||
|
||||
MesoscaleState.get(this.plugin).link)
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
|
||||
this.setState({ isDisabled: v });
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
|
||||
if (!this.state.isDisabled && MesoscaleState.has(this.plugin) && MesoscaleState.ref(this.plugin) === e.ref) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.managers.structure.selection.events.changed, e => {
|
||||
if (!this.state.isDisabled) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className='msp-scrollable-container'>
|
||||
{this.hasModelInfo && <>
|
||||
<SectionHeader title='Model' />
|
||||
<ModelInfo />
|
||||
<Spacer />
|
||||
</>}
|
||||
|
||||
<>
|
||||
<SectionHeader title='Selection' />
|
||||
<SelectionInfo />
|
||||
<Spacer />
|
||||
</>
|
||||
|
||||
<SectionHeader title='Entities' />
|
||||
<EntityControls />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
363
src/apps/mesoscale-explorer/ui/states.tsx
Normal file
363
src/apps/mesoscale-explorer/ui/states.tsx
Normal file
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
import { MmcifProvider } from '../../../mol-plugin-state/formats/trajectory';
|
||||
import { PluginStateObject } from '../../../mol-plugin-state/objects';
|
||||
import { PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { Button, ExpandGroup } from '../../../mol-plugin-ui/controls/common';
|
||||
import { GetAppSvg, Icon, OpenInBrowserSvg } from '../../../mol-plugin-ui/controls/icons';
|
||||
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
|
||||
import { LocalStateSnapshotList, LocalStateSnapshotParams, LocalStateSnapshots } from '../../../mol-plugin-ui/state/snapshots';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateAction, StateObjectRef, StateTransform } from '../../../mol-state';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { Color } from '../../../mol-util/color/color';
|
||||
import { getFileNameInfo } from '../../../mol-util/file-info';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { ExampleEntry, MesoscaleExplorerState } from '../app';
|
||||
import { createCellpackHierarchy } from '../data/cellpack/preset';
|
||||
import { createGenericHierarchy } from '../data/generic/preset';
|
||||
import { createMmcifHierarchy } from '../data/mmcif/preset';
|
||||
import { createPetworldHierarchy } from '../data/petworld/preset';
|
||||
import { MesoscaleState, MesoscaleStateObject, setGraphicsCanvas3DProps } from '../data/state';
|
||||
|
||||
function adjustPluginProps(ctx: PluginContext) {
|
||||
ctx.managers.interactivity.setProps({ granularity: 'chain' });
|
||||
ctx.canvas3d?.setProps({
|
||||
multiSample: { mode: 'off' },
|
||||
cameraClipping: { far: false, minNear: 50 },
|
||||
sceneRadiusFactor: 2,
|
||||
renderer: {
|
||||
colorMarker: true,
|
||||
highlightColor: Color(0xffffff),
|
||||
highlightStrength: 0,
|
||||
selectColor: Color(0xffffff),
|
||||
selectStrength: 0,
|
||||
dimColor: Color(0xffffff),
|
||||
dimStrength: 1,
|
||||
markerPriority: 2,
|
||||
interiorColorFlag: false,
|
||||
interiorDarkening: 0.15,
|
||||
exposure: 1.1,
|
||||
xrayEdgeFalloff: 3,
|
||||
},
|
||||
marking: {
|
||||
enabled: true,
|
||||
highlightEdgeColor: Color(0x999999),
|
||||
selectEdgeColor: Color(0xffff00),
|
||||
highlightEdgeStrength: 1,
|
||||
selectEdgeStrength: 1,
|
||||
ghostEdgeStrength: 1,
|
||||
innerEdgeFactor: 2.5,
|
||||
edgeScale: 2,
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
multiScale: {
|
||||
name: 'on',
|
||||
params: {
|
||||
levels: [
|
||||
{ radius: 2, bias: 1.0 },
|
||||
{ radius: 5, bias: 1.0 },
|
||||
{ radius: 8, bias: 1.0 },
|
||||
{ radius: 11, bias: 1.0 },
|
||||
],
|
||||
nearThreshold: 10,
|
||||
farThreshold: 1500,
|
||||
}
|
||||
},
|
||||
radius: 5,
|
||||
bias: 1,
|
||||
blurKernelSize: 11,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
}
|
||||
},
|
||||
shadow: {
|
||||
name: 'on',
|
||||
params: {
|
||||
bias: 0.6,
|
||||
maxDistance: 80,
|
||||
steps: 3,
|
||||
tolerance: 1.0,
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
name: 'on',
|
||||
params: {
|
||||
scale: 1,
|
||||
threshold: 0.15,
|
||||
color: Color(0x000000),
|
||||
includeTransparent: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { graphics } = MesoscaleState.get(ctx);
|
||||
setGraphicsCanvas3DProps(ctx, graphics);
|
||||
}
|
||||
|
||||
async function createHierarchy(ctx: PluginContext, ref: string) {
|
||||
const parsed = await MmcifProvider.parse(ctx, ref);
|
||||
|
||||
const tr = StateObjectRef.resolveAndCheck(ctx.state.data, parsed.trajectory)?.obj?.data;
|
||||
if (!tr) throw new Error('no trajectory');
|
||||
|
||||
if (!MmcifFormat.is(tr.representative.sourceData)) {
|
||||
throw new Error('not mmcif');
|
||||
}
|
||||
|
||||
const { frame, db } = tr.representative.sourceData.data;
|
||||
|
||||
let hasCellpackAssemblyMethodDetails = false;
|
||||
const { method_details } = db.pdbx_struct_assembly;
|
||||
for (let i = 0, il = method_details.rowCount; i < il; ++i) {
|
||||
if (method_details.value(i).toUpperCase() === 'CELLPACK') {
|
||||
hasCellpackAssemblyMethodDetails = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame.categories.pdbx_model) {
|
||||
await createPetworldHierarchy(ctx, parsed.trajectory);
|
||||
} else if (
|
||||
frame.header.toUpperCase().includes('CELLPACK') ||
|
||||
hasCellpackAssemblyMethodDetails
|
||||
) {
|
||||
await createCellpackHierarchy(ctx, parsed.trajectory);
|
||||
} else {
|
||||
await createMmcifHierarchy(ctx, parsed.trajectory);
|
||||
}
|
||||
}
|
||||
|
||||
async function reset(ctx: PluginContext) {
|
||||
const customState = ctx.customState as MesoscaleExplorerState;
|
||||
delete customState.stateRef;
|
||||
customState.stateCache = {};
|
||||
ctx.managers.asset.clear();
|
||||
|
||||
await PluginCommands.State.Snapshots.Clear(ctx);
|
||||
await PluginCommands.State.RemoveObject(ctx, { state: ctx.state.data, ref: StateTransform.RootRef });
|
||||
|
||||
await MesoscaleState.init(ctx);
|
||||
adjustPluginProps(ctx);
|
||||
}
|
||||
|
||||
export async function loadExampleEntry(ctx: PluginContext, entry: ExampleEntry) {
|
||||
const { url, type } = entry;
|
||||
await loadUrl(ctx, url, type);
|
||||
MesoscaleState.set(ctx, {
|
||||
description: entry.description || entry.label,
|
||||
link: entry.link,
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadUrl(ctx: PluginContext, url: string, type: 'molx' | 'molj' | 'cif' | 'bcif') {
|
||||
if (type === 'molx' || type === 'molj') {
|
||||
await PluginCommands.State.Snapshots.OpenUrl(ctx, { url, type });
|
||||
} else {
|
||||
await reset(ctx);
|
||||
const isBinary = type === 'bcif';
|
||||
const data = await ctx.builders.data.download({ url, isBinary });
|
||||
await createHierarchy(ctx, data.ref);
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadPdb(ctx: PluginContext, id: string) {
|
||||
await reset(ctx);
|
||||
const url = `https://models.rcsb.org/${id.toUpperCase()}.bcif`;
|
||||
const data = await ctx.builders.data.download({ url, isBinary: true });
|
||||
await createHierarchy(ctx, data.ref);
|
||||
}
|
||||
|
||||
export async function loadPdbDev(ctx: PluginContext, id: string) {
|
||||
await reset(ctx);
|
||||
const nId = id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`;
|
||||
const url = `https://pdb-dev.wwpdb.org/bcif/${nId.toUpperCase()}.bcif`;
|
||||
const data = await ctx.builders.data.download({ url, isBinary: true });
|
||||
await createHierarchy(ctx, data.ref);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export const LoadDatabase = StateAction.build({
|
||||
display: { name: 'Database', description: 'Load from Database' },
|
||||
params: (a, ctx: PluginContext) => {
|
||||
return {
|
||||
source: PD.Select('pdb', PD.objectToOptions({ pdb: 'PDB', pdbDev: 'PDB-Dev' })),
|
||||
entry: PD.Text(''),
|
||||
};
|
||||
},
|
||||
from: PluginStateObject.Root
|
||||
})(({ params }, ctx: PluginContext) => Task.create('Loading from database...', async taskCtx => {
|
||||
if (params.source === 'pdb') {
|
||||
await loadPdb(ctx, params.entry);
|
||||
} else if (params.source === 'pdbDev') {
|
||||
await loadPdbDev(ctx, params.entry);
|
||||
}
|
||||
}));
|
||||
|
||||
export const LoadExample = StateAction.build({
|
||||
display: { name: 'Load', description: 'Load an example' },
|
||||
params: (a, ctx: PluginContext) => {
|
||||
const entries = (ctx.customState as MesoscaleExplorerState).examples || [];
|
||||
return {
|
||||
entry: PD.Select(0, entries.map((s, i) => [i, s.label])),
|
||||
};
|
||||
},
|
||||
from: PluginStateObject.Root
|
||||
})(({ params }, ctx: PluginContext) => Task.create('Loading example...', async taskCtx => {
|
||||
const entries = (ctx.customState as MesoscaleExplorerState).examples || [];
|
||||
await loadExampleEntry(ctx, entries[params.entry]);
|
||||
}));
|
||||
|
||||
export const LoadModel = StateAction.build({
|
||||
display: { name: 'Load', description: 'Load a model' },
|
||||
params: {
|
||||
files: PD.FileList({ accept: '.cif,.bcif,.cif.gz,.bcif.gz,.zip', multiple: true, description: 'mmCIF or Cellpack- or Petworld-style cif file.', label: 'File(s)' }),
|
||||
},
|
||||
from: PluginStateObject.Root
|
||||
})(({ params }, ctx: PluginContext) => Task.create('Loading model...', async taskCtx => {
|
||||
if (params.files === null || params.files.length === 0) {
|
||||
ctx.log.error('No file(s) selected');
|
||||
return;
|
||||
}
|
||||
|
||||
await reset(ctx);
|
||||
|
||||
const firstFile = params.files[0];
|
||||
const firstInfo = getFileNameInfo(firstFile.file!.name);
|
||||
|
||||
if (firstInfo.name.endsWith('zip')) {
|
||||
try {
|
||||
await createGenericHierarchy(ctx, firstFile);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
ctx.log.error(`Error opening file '${firstFile.name}'`);
|
||||
}
|
||||
} else {
|
||||
for (const file of params.files) {
|
||||
try {
|
||||
const info = getFileNameInfo(file.file!.name);
|
||||
if (!['cif', 'bcif'].includes(info.ext)) continue;
|
||||
|
||||
const isBinary = ctx.dataFormats.binaryExtensions.has(info.ext);
|
||||
const { data } = await ctx.builders.data.readFile({ file, isBinary });
|
||||
await createHierarchy(ctx, data.ref);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
ctx.log.error(`Error opening file '${file.name}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
//
|
||||
|
||||
export class DatabaseControls extends PluginUIComponent {
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div style={{ margin: '5px' }}>
|
||||
<ApplyActionControl state={this.plugin.state.data} action={LoadDatabase} nodeRef={this.plugin.state.data.tree.root.ref} applyLabel={'Load'} hideHeader />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class LoaderControls extends PluginUIComponent {
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div style={{ margin: '5px' }}>
|
||||
<ApplyActionControl state={this.plugin.state.data} action={LoadModel} nodeRef={this.plugin.state.data.tree.root.ref} applyLabel={'Load'} hideHeader />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExampleControls extends PluginUIComponent {
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div style={{ margin: '5px' }}>
|
||||
<ApplyActionControl state={this.plugin.state.data} action={LoadExample} nodeRef={this.plugin.state.data.tree.root.ref} applyLabel={'Load'} hideHeader />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export async function openState(ctx: PluginContext, file: File) {
|
||||
const customState = ctx.customState as MesoscaleExplorerState;
|
||||
delete customState.stateRef;
|
||||
customState.stateCache = {};
|
||||
ctx.managers.asset.clear();
|
||||
|
||||
await PluginCommands.State.Snapshots.Clear(ctx);
|
||||
await PluginCommands.State.Snapshots.OpenFile(ctx, { file });
|
||||
|
||||
const cell = ctx.state.data.selectQ(q => q.ofType(MesoscaleStateObject))[0];
|
||||
if (!cell) throw new Error('Missing MesoscaleState');
|
||||
|
||||
customState.stateRef = cell.transform.ref;
|
||||
customState.graphicsMode = cell.obj?.data.graphics || customState.graphicsMode;
|
||||
}
|
||||
|
||||
export class SessionControls extends PluginUIComponent {
|
||||
downloadToFileZip = () => {
|
||||
PluginCommands.State.Snapshots.DownloadToFile(this.plugin, { type: 'zip' });
|
||||
};
|
||||
|
||||
open = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.files || !e.target.files[0]) {
|
||||
this.plugin.log.error('No state file selected');
|
||||
return;
|
||||
}
|
||||
|
||||
openState(this.plugin, e.target.files[0]);
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div style={{ margin: '5px' }}>
|
||||
<div className='msp-flex-row'>
|
||||
<Button icon={GetAppSvg} onClick={this.downloadToFileZip} title='Download the state.'>
|
||||
Download
|
||||
</Button>
|
||||
<div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file'>
|
||||
<Icon svg={OpenInBrowserSvg} inline /> Open <input onChange={this.open} type='file' multiple={false} accept='.molx,.molj' />
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class SnapshotControls extends PluginUIComponent<{}> {
|
||||
render() {
|
||||
return <div style={{ margin: '5px' }}>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<LocalStateSnapshotList />
|
||||
</div>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<LocalStateSnapshots />
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<ExpandGroup header='Snapshot Options' initiallyExpanded={false}>
|
||||
<LocalStateSnapshotParams />
|
||||
</ExpandGroup>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
581
src/apps/viewer/app.ts
Normal file
581
src/apps/viewer/app.ts
Normal file
@@ -0,0 +1,581 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2024 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 { Backgrounds } from '../../extensions/backgrounds';
|
||||
import { DnatcoNtCs } from '../../extensions/dnatco';
|
||||
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
|
||||
import { GeometryExport } from '../../extensions/geo-export';
|
||||
import { MAQualityAssessment, QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
|
||||
import { ModelExport } from '../../extensions/model-export';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { MolViewSpec } from '../../extensions/mvs/behavior';
|
||||
import { loadMVSX } from '../../extensions/mvs/components/formats';
|
||||
import { loadMVS } from '../../extensions/mvs/load';
|
||||
import { MVSData } from '../../extensions/mvs/mvs-data';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { AssemblySymmetry, AssemblySymmetryConfig } from '../../extensions/assembly-symmetry';
|
||||
import { SbNcbrPartialCharges, SbNcbrPartialChargesPreset, SbNcbrPartialChargesPropertyProvider } from '../../extensions/sb-ncbr';
|
||||
import { Volseg, VolsegVolumeServerConfig } from '../../extensions/volumes-and-segmentations';
|
||||
import { wwPDBChemicalComponentDictionary } from '../../extensions/wwpdb/ccd/behavior';
|
||||
import { wwPDBStructConnExtensionFunctions } from '../../extensions/wwpdb/struct-conn';
|
||||
import { ZenodoImport } from '../../extensions/zenodo';
|
||||
import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
|
||||
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
|
||||
import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset';
|
||||
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { BuiltInCoordinatesFormat } from '../../mol-plugin-state/formats/coordinates';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
import { BuiltInTopologyFormat } from '../../mol-plugin-state/formats/topology';
|
||||
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 { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { renderReact18 } from '../../mol-plugin-ui/react18';
|
||||
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 { Task } from '../../mol-task';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { consoleStats, setDebugMode, setProductionMode, setTimingMode } from '../../mol-util/debug';
|
||||
|
||||
const CustomFormats = [
|
||||
['g3d', G3dProvider] as const
|
||||
];
|
||||
|
||||
export const ExtensionMap = {
|
||||
'volseg': PluginSpec.Behavior(Volseg),
|
||||
'backgrounds': PluginSpec.Behavior(Backgrounds),
|
||||
'dnatco-ntcs': PluginSpec.Behavior(DnatcoNtCs),
|
||||
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
'assembly-symmetry': PluginSpec.Behavior(AssemblySymmetry),
|
||||
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
|
||||
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
|
||||
'g3d': PluginSpec.Behavior(G3DFormat),
|
||||
'model-export': PluginSpec.Behavior(ModelExport),
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export),
|
||||
'geo-export': PluginSpec.Behavior(GeometryExport),
|
||||
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
|
||||
'zenodo-import': PluginSpec.Behavior(ZenodoImport),
|
||||
'sb-ncbr-partial-charges': PluginSpec.Behavior(SbNcbrPartialCharges),
|
||||
'wwpdb-chemical-component-dictionary': PluginSpec.Behavior(wwPDBChemicalComponentDictionary),
|
||||
'mvs': PluginSpec.Behavior(MolViewSpec),
|
||||
};
|
||||
|
||||
const DefaultViewerOptions = {
|
||||
customFormats: CustomFormats as [string, DataFormatProvider][],
|
||||
extensions: ObjectKeys(ExtensionMap),
|
||||
disabledExtensions: [] as string[],
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: true,
|
||||
layoutShowRemoteState: true,
|
||||
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
|
||||
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,
|
||||
transparency: PluginConfig.General.Transparency.defaultValue,
|
||||
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
|
||||
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
|
||||
powerPreference: PluginConfig.General.PowerPreference.defaultValue,
|
||||
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
|
||||
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
|
||||
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
|
||||
viewportShowTrajectoryControls: PluginConfig.Viewport.ShowTrajectoryControls.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,
|
||||
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
|
||||
volumesAndSegmentationsDefaultServer: VolsegVolumeServerConfig.DefaultServer.defaultValue,
|
||||
rcsbAssemblySymmetryDefaultServerType: AssemblySymmetryConfig.DefaultServerType.defaultValue,
|
||||
rcsbAssemblySymmetryDefaultServerUrl: AssemblySymmetryConfig.DefaultServerUrl.defaultValue,
|
||||
rcsbAssemblySymmetryApplyColors: AssemblySymmetryConfig.ApplyColors.defaultValue,
|
||||
};
|
||||
type ViewerOptions = typeof DefaultViewerOptions;
|
||||
|
||||
export class Viewer {
|
||||
constructor(public plugin: PluginUIContext) {
|
||||
}
|
||||
|
||||
static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
|
||||
const definedOptions = {} as any;
|
||||
// filter for defined properies only so the default values
|
||||
// are property applied
|
||||
for (const p of Object.keys(options) as (keyof ViewerOptions)[]) {
|
||||
if (options[p] !== void 0) definedOptions[p] = options[p];
|
||||
}
|
||||
|
||||
const o: ViewerOptions = { ...DefaultViewerOptions, ...definedOptions };
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const disabledExtension = new Set(o.disabledExtensions ?? []);
|
||||
|
||||
const spec: PluginUISpec = {
|
||||
actions: defaultSpec.actions,
|
||||
behaviors: [
|
||||
...defaultSpec.behaviors,
|
||||
...o.extensions.filter(e => !disabledExtension.has(e)).map(e => ExtensionMap[e]),
|
||||
],
|
||||
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: {
|
||||
...defaultSpec.components?.controls,
|
||||
top: o.layoutShowSequence ? undefined : 'none',
|
||||
bottom: o.layoutShowLog ? undefined : 'none',
|
||||
left: o.layoutShowLeftPanel ? undefined : 'none',
|
||||
},
|
||||
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
|
||||
},
|
||||
config: [
|
||||
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
|
||||
[PluginConfig.General.PixelScale, o.pixelScale],
|
||||
[PluginConfig.General.PickScale, o.pickScale],
|
||||
[PluginConfig.General.Transparency, o.transparency],
|
||||
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
|
||||
[PluginConfig.General.AllowMajorPerformanceCaveat, o.allowMajorPerformanceCaveat],
|
||||
[PluginConfig.General.PowerPreference, o.powerPreference],
|
||||
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
|
||||
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
|
||||
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
|
||||
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
|
||||
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
|
||||
[PluginConfig.Viewport.ShowTrajectoryControls, o.viewportShowTrajectoryControls],
|
||||
[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.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
|
||||
[PluginConfig.Structure.SaccharideCompIdMapType, o.saccharideCompIdMapType],
|
||||
[VolsegVolumeServerConfig.DefaultServer, o.volumesAndSegmentationsDefaultServer],
|
||||
[AssemblySymmetryConfig.DefaultServerType, o.rcsbAssemblySymmetryDefaultServerType],
|
||||
[AssemblySymmetryConfig.DefaultServerUrl, o.rcsbAssemblySymmetryDefaultServerUrl],
|
||||
[AssemblySymmetryConfig.ApplyColors, o.rcsbAssemblySymmetryApplyColors],
|
||||
]
|
||||
};
|
||||
|
||||
const element = typeof elementOrId === 'string'
|
||||
? document.getElementById(elementOrId)
|
||||
: elementOrId;
|
||||
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
|
||||
const plugin = await createPluginUI({
|
||||
target: element,
|
||||
spec,
|
||||
render: renderReact18,
|
||||
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) {
|
||||
const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`;
|
||||
return PluginCommands.State.Snapshots.Fetch(this.plugin, { url });
|
||||
}
|
||||
|
||||
loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) {
|
||||
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
|
||||
}
|
||||
|
||||
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions & { label?: 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: 'url',
|
||||
params: {
|
||||
url: Asset.Url(url),
|
||||
format: format as any,
|
||||
isBinary,
|
||||
label: options?.label,
|
||||
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, 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, {
|
||||
source: {
|
||||
name: 'pdb' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: pdb,
|
||||
server: {
|
||||
name: provider,
|
||||
params: PdbDownloadProvider[provider].defaultValue as any
|
||||
}
|
||||
},
|
||||
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
loadPdbDev(pdbDev: 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: 'pdb-dev' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: pdbDev,
|
||||
encoding: 'bcif',
|
||||
},
|
||||
options: params.source.params.options,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
loadEmdb(emdb: string, options?: { detail?: number }) {
|
||||
const provider = this.plugin.config.get(PluginConfig.Download.DefaultEmdbProvider)!;
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, {
|
||||
source: {
|
||||
name: 'pdb-emd-ds' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: emdb,
|
||||
server: provider,
|
||||
},
|
||||
detail: options?.detail ?? 3,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
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) {
|
||||
const volume: StateObjectSelector<PluginStateObject.Volume.Data> = parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume;
|
||||
const volumeData = volume.cell!.obj!.data;
|
||||
repr
|
||||
.to(volume)
|
||||
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
|
||||
type: 'isosurface',
|
||||
typeParams: { alpha: iso.alpha ?? 1, isoValue: Volume.adjustedIsoValue(volumeData, iso.value, iso.type) },
|
||||
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;
|
||||
|
||||
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);
|
||||
const 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 };
|
||||
}
|
||||
|
||||
async loadMvsFromUrl(url: string, format: 'mvsj' | 'mvsx') {
|
||||
if (format === 'mvsj') {
|
||||
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'string' }));
|
||||
const mvsData = MVSData.fromMVSJ(data);
|
||||
await loadMVS(this.plugin, mvsData, { sanityChecks: true, sourceUrl: url });
|
||||
} else if (format === 'mvsx') {
|
||||
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'binary' }));
|
||||
await this.plugin.runTask(Task.create('Load MVSX file', async ctx => {
|
||||
const parsed = await loadMVSX(this.plugin, ctx, data);
|
||||
await loadMVS(this.plugin, parsed.mvsData, { sanityChecks: true, sourceUrl: parsed.sourceUrl });
|
||||
}));
|
||||
} else {
|
||||
throw new Error(`Unknown MolViewSpec format: ${format}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Load MolViewSpec from `data`.
|
||||
* If `format` is 'mvsj', `data` must be a string or a Uint8Array containing a UTF8-encoded string.
|
||||
* If `format` is 'mvsx', `data` must be a Uint8Array or a string containing base64-encoded binary data prefixed with 'base64,'. */
|
||||
async loadMvsData(data: string | Uint8Array, format: 'mvsj' | 'mvsx') {
|
||||
if (typeof data === 'string' && data.startsWith('base64')) {
|
||||
data = Uint8Array.from(atob(data.substring(7)), c => c.charCodeAt(0)); // Decode base64 string to Uint8Array
|
||||
}
|
||||
if (format === 'mvsj') {
|
||||
if (typeof data !== 'string') {
|
||||
data = new TextDecoder().decode(data); // Decode Uint8Array to string using UTF8
|
||||
}
|
||||
const mvsData = MVSData.fromMVSJ(data);
|
||||
await loadMVS(this.plugin, mvsData, { sanityChecks: true, sourceUrl: undefined });
|
||||
} else if (format === 'mvsx') {
|
||||
if (typeof data === 'string') {
|
||||
throw new Error("loadMvsData: if `format` is 'mvsx', then `data` must be a Uint8Array or a base64-encoded string prefixed with 'base64,'.");
|
||||
}
|
||||
await this.plugin.runTask(Task.create('Load MVSX file', async ctx => {
|
||||
const parsed = await loadMVSX(this.plugin, ctx, data as Uint8Array);
|
||||
await loadMVS(this.plugin, parsed.mvsData, { sanityChecks: true, sourceUrl: parsed.sourceUrl });
|
||||
}));
|
||||
} else {
|
||||
throw new Error(`Unknown MolViewSpec format: ${format}`);
|
||||
}
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
this.plugin.layout.events.updated.next(void 0);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.plugin.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
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: BuiltInTopologyFormat, isBinary?: boolean }
|
||||
| { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuiltInTopologyFormat },
|
||||
modelLabel?: string,
|
||||
coordinates: { kind: 'coordinates-url', url: string, format: BuiltInCoordinatesFormat, isBinary?: boolean }
|
||||
| { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuiltInCoordinatesFormat },
|
||||
coordinatesLabel?: string,
|
||||
preset?: keyof PresetTrajectoryHierarchy
|
||||
}
|
||||
|
||||
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 if (!!structure.models.some(m => SbNcbrPartialChargesPropertyProvider.isApplicable(m))) {
|
||||
return await SbNcbrPartialChargesPreset.apply(ref, params, plugin);
|
||||
} else {
|
||||
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const PluginExtensions = {
|
||||
wwPDBStructConn: wwPDBStructConnExtensionFunctions,
|
||||
mvs: { MVSData, loadMVS },
|
||||
};
|
||||
@@ -20,7 +20,7 @@
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
var viewer = new molstar.Viewer('app', {
|
||||
molstar.Viewer.create('app', {
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: false,
|
||||
layoutShowRemoteState: false,
|
||||
@@ -34,11 +34,20 @@
|
||||
|
||||
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.loadStructureFromUrl('my url', 'pdb', false, {
|
||||
// representationParams: {
|
||||
// theme: {
|
||||
// globalName: 'uniform',
|
||||
// globalColorParams: { value: 0xff0000 }
|
||||
// }
|
||||
// },
|
||||
// label: 'my structure'
|
||||
// });
|
||||
});
|
||||
viewer.loadPdb('7bv2');
|
||||
viewer.loadEmdb('EMD-30210', { detail: 6 });
|
||||
|
||||
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -46,7 +46,10 @@
|
||||
}
|
||||
|
||||
var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
|
||||
if (debugMode) molstar.setDebugMode(debugMode, debugMode);
|
||||
if (debugMode) molstar.setDebugMode(debugMode);
|
||||
|
||||
var timingMode = getParam('timing-mode', '[^&]+').trim() === '1';
|
||||
if (timingMode) molstar.setTimingMode(timingMode);
|
||||
|
||||
var hideControls = getParam('hide-controls', '[^&]+').trim() === '1';
|
||||
var collapseLeftPanel = getParam('collapse-left-panel', '[^&]+').trim() === '1';
|
||||
@@ -56,7 +59,15 @@
|
||||
var pixelScale = getParam('pixel-scale', '[^&]+').trim();
|
||||
var pickScale = getParam('pick-scale', '[^&]+').trim();
|
||||
var pickPadding = getParam('pick-padding', '[^&]+').trim();
|
||||
var viewer = new molstar.Viewer('app', {
|
||||
var transparency = getParam('transparency', '[^&]+').trim().toLowerCase();
|
||||
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1' || void 0;
|
||||
var allowMajorPerformanceCaveat = getParam('allow-major-performance-caveat', '[^&]+').trim() === '1';
|
||||
var powerPreference = getParam('power-preference', '[^&]+').trim().toLowerCase();
|
||||
|
||||
// console.log('Available extensions: ', Object.keys(molstar.ExtensionMap));
|
||||
|
||||
molstar.Viewer.create('app', {
|
||||
disabledExtensions: [], // anything from Object.keys(molstar.ExtensionMap)
|
||||
layoutShowControls: !hideControls,
|
||||
viewportShowExpand: false,
|
||||
collapseLeftPanel: collapseLeftPanel,
|
||||
@@ -68,28 +79,52 @@
|
||||
pixelScale: parseFloat(pixelScale) || 1,
|
||||
pickScale: parseFloat(pickScale) || 0.25,
|
||||
pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
|
||||
transparency: transparency || undefined,
|
||||
preferWebgl1: preferWebgl1,
|
||||
allowMajorPerformanceCaveat: allowMajorPerformanceCaveat,
|
||||
powerPreference: powerPreference || 'high-performance',
|
||||
}).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 mvsUrl = getParam('mvs-url', '[^&]+').trim();
|
||||
var mvsData = getParam('mvs-data', '[^&]+').trim();
|
||||
var mvsFormat = getParam('mvs-format', '[^&]+').trim() || 'mvsj';
|
||||
if (mvsUrl && mvsData) console.error('Cannot specify mvs-url and mvs-data URL parameters at the same time. Ignoring both.');
|
||||
else if (mvsUrl) viewer.loadMvsFromUrl(mvsUrl, mvsFormat);
|
||||
else if (mvsData) viewer.loadMvsData(mvsData, mvsFormat);
|
||||
|
||||
|
||||
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);
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
// to aid GC
|
||||
viewer.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
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,412 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author 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 { 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 { 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 { createPlugin } 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 { StateObjectSelector } from '../../mol-state';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import './embedded.html';
|
||||
import './favicon.ico';
|
||||
import './index.html';
|
||||
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
|
||||
|
||||
const CustomFormats = [
|
||||
['g3d', G3dProvider] as const
|
||||
];
|
||||
|
||||
const Extensions = {
|
||||
'cellpack': PluginSpec.Behavior(CellPack),
|
||||
'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids),
|
||||
'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),
|
||||
'g3d': PluginSpec.Behavior(G3DFormat),
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export),
|
||||
'geo-export': PluginSpec.Behavior(GeometryExport)
|
||||
};
|
||||
|
||||
const DefaultViewerOptions = {
|
||||
customFormats: CustomFormats as [string, DataFormatProvider][],
|
||||
extensions: ObjectKeys(Extensions),
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: true,
|
||||
layoutShowRemoteState: true,
|
||||
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
|
||||
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,
|
||||
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
|
||||
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
|
||||
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: PluginUIContext
|
||||
|
||||
constructor(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
|
||||
const o = { ...DefaultViewerOptions, ...options };
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const spec: PluginUISpec = {
|
||||
actions: defaultSpec.actions,
|
||||
behaviors: [
|
||||
...defaultSpec.behaviors,
|
||||
...o.extensions.map(e => Extensions[e]),
|
||||
],
|
||||
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: {
|
||||
...defaultSpec.components?.controls,
|
||||
top: o.layoutShowSequence ? undefined : 'none',
|
||||
bottom: o.layoutShowLog ? undefined : 'none',
|
||||
left: o.layoutShowLeftPanel ? undefined : '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.Viewport.ShowExpand, o.viewportShowExpand],
|
||||
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
|
||||
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
|
||||
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
|
||||
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
|
||||
[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]
|
||||
]
|
||||
};
|
||||
|
||||
const element = typeof elementOrId === 'string'
|
||||
? document.getElementById(elementOrId)
|
||||
: elementOrId;
|
||||
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
|
||||
this.plugin = createPlugin(element, spec);
|
||||
}
|
||||
|
||||
setRemoteSnapshot(id: string) {
|
||||
const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`;
|
||||
return PluginCommands.State.Snapshots.Fetch(this.plugin, { url });
|
||||
}
|
||||
|
||||
loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) {
|
||||
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
|
||||
}
|
||||
|
||||
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: {
|
||||
name: 'url',
|
||||
params: {
|
||||
url: Asset.Url(url),
|
||||
format: format as any,
|
||||
isBinary,
|
||||
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, 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, {
|
||||
source: {
|
||||
name: 'pdb' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: pdb,
|
||||
server: {
|
||||
name: provider,
|
||||
params: PdbDownloadProvider[provider].defaultValue as any
|
||||
}
|
||||
},
|
||||
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
loadPdbDev(pdbDev: 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: 'pdb-dev' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: pdbDev,
|
||||
encoding: 'bcif',
|
||||
},
|
||||
options: params.source.params.options,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
loadEmdb(emdb: string, options?: { detail?: number }) {
|
||||
const provider = this.plugin.config.get(PluginConfig.Download.DefaultEmdbProvider)!;
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, {
|
||||
source: {
|
||||
name: 'pdb-emd-ds' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: emdb,
|
||||
server: provider,
|
||||
},
|
||||
detail: options?.detail ?? 3,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 * from './app';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Josh McMenemy <josh.mcmenemy@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as argparse from 'argparse';
|
||||
@@ -14,7 +15,7 @@ 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';
|
||||
import { DefaultDataOptions, ensureDataAvailable, readCCD } from './util';
|
||||
|
||||
function extractIonNames(ccd: DatabaseCollection<CCD_Schema>) {
|
||||
const ionNames: string[] = [];
|
||||
@@ -31,11 +32,11 @@ function extractIonNames(ccd: DatabaseCollection<CCD_Schema>) {
|
||||
|
||||
function writeIonNamesFile(filePath: string, ionNames: string[]) {
|
||||
const output = `/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated ion names params file. Names extracted from CCD components.
|
||||
*
|
||||
* @author molstar/chem-comp-dict/create-table cli
|
||||
* @author molstar/chem-comp-dict/create-ions cli
|
||||
*/
|
||||
|
||||
export const IonNames = new Set(${JSON.stringify(ionNames).replace(/"/g, "'").replace(/,/g, ', ')});
|
||||
@@ -43,8 +44,8 @@ export const IonNames = new Set(${JSON.stringify(ionNames).replace(/"/g, "'").re
|
||||
writeFile(filePath, output);
|
||||
}
|
||||
|
||||
async function run(out: string, forceDownload = false) {
|
||||
await ensureDataAvailable(forceDownload);
|
||||
async function run(out: string, options = DefaultDataOptions) {
|
||||
await ensureDataAvailable(options);
|
||||
const ccd = await readCCD();
|
||||
const ionNames = extractIonNames(ccd);
|
||||
if (!fs.existsSync(path.dirname(out))) {
|
||||
@@ -64,10 +65,15 @@ parser.add_argument('--forceDownload', '-f', {
|
||||
action: 'store_true',
|
||||
help: 'Force download of CCD and PVCD.'
|
||||
});
|
||||
parser.add_argument('--ccdUrl', '-c', {
|
||||
help: 'Fetch the CCD from a custom URL. This forces download of the CCD.',
|
||||
required: false
|
||||
});
|
||||
interface Args {
|
||||
out: string,
|
||||
forceDownload?: boolean,
|
||||
ccdUrl?: string
|
||||
}
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
run(args.out, args.forceDownload);
|
||||
run(args.out, { forceDownload: args.forceDownload, ccdUrl: args.ccdUrl });
|
||||
|
||||
82
src/cli/chem-comp-dict/create-saccharides.ts
Normal file
82
src/cli/chem-comp-dict/create-saccharides.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as argparse from 'argparse';
|
||||
import * as path from 'path';
|
||||
import util from 'util';
|
||||
import fs from 'fs';
|
||||
require('util.promisify').shim();
|
||||
const writeFile = util.promisify(fs.writeFile);
|
||||
|
||||
import { DatabaseCollection } from '../../mol-data/db';
|
||||
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
|
||||
import { DefaultDataOptions, ensureDataAvailable, readCCD } from './util';
|
||||
|
||||
function extractSaccharideNames(ccd: DatabaseCollection<CCD_Schema>) {
|
||||
const saccharideNames: string[] = [];
|
||||
for (const k in ccd) {
|
||||
const { chem_comp } = ccd[k];
|
||||
const type = chem_comp.type.value(0).toUpperCase();
|
||||
if (type.includes('SACCHARIDE')) {
|
||||
saccharideNames.push(chem_comp.id.value(0));
|
||||
}
|
||||
}
|
||||
// these are extra saccharides that don't have SACCHARIDE in their type
|
||||
saccharideNames.push(
|
||||
'UMQ', // UNDECYL-MALTOSIDE, via GlyFinder
|
||||
'SQD', // SULFOQUINOVOSYLDIACYLGLYCEROL, via GlyFinder
|
||||
);
|
||||
return saccharideNames;
|
||||
}
|
||||
|
||||
function writeSaccharideNamesFile(filePath: string, ionNames: string[]) {
|
||||
const output = `/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated ion names params file. Names extracted from CCD components.
|
||||
*
|
||||
* @author molstar/cli/chem-comp-dict/create-saccharides
|
||||
*/
|
||||
|
||||
export const SaccharideNames = new Set(${JSON.stringify(ionNames).replace(/"/g, "'").replace(/,/g, ', ')});
|
||||
`;
|
||||
writeFile(filePath, output);
|
||||
}
|
||||
|
||||
async function run(out: string, options = DefaultDataOptions) {
|
||||
await ensureDataAvailable(options);
|
||||
const ccd = await readCCD();
|
||||
const saccharideNames = extractSaccharideNames(ccd);
|
||||
if (!fs.existsSync(path.dirname(out))) {
|
||||
fs.mkdirSync(path.dirname(out));
|
||||
}
|
||||
writeSaccharideNamesFile(out, saccharideNames);
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
add_help: true,
|
||||
description: 'Extract and save SaccharideNames from CCD.'
|
||||
});
|
||||
parser.add_argument('out', {
|
||||
help: 'Generated file output path.'
|
||||
});
|
||||
parser.add_argument('--forceDownload', '-f', {
|
||||
action: 'store_true',
|
||||
help: 'Force download of CCD and PVCD.'
|
||||
});
|
||||
parser.add_argument('--ccdUrl', '-c', {
|
||||
help: 'Fetch the CCD from a custom URL. This forces download of the CCD.',
|
||||
required: false
|
||||
});
|
||||
interface Args {
|
||||
out: string,
|
||||
forceDownload?: boolean,
|
||||
ccdUrl?: string
|
||||
}
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
run(args.out, { forceDownload: args.forceDownload, ccdUrl: args.ccdUrl });
|
||||
@@ -18,7 +18,7 @@ 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';
|
||||
import { ensureDataAvailable, getEncodedCif, readCCD, readPVCD } from './util';
|
||||
import { DefaultDataOptions, ensureDataAvailable, getEncodedCif, readCCD, readPVCD } from './util';
|
||||
|
||||
type CCB = Table<CCD_Schema['chem_comp_bond']>
|
||||
type CCA = Table<CCD_Schema['chem_comp_atom']>
|
||||
@@ -239,8 +239,8 @@ function createAtoms(ccd: DatabaseCollection<CCD_Schema>, pvcd: DatabaseCollecti
|
||||
);
|
||||
}
|
||||
|
||||
async function run(out: string, binary = false, forceDownload = false, ccaOut?: string) {
|
||||
await ensureDataAvailable(forceDownload);
|
||||
async function run(out: string, binary = false, options = DefaultDataOptions, ccaOut?: string) {
|
||||
await ensureDataAvailable(options);
|
||||
const ccd = await readCCD();
|
||||
const pvcd = await readPVCD();
|
||||
|
||||
@@ -283,12 +283,22 @@ parser.add_argument('--ccaOut', '-a', {
|
||||
help: 'Optional generated file output path for chem_comp_atom data.',
|
||||
required: false
|
||||
});
|
||||
parser.add_argument('--ccdUrl', '-c', {
|
||||
help: 'Fetch the CCD from a custom URL. This forces download of the CCD.',
|
||||
required: false
|
||||
});
|
||||
parser.add_argument('--pvcdUrl', '-p', {
|
||||
help: 'Fetch the PVCD from a custom URL. This forces download of the PVCD.',
|
||||
required: false
|
||||
});
|
||||
interface Args {
|
||||
out: string,
|
||||
forceDownload?: boolean,
|
||||
binary?: boolean,
|
||||
ccaOut?: string
|
||||
ccaOut?: string,
|
||||
ccdUrl?: string,
|
||||
pvcdUrl?: string
|
||||
}
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
run(args.out, args.binary, args.forceDownload, args.ccaOut);
|
||||
run(args.out, args.binary, { forceDownload: args.forceDownload, ccdUrl: args.ccdUrl, pvcdUrl: args.pvcdUrl }, args.ccaOut);
|
||||
|
||||
@@ -35,9 +35,9 @@ export async function ensureAvailable(path: string, url: string, forceDownload =
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureDataAvailable(forceDownload = false) {
|
||||
await ensureAvailable(CCD_PATH, CCD_URL, forceDownload);
|
||||
await ensureAvailable(PVCD_PATH, PVCD_URL, forceDownload);
|
||||
export async function ensureDataAvailable(options: DataOptions) {
|
||||
await ensureAvailable(CCD_PATH, options.ccdUrl || CCD_URL, !!options.ccdUrl || options.forceDownload);
|
||||
await ensureAvailable(PVCD_PATH, options.pvcdUrl || PVCD_URL, !!options.pvcdUrl || options.forceDownload);
|
||||
}
|
||||
|
||||
export async function readFileAsCollection<S extends Database.Schema>(path: string, schema: S) {
|
||||
@@ -68,8 +68,18 @@ export function getEncodedCif(name: string, database: Database<Database.Schema>,
|
||||
return encoder.getData();
|
||||
}
|
||||
|
||||
export type DataOptions = {
|
||||
ccdUrl?: string,
|
||||
pvcdUrl?: string,
|
||||
forceDownload?: boolean
|
||||
}
|
||||
|
||||
export const DefaultDataOptions: DataOptions = {
|
||||
forceDownload: false
|
||||
};
|
||||
|
||||
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 CCD_URL = 'https://files.wwpdb.org/pub/pdb/data/monomers/components.cif';
|
||||
const PVCD_URL = 'https://files.wwpdb.org/pub/pdb/data/monomers/aa-variants-v1.cif';
|
||||
|
||||
@@ -71,7 +71,7 @@ function classify(name: string, field: CifField): CifWriter.Field {
|
||||
}
|
||||
|
||||
export function convert(path: string, asText = false, hints?: EncodingStrategyHint[], filter?: string) {
|
||||
return Task.create<Uint8Array>('BinaryCIF', async ctx => {
|
||||
return Task.create<Uint8Array>('Convert CIF', async ctx => {
|
||||
const encodingProvider: BinaryEncodingProvider = hints
|
||||
? CifWriter.createEncodingProviderFromJsonConfig(hints)
|
||||
: { get: (c, f) => void 0 };
|
||||
|
||||
@@ -18,7 +18,7 @@ async function process(srcPath: string, outPath: string, configPath?: string, fi
|
||||
const config = configPath ? JSON.parse(fs.readFileSync(configPath, 'utf8')) : void 0;
|
||||
const filter = filterPath ? fs.readFileSync(filterPath, 'utf8') : void 0;
|
||||
|
||||
const res = await convert(srcPath, false, config, filter);
|
||||
const res = await convert(srcPath, srcPath.toLowerCase().indexOf('.bcif') > 0, config, filter);
|
||||
await write(outPath, res);
|
||||
}
|
||||
|
||||
@@ -38,13 +38,13 @@ function run(args: Args) {
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
add_help: true,
|
||||
description: 'Convert any CIF file to a BCIF file'
|
||||
description: 'Convert any BCIF file to a CIF file or vice versa'
|
||||
});
|
||||
parser.add_argument('src', {
|
||||
help: 'Source CIF path'
|
||||
help: 'Source file path'
|
||||
});
|
||||
parser.add_argument('out', {
|
||||
help: 'Output BCIF path'
|
||||
help: 'Output file path'
|
||||
});
|
||||
parser.add_argument('-c', '--config', {
|
||||
help: 'Optional encoding strategy/precision config path',
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -35,20 +35,16 @@ async function runGenerateSchemaMmcif(name: string, fieldNamesPath: string, type
|
||||
const ihmDic = await parseCifText(fs.readFileSync(IHM_DIC_PATH, 'utf8')).run();
|
||||
if (ihmDic.isError) throw ihmDic;
|
||||
|
||||
await ensureCarbBranchDicAvailable();
|
||||
const carbBranchDic = await parseCifText(fs.readFileSync(CARB_BRANCH_DIC_PATH, 'utf8')).run();
|
||||
if (carbBranchDic.isError) throw carbBranchDic;
|
||||
|
||||
await ensureCarbCompDicAvailable();
|
||||
const carbCompDic = await parseCifText(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);
|
||||
@@ -139,8 +135,7 @@ async function getFieldNamesFilter(fieldNamesPath: string): Promise<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);
|
||||
@@ -163,12 +158,10 @@ async function ensureDicAvailable(dicPath: string, dicUrl: string) {
|
||||
const DIC_DIR = path.resolve(__dirname, '../../../../build/dics/');
|
||||
const MMCIF_DIC_PATH = `${DIC_DIR}/mmcif_pdbx_v50.dic`;
|
||||
const MMCIF_DIC_URL = 'http://mmcif.wwpdb.org/dictionaries/ascii/mmcif_pdbx_v50.dic';
|
||||
const IHM_DIC_PATH = `${DIC_DIR}/ihm-extension.dic`;
|
||||
const IHM_DIC_URL = 'https://raw.githubusercontent.com/ihmwg/IHM-dictionary/master/ihm-extension.dic';
|
||||
const CARB_BRANCH_DIC_PATH = `${DIC_DIR}/entity_branch-extension.dic`;
|
||||
const CARB_BRANCH_DIC_URL = 'https://raw.githubusercontent.com/pdbxmmcifwg/carbohydrate-extension/master/dict/entity_branch-extension.dic';
|
||||
const CARB_COMP_DIC_PATH = `${DIC_DIR}/chem_comp-extension.dic`;
|
||||
const CARB_COMP_DIC_URL = 'https://raw.githubusercontent.com/pdbxmmcifwg/carbohydrate-extension/master/dict/chem_comp-extension.dic';
|
||||
const IHM_DIC_PATH = `${DIC_DIR}/mmcif_ihm_ext.dic`;
|
||||
const IHM_DIC_URL = 'https://raw.githubusercontent.com/ihmwg/IHMCIF/master/dist/mmcif_ihm_ext.dic';
|
||||
const MA_DIC_PATH = `${DIC_DIR}/ma-extension.dic`;
|
||||
const MA_DIC_URL = 'https://raw.githubusercontent.com/ihmwg/ModelCIF/master/dist/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';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -13,15 +13,18 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
switch (type) {
|
||||
// mmCIF
|
||||
case 'code':
|
||||
case 'ucode':
|
||||
case 'line':
|
||||
case 'uline':
|
||||
case 'text':
|
||||
case 'char':
|
||||
case 'uchar3':
|
||||
case 'uchar1':
|
||||
case 'boolean':
|
||||
return values && values.length ? EnumCol(values, 'str', description) : StrCol(description);
|
||||
case 'ucode':
|
||||
case 'uline':
|
||||
case 'uchar3':
|
||||
case 'uchar1':
|
||||
case 'uchar5':
|
||||
// only force lower-case for enums
|
||||
return values && values.length ? EnumCol(values.map(x => x.toLowerCase()), 'lstr', description) : StrCol(description);
|
||||
case 'aliasname':
|
||||
case 'name':
|
||||
case 'idname':
|
||||
@@ -49,6 +52,7 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
case 'operation_expression':
|
||||
case 'point_symmetry':
|
||||
case '4x3_matrix':
|
||||
case '3x4_matrix':
|
||||
case '3x4_matrices':
|
||||
case 'point_group':
|
||||
case 'point_group_helical':
|
||||
@@ -58,6 +62,7 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
case 'symop':
|
||||
case 'exp_data_doi':
|
||||
case 'asym_id':
|
||||
case 'uniprot_ptm_id':
|
||||
return StrCol(description);
|
||||
case 'int':
|
||||
case 'non_negative_int':
|
||||
@@ -68,6 +73,7 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
case 'ec-type':
|
||||
case 'ucode-alphanum-csv':
|
||||
case 'id_list':
|
||||
case 'entity_id_list':
|
||||
return ListCol('str', ',', description);
|
||||
case 'id_list_spc':
|
||||
return ListCol('str', ' ', description);
|
||||
@@ -85,6 +91,7 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
case 'Tag':
|
||||
case 'Implied':
|
||||
case 'Word':
|
||||
case 'Uri':
|
||||
return wrapContainer('str', ',', description, container);
|
||||
case 'Real':
|
||||
return wrapContainer('float', ',', description, container);
|
||||
@@ -148,7 +155,7 @@ function getImportFrames(d: Data.CifFrame, imports: Imports) {
|
||||
}
|
||||
|
||||
/** get field from given or linked category */
|
||||
function getField(category: string, field: string, d: Data.CifFrame, imports: Imports, ctx: FrameData): Data.CifField|undefined {
|
||||
function getField(category: string, field: string, d: Data.CifFrame, imports: Imports, ctx: FrameData): Data.CifField | undefined {
|
||||
const { categories, links } = ctx;
|
||||
const cat = d.categories[category];
|
||||
if (cat) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -10,7 +10,7 @@ import { FieldPath } from '../../../mol-io/reader/cif/schema';
|
||||
|
||||
function header(name: string, info: string, moldataImportPath: string) {
|
||||
return `/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated '${name}' schema file. ${info}
|
||||
*
|
||||
@@ -35,13 +35,17 @@ function getTypeShorthands(schema: Database, fields?: Filter) {
|
||||
const { columns } = schema.tables[table];
|
||||
Object.keys(columns).forEach(columnName => {
|
||||
if (fields && !fields[table][columnName]) return;
|
||||
types.add(schema.tables[table].columns[columnName].type);
|
||||
const col = schema.tables[table].columns[columnName];
|
||||
if (col.type === 'enum') types.add(col.subType);
|
||||
types.add(col.type);
|
||||
});
|
||||
});
|
||||
const shorthands: string[] = [];
|
||||
types.forEach(type => {
|
||||
switch (type) {
|
||||
case 'str': shorthands.push('const str = Schema.str;'); break;
|
||||
case 'ustr': shorthands.push('const ustr = Schema.ustr;'); break;
|
||||
case 'lstr': shorthands.push('const lstr = Schema.lstr;'); break;
|
||||
case 'int': shorthands.push('const int = Schema.int;'); break;
|
||||
case 'float': shorthands.push('const float = Schema.float;'); break;
|
||||
case 'coord': shorthands.push('const coord = Schema.coord;'); break;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -29,8 +29,8 @@ export function FloatCol(description: string): FloatCol { return { type: 'float'
|
||||
export type CoordCol = { type: 'coord' } & BaseCol
|
||||
export function CoordCol(description: string): CoordCol { return { type: 'coord', description }; }
|
||||
|
||||
export type EnumCol = { type: 'enum', subType: 'int' | 'str', values: string[] } & BaseCol
|
||||
export function EnumCol(values: string[], subType: 'int' | 'str', description: string): EnumCol {
|
||||
export type EnumCol = { type: 'enum', subType: 'int' | 'str' | 'ustr' | 'lstr', values: string[] } & BaseCol
|
||||
export function EnumCol(values: string[], subType: 'int' | 'str' | 'ustr' | 'lstr', description: string): EnumCol {
|
||||
return { type: 'enum', description, values, subType };
|
||||
}
|
||||
|
||||
|
||||
41
src/cli/mvs/mvs-print-schema.ts
Normal file
41
src/cli/mvs/mvs-print-schema.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*
|
||||
* Command-line application for printing MolViewSpec tree schema
|
||||
* Build: npm run build
|
||||
* Run: node lib/commonjs/cli/mvs/mvs-print-schema
|
||||
* node lib/commonjs/cli/mvs/mvs-print-schema --markdown
|
||||
*/
|
||||
|
||||
import { ArgumentParser } from 'argparse';
|
||||
import { treeSchemaToMarkdown, treeSchemaToString } from '../../extensions/mvs/tree/generic/tree-schema';
|
||||
import { MVSDefaults } from '../../extensions/mvs/tree/mvs/mvs-defaults';
|
||||
import { MVSTreeSchema } from '../../extensions/mvs/tree/mvs/mvs-tree';
|
||||
|
||||
|
||||
/** Command line argument values for `main` */
|
||||
interface Args {
|
||||
markdown: boolean,
|
||||
}
|
||||
|
||||
/** Return parsed command line arguments for `main` */
|
||||
function parseArguments(): Args {
|
||||
const parser = new ArgumentParser({ description: 'Command-line application for printing MolViewSpec tree schema.' });
|
||||
parser.add_argument('-m', '--markdown', { action: 'store_true', help: 'Print the schema as markdown instead of plain text.' });
|
||||
const args = parser.parse_args();
|
||||
return { ...args };
|
||||
}
|
||||
|
||||
/** Main workflow for printing MolViewSpec tree schema. */
|
||||
function main(args: Args) {
|
||||
if (args.markdown) {
|
||||
console.log(treeSchemaToMarkdown(MVSTreeSchema, MVSDefaults));
|
||||
} else {
|
||||
console.log(treeSchemaToString(MVSTreeSchema, MVSDefaults));
|
||||
}
|
||||
}
|
||||
|
||||
main(parseArguments());
|
||||
158
src/cli/mvs/mvs-render.ts
Normal file
158
src/cli/mvs/mvs-render.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*
|
||||
* Command-line application for rendering images from MolViewSpec files
|
||||
* Build: npm install --no-save canvas gl jpeg-js pngjs // these packages are not listed in Mol* dependencies for performance reasons
|
||||
* npm run build
|
||||
* Run: node lib/commonjs/cli/mvs/mvs-render -i examples/mvs/1cbs.mvsj -o ../outputs/1cbs.png --size 800x600 --molj
|
||||
*/
|
||||
|
||||
import { ArgumentParser } from 'argparse';
|
||||
import fs from 'fs';
|
||||
import gl from 'gl';
|
||||
import jpegjs from 'jpeg-js';
|
||||
import path from 'path';
|
||||
import pngjs from 'pngjs';
|
||||
|
||||
import { Canvas3DParams } from '../../mol-canvas3d/canvas3d';
|
||||
import { setCanvasModule } from '../../mol-geo/geometry/text/font-atlas';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { HeadlessPluginContext } from '../../mol-plugin/headless-plugin-context';
|
||||
import { DefaultPluginSpec, PluginSpec } from '../../mol-plugin/spec';
|
||||
import { ExternalModules, defaultCanvas3DParams } from '../../mol-plugin/util/headless-screenshot';
|
||||
import { Task } from '../../mol-task';
|
||||
import { setFSModule } from '../../mol-util/data-source';
|
||||
import { onelinerJsonString } from '../../mol-util/json';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
// MolViewSpec must be imported after HeadlessPluginContext
|
||||
import { MolViewSpec } from '../../extensions/mvs/behavior';
|
||||
import { loadMVSX } from '../../extensions/mvs/components/formats';
|
||||
import { loadMVS } from '../../extensions/mvs/load';
|
||||
import { MVSData } from '../../extensions/mvs/mvs-data';
|
||||
|
||||
|
||||
setFSModule(fs);
|
||||
setCanvasModule(require('canvas'));
|
||||
|
||||
const DEFAULT_SIZE = '800x800';
|
||||
|
||||
/** Command line argument values for `main` */
|
||||
interface Args {
|
||||
input: string[],
|
||||
output: string[],
|
||||
size: { width: number, height: number },
|
||||
molj: boolean,
|
||||
}
|
||||
|
||||
/** Return parsed command line arguments for `main` */
|
||||
function parseArguments(): Args {
|
||||
const parser = new ArgumentParser({ description: 'Command-line application for rendering images from MolViewSpec files' });
|
||||
parser.add_argument('-i', '--input', { required: true, nargs: '+', help: 'Input file(s) in .mvsj or .mvsx format. File format is inferred from the file extension.' });
|
||||
parser.add_argument('-o', '--output', { required: true, nargs: '+', help: 'File path(s) for output files (one output path for each input file). Output format is inferred from the file extension (.png or .jpg)' });
|
||||
parser.add_argument('-s', '--size', { help: `Output image resolution, {width}x{height}. Default: ${DEFAULT_SIZE}.`, default: DEFAULT_SIZE });
|
||||
parser.add_argument('-m', '--molj', { action: 'store_true', help: `Save Mol* state (.molj) in addition to rendered images (use the same output file paths but with .molj extension)` });
|
||||
const args = parser.parse_args();
|
||||
try {
|
||||
const parts = args.size.split('x');
|
||||
if (parts.length !== 2) throw new Error('Must contain two x-separated parts');
|
||||
args.size = { width: parseIntStrict(parts[0]), height: parseIntStrict(parts[1]) };
|
||||
} catch {
|
||||
parser.error(`argument: --size: invalid image size string: '${args.size}' (must be two x-separated integers (width and height), e.g. '400x300')`);
|
||||
}
|
||||
if (args.input.length !== args.output.length) {
|
||||
parser.error(`argument: --output: must specify the same number of input and output file paths (specified ${args.input.length} input path${args.input.length !== 1 ? 's' : ''} but ${args.output.length} output path${args.output.length !== 1 ? 's' : ''})`);
|
||||
}
|
||||
return { ...args };
|
||||
}
|
||||
|
||||
/** Main workflow for rendering images from MolViewSpec files */
|
||||
async function main(args: Args): Promise<void> {
|
||||
const plugin = await createHeadlessPlugin(args);
|
||||
|
||||
for (let i = 0; i < args.input.length; i++) {
|
||||
const input = args.input[i];
|
||||
const output = args.output[i];
|
||||
console.log(`Processing ${input} -> ${output}`);
|
||||
|
||||
let mvsData: MVSData;
|
||||
let sourceUrl: string | undefined;
|
||||
if (input.toLowerCase().endsWith('.mvsj')) {
|
||||
const data = fs.readFileSync(input, { encoding: 'utf8' });
|
||||
mvsData = MVSData.fromMVSJ(data);
|
||||
sourceUrl = `file://${path.resolve(input)}`;
|
||||
} else if (input.toLowerCase().endsWith('.mvsx')) {
|
||||
const data = fs.readFileSync(input);
|
||||
const mvsx = await plugin.runTask(Task.create('Load MVSX', async ctx => loadMVSX(plugin, ctx, data)));
|
||||
mvsData = mvsx.mvsData;
|
||||
sourceUrl = mvsx.sourceUrl;
|
||||
} else {
|
||||
throw new Error(`Input file name must end with .mvsj or .mvsx: ${input}`);
|
||||
}
|
||||
await loadMVS(plugin, mvsData, { sanityChecks: true, replaceExisting: true, sourceUrl: sourceUrl });
|
||||
|
||||
fs.mkdirSync(path.dirname(output), { recursive: true });
|
||||
if (args.molj) {
|
||||
await plugin.saveStateSnapshot(withExtension(output, '.molj'));
|
||||
}
|
||||
await plugin.saveImage(output);
|
||||
checkState(plugin);
|
||||
}
|
||||
await plugin.clear();
|
||||
plugin.dispose();
|
||||
}
|
||||
|
||||
/** Return a new and initiatized HeadlessPlugin */
|
||||
async function createHeadlessPlugin(args: Pick<Args, 'size'>): Promise<HeadlessPluginContext> {
|
||||
const externalModules: ExternalModules = { gl, pngjs, 'jpeg-js': jpegjs };
|
||||
const spec = DefaultPluginSpec();
|
||||
spec.behaviors.push(PluginSpec.Behavior(MolViewSpec));
|
||||
const headlessCanvasOptions = defaultCanvas3DParams();
|
||||
const canvasOptions = {
|
||||
...PD.getDefaultValues(Canvas3DParams),
|
||||
cameraResetDurationMs: headlessCanvasOptions.cameraResetDurationMs,
|
||||
postprocessing: headlessCanvasOptions.postprocessing,
|
||||
};
|
||||
const plugin = new HeadlessPluginContext(externalModules, spec, args.size, { canvas: canvasOptions });
|
||||
try {
|
||||
await plugin.init();
|
||||
} catch (error) {
|
||||
plugin.dispose();
|
||||
throw error;
|
||||
}
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/** Parse integer, fail early. */
|
||||
function parseIntStrict(str: string): number {
|
||||
if (str === '') throw new Error('Is empty string');
|
||||
const result = Number(str);
|
||||
if (isNaN(result)) throw new Error('Is NaN');
|
||||
if (Math.floor(result) !== result) throw new Error('Is not integer');
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Replace the file extension in `filename` by `extension`. If `filename` has no extension, add it. */
|
||||
function withExtension(filename: string, extension: string): string {
|
||||
const oldExtension = path.extname(filename);
|
||||
return filename.slice(0, -oldExtension.length) + extension;
|
||||
}
|
||||
|
||||
/** Check Mol* state, print and throw error if any cell is not OK. */
|
||||
function checkState(plugin: PluginContext): void {
|
||||
const cells = Array.from(plugin.state.data.cells.values());
|
||||
const badCell = cells.find(cell => cell.status !== 'ok');
|
||||
if (badCell) {
|
||||
console.error(`Building Mol* state failed`);
|
||||
console.error(` Transformer: ${badCell.transform.transformer.id}`);
|
||||
console.error(` Params: ${onelinerJsonString(badCell.transform.params)}`);
|
||||
console.error(` Error: ${badCell.errorText}`);
|
||||
console.error(``);
|
||||
throw new Error(`Building Mol* state failed: ${badCell.errorText}`);
|
||||
}
|
||||
}
|
||||
|
||||
main(parseArguments());
|
||||
58
src/cli/mvs/mvs-validate.ts
Normal file
58
src/cli/mvs/mvs-validate.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*
|
||||
* Command-line application for validating MolViewSpec files
|
||||
* Build: npm run build
|
||||
* Run: node lib/commonjs/cli/mvs/mvs-validate examples/mvs/1cbs.mvsj
|
||||
*/
|
||||
|
||||
import { ArgumentParser } from 'argparse';
|
||||
import fs from 'fs';
|
||||
|
||||
import { setFSModule } from '../../mol-util/data-source';
|
||||
import { MVSData } from '../../extensions/mvs/mvs-data';
|
||||
|
||||
|
||||
setFSModule(fs);
|
||||
|
||||
/** Command line argument values for `main` */
|
||||
interface Args {
|
||||
input: string[],
|
||||
no_extra: boolean,
|
||||
}
|
||||
|
||||
/** Return parsed command line arguments for `main` */
|
||||
function parseArguments(): Args {
|
||||
const parser = new ArgumentParser({ description: 'Command-line application for validating MolViewSpec files. Prints validation status (OK/FAILED) to stdout, detailed validation issues to stderr. Exits with a zero exit code if all input files are OK.' });
|
||||
parser.add_argument('input', { nargs: '+', help: 'Input file(s) in .mvsj format' });
|
||||
parser.add_argument('--no-extra', { action: 'store_true', help: 'Treat presence of extra node params as an issue.' });
|
||||
const args = parser.parse_args();
|
||||
return { ...args };
|
||||
}
|
||||
|
||||
/** Main workflow for validating MolViewSpec files. Returns the number of failed input files. */
|
||||
function main(args: Args): number {
|
||||
let nFailed = 0;
|
||||
for (const input of args.input) {
|
||||
const data = fs.readFileSync(input, { encoding: 'utf8' });
|
||||
const mvsData = MVSData.fromMVSJ(data);
|
||||
const issues = MVSData.validationIssues(mvsData, { noExtra: args.no_extra });
|
||||
const status = issues ? 'FAILED' : 'OK';
|
||||
console.log(`${status.padEnd(6)} ${input}`);
|
||||
if (issues) {
|
||||
nFailed++;
|
||||
for (const issue of issues) {
|
||||
console.error(issue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nFailed;
|
||||
}
|
||||
|
||||
const nFailed = main(parseArguments());
|
||||
if (nFailed > 0) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ async function readFile(path: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function parseCif(data: string|Uint8Array) {
|
||||
async function parseCif(data: string | Uint8Array) {
|
||||
const comp = CIF.parse(data);
|
||||
const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
|
||||
if (parsed.isError) throw parsed;
|
||||
|
||||
@@ -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, -1, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) })).run();
|
||||
console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
|
||||
|
||||
// Export the mesh in OBJ format.
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
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}>
|
||||
createRoot(parent).render(<PluginContextContainer plugin={orbitals.plugin}>
|
||||
<Controls orbitals={orbitals} />
|
||||
</PluginContextContainer>, parent);
|
||||
</PluginContextContainer>);
|
||||
}
|
||||
|
||||
function Controls({ orbitals }: { orbitals: AlphaOrbitalsExample }) {
|
||||
|
||||
@@ -55,7 +55,19 @@
|
||||
</a>
|
||||
</div>
|
||||
<script>
|
||||
function getParam(name, regex) {
|
||||
var r = new RegExp(name + '=' + '(' + regex + ')[&]?', 'i');
|
||||
return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || '');
|
||||
}
|
||||
|
||||
var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
|
||||
if (debugMode) AlphaOrbitalsExample.setDebugMode(debugMode);
|
||||
|
||||
var timingMode = getParam('timing-mode', '[^&]+').trim() === '1';
|
||||
if (timingMode) AlphaOrbitalsExample.setTimingMode(timingMode);
|
||||
|
||||
AlphaOrbitalsExample.init('app')
|
||||
</script>
|
||||
<!-- __MOLSTAR_ANALYTICS__ -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -11,7 +11,8 @@ import { SphericalBasisOrder } from '../../extensions/alpha-orbitals/spherical-f
|
||||
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 { createPluginAsync } from '../../mol-plugin-ui';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { renderReact18 } from '../../mol-plugin-ui/react18';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
@@ -25,6 +26,8 @@ import { DemoMoleculeSDF, DemoOrbitals } from './example-data';
|
||||
import './index.html';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
import { setDebugMode, setTimingMode, consoleStats } from '../../mol-util/debug';
|
||||
|
||||
interface DemoInput {
|
||||
moleculeSdf: string,
|
||||
basis: Basis,
|
||||
@@ -54,28 +57,32 @@ export class AlphaOrbitalsExample {
|
||||
|
||||
async init(target: string | HTMLElement) {
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
this.plugin = await createPluginAsync(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...defaultSpec,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
this.plugin = await createPluginUI({
|
||||
target: typeof target === 'string' ? document.getElementById(target)! : target,
|
||||
render: renderReact18,
|
||||
spec: {
|
||||
...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],
|
||||
]
|
||||
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' });
|
||||
@@ -97,7 +104,7 @@ export class AlphaOrbitalsExample {
|
||||
}
|
||||
|
||||
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
|
||||
readonly state = new BehaviorSubject<Params>({ show: { name: 'orbital', params: { index: 32 } }, isoValue: 1, gpuSurface: false });
|
||||
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;
|
||||
@@ -170,12 +177,11 @@ export class AlphaOrbitalsExample {
|
||||
return {
|
||||
alpha: 0.85,
|
||||
color,
|
||||
directVolume: this.state.value.gpuSurface,
|
||||
kind,
|
||||
relativeIsovalue: this.state.value.isoValue,
|
||||
pickable: false,
|
||||
xrayShaded: true,
|
||||
tryUseGpu: false
|
||||
tryUseGpu: true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -219,4 +225,7 @@ export class AlphaOrbitalsExample {
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).AlphaOrbitalsExample = new AlphaOrbitalsExample();
|
||||
(window as any).AlphaOrbitalsExample = new AlphaOrbitalsExample();
|
||||
(window as any).AlphaOrbitalsExample.setDebugMode = setDebugMode;
|
||||
(window as any).AlphaOrbitalsExample.setTimingMode = setTimingMode;
|
||||
(window as any).AlphaOrbitalsExample.consoleStats = consoleStats;
|
||||
@@ -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 }));
|
||||
|
||||
@@ -9,8 +9,9 @@ import { EmptyLoci } from '../../mol-model/loci';
|
||||
import { StructureSelection } from '../../mol-model/structure';
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { createPlugin } from '../../mol-plugin-ui';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { renderReact18 } from '../../mol-plugin-ui/react18';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { Script } from '../../mol-script/script';
|
||||
@@ -28,17 +29,21 @@ type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: bo
|
||||
class BasicWrapper {
|
||||
plugin: PluginUIContext;
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginUISpec(),
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
async init(target: string | HTMLElement) {
|
||||
this.plugin = await createPluginUI({
|
||||
target: typeof target === 'string' ? document.getElementById(target)! : target,
|
||||
render: renderReact18,
|
||||
spec: {
|
||||
...DefaultPluginUISpec(),
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
remoteState: 'none'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
remoteState: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -46,6 +51,14 @@ class BasicWrapper {
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(CustomColorThemeProvider);
|
||||
this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
|
||||
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
|
||||
|
||||
this.plugin.managers.dragAndDrop.addHandler('custom-wrapper', (files) => {
|
||||
if (files.some(f => f.name.toLowerCase().endsWith('.testext'))) {
|
||||
console.log('.testext File dropped');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
async load({ url, format = 'mmcif', isBinary = false, assemblyId = '' }: LoadParams) {
|
||||
@@ -60,7 +73,7 @@ class BasicWrapper {
|
||||
params: { id: assemblyId }
|
||||
} : {
|
||||
name: 'model',
|
||||
params: { }
|
||||
params: {}
|
||||
},
|
||||
showUnitcell: false,
|
||||
representationPreset: 'auto'
|
||||
@@ -74,12 +87,20 @@ class BasicWrapper {
|
||||
toggleSpin() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
const trackball = this.plugin.canvas3d.props.trackball;
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
settings: props => {
|
||||
props.trackball.spin = !props.trackball.spin;
|
||||
settings: {
|
||||
trackball: {
|
||||
...trackball,
|
||||
animate: trackball.animate.name === 'spin'
|
||||
? { name: 'off', params: {} }
|
||||
: { name: 'spin', params: { speed: 1 } }
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!this.plugin.canvas3d.props.trackball.spin) PluginCommands.Camera.Reset(this.plugin, {});
|
||||
if (this.plugin.canvas3d.props.trackball.animate.name !== 'spin') {
|
||||
PluginCommands.Camera.Reset(this.plugin, {});
|
||||
}
|
||||
}
|
||||
|
||||
private animateModelIndexTargetFps() {
|
||||
@@ -95,7 +116,7 @@ class BasicWrapper {
|
||||
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 () => {
|
||||
@@ -119,7 +140,7 @@ class BasicWrapper {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
interactivity = {
|
||||
highlightOn: () => {
|
||||
@@ -137,7 +158,7 @@ class BasicWrapper {
|
||||
clearHighlight: () => {
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tests = {
|
||||
staticSuperposition: async () => {
|
||||
@@ -168,7 +189,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();
|
||||
94
src/examples/image-renderer/index.ts
Normal file
94
src/examples/image-renderer/index.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*
|
||||
* Example command-line application generating images of PDB structures
|
||||
* Build: npm install --no-save gl jpeg-js pngjs // these packages are not listed in dependencies for performance reasons
|
||||
* npm run build
|
||||
* Run: node lib/commonjs/examples/image-renderer 1cbs ../outputs_1cbs/
|
||||
*/
|
||||
|
||||
import { ArgumentParser } from 'argparse';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import gl from 'gl';
|
||||
import pngjs from 'pngjs';
|
||||
import jpegjs from 'jpeg-js';
|
||||
|
||||
import { Download, ParseCif } from '../../mol-plugin-state/transforms/data';
|
||||
import { ModelFromTrajectory, StructureComponent, StructureFromModel, TrajectoryFromMmCif } from '../../mol-plugin-state/transforms/model';
|
||||
import { StructureRepresentation3D } from '../../mol-plugin-state/transforms/representation';
|
||||
import { HeadlessPluginContext } from '../../mol-plugin/headless-plugin-context';
|
||||
import { DefaultPluginSpec } from '../../mol-plugin/spec';
|
||||
import { ExternalModules, STYLIZED_POSTPROCESSING } from '../../mol-plugin/util/headless-screenshot';
|
||||
import { setFSModule } from '../../mol-util/data-source';
|
||||
|
||||
|
||||
setFSModule(fs);
|
||||
|
||||
interface Args {
|
||||
pdbId: string,
|
||||
outDirectory: string
|
||||
}
|
||||
|
||||
function parseArguments(): Args {
|
||||
const parser = new ArgumentParser({ description: 'Example command-line application generating images of PDB structures' });
|
||||
parser.add_argument('pdbId', { help: 'PDB identifier' });
|
||||
parser.add_argument('outDirectory', { help: 'Directory for outputs' });
|
||||
const args = parser.parse_args();
|
||||
return { ...args };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArguments();
|
||||
const url = `https://www.ebi.ac.uk/pdbe/entry-files/download/${args.pdbId}.bcif`;
|
||||
console.log('PDB ID:', args.pdbId);
|
||||
console.log('Source URL:', url);
|
||||
console.log('Outputs:', args.outDirectory);
|
||||
|
||||
// Create a headless plugin
|
||||
const externalModules: ExternalModules = { gl, pngjs, 'jpeg-js': jpegjs };
|
||||
const plugin = new HeadlessPluginContext(externalModules, DefaultPluginSpec(), { width: 800, height: 800 });
|
||||
await plugin.init();
|
||||
|
||||
// Download and visualize data in the plugin
|
||||
const update = plugin.build();
|
||||
const structure = update.toRoot()
|
||||
.apply(Download, { url, isBinary: true })
|
||||
.apply(ParseCif)
|
||||
.apply(TrajectoryFromMmCif)
|
||||
.apply(ModelFromTrajectory)
|
||||
.apply(StructureFromModel);
|
||||
const polymer = structure.apply(StructureComponent, { type: { name: 'static', params: 'polymer' } });
|
||||
const ligand = structure.apply(StructureComponent, { type: { name: 'static', params: 'ligand' } });
|
||||
polymer.apply(StructureRepresentation3D, {
|
||||
type: { name: 'cartoon', params: { alpha: 1 } },
|
||||
colorTheme: { name: 'sequence-id', params: {} },
|
||||
});
|
||||
ligand.apply(StructureRepresentation3D, {
|
||||
type: { name: 'ball-and-stick', params: { sizeFactor: 1 } },
|
||||
colorTheme: { name: 'element-symbol', params: { carbonColor: { name: 'element-symbol', params: {} } } },
|
||||
sizeTheme: { name: 'physical', params: {} },
|
||||
});
|
||||
await update.commit();
|
||||
|
||||
// Export images
|
||||
fs.mkdirSync(args.outDirectory, { recursive: true });
|
||||
await plugin.saveImage(path.join(args.outDirectory, 'basic.png'));
|
||||
await plugin.saveImage(path.join(args.outDirectory, 'basic.jpg'));
|
||||
await plugin.saveImage(path.join(args.outDirectory, 'large.png'), { width: 1600, height: 1200 });
|
||||
await plugin.saveImage(path.join(args.outDirectory, 'large.jpg'), { width: 1600, height: 1200 });
|
||||
await plugin.saveImage(path.join(args.outDirectory, 'stylized.png'), undefined, STYLIZED_POSTPROCESSING);
|
||||
await plugin.saveImage(path.join(args.outDirectory, 'stylized.jpg'), undefined, STYLIZED_POSTPROCESSING);
|
||||
await plugin.saveImage(path.join(args.outDirectory, 'stylized-compressed-jpg.jpg'), undefined, STYLIZED_POSTPROCESSING, undefined, 10);
|
||||
|
||||
// Export state loadable in Mol* Viewer
|
||||
await plugin.saveStateSnapshot(path.join(args.outDirectory, 'molstar-state.molj'));
|
||||
|
||||
// Cleanup
|
||||
await plugin.clear();
|
||||
plugin.dispose();
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -45,8 +45,9 @@
|
||||
<div id='controls'></div>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
LightingDemo.init('app')
|
||||
LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3)
|
||||
LightingDemo.init('app').then(() => {
|
||||
LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3);
|
||||
});
|
||||
|
||||
addHeader('Example PDB IDs');
|
||||
addControl('4KTC', () => LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3));
|
||||
@@ -83,5 +84,6 @@
|
||||
$('controls').appendChild(h);
|
||||
}
|
||||
</script>
|
||||
<!-- __MOLSTAR_ANALYTICS__ -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,59 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2023 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 { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { createPlugin } from '../../mol-plugin-ui';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { renderReact18 } from '../../mol-plugin-ui/react18';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
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: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
|
||||
outline: { name: 'on', params: { scale: 1, threshold: 0.1 } }
|
||||
},
|
||||
renderer: {
|
||||
style: { name: 'flat', params: {} }
|
||||
illustrative: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
multiScale: { name: 'off', params: {} },
|
||||
radius: 5,
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
name: 'on',
|
||||
params: {
|
||||
scale: 1,
|
||||
threshold: 0.33,
|
||||
color: Color(0x000000),
|
||||
includeTransparent: true,
|
||||
}
|
||||
},
|
||||
shadow: {
|
||||
name: 'off',
|
||||
params: {}
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 1.0,
|
||||
light: []
|
||||
}
|
||||
}
|
||||
},
|
||||
occlusion: <Preset> {
|
||||
multiSample: {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
|
||||
outline: { name: 'off', params: { } }
|
||||
},
|
||||
renderer: {
|
||||
style: { name: 'matte', params: {} }
|
||||
occlusion: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
multiScale: { name: 'off', params: {} },
|
||||
radius: 5,
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
resolutionScale: 1,
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
name: 'off',
|
||||
params: {}
|
||||
},
|
||||
shadow: {
|
||||
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: {
|
||||
style: { name: 'matte', params: {} }
|
||||
standard: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: { name: 'off', params: {} },
|
||||
outline: { name: 'off', params: {} },
|
||||
shadow: { name: 'off', params: {} },
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
light: [{ inclination: 180, azimuth: 0, color: Color.fromNormalizedRgb(1.0, 1.0, 1.0),
|
||||
intensity: 0.6 }]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -68,17 +113,21 @@ class LightingDemo {
|
||||
private bias = 1.1;
|
||||
private preset: Canvas3DPreset = 'illustrative';
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginUISpec(),
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
async init(target: string | HTMLElement) {
|
||||
this.plugin = await createPluginUI({
|
||||
target: typeof target === 'string' ? document.getElementById(target)! : target,
|
||||
render: renderReact18,
|
||||
spec: {
|
||||
...DefaultPluginUISpec(),
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
|
||||
components: {
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -87,25 +136,23 @@ class LightingDemo {
|
||||
|
||||
setPreset(preset: Canvas3DPreset) {
|
||||
const props = Canvas3DPresets[preset];
|
||||
if (props.postprocessing.occlusion?.name === 'on') {
|
||||
props.postprocessing.occlusion.params.radius = this.radius;
|
||||
props.postprocessing.occlusion.params.bias = this.bias;
|
||||
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,
|
||||
multiSample: {
|
||||
...this.plugin.canvas3d!.props.multiSample,
|
||||
...props.multiSample
|
||||
},
|
||||
renderer: {
|
||||
...this.plugin.canvas3d!.props.renderer,
|
||||
...props.renderer
|
||||
},
|
||||
postprocessing: {
|
||||
...this.plugin.canvas3d!.props.postprocessing,
|
||||
...props.postprocessing
|
||||
},
|
||||
} });
|
||||
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 = true, assemblyId = '' }: LoadParams, radius: number, bias: number) {
|
||||
@@ -114,7 +161,7 @@ class LightingDemo {
|
||||
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' });
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -10,7 +10,8 @@ import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/mod
|
||||
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 { createPlugin } from '../../mol-plugin-ui';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { renderReact18 } from '../../mol-plugin-ui/react18';
|
||||
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';
|
||||
@@ -43,22 +44,26 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
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, {
|
||||
...DefaultPluginUISpec(),
|
||||
animations: [
|
||||
AnimateModelIndex
|
||||
],
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
this.plugin = await createPluginUI({
|
||||
target: typeof target === 'string' ? document.getElementById(target)! : target,
|
||||
render: renderReact18,
|
||||
spec: {
|
||||
...DefaultPluginUISpec(),
|
||||
animations: [
|
||||
AnimateModelIndex
|
||||
],
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
remoteState: 'none'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
remoteState: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -95,7 +100,7 @@ class MolStarProteopediaWrapper {
|
||||
params: { id: assemblyId }
|
||||
} : {
|
||||
name: 'model' as const,
|
||||
params: { }
|
||||
params: {}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,7 +118,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();
|
||||
|
||||
@@ -229,7 +234,7 @@ class MolStarProteopediaWrapper {
|
||||
params: { id: asmId }
|
||||
} : {
|
||||
name: 'model' as const,
|
||||
params: { }
|
||||
params: {}
|
||||
}
|
||||
};
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
|
||||
@@ -256,7 +261,16 @@ class MolStarProteopediaWrapper {
|
||||
toggleSpin() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const trackball = this.plugin.canvas3d.props.trackball;
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
settings: {
|
||||
trackball: {
|
||||
...trackball,
|
||||
animate: trackball.animate.name === 'spin'
|
||||
? { name: 'off', params: {} }
|
||||
: { name: 'spin', params: { speed: 1 } }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
viewport = {
|
||||
@@ -269,8 +283,8 @@ 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);
|
||||
@@ -285,7 +299,7 @@ class MolStarProteopediaWrapper {
|
||||
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 }) => {
|
||||
@@ -306,7 +320,7 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private experimentalDataElement?: Element = void 0;
|
||||
experimentalData = {
|
||||
@@ -330,17 +344,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();
|
||||
|
||||
@@ -397,7 +411,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) => {
|
||||
@@ -420,7 +434,7 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
(window as any).MolStarProteopediaWrapper = MolStarProteopediaWrapper;
|
||||
@@ -50,7 +50,7 @@ const _testBasis: Basis = {
|
||||
0.025886090588624934,
|
||||
0.019164790004065606,
|
||||
-0.013539970104105408
|
||||
] as Vec3,
|
||||
],
|
||||
'shells': [
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
@@ -101,7 +101,7 @@ const _testBasis: Basis = {
|
||||
0.5082729578468134,
|
||||
1.6880351220025265,
|
||||
0.4963443067810461
|
||||
] as Vec3,
|
||||
],
|
||||
'shells': [
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
@@ -158,7 +158,7 @@ const _testBasis: Basis = {
|
||||
1.1367367844436005,
|
||||
-0.47018519422670163,
|
||||
-1.356802622574504
|
||||
] as Vec3,
|
||||
],
|
||||
'shells': [
|
||||
{
|
||||
'angularMomentum': [0],
|
||||
|
||||
@@ -53,7 +53,7 @@ export async function sphericalCollocation(
|
||||
L,
|
||||
shell.coefficients[amIndex++],
|
||||
shell.exponents,
|
||||
atom.center,
|
||||
atom.center as unknown as Vec3,
|
||||
cutoffThreshold,
|
||||
alpha
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface SphericalElectronShell {
|
||||
export interface Basis {
|
||||
atoms: {
|
||||
// in Bohr units!
|
||||
center: Vec3;
|
||||
center: [number, number, number];
|
||||
shells: SphericalElectronShell[];
|
||||
}[];
|
||||
}
|
||||
@@ -78,7 +78,7 @@ export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
|
||||
const count = geometry.length;
|
||||
const box = Box3D.expand(
|
||||
Box3D(),
|
||||
Box3D.fromVec3Array(Box3D(), geometry),
|
||||
Box3D.fromVec3Array(Box3D(), geometry as unknown as Vec3[]),
|
||||
Vec3.create(expand, expand, expand)
|
||||
);
|
||||
const size = Box3D.size(Vec3(), box);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -8,6 +8,7 @@ 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 { isTimingMode } from '../../mol-util/debug';
|
||||
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
|
||||
import { gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
|
||||
|
||||
@@ -19,9 +20,9 @@ export function createSphericalCollocationDensityGrid(
|
||||
|
||||
let matrix: Float32Array;
|
||||
if (canComputeGrid3dOnGPU(webgl)) {
|
||||
// console.time('gpu');
|
||||
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(ctx, webgl!, cubeGrid, orbitals);
|
||||
// console.timeEnd('gpu');
|
||||
if (isTimingMode) webgl.timer.mark('createSphericalCollocationDensityGrid');
|
||||
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(ctx, webgl, cubeGrid, orbitals);
|
||||
if (isTimingMode) webgl.timer.markEnd('createSphericalCollocationDensityGrid');
|
||||
} else {
|
||||
throw new Error('Missing OES_texture_float WebGL extension.');
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Inspired by https://github.com/dgasmith/gau2grid.
|
||||
*
|
||||
@@ -10,12 +10,11 @@ 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 { isTimingMode } from '../../mol-util/debug';
|
||||
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> {
|
||||
@@ -24,9 +23,9 @@ export function createSphericalCollocationGrid(
|
||||
|
||||
let matrix: Float32Array;
|
||||
if (canComputeGrid3dOnGPU(webgl)) {
|
||||
// console.time('gpu');
|
||||
matrix = await gpuComputeAlphaOrbitalsGridValues(ctx, webgl!, cubeGrid, orbital);
|
||||
// console.timeEnd('gpu');
|
||||
if (isTimingMode) webgl.timer.mark('createSphericalCollocationGrid');
|
||||
matrix = await gpuComputeAlphaOrbitalsGridValues(ctx, webgl, cubeGrid, orbital);
|
||||
if (isTimingMode) webgl.timer.markEnd('createSphericalCollocationGrid');
|
||||
} else {
|
||||
// console.time('cpu');
|
||||
matrix = await sphericalCollocation(cubeGrid, orbital, ctx);
|
||||
|
||||
@@ -166,7 +166,6 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
from: PluginStateObject.Volume.Data,
|
||||
to: PluginStateObject.Volume.Representation3D,
|
||||
params: {
|
||||
directVolume: PD.Boolean(false),
|
||||
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),
|
||||
@@ -217,19 +216,7 @@ function volumeParams(plugin: PluginContext, volume: PluginStateObject.Volume.Da
|
||||
|
||||
const value = isovalues[params.kind];
|
||||
|
||||
return createVolumeRepresentationParams(plugin, volume.data, params.directVolume ? {
|
||||
type: 'direct-volume',
|
||||
typeParams: {
|
||||
alpha: params.alpha,
|
||||
renderMode: {
|
||||
name: 'isosurface',
|
||||
params: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, singleLayer: false }
|
||||
},
|
||||
xrayShaded: params.xrayShaded
|
||||
},
|
||||
color: 'uniform',
|
||||
colorParams: { value: params.color }
|
||||
} : {
|
||||
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',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -43,9 +43,9 @@ export type BilayerPlanesProps = PD.Values<BilayerPlanesParams>
|
||||
const BilayerRimsParams = {
|
||||
...Lines.Params,
|
||||
...SharedParams,
|
||||
lineSizeAttenuation: PD.Boolean(true),
|
||||
linesSize: PD.Numeric(0.3, { min: 0.01, max: 50, step: 0.01 }),
|
||||
dashedLines: PD.Boolean(true),
|
||||
lineSizeAttenuation: PD.Boolean(false),
|
||||
linesSize: PD.Numeric(0.5, { min: 0.01, max: 50, step: 0.01 }),
|
||||
dashedLines: PD.Boolean(false),
|
||||
};
|
||||
export type BilayerRimsParams = typeof BilayerRimsParams
|
||||
export type BilayerRimsProps = PD.Values<BilayerRimsParams>
|
||||
|
||||
@@ -4,30 +4,31 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry, AssemblySymmetryDataProvider } from './prop';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetryData, AssemblySymmetryDataProvider, AssemblySymmetryDataParams } from './prop';
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
|
||||
import { AssemblySymmetryParams, AssemblySymmetryRepresentation } from './representation';
|
||||
import { AssemblySymmetryClusterColorThemeProvider } from './color';
|
||||
import { PluginStateTransform, PluginStateObject } from '../../../mol-plugin-state/objects';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateTransformer, StateAction, StateObject, StateTransform, StateObjectRef } from '../../../mol-state';
|
||||
import { GenericRepresentationRef } from '../../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
import { PluginStateTransform, PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { Task } from '../../mol-task';
|
||||
import { PluginConfigItem } from '../../mol-plugin/config';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateTransformer, StateAction, StateObject, StateTransform, StateObjectRef } from '../../mol-state';
|
||||
import { GenericRepresentationRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
import { AssemblySymmetryControls } from './ui';
|
||||
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
|
||||
const Tag = AssemblySymmetry.Tag;
|
||||
const Tag = AssemblySymmetryData.Tag;
|
||||
|
||||
export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean }>({
|
||||
name: 'rcsb-assembly-symmetry-prop',
|
||||
export const AssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean }>({
|
||||
name: 'assembly-symmetry-prop',
|
||||
category: 'custom-props',
|
||||
display: {
|
||||
name: 'Assembly Symmetry',
|
||||
description: 'Assembly Symmetry data calculated with BioJava, obtained via RCSB PDB.'
|
||||
description: 'Assembly Symmetry data provided by RCSB PDB (calculated with BioJava) or by PDBe.'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> {
|
||||
private provider = AssemblySymmetryProvider
|
||||
private provider = AssemblySymmetryProvider;
|
||||
|
||||
register(): void {
|
||||
this.ctx.state.data.actions.add(InitAssemblySymmetry3D);
|
||||
@@ -65,7 +66,7 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
|
||||
},
|
||||
params: () => ({
|
||||
autoAttach: PD.Boolean(false),
|
||||
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl)
|
||||
serverUrl: PD.Text(AssemblySymmetryData.DefaultServerUrl)
|
||||
})
|
||||
});
|
||||
|
||||
@@ -74,23 +75,24 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
|
||||
export const InitAssemblySymmetry3D = StateAction.build({
|
||||
display: {
|
||||
name: 'Assembly Symmetry',
|
||||
description: 'Initialize Assembly Symmetry axes and cage. Data calculated with BioJava, obtained via RCSB PDB.'
|
||||
description: 'Initialize Assembly Symmetry axes and cage. Data provided by RCSB PDB (calculated with BioJava) or by PDBe.'
|
||||
},
|
||||
from: PluginStateObject.Molecule.Structure,
|
||||
isApplicable: (a) => AssemblySymmetry.isApplicable(a.data)
|
||||
})(({ a, ref, state }, plugin: PluginContext) => Task.create('Init Assembly Symmetry', async ctx => {
|
||||
isApplicable: (a) => AssemblySymmetryData.isApplicable(a.data),
|
||||
params: (a, plugin: PluginContext) => getConfiguredDefaultParams(plugin)
|
||||
})(({ a, ref, state, params }, plugin: PluginContext) => Task.create('Init Assembly Symmetry', async ctx => {
|
||||
try {
|
||||
const propCtx = { runtime: ctx, assetManager: plugin.managers.asset };
|
||||
await AssemblySymmetryDataProvider.attach(propCtx, a.data);
|
||||
await AssemblySymmetryDataProvider.attach(propCtx, a.data, params);
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(a.data).value;
|
||||
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
|
||||
await AssemblySymmetryProvider.attach(propCtx, a.data, { symmetryIndex });
|
||||
const symmetryIndex = assemblySymmetryData ? AssemblySymmetryData.firstNonC1(assemblySymmetryData) : -1;
|
||||
await AssemblySymmetryProvider.attach(propCtx, a.data, { ...params, symmetryIndex });
|
||||
} catch (e) {
|
||||
plugin.log.error(`Assembly Symmetry: ${e}`);
|
||||
return;
|
||||
}
|
||||
const tree = state.build().to(ref)
|
||||
.applyOrUpdateTagged(AssemblySymmetry.Tag.Representation, AssemblySymmetry3D);
|
||||
.applyOrUpdateTagged(AssemblySymmetryData.Tag.Representation, AssemblySymmetry3D);
|
||||
await state.updateTree(tree).runInContext(ctx);
|
||||
}));
|
||||
|
||||
@@ -101,7 +103,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
|
||||
name: Tag.Representation,
|
||||
display: {
|
||||
name: 'Assembly Symmetry',
|
||||
description: 'Assembly Symmetry axes and cage. Data calculated with BioJava, obtained via RCSB PDB.'
|
||||
description: 'Assembly Symmetry axes and cage. Data provided by RCSB PDB (calculated with BioJava) or by PDBe.'
|
||||
},
|
||||
from: PluginStateObject.Molecule.Structure,
|
||||
to: PluginStateObject.Shape.Representation3D,
|
||||
@@ -146,26 +148,27 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
|
||||
});
|
||||
},
|
||||
isApplicable(a) {
|
||||
return AssemblySymmetry.isApplicable(a.data);
|
||||
return AssemblySymmetryData.isApplicable(a.data);
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
export const AssemblySymmetryPresetParams = {
|
||||
...StructureRepresentationPresetProvider.CommonParams,
|
||||
};
|
||||
|
||||
export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-rcsb-assembly-symmetry',
|
||||
id: 'preset-structure-representation-assembly-symmetry',
|
||||
display: {
|
||||
name: 'Assembly Symmetry', group: 'Annotation',
|
||||
description: 'Shows Assembly Symmetry axes and cage; colors structure according to assembly symmetry cluster membership. Data calculated with BioJava, obtained via RCSB PDB.'
|
||||
description: 'Shows Assembly Symmetry axes and cage; colors structure according to assembly symmetry cluster membership. Data provided by RCSB PDB (calculated with BioJava) or by PDBe.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return AssemblySymmetry.isApplicable(a.data);
|
||||
return AssemblySymmetryData.isApplicable(a.data);
|
||||
},
|
||||
params: (a, plugin) => {
|
||||
return {
|
||||
...StructureRepresentationPresetProvider.CommonParams,
|
||||
...getConfiguredDefaultParams(plugin)
|
||||
};
|
||||
},
|
||||
params: () => AssemblySymmetryPresetParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
@@ -174,15 +177,16 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
|
||||
if (!AssemblySymmetryDataProvider.get(structure).value) {
|
||||
await plugin.runTask(Task.create('Assembly Symmetry', async runtime => {
|
||||
const propCtx = { runtime, assetManager: plugin.managers.asset };
|
||||
await AssemblySymmetryDataProvider.attach(propCtx, structure);
|
||||
const propProps = { serverType: params.serverType, serverUrl: params.serverUrl };
|
||||
await AssemblySymmetryDataProvider.attach(propCtx, structure, propProps);
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value;
|
||||
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
|
||||
await AssemblySymmetryProvider.attach(propCtx, structure, { symmetryIndex });
|
||||
const symmetryIndex = assemblySymmetryData ? AssemblySymmetryData.firstNonC1(assemblySymmetryData) : -1;
|
||||
await AssemblySymmetryProvider.attach(propCtx, structure, { ...propProps, symmetryIndex });
|
||||
}));
|
||||
}
|
||||
|
||||
const assemblySymmetry = await tryCreateAssemblySymmetry(plugin, structureCell);
|
||||
const colorTheme = assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
|
||||
const colorTheme = getAssemblySymmetryConfig(plugin).ApplyColors && assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
|
||||
const preset = await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
|
||||
|
||||
return { components: preset.components, representations: { ...preset.representations, assemblySymmetry } };
|
||||
@@ -192,6 +196,29 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
|
||||
export function tryCreateAssemblySymmetry(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, params?: StateTransformer.Params<AssemblySymmetry3D>, initialState?: Partial<StateTransform.State>) {
|
||||
const state = plugin.state.data;
|
||||
const assemblySymmetry = state.build().to(structure)
|
||||
.applyOrUpdateTagged(AssemblySymmetry.Tag.Representation, AssemblySymmetry3D, params, { state: initialState });
|
||||
.applyOrUpdateTagged(AssemblySymmetryData.Tag.Representation, AssemblySymmetry3D, params, { state: initialState });
|
||||
return assemblySymmetry.commit({ revertOnError: true });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export const AssemblySymmetryConfig = {
|
||||
DefaultServerType: new PluginConfigItem('assembly-symmetry.server-type', AssemblySymmetryDataParams.serverType.defaultValue),
|
||||
DefaultServerUrl: new PluginConfigItem('assembly-symmetry.server-url', AssemblySymmetryDataParams.serverUrl.defaultValue),
|
||||
ApplyColors: new PluginConfigItem('assembly-symmetry.apply-colors', true),
|
||||
};
|
||||
|
||||
export function getAssemblySymmetryConfig(plugin: PluginContext): { [key in keyof typeof AssemblySymmetryConfig]: NonNullable<typeof AssemblySymmetryConfig[key]['defaultValue']> } {
|
||||
return {
|
||||
ApplyColors: plugin.config.get(AssemblySymmetryConfig.ApplyColors) ?? AssemblySymmetryConfig.ApplyColors.defaultValue ?? true,
|
||||
DefaultServerType: plugin.config.get(AssemblySymmetryConfig.DefaultServerType) ?? AssemblySymmetryConfig.DefaultServerType.defaultValue ?? AssemblySymmetryDataParams.serverType.defaultValue,
|
||||
DefaultServerUrl: plugin.config.get(AssemblySymmetryConfig.DefaultServerUrl) ?? AssemblySymmetryConfig.DefaultServerUrl.defaultValue ?? AssemblySymmetryDataParams.serverUrl.defaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
function getConfiguredDefaultParams(plugin: PluginContext) {
|
||||
const config = getAssemblySymmetryConfig(plugin);
|
||||
const params = PD.clone(AssemblySymmetryDataParams);
|
||||
PD.setDefaultValues(params, { serverType: config.DefaultServerType, serverUrl: config.DefaultServerUrl });
|
||||
return params;
|
||||
}
|
||||
@@ -4,16 +4,16 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry } from './prop';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { Unit, StructureElement, StructureProperties, Bond } from '../../../mol-model/structure';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
|
||||
import { getPalette, getPaletteParams } from '../../../mol-util/color/palette';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { ThemeDataContext } from '../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetryData } from './prop';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { Unit, StructureElement, StructureProperties, Bond } from '../../mol-model/structure';
|
||||
import { Location } from '../../mol-model/location';
|
||||
import { ScaleLegend, TableLegend } from '../../mol-util/legend';
|
||||
import { getPalette, getPaletteParams } from '../../mol-util/color/palette';
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
|
||||
@@ -94,19 +94,19 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
|
||||
color,
|
||||
props,
|
||||
contextHash,
|
||||
description: 'Assigns chain colors according to assembly symmetry cluster membership calculated with BioJava and obtained via RCSB PDB.',
|
||||
description: 'Assigns chain colors according to assembly symmetry cluster membership data provided by RCSB PDB (calculated with BioJava) or by PDBe.',
|
||||
legend
|
||||
};
|
||||
}
|
||||
|
||||
export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<AssemblySymmetryClusterColorThemeParams, AssemblySymmetry.Tag.Cluster> = {
|
||||
name: AssemblySymmetry.Tag.Cluster,
|
||||
export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<AssemblySymmetryClusterColorThemeParams, AssemblySymmetryData.Tag.Cluster> = {
|
||||
name: AssemblySymmetryData.Tag.Cluster,
|
||||
label: 'Assembly Symmetry Cluster',
|
||||
category: ColorTheme.Category.Symmetry,
|
||||
factory: AssemblySymmetryClusterColorTheme,
|
||||
getParams: getAssemblySymmetryClusterColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(AssemblySymmetryClusterColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => AssemblySymmetry.isApplicable(ctx.structure),
|
||||
isApplicable: (ctx: ThemeDataContext) => AssemblySymmetryData.isApplicable(ctx.structure),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && AssemblySymmetryProvider.ref(data.structure, false)
|
||||
7
src/extensions/assembly-symmetry/index.ts
Normal file
7
src/extensions/assembly-symmetry/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
export { AssemblySymmetry, AssemblySymmetryConfig } from './behavior';
|
||||
@@ -1,25 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { AssemblySymmetryQuery, AssemblySymmetryQueryVariables } from '../graphql/types';
|
||||
import { symmetry_gql } from '../graphql/symmetry.gql';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Structure, Model, StructureSelection, QueryContext } from '../../mol-model/structure';
|
||||
import { Database as _Database, Column } from '../../mol-data/db';
|
||||
import { GraphQLClient } from '../../mol-util/graphql-client';
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
import { CustomStructureProperty } from '../../mol-model-props/common/custom-structure-property';
|
||||
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { ReadonlyVec3 } from '../../mol-math/linear-algebra/3d/vec3';
|
||||
import { SetUtils } from '../../mol-util/set';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Structure, Model, StructureSelection, QueryContext } from '../../../mol-model/structure';
|
||||
import { Database as _Database, Column } from '../../../mol-data/db';
|
||||
import { GraphQLClient } from '../../../mol-util/graphql-client';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { NonNullableArray } from '../../../mol-util/type-helpers';
|
||||
import { CustomStructureProperty } from '../../../mol-model-props/common/custom-structure-property';
|
||||
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
import { ReadonlyVec3 } from '../../../mol-math/linear-algebra/3d/vec3';
|
||||
import { SetUtils } from '../../../mol-util/set';
|
||||
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
|
||||
import { compile } from '../../../mol-script/runtime/query/compiler';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
|
||||
const rcsb_symmetry_gql = /* GraphQL */ `
|
||||
query AssemblySymmetry($assembly_id: String!, $entry_id: String!) {
|
||||
assembly(assembly_id: $assembly_id, entry_id: $entry_id) {
|
||||
rcsb_struct_symmetry {
|
||||
clusters {
|
||||
avg_rmsd
|
||||
members {
|
||||
asym_id
|
||||
pdbx_struct_oper_list_ids
|
||||
}
|
||||
}
|
||||
kind
|
||||
oligomeric_state
|
||||
rotation_axes {
|
||||
order
|
||||
start
|
||||
end
|
||||
}
|
||||
stoichiometry
|
||||
symbol
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const BiologicalAssemblyNames = new Set([
|
||||
'author_and_software_defined_assembly',
|
||||
@@ -42,18 +65,18 @@ export function isBiologicalAssembly(structure: Structure): boolean {
|
||||
return BiologicalAssemblyNames.has(details);
|
||||
}
|
||||
|
||||
export namespace AssemblySymmetry {
|
||||
export namespace AssemblySymmetryData {
|
||||
export enum Tag {
|
||||
Cluster = 'rcsb-assembly-symmetry-cluster',
|
||||
Representation = 'rcsb-assembly-symmetry-3d'
|
||||
Cluster = 'assembly-symmetry-cluster',
|
||||
Representation = 'assembly-symmetry-3d'
|
||||
}
|
||||
|
||||
export const DefaultServerUrl = 'https://data.rcsb.org/graphql';
|
||||
export const DefaultServerUrl = 'https://data.rcsb.org/graphql'; // Alternative: 'https://www.ebi.ac.uk/pdbe/aggregated-api/pdb/symmetry' (if serverType is 'pdbe')
|
||||
|
||||
export function isApplicable(structure?: Structure): boolean {
|
||||
return (
|
||||
!!structure && structure.models.length === 1 &&
|
||||
Model.isFromPdbArchive(structure.models[0]) &&
|
||||
Model.hasPdbId(structure.models[0]) &&
|
||||
isBiologicalAssembly(structure)
|
||||
);
|
||||
}
|
||||
@@ -61,12 +84,17 @@ export namespace AssemblySymmetry {
|
||||
export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
|
||||
if (!isApplicable(structure)) return { value: [] };
|
||||
|
||||
if (props.serverType === 'pdbe') return fetchPDBe(ctx, structure, props);
|
||||
else return fetchRCSB(ctx, structure, props);
|
||||
}
|
||||
|
||||
export async function fetchRCSB(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
|
||||
const client = new GraphQLClient(props.serverUrl, ctx.assetManager);
|
||||
const variables: AssemblySymmetryQueryVariables = {
|
||||
const variables = {
|
||||
assembly_id: structure.units[0].conformation.operator.assembly?.id || '',
|
||||
entry_id: structure.units[0].model.entryId
|
||||
};
|
||||
const result = await client.request(ctx.runtime, symmetry_gql, variables);
|
||||
const result = await client.request(ctx.runtime, rcsb_symmetry_gql, variables);
|
||||
let value: AssemblySymmetryDataValue = [];
|
||||
|
||||
if (!result.data.assembly?.rcsb_struct_symmetry) {
|
||||
@@ -77,6 +105,37 @@ export namespace AssemblySymmetry {
|
||||
return { value, assets: [result] };
|
||||
}
|
||||
|
||||
async function fetchPDBe(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
|
||||
const assembly_id = structure.units[0].conformation.operator.assembly?.id || '-1'; // should use '' instead of '-1' but the API does not support non-number assembly_id
|
||||
const entry_id = structure.units[0].model.entryId.toLowerCase();
|
||||
const url = `${props.serverUrl}/${entry_id}?assembly_id=${assembly_id}`;
|
||||
const asset = Asset.getUrlAsset(ctx.assetManager, url);
|
||||
let dataWrapper: Asset.Wrapper<'json'>;
|
||||
try {
|
||||
dataWrapper = await ctx.assetManager.resolve(asset, 'json').runInContext(ctx.runtime);
|
||||
} catch (err) {
|
||||
// PDBe API returns 404 when there are no symmetries -> treat as success with empty json in body
|
||||
if (`${err}`.includes('404')) { // dirrrty
|
||||
dataWrapper = Asset.Wrapper({}, asset, ctx.assetManager);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
const data = dataWrapper.data;
|
||||
|
||||
const value: AssemblySymmetryDataValue = (data[entry_id] ?? []).map((v: any) => ({
|
||||
kind: 'Global Symmetry',
|
||||
oligomeric_state: v.oligomeric_state,
|
||||
stoichiometry: [v.stoichiometry],
|
||||
symbol: v.symbol,
|
||||
type: v.type,
|
||||
clusters: [],
|
||||
rotation_axes: v.rotation_axes,
|
||||
}));
|
||||
|
||||
return { value, assets: [dataWrapper] };
|
||||
}
|
||||
|
||||
/** Returns the index of the first non C1 symmetry or -1 */
|
||||
export function firstNonC1(assemblySymmetryData: AssemblySymmetryDataValue) {
|
||||
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
|
||||
@@ -114,7 +173,7 @@ export namespace AssemblySymmetry {
|
||||
|
||||
/** Returns structure limited to all cluster member chains */
|
||||
export function getStructure(structure: Structure, assemblySymmetry: AssemblySymmetryValue) {
|
||||
const asymIds = AssemblySymmetry.getAsymIds(assemblySymmetry);
|
||||
const asymIds = AssemblySymmetryData.getAsymIds(assemblySymmetry);
|
||||
return asymIds.length > 0 ? getAsymIdsStructure(structure, asymIds) : structure;
|
||||
}
|
||||
}
|
||||
@@ -147,26 +206,45 @@ export function getSymmetrySelectParam(structure?: Structure) {
|
||||
//
|
||||
|
||||
export const AssemblySymmetryDataParams = {
|
||||
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl, { description: 'GraphQL endpoint URL' })
|
||||
serverType: PD.Select('rcsb', [['rcsb', 'RCSB'], ['pdbe', 'PDBe']] as const),
|
||||
serverUrl: PD.Text(AssemblySymmetryData.DefaultServerUrl, { description: 'GraphQL endpoint URL (if server type is RCSB) or PDBe API endpoint URL (if server type is PDBe)' })
|
||||
};
|
||||
export type AssemblySymmetryDataParams = typeof AssemblySymmetryDataParams
|
||||
export type AssemblySymmetryDataProps = PD.Values<AssemblySymmetryDataParams>
|
||||
|
||||
export type AssemblySymmetryDataValue = NonNullableArray<NonNullable<NonNullable<AssemblySymmetryQuery['assembly']>['rcsb_struct_symmetry']>>
|
||||
export type AssemblySymmetryDataValue = ReadonlyArray<{
|
||||
readonly kind: string,
|
||||
readonly oligomeric_state: string,
|
||||
readonly stoichiometry: ReadonlyArray<string>,
|
||||
readonly symbol: string,
|
||||
readonly type: string,
|
||||
readonly clusters: ReadonlyArray<{
|
||||
readonly avg_rmsd?: number,
|
||||
readonly members: ReadonlyArray<{
|
||||
readonly asym_id: string,
|
||||
readonly pdbx_struct_oper_list_ids?: ReadonlyArray<string>
|
||||
}>
|
||||
}>,
|
||||
readonly rotation_axes?: ReadonlyArray<{
|
||||
readonly order?: number,
|
||||
readonly start: ReadonlyArray<number>,
|
||||
readonly end: ReadonlyArray<number>
|
||||
}>
|
||||
}>
|
||||
|
||||
export const AssemblySymmetryDataProvider: CustomStructureProperty.Provider<AssemblySymmetryDataParams, AssemblySymmetryDataValue> = CustomStructureProperty.createProvider({
|
||||
label: 'Assembly Symmetry Data',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'rcsb_struct_symmetry_data',
|
||||
name: 'molstar_struct_symmetry_data',
|
||||
// TODO `cifExport` and `symbol`
|
||||
}),
|
||||
type: 'root',
|
||||
defaultParams: AssemblySymmetryDataParams,
|
||||
getParams: (data: Structure) => AssemblySymmetryDataParams,
|
||||
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
|
||||
isApplicable: (data: Structure) => AssemblySymmetryData.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryDataProps>) => {
|
||||
const p = { ...PD.getDefaultValues(AssemblySymmetryDataParams), ...props };
|
||||
return await AssemblySymmetry.fetch(ctx, data, p);
|
||||
return await AssemblySymmetryData.fetch(ctx, data, p);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -174,7 +252,7 @@ export const AssemblySymmetryDataProvider: CustomStructureProperty.Provider<Asse
|
||||
|
||||
function getAssemblySymmetryParams(data?: Structure) {
|
||||
return {
|
||||
... AssemblySymmetryDataParams,
|
||||
...AssemblySymmetryDataParams,
|
||||
symmetryIndex: getSymmetrySelectParam(data)
|
||||
};
|
||||
}
|
||||
@@ -188,13 +266,13 @@ export type AssemblySymmetryValue = AssemblySymmetryDataValue[0]
|
||||
export const AssemblySymmetryProvider: CustomStructureProperty.Provider<AssemblySymmetryParams, AssemblySymmetryValue> = CustomStructureProperty.createProvider({
|
||||
label: 'Assembly Symmetry',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'rcsb_struct_symmetry',
|
||||
name: 'molstar_struct_symmetry',
|
||||
// TODO `cifExport` and `symbol`
|
||||
}),
|
||||
type: 'root',
|
||||
defaultParams: AssemblySymmetryParams,
|
||||
getParams: getAssemblySymmetryParams,
|
||||
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
|
||||
isApplicable: (data: Structure) => AssemblySymmetryData.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryProps>) => {
|
||||
const p = { ...PD.getDefaultValues(getAssemblySymmetryParams(data)), ...props };
|
||||
await AssemblySymmetryDataProvider.attach(ctx, data, p);
|
||||
@@ -4,35 +4,35 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { AssemblySymmetryValue, AssemblySymmetryProvider, AssemblySymmetry } from './prop';
|
||||
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3, Mat4, Mat3 } from '../../../mol-math/linear-algebra';
|
||||
import { addCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
import { Shape } from '../../../mol-model/shape';
|
||||
import { ColorNames } from '../../../mol-util/color/names';
|
||||
import { ShapeRepresentation } from '../../../mol-repr/shape/representation';
|
||||
import { MarkerActions } from '../../../mol-util/marker-action';
|
||||
import { Prism, PrismCage } from '../../../mol-geo/primitive/prism';
|
||||
import { Wedge, WedgeCage } from '../../../mol-geo/primitive/wedge';
|
||||
import { Primitive, transformPrimitive } from '../../../mol-geo/primitive/primitive';
|
||||
import { memoize1 } from '../../../mol-util/memoize';
|
||||
import { polygon } from '../../../mol-geo/primitive/polygon';
|
||||
import { ColorMap, Color } from '../../../mol-util/color';
|
||||
import { TableLegend } from '../../../mol-util/legend';
|
||||
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
|
||||
import { Cage, transformCage, cloneCage } from '../../../mol-geo/primitive/cage';
|
||||
import { OctahedronCage } from '../../../mol-geo/primitive/octahedron';
|
||||
import { TetrahedronCage } from '../../../mol-geo/primitive/tetrahedron';
|
||||
import { IcosahedronCage } from '../../../mol-geo/primitive/icosahedron';
|
||||
import { degToRad, radToDeg } from '../../../mol-math/misc';
|
||||
import { Mutable } from '../../../mol-util/type-helpers';
|
||||
import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
|
||||
import { Structure } from '../../../mol-model/structure';
|
||||
import { isInteger } from '../../../mol-util/number';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { AssemblySymmetryValue, AssemblySymmetryProvider, AssemblySymmetryData } from './prop';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3, Mat4, Mat3 } from '../../mol-math/linear-algebra';
|
||||
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ShapeRepresentation } from '../../mol-repr/shape/representation';
|
||||
import { MarkerActions } from '../../mol-util/marker-action';
|
||||
import { Prism, PrismCage } from '../../mol-geo/primitive/prism';
|
||||
import { Wedge, WedgeCage } from '../../mol-geo/primitive/wedge';
|
||||
import { Primitive, transformPrimitive } from '../../mol-geo/primitive/primitive';
|
||||
import { memoize1 } from '../../mol-util/memoize';
|
||||
import { polygon } from '../../mol-geo/primitive/polygon';
|
||||
import { ColorMap, Color } from '../../mol-util/color';
|
||||
import { TableLegend } from '../../mol-util/legend';
|
||||
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../mol-repr/representation';
|
||||
import { Cage, transformCage, cloneCage } from '../../mol-geo/primitive/cage';
|
||||
import { OctahedronCage } from '../../mol-geo/primitive/octahedron';
|
||||
import { TetrahedronCage } from '../../mol-geo/primitive/tetrahedron';
|
||||
import { IcosahedronCage } from '../../mol-geo/primitive/icosahedron';
|
||||
import { degToRad, radToDeg } from '../../mol-math/misc';
|
||||
import { Mutable } from '../../mol-util/type-helpers';
|
||||
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { isInteger } from '../../mol-util/number';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
|
||||
const OrderColors = ColorMap({
|
||||
'2': ColorNames.deepskyblue,
|
||||
@@ -117,7 +117,7 @@ function getAxesMesh(data: AssemblySymmetryValue, props: PD.Values<AxesParams>,
|
||||
const { scale } = props;
|
||||
|
||||
const { rotation_axes } = data;
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh);
|
||||
if (!AssemblySymmetryData.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh);
|
||||
|
||||
const { start, end } = rotation_axes[0];
|
||||
const radius = (Vec3.distance(start, end) / 500) * scale;
|
||||
@@ -232,12 +232,12 @@ function getSymbolScale(symbol: string) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.RotationAxes, size: number, structure: Structure) {
|
||||
function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetryData.RotationAxes, size: number, structure: Structure) {
|
||||
const eye = Vec3();
|
||||
const target = Vec3();
|
||||
const dir = Vec3();
|
||||
const up = Vec3();
|
||||
let pair: Mutable<AssemblySymmetry.RotationAxes> | undefined = undefined;
|
||||
let pair: Mutable<AssemblySymmetryData.RotationAxes> | undefined = undefined;
|
||||
|
||||
if (symbol.startsWith('C')) {
|
||||
pair = [axes[0]];
|
||||
@@ -337,9 +337,9 @@ function getCageMesh(data: Structure, props: PD.Values<CageParams>, mesh?: Mesh)
|
||||
const { scale } = props;
|
||||
|
||||
const { rotation_axes, symbol } = assemblySymmetry;
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh);
|
||||
if (!AssemblySymmetryData.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh);
|
||||
|
||||
const structure = AssemblySymmetry.getStructure(data, assemblySymmetry);
|
||||
const structure = AssemblySymmetryData.getStructure(data, assemblySymmetry);
|
||||
|
||||
const cage = getSymbolCage(symbol);
|
||||
if (!cage) return Mesh.createEmpty(mesh);
|
||||
@@ -4,18 +4,18 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CollapsableState, CollapsableControls } from '../../../mol-plugin-ui/base';
|
||||
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
|
||||
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry } from './behavior';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetryProps, AssemblySymmetryDataProvider, AssemblySymmetry } from './prop';
|
||||
import { ParameterControls } from '../../../mol-plugin-ui/controls/parameters';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { StructureHierarchyManager } from '../../../mol-plugin-state/manager/structure/hierarchy';
|
||||
import { StateAction, StateSelection } from '../../../mol-state';
|
||||
import { PluginStateObject } from '../../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { ExtensionSvg, CheckSvg } from '../../../mol-plugin-ui/controls/icons';
|
||||
import { CollapsableState, CollapsableControls } from '../../mol-plugin-ui/base';
|
||||
import { ApplyActionControl } from '../../mol-plugin-ui/state/apply-action';
|
||||
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry, getAssemblySymmetryConfig } from './behavior';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetryProps, AssemblySymmetryDataProvider, AssemblySymmetryData } from './prop';
|
||||
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { StructureHierarchyManager } from '../../mol-plugin-state/manager/structure/hierarchy';
|
||||
import { StateAction, StateSelection } from '../../mol-state';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ExtensionSvg, CheckSvg } from '../../mol-plugin-ui/controls/icons';
|
||||
|
||||
interface AssemblySymmetryControlState extends CollapsableState {
|
||||
isBusy: boolean
|
||||
@@ -72,6 +72,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
|
||||
get params() {
|
||||
const structure = this.pivot.cell.obj?.data;
|
||||
const params = PD.clone(structure ? AssemblySymmetryProvider.getParams(structure) : AssemblySymmetryProvider.defaultParams);
|
||||
params.serverType.isHidden = true;
|
||||
params.serverUrl.isHidden = true;
|
||||
return params;
|
||||
}
|
||||
@@ -106,22 +107,24 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
|
||||
for (const components of this.plugin.managers.structure.hierarchy.currentComponentGroups) {
|
||||
if (values.symmetryIndex === -1) {
|
||||
const name = components[0]?.representations[0]?.cell.transform.params?.colorTheme.name;
|
||||
if (name === AssemblySymmetry.Tag.Cluster) {
|
||||
if (name === AssemblySymmetryData.Tag.Cluster) {
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: 'default' });
|
||||
}
|
||||
} else {
|
||||
tryCreateAssemblySymmetry(this.plugin, s.cell);
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: AssemblySymmetry.Tag.Cluster as any });
|
||||
if (getAssemblySymmetryConfig(this.plugin).ApplyColors) {
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: AssemblySymmetryData.Tag.Cluster as any });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paramsOnChange = (options: AssemblySymmetryProps) => {
|
||||
this.updateAssemblySymmetry(options);
|
||||
}
|
||||
};
|
||||
|
||||
get hasAssemblySymmetry3D() {
|
||||
return !this.pivot.cell.parent || !!StateSelection.findTagInSubtree(this.pivot.cell.parent.tree, this.pivot.cell.transform.ref, AssemblySymmetry.Tag.Representation);
|
||||
return !this.pivot.cell.parent || !!StateSelection.findTagInSubtree(this.pivot.cell.parent.tree, this.pivot.cell.transform.ref, AssemblySymmetryData.Tag.Representation);
|
||||
}
|
||||
|
||||
get enable() {
|
||||
@@ -151,5 +154,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
|
||||
const EnableAssemblySymmetry3D = StateAction.build({
|
||||
from: PluginStateObject.Molecule.Structure,
|
||||
})(({ a, ref, state }, plugin: PluginContext) => Task.create('Enable Assembly Symmetry', async ctx => {
|
||||
await AssemblySymmetryPreset.apply(ref, Object.create(null), plugin);
|
||||
const presetParams = AssemblySymmetryPreset.params?.(a, plugin) as PD.Params | undefined;
|
||||
const presetProps = presetParams ? PD.getDefaultValues(presetParams) : Object.create(null);
|
||||
await AssemblySymmetryPreset.apply(ref, presetProps, plugin);
|
||||
}));
|
||||
BIN
src/extensions/backgrounds/images/cells.jpg
Normal file
BIN
src/extensions/backgrounds/images/cells.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
99
src/extensions/backgrounds/index.ts
Normal file
99
src/extensions/backgrounds/index.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { Color } from '../../mol-util/color/color';
|
||||
|
||||
// from https://visualsonline.cancer.gov/details.cfm?imageid=2304, public domain
|
||||
import image_cells from './images/cells.jpg';
|
||||
|
||||
// created with http://alexcpeterson.com/spacescape/
|
||||
import face_nebula_nx from './skyboxes/nebula/nebula_left2.jpg';
|
||||
import face_nebula_ny from './skyboxes/nebula/nebula_bottom4.jpg';
|
||||
import face_nebula_nz from './skyboxes/nebula/nebula_back6.jpg';
|
||||
import face_nebula_px from './skyboxes/nebula/nebula_right1.jpg';
|
||||
import face_nebula_py from './skyboxes/nebula/nebula_top3.jpg';
|
||||
import face_nebula_pz from './skyboxes/nebula/nebula_front5.jpg';
|
||||
|
||||
export const Backgrounds = PluginBehavior.create<{ }>({
|
||||
name: 'extension-backgrounds',
|
||||
category: 'misc',
|
||||
display: {
|
||||
name: 'Backgrounds'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ }> {
|
||||
register(): void {
|
||||
this.ctx.config.set(PluginConfig.Background.Styles, [
|
||||
[{
|
||||
variant: {
|
||||
name: 'off',
|
||||
params: {}
|
||||
}
|
||||
}, 'Off'],
|
||||
[{
|
||||
variant: {
|
||||
name: 'radialGradient',
|
||||
params: {
|
||||
centerColor: Color(0xFFFFFF),
|
||||
edgeColor: Color(0x808080),
|
||||
ratio: 0.2,
|
||||
coverage: 'viewport',
|
||||
}
|
||||
}
|
||||
}, 'Light Radial Gradient'],
|
||||
[{
|
||||
variant: {
|
||||
name: 'image',
|
||||
params: {
|
||||
source: {
|
||||
name: 'url',
|
||||
params: image_cells
|
||||
},
|
||||
lightness: 0,
|
||||
saturation: 0,
|
||||
opacity: 1,
|
||||
blur: 0,
|
||||
coverage: 'viewport',
|
||||
}
|
||||
}
|
||||
}, 'Normal Cells Image'],
|
||||
[{
|
||||
variant: {
|
||||
name: 'skybox',
|
||||
params: {
|
||||
faces: {
|
||||
name: 'urls',
|
||||
params: {
|
||||
nx: face_nebula_nx,
|
||||
ny: face_nebula_ny,
|
||||
nz: face_nebula_nz,
|
||||
px: face_nebula_px,
|
||||
py: face_nebula_py,
|
||||
pz: face_nebula_pz,
|
||||
}
|
||||
},
|
||||
lightness: 0,
|
||||
saturation: 0,
|
||||
opacity: 1,
|
||||
blur: 0.3,
|
||||
rotation: { x: 0, y: 0, z: 0 },
|
||||
}
|
||||
}
|
||||
}, 'Purple Nebula Skybox'],
|
||||
]);
|
||||
}
|
||||
|
||||
update() {
|
||||
return false;
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.ctx.config.set(PluginConfig.Background.Styles, []);
|
||||
}
|
||||
},
|
||||
params: () => ({ })
|
||||
});
|
||||
BIN
src/extensions/backgrounds/skyboxes/nebula/nebula_back6.jpg
Normal file
BIN
src/extensions/backgrounds/skyboxes/nebula/nebula_back6.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
BIN
src/extensions/backgrounds/skyboxes/nebula/nebula_bottom4.jpg
Normal file
BIN
src/extensions/backgrounds/skyboxes/nebula/nebula_bottom4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user