mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 14:04:36 +08:00
Compare commits
927 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acf6c31a36 | ||
|
|
339b2e696c | ||
|
|
6417fd49d6 | ||
|
|
374fd4db65 | ||
|
|
0b70dd9e38 | ||
|
|
55b19a7922 | ||
|
|
beb1b2655e | ||
|
|
6a81e48c3a | ||
|
|
f9841dd3df | ||
|
|
b563c773c1 | ||
|
|
dcda649d9d | ||
|
|
d6cfd23ae5 | ||
|
|
b69f62c9a4 | ||
|
|
582ee7d623 | ||
|
|
7c4202186d | ||
|
|
7c56e4c09d | ||
|
|
b10b466c61 | ||
|
|
80d1986c61 | ||
|
|
7f9e413604 | ||
|
|
4dfbc3830f | ||
|
|
46cdefa9ee | ||
|
|
f857ea6095 | ||
|
|
994920f99f | ||
|
|
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 | ||
|
|
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 | ||
|
|
dafb5a8299 | ||
|
|
4a1af03744 | ||
|
|
bb176f1efb | ||
|
|
a53bcde973 | ||
|
|
1a8dc2c637 | ||
|
|
f96211ff91 | ||
|
|
77f9c02785 | ||
|
|
7910b65fdc | ||
|
|
eb4fc4588d | ||
|
|
5430674071 | ||
|
|
17e67e3b79 | ||
|
|
e87a0d72e4 | ||
|
|
67d3c65907 | ||
|
|
564a5486c9 | ||
|
|
9ce11c4c32 | ||
|
|
5e97b551a5 | ||
|
|
77536e75af | ||
|
|
6f12f714d2 | ||
|
|
1f67077400 | ||
|
|
d1c4cf69cb | ||
|
|
803c5eaa15 | ||
|
|
970fd5d9c3 | ||
|
|
7ccd4a1e0d | ||
|
|
3a6ab55266 | ||
|
|
eb41882c56 | ||
|
|
734851a810 | ||
|
|
6318717a15 | ||
|
|
d8498feaef | ||
|
|
aaec452bc2 | ||
|
|
bce959195a | ||
|
|
4287e09a9a | ||
|
|
fdc006f833 | ||
|
|
c704b7505c | ||
|
|
ceaf238322 | ||
|
|
b7224ce5c7 | ||
|
|
95654175fe | ||
|
|
de96244706 | ||
|
|
3104ee5742 | ||
|
|
73ac445a44 | ||
|
|
1a1d1d9d30 | ||
|
|
062aff76da | ||
|
|
7d0d24b66d | ||
|
|
6655672d11 | ||
|
|
6e573ae410 | ||
|
|
1c48c02473 | ||
|
|
78be3320ce | ||
|
|
c8018800cc | ||
|
|
bb795aca98 | ||
|
|
2cb1279f4c | ||
|
|
b876c6f618 | ||
|
|
3a7dfc055e | ||
|
|
928e521ac7 | ||
|
|
e5e9598e4b | ||
|
|
e6e1809592 | ||
|
|
812f97ddb7 | ||
|
|
c6b814b31b | ||
|
|
98566fa389 | ||
|
|
4318c89bdb | ||
|
|
b41a97ce6a | ||
|
|
862c384dc0 | ||
|
|
26cc7e94c2 | ||
|
|
c6fe6ddcba | ||
|
|
154984e74d | ||
|
|
72fcaf8321 | ||
|
|
0c14ca5888 | ||
|
|
a85ede5058 | ||
|
|
db49a16184 | ||
|
|
0704db2343 | ||
|
|
425dca4665 | ||
|
|
8d65ccabd2 | ||
|
|
cbd417ca13 | ||
|
|
1e4d1e45f9 | ||
|
|
1578211157 | ||
|
|
15932dc5df | ||
|
|
7db2205956 | ||
|
|
d87f0d236a | ||
|
|
16daca6008 | ||
|
|
a0d919c8db | ||
|
|
ffee2bf1c4 | ||
|
|
de77f6ac59 | ||
|
|
c8c2ebcd65 | ||
|
|
42796b984f | ||
|
|
746557bf52 | ||
|
|
a5020a9e96 | ||
|
|
2ebb0a35fd | ||
|
|
64aaa92d45 | ||
|
|
4baf391efe | ||
|
|
5afdcff6a5 | ||
|
|
339c397860 | ||
|
|
a58cbd31ef | ||
|
|
d232b01cf9 | ||
|
|
ec95270854 | ||
|
|
78cc0d960f | ||
|
|
b5ccdfdd53 | ||
|
|
d0c0d8e703 | ||
|
|
ebf64404be | ||
|
|
7f39cf0f37 | ||
|
|
e5dcc8e54f | ||
|
|
4592510a95 | ||
|
|
46d5442dc5 | ||
|
|
271cff4aba | ||
|
|
94fd5a97d6 | ||
|
|
28678e2f80 | ||
|
|
406307a432 | ||
|
|
56345b5096 | ||
|
|
3fcc42ee0e | ||
|
|
b903677f8a | ||
|
|
ef4b632a07 | ||
|
|
e9d485ca85 | ||
|
|
a149fa5929 | ||
|
|
bb3dde585b | ||
|
|
cd6bbeaa86 | ||
|
|
e3d24dae4b | ||
|
|
687a814a62 | ||
|
|
8f2e99dc51 | ||
|
|
568be030c3 | ||
|
|
97c3ab8b5a | ||
|
|
5db646d139 | ||
|
|
340f8f1af3 | ||
|
|
4484a4452c | ||
|
|
65b654a0a2 | ||
|
|
a8e0c13b0e | ||
|
|
41d67eb642 | ||
|
|
c76c8335d1 | ||
|
|
42528b7be5 | ||
|
|
b371f8c11c | ||
|
|
3d651b40f0 | ||
|
|
895a13fc0d | ||
|
|
c94acff82e | ||
|
|
2f9ac711d1 | ||
|
|
93b9953f6d | ||
|
|
dcaf6f8927 | ||
|
|
d96eb404e1 | ||
|
|
07322819f0 | ||
|
|
9be686686d | ||
|
|
3df539c9e1 | ||
|
|
903f06bab6 | ||
|
|
13f2810f90 | ||
|
|
ee8cae16d2 | ||
|
|
feaf6f7fd4 | ||
|
|
04775a2e44 | ||
|
|
bec9fec755 | ||
|
|
e840059a38 | ||
|
|
1bd0339dec | ||
|
|
d0eaf2f71e | ||
|
|
c7edf40afe | ||
|
|
b44962eb2f | ||
|
|
254c9efbf5 | ||
|
|
73e9aed98c | ||
|
|
6e60d9713a | ||
|
|
ef0593b1e2 | ||
|
|
7831fa8b33 | ||
|
|
c64851492c | ||
|
|
4a2e93e265 | ||
|
|
d4bb1a6e93 | ||
|
|
55de0aba69 | ||
|
|
ecfa7b5a99 | ||
|
|
bee3dc4595 | ||
|
|
787ca47825 | ||
|
|
b06c134b61 | ||
|
|
3436d03468 | ||
|
|
58df6f3b85 | ||
|
|
6fab6ce1f2 | ||
|
|
9fd95f1a11 | ||
|
|
69fe0901e2 | ||
|
|
ffaf008dce | ||
|
|
eb196a41b5 | ||
|
|
35baaaf594 | ||
|
|
0fc305aaea | ||
|
|
bf67546a61 | ||
|
|
8c417ef35c | ||
|
|
01691f3050 | ||
|
|
128abf3090 | ||
|
|
0c0e995256 | ||
|
|
4090498f92 | ||
|
|
24677d6931 | ||
|
|
80d54afdd0 | ||
|
|
908fff8041 | ||
|
|
aa1eb90f66 | ||
|
|
1d21787e7e | ||
|
|
42409a2bc7 | ||
|
|
b31302ba3a | ||
|
|
3840b0041b | ||
|
|
cc68f8311d | ||
|
|
3400c8e94a | ||
|
|
61c47c517b | ||
|
|
76674917ca | ||
|
|
b835eb8de7 | ||
|
|
2a74dfcf46 | ||
|
|
b0f447adde | ||
|
|
3f0d476e94 | ||
|
|
9423d56d36 | ||
|
|
0f03fe99d6 | ||
|
|
556d5bb003 | ||
|
|
8e3ea6943f | ||
|
|
599cfc1b1c | ||
|
|
84eccb5019 | ||
|
|
2dd07bb0e3 | ||
|
|
f404d23280 | ||
|
|
8b7333b470 | ||
|
|
b8628ccff1 | ||
|
|
7d26567d40 | ||
|
|
8f6dbf2192 | ||
|
|
db350ddfd3 | ||
|
|
c0144d826c | ||
|
|
de3e819b80 | ||
|
|
bbf96567b1 | ||
|
|
c9a3254bd6 | ||
|
|
bad6d030f1 | ||
|
|
e1ad67a059 | ||
|
|
4c30057edf | ||
|
|
53a4826274 | ||
|
|
8d3ac92989 | ||
|
|
d6eb334d12 | ||
|
|
c62f19623c | ||
|
|
77139afe7f | ||
|
|
54476ad85e | ||
|
|
6dd876232d | ||
|
|
ae2314d76c | ||
|
|
6667509745 | ||
|
|
5c871a5aae | ||
|
|
393fc99ed2 | ||
|
|
7c5dff1c8b | ||
|
|
2482ef92af | ||
|
|
db59303a84 | ||
|
|
fe700953ff | ||
|
|
047946e41c | ||
|
|
ce3f13431d | ||
|
|
df54766ab2 | ||
|
|
94233fbcd9 | ||
|
|
d044496eaa | ||
|
|
ca825d720e | ||
|
|
f833efae37 | ||
|
|
be4b787e66 | ||
|
|
950b1c179a | ||
|
|
2bd1a01afb | ||
|
|
2fe43eda2b | ||
|
|
45fc0c61af | ||
|
|
7e7993f5ba | ||
|
|
9d34dbff0f | ||
|
|
ba1b03f01b | ||
|
|
791f7ca3c8 | ||
|
|
c14d50e4ff | ||
|
|
3e9de449c8 | ||
|
|
0132c7ef5e | ||
|
|
aa2222c086 | ||
|
|
fc2765d376 | ||
|
|
9d85194082 | ||
|
|
abfcc60898 | ||
|
|
c688a83fa2 | ||
|
|
77376056b9 | ||
|
|
8efd943c2b | ||
|
|
b230655439 | ||
|
|
8ba792c4b0 | ||
|
|
fde8ca69e4 | ||
|
|
9ee1439299 | ||
|
|
195668760e | ||
|
|
bd64f1db9a | ||
|
|
38a5a857aa | ||
|
|
5e8cdfe3a7 | ||
|
|
504406eb22 | ||
|
|
f892917e1c | ||
|
|
738b7f4ca5 | ||
|
|
bce53d03a5 | ||
|
|
74f721ab9f | ||
|
|
f011025f16 | ||
|
|
9e44cd83fa | ||
|
|
e0e45b64ac | ||
|
|
f10b152252 | ||
|
|
23cf5c2fdd | ||
|
|
e211abd5ae | ||
|
|
7199be4d62 | ||
|
|
e1a40ded1d | ||
|
|
8999c3097d | ||
|
|
44308fa1fd | ||
|
|
f5dd2f4579 | ||
|
|
104999b7dc | ||
|
|
e5341623d3 | ||
|
|
0e9238e5ec | ||
|
|
43c292e2df | ||
|
|
fbfd1b20d8 | ||
|
|
5330df87e1 | ||
|
|
ad6b3c6fe0 | ||
|
|
b983df7eb5 | ||
|
|
add76a87d9 | ||
|
|
f9f8350d28 | ||
|
|
b71c2f365c | ||
|
|
a5443189d3 | ||
|
|
7686b61728 | ||
|
|
844c13cd35 | ||
|
|
d1c8b92fdf | ||
|
|
93d33bca80 | ||
|
|
6550e53414 | ||
|
|
96dddb0998 | ||
|
|
baa64d8109 | ||
|
|
2df145aa8f | ||
|
|
06b9c5f2de | ||
|
|
e03b689f27 | ||
|
|
e4cdcff3ee | ||
|
|
f73150d074 | ||
|
|
451dc12689 | ||
|
|
a3fb7762d8 | ||
|
|
3dfafc3202 | ||
|
|
4fcea991d3 | ||
|
|
0607ed46d1 | ||
|
|
30d6244e82 | ||
|
|
fab8c74365 | ||
|
|
1952922e4e | ||
|
|
1eb351369e | ||
|
|
701d782485 | ||
|
|
dc8457c4dc | ||
|
|
f104cd4d11 | ||
|
|
9b56a6ae65 | ||
|
|
2485ad5a2f | ||
|
|
a56716ab6a | ||
|
|
e0aaaa989e | ||
|
|
9ec0f9e736 | ||
|
|
47968eeeec | ||
|
|
9c157b70e1 | ||
|
|
6d7e4ca227 | ||
|
|
fccd08d2ec | ||
|
|
19bae202d0 | ||
|
|
4ba0ae24e4 | ||
|
|
fcf3718d75 | ||
|
|
df1dd94f1c | ||
|
|
65ba401850 | ||
|
|
a98f5e1047 | ||
|
|
e5cf97d1ea | ||
|
|
1844fc14b2 |
@@ -38,7 +38,24 @@
|
||||
"selector": "ExportDefaultDeclaration",
|
||||
"message": "Default exports are not allowed"
|
||||
}
|
||||
]
|
||||
],
|
||||
"no-throw-literal": "error",
|
||||
"key-spacing": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"array-bracket-spacing": "error",
|
||||
"space-in-parens": "error",
|
||||
"computed-property-spacing": "error",
|
||||
"prefer-const": ["error", {
|
||||
"destructuring": "all",
|
||||
"ignoreReadBeforeAssign": false
|
||||
}],
|
||||
"space-before-function-paren": "off",
|
||||
"func-call-spacing": "off",
|
||||
"no-multi-spaces": "error",
|
||||
"block-spacing": "error",
|
||||
"keyword-spacing": "off",
|
||||
"space-before-blocks": "error",
|
||||
"semi-spacing": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
@@ -89,7 +106,14 @@
|
||||
"error",
|
||||
"1tbs", { "allowSingleLine": true }
|
||||
],
|
||||
"@typescript-eslint/comma-spacing": "error"
|
||||
"@typescript-eslint/comma-spacing": "error",
|
||||
"@typescript-eslint/space-before-function-paren": ["error", {
|
||||
"anonymous": "always",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}],
|
||||
"@typescript-eslint/func-call-spacing": ["error"],
|
||||
"@typescript-eslint/keyword-spacing": ["error"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
18
.github/workflows/lint.yml
vendored
18
.github/workflows/lint.yml
vendored
@@ -1,18 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
name: eslint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: install node v12
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- name: yarn install
|
||||
run: yarn install
|
||||
- name: eslint
|
||||
uses: icrawl/action-eslint@v1
|
||||
20
.github/workflows/node.yml
vendored
Normal file
20
.github/workflows/node.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
- run: npm ci
|
||||
- run: sudo apt-get install xvfb
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
- name: Test
|
||||
run: npm install --no-save "gl@^5.0.0" && xvfb-run --auto-servernum npm run jest
|
||||
- name: Build
|
||||
run: npm run build
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,3 +9,5 @@ tsconfig.commonjs.tsbuildinfo
|
||||
|
||||
*.sublime-workspace
|
||||
.idea
|
||||
|
||||
.DS_Store
|
||||
@@ -1 +1,5 @@
|
||||
tsconfig.commonjs.tsbuildinfo
|
||||
tests
|
||||
perf-tests
|
||||
_spec
|
||||
*.tsbuildinfo
|
||||
*.js.map
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -7,6 +7,8 @@
|
||||
"*.gql.ts": "graphql"
|
||||
},
|
||||
"eslint.options": {
|
||||
"ignorePattern": ["webpack.config.js", "scripts/*"],
|
||||
"overrideConfig": {
|
||||
"ignorePatterns": ["webpack.config.js", "scripts/*"],
|
||||
},
|
||||
}
|
||||
}
|
||||
501
CHANGELOG.md
501
CHANGELOG.md
@@ -6,6 +6,482 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [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
|
||||
- Add substance theme with per-group material rendering properties
|
||||
- ``StructureComponentManager.Options`` state saving support
|
||||
- ``ParamDefinition.Group.presets`` support
|
||||
|
||||
## [v2.4.1] - 2021-11-28
|
||||
|
||||
- Fix: allow atoms in aromatic rings to do hydrogen bonds
|
||||
|
||||
## [v2.4.0] - 2021-11-25
|
||||
|
||||
- Fix secondary-structure property handling
|
||||
- StructureElement.Property was incorrectly resolving type & key
|
||||
- StructureSelectionQuery helpers 'helix' & 'beta' were not ensuring property availability
|
||||
- Re-enable VAO with better workaround (bind null elements buffer before deleting)
|
||||
- Add ``Representation.geometryVersion`` (increments whenever the geometry of any of its visuals changes)
|
||||
- Add support for grid-based smoothing of Overpaint and Transparency visual state for surfaces
|
||||
|
||||
## [v2.3.9] - 2021-11-20
|
||||
|
||||
- Workaround: switch off VAO support for now
|
||||
|
||||
## [v2.3.8] - 2021-11-20
|
||||
|
||||
- Fix double canvas context creation (in plugin context)
|
||||
- Fix unused vertex attribute handling (track which are used, disable the rest)
|
||||
- Workaround for VAO issue in Chrome 96 (can cause WebGL to crash on geometry updates)
|
||||
|
||||
## [v2.3.7] - 2021-11-15
|
||||
|
||||
- Added ``ViewerOptions.collapseRightPanel``
|
||||
- Added ``Viewer.loadTrajectory`` to support loading "composed" trajectories (e.g. from gro + xtc)
|
||||
- Fix: handle parent in Structure.remapModel
|
||||
- Add ``rounded`` and ``square`` helix profile options to Cartoon representation (in addition to the default ``elliptical``)
|
||||
|
||||
## [v2.3.6] - 2021-11-8
|
||||
|
||||
- Add additional measurement controls: orientation (box, axes, ellipsoid) & plane (best fit)
|
||||
- Improve aromatic bond visuals (add ``aromaticScale``, ``aromaticSpacing``, ``aromaticDashCount`` params)
|
||||
- [Breaking] Change ``adjustCylinderLength`` default to ``false`` (set to true for focus representation)
|
||||
- Fix marker highlight color overriding select color
|
||||
- CellPack extension update
|
||||
- add binary model support
|
||||
- add compartment (including membrane) geometry support
|
||||
- add latest mycoplasma model example
|
||||
- Prefer WebGL1 in Safari 15.1.
|
||||
|
||||
## [v2.3.5] - 2021-10-19
|
||||
|
||||
- Fix sequence viewer for PDB files with COMPND record and multichain entities.
|
||||
- Fix index pair bonds order assignment
|
||||
|
||||
## [v2.3.4] - 2021-10-12
|
||||
|
||||
- Fix pickScale not taken into account in line/point shader
|
||||
- Add pixel-scale, pick-scale & pick-padding GET params to Viewer app
|
||||
- Fix selecting bonds not adding their atoms in selection manager
|
||||
- Add ``preferAtoms`` option to SelectLoci/HighlightLoci behaviors
|
||||
- Make the implicit atoms of bond visuals pickable
|
||||
- Add ``preferAtomPixelPadding`` to Canvas3dInteractionHelper
|
||||
- Add points & crosses visuals to Line representation
|
||||
- Add ``pickPadding`` config option (look around in case target pixel is empty)
|
||||
- Add ``multipleBonds`` param to bond visuals with options: off, symmetric, offset
|
||||
- Fix ``argparse`` config in servers.
|
||||
|
||||
## [v2.3.3] - 2021-10-01
|
||||
|
||||
- Fix direct volume shader
|
||||
|
||||
## [v2.3.2] - 2021-10-01
|
||||
|
||||
- Prefer WebGL1 on iOS devices until WebGL2 support has stabilized.
|
||||
|
||||
## [v2.3.1] - 2021-09-28
|
||||
|
||||
- Add Charmm saccharide names
|
||||
- Treat missing occupancy column as occupancy of 1
|
||||
- Fix line shader not accounting for aspect ratio
|
||||
- [Breaking] Fix point repr & shader
|
||||
- Was unusable with ``wboit``
|
||||
- Replaced ``pointFilledCircle`` & ``pointEdgeBleach`` params by ``pointStyle`` (square, circle, fuzzy)
|
||||
- Set ``pointSizeAttenuation`` to false by default
|
||||
- Set ``sizeTheme`` to ``uniform`` by default
|
||||
- Add ``markerPriority`` option to Renderer (useful in combination with edges of marking pass)
|
||||
- Add support support for ``chem_comp_bond`` and ``struct_conn`` categories (fixes ModelServer behavior where these categories should have been present)
|
||||
- Model and VolumeServer: fix argparse config
|
||||
|
||||
## [v2.3.0] - 2021-09-06
|
||||
|
||||
- Take include/exclude flags into account when displaying aromatic bonds
|
||||
- Improve marking performance
|
||||
- Avoid unnecessary draw calls/ui updates when marking
|
||||
- Check if loci is superset of visual
|
||||
- Check if loci overlaps with unit visual
|
||||
- Ensure ``Interval`` is used for ranges instead of ``SortedArray``
|
||||
- Add uniform marker type
|
||||
- Special case for reversing previous mark
|
||||
- Add optional marking pass
|
||||
- Outlines visible and hidden parts of highlighted/selected groups
|
||||
- Add highlightStrength/selectStrength renderer params
|
||||
|
||||
## [v2.2.3] - 2021-08-25
|
||||
|
||||
- Add ``invertCantorPairing`` helper function
|
||||
- Add ``Mesh`` processing helper ``.smoothEdges``
|
||||
- Smooth border of molecular-surface with ``includeParent`` enabled
|
||||
- Hide ``includeParent`` option from gaussian-surface visuals (not particularly useful)
|
||||
- Improved ``StructureElement.Loci.size`` performance (for marking large cellpack models)
|
||||
- Fix new ``TransformData`` issues (camera/bounding helper not showing up)
|
||||
- Improve marking performance (avoid superfluous calls to ``StructureElement.Loci.isWholeStructure``)
|
||||
|
||||
## [v2.2.2] - 2021-08-11
|
||||
|
||||
- Fix ``TransformData`` issues [#133](https://github.com/molstar/molstar/issues/133)
|
||||
- Fix ``mol-script`` query compiler const expression recognition.
|
||||
|
||||
## [v2.2.1] - 2021-08-02
|
||||
|
||||
- Add surrounding atoms (5 Angstrom) structure selection query
|
||||
- [Breaking] Add maxDistance prop to ``IndexPairBonds``
|
||||
- Fix coordinateSystem not handled in ``Structure.asParent``
|
||||
- Add ``dynamicBonds`` to ``Structure`` props (force re-calc on model change)
|
||||
- Expose as optional param in root structure transform helper
|
||||
- Add overpaint support to geometry exporters
|
||||
- ``InputObserver`` improvements
|
||||
- normalize wheel speed across browsers/platforms
|
||||
- support Safari gestures (used by ``TrackballControls``)
|
||||
- ``PinchInput.fractionDelta`` and use it in ``TrackballControls``
|
||||
|
||||
## [v2.2.0] - 2021-07-31
|
||||
|
||||
- Add ``tubularHelices`` parameter to Cartoon representation
|
||||
- Add ``SdfFormat`` and update SDF parser to be able to parse data headers according to spec (hopefully :)) #230
|
||||
- Fix mononucleotides detected as polymer components (#229)
|
||||
- Set default outline scale back to 1
|
||||
- Improved DCD reader cell angle handling (interpret near 0 angles as 90 deg)
|
||||
- Handle more residue/atom names commonly used in force-fields
|
||||
- Add USDZ support to ``geo-export`` extension.
|
||||
- Fix ``includeParent`` support for multi-instance bond visuals.
|
||||
- Add ``operator`` Loci granularity, selecting everything with the same operator name.
|
||||
- Prefer ``_label_seq_id`` fields in secondary structure assignment.
|
||||
- Support new EMDB API (https://www.ebi.ac.uk/emdb/api/entry/map/[EMBD-ID]) for EM volume contour levels.
|
||||
- ``Canvas3D`` tweaks:
|
||||
- Update ``forceDraw`` logic.
|
||||
- Ensure the scene is re-rendered when viewport size changes.
|
||||
- Support ``noDraw`` mode in ``PluginAnimationLoop``.
|
||||
|
||||
## [v2.1.0] - 2021-07-05
|
||||
|
||||
- Add parameter for to display aromatic bonds as dashes next to solid cylinder/line.
|
||||
- Add backbone representation
|
||||
- Fix outline in orthographic mode and set default scale to 2.
|
||||
|
||||
## [v2.0.7] - 2021-06-23
|
||||
|
||||
@@ -33,8 +509,8 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
- Add ability to select residues from a list of identifiers to the Selection UI.
|
||||
- Fix SSAO bugs when used with ``Canvas3D`` viewport.
|
||||
- Support for full pausing (no draw) rendering: ``Canvas3D.pause(true)``.
|
||||
- Add `MeshBuilder.addMesh`.
|
||||
- Add `Torus` primitive.
|
||||
- Add ``MeshBuilder.addMesh``.
|
||||
- Add ``Torus`` primitive.
|
||||
- Lazy volume loading support.
|
||||
- [Breaking] ``Viewer.loadVolumeFromUrl`` signature change.
|
||||
- ``loadVolumeFromUrl(url, format, isBinary, isovalues, entryId)`` => ``loadVolumeFromUrl({ url, format, isBinary }, isovalues, { entryId, isLazy })``
|
||||
@@ -48,29 +524,22 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
- Fixed Measurements UI labels (#166)
|
||||
|
||||
## [v2.0.3] - 2021-04-09
|
||||
### Added
|
||||
- Support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
|
||||
|
||||
### Changed
|
||||
- [Breaking] The `zip` function is now asynchronous and expects a `RuntimeContext`. Also added `Zip()` returning a `Task`.
|
||||
- Add support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
|
||||
- [Breaking] The ``zip`` function is now asynchronous and expects a ``RuntimeContext``. Also added ``Zip()`` returning a ``Task``.
|
||||
- [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
|
||||
|
||||
## [v2.0.2] - 2021-03-29
|
||||
### Added
|
||||
- `Canvas3D.getRenderObjects`.
|
||||
|
||||
- Add ``Canvas3D.getRenderObjects``.
|
||||
- [WIP] Animate state interpolating, including model trajectories
|
||||
|
||||
### Changed
|
||||
- Recognise MSE, SEP, TPO, PTR and PCA as non-standard amino-acids.
|
||||
|
||||
### Fixed
|
||||
- VolumeFromDensityServerCif transform label
|
||||
|
||||
- Fix VolumeFromDensityServerCif transform label
|
||||
|
||||
## [v2.0.1] - 2021-03-23
|
||||
### Fixed
|
||||
|
||||
- Exclude tsconfig.commonjs.tsbuildinfo from npm bundle
|
||||
|
||||
|
||||
## [v2.0.0] - 2021-03-23
|
||||
|
||||
Too many changes to list as this is the start of the changelog... Notably, default exports are now forbidden.
|
||||
|
||||
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"
|
||||
32
README.md
32
README.md
@@ -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/`):
|
||||
@@ -68,6 +75,17 @@ If working on just the viewer, ``npm run watch-viewer`` will provide shorter com
|
||||
|
||||
Debug/production mode in browsers can be turned on/off during runtime by calling ``setMolStarDebugMode(true/false, true/false)`` from the dev console.
|
||||
|
||||
### Cleaning and forcing a full rebuild
|
||||
npm run clean
|
||||
|
||||
Wipes the `build` and `lib` directories and `.tsbuildinfo` files.
|
||||
|
||||
npm run rebuild
|
||||
|
||||
Runs the cleanup script prior to building the project, forcing a full rebuild of the project.
|
||||
|
||||
Use these commands to resolve occassional build failures which may arise after some dependency updates. Once done, `npm run build` should work again. Note that full rebuilds take more time to complete.
|
||||
|
||||
### Build for production:
|
||||
NODE_ENV=production npm run build
|
||||
|
||||
@@ -102,6 +120,9 @@ and navigate to `build/viewer`
|
||||
|
||||
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-ions.js src/mol-model/structure/model/types/ions.ts
|
||||
|
||||
**Saccharide names**
|
||||
|
||||
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-saccharides.js src/mol-model/structure/model/types/saccharides.ts
|
||||
|
||||
**GraphQL schemas**
|
||||
|
||||
@@ -120,16 +141,21 @@ 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/servers/model/preprocess -i file.cif -ob file.bcif
|
||||
node lib/commonjs/servers/model/preprocess -i file.cif -ob file.bcif
|
||||
|
||||
To see all available commands, use ``node lib/servers/model/preprocess -h``.
|
||||
To see all available commands, use ``node lib/commonjs/servers/model/preprocess -h``.
|
||||
|
||||
Or
|
||||
|
||||
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
|
||||
|
||||
@@ -2,11 +2,11 @@ audit.block_doi
|
||||
|
||||
database_code.depnum_ccdc_archive
|
||||
database_code.depnum_ccdc_fiz
|
||||
database_code.ICSD
|
||||
database_code.MDF
|
||||
database_code.NBS
|
||||
database_code.CSD
|
||||
database_code.COD
|
||||
database_code.icsd
|
||||
database_code.mdf
|
||||
database_code.nbs
|
||||
database_code.csd
|
||||
database_code.cod
|
||||
|
||||
chemical.name_systematic
|
||||
chemical.name_common
|
||||
@@ -24,8 +24,8 @@ atom_type_scat.dispersion_imag
|
||||
atom_type_scat.source
|
||||
|
||||
space_group.crystal_system
|
||||
space_group.name_H-M_full
|
||||
space_group.IT_number
|
||||
space_group.name_h-m_full
|
||||
space_group.it_number
|
||||
space_group_symop.operation_xyz
|
||||
|
||||
cell.length_a
|
||||
@@ -35,14 +35,14 @@ cell.angle_alpha
|
||||
cell.angle_beta
|
||||
cell.angle_gamma
|
||||
cell.volume
|
||||
cell.formula_units_Z
|
||||
cell.formula_units_z
|
||||
|
||||
atom_site.label
|
||||
atom_site.type_symbol
|
||||
atom_site.fract_x
|
||||
atom_site.fract_y
|
||||
atom_site.fract_z
|
||||
atom_site.U_iso_or_equiv
|
||||
atom_site.u_iso_or_equiv
|
||||
atom_site.adp_type
|
||||
atom_site.occupancy
|
||||
atom_site.calc_flag
|
||||
@@ -52,20 +52,13 @@ atom_site.disorder_group
|
||||
atom_site.site_symmetry_multiplicity
|
||||
|
||||
atom_site_aniso.label
|
||||
atom_site_aniso.U
|
||||
atom_site_aniso.U_11
|
||||
atom_site_aniso.U_22
|
||||
atom_site_aniso.U_33
|
||||
atom_site_aniso.U_23
|
||||
atom_site_aniso.U_13
|
||||
atom_site_aniso.U_12
|
||||
atom_site_aniso.U_su
|
||||
atom_site_aniso.U_11_su
|
||||
atom_site_aniso.U_22_su
|
||||
atom_site_aniso.U_33_su
|
||||
atom_site_aniso.U_23_su
|
||||
atom_site_aniso.U_13_su
|
||||
atom_site_aniso.U_12_su
|
||||
atom_site_aniso.u
|
||||
atom_site_aniso.u_11
|
||||
atom_site_aniso.u_22
|
||||
atom_site_aniso.u_33
|
||||
atom_site_aniso.u_23
|
||||
atom_site_aniso.u_13
|
||||
atom_site_aniso.u_12
|
||||
|
||||
geom_bond.atom_site_label_1
|
||||
geom_bond.atom_site_label_2
|
||||
|
||||
|
@@ -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
|
||||
|
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)
|
||||
@@ -29,6 +30,19 @@
|
||||
* 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)
|
||||
|
||||
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
48128
package-lock.json
generated
48128
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
117
package.json
117
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.0.7",
|
||||
"version": "3.12.1",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -13,9 +13,11 @@
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"lint-fix": "eslint . --fix",
|
||||
"test": "npm run lint && jest",
|
||||
"test": "npm install --no-save \"gl@^5.0.0\" && npm run lint && jest",
|
||||
"jest": "jest",
|
||||
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
|
||||
"clean": "node ./scripts/clean.js",
|
||||
"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/",
|
||||
@@ -36,7 +38,7 @@
|
||||
"volume-server-test": "node lib/commonjs/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
|
||||
"plugin-state": "node lib/commonjs/servers/plugin-state/index.js --working-folder ./build/state --port 1339",
|
||||
"preversion": "npm run test",
|
||||
"version": "npm run build",
|
||||
"version": "npm run rebuild && cpx .npmignore lib/",
|
||||
"postversion": "git push && git push --tags"
|
||||
},
|
||||
"files": [
|
||||
@@ -84,72 +86,77 @@
|
||||
"Áron Samuel Kovács <aron.kovacs@mail.muni.cz>",
|
||||
"Ludovic Autin <autin@scripps.edu>",
|
||||
"Michal Malý <michal.maly@ibt.cas.cz>",
|
||||
"Jiří Černý <jiri.cerny@ibt.cas.cz>"
|
||||
"Jiří Černý <jiri.cerny@ibt.cas.cz>",
|
||||
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^2.0.2",
|
||||
"@graphql-codegen/cli": "^1.19.4",
|
||||
"@graphql-codegen/time": "^2.0.2",
|
||||
"@graphql-codegen/typescript": "^1.19.0",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.18.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^2.0.3",
|
||||
"@graphql-codegen/typescript-operations": "^1.17.12",
|
||||
"@types/cors": "^2.8.8",
|
||||
"@typescript-eslint/eslint-plugin": "^4.9.1",
|
||||
"@typescript-eslint/parser": "^4.9.1",
|
||||
"@graphql-codegen/add": "^3.2.0",
|
||||
"@graphql-codegen/cli": "^2.8.1",
|
||||
"@graphql-codegen/time": "^3.2.0",
|
||||
"@graphql-codegen/typescript": "^2.7.1",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.0",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.1",
|
||||
"@graphql-codegen/typescript-operations": "^2.5.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/gl": "^4.1.1",
|
||||
"@types/jest": "^28.1.6",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.6",
|
||||
"@typescript-eslint/parser": "^5.30.6",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^5.3.0",
|
||||
"cpx2": "^3.0.0",
|
||||
"concurrently": "^7.2.2",
|
||||
"cpx2": "^4.2.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^5.0.1",
|
||||
"eslint": "^7.15.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"eslint": "^8.19.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"graphql": "^15.4.0",
|
||||
"http-server": "^0.12.3",
|
||||
"jest": "^26.6.3",
|
||||
"mini-css-extract-plugin": "^1.3.2",
|
||||
"node-sass": "^6.0.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"graphql": "^16.5.0",
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "^28.1.3",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass-loader": "^11.1.1",
|
||||
"simple-git": "^2.25.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sass": "^1.53.0",
|
||||
"sass-loader": "^13.0.2",
|
||||
"simple-git": "^3.10.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"ts-jest": "^26.4.4",
|
||||
"typescript": "^4.2.4",
|
||||
"webpack": "^5.37.1",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-version-file-plugin": "^0.4.0"
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^28.0.6",
|
||||
"typescript": "^4.7.4",
|
||||
"webpack": "^5.73.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
"@types/benchmark": "^2.1.0",
|
||||
"@types/compression": "1.7.0",
|
||||
"@types/express": "^4.17.9",
|
||||
"@types/jest": "^26.0.18",
|
||||
"@types/node": "^14.14.11",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/swagger-ui-dist": "3.30.0",
|
||||
"argparse": "^1.0.10",
|
||||
"body-parser": "^1.19.0",
|
||||
"@types/argparse": "^2.0.10",
|
||||
"@types/benchmark": "^2.1.1",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^16.11.45",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"argparse": "^2.0.1",
|
||||
"body-parser": "^1.20.0",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"express": "^4.18.1",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^8.0.1",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.1",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"rxjs": "^6.6.6",
|
||||
"swagger-ui-dist": "^3.37.2",
|
||||
"tslib": "^2.1.0",
|
||||
"util.promisify": "^1.0.1",
|
||||
"xhr2": "^0.2.0"
|
||||
"immer": "^9.0.15",
|
||||
"immutable": "^4.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"rxjs": "^7.5.6",
|
||||
"swagger-ui-dist": "^4.12.0",
|
||||
"tslib": "^2.4.0",
|
||||
"util.promisify": "^1.1.1",
|
||||
"xhr2": "^0.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.1.0 || ^17.0.2 || ^16.14.0",
|
||||
"react-dom": "^18.1.0 || ^17.0.2 || ^16.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
41
scripts/clean.js
Normal file
41
scripts/clean.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Michal Malý <malym@ibt.cas.cz>
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function removeDir(dirPath) {
|
||||
for (const ent of fs.readdirSync(dirPath)) {
|
||||
const entryPath = path.join(dirPath, ent);
|
||||
remove(entryPath);
|
||||
}
|
||||
|
||||
fs.rmdirSync(dirPath);
|
||||
}
|
||||
|
||||
function remove(entryPath) {
|
||||
const st = fs.statSync(entryPath);
|
||||
if (st.isDirectory())
|
||||
removeDir(entryPath);
|
||||
else
|
||||
fs.unlinkSync(entryPath);
|
||||
}
|
||||
|
||||
const toClean = [
|
||||
path.resolve(__dirname, '../build'),
|
||||
path.resolve(__dirname, '../lib'),
|
||||
path.resolve(__dirname, '../tsconfig.tsbuildinfo'),
|
||||
];
|
||||
|
||||
toClean.forEach(ph => {
|
||||
if (fs.existsSync(ph)) {
|
||||
try {
|
||||
remove(ph);
|
||||
} catch (err) {
|
||||
console.warn(`Cleanup failed: ${err}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -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 -->`;
|
||||
|
||||
function log(command, stdout, stderr) {
|
||||
if (command) {
|
||||
console.log('\n###', command);
|
||||
@@ -22,11 +25,36 @@ 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 copyDemos() {
|
||||
console.log('\n###', 'copy demos files');
|
||||
const lightingBuildPath = path.resolve(buildDir, '../build/examples/lighting/');
|
||||
const lightingDeployPath = path.resolve(localPath, 'demos/lighting/');
|
||||
fse.copySync(lightingBuildPath, lightingDeployPath, { overwrite: true });
|
||||
addAnalytics(path.resolve(lightingDeployPath, 'index.html'));
|
||||
|
||||
const orbitalsBuildPath = path.resolve(buildDir, '../build/examples/alpha-orbitals/');
|
||||
const orbitalsDeployPath = path.resolve(localPath, 'demos/alpha-orbitals/');
|
||||
fse.copySync(orbitalsBuildPath, orbitalsDeployPath, { overwrite: true });
|
||||
addAnalytics(path.resolve(orbitalsDeployPath, 'index.html'));
|
||||
}
|
||||
|
||||
function copyFiles() {
|
||||
copyViewer();
|
||||
copyDemos();
|
||||
}
|
||||
|
||||
if (!fs.existsSync(localPath)) {
|
||||
@@ -42,9 +70,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 +80,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,7 @@
|
||||
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/react18';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
@@ -54,9 +54,10 @@ const DefaultViewerOptions = {
|
||||
};
|
||||
|
||||
class Viewer {
|
||||
plugin: PluginUIContext
|
||||
constructor(public plugin: PluginUIContext) {
|
||||
}
|
||||
|
||||
constructor(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
|
||||
static async create(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
|
||||
const o = { ...DefaultViewerOptions, ...{
|
||||
layoutIsExpanded: false,
|
||||
layoutShowControls: false,
|
||||
@@ -125,29 +126,31 @@ class Viewer {
|
||||
? document.getElementById(elementOrId)
|
||||
: elementOrId;
|
||||
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
|
||||
this.plugin = createPlugin(element, spec);
|
||||
const plugin = await createPluginUI(element, spec);
|
||||
|
||||
(this.plugin.customState as any) = {
|
||||
(plugin.customState as any) = {
|
||||
colorPalette: {
|
||||
name: 'colors',
|
||||
params: { list: { colors } }
|
||||
}
|
||||
};
|
||||
|
||||
this.plugin.behaviors.canvas3d.initialized.subscribe(v => {
|
||||
plugin.behaviors.canvas3d.initialized.subscribe(v => {
|
||||
if (v) {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
|
||||
PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
|
||||
renderer: {
|
||||
...this.plugin.canvas3d!.props.renderer,
|
||||
...plugin.canvas3d!.props.renderer,
|
||||
backgroundColor: ColorNames.white,
|
||||
},
|
||||
camera: {
|
||||
...this.plugin.canvas3d!.props.camera,
|
||||
...plugin.canvas3d!.props.camera,
|
||||
helper: { axes: { name: 'off', params: {} } }
|
||||
}
|
||||
} });
|
||||
}
|
||||
});
|
||||
|
||||
return new Viewer(plugin);
|
||||
}
|
||||
|
||||
async loadStructuresFromUrlsAndMerge(sources: { url: string, format: BuiltInTrajectoryFormat, isBinary?: boolean }[]) {
|
||||
|
||||
@@ -21,12 +21,12 @@ import { PluginContext } from '../../mol-plugin/context';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { Material } from '../../mol-util/material';
|
||||
|
||||
function shinyStyle(plugin: PluginContext) {
|
||||
return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
|
||||
renderer: {
|
||||
...plugin.canvas3d!.props.renderer,
|
||||
style: { name: 'plastic', params: {} },
|
||||
},
|
||||
postprocessing: {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
@@ -40,19 +40,20 @@ function occlusionStyle(plugin: PluginContext) {
|
||||
return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
|
||||
renderer: {
|
||||
...plugin.canvas3d!.props.renderer,
|
||||
style: { name: 'flat', params: {} }
|
||||
},
|
||||
postprocessing: {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
occlusion: { name: 'on', params: {
|
||||
samples: 64,
|
||||
radius: 8,
|
||||
bias: 1.0,
|
||||
blurKernelSize: 13
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
radius: 5,
|
||||
samples: 32,
|
||||
resolutionScale: 1
|
||||
} },
|
||||
outline: { name: 'on', params: {
|
||||
scale: 1.0,
|
||||
threshold: 0.33
|
||||
threshold: 0.33,
|
||||
color: Color(0x0000),
|
||||
} }
|
||||
}
|
||||
} });
|
||||
@@ -77,7 +78,7 @@ const PresetParams = {
|
||||
...StructureRepresentationPresetProvider.CommonParams,
|
||||
};
|
||||
|
||||
|
||||
const CustomMaterial = Material({ roughness: 0.2, metalness: 0 });
|
||||
|
||||
export const StructurePreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure',
|
||||
@@ -94,8 +95,8 @@ export const StructurePreset = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, material: CustomMaterial }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -121,8 +122,8 @@ export const IllustrativePreset = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'spacefill', typeParams: { ...typeParams }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'spacefill', typeParams: { ...typeParams }, color: 'illustrative', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'spacefill', typeParams: { ...typeParams, ignoreLight: true }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'spacefill', typeParams: { ...typeParams, ignoreLight: true }, color: 'illustrative', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -149,8 +150,8 @@ const SurfacePreset = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'molecular-surface', typeParams: { ...typeParams, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'molecular-surface', typeParams: { ...typeParams, material: CustomMaterial, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }),
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -177,8 +178,8 @@ const PocketPreset = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
surroundings: builder.buildRepresentation(update, components.surroundings, { type: 'molecular-surface', typeParams: { ...typeParams, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
surroundings: builder.buildRepresentation(update, components.surroundings, { type: 'molecular-surface', typeParams: { ...typeParams, material: CustomMaterial, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }),
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -206,10 +207,10 @@ const InteractionsPreset = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
|
||||
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
|
||||
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
|
||||
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
|
||||
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, material: CustomMaterial, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -230,7 +231,7 @@ export class ViewportComponent extends PluginUIComponent {
|
||||
|
||||
set = async (preset: StructureRepresentationPresetProvider) => {
|
||||
await this._set(this.plugin.managers.structure.hierarchy.selection.structures, preset);
|
||||
}
|
||||
};
|
||||
|
||||
structurePreset = () => this.set(StructurePreset);
|
||||
illustrativePreset = () => this.set(IllustrativePreset);
|
||||
@@ -238,7 +239,7 @@ export class ViewportComponent extends PluginUIComponent {
|
||||
pocketPreset = () => this.set(PocketPreset);
|
||||
interactionsPreset = () => this.set(InteractionsPreset);
|
||||
|
||||
get showButtons () {
|
||||
get showButtons() {
|
||||
return this.plugin.config.get(ShowButtons);
|
||||
}
|
||||
|
||||
|
||||
497
src/apps/viewer/app.ts
Normal file
497
src/apps/viewer/app.ts
Normal file
@@ -0,0 +1,497 @@
|
||||
/**
|
||||
* 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 { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior';
|
||||
import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop';
|
||||
import { ModelExport } from '../../extensions/model-export';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { ZenodoImport } from '../../extensions/zenodo';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
|
||||
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
|
||||
import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset';
|
||||
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
import { BuiltInTopologyFormat } from '../../mol-plugin-state/formats/topology';
|
||||
import { BuiltInCoordinatesFormat } from '../../mol-plugin-state/formats/coordinates';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
|
||||
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model';
|
||||
import { createPluginUI } from '../../mol-plugin-ui/react18';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { StateObjectRef, StateObjectSelector } from '../../mol-state';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants';
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setDebugMode, setProductionMode, setTimingMode } 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),
|
||||
'model-export': PluginSpec.Behavior(ModelExport),
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export),
|
||||
'geo-export': PluginSpec.Behavior(GeometryExport),
|
||||
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
|
||||
'zenodo-import': PluginSpec.Behavior(ZenodoImport),
|
||||
};
|
||||
|
||||
const DefaultViewerOptions = {
|
||||
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,
|
||||
preferWebgl1: PluginConfig.General.PreferWebGl1.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,
|
||||
};
|
||||
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 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.General.PreferWebGl1, o.preferWebgl1],
|
||||
[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],
|
||||
]
|
||||
};
|
||||
|
||||
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(element, spec, {
|
||||
onBeforeUIRender: plugin => {
|
||||
// the preset needs to be added before the UI renders otherwise
|
||||
// "Download Structure" wont be able to pick it up
|
||||
plugin.builders.structure.representation.registerPreset(ViewerAutoPreset);
|
||||
}
|
||||
});
|
||||
return new Viewer(plugin);
|
||||
}
|
||||
|
||||
setRemoteSnapshot(id: string) {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
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: 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 {
|
||||
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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,11 @@
|
||||
|
||||
pdbProvider: 'rcsb',
|
||||
emdbProvider: 'rcsb',
|
||||
}).then(viewer => {
|
||||
viewer.loadPdb('7bv2');
|
||||
viewer.loadEmdb('EMD-30210', { detail: 6 });
|
||||
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
|
||||
});
|
||||
viewer.loadPdb('7bv2');
|
||||
viewer.loadEmdb('EMD-30210', { detail: 6 });
|
||||
|
||||
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -46,40 +46,65 @@
|
||||
}
|
||||
|
||||
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';
|
||||
var pdbProvider = getParam('pdb-provider', '[^&]+').trim().toLowerCase();
|
||||
var emdbProvider = getParam('emdb-provider', '[^&]+').trim().toLowerCase();
|
||||
var viewer = new molstar.Viewer('app', {
|
||||
var mapProvider = getParam('map-provider', '[^&]+').trim().toLowerCase();
|
||||
var pixelScale = getParam('pixel-scale', '[^&]+').trim();
|
||||
var pickScale = getParam('pick-scale', '[^&]+').trim();
|
||||
var pickPadding = getParam('pick-padding', '[^&]+').trim();
|
||||
var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
|
||||
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1' || void 0;
|
||||
|
||||
molstar.Viewer.create('app', {
|
||||
layoutShowControls: !hideControls,
|
||||
viewportShowExpand: false,
|
||||
collapseLeftPanel: collapseLeftPanel,
|
||||
pdbProvider: pdbProvider || 'pdbe',
|
||||
emdbProvider: emdbProvider || 'pdbe',
|
||||
volumeStreamingServer: (mapProvider || 'pdbe') === 'rcsb'
|
||||
? 'https://maps.rcsb.org'
|
||||
: 'https://www.ebi.ac.uk/pdbe/densities',
|
||||
pixelScale: parseFloat(pixelScale) || 1,
|
||||
pickScale: parseFloat(pickScale) || 0.25,
|
||||
pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
|
||||
enableWboit: disableWboit ? false : void 0, // use default value if disable-wboit is not set
|
||||
preferWebgl1: preferWebgl1,
|
||||
}).then(viewer => {
|
||||
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
|
||||
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);
|
||||
|
||||
var snapshotUrl = getParam('snapshot-url', '[^&]+').trim();
|
||||
var snapshotUrlType = getParam('snapshot-url-type', '[^&]+').toLowerCase().trim() || 'molj';
|
||||
if (snapshotUrl && snapshotUrlType) viewer.loadSnapshotFromUrl(snapshotUrl, snapshotUrlType);
|
||||
|
||||
var structureUrl = getParam('structure-url', '[^&]+').trim();
|
||||
var structureUrlFormat = getParam('structure-url-format', '[a-z]+').toLowerCase().trim();
|
||||
var structureUrlIsBinary = getParam('structure-url-is-binary', '[^&]+').trim() === '1';
|
||||
if (structureUrl) viewer.loadStructureFromUrl(structureUrl, structureUrlFormat, structureUrlIsBinary);
|
||||
|
||||
var pdb = getParam('pdb', '[^&]+').trim();
|
||||
if (pdb) viewer.loadPdb(pdb);
|
||||
|
||||
var pdbDev = getParam('pdb-dev', '[^&]+').trim();
|
||||
if (pdbDev) viewer.loadPdbDev(pdbDev);
|
||||
|
||||
var emdb = getParam('emdb', '[^&]+').trim();
|
||||
if (emdb) viewer.loadEmdb(emdb);
|
||||
|
||||
var afdb = getParam('afdb', '[^&]+').trim();
|
||||
if (afdb) viewer.loadAlphaFoldDb(afdb);
|
||||
|
||||
var modelArchive = getParam('model-archive', '[^&]+').trim();
|
||||
if (modelArchive) viewer.loadModelArchive(modelArchive);
|
||||
});
|
||||
|
||||
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
|
||||
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);
|
||||
|
||||
var snapshotUrl = getParam('snapshot-url', '[^&]+').trim();
|
||||
var snapshotUrlType = getParam('snapshot-url-type', '[^&]+').toLowerCase().trim() || 'molj';
|
||||
if (snapshotUrl && snapshotUrlType) viewer.loadSnapshotFromUrl(snapshotUrl, snapshotUrlType);
|
||||
|
||||
var structureUrl = getParam('structure-url', '[^&]+').trim();
|
||||
var structureUrlFormat = getParam('structure-url-format', '[a-z]+').toLowerCase().trim();
|
||||
var structureUrlIsBinary = getParam('structure-url-is-binary', '[^&]+').trim() === '1';
|
||||
if (structureUrl) viewer.loadStructureFromUrl(structureUrl, structureUrlFormat, structureUrlIsBinary);
|
||||
|
||||
var pdb = getParam('pdb', '[^&]+').trim();
|
||||
if (pdb) viewer.loadPdb(pdb);
|
||||
|
||||
var pdbDev = getParam('pdb-dev', '[^&]+').trim();
|
||||
if (pdbDev) viewer.loadPdbDev(pdbDev);
|
||||
|
||||
var emdb = getParam('emdb', '[^&]+').trim();
|
||||
if (emdb) viewer.loadEmdb(emdb);
|
||||
</script>
|
||||
<!-- __MOLSTAR_ANALYTICS__ -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,342 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
|
||||
import { CellPack } from '../../extensions/cellpack';
|
||||
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
|
||||
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { GeometryExport } from '../../extensions/geo-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 { StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
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 { createPlugin } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
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,
|
||||
disableAntialiasing: false,
|
||||
pixelScale: 1,
|
||||
enableWboit: true,
|
||||
|
||||
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: '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.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();
|
||||
});
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
this.plugin.layout.events.updated.next();
|
||||
}
|
||||
}
|
||||
|
||||
export interface LoadStructureOptions {
|
||||
representationParams?: StructureRepresentationPresetProvider.CommonParams
|
||||
}
|
||||
|
||||
export interface VolumeIsovalueInfo {
|
||||
type: 'absolute' | 'relative',
|
||||
value: number,
|
||||
color: Color,
|
||||
alpha?: number,
|
||||
volumeIndex?: number
|
||||
}
|
||||
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';
|
||||
@@ -19,7 +20,7 @@ import { ensureDataAvailable, readCCD } from './util';
|
||||
function extractIonNames(ccd: DatabaseCollection<CCD_Schema>) {
|
||||
const ionNames: string[] = [];
|
||||
for (const k in ccd) {
|
||||
const {chem_comp} = ccd[k];
|
||||
const { chem_comp } = ccd[k];
|
||||
if (chem_comp.name.value(0).toUpperCase().includes(' ION')) {
|
||||
ionNames.push(chem_comp.id.value(0));
|
||||
}
|
||||
@@ -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, ', ')});
|
||||
@@ -54,20 +55,20 @@ async function run(out: string, forceDownload = false) {
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
add_help: true,
|
||||
description: 'Extract and save IonNames from CCD.'
|
||||
});
|
||||
parser.addArgument('out', {
|
||||
parser.add_argument('out', {
|
||||
help: 'Generated file output path.'
|
||||
});
|
||||
parser.addArgument([ '--forceDownload', '-f' ], {
|
||||
action: 'storeTrue',
|
||||
parser.add_argument('--forceDownload', '-f', {
|
||||
action: 'store_true',
|
||||
help: 'Force download of CCD and PVCD.'
|
||||
});
|
||||
interface Args {
|
||||
out: string,
|
||||
forceDownload?: boolean,
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
run(args.out, args.forceDownload);
|
||||
|
||||
77
src/cli/chem-comp-dict/create-saccharides.ts
Normal file
77
src/cli/chem-comp-dict/create-saccharides.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as argparse from 'argparse';
|
||||
import * as path from 'path';
|
||||
import util from 'util';
|
||||
import fs from 'fs';
|
||||
require('util.promisify').shim();
|
||||
const writeFile = util.promisify(fs.writeFile);
|
||||
|
||||
import { DatabaseCollection } from '../../mol-data/db';
|
||||
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
|
||||
import { ensureDataAvailable, readCCD } from './util';
|
||||
|
||||
function extractSaccharideNames(ccd: DatabaseCollection<CCD_Schema>) {
|
||||
const saccharideNames: string[] = [];
|
||||
for (const k in ccd) {
|
||||
const { chem_comp } = ccd[k];
|
||||
const type = chem_comp.type.value(0).toUpperCase();
|
||||
if (type.includes('SACCHARIDE')) {
|
||||
saccharideNames.push(chem_comp.id.value(0));
|
||||
}
|
||||
}
|
||||
// these are extra saccharides that don't have SACCHARIDE in their type
|
||||
saccharideNames.push(
|
||||
'UMQ', // UNDECYL-MALTOSIDE, via GlyFinder
|
||||
'SQD', // SULFOQUINOVOSYLDIACYLGLYCEROL, via GlyFinder
|
||||
);
|
||||
return saccharideNames;
|
||||
}
|
||||
|
||||
function writeSaccharideNamesFile(filePath: string, ionNames: string[]) {
|
||||
const output = `/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated ion names params file. Names extracted from CCD components.
|
||||
*
|
||||
* @author molstar/cli/chem-comp-dict/create-saccharides
|
||||
*/
|
||||
|
||||
export const SaccharideNames = new Set(${JSON.stringify(ionNames).replace(/"/g, "'").replace(/,/g, ', ')});
|
||||
`;
|
||||
writeFile(filePath, output);
|
||||
}
|
||||
|
||||
async function run(out: string, forceDownload = false) {
|
||||
await ensureDataAvailable(forceDownload);
|
||||
const ccd = await readCCD();
|
||||
const saccharideNames = extractSaccharideNames(ccd);
|
||||
if (!fs.existsSync(path.dirname(out))) {
|
||||
fs.mkdirSync(path.dirname(out));
|
||||
}
|
||||
writeSaccharideNamesFile(out, saccharideNames);
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
add_help: true,
|
||||
description: 'Extract and save SaccharideNames from CCD.'
|
||||
});
|
||||
parser.add_argument('out', {
|
||||
help: 'Generated file output path.'
|
||||
});
|
||||
parser.add_argument('--forceDownload', '-f', {
|
||||
action: 'store_true',
|
||||
help: 'Force download of CCD and PVCD.'
|
||||
});
|
||||
interface Args {
|
||||
out: string,
|
||||
forceDownload?: boolean,
|
||||
}
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
run(args.out, args.forceDownload);
|
||||
@@ -171,7 +171,7 @@ async function createBonds(
|
||||
pdbx_aromatic_flag, pdbx_stereo_config, molstar_protonation_variant
|
||||
});
|
||||
|
||||
const bondDatabase = Database.ofTables(
|
||||
const bondDatabase = Database.ofTables(
|
||||
CCB_TABLE_NAME,
|
||||
{ chem_comp_bond: mmCIF_chemCompBond_schema },
|
||||
{ chem_comp_bond: bondTable }
|
||||
@@ -265,21 +265,21 @@ const CCB_TABLE_NAME = 'CHEM_COMP_BONDS';
|
||||
const CCA_TABLE_NAME = 'CHEM_COMP_ATOMS';
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
add_help: true,
|
||||
description: 'Create a cif file with one big table of all chem_comp_bond entries from the CCD and PVCD.'
|
||||
});
|
||||
parser.addArgument('out', {
|
||||
parser.add_argument('out', {
|
||||
help: 'Generated file output path.'
|
||||
});
|
||||
parser.addArgument([ '--forceDownload', '-f' ], {
|
||||
action: 'storeTrue',
|
||||
parser.add_argument('--forceDownload', '-f', {
|
||||
action: 'store_true',
|
||||
help: 'Force download of CCD and PVCD.'
|
||||
});
|
||||
parser.addArgument([ '--binary', '-b' ], {
|
||||
action: 'storeTrue',
|
||||
parser.add_argument('--binary', '-b', {
|
||||
action: 'store_true',
|
||||
help: 'Output as BinaryCIF.'
|
||||
});
|
||||
parser.addArgument(['--ccaOut', '-a'], {
|
||||
parser.add_argument('--ccaOut', '-a', {
|
||||
help: 'Optional generated file output path for chem_comp_atom data.',
|
||||
required: false
|
||||
});
|
||||
@@ -289,6 +289,6 @@ interface Args {
|
||||
binary?: boolean,
|
||||
ccaOut?: string
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
run(args.out, args.binary, args.forceDownload, args.ccaOut);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -37,20 +37,20 @@ function run(args: Args) {
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
description: 'Convert any CIF file to a BCIF file'
|
||||
add_help: true,
|
||||
description: 'Convert any BCIF file to a CIF file or vice versa'
|
||||
});
|
||||
parser.addArgument([ 'src' ], {
|
||||
help: 'Source CIF path'
|
||||
parser.add_argument('src', {
|
||||
help: 'Source file path'
|
||||
});
|
||||
parser.addArgument([ 'out' ], {
|
||||
help: 'Output BCIF path'
|
||||
parser.add_argument('out', {
|
||||
help: 'Output file path'
|
||||
});
|
||||
parser.addArgument([ '-c', '--config' ], {
|
||||
parser.add_argument('-c', '--config', {
|
||||
help: 'Optional encoding strategy/precision config path',
|
||||
required: false
|
||||
});
|
||||
parser.addArgument([ '-f', '--filter' ], {
|
||||
parser.add_argument('-f', '--filter', {
|
||||
help: 'Optional filter whitelist/blacklist path',
|
||||
required: false
|
||||
});
|
||||
@@ -61,7 +61,7 @@ interface Args {
|
||||
config?: string
|
||||
filter?: string
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
if (args) {
|
||||
run(args);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -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);
|
||||
@@ -124,23 +120,22 @@ async function getFieldNamesFilter(fieldNamesPath: string): Promise<Filter> {
|
||||
const csvFile = parsed.result;
|
||||
|
||||
const fieldNamesCol = csvFile.table.getColumn('0');
|
||||
if (!fieldNamesCol) throw 'error getting fields columns';
|
||||
if (!fieldNamesCol) throw new Error('error getting fields columns');
|
||||
const fieldNames = fieldNamesCol.toStringArray();
|
||||
|
||||
const filter: Filter = {};
|
||||
fieldNames.forEach((name, i) => {
|
||||
const [ category, field ] = name.split('.');
|
||||
const [category, field] = name.split('.');
|
||||
// console.log(category, field)
|
||||
if (!filter[ category ]) filter[ category ] = {};
|
||||
filter[ category ][ field ] = true;
|
||||
if (!filter[category]) filter[category] = {};
|
||||
filter[category][field] = true;
|
||||
});
|
||||
return filter;
|
||||
}
|
||||
|
||||
async function ensureMmcifDicAvailable() { await ensureDicAvailable(MMCIF_DIC_PATH, MMCIF_DIC_URL); }
|
||||
async function ensureIhmDicAvailable() { await ensureDicAvailable(IHM_DIC_PATH, IHM_DIC_URL); }
|
||||
async function ensureCarbBranchDicAvailable() { await ensureDicAvailable(CARB_BRANCH_DIC_PATH, CARB_BRANCH_DIC_URL); }
|
||||
async function ensureCarbCompDicAvailable() { await ensureDicAvailable(CARB_COMP_DIC_PATH, CARB_COMP_DIC_URL); }
|
||||
async function ensureMaDicAvailable() { await ensureDicAvailable(MA_DIC_PATH, MA_DIC_URL); }
|
||||
async function ensureCifCoreDicAvailable() {
|
||||
await ensureDicAvailable(CIF_CORE_DIC_PATH, CIF_CORE_DIC_URL);
|
||||
await ensureDicAvailable(CIF_CORE_ENUM_PATH, CIF_CORE_ENUM_URL);
|
||||
@@ -165,10 +160,8 @@ const MMCIF_DIC_PATH = `${DIC_DIR}/mmcif_pdbx_v50.dic`;
|
||||
const MMCIF_DIC_URL = 'http://mmcif.wwpdb.org/dictionaries/ascii/mmcif_pdbx_v50.dic';
|
||||
const IHM_DIC_PATH = `${DIC_DIR}/ihm-extension.dic`;
|
||||
const IHM_DIC_URL = 'https://raw.githubusercontent.com/ihmwg/IHM-dictionary/master/ihm-extension.dic';
|
||||
const CARB_BRANCH_DIC_PATH = `${DIC_DIR}/entity_branch-extension.dic`;
|
||||
const CARB_BRANCH_DIC_URL = 'https://raw.githubusercontent.com/pdbxmmcifwg/carbohydrate-extension/master/dict/entity_branch-extension.dic';
|
||||
const CARB_COMP_DIC_PATH = `${DIC_DIR}/chem_comp-extension.dic`;
|
||||
const CARB_COMP_DIC_URL = 'https://raw.githubusercontent.com/pdbxmmcifwg/carbohydrate-extension/master/dict/chem_comp-extension.dic';
|
||||
const MA_DIC_PATH = `${DIC_DIR}/ma-extension.dic`;
|
||||
const MA_DIC_URL = 'https://raw.githubusercontent.com/ihmwg/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';
|
||||
@@ -178,44 +171,44 @@ const CIF_CORE_ATTR_PATH = `${DIC_DIR}/templ_attr.cif`;
|
||||
const CIF_CORE_ATTR_URL = 'https://raw.githubusercontent.com/COMCIFS/cif_core/master/templ_attr.cif';
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
add_help: true,
|
||||
description: 'Create schema from mmcif dictionary (v50 plus IHM and entity_branch extensions, downloaded from wwPDB)'
|
||||
});
|
||||
parser.addArgument([ '--preset', '-p' ], {
|
||||
defaultValue: '',
|
||||
parser.add_argument('--preset', '-p', {
|
||||
default: '',
|
||||
choices: ['', 'mmCIF', 'CCD', 'BIRD', 'CifCore'],
|
||||
help: 'Preset name'
|
||||
});
|
||||
parser.addArgument([ '--name', '-n' ], {
|
||||
defaultValue: '',
|
||||
parser.add_argument('--name', '-n', {
|
||||
default: '',
|
||||
help: 'Schema name'
|
||||
});
|
||||
parser.addArgument([ '--out', '-o' ], {
|
||||
parser.add_argument('--out', '-o', {
|
||||
help: 'Generated schema output path, if not given printed to stdout'
|
||||
});
|
||||
parser.addArgument([ '--targetFormat', '-tf' ], {
|
||||
defaultValue: 'typescript-molstar',
|
||||
parser.add_argument('--targetFormat', '-tf', {
|
||||
default: 'typescript-molstar',
|
||||
choices: ['typescript-molstar', 'json-internal'],
|
||||
help: 'Target format'
|
||||
});
|
||||
parser.addArgument([ '--dicPath', '-d' ], {
|
||||
defaultValue: '',
|
||||
parser.add_argument('--dicPath', '-d', {
|
||||
default: '',
|
||||
help: 'Path to dictionary'
|
||||
});
|
||||
parser.addArgument([ '--fieldNamesPath', '-fn' ], {
|
||||
defaultValue: '',
|
||||
parser.add_argument('--fieldNamesPath', '-fn', {
|
||||
default: '',
|
||||
help: 'Field names to include'
|
||||
});
|
||||
parser.addArgument([ '--forceDicDownload', '-f' ], {
|
||||
action: 'storeTrue',
|
||||
parser.add_argument('--forceDicDownload', '-f', {
|
||||
action: 'store_true',
|
||||
help: 'Force download of dictionaries'
|
||||
});
|
||||
parser.addArgument([ '--moldataImportPath', '-mip' ], {
|
||||
defaultValue: 'molstar/lib/mol-data',
|
||||
parser.add_argument('--moldataImportPath', '-mip', {
|
||||
default: 'molstar/lib/mol-data',
|
||||
help: 'mol-data import path (for typescript target only)'
|
||||
});
|
||||
parser.addArgument([ '--addAliases', '-aa' ], {
|
||||
action: 'storeTrue',
|
||||
parser.add_argument('--addAliases', '-aa', {
|
||||
action: 'store_true',
|
||||
help: 'Add field name/path aliases'
|
||||
});
|
||||
interface Args {
|
||||
@@ -230,7 +223,7 @@ interface Args {
|
||||
moldataImportPath: string
|
||||
addAliases: boolean
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
const FORCE_DIC_DOWNLOAD = args.forceDicDownload;
|
||||
|
||||
|
||||
@@ -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,17 @@ 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':
|
||||
// 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':
|
||||
@@ -34,6 +36,8 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
case 'seq-one-letter-code':
|
||||
case 'author':
|
||||
case 'orcid_id':
|
||||
case 'pdbx_PDB_obsoleted_db_id':
|
||||
case 'pdbx_related_db_id':
|
||||
case 'sequence_dep':
|
||||
case 'pdb_id':
|
||||
case 'emd_id':
|
||||
@@ -47,6 +51,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':
|
||||
@@ -79,9 +84,10 @@ export function getFieldType(type: string, description: string, values?: string[
|
||||
case 'List(Real,Real)':
|
||||
case 'List(Real,Real,Real,Real)':
|
||||
case 'Date':
|
||||
case 'Datetime':
|
||||
case 'DateTime':
|
||||
case 'Tag':
|
||||
case 'Implied':
|
||||
case 'Word':
|
||||
return wrapContainer('str', ',', description, container);
|
||||
case 'Real':
|
||||
return wrapContainer('float', ',', description, container);
|
||||
@@ -187,7 +193,7 @@ function getContainer(d: Data.CifFrame, imports: Imports, ctx: FrameData) {
|
||||
function getCode(d: Data.CifFrame, imports: Imports, ctx: FrameData): [string, string[] | undefined, string | undefined ] | undefined {
|
||||
const code = getField('item_type', 'code', d, imports, ctx) || getField('type', 'contents', d, imports, ctx);
|
||||
if (code) {
|
||||
return [ code.str(0), getEnums(d, imports, ctx), getContainer(d, imports, ctx) ];
|
||||
return [code.str(0), getEnums(d, imports, ctx), getContainer(d, imports, ctx)];
|
||||
} else {
|
||||
console.log(`item_type.code or type.contents not found for '${d.header}'`);
|
||||
}
|
||||
@@ -232,29 +238,26 @@ const FORCE_INT_FIELDS = [
|
||||
'_struct_sheet_range.end_auth_seq_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* Note that name and mapped name must share a prefix. This is not always the case in
|
||||
* the cifCore dictionary, but for downstream code to work a container field with the
|
||||
* same prefix as the member fields must be given here and in the field names filter
|
||||
* list.
|
||||
*/
|
||||
const FORCE_MATRIX_FIELDS_MAP: { [k: string]: string } = {
|
||||
'atom_site_aniso.U_11': 'U',
|
||||
'atom_site_aniso.U_22': 'U',
|
||||
'atom_site_aniso.U_33': 'U',
|
||||
'atom_site_aniso.U_23': 'U',
|
||||
'atom_site_aniso.U_13': 'U',
|
||||
'atom_site_aniso.U_12': 'U',
|
||||
'atom_site_aniso.U_11_su': 'U_su',
|
||||
'atom_site_aniso.U_22_su': 'U_su',
|
||||
'atom_site_aniso.U_33_su': 'U_su',
|
||||
'atom_site_aniso.U_23_su': 'U_su',
|
||||
'atom_site_aniso.U_13_su': 'U_su',
|
||||
'atom_site_aniso.U_12_su': 'U_su',
|
||||
'atom_site_aniso.u_11': 'u', // is matrix_u in the the dic
|
||||
'atom_site_aniso.u_22': 'u',
|
||||
'atom_site_aniso.u_33': 'u',
|
||||
'atom_site_aniso.u_23': 'u',
|
||||
'atom_site_aniso.u_13': 'u',
|
||||
'atom_site_aniso.u_12': 'u',
|
||||
};
|
||||
const FORCE_MATRIX_FIELDS = Object.keys(FORCE_MATRIX_FIELDS_MAP);
|
||||
|
||||
const EXTRA_ALIASES: Database['aliases'] = {
|
||||
'atom_site_aniso.U': [
|
||||
'atom_site_anisotrop_U'
|
||||
],
|
||||
'atom_site_aniso.U_su': [
|
||||
'atom_site_aniso_U_esd',
|
||||
'atom_site_anisotrop_U_esd',
|
||||
'atom_site_aniso.matrix_u': [
|
||||
'atom_site_anisotrop_U',
|
||||
'atom_site_aniso.U'
|
||||
],
|
||||
};
|
||||
|
||||
@@ -317,7 +320,7 @@ export function generateSchema(frames: CifFrame[], imports: Imports = new Map())
|
||||
frames.forEach(d => {
|
||||
// category definitions in mmCIF start with '_' and don't include a '.'
|
||||
// category definitions in cifCore don't include a '.'
|
||||
if (d.header[0] === '_' || d.header.includes('.')) return;
|
||||
if (d.header[0] === '_' || d.header.includes('.')) return;
|
||||
const categoryName = d.header.toLowerCase();
|
||||
// console.log(d.header, d.categoryNames, d.categories)
|
||||
let descriptionField: Data.CifField | undefined;
|
||||
@@ -372,7 +375,7 @@ export function generateSchema(frames: CifFrame[], imports: Imports = new Map())
|
||||
const parent_name = item_linked.getField('parent_name');
|
||||
if (child_name && parent_name) {
|
||||
for (let i = 0; i < item_linked.rowCount; ++i) {
|
||||
const childName = child_name.str(i);
|
||||
const childName: string = child_name.str(i);
|
||||
const parentName = parent_name.str(i);
|
||||
if (childName in links && links[childName] !== parentName) {
|
||||
console.log(`${childName} linked to ${links[childName]}, ignoring link to ${parentName}`);
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -8,9 +8,9 @@ import { Database, Filter, Column } from './schema';
|
||||
import { indentString } from '../../../mol-util/string';
|
||||
import { FieldPath } from '../../../mol-io/reader/cif/schema';
|
||||
|
||||
function header (name: string, info: string, moldataImportPath: string) {
|
||||
function header(name: string, info: string, moldataImportPath: string) {
|
||||
return `/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated '${name}' schema file. ${info}
|
||||
*
|
||||
@@ -22,7 +22,7 @@ import { Database, Column } from '${moldataImportPath}/db';
|
||||
import Schema = Column.Schema;`;
|
||||
}
|
||||
|
||||
function footer (name: string) {
|
||||
function footer(name: string) {
|
||||
return `
|
||||
export type ${name}_Schema = typeof ${name}_Schema;
|
||||
export interface ${name}_Database extends Database<${name}_Schema> {};`;
|
||||
@@ -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;
|
||||
@@ -89,7 +93,7 @@ function doc(description: string, spacesCount: number) {
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function generate (name: string, info: string, schema: Database, fields: Filter | undefined, moldataImportPath: string, addAliases: boolean) {
|
||||
export function generate(name: string, info: string, schema: Database, fields: Filter | undefined, moldataImportPath: string, addAliases: boolean) {
|
||||
const codeLines: string[] = [];
|
||||
|
||||
if (fields) {
|
||||
@@ -128,7 +132,7 @@ export function generate (name: string, info: string, schema: Database, fields:
|
||||
codeLines.push('');
|
||||
codeLines.push(`export const ${name}_Aliases = {`);
|
||||
Object.keys(schema.aliases).forEach(path => {
|
||||
const [ table, columnName ] = path.split('.');
|
||||
const [table, columnName] = path.split('.');
|
||||
if (fields && !fields[table]) return;
|
||||
if (fields && !fields[table][columnName]) return;
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ export function parseImportGet(s: string): Import[] {
|
||||
// [{'save':hi_ang_Fox_coeffs 'file':templ_attr.cif} {'save':hi_ang_Fox_c0 'file':templ_enum.cif}]
|
||||
// [{"file":'templ_enum.cif' "save":'H_M_ref'}]
|
||||
return s.trim().substring(2, s.length - 2).split(/}[ \n\t]*{/g).map(s => {
|
||||
const save = s.match(/('save'|"save"):([^ \t\n]+)/);
|
||||
const file = s.match(/('file'|"file"):([^ \t\n]+)/);
|
||||
const save = s.match(/('save'|"save"):([^ \t\n{}]+)/);
|
||||
const file = s.match(/('file'|"file"):([^ \t\n{}]+)/);
|
||||
return {
|
||||
save: save ? save[0].substr(7).replace(/['"]/g, '') : undefined,
|
||||
file: file ? file[0].substr(7).replace(/['"]/g, '') : undefined
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -51,13 +51,13 @@ export function ListCol(subType: 'int' | 'str' | 'float' | 'coord', separator: s
|
||||
|
||||
export type Filter = { [ table: string ]: { [ column: string ]: true } }
|
||||
|
||||
export function mergeFilters (...filters: Filter[]) {
|
||||
export function mergeFilters(...filters: Filter[]) {
|
||||
const n = filters.length;
|
||||
const mergedFilter: Filter = {};
|
||||
const fields: Map<string, number> = new Map();
|
||||
filters.forEach(filter => {
|
||||
Object.keys(filter).forEach(category => {
|
||||
Object.keys(filter[ category ]).forEach(field => {
|
||||
Object.keys(filter[category]).forEach(field => {
|
||||
const key = `${category}.${field}`;
|
||||
const value = fields.get(key) || 0;
|
||||
fields.set(key, value + 1);
|
||||
|
||||
@@ -70,21 +70,21 @@ export const LipidNames = new Set(${lipidNames.replace(/"/g, "'").replace(/,/g,
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
add_help: true,
|
||||
description: 'Create lipid params (from martini lipids itp)'
|
||||
});
|
||||
parser.addArgument([ '--out', '-o' ], {
|
||||
parser.add_argument('--out', '-o', {
|
||||
help: 'Generated lipid params output path, if not given printed to stdout'
|
||||
});
|
||||
parser.addArgument([ '--forceDownload', '-f' ], {
|
||||
action: 'storeTrue',
|
||||
parser.add_argument('--forceDownload', '-f', {
|
||||
action: 'store_true',
|
||||
help: 'Force download of martini lipids itp'
|
||||
});
|
||||
interface Args {
|
||||
out: string
|
||||
forceDownload: boolean
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
const FORCE_DOWNLOAD = args.forceDownload;
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export function printSecStructure(model: Model) {
|
||||
const count = residues._rowCount;
|
||||
let rI = 0;
|
||||
while (rI < count) {
|
||||
let start = rI;
|
||||
const start = rI;
|
||||
while (rI < count && key[start] === key[rI]) rI++;
|
||||
rI--;
|
||||
|
||||
@@ -230,21 +230,21 @@ async function runFile(filename: string, args: Args) {
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
add_help: true,
|
||||
description: 'Print info about a structure, mainly to test and showcase the mol-model module'
|
||||
});
|
||||
parser.addArgument(['--download', '-d'], { help: 'Pdb entry id' });
|
||||
parser.addArgument(['--file', '-f'], { help: 'filename' });
|
||||
parser.add_argument('--download', '-d', { help: 'Pdb entry id' });
|
||||
parser.add_argument('--file', '-f', { help: 'filename' });
|
||||
|
||||
parser.addArgument(['--models'], { help: 'print models info', action: 'storeTrue' });
|
||||
parser.addArgument(['--seq'], { help: 'print sequence', action: 'storeTrue' });
|
||||
parser.addArgument(['--units'], { help: 'print units', action: 'storeTrue' });
|
||||
parser.addArgument(['--sym'], { help: 'print symmetry', action: 'storeTrue' });
|
||||
parser.addArgument(['--rings'], { help: 'print rings', action: 'storeTrue' });
|
||||
parser.addArgument(['--intraBonds'], { help: 'print intra unit bonds', action: 'storeTrue' });
|
||||
parser.addArgument(['--interBonds'], { help: 'print inter unit bonds', action: 'storeTrue' });
|
||||
parser.addArgument(['--mod'], { help: 'print modified residues', action: 'storeTrue' });
|
||||
parser.addArgument(['--sec'], { help: 'print secoundary structure', action: 'storeTrue' });
|
||||
parser.add_argument('--models', { help: 'print models info', action: 'store_true' });
|
||||
parser.add_argument('--seq', { help: 'print sequence', action: 'store_true' });
|
||||
parser.add_argument('--units', { help: 'print units', action: 'store_true' });
|
||||
parser.add_argument('--sym', { help: 'print symmetry', action: 'store_true' });
|
||||
parser.add_argument('--rings', { help: 'print rings', action: 'store_true' });
|
||||
parser.add_argument('--intraBonds', { help: 'print intra unit bonds', action: 'store_true' });
|
||||
parser.add_argument('--interBonds', { help: 'print inter unit bonds', action: 'store_true' });
|
||||
parser.add_argument('--mod', { help: 'print modified residues', action: 'store_true' });
|
||||
parser.add_argument('--sec', { help: 'print secoundary structure', action: 'store_true' });
|
||||
interface Args {
|
||||
download?: string,
|
||||
file?: string,
|
||||
@@ -260,7 +260,7 @@ interface Args {
|
||||
mod?: boolean,
|
||||
sec?: boolean,
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
if (args.download) runDL(args.download, args);
|
||||
else if (args.file) runFile(args.file, args);
|
||||
|
||||
@@ -38,7 +38,7 @@ function print(volume: Volume) {
|
||||
}
|
||||
|
||||
async function doMesh(volume: Volume, filename: string) {
|
||||
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) } )).run();
|
||||
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) })).run();
|
||||
console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
|
||||
|
||||
// Export the mesh in OBJ format.
|
||||
@@ -75,13 +75,13 @@ async function run(url: string, meshFilename: string) {
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
add_help: true,
|
||||
description: 'Info about VolumeData from mol-model module'
|
||||
});
|
||||
parser.addArgument([ '--emdb', '-e' ], {
|
||||
parser.add_argument('--emdb', '-e', {
|
||||
help: 'EMDB id, for example 8116',
|
||||
});
|
||||
parser.addArgument([ '--mesh' ], {
|
||||
parser.add_argument('--mesh', {
|
||||
help: 'Mesh filename',
|
||||
required: true
|
||||
});
|
||||
@@ -89,6 +89,6 @@ interface Args {
|
||||
emdb?: string,
|
||||
mesh: string
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
const args: Args = parser.parse_args();
|
||||
|
||||
run(`https://ds.litemol.org/em/emd-${args.emdb}/cell?detail=4`, args.mesh);
|
||||
@@ -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,7 @@ 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/react18';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
@@ -25,6 +25,8 @@ import { DemoMoleculeSDF, DemoOrbitals } from './example-data';
|
||||
import './index.html';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
import { setDebugMode, setTimingMode } from '../../mol-util/debug';
|
||||
|
||||
interface DemoInput {
|
||||
moleculeSdf: string,
|
||||
basis: Basis,
|
||||
@@ -54,7 +56,7 @@ export class AlphaOrbitalsExample {
|
||||
|
||||
async init(target: string | HTMLElement) {
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
this.plugin = await createPluginAsync(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...defaultSpec,
|
||||
layout: {
|
||||
initial: {
|
||||
@@ -67,7 +69,7 @@ export class AlphaOrbitalsExample {
|
||||
},
|
||||
canvas3d: {
|
||||
camera: {
|
||||
helper: { axes: { name: 'off', params: { } } }
|
||||
helper: { axes: { name: 'off', params: {} } }
|
||||
}
|
||||
},
|
||||
config: [
|
||||
@@ -80,24 +82,28 @@ export class AlphaOrbitalsExample {
|
||||
|
||||
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
|
||||
|
||||
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Error',
|
||||
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
|
||||
this.plugin.behaviors.canvas3d.initialized.subscribe(init => {
|
||||
if (!init) return;
|
||||
|
||||
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Error',
|
||||
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.load({
|
||||
moleculeSdf: DemoMoleculeSDF,
|
||||
...DemoOrbitals
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.load({
|
||||
moleculeSdf: DemoMoleculeSDF,
|
||||
...DemoOrbitals
|
||||
mountControls(this, document.getElementById('controls')!);
|
||||
});
|
||||
|
||||
mountControls(this, document.getElementById('controls')!);
|
||||
}
|
||||
|
||||
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
|
||||
readonly state = new BehaviorSubject<Params>({ show: { name: 'orbital', params: { index: 32 } }, isoValue: 1, gpuSurface: 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 +176,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 +224,6 @@ 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;
|
||||
@@ -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,7 +9,7 @@ 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/react18';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
@@ -28,8 +28,8 @@ 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, {
|
||||
async init(target: string | HTMLElement) {
|
||||
this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginUISpec(),
|
||||
layout: {
|
||||
initial: {
|
||||
@@ -60,7 +60,7 @@ class BasicWrapper {
|
||||
params: { id: assemblyId }
|
||||
} : {
|
||||
name: 'model',
|
||||
params: { }
|
||||
params: {}
|
||||
},
|
||||
showUnitcell: false,
|
||||
representationPreset: 'auto'
|
||||
@@ -74,12 +74,20 @@ class BasicWrapper {
|
||||
toggleSpin() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
const trackball = this.plugin.canvas3d.props.trackball;
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
settings: props => {
|
||||
props.trackball.spin = !props.trackball.spin;
|
||||
settings: {
|
||||
trackball: {
|
||||
...trackball,
|
||||
animate: trackball.animate.name === 'spin'
|
||||
? { name: 'off', params: {} }
|
||||
: { name: 'spin', params: { speed: 1 } }
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!this.plugin.canvas3d.props.trackball.spin) PluginCommands.Camera.Reset(this.plugin, {});
|
||||
if (this.plugin.canvas3d.props.trackball.animate.name !== 'spin') {
|
||||
PluginCommands.Camera.Reset(this.plugin, {});
|
||||
}
|
||||
}
|
||||
|
||||
private animateModelIndexTargetFps() {
|
||||
@@ -95,7 +103,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 +127,7 @@ class BasicWrapper {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
interactivity = {
|
||||
highlightOn: () => {
|
||||
@@ -137,7 +145,7 @@ class BasicWrapper {
|
||||
clearHighlight: () => {
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tests = {
|
||||
staticSuperposition: async () => {
|
||||
@@ -168,7 +176,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();
|
||||
@@ -38,7 +38,7 @@ type MappingRow = Table.Row<S.mapping>;
|
||||
|
||||
function writeDomain(enc: CifWriter.Encoder, domain: DomainAnnotation | undefined) {
|
||||
if (!domain) return;
|
||||
enc.writeCategory({ name: `pdbx_${domain.name}_domain_annotation`, instance: () => CifWriter.Category.ofTable(domain.domains) });
|
||||
enc.writeCategory({ name: `pdbx_${domain.name}_domain_annotation`, instance: () => CifWriter.Category.ofTable(domain.domains) });
|
||||
enc.writeCategory({ name: `pdbx_${domain.name}_domain_mapping`, instance: () => CifWriter.Category.ofTable(domain.mappings) });
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ async function getMappings(id: string) {
|
||||
};
|
||||
|
||||
|
||||
let PORT = process.env.port || 1338;
|
||||
const PORT = process.env.port || 1338;
|
||||
|
||||
const app = express();
|
||||
|
||||
|
||||
@@ -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,62 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Canvas3DProps } from '../../mol-canvas3d/canvas3d';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { createPlugin } from '../../mol-plugin-ui';
|
||||
import { createPluginUI } 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';
|
||||
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, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
|
||||
outline: { name: 'on', params: { scale: 1, threshold: 0.33, color: Color(0x000000) } }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 1.0,
|
||||
light: []
|
||||
}
|
||||
}
|
||||
},
|
||||
occlusion: <Preset> {
|
||||
multiSample: {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { 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, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
|
||||
outline: { name: 'off', params: {} }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
light: [{ inclination: 180, azimuth: 0, color: Color.fromNormalizedRgb(1.0, 1.0, 1.0),
|
||||
intensity: 0.6 }]
|
||||
}
|
||||
}
|
||||
},
|
||||
standard: <Preset> {
|
||||
multiSample: {
|
||||
mode: 'off' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: { name: 'off', params: { } },
|
||||
outline: { name: 'off', params: { } }
|
||||
},
|
||||
renderer: {
|
||||
style: { name: 'matte', params: {} }
|
||||
standard: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: { name: 'off', params: {} },
|
||||
outline: { name: 'off', params: {} }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
light: [{ inclination: 180, azimuth: 0, color: Color.fromNormalizedRgb(1.0, 1.0, 1.0),
|
||||
intensity: 0.6 }]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -68,8 +71,8 @@ class LightingDemo {
|
||||
private bias = 1.1;
|
||||
private preset: Canvas3DPreset = 'illustrative';
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
async init(target: string | HTMLElement) {
|
||||
this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginUISpec(),
|
||||
layout: {
|
||||
initial: {
|
||||
@@ -87,25 +90,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 +115,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,7 @@ 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/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,10 +43,10 @@ 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, {
|
||||
this.plugin = await createPluginUI(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginUISpec(),
|
||||
animations: [
|
||||
AnimateModelIndex
|
||||
@@ -95,7 +95,7 @@ class MolStarProteopediaWrapper {
|
||||
params: { id: assemblyId }
|
||||
} : {
|
||||
name: 'model' as const,
|
||||
params: { }
|
||||
params: {}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,7 +113,7 @@ class MolStarProteopediaWrapper {
|
||||
const structure = this.getObj<PluginStateObject.Molecule.Structure>(StateElements.Assembly);
|
||||
if (!structure) return;
|
||||
|
||||
const style = _style || { };
|
||||
const style = _style || {};
|
||||
|
||||
const update = this.state.build();
|
||||
|
||||
@@ -229,7 +229,7 @@ class MolStarProteopediaWrapper {
|
||||
params: { id: asmId }
|
||||
} : {
|
||||
name: 'model' as const,
|
||||
params: { }
|
||||
params: {}
|
||||
}
|
||||
};
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
|
||||
@@ -250,13 +250,22 @@ class MolStarProteopediaWrapper {
|
||||
setBackground(color: number) {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const renderer = this.plugin.canvas3d.props.renderer;
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
}
|
||||
|
||||
toggleSpin() {
|
||||
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 +278,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 +294,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 +315,7 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private experimentalDataElement?: Element = void 0;
|
||||
experimentalData = {
|
||||
@@ -330,17 +339,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 +406,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 +429,7 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
(window as any).MolStarProteopediaWrapper = MolStarProteopediaWrapper;
|
||||
@@ -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',
|
||||
|
||||
@@ -39,22 +39,34 @@ interface ANVILContext {
|
||||
};
|
||||
|
||||
export const ANVILParams = {
|
||||
numberOfSpherePoints: PD.Numeric(140, { min: 35, max: 700, step: 1 }, { description: 'Number of spheres/directions to test for membrane placement. Original value is 350.' }),
|
||||
numberOfSpherePoints: PD.Numeric(175, { min: 35, max: 700, step: 1 }, { description: 'Number of spheres/directions to test for membrane placement. Original value is 350.' }),
|
||||
stepSize: PD.Numeric(1, { min: 0.25, max: 4, step: 0.25 }, { description: 'Thickness of membrane slices that will be tested' }),
|
||||
minThickness: PD.Numeric(20, { min: 10, max: 30, step: 1}, { description: 'Minimum membrane thickness used during refinement' }),
|
||||
maxThickness: PD.Numeric(40, { min: 30, max: 50, step: 1}, { description: 'Maximum membrane thickness used during refinement' }),
|
||||
minThickness: PD.Numeric(20, { min: 10, max: 30, step: 1 }, { description: 'Minimum membrane thickness used during refinement' }),
|
||||
maxThickness: PD.Numeric(40, { min: 30, max: 50, step: 1 }, { description: 'Maximum membrane thickness used during refinement' }),
|
||||
asaCutoff: PD.Numeric(40, { min: 10, max: 100, step: 1 }, { description: 'Relative ASA cutoff above which residues will be considered' }),
|
||||
adjust: PD.Numeric(14, { min: 0, max: 30, step: 1 }, { description: 'Minimum length of membrane-spanning regions (original values: 14 for alpha-helices and 5 for beta sheets). Set to 0 to not optimize membrane thickness.' })
|
||||
adjust: PD.Numeric(14, { min: 0, max: 30, step: 1 }, { description: 'Minimum length of membrane-spanning regions (original values: 14 for alpha-helices and 5 for beta sheets). Set to 0 to not optimize membrane thickness.' }),
|
||||
tmdetDefinition: PD.Boolean(false, { description: `Use TMDET's classification of membrane-favoring amino acids. TMDET's classification shows better performance on porins and other beta-barrel structures.` })
|
||||
};
|
||||
export type ANVILParams = typeof ANVILParams
|
||||
export type ANVILProps = PD.Values<ANVILParams>
|
||||
|
||||
/** ANVIL-specific (not general) definition of membrane-favoring amino acids */
|
||||
const ANVIL_DEFINITION = new Set(['ALA', 'CYS', 'GLY', 'HIS', 'ILE', 'LEU', 'MET', 'PHE', 'SER', 'TRP', 'VAL']);
|
||||
/** TMDET-specific (not general) definition of membrane-favoring amino acids */
|
||||
const TMDET_DEFINITION = new Set(['LEU', 'ILE', 'VAL', 'PHE', 'MET', 'GLY', 'TRP', 'TYR']);
|
||||
|
||||
/**
|
||||
* Implements:
|
||||
* Membrane positioning for high- and low-resolution protein structures through a binary classification approach
|
||||
* Guillaume Postic, Yassine Ghouzam, Vincent Guiraud, and Jean-Christophe Gelly
|
||||
* Protein Engineering, Design & Selection, 2015, 1–5
|
||||
* doi: 10.1093/protein/gzv063
|
||||
*
|
||||
* ANVIL is derived from TMDET, the corresponding classification of hydrophobic amino acids is provided as optional parameter:
|
||||
* Gabor E. Tusnady, Zsuzsanna Dosztanyi and Istvan Simon
|
||||
* Transmembrane proteins in the Protein Data Bank: identification and classification
|
||||
* Bioinformatics, 2004, 2964-2972
|
||||
* doi: 10.1093/bioinformatics/bth340
|
||||
*/
|
||||
export function computeANVIL(structure: Structure, props: ANVILProps) {
|
||||
return Task.create('Compute Membrane Orientation', async runtime => {
|
||||
@@ -87,6 +99,11 @@ async function initialize(structure: Structure, props: ANVILProps, accessibleSur
|
||||
const offsets = new Array<number>();
|
||||
const exposed = new Array<number>();
|
||||
const hydrophobic = new Array<boolean>();
|
||||
const definition = props.tmdetDefinition ? TMDET_DEFINITION : ANVIL_DEFINITION;
|
||||
|
||||
function isPartOfEntity(l: StructureElement.Location): boolean {
|
||||
return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_seq_id.valueKind(l.unit.residueIndex[l.element]) === 0;
|
||||
}
|
||||
|
||||
const vec = v3zero();
|
||||
for (let i = 0, il = structure.units.length; i < il; ++i) {
|
||||
@@ -98,8 +115,8 @@ async function initialize(structure: Structure, props: ANVILProps, accessibleSur
|
||||
const eI = elements[j];
|
||||
l.element = eI;
|
||||
|
||||
// consider only amino acids
|
||||
if (getElementMoleculeType(unit, eI) !== MoleculeType.Protein) {
|
||||
// consider only amino acids in chains
|
||||
if (getElementMoleculeType(unit, eI) !== MoleculeType.Protein || !isPartOfEntity(l)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -121,7 +138,7 @@ async function initialize(structure: Structure, props: ANVILProps, accessibleSur
|
||||
offsets.push(structure.serialMapping.getSerialIndex(l.unit, l.element));
|
||||
if (AccessibleSurfaceArea.getValue(l, accessibleSurfaceArea) / MaxAsa[label_comp_id(l)] > asaCutoff) {
|
||||
exposed.push(structure.serialMapping.getSerialIndex(l.unit, l.element));
|
||||
hydrophobic.push(isHydrophobic(label_comp_id(l)));
|
||||
hydrophobic.push(isHydrophobic(definition, label_comp_id(l)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,7 +183,7 @@ export async function calculate(runtime: RuntimeContext, structure: Structure, p
|
||||
}
|
||||
|
||||
const normalVector = v3zero();
|
||||
const center = v3zero();
|
||||
const center = v3zero();
|
||||
v3sub(normalVector, membrane.planePoint1, membrane.planePoint2);
|
||||
v3normalize(normalVector, normalVector);
|
||||
|
||||
@@ -344,7 +361,7 @@ function membraneSegments(ctx: ANVILContext, membrane: MembraneCandidate): Array
|
||||
// collect all residues in membrane layer
|
||||
for (let k = 0, kl = offsets.length; k < kl; k++) {
|
||||
const unit = units[unitIndices[offsets[k]]];
|
||||
if (!Unit.isAtomic(unit)) throw 'Property only available for atomic models.';
|
||||
if (!Unit.isAtomic(unit)) notAtomic();
|
||||
const elementIndex = elementIndices[offsets[k]];
|
||||
|
||||
authAsymId = unit.model.atomicHierarchy.chains.auth_asym_id.value(unit.chainIndex[elementIndex]);
|
||||
@@ -365,7 +382,7 @@ function membraneSegments(ctx: ANVILContext, membrane: MembraneCandidate): Array
|
||||
|
||||
for (let k = 0, kl = offsets.length; k < kl; k++) {
|
||||
const unit = units[unitIndices[offsets[k]]];
|
||||
if (!Unit.isAtomic(unit)) throw 'Property only available for atomic models.';
|
||||
if (!Unit.isAtomic(unit)) notAtomic();
|
||||
const elementIndex = elementIndices[offsets[k]];
|
||||
|
||||
authAsymId = unit.model.atomicHierarchy.chains.auth_asym_id.value(unit.chainIndex[elementIndex]);
|
||||
@@ -387,7 +404,7 @@ function membraneSegments(ctx: ANVILContext, membrane: MembraneCandidate): Array
|
||||
}
|
||||
lastAuthSeqId = authSeqId;
|
||||
endOffset = k;
|
||||
} else {
|
||||
} else {
|
||||
lastAuthSeqId++;
|
||||
endOffset++;
|
||||
}
|
||||
@@ -428,6 +445,10 @@ function membraneSegments(ctx: ANVILContext, membrane: MembraneCandidate): Array
|
||||
return refinedSegments;
|
||||
}
|
||||
|
||||
function notAtomic(): never {
|
||||
throw new Error('Property only available for atomic models.');
|
||||
}
|
||||
|
||||
/** Filter for membrane residues and calculate the final extent of the membrane layer */
|
||||
function adjustExtent(ctx: ANVILContext, membrane: MembraneCandidate, centroid: Vec3): number {
|
||||
const { offsets, structure } = ctx;
|
||||
@@ -455,11 +476,11 @@ function adjustExtent(ctx: ANVILContext, membrane: MembraneCandidate, centroid:
|
||||
}
|
||||
|
||||
function qValue(currentStats: HphobHphil, initialStats: HphobHphil): number {
|
||||
if(initialStats.hphob < 1) {
|
||||
if (initialStats.hphob < 1) {
|
||||
initialStats.hphob = 0.1;
|
||||
}
|
||||
|
||||
if(initialStats.hphil < 1) {
|
||||
if (initialStats.hphil < 1) {
|
||||
initialStats.hphil += 1;
|
||||
}
|
||||
|
||||
@@ -484,7 +505,7 @@ function generateSpherePoints(ctx: ANVILContext, numberOfSpherePoints: number):
|
||||
const { centroid, extent } = ctx;
|
||||
const points = [];
|
||||
let oldPhi = 0, h, theta, phi;
|
||||
for(let k = 1, kl = numberOfSpherePoints + 1; k < kl; k++) {
|
||||
for (let k = 1, kl = numberOfSpherePoints + 1; k < kl; k++) {
|
||||
h = -1 + 2 * (k - 1) / (2 * numberOfSpherePoints - 1);
|
||||
theta = Math.acos(h);
|
||||
phi = (k === 1 || k === numberOfSpherePoints) ? 0 : (oldPhi + 3.6 / Math.sqrt(2 * numberOfSpherePoints * (1 - h * h))) % (2 * Math.PI);
|
||||
@@ -566,11 +587,9 @@ namespace HphobHphil {
|
||||
}
|
||||
}
|
||||
|
||||
/** ANVIL-specific (not general) definition of membrane-favoring amino acids */
|
||||
const HYDROPHOBIC_AMINO_ACIDS = new Set(['ALA', 'CYS', 'GLY', 'HIS', 'ILE', 'LEU', 'MET', 'PHE', 'SER', 'TRP', 'VAL']);
|
||||
/** Returns true if ANVIL considers this as amino acid that favors being embedded in a membrane */
|
||||
export function isHydrophobic(label_comp_id: string): boolean {
|
||||
return HYDROPHOBIC_AMINO_ACIDS.has(label_comp_id);
|
||||
/** Returns true if the definition considers this as membrane-favoring amino acid */
|
||||
export function isHydrophobic(definition: Set<string>, label_comp_id: string): boolean {
|
||||
return definition.has(label_comp_id);
|
||||
}
|
||||
|
||||
/** Accessible surface area used for normalization. ANVIL uses 'Total-Side REL' values from NACCESS, from: Hubbard, S. J., & Thornton, J. M. (1993). naccess. Computer Program, Department of Biochemistry and Molecular Biology, University College London, 2(1). */
|
||||
|
||||
@@ -30,7 +30,7 @@ export const ANVILMembraneOrientation = PluginBehavior.create<{ autoAttach: bool
|
||||
description: 'Data calculated with ANVIL algorithm.'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> {
|
||||
private provider = MembraneOrientationProvider
|
||||
private provider = MembraneOrientationProvider;
|
||||
|
||||
register(): void {
|
||||
DefaultQueryRuntimeTable.addCustomProp(this.provider.descriptor);
|
||||
@@ -52,7 +52,7 @@ export const ANVILMembraneOrientation = PluginBehavior.create<{ autoAttach: bool
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean }) {
|
||||
let updated = this.params.autoAttach !== p.autoAttach;
|
||||
const updated = this.params.autoAttach !== p.autoAttach;
|
||||
this.params.autoAttach = p.autoAttach;
|
||||
this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
|
||||
return updated;
|
||||
@@ -150,7 +150,7 @@ export const MembraneOrientationPreset = StructureRepresentationPresetProvider({
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
if (!MembraneOrientationProvider.get(structure).value) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -33,7 +33,7 @@ export function CellPackGenerateColorTheme(ctx: ThemeDataContext, props: PD.Valu
|
||||
|
||||
if (ctx.structure && info) {
|
||||
const colors = distinctColors(info.packingsCount);
|
||||
let hcl = Hcl.fromColor(Hcl(), colors[info.packingIndex]);
|
||||
const hcl = Hcl.fromColor(Hcl(), colors[info.packingIndex]);
|
||||
|
||||
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number];
|
||||
|
||||
@@ -48,7 +48,7 @@ export function CellPackGenerateColorTheme(ctx: ThemeDataContext, props: PD.Valu
|
||||
hue, chroma: [30, 80], luminance: [15, 85],
|
||||
clusteringStepCount: 50, minSampleCount: 800, maxCount: 75
|
||||
}
|
||||
}}, { minLabel: 'Min', maxLabel: 'Max' });
|
||||
} }, { minLabel: 'Min', maxLabel: 'Max' });
|
||||
legend = palette.legend;
|
||||
const modelColor = new Map<number, Color>();
|
||||
for (let i = 0, il = models.length; i < il; ++i) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Ludovic Autin <autin@scripps.edu>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
@@ -49,7 +49,7 @@ function ResampleControlPoints(points: NumberArray, segmentLength: number) {
|
||||
// controlPoints.Insert(0, controlPoints[0] + (controlPoints[0] - controlPoints[1]) / 2.0f);
|
||||
// controlPoints.Add(controlPoints[nP - 1] + (controlPoints[nP - 1] - controlPoints[nP - 2]) / 2.0f);
|
||||
|
||||
let resampledControlPoints: Vec3[] = [];
|
||||
const resampledControlPoints: Vec3[] = [];
|
||||
// resampledControlPoints.Add(controlPoints[0]);
|
||||
// resampledControlPoints.Add(controlPoints[1]);
|
||||
|
||||
@@ -111,7 +111,7 @@ function GetSmoothNormals(points: Vec3[]) {
|
||||
let p1 = points[1];
|
||||
let p2 = points[2];
|
||||
const p21 = Vec3.sub(tmpV1, p2, p1);
|
||||
const p01 = Vec3.sub(tmpV2, p0, p1);
|
||||
const p01 = Vec3.sub(tmpV2, p0, p1);
|
||||
const p0121 = Vec3.cross(tmpV3, p01, p21);
|
||||
Vec3.normalize(prevV, p0121);
|
||||
smoothNormals.push(Vec3.clone(prevV));
|
||||
@@ -179,7 +179,7 @@ function GetMiniFrame(points: Vec3[], normals: Vec3[]) {
|
||||
const v1t = Vec3.scale(mfTmpV5, v1, (2.0 / c1) * Vec3.dot(v1, frames[i].t));
|
||||
const tan_L_i = Vec3.sub(mfTmpV6, frames[i].t, v1t);
|
||||
// # compute reflection vector of R_2
|
||||
const v2 = Vec3.sub(mfTmpV7, t2, tan_L_i);
|
||||
const v2 = Vec3.sub(mfTmpV7, t2, tan_L_i);
|
||||
const c2 = Vec3.dot(v2, v2);
|
||||
// compute r_(i+1) = R_2 * r_i^L
|
||||
const v2l = Vec3.scale(mfTmpV8, v1, (2.0 / c2) * Vec3.dot(v2, ref_L_i));
|
||||
@@ -195,7 +195,7 @@ export function getMatFromResamplePoints(points: NumberArray, segmentLength: num
|
||||
let new_points: Vec3[] = [];
|
||||
if (resample) new_points = ResampleControlPoints(points, segmentLength);
|
||||
else {
|
||||
for (let idx = 0; idx < points.length / 3; ++idx){
|
||||
for (let idx = 0; idx < points.length / 3; ++idx) {
|
||||
new_points.push(Vec3.fromArray(Vec3.zero(), points, idx * 3));
|
||||
}
|
||||
}
|
||||
@@ -211,7 +211,7 @@ export function getMatFromResamplePoints(points: NumberArray, segmentLength: num
|
||||
if (d >= segmentLength) {
|
||||
// use twist or random?
|
||||
const quat = Quat.rotationTo(Quat.zero(), Vec3.create(0, 0, 1), frames[i].t); // Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),new_normal[i]);//Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),direction);new_normal
|
||||
const rq = Quat.setAxisAngle(Quat.zero(), frames[i].t, Math.random() * 3.60 ); // Quat.setAxisAngle(Quat.zero(),direction, Math.random()*3.60 );//Quat.identity();//
|
||||
const rq = Quat.setAxisAngle(Quat.zero(), frames[i].t, Math.random() * 3.60); // Quat.setAxisAngle(Quat.zero(),direction, Math.random()*3.60 );//Quat.identity();//
|
||||
const m = Mat4.fromQuat(Mat4.zero(), Quat.multiply(Quat.zero(), rq, quat)); // Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),quat1,quat2));//Mat4.fromQuat(Mat4.zero(),quat);//Mat4.identity();//Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),rq,quat));
|
||||
// let pos:Vec3 = Vec3.add(Vec3.zero(),pti1,pti)
|
||||
// pos = Vec3.scale(pos,pos,1.0/2.0);
|
||||
|
||||
@@ -13,16 +13,27 @@ export interface CellPack {
|
||||
|
||||
export interface CellPacking {
|
||||
name: string,
|
||||
location: 'surface' | 'interior' | 'cytoplasme',
|
||||
location: 'surface' | 'interior' | 'cytoplasme'
|
||||
ingredients: Packing['ingredients']
|
||||
compartment?: CellCompartment
|
||||
}
|
||||
|
||||
//
|
||||
export interface CellCompartment {
|
||||
filename?: string
|
||||
geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
|
||||
compartment_primitives?: CompartmentPrimitives
|
||||
}
|
||||
|
||||
export interface Cell {
|
||||
recipe: Recipe
|
||||
options?: RecipeOptions
|
||||
cytoplasme?: Packing
|
||||
compartments?: { [key: string]: Compartment }
|
||||
mapping_ids?: { [key: number]: [number, string] }
|
||||
}
|
||||
|
||||
export interface RecipeOptions {
|
||||
resultfile?: string
|
||||
}
|
||||
|
||||
export interface Recipe {
|
||||
@@ -35,8 +46,29 @@ export interface Recipe {
|
||||
export interface Compartment {
|
||||
surface?: Packing
|
||||
interior?: Packing
|
||||
geom?: unknown
|
||||
geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
|
||||
mb?: CompartmentPrimitives
|
||||
}
|
||||
|
||||
// Primitives discribing a compartment
|
||||
export const enum CompartmentPrimitiveType {
|
||||
MetaBall = 0,
|
||||
Sphere = 1,
|
||||
Cube = 2,
|
||||
Cylinder = 3,
|
||||
Cone = 4,
|
||||
Plane = 5,
|
||||
None = 6
|
||||
}
|
||||
|
||||
export interface CompartmentPrimitives{
|
||||
positions?: number[];
|
||||
radii?: number[];
|
||||
types?: CompartmentPrimitiveType[];
|
||||
}
|
||||
|
||||
|
||||
export interface Packing {
|
||||
ingredients: { [key: string]: Ingredient }
|
||||
}
|
||||
@@ -64,18 +96,20 @@ export interface Ingredient {
|
||||
[curveX: string]: unknown;
|
||||
/** the orientation in the membrane */
|
||||
principalAxis?: Vec3;
|
||||
principalVector?: Vec3;
|
||||
/** offset along membrane */
|
||||
offset?: Vec3;
|
||||
ingtype?: string;
|
||||
color?: Vec3;
|
||||
confidence?: number;
|
||||
Type?: string;
|
||||
}
|
||||
|
||||
export interface IngredientSource {
|
||||
pdb: string;
|
||||
bu?: string; /** biological unit e.g AU,BU1,etc.. */
|
||||
bu?: string; /** biological unit e.g AU,BU1,etc.. */
|
||||
selection?: string; /** NGL selection or :A or :B etc.. */
|
||||
model?: string; /** model number e.g 0,1,2... */
|
||||
model?: string; /** model number e.g 0,1,2... */
|
||||
transform: {
|
||||
center: boolean;
|
||||
translate?: Vec3;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
@@ -1,35 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { StateAction, StateBuilder, StateTransformer, State } from '../../mol-state';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Ingredient, IngredientSource, CellPacking } from './data';
|
||||
import { Ingredient, CellPacking, CompartmentPrimitives } from './data';
|
||||
import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean, getFromOPM } from './util';
|
||||
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit, Trajectory } from '../../mol-model/structure';
|
||||
import { trajectoryFromMmCIF, MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
|
||||
import { trajectoryFromPDB } from '../../mol-model-formats/structure/pdb';
|
||||
import { Mat4, Vec3, Quat } from '../../mol-math/linear-algebra';
|
||||
import { SymmetryOperator } from '../../mol-math/geometry';
|
||||
import { Task, RuntimeContext } from '../../mol-task';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies } from './state';
|
||||
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies, CreateCompartmentSphere } from './state';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { getMatFromResamplePoints } from './curve';
|
||||
import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
import { CifCategory, CifField } from '../../mol-io/reader/cif';
|
||||
import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Column } from '../../mol-data/db';
|
||||
import { createModels } from '../../mol-model-formats/structure/basic/parser';
|
||||
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { readFromFile } from '../../mol-util/data-source';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
import { readFromFile } from '../../mol-util/data-source';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/results/${fileName}`;
|
||||
@@ -41,12 +39,16 @@ class TrajectoryCache {
|
||||
get(id: string) { return this.map.get(id); }
|
||||
}
|
||||
|
||||
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, baseUrl: string, trajCache: TrajectoryCache, file?: Asset.File) {
|
||||
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient,
|
||||
baseUrl: string, trajCache: TrajectoryCache, location: string,
|
||||
file?: Asset.File
|
||||
) {
|
||||
const assetManager = plugin.managers.asset;
|
||||
const modelIndex = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
|
||||
const surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
|
||||
let surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
|
||||
if (location === 'surface') surface = true;
|
||||
let trajectory = trajCache.get(id);
|
||||
let assets: Asset.Wrapper[] = [];
|
||||
const assets: Asset.Wrapper[] = [];
|
||||
if (!trajectory) {
|
||||
if (file) {
|
||||
if (file.name.endsWith('.cif')) {
|
||||
@@ -68,10 +70,11 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien
|
||||
throw new Error(`unsupported file type '${file.name}'`);
|
||||
}
|
||||
} else if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
|
||||
if (surface){
|
||||
if (surface) {
|
||||
try {
|
||||
const data = await getFromOPM(plugin, id, assetManager);
|
||||
assets.push(data.asset);
|
||||
data.pdb.id! = id.toUpperCase();
|
||||
trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
|
||||
} catch (e) {
|
||||
// fallback to getFromPdb
|
||||
@@ -100,7 +103,7 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien
|
||||
return { model, assets };
|
||||
}
|
||||
|
||||
async function getStructure(plugin: PluginContext, model: Model, source: IngredientSource, props: { assembly?: string } = {}) {
|
||||
async function getStructure(plugin: PluginContext, model: Model, source: Ingredient, props: { assembly?: string } = {}) {
|
||||
let structure = Structure.ofModel(model);
|
||||
const { assembly } = props;
|
||||
|
||||
@@ -108,11 +111,12 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi
|
||||
structure = await plugin.runTask(StructureSymmetry.buildAssembly(structure, assembly));
|
||||
}
|
||||
let query;
|
||||
if (source.selection){
|
||||
const asymIds: string[] = source.selection.replace(' ', '').replace(':', '').split('or');
|
||||
if (source.source.selection) {
|
||||
const sel = source.source.selection;
|
||||
// selection can have the model ID as well. remove it
|
||||
const asymIds: string[] = sel.replace(/ /g, '').replace(/:/g, '').split('or').slice(1);
|
||||
query = MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
'chain-test': MS.core.set.has([MS.set(...asymIds), MS.ammp('auth_asym_id')])
|
||||
})
|
||||
]);
|
||||
@@ -123,17 +127,17 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
const compiled = compile<StructureSelection>(query);
|
||||
const result = compiled(new QueryContext(structure));
|
||||
structure = StructureSelection.unionStructure(result);
|
||||
|
||||
// change here if possible the label ?
|
||||
// structure.label = source.name;
|
||||
return structure;
|
||||
}
|
||||
|
||||
function getTransformLegacy(trans: Vec3, rot: Quat) {
|
||||
const q: Quat = Quat.create(-rot[3], rot[0], rot[1], rot[2]);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4(), q);
|
||||
Mat4.transpose(m, m);
|
||||
Mat4.scale(m, m, Vec3.create(-1.0, 1.0, -1.0));
|
||||
Mat4.setTranslation(m, trans);
|
||||
@@ -141,9 +145,9 @@ function getTransformLegacy(trans: Vec3, rot: Quat) {
|
||||
}
|
||||
|
||||
function getTransform(trans: Vec3, rot: Quat) {
|
||||
const q: Quat = Quat.create(rot[0], rot[1], rot[2], rot[3]);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
|
||||
const p: Vec3 = Vec3.create(trans[0], trans[1], trans[2]);
|
||||
const q: Quat = Quat.create(-rot[0], rot[1], rot[2], -rot[3]);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4(), q);
|
||||
const p: Vec3 = Vec3.create(-trans[0], trans[1], trans[2]);
|
||||
Mat4.setTranslation(m, p);
|
||||
return m;
|
||||
}
|
||||
@@ -157,9 +161,9 @@ function getCurveTransforms(ingredient: Ingredient) {
|
||||
const n = ingredient.nbCurve || 0;
|
||||
const instances: Mat4[] = [];
|
||||
let segmentLength = 3.4;
|
||||
if (ingredient.uLength){
|
||||
if (ingredient.uLength) {
|
||||
segmentLength = ingredient.uLength;
|
||||
} else if (ingredient.radii){
|
||||
} else if (ingredient.radii) {
|
||||
segmentLength = ingredient.radii[0].radii
|
||||
? ingredient.radii[0].radii[0] * 2.0
|
||||
: 3.4;
|
||||
@@ -168,7 +172,7 @@ function getCurveTransforms(ingredient: Ingredient) {
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const cname = `curve${i}`;
|
||||
if (!(cname in ingredient)) {
|
||||
// console.warn(`Expected '${cname}' in ingredient`)
|
||||
console.warn(`Expected '${cname}' in ingredient`);
|
||||
continue;
|
||||
}
|
||||
const _points = ingredient[cname] as Vec3[];
|
||||
@@ -177,9 +181,9 @@ function getCurveTransforms(ingredient: Ingredient) {
|
||||
continue;
|
||||
}
|
||||
// test for resampling
|
||||
let distance: number = Vec3.distance(_points[0], _points[1]);
|
||||
const distance: number = Vec3.distance(_points[0], _points[1]);
|
||||
if (distance >= segmentLength + 2.0) {
|
||||
console.info(distance);
|
||||
// console.info(distance);
|
||||
resampling = true;
|
||||
}
|
||||
const points = new Float32Array(_points.length * 3);
|
||||
@@ -190,13 +194,13 @@ function getCurveTransforms(ingredient: Ingredient) {
|
||||
return instances;
|
||||
}
|
||||
|
||||
function getAssembly(transforms: Mat4[], structure: Structure) {
|
||||
const builder = Structure.Builder();
|
||||
function getAssembly(name: string, transforms: Mat4[], structure: Structure) {
|
||||
const builder = Structure.Builder({ label: name });
|
||||
const { units } = structure;
|
||||
|
||||
for (let i = 0, il = transforms.length; i < il; ++i) {
|
||||
const id = `${i + 1}`;
|
||||
const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [ id ] } });
|
||||
const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [id] } });
|
||||
for (const unit of units) {
|
||||
builder.addWithOperator(unit, op);
|
||||
}
|
||||
@@ -205,115 +209,15 @@ function getAssembly(transforms: Mat4[], structure: Structure) {
|
||||
return builder.getStructure();
|
||||
}
|
||||
|
||||
function getCifCurve(name: string, transforms: Mat4[], model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) throw new Error('mmcif source data needed');
|
||||
|
||||
const { db } = model.sourceData.data;
|
||||
const d = db.atom_site;
|
||||
const n = d._rowCount;
|
||||
const rowCount = n * transforms.length;
|
||||
|
||||
const { offsets, count } = model.atomicHierarchy.chainAtomSegments;
|
||||
|
||||
const x = d.Cartn_x.toArray();
|
||||
const y = d.Cartn_y.toArray();
|
||||
const z = d.Cartn_z.toArray();
|
||||
|
||||
const Cartn_x = new Float32Array(rowCount);
|
||||
const Cartn_y = new Float32Array(rowCount);
|
||||
const Cartn_z = new Float32Array(rowCount);
|
||||
const map = new Uint32Array(rowCount);
|
||||
const seq = new Int32Array(rowCount);
|
||||
let offset = 0;
|
||||
for (let c = 0; c < count; ++c) {
|
||||
const cStart = offsets[c];
|
||||
const cEnd = offsets[c + 1];
|
||||
const cLength = cEnd - cStart;
|
||||
for (let t = 0, tl = transforms.length; t < tl; ++t) {
|
||||
const m = transforms[t];
|
||||
for (let j = cStart; j < cEnd; ++j) {
|
||||
const i = offset + j - cStart;
|
||||
const xj = x[j], yj = y[j], zj = z[j];
|
||||
Cartn_x[i] = m[0] * xj + m[4] * yj + m[8] * zj + m[12];
|
||||
Cartn_y[i] = m[1] * xj + m[5] * yj + m[9] * zj + m[13];
|
||||
Cartn_z[i] = m[2] * xj + m[6] * yj + m[10] * zj + m[14];
|
||||
map[i] = j;
|
||||
seq[i] = t + 1;
|
||||
}
|
||||
offset += cLength;
|
||||
}
|
||||
}
|
||||
|
||||
function multColumn<T>(column: Column<T>) {
|
||||
const array = column.toArray();
|
||||
return Column.ofLambda({
|
||||
value: row => array[map[row]],
|
||||
areValuesEqual: (rowA, rowB) => map[rowA] === map[rowB] || array[map[rowA]] === array[map[rowB]],
|
||||
rowCount, schema: column.schema
|
||||
});
|
||||
}
|
||||
|
||||
const _atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = {
|
||||
auth_asym_id: CifField.ofColumn(multColumn(d.auth_asym_id)),
|
||||
auth_atom_id: CifField.ofColumn(multColumn(d.auth_atom_id)),
|
||||
auth_comp_id: CifField.ofColumn(multColumn(d.auth_comp_id)),
|
||||
auth_seq_id: CifField.ofNumbers(seq),
|
||||
|
||||
B_iso_or_equiv: CifField.ofColumn(Column.ofConst(0, rowCount, Column.Schema.float)),
|
||||
Cartn_x: CifField.ofNumbers(Cartn_x),
|
||||
Cartn_y: CifField.ofNumbers(Cartn_y),
|
||||
Cartn_z: CifField.ofNumbers(Cartn_z),
|
||||
group_PDB: CifField.ofColumn(Column.ofConst('ATOM', rowCount, Column.Schema.str)),
|
||||
id: CifField.ofColumn(Column.ofLambda({
|
||||
value: row => row,
|
||||
areValuesEqual: (rowA, rowB) => rowA === rowB,
|
||||
rowCount, schema: d.id.schema,
|
||||
})),
|
||||
|
||||
label_alt_id: CifField.ofColumn(multColumn(d.label_alt_id)),
|
||||
|
||||
label_asym_id: CifField.ofColumn(multColumn(d.label_asym_id)),
|
||||
label_atom_id: CifField.ofColumn(multColumn(d.label_atom_id)),
|
||||
label_comp_id: CifField.ofColumn(multColumn(d.label_comp_id)),
|
||||
label_seq_id: CifField.ofNumbers(seq),
|
||||
label_entity_id: CifField.ofColumn(Column.ofConst('1', rowCount, Column.Schema.str)),
|
||||
|
||||
occupancy: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.float)),
|
||||
type_symbol: CifField.ofColumn(multColumn(d.type_symbol)),
|
||||
|
||||
pdbx_PDB_ins_code: CifField.ofColumn(Column.ofConst('', rowCount, Column.Schema.str)),
|
||||
pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.int)),
|
||||
};
|
||||
|
||||
const categories = {
|
||||
entity: CifCategory.ofTable('entity', db.entity),
|
||||
chem_comp: CifCategory.ofTable('chem_comp', db.chem_comp),
|
||||
atom_site: CifCategory.ofFields('atom_site', _atom_site)
|
||||
};
|
||||
|
||||
return {
|
||||
header: name,
|
||||
categoryNames: Object.keys(categories),
|
||||
categories
|
||||
};
|
||||
async function getCurve(name: string, transforms: Mat4[], model: Model) {
|
||||
const structure = Structure.ofModel(model);
|
||||
const assembly = getAssembly(name, transforms, structure);
|
||||
return assembly;
|
||||
}
|
||||
|
||||
async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredient, transforms: Mat4[], model: Model) {
|
||||
const cif = getCifCurve(name, transforms, model);
|
||||
const curveModelTask = Task.create('Curve Model', async ctx => {
|
||||
const format = MmcifFormat.fromFrame(cif);
|
||||
const models = await createModels(format.data.db, format, ctx);
|
||||
return models.representative;
|
||||
});
|
||||
|
||||
const curveModel = await plugin.runTask(curveModelTask);
|
||||
return getStructure(plugin, curveModel, ingredient.source);
|
||||
}
|
||||
|
||||
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache) {
|
||||
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache, location: 'surface' | 'interior' | 'cytoplasme') {
|
||||
const { name, source, results, nbCurve } = ingredient;
|
||||
if (source.pdb === 'None') return;
|
||||
|
||||
const file = ingredientFiles[source.pdb];
|
||||
if (!file) {
|
||||
// TODO can these be added to the library?
|
||||
@@ -325,72 +229,79 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
|
||||
}
|
||||
|
||||
// model id in case structure is NMR
|
||||
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, file);
|
||||
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, location, file);
|
||||
if (!model) return;
|
||||
|
||||
let structure: Structure;
|
||||
if (nbCurve) {
|
||||
structure = await getCurve(plugin, name, ingredient, getCurveTransforms(ingredient), model);
|
||||
structure = await getCurve(name, getCurveTransforms(ingredient), model);
|
||||
} else {
|
||||
if ((!results || results.length === 0)) return;
|
||||
let bu: string|undefined = source.bu ? source.bu : undefined;
|
||||
if (bu){
|
||||
if (bu) {
|
||||
if (bu === 'AU') {
|
||||
bu = undefined;
|
||||
} else {
|
||||
bu = bu.slice(2);
|
||||
}
|
||||
}
|
||||
structure = await getStructure(plugin, model, source, { assembly: bu });
|
||||
structure = await getStructure(plugin, model, ingredient, { assembly: bu });
|
||||
// transform with offset and pcp
|
||||
let legacy: boolean = true;
|
||||
if (ingredient.offset || ingredient.principalAxis){
|
||||
const pcp = ingredient.principalVector ? ingredient.principalVector : ingredient.principalAxis;
|
||||
if (pcp) {
|
||||
legacy = false;
|
||||
const structureMean = getStructureMean(structure);
|
||||
Vec3.negate(structureMean, structureMean);
|
||||
const m1: Mat4 = Mat4.identity();
|
||||
Mat4.setTranslation(m1, structureMean);
|
||||
structure = Structure.transform(structure, m1);
|
||||
if (ingredient.offset){
|
||||
if (!Vec3.exactEquals(ingredient.offset, Vec3.zero())){
|
||||
if (ingredient.offset) {
|
||||
const o: Vec3 = Vec3.create(ingredient.offset[0], ingredient.offset[1], ingredient.offset[2]);
|
||||
if (!Vec3.exactEquals(o, Vec3())) { // -1, 1, 4e-16 ??
|
||||
if (location !== 'surface') {
|
||||
Vec3.negate(o, o);
|
||||
}
|
||||
const m: Mat4 = Mat4.identity();
|
||||
Mat4.setTranslation(m, ingredient.offset);
|
||||
Mat4.setTranslation(m, o);
|
||||
structure = Structure.transform(structure, m);
|
||||
}
|
||||
}
|
||||
if (ingredient.principalAxis){
|
||||
if (!Vec3.exactEquals(ingredient.principalAxis, Vec3.unitZ)){
|
||||
if (pcp) {
|
||||
const p: Vec3 = Vec3.create(pcp[0], pcp[1], pcp[2]);
|
||||
if (!Vec3.exactEquals(p, Vec3.unitZ)) {
|
||||
const q: Quat = Quat.identity();
|
||||
Quat.rotationTo(q, ingredient.principalAxis, Vec3.unitZ);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
|
||||
Quat.rotationTo(q, p, Vec3.unitZ);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4(), q);
|
||||
structure = Structure.transform(structure, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
structure = getAssembly(getResultTransforms(results, legacy), structure);
|
||||
|
||||
structure = getAssembly(name, getResultTransforms(results, legacy), structure);
|
||||
}
|
||||
|
||||
return { structure, assets };
|
||||
}
|
||||
|
||||
|
||||
export function createStructureFromCellPack(plugin: PluginContext, packing: CellPacking, baseUrl: string, ingredientFiles: IngredientFiles) {
|
||||
return Task.create('Create Packing Structure', async ctx => {
|
||||
const { ingredients, name } = packing;
|
||||
const { ingredients, location, name } = packing;
|
||||
const assets: Asset.Wrapper[] = [];
|
||||
const trajCache = new TrajectoryCache();
|
||||
const structures: Structure[] = [];
|
||||
const colors: Color[] = [];
|
||||
let skipColors: boolean = false;
|
||||
for (const iName in ingredients) {
|
||||
if (ctx.shouldUpdate) await ctx.update(iName);
|
||||
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache);
|
||||
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache, location);
|
||||
if (ingredientStructure) {
|
||||
structures.push(ingredientStructure.structure);
|
||||
assets.push(...ingredientStructure.assets);
|
||||
const c = ingredients[iName].color;
|
||||
if (c){
|
||||
if (c) {
|
||||
colors.push(Color.fromNormalizedRgb(c[0], c[1], c[2]));
|
||||
} else {
|
||||
skipColors = true;
|
||||
colors.push(Color.fromNormalizedRgb(1, 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,7 +313,7 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
|
||||
for (const s of structures) {
|
||||
if (ctx.shouldUpdate) await ctx.update(`${s.label}`);
|
||||
let maxInvariantId = 0;
|
||||
let maxChainGroupId = 0;
|
||||
const maxChainGroupId = 0;
|
||||
for (const u of s.units) {
|
||||
const invariantId = u.invariantId + offsetInvariantId;
|
||||
const chainGroupId = u.chainGroupId + offsetChainGroupId;
|
||||
@@ -414,21 +325,20 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
|
||||
const structure = Structure.create(units);
|
||||
for( let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
const structure = Structure.create(units, { label: name + '.' + location });
|
||||
for (let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
|
||||
}
|
||||
return { structure, assets, colors: skipColors ? undefined : colors };
|
||||
return { structure, assets, colors: colors };
|
||||
});
|
||||
}
|
||||
|
||||
async function handleHivRna(plugin: PluginContext, packings: CellPacking[], baseUrl: string) {
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0' || packings[i].name === 'HIV_capsid') {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${baseUrl}/extras/rna_allpoints.json`);
|
||||
const json = await plugin.runTask(plugin.managers.asset.resolve(url, 'json', false));
|
||||
const points = json.data.points as number[];
|
||||
|
||||
const curve0: Vec3[] = [];
|
||||
for (let j = 0, jl = points.length; j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j));
|
||||
@@ -454,7 +364,7 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!file){
|
||||
if (!file) {
|
||||
// check for cif directly
|
||||
const cifileName = `${name}.cif`;
|
||||
for (const f of params.ingredients) {
|
||||
@@ -465,7 +375,8 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let legacy_membrane: boolean = false; // temporary variable until all membrane are converted to the new correct cif format
|
||||
let geometry_membrane: boolean = false; // membrane can be a mesh geometry
|
||||
let b = state.build().toRoot();
|
||||
if (file) {
|
||||
if (file.name.endsWith('.cif')) {
|
||||
@@ -474,27 +385,84 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
|
||||
b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } });
|
||||
}
|
||||
} else {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
|
||||
if (name.toLowerCase().endsWith('.bcif')) {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
|
||||
} else if (name.toLowerCase().endsWith('.cif')) {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
|
||||
} else if (name.toLowerCase().endsWith('.ply')) {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/geometries/${name}`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
|
||||
geometry_membrane = true;
|
||||
} else {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
|
||||
legacy_membrane = true;
|
||||
}
|
||||
}
|
||||
|
||||
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StructureFromAssemblies, undefined, { state: { isGhost: true } })
|
||||
.commit({ revertOnError: true });
|
||||
|
||||
const membraneParams = {
|
||||
representation: params.preset.representation,
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: '1' }
|
||||
}
|
||||
};
|
||||
if (legacy_membrane) {
|
||||
// old membrane
|
||||
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StructureFromAssemblies, undefined, { state: { isGhost: true } })
|
||||
.commit({ revertOnError: true });
|
||||
const membraneParams = {
|
||||
ignoreLight: params.preset.adjustStyle,
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
|
||||
} else if (geometry_membrane) {
|
||||
await b.apply(StateTransforms.Data.ParsePly, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ShapeFromPly)
|
||||
.apply(StateTransforms.Representation.ShapeRepresentation3D, { xrayShaded: true,
|
||||
doubleSided: true, coloring: { name: 'uniform', params: { color: ColorNames.orange } } })
|
||||
.commit({ revertOnError: true });
|
||||
} else {
|
||||
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { state: { isGhost: true } })
|
||||
.commit({ revertOnError: true });
|
||||
const membraneParams = {
|
||||
ignoreLight: params.preset.adjustStyle,
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
|
||||
async function handleMembraneSpheres(state: State, primitives: CompartmentPrimitives) {
|
||||
const nSpheres = primitives.positions!.length / 3;
|
||||
// console.log('ok mb ', nSpheres);
|
||||
// TODO : take in account the type of the primitives.
|
||||
for (let j = 0; j < nSpheres; j++) {
|
||||
await state.build()
|
||||
.toRoot()
|
||||
.apply(CreateCompartmentSphere, {
|
||||
center: Vec3.create(
|
||||
primitives.positions![j * 3 + 0],
|
||||
primitives.positions![j * 3 + 1],
|
||||
primitives.positions![j * 3 + 2]
|
||||
),
|
||||
radius: primitives!.radii![j]
|
||||
})
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
|
||||
const ingredientFiles = params.ingredients || [];
|
||||
|
||||
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
|
||||
let resultsFile: Asset.File | null = params.results;
|
||||
if (params.source.name === 'id') {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl));
|
||||
cellPackJson = state.build().toRoot()
|
||||
@@ -506,29 +474,36 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
|
||||
return;
|
||||
}
|
||||
|
||||
let jsonFile: Asset.File;
|
||||
let modelFile: Asset.File;
|
||||
if (file.name.toLowerCase().endsWith('.zip')) {
|
||||
const data = await readFromFile(file.file, 'zip').runInContext(runtime);
|
||||
jsonFile = Asset.File(new File([data['model.json']], 'model.json'));
|
||||
if (data['model.json']) {
|
||||
modelFile = Asset.File(new File([data['model.json']], 'model.json'));
|
||||
} else {
|
||||
throw new Error('model.json missing from zip file');
|
||||
}
|
||||
if (data['results.bin']) {
|
||||
resultsFile = Asset.File(new File([data['results.bin']], 'results.bin'));
|
||||
}
|
||||
objectForEach(data, (v, k) => {
|
||||
if (k === 'model.json') return;
|
||||
if (k === 'results.bin') return;
|
||||
ingredientFiles.push(Asset.File(new File([v], k)));
|
||||
});
|
||||
} else {
|
||||
jsonFile = file;
|
||||
modelFile = file;
|
||||
}
|
||||
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.ReadFile, { file: jsonFile, isBinary: false, label: jsonFile.name }, { state: { isGhost: true } });
|
||||
.apply(StateTransforms.Data.ReadFile, { file: modelFile, isBinary: false, label: modelFile.name }, { state: { isGhost: true } });
|
||||
}
|
||||
|
||||
const cellPackBuilder = cellPackJson
|
||||
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack);
|
||||
.apply(ParseCellPack, { resultsFile, baseUrl: params.baseUrl });
|
||||
|
||||
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime);
|
||||
const { packings } = cellPackObject.obj!.data;
|
||||
|
||||
const { packings } = cellPackObject.obj!.data;
|
||||
await handleHivRna(plugin, packings, params.baseUrl);
|
||||
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
@@ -541,11 +516,34 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
|
||||
|
||||
const packingParams = {
|
||||
traceOnly: params.preset.traceOnly,
|
||||
ignoreLight: params.preset.adjustStyle,
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackPackingPreset.apply(packing, packingParams, plugin);
|
||||
if ( packings[i].location === 'surface' && params.membrane){
|
||||
await loadMembrane(plugin, packings[i].name, state, params);
|
||||
if (packings[i].compartment) {
|
||||
if (params.membrane === 'lipids') {
|
||||
if (packings[i].compartment!.geom_type) {
|
||||
if (packings[i].compartment!.geom_type === 'file') {
|
||||
// TODO: load mesh files or vertex,faces data
|
||||
await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
|
||||
} else if (packings[i].compartment!.compartment_primitives) {
|
||||
await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
|
||||
}
|
||||
} else {
|
||||
// try loading membrane from repo as a bcif file or from the given list of files.
|
||||
if (params.membrane === 'lipids') {
|
||||
await loadMembrane(plugin, packings[i].name, state, params);
|
||||
}
|
||||
}
|
||||
} else if (params.membrane === 'geometry') {
|
||||
if (packings[i].compartment!.compartment_primitives) {
|
||||
await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
|
||||
} else if (packings[i].compartment!.geom_type === 'file') {
|
||||
if (packings[i].compartment!.filename!.toLowerCase().endsWith('.ply')) {
|
||||
await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -555,22 +553,23 @@ const LoadCellPackModelParams = {
|
||||
'id': PD.Select('InfluenzaModel2.json', [
|
||||
['blood_hiv_immature_inside.json', 'Blood HIV immature'],
|
||||
['HIV_immature_model.json', 'HIV immature'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'Blood HIV'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV'],
|
||||
['Blood_HIV.json', 'Blood HIV'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.json', 'HIV'],
|
||||
['influenza_model1.json', 'Influenza envelope'],
|
||||
['InfluenzaModel2.json', 'Influenza Complete'],
|
||||
['InfluenzaModel2.json', 'Influenza complete'],
|
||||
['ExosomeModel.json', 'Exosome Model'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma simple'],
|
||||
['MycoplasmaModel.json', 'Mycoplasma WholeCell model'],
|
||||
['MycoplasmaGenitalium.json', 'Mycoplasma Genitalium curated model'],
|
||||
] as const, { description: 'Download the model definition with `id` from the server at `baseUrl.`' }),
|
||||
'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.' }),
|
||||
'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.', label: 'Recipe file' }),
|
||||
}, { options: [['id', 'Id'], ['file', 'File']] }),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
membrane: PD.Boolean(true),
|
||||
ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredients' }),
|
||||
results: PD.File({ accept: '.bin', description: 'open results file in binary format from cellpackgpu for the specified recipe', label: 'Results file' }),
|
||||
membrane: PD.Select('lipids', PD.arrayToOptions(['lipids', 'geometry', 'none'])),
|
||||
ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredient files' }),
|
||||
preset: PD.Group({
|
||||
traceOnly: PD.Boolean(false),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation']))
|
||||
adjustStyle: PD.Boolean(true),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation'] as const))
|
||||
}, { isExpanded: true })
|
||||
};
|
||||
type LoadCellPackModelParams = PD.Values<typeof LoadCellPackModelParams>
|
||||
@@ -580,5 +579,43 @@ export const LoadCellPackModel = StateAction.build({
|
||||
params: LoadCellPackModelParams,
|
||||
from: PSO.Root
|
||||
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
|
||||
if (params.preset.adjustStyle) {
|
||||
ctx.managers.interactivity.setProps({ granularity: 'chain' });
|
||||
ctx.managers.structure.component.setOptions({
|
||||
... ctx.managers.structure.component.state.options,
|
||||
visualQuality: 'custom',
|
||||
ignoreLight: true,
|
||||
showHydrogens: false,
|
||||
});
|
||||
ctx.canvas3d?.setProps({
|
||||
multiSample: { mode: 'off' },
|
||||
cameraClipping: { far: false },
|
||||
renderer: { colorMarker: false },
|
||||
marking: {
|
||||
enabled: true,
|
||||
ghostEdgeStrength: 1,
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
radius: 8,
|
||||
bias: 1,
|
||||
blurKernelSize: 15,
|
||||
resolutionScale: 1,
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
name: 'on',
|
||||
params: {
|
||||
scale: 1,
|
||||
threshold: 0.33,
|
||||
color: ColorNames.black,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
await loadPackings(ctx, taskCtx, state, params);
|
||||
}));
|
||||
}));
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
@@ -9,12 +10,11 @@ import { StructureRepresentationPresetProvider, presetStaticComponent } from '..
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { CellPackGenerateColorThemeProvider } from './color/generate';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
import { CellPackProvidedColorThemeProvider } from './color/provided';
|
||||
|
||||
export const CellpackPackingPresetParams = {
|
||||
traceOnly: PD.Boolean(true),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'])),
|
||||
ignoreLight: PD.Boolean(false),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'] as const)),
|
||||
};
|
||||
export type CellpackPackingPresetParams = PD.ValuesFor<typeof CellpackPackingPresetParams>
|
||||
|
||||
@@ -28,7 +28,9 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
|
||||
|
||||
const reprProps = {
|
||||
ignoreHydrogens: true,
|
||||
traceOnly: params.traceOnly
|
||||
traceOnly: params.traceOnly,
|
||||
instanceGranularity: true,
|
||||
ignoreLight: params.ignoreLight,
|
||||
};
|
||||
const components = {
|
||||
polymer: await presetStaticComponent(plugin, structureCell, 'polymer')
|
||||
@@ -38,12 +40,12 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
|
||||
Object.assign(reprProps, {
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2, doubleSided: false
|
||||
});
|
||||
} else if (params.representation === 'spacefill' && params.traceOnly) {
|
||||
Object.assign(reprProps, { sizeFactor: 2 });
|
||||
} else if (params.representation === 'spacefill') {
|
||||
Object.assign(reprProps, { sizeFactor: params.traceOnly ? 2 : 1 });
|
||||
}
|
||||
|
||||
const info = structureCell.obj?.data && CellPackInfoProvider.get(structureCell.obj?.data).value;
|
||||
const color = info?.colors ? CellPackProvidedColorThemeProvider.name : CellPackGenerateColorThemeProvider.name;
|
||||
// default is generated
|
||||
const color = CellPackGenerateColorThemeProvider.name;
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
|
||||
const representations = {
|
||||
@@ -58,7 +60,8 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
|
||||
//
|
||||
|
||||
export const CellpackMembranePresetParams = {
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'])),
|
||||
ignoreLight: PD.Boolean(false),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'] as const)),
|
||||
};
|
||||
export type CellpackMembranePresetParams = PD.ValuesFor<typeof CellpackMembranePresetParams>
|
||||
|
||||
@@ -72,6 +75,8 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({
|
||||
|
||||
const reprProps = {
|
||||
ignoreHydrogens: true,
|
||||
instanceGranularity: true,
|
||||
ignoreLight: params.ignoreLight,
|
||||
};
|
||||
const components = {
|
||||
membrane: await presetStaticComponent(plugin, structureCell, 'all', { label: 'Membrane' })
|
||||
@@ -85,11 +90,11 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
|
||||
const representations = {
|
||||
membrane: builder.buildRepresentation(update, components.membrane, { type: 'gaussian-surface', typeParams: { ...typeParams, ...reprProps }, color: 'uniform', colorParams: { value: ColorNames.lightgrey } }, { tag: 'all' })
|
||||
membrane: builder.buildRepresentation(update, components.membrane, { type: params.representation, typeParams: { ...typeParams, ...reprProps }, color: 'uniform', colorParams: { value: ColorNames.lightgrey } }, { tag: 'all' })
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -34,4 +34,4 @@ export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellP
|
||||
value: { ...CellPackInfoParams.info.defaultValue, ...props.info }
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
70
src/extensions/cellpack/representation.ts
Normal file
70
src/extensions/cellpack/representation.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { ShapeRepresentation } from '../../mol-repr/shape/representation';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
// import { Polyhedron, DefaultPolyhedronProps } from '../../mol-geo/primitive/polyhedron';
|
||||
// import { Icosahedron } from '../../mol-geo/primitive/icosahedron';
|
||||
import { Sphere } from '../../mol-geo/primitive/sphere';
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { RepresentationParamsGetter, Representation, RepresentationContext } from '../../mol-repr/representation';
|
||||
|
||||
|
||||
interface MembraneSphereData {
|
||||
radius: number
|
||||
center: Vec3
|
||||
}
|
||||
|
||||
|
||||
const MembraneSphereParams = {
|
||||
...Mesh.Params,
|
||||
cellColor: PD.Color(ColorNames.orange),
|
||||
cellScale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
|
||||
radius: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
|
||||
center: PD.Vec3(Vec3.create(0, 0, 0)),
|
||||
quality: { ...Mesh.Params.quality, isEssential: false },
|
||||
};
|
||||
|
||||
type MeshParams = typeof MembraneSphereParams
|
||||
|
||||
const MembraneSphereVisuals = {
|
||||
'mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MeshParams>) => ShapeRepresentation(getMBShape, Mesh.Utils),
|
||||
};
|
||||
|
||||
export const MBParams = {
|
||||
...MembraneSphereParams
|
||||
};
|
||||
export type MBParams = typeof MBParams
|
||||
export type UnitcellProps = PD.Values<MBParams>
|
||||
|
||||
function getMBMesh(data: MembraneSphereData, props: UnitcellProps, mesh?: Mesh) {
|
||||
const state = MeshBuilder.createState(256, 128, mesh);
|
||||
const radius = props.radius;
|
||||
const asphere = Sphere(3);
|
||||
const trans: Mat4 = Mat4.identity();
|
||||
Mat4.fromScaling(trans, Vec3.create(radius, radius, radius));
|
||||
state.currentGroup = 1;
|
||||
MeshBuilder.addPrimitive(state, trans, asphere);
|
||||
const m = MeshBuilder.getMesh(state);
|
||||
return m;
|
||||
}
|
||||
|
||||
function getMBShape(ctx: RuntimeContext, data: MembraneSphereData, props: UnitcellProps, shape?: Shape<Mesh>) {
|
||||
const geo = getMBMesh(data, props, shape && shape.geometry);
|
||||
const label = 'mb';
|
||||
return Shape.create(label, data, geo, () => props.cellColor, () => 1, () => label);
|
||||
}
|
||||
|
||||
export type MBRepresentation = Representation<MembraneSphereData, MBParams>
|
||||
export function MBRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MBParams>): MBRepresentation {
|
||||
return Representation.createMulti('MB', ctx, getParams, Representation.StateBuilder, MembraneSphereVisuals as unknown as Representation.Def<MembraneSphereData, MBParams>);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
@@ -15,9 +16,13 @@ import { PluginContext } from '../../mol-plugin/context';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
import { Structure, StructureSymmetry, Unit, Model } from '../../mol-model/structure';
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
import { Vec3, Quat } from '../../mol-math/linear-algebra';
|
||||
import { StateTransformer } from '../../mol-state';
|
||||
import { MBRepresentation, MBParams } from './representation';
|
||||
import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
|
||||
import { getFloatValue } from './util';
|
||||
|
||||
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/';
|
||||
|
||||
export const DefaultCellPackBaseUrl = 'https://raw.githubusercontent.com/mesoscope/cellPACK_data/master/cellPACK_database_1.1.0';
|
||||
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
|
||||
|
||||
export { ParseCellPack };
|
||||
@@ -26,26 +31,173 @@ const ParseCellPack = PluginStateTransform.BuiltIn({
|
||||
name: 'parse-cellpack',
|
||||
display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
|
||||
from: PSO.Format.Json,
|
||||
to: CellPack
|
||||
to: CellPack,
|
||||
params: a => {
|
||||
return {
|
||||
resultsFile: PD.File({ accept: '.bin' }),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl)
|
||||
};
|
||||
}
|
||||
})({
|
||||
apply({ a }) {
|
||||
apply({ a, params, cache }, plugin: PluginContext) {
|
||||
return Task.create('Parse CellPack', async ctx => {
|
||||
const cell = a.data as Cell;
|
||||
|
||||
let counter_id = 0;
|
||||
let fiber_counter_id = 0;
|
||||
let comp_counter = 0;
|
||||
const packings: CellPacking[] = [];
|
||||
const { compartments, cytoplasme } = cell;
|
||||
if (!cell.mapping_ids) cell.mapping_ids = {};
|
||||
if (cytoplasme) {
|
||||
packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
|
||||
for (const iName in cytoplasme.ingredients) {
|
||||
if (cytoplasme.ingredients[iName].ingtype === 'fiber') {
|
||||
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
|
||||
if (!cytoplasme.ingredients[iName].nbCurve) cytoplasme.ingredients[iName].nbCurve = 0;
|
||||
fiber_counter_id++;
|
||||
} else {
|
||||
cell.mapping_ids[counter_id] = [comp_counter, iName];
|
||||
if (!cytoplasme.ingredients[iName].results) { cytoplasme.ingredients[iName].results = []; }
|
||||
counter_id++;
|
||||
}
|
||||
}
|
||||
comp_counter++;
|
||||
}
|
||||
if (compartments) {
|
||||
for (const name in compartments) {
|
||||
const { surface, interior } = compartments[name];
|
||||
if (surface) packings.push({ name, location: 'surface', ingredients: surface.ingredients });
|
||||
if (interior) packings.push({ name, location: 'interior', ingredients: interior.ingredients });
|
||||
let filename = '';
|
||||
if (compartments[name].geom_type === 'file') {
|
||||
filename = (compartments[name].geom) ? compartments[name].geom as string : '';
|
||||
}
|
||||
const compartment = { filename: filename, geom_type: compartments[name].geom_type, compartment_primitives: compartments[name].mb };
|
||||
if (surface) {
|
||||
packings.push({ name, location: 'surface', ingredients: surface.ingredients, compartment: compartment });
|
||||
for (const iName in surface.ingredients) {
|
||||
if (surface.ingredients[iName].ingtype === 'fiber') {
|
||||
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
|
||||
if (!surface.ingredients[iName].nbCurve) surface.ingredients[iName].nbCurve = 0;
|
||||
fiber_counter_id++;
|
||||
} else {
|
||||
cell.mapping_ids[counter_id] = [comp_counter, iName];
|
||||
if (!surface.ingredients[iName].results) { surface.ingredients[iName].results = []; }
|
||||
counter_id++;
|
||||
}
|
||||
}
|
||||
comp_counter++;
|
||||
}
|
||||
if (interior) {
|
||||
if (!surface) packings.push({ name, location: 'interior', ingredients: interior.ingredients, compartment: compartment });
|
||||
else packings.push({ name, location: 'interior', ingredients: interior.ingredients });
|
||||
for (const iName in interior.ingredients) {
|
||||
if (interior.ingredients[iName].ingtype === 'fiber') {
|
||||
cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
|
||||
if (!interior.ingredients[iName].nbCurve) interior.ingredients[iName].nbCurve = 0;
|
||||
fiber_counter_id++;
|
||||
} else {
|
||||
cell.mapping_ids[counter_id] = [comp_counter, iName];
|
||||
if (!interior.ingredients[iName].results) { interior.ingredients[iName].results = []; }
|
||||
counter_id++;
|
||||
}
|
||||
}
|
||||
comp_counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
|
||||
const { options } = cell;
|
||||
let resultsAsset: Asset.Wrapper<'binary'> | undefined;
|
||||
if (params.resultsFile) {
|
||||
resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(params.resultsFile, 'binary', true));
|
||||
} else if (options?.resultfile) {
|
||||
const url = `${params.baseUrl}/results/${options.resultfile}`;
|
||||
resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(Asset.getUrlAsset(plugin.managers.asset, url), 'binary', true));
|
||||
}
|
||||
if (resultsAsset) {
|
||||
(cache as any).asset = resultsAsset;
|
||||
const results = resultsAsset.data;
|
||||
// flip the byte order if needed
|
||||
const buffer = IsNativeEndianLittle ? results.buffer : flipByteOrder(results, 4);
|
||||
const numbers = new DataView(buffer);
|
||||
const ninst = getFloatValue(numbers, 0);
|
||||
const npoints = getFloatValue(numbers, 4);
|
||||
const ncurve = getFloatValue(numbers, 8);
|
||||
|
||||
let offset = 12;
|
||||
|
||||
if (ninst !== 0) {
|
||||
const pos = new Float32Array(buffer, offset, ninst * 4);
|
||||
offset += ninst * 4 * 4;
|
||||
const quat = new Float32Array(buffer, offset, ninst * 4);
|
||||
offset += ninst * 4 * 4;
|
||||
|
||||
for (let i = 0; i < ninst; i++) {
|
||||
const x: number = pos[i * 4 + 0];
|
||||
const y: number = pos[i * 4 + 1];
|
||||
const z: number = pos[i * 4 + 2];
|
||||
const ingr_id = pos[i * 4 + 3] as number;
|
||||
const pid = cell.mapping_ids![ingr_id];
|
||||
if (!packings[pid[0]].ingredients[pid[1]].results) {
|
||||
packings[pid[0]].ingredients[pid[1]].results = [];
|
||||
}
|
||||
packings[pid[0]].ingredients[pid[1]].results.push([Vec3.create(x, y, z),
|
||||
Quat.create(quat[i * 4 + 0], quat[i * 4 + 1], quat[i * 4 + 2], quat[i * 4 + 3])]);
|
||||
}
|
||||
}
|
||||
|
||||
if (npoints !== 0) {
|
||||
const ctr_pos = new Float32Array(buffer, offset, npoints * 4);
|
||||
offset += npoints * 4 * 4;
|
||||
offset += npoints * 4 * 4;
|
||||
const ctr_info = new Float32Array(buffer, offset, npoints * 4);
|
||||
offset += npoints * 4 * 4;
|
||||
const curve_ids = new Float32Array(buffer, offset, ncurve * 4);
|
||||
offset += ncurve * 4 * 4;
|
||||
|
||||
let counter = 0;
|
||||
let ctr_points: Vec3[] = [];
|
||||
let prev_ctype = 0;
|
||||
let prev_cid = 0;
|
||||
|
||||
for (let i = 0; i < npoints; i++) {
|
||||
const x: number = -ctr_pos[i * 4 + 0];
|
||||
const y: number = ctr_pos[i * 4 + 1];
|
||||
const z: number = ctr_pos[i * 4 + 2];
|
||||
const cid: number = ctr_info[i * 4 + 0]; // curve id
|
||||
const ctype: number = curve_ids[cid * 4 + 0]; // curve type
|
||||
// cid 148 165 -1 0
|
||||
// console.log("cid ",cid,ctype,prev_cid,prev_ctype);//165,148
|
||||
if (prev_ctype !== ctype) {
|
||||
const pid = cell.mapping_ids![-prev_ctype - 1];
|
||||
const cname = `curve${counter}`;
|
||||
packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
|
||||
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
|
||||
ctr_points = [];
|
||||
counter = 0;
|
||||
} else if (prev_cid !== cid) {
|
||||
ctr_points = [];
|
||||
const pid = cell.mapping_ids![-prev_ctype - 1];
|
||||
const cname = `curve${counter}`;
|
||||
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
|
||||
counter += 1;
|
||||
}
|
||||
ctr_points.push(Vec3.create(x, y, z));
|
||||
prev_ctype = ctype;
|
||||
prev_cid = cid;
|
||||
}
|
||||
|
||||
// do the last one
|
||||
const pid = cell.mapping_ids![-prev_ctype - 1];
|
||||
const cname = `curve${counter}`;
|
||||
packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
|
||||
packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
|
||||
}
|
||||
}
|
||||
return new CellPack({ cell, packings });
|
||||
});
|
||||
}
|
||||
},
|
||||
dispose({ cache }) {
|
||||
((cache as any)?.asset as Asset.Wrapper | undefined)?.dispose();
|
||||
},
|
||||
});
|
||||
|
||||
export { StructureFromCellpack };
|
||||
@@ -77,14 +229,13 @@ const StructureFromCellpack = PluginStateTransform.BuiltIn({
|
||||
await CellPackInfoProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, structure, {
|
||||
info: { packingsCount: a.data.packings.length, packingIndex: params.packing, colors }
|
||||
});
|
||||
|
||||
(cache as any).assets = assets;
|
||||
return new PSO.Molecule.Structure(structure, { label: packing.name });
|
||||
return new PSO.Molecule.Structure(structure, { label: packing.name + '.' + packing.location });
|
||||
});
|
||||
},
|
||||
dispose({ b, cache }) {
|
||||
const assets = (cache as any).assets as Asset.Wrapper[];
|
||||
if(assets) {
|
||||
if (assets) {
|
||||
for (const a of assets) a.dispose();
|
||||
}
|
||||
|
||||
@@ -115,17 +266,17 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
|
||||
// TODO: optimze
|
||||
// TODO: think of ways how to fast-track changes to this for animations
|
||||
const model = a.data;
|
||||
let initial_structure = Structure.ofModel(model);
|
||||
const initial_structure = Structure.ofModel(model);
|
||||
const structures: Structure[] = [];
|
||||
let structure: Structure = initial_structure;
|
||||
// the list of asambly *?
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
if (symmetry && symmetry.assemblies.length !== 0){
|
||||
if (symmetry && symmetry.assemblies.length !== 0) {
|
||||
for (const a of symmetry.assemblies) {
|
||||
const s = await StructureSymmetry.buildAssembly(initial_structure, a.id).runInContext(ctx);
|
||||
structures.push(s);
|
||||
}
|
||||
const builder = Structure.Builder();
|
||||
const builder = Structure.Builder({ label: 'Membrane' });
|
||||
let offsetInvariantId = 0;
|
||||
for (const s of structures) {
|
||||
let maxInvariantId = 0;
|
||||
@@ -137,7 +288,7 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
|
||||
offsetInvariantId += maxInvariantId + 1;
|
||||
}
|
||||
structure = builder.getStructure();
|
||||
for( let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
for (let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
|
||||
}
|
||||
}
|
||||
@@ -148,3 +299,28 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
const CreateTransformer = StateTransformer.builderFactory('cellPACK');
|
||||
export const CreateCompartmentSphere = CreateTransformer({
|
||||
name: 'create-compartment-sphere',
|
||||
display: 'CompartmentSphere',
|
||||
from: PSO.Root, // or whatever data source
|
||||
to: PSO.Shape.Representation3D,
|
||||
params: {
|
||||
center: PD.Vec3(Vec3()),
|
||||
radius: PD.Numeric(1),
|
||||
label: PD.Text(`Compartment Sphere`)
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ oldParams, newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Compartment Sphere', async ctx => {
|
||||
const data = params;
|
||||
const repr = MBRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => (MBParams));
|
||||
await repr.createOrUpdate({ ...params, quality: 'custom', xrayShaded: true, doubleSided: true }, data).runInContext(ctx);
|
||||
return new PSO.Shape.Representation3D({ repr, sourceData: a }, { label: data.label });
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { CIF } from '../../mol-io/reader/cif';
|
||||
@@ -37,11 +38,11 @@ async function downloadPDB(plugin: PluginContext, url: string, id: string, asset
|
||||
}
|
||||
|
||||
export async function getFromPdb(plugin: PluginContext, pdbId: string, assetManager: AssetManager) {
|
||||
const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId.toUpperCase()}.bcif`, true, assetManager);
|
||||
const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId}.bcif`, true, assetManager);
|
||||
return { mmcif: cif.blocks[0], asset };
|
||||
}
|
||||
|
||||
export async function getFromOPM(plugin: PluginContext, pdbId: string, assetManager: AssetManager){
|
||||
export async function getFromOPM(plugin: PluginContext, pdbId: string, assetManager: AssetManager) {
|
||||
const asset = await plugin.runTask(assetManager.resolve(Asset.getUrlAsset(assetManager, `https://opm-assets.storage.googleapis.com/pdb/${pdbId.toLowerCase()}.pdb`), 'string'));
|
||||
return { pdb: await parsePDBfile(plugin, asset.data, pdbId), asset };
|
||||
}
|
||||
@@ -74,4 +75,35 @@ export function getStructureMean(structure: Structure) {
|
||||
}
|
||||
const { elementCount } = structure;
|
||||
return Vec3.create(xSum / elementCount, ySum / elementCount, zSum / elementCount);
|
||||
}
|
||||
|
||||
export function getFloatValue(value: DataView, offset: number) {
|
||||
// if the last byte is a negative value (MSB is 1), the final
|
||||
// float should be too
|
||||
const negative = value.getInt8(offset + 2) >>> 31;
|
||||
|
||||
// this is how the bytes are arranged in the byte array/DataView
|
||||
// buffer
|
||||
const [b0, b1, b2, exponent] = [
|
||||
// get first three bytes as unsigned since we only care
|
||||
// about the last 8 bits of 32-bit js number returned by
|
||||
// getUint8().
|
||||
// Should be the same as: getInt8(offset) & -1 >>> 24
|
||||
value.getUint8(offset),
|
||||
value.getUint8(offset + 1),
|
||||
value.getUint8(offset + 2),
|
||||
|
||||
// get the last byte, which is the exponent, as a signed int
|
||||
// since it's already correct
|
||||
value.getInt8(offset + 3)
|
||||
];
|
||||
|
||||
let mantissa = b0 | (b1 << 8) | (b2 << 16);
|
||||
if (negative) {
|
||||
// need to set the most significant 8 bits to 1's since a js
|
||||
// number is 32 bits but our mantissa is only 24.
|
||||
mantissa |= 255 << 24;
|
||||
}
|
||||
|
||||
return mantissa * Math.pow(10, exponent);
|
||||
}
|
||||
@@ -41,10 +41,10 @@ export const DnatcoConfalPyramidsPreset = StructureRepresentationPresetProvider(
|
||||
|
||||
let pyramidsRepr;
|
||||
if (representations)
|
||||
pyramidsRepr = builder.buildRepresentation(update, pyramids, { type: ConfalPyramidsRepresentationProvider, typeParams, color: ConfalPyramidsColorThemeProvider }, { tag: 'confal-pyramdis' } );
|
||||
pyramidsRepr = builder.buildRepresentation(update, pyramids, { type: ConfalPyramidsRepresentationProvider, typeParams, color: ConfalPyramidsColorThemeProvider }, { tag: 'confal-pyramdis' });
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
return { components: { ...components, pyramids }, representations: { ...representations, pyramidsRepr } };
|
||||
return { components: { ...components, pyramids }, representations: { ...representations, pyramidsRepr } };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -66,7 +66,7 @@ export const DnatcoConfalPyramids = PluginBehavior.create<{ autoAttach: boolean,
|
||||
/* TODO: Implement this */
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
register(): void {
|
||||
this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
|
||||
|
||||
@@ -11,149 +11,248 @@ import { Location } from '../../../mol-model/location';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { ColorTheme } from '../../../mol-theme/color';
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { Color, ColorMap } from '../../../mol-util/color';
|
||||
import { getColorMapParams } from '../../../mol-util/color/params';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { TableLegend } from '../../../mol-util/legend';
|
||||
import { iterableToArray } from '../../../mol-data/util';
|
||||
import { ObjectKeys } from '../../../mol-util/type-helpers';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
const Description = 'Assigns colors to confal pyramids';
|
||||
|
||||
const DefaultClassColors = {
|
||||
A: 0xFFC1C1,
|
||||
B: 0xC8CFFF,
|
||||
BII: 0x0059DA,
|
||||
miB: 0x3BE8FB,
|
||||
Z: 0x01F60E,
|
||||
IC: 0xFA5CFB,
|
||||
OPN: 0xE90000,
|
||||
SYN: 0xFFFF01,
|
||||
N: 0xF2F2F2,
|
||||
};
|
||||
const ErrorColor = Color(0xFFA10A);
|
||||
|
||||
type ConformerClasses = 'A' | 'B' | 'BII' | 'miB' | 'Z' | 'IC' | 'OPN' | 'SYN' | 'N';
|
||||
const PyramidsColors = ColorMap({
|
||||
NANT_Upr: DefaultClassColors.N,
|
||||
NANT_Lwr: DefaultClassColors.N,
|
||||
AA00_Upr: DefaultClassColors.A,
|
||||
AA00_Lwr: DefaultClassColors.A,
|
||||
AA02_Upr: DefaultClassColors.A,
|
||||
AA02_Lwr: DefaultClassColors.A,
|
||||
AA03_Upr: DefaultClassColors.A,
|
||||
AA03_Lwr: DefaultClassColors.A,
|
||||
AA04_Upr: DefaultClassColors.A,
|
||||
AA04_Lwr: DefaultClassColors.A,
|
||||
AA08_Upr: DefaultClassColors.A,
|
||||
AA08_Lwr: DefaultClassColors.A,
|
||||
AA09_Upr: DefaultClassColors.A,
|
||||
AA09_Lwr: DefaultClassColors.A,
|
||||
AA01_Upr: DefaultClassColors.A,
|
||||
AA01_Lwr: DefaultClassColors.A,
|
||||
AA05_Upr: DefaultClassColors.A,
|
||||
AA05_Lwr: DefaultClassColors.A,
|
||||
AA06_Upr: DefaultClassColors.A,
|
||||
AA06_Lwr: DefaultClassColors.A,
|
||||
AA10_Upr: DefaultClassColors.A,
|
||||
AA10_Lwr: DefaultClassColors.A,
|
||||
AA11_Upr: DefaultClassColors.A,
|
||||
AA11_Lwr: DefaultClassColors.A,
|
||||
AA07_Upr: DefaultClassColors.A,
|
||||
AA07_Lwr: DefaultClassColors.A,
|
||||
AA12_Upr: DefaultClassColors.A,
|
||||
AA12_Lwr: DefaultClassColors.A,
|
||||
AA13_Upr: DefaultClassColors.A,
|
||||
AA13_Lwr: DefaultClassColors.A,
|
||||
AB01_Upr: DefaultClassColors.A,
|
||||
AB01_Lwr: DefaultClassColors.B,
|
||||
AB02_Upr: DefaultClassColors.A,
|
||||
AB02_Lwr: DefaultClassColors.B,
|
||||
AB03_Upr: DefaultClassColors.A,
|
||||
AB03_Lwr: DefaultClassColors.B,
|
||||
AB04_Upr: DefaultClassColors.A,
|
||||
AB04_Lwr: DefaultClassColors.B,
|
||||
AB05_Upr: DefaultClassColors.A,
|
||||
AB05_Lwr: DefaultClassColors.B,
|
||||
BA01_Upr: DefaultClassColors.B,
|
||||
BA01_Lwr: DefaultClassColors.A,
|
||||
BA05_Upr: DefaultClassColors.B,
|
||||
BA05_Lwr: DefaultClassColors.A,
|
||||
BA09_Upr: DefaultClassColors.B,
|
||||
BA09_Lwr: DefaultClassColors.A,
|
||||
BA08_Upr: DefaultClassColors.BII,
|
||||
BA08_Lwr: DefaultClassColors.A,
|
||||
BA10_Upr: DefaultClassColors.B,
|
||||
BA10_Lwr: DefaultClassColors.A,
|
||||
BA13_Upr: DefaultClassColors.BII,
|
||||
BA13_Lwr: DefaultClassColors.A,
|
||||
BA16_Upr: DefaultClassColors.BII,
|
||||
BA16_Lwr: DefaultClassColors.A,
|
||||
BA17_Upr: DefaultClassColors.BII,
|
||||
BA17_Lwr: DefaultClassColors.A,
|
||||
BB00_Upr: DefaultClassColors.B,
|
||||
BB00_Lwr: DefaultClassColors.B,
|
||||
BB01_Upr: DefaultClassColors.B,
|
||||
BB01_Lwr: DefaultClassColors.B,
|
||||
BB17_Upr: DefaultClassColors.B,
|
||||
BB17_Lwr: DefaultClassColors.B,
|
||||
BB02_Upr: DefaultClassColors.B,
|
||||
BB02_Lwr: DefaultClassColors.B,
|
||||
BB03_Upr: DefaultClassColors.B,
|
||||
BB03_Lwr: DefaultClassColors.B,
|
||||
BB11_Upr: DefaultClassColors.B,
|
||||
BB11_Lwr: DefaultClassColors.B,
|
||||
BB16_Upr: DefaultClassColors.B,
|
||||
BB16_Lwr: DefaultClassColors.B,
|
||||
BB04_Upr: DefaultClassColors.B,
|
||||
BB04_Lwr: DefaultClassColors.BII,
|
||||
BB05_Upr: DefaultClassColors.B,
|
||||
BB05_Lwr: DefaultClassColors.BII,
|
||||
BB07_Upr: DefaultClassColors.BII,
|
||||
BB07_Lwr: DefaultClassColors.BII,
|
||||
BB08_Upr: DefaultClassColors.BII,
|
||||
BB08_Lwr: DefaultClassColors.BII,
|
||||
BB10_Upr: DefaultClassColors.miB,
|
||||
BB10_Lwr: DefaultClassColors.miB,
|
||||
BB12_Upr: DefaultClassColors.miB,
|
||||
BB12_Lwr: DefaultClassColors.miB,
|
||||
BB13_Upr: DefaultClassColors.miB,
|
||||
BB13_Lwr: DefaultClassColors.miB,
|
||||
BB14_Upr: DefaultClassColors.miB,
|
||||
BB14_Lwr: DefaultClassColors.miB,
|
||||
BB15_Upr: DefaultClassColors.miB,
|
||||
BB15_Lwr: DefaultClassColors.miB,
|
||||
BB20_Upr: DefaultClassColors.miB,
|
||||
BB20_Lwr: DefaultClassColors.miB,
|
||||
IC01_Upr: DefaultClassColors.IC,
|
||||
IC01_Lwr: DefaultClassColors.IC,
|
||||
IC02_Upr: DefaultClassColors.IC,
|
||||
IC02_Lwr: DefaultClassColors.IC,
|
||||
IC03_Upr: DefaultClassColors.IC,
|
||||
IC03_Lwr: DefaultClassColors.IC,
|
||||
IC04_Upr: DefaultClassColors.IC,
|
||||
IC04_Lwr: DefaultClassColors.IC,
|
||||
IC05_Upr: DefaultClassColors.IC,
|
||||
IC05_Lwr: DefaultClassColors.IC,
|
||||
IC06_Upr: DefaultClassColors.IC,
|
||||
IC06_Lwr: DefaultClassColors.IC,
|
||||
IC07_Upr: DefaultClassColors.IC,
|
||||
IC07_Lwr: DefaultClassColors.IC,
|
||||
OP01_Upr: DefaultClassColors.OPN,
|
||||
OP01_Lwr: DefaultClassColors.OPN,
|
||||
OP02_Upr: DefaultClassColors.OPN,
|
||||
OP02_Lwr: DefaultClassColors.OPN,
|
||||
OP03_Upr: DefaultClassColors.OPN,
|
||||
OP03_Lwr: DefaultClassColors.OPN,
|
||||
OP04_Upr: DefaultClassColors.OPN,
|
||||
OP04_Lwr: DefaultClassColors.OPN,
|
||||
OP05_Upr: DefaultClassColors.OPN,
|
||||
OP05_Lwr: DefaultClassColors.OPN,
|
||||
OP06_Upr: DefaultClassColors.OPN,
|
||||
OP06_Lwr: DefaultClassColors.OPN,
|
||||
OP07_Upr: DefaultClassColors.OPN,
|
||||
OP07_Lwr: DefaultClassColors.OPN,
|
||||
OP08_Upr: DefaultClassColors.OPN,
|
||||
OP08_Lwr: DefaultClassColors.OPN,
|
||||
OP09_Upr: DefaultClassColors.OPN,
|
||||
OP09_Lwr: DefaultClassColors.OPN,
|
||||
OP10_Upr: DefaultClassColors.OPN,
|
||||
OP10_Lwr: DefaultClassColors.OPN,
|
||||
OP11_Upr: DefaultClassColors.OPN,
|
||||
OP11_Lwr: DefaultClassColors.OPN,
|
||||
OP12_Upr: DefaultClassColors.OPN,
|
||||
OP12_Lwr: DefaultClassColors.OPN,
|
||||
OP13_Upr: DefaultClassColors.OPN,
|
||||
OP13_Lwr: DefaultClassColors.OPN,
|
||||
OP14_Upr: DefaultClassColors.OPN,
|
||||
OP14_Lwr: DefaultClassColors.OPN,
|
||||
OP15_Upr: DefaultClassColors.OPN,
|
||||
OP15_Lwr: DefaultClassColors.OPN,
|
||||
OP16_Upr: DefaultClassColors.OPN,
|
||||
OP16_Lwr: DefaultClassColors.OPN,
|
||||
OP17_Upr: DefaultClassColors.OPN,
|
||||
OP17_Lwr: DefaultClassColors.OPN,
|
||||
OP18_Upr: DefaultClassColors.OPN,
|
||||
OP18_Lwr: DefaultClassColors.OPN,
|
||||
OP19_Upr: DefaultClassColors.OPN,
|
||||
OP19_Lwr: DefaultClassColors.OPN,
|
||||
OP20_Upr: DefaultClassColors.OPN,
|
||||
OP20_Lwr: DefaultClassColors.OPN,
|
||||
OP21_Upr: DefaultClassColors.OPN,
|
||||
OP21_Lwr: DefaultClassColors.OPN,
|
||||
OP22_Upr: DefaultClassColors.OPN,
|
||||
OP22_Lwr: DefaultClassColors.OPN,
|
||||
OP23_Upr: DefaultClassColors.OPN,
|
||||
OP23_Lwr: DefaultClassColors.OPN,
|
||||
OP24_Upr: DefaultClassColors.OPN,
|
||||
OP24_Lwr: DefaultClassColors.OPN,
|
||||
OP25_Upr: DefaultClassColors.OPN,
|
||||
OP25_Lwr: DefaultClassColors.OPN,
|
||||
OP26_Upr: DefaultClassColors.OPN,
|
||||
OP26_Lwr: DefaultClassColors.OPN,
|
||||
OP27_Upr: DefaultClassColors.OPN,
|
||||
OP27_Lwr: DefaultClassColors.OPN,
|
||||
OP28_Upr: DefaultClassColors.OPN,
|
||||
OP28_Lwr: DefaultClassColors.OPN,
|
||||
OP29_Upr: DefaultClassColors.OPN,
|
||||
OP29_Lwr: DefaultClassColors.OPN,
|
||||
OP30_Upr: DefaultClassColors.OPN,
|
||||
OP30_Lwr: DefaultClassColors.OPN,
|
||||
OP31_Upr: DefaultClassColors.OPN,
|
||||
OP31_Lwr: DefaultClassColors.OPN,
|
||||
OPS1_Upr: DefaultClassColors.OPN,
|
||||
OPS1_Lwr: DefaultClassColors.OPN,
|
||||
OP1S_Upr: DefaultClassColors.OPN,
|
||||
OP1S_Lwr: DefaultClassColors.SYN,
|
||||
AAS1_Upr: DefaultClassColors.SYN,
|
||||
AAS1_Lwr: DefaultClassColors.A,
|
||||
AB1S_Upr: DefaultClassColors.A,
|
||||
AB1S_Lwr: DefaultClassColors.SYN,
|
||||
AB2S_Upr: DefaultClassColors.A,
|
||||
AB2S_Lwr: DefaultClassColors.SYN,
|
||||
BB1S_Upr: DefaultClassColors.B,
|
||||
BB1S_Lwr: DefaultClassColors.SYN,
|
||||
BB2S_Upr: DefaultClassColors.B,
|
||||
BB2S_Lwr: DefaultClassColors.SYN,
|
||||
BBS1_Upr: DefaultClassColors.SYN,
|
||||
BBS1_Lwr: DefaultClassColors.B,
|
||||
ZZ01_Upr: DefaultClassColors.Z,
|
||||
ZZ01_Lwr: DefaultClassColors.Z,
|
||||
ZZ02_Upr: DefaultClassColors.Z,
|
||||
ZZ02_Lwr: DefaultClassColors.Z,
|
||||
ZZ1S_Upr: DefaultClassColors.Z,
|
||||
ZZ1S_Lwr: DefaultClassColors.SYN,
|
||||
ZZ2S_Upr: DefaultClassColors.Z,
|
||||
ZZ2S_Lwr: DefaultClassColors.SYN,
|
||||
ZZS1_Upr: DefaultClassColors.SYN,
|
||||
ZZS1_Lwr: DefaultClassColors.Z,
|
||||
ZZS2_Upr: DefaultClassColors.SYN,
|
||||
ZZS2_Lwr: DefaultClassColors.Z,
|
||||
});
|
||||
type PyramidsColors = typeof PyramidsColors;
|
||||
|
||||
const ColorMapping: ReadonlyMap<ConformerClasses, Color> = new Map([
|
||||
['A', Color(0xFFC1C1)],
|
||||
['B', Color(0xC8CFFF)],
|
||||
['BII', Color(0x0059DA)],
|
||||
['miB', Color(0x3BE8FB)],
|
||||
['Z', Color(0x01F60E)],
|
||||
['IC', Color(0xFA5CFB)],
|
||||
['OPN', Color(0xE90000)],
|
||||
['SYN', Color(0xFFFF01)],
|
||||
['N', Color(0xF2F2F2)],
|
||||
]);
|
||||
export const ConfalPyramidsColorThemeParams = {
|
||||
colors: PD.MappedStatic('default', {
|
||||
'default': PD.EmptyGroup(),
|
||||
'custom': PD.Group(getColorMapParams(PyramidsColors))
|
||||
}),
|
||||
};
|
||||
export type ConfalPyramidsColorThemeParams = typeof ConfalPyramidsColorThemeParams;
|
||||
|
||||
const NtCToClasses: ReadonlyMap<string, [ConformerClasses, ConformerClasses]> = new Map([
|
||||
['NANT', ['N', 'N']],
|
||||
['AA00', ['A', 'A']],
|
||||
['AA02', ['A', 'A']],
|
||||
['AA03', ['A', 'A']],
|
||||
['AA04', ['A', 'A']],
|
||||
['AA08', ['A', 'A']],
|
||||
['AA09', ['A', 'A']],
|
||||
['AA01', ['A', 'A']],
|
||||
['AA05', ['A', 'A']],
|
||||
['AA06', ['A', 'A']],
|
||||
['AA10', ['A', 'A']],
|
||||
['AA11', ['A', 'A']],
|
||||
['AA07', ['A', 'A']],
|
||||
['AA12', ['A', 'A']],
|
||||
['AA13', ['A', 'A']],
|
||||
['AB01', ['A', 'B']],
|
||||
['AB02', ['A', 'B']],
|
||||
['AB03', ['A', 'B']],
|
||||
['AB04', ['A', 'B']],
|
||||
['AB05', ['A', 'B']],
|
||||
['BA01', ['B', 'A']],
|
||||
['BA05', ['B', 'A']],
|
||||
['BA09', ['B', 'A']],
|
||||
['BA08', ['BII', 'A']],
|
||||
['BA10', ['B', 'A']],
|
||||
['BA13', ['BII', 'A']],
|
||||
['BA16', ['BII', 'A']],
|
||||
['BA17', ['BII', 'A']],
|
||||
['BB00', ['B', 'B']],
|
||||
['BB01', ['B', 'B']],
|
||||
['BB17', ['B', 'B']],
|
||||
['BB02', ['B', 'B']],
|
||||
['BB03', ['B', 'B']],
|
||||
['BB11', ['B', 'B']],
|
||||
['BB16', ['B', 'B']],
|
||||
['BB04', ['B', 'BII']],
|
||||
['BB05', ['B', 'BII']],
|
||||
['BB07', ['BII', 'BII']],
|
||||
['BB08', ['BII', 'BII']],
|
||||
['BB10', ['miB', 'miB']],
|
||||
['BB12', ['miB', 'miB']],
|
||||
['BB13', ['miB', 'miB']],
|
||||
['BB14', ['miB', 'miB']],
|
||||
['BB15', ['miB', 'miB']],
|
||||
['BB20', ['miB', 'miB']],
|
||||
['IC01', ['IC', 'IC']],
|
||||
['IC02', ['IC', 'IC']],
|
||||
['IC03', ['IC', 'IC']],
|
||||
['IC04', ['IC', 'IC']],
|
||||
['IC05', ['IC', 'IC']],
|
||||
['IC06', ['IC', 'IC']],
|
||||
['IC07', ['IC', 'IC']],
|
||||
['OP01', ['OPN', 'OPN']],
|
||||
['OP02', ['OPN', 'OPN']],
|
||||
['OP03', ['OPN', 'OPN']],
|
||||
['OP04', ['OPN', 'OPN']],
|
||||
['OP05', ['OPN', 'OPN']],
|
||||
['OP06', ['OPN', 'OPN']],
|
||||
['OP07', ['OPN', 'OPN']],
|
||||
['OP08', ['OPN', 'OPN']],
|
||||
['OP09', ['OPN', 'OPN']],
|
||||
['OP10', ['OPN', 'OPN']],
|
||||
['OP11', ['OPN', 'OPN']],
|
||||
['OP12', ['OPN', 'OPN']],
|
||||
['OP13', ['OPN', 'OPN']],
|
||||
['OP14', ['OPN', 'OPN']],
|
||||
['OP15', ['OPN', 'OPN']],
|
||||
['OP16', ['OPN', 'OPN']],
|
||||
['OP17', ['OPN', 'OPN']],
|
||||
['OP18', ['OPN', 'OPN']],
|
||||
['OP19', ['OPN', 'OPN']],
|
||||
['OP20', ['OPN', 'OPN']],
|
||||
['OP21', ['OPN', 'OPN']],
|
||||
['OP22', ['OPN', 'OPN']],
|
||||
['OP23', ['OPN', 'OPN']],
|
||||
['OP24', ['OPN', 'OPN']],
|
||||
['OP25', ['OPN', 'OPN']],
|
||||
['OP26', ['OPN', 'OPN']],
|
||||
['OP27', ['OPN', 'OPN']],
|
||||
['OP28', ['OPN', 'OPN']],
|
||||
['OP29', ['OPN', 'OPN']],
|
||||
['OP30', ['OPN', 'OPN']],
|
||||
['OP31', ['OPN', 'OPN']],
|
||||
['OPS1', ['OPN', 'OPN']],
|
||||
['OP1S', ['OPN', 'SYN']],
|
||||
['AAS1', ['SYN', 'A']],
|
||||
['AB1S', ['A', 'SYN']],
|
||||
['AB2S', ['A', 'SYN']],
|
||||
['BB1S', ['B', 'SYN']],
|
||||
['BB2S', ['B', 'SYN']],
|
||||
['BBS1', ['SYN', 'B']],
|
||||
['ZZ01', ['Z', 'Z']],
|
||||
['ZZ02', ['Z', 'Z']],
|
||||
['ZZ1S', ['Z', 'SYN']],
|
||||
['ZZ2S', ['Z', 'SYN']],
|
||||
['ZZS1', ['SYN', 'Z']],
|
||||
['ZZS2', ['SYN', 'Z']],
|
||||
]);
|
||||
|
||||
function getConformerColor(ntc: string, useLower: boolean): Color {
|
||||
const item = NtCToClasses.get(ntc);
|
||||
if (!item) return ErrorColor;
|
||||
return ColorMapping.get(useLower ? item[1] : item[0]) ?? ErrorColor;
|
||||
}
|
||||
|
||||
export const ConfalPyramidsColorThemeParams = {};
|
||||
export type ConfalPyramidsColorThemeParams = typeof ConfalPyramidsColorThemeParams
|
||||
export function getConfalPyramidsColorThemeParams(ctx: ThemeDataContext) {
|
||||
return ConfalPyramidsColorThemeParams; // TODO return copy
|
||||
return PD.clone(ConfalPyramidsColorThemeParams);
|
||||
}
|
||||
|
||||
export function ConfalPyramidsColorTheme(ctx: ThemeDataContext, props: PD.Values<ConfalPyramidsColorThemeParams>): ColorTheme<ConfalPyramidsColorThemeParams> {
|
||||
const colorMap = props.colors.name === 'default' ? PyramidsColors : props.colors.params;
|
||||
|
||||
function color(location: Location, isSecondary: boolean): Color {
|
||||
if (CPT.isLocation(location)) {
|
||||
const { pyramid, isLower } = location.data;
|
||||
return getConformerColor(pyramid.NtC, isLower);
|
||||
const key = pyramid.NtC + `_${isLower ? 'Lwr' : 'Upr'}` as keyof PyramidsColors;
|
||||
return colorMap[key] ?? ErrorColor;
|
||||
}
|
||||
|
||||
return DefaultColor;
|
||||
return ErrorColor;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -162,12 +261,7 @@ export function ConfalPyramidsColorTheme(ctx: ThemeDataContext, props: PD.Values
|
||||
color,
|
||||
props,
|
||||
description: Description,
|
||||
legend: TableLegend(iterableToArray(ColorMapping.entries()).map(([conformer, color]) => {
|
||||
return [conformer, color] as [string, Color];
|
||||
}).concat([
|
||||
[ 'Error', ErrorColor ],
|
||||
[ 'Unknown', DefaultColor ]
|
||||
]))
|
||||
legend: TableLegend(ObjectKeys(colorMap).map(k => [k.replace('_', ' '), colorMap[k]] as [string, Color]).concat([['Error', ErrorColor]])),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -123,10 +123,8 @@ function createPyramidsFromCif(model: Model,
|
||||
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
const model_num = PDB_model_number.value(i);
|
||||
if (model_num !== model.modelNum) {
|
||||
if (model_num !== model.modelNum)
|
||||
hasMultipleModels = true;
|
||||
continue; // We are only interested in data for the current model
|
||||
}
|
||||
|
||||
const { _NtC, _confal_score } = getNtCAndConfalScore(id.value(i), i, stepsSummary);
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ import { Structure, StructureProperties, Unit } from '../../../mol-model/structu
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
|
||||
import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder, UnitsRepresentation } from '../../../mol-repr/structure/representation';
|
||||
import { StructureGroup, UnitsMeshParams, UnitsMeshVisual, UnitsVisual } from '../../../mol-repr/structure/units-visual';
|
||||
import { UnitsMeshParams, UnitsMeshVisual, UnitsVisual } from '../../../mol-repr/structure/units-visual';
|
||||
import { VisualUpdateState } from '../../../mol-repr/util';
|
||||
import { VisualContext } from '../../../mol-repr/visual';
|
||||
import { getAltResidueLociFromId } from '../../../mol-repr/structure/visual/util/common';
|
||||
import { getAltResidueLociFromId, StructureGroup } from '../../../mol-repr/structure/visual/util/common';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Theme, ThemeRegistryContext } from '../../../mol-theme/theme';
|
||||
import { NullLocation } from '../../../mol-model/location';
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { ConfalPyramidsProvider } from './property';
|
||||
import { ConfalPyramidsTypes as CPT } from './types';
|
||||
import { OrderedSet, Segmentation } from '../../../mol-data/int';
|
||||
import { Segmentation } from '../../../mol-data/int';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { ChainIndex, ElementIndex, ResidueIndex, Structure, StructureElement, StructureProperties, Unit } from '../../../mol-model/structure';
|
||||
|
||||
@@ -63,15 +63,12 @@ export namespace ConfalPyramidsUtil {
|
||||
return prop.data.hasMultipleModels;
|
||||
}
|
||||
|
||||
function getPossibleAltIdsIndices(eIFirst: ElementIndex, eILast: ElementIndex, structure: Structure, unit: Unit.Atomic): string[] {
|
||||
const loc = StructureElement.Location.create(structure, unit, -1 as ElementIndex);
|
||||
|
||||
const uIFirst = OrderedSet.indexOf(unit.elements, eIFirst);
|
||||
const uILast = OrderedSet.indexOf(unit.elements, eILast);
|
||||
|
||||
function getPossibleAltIds(residue: Residue, structure: Structure, unit: Unit.Atomic): string[] {
|
||||
const possibleAltIds: string[] = [];
|
||||
for (let uI = uIFirst; uI <= uILast; uI++) {
|
||||
loc.element = unit.elements[uI];
|
||||
|
||||
const loc = StructureElement.Location.create(structure, unit, -1 as ElementIndex);
|
||||
for (let rI = residue.start; rI <= residue.end - 1; rI++) {
|
||||
loc.element = unit.elements[rI];
|
||||
const altId = StructureProperties.atom.label_alt_id(loc);
|
||||
if (altId !== '' && !possibleAltIds.includes(altId)) possibleAltIds.push(altId);
|
||||
}
|
||||
@@ -79,10 +76,6 @@ export namespace ConfalPyramidsUtil {
|
||||
return possibleAltIds;
|
||||
}
|
||||
|
||||
function getPossibleAltIdsResidue(residue: Residue, structure: Structure, unit: Unit.Atomic): string[] {
|
||||
return getPossibleAltIdsIndices(unit.elements[residue.start], unit.elements[residue.end - 1], structure, unit);
|
||||
}
|
||||
|
||||
class Utility {
|
||||
protected getPyramidByName(name: string): { pyramid: CPT.Pyramid | undefined, index: number } {
|
||||
const index = this.data.names.get(name);
|
||||
@@ -95,7 +88,7 @@ export namespace ConfalPyramidsUtil {
|
||||
const first = residueInfoFromLocation(locFirst);
|
||||
const second = residueInfoFromLocation(locSecond);
|
||||
const model_id = this.hasMultipleModels ? `-m${modelNum}` : '';
|
||||
const alt_id_1 = fakeAltId_1 !== '' ? `.${fakeAltId_1}` : (first.alt_id.length ? `.${first.alt_id}` : '');
|
||||
const alt_id_1 = fakeAltId_1 !== '' ? `.${fakeAltId_1}` : (first.alt_id.length ? `.${first.alt_id}` : '');
|
||||
const alt_id_2 = fakeAltId_2 !== '' ? `.${fakeAltId_2}` : (second.alt_id.length ? `.${second.alt_id}` : '');
|
||||
const ins_code_1 = first.ins_code.length ? `.${first.ins_code}` : '';
|
||||
const ins_code_2 = second.ins_code.length ? `.${second.ins_code}` : '';
|
||||
@@ -114,7 +107,7 @@ export namespace ConfalPyramidsUtil {
|
||||
this.modelNum = unit.model.modelNum;
|
||||
}
|
||||
|
||||
protected readonly data: CPT.PyramidsData
|
||||
protected readonly data: CPT.PyramidsData;
|
||||
protected readonly hasMultipleModels: boolean;
|
||||
protected readonly entryId: string;
|
||||
protected readonly modelNum: number;
|
||||
@@ -122,19 +115,22 @@ export namespace ConfalPyramidsUtil {
|
||||
|
||||
export class UnitWalker extends Utility {
|
||||
private getAtomIndices(names: string[], residue: Residue): ElementIndex[] {
|
||||
let rI = residue.start;
|
||||
const rILast = residue.end - 1;
|
||||
const indices: ElementIndex[] = [];
|
||||
|
||||
for (; rI !== rILast; rI++) {
|
||||
const eI = this.unit.elements[rI];
|
||||
const loc = StructureElement.Location.create(this.structure, this.unit, eI);
|
||||
const loc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex);
|
||||
for (let rI = residue.start; rI <= residue.end - 1; rI++) {
|
||||
loc.element = this.unit.elements[rI];
|
||||
const thisName = StructureProperties.atom.label_atom_id(loc);
|
||||
if (names.includes(thisName)) indices.push(eI);
|
||||
if (names.includes(thisName)) indices.push(loc.element);
|
||||
}
|
||||
|
||||
if (indices.length === 0)
|
||||
throw new Error(`Element ${name} not found on residue ${residue.index}`);
|
||||
if (indices.length === 0) {
|
||||
let namesStr = '';
|
||||
for (const n of names)
|
||||
namesStr += `${n} `;
|
||||
|
||||
throw new Error(`Element [${namesStr}] not found on residue ${residue.index}`);
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
@@ -257,12 +253,12 @@ export namespace ConfalPyramidsUtil {
|
||||
}
|
||||
|
||||
private step(residue: Residue): { firstAtoms: FirstResidueAtoms[], secondAtoms: SecondResidueAtoms[] } {
|
||||
const firstPossibleAltIds = getPossibleAltIdsResidue(residue, this.structure, this.unit);
|
||||
const firstPossibleAltIds = getPossibleAltIds(residue, this.structure, this.unit);
|
||||
const firstAtoms = this.processFirstResidue(residue, firstPossibleAltIds);
|
||||
|
||||
residue = this.residueIt.move();
|
||||
|
||||
const secondPossibleAltIds = getPossibleAltIdsResidue(residue, this.structure, this.unit);
|
||||
const secondPossibleAltIds = getPossibleAltIds(residue, this.structure, this.unit);
|
||||
const secondAtoms = this.processSecondResidue(residue, secondPossibleAltIds);
|
||||
|
||||
return { firstAtoms, secondAtoms };
|
||||
|
||||
@@ -61,7 +61,7 @@ function getColumns(block: G3dDataBlock) {
|
||||
objectForEach(data, (hs, h) => {
|
||||
objectForEach(hs, (chs, ch) => {
|
||||
const entity_id = `${ch}-${h}`;
|
||||
const l = chs.start.length;
|
||||
const l = chs.start.length;
|
||||
if (l === 0) return;
|
||||
|
||||
let x = chs.x[0];
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
*/
|
||||
|
||||
import { getStyle } from '../../mol-gl/renderer';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { PluginComponent } from '../../mol-plugin-state/component';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
@@ -13,22 +12,24 @@ import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { StateSelection } from '../../mol-state';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { SetUtils } from '../../mol-util/set';
|
||||
import { ObjExporter } from './obj-exporter';
|
||||
import { GlbExporter } from './glb-exporter';
|
||||
import { ObjExporter } from './obj-exporter';
|
||||
import { StlExporter } from './stl-exporter';
|
||||
import { UsdzExporter } from './usdz-exporter';
|
||||
|
||||
export const GeometryParams = {
|
||||
format: PD.Select('glb', [
|
||||
['glb', 'glTF 2.0 Binary (.glb)'],
|
||||
['stl', 'Stl (.stl)'],
|
||||
['obj', 'Wavefront (.obj)']
|
||||
['obj', 'Wavefront (.obj)'],
|
||||
['usdz', 'Universal Scene Description (.usdz)']
|
||||
])
|
||||
};
|
||||
|
||||
export class GeometryControls extends PluginComponent {
|
||||
readonly behaviors = {
|
||||
params: this.ev.behavior<PD.Values<typeof GeometryParams>>(PD.getDefaultValues(GeometryParams))
|
||||
}
|
||||
};
|
||||
|
||||
private getFilename() {
|
||||
const models = this.plugin.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Model)).map(s => s.obj!.data);
|
||||
@@ -44,12 +45,12 @@ export class GeometryControls extends PluginComponent {
|
||||
const renderObjects = this.plugin.canvas3d?.getRenderObjects()!;
|
||||
const filename = this.getFilename();
|
||||
|
||||
const boundingBox = Box3D.fromSphere3D(Box3D(), this.plugin.canvas3d?.boundingSphereVisible!);
|
||||
let renderObjectExporter: GlbExporter | ObjExporter | StlExporter;
|
||||
const boundingSphere = this.plugin.canvas3d?.boundingSphereVisible!;
|
||||
const boundingBox = Box3D.fromSphere3D(Box3D(), boundingSphere);
|
||||
let renderObjectExporter: GlbExporter | ObjExporter | StlExporter | UsdzExporter;
|
||||
switch (this.behaviors.params.value.format) {
|
||||
case 'glb':
|
||||
const style = getStyle(this.plugin.canvas3d?.props.renderer.style!);
|
||||
renderObjectExporter = new GlbExporter(style, boundingBox);
|
||||
renderObjectExporter = new GlbExporter(boundingBox);
|
||||
break;
|
||||
case 'obj':
|
||||
renderObjectExporter = new ObjExporter(filename, boundingBox);
|
||||
@@ -57,6 +58,9 @@ export class GeometryControls extends PluginComponent {
|
||||
case 'stl':
|
||||
renderObjectExporter = new StlExporter(boundingBox);
|
||||
break;
|
||||
case 'usdz':
|
||||
renderObjectExporter = new UsdzExporter(boundingBox, boundingSphere.radius);
|
||||
break;
|
||||
default: throw new Error('Unsupported format.');
|
||||
}
|
||||
|
||||
@@ -71,7 +75,7 @@ export class GeometryControls extends PluginComponent {
|
||||
filename: filename + '.' + renderObjectExporter.fileExtension
|
||||
};
|
||||
} catch (e) {
|
||||
this.plugin.log.error('' + e);
|
||||
this.plugin.log.error('Error during geometry export');
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { BaseValues } from '../../mol-gl/renderable/schema';
|
||||
import { Style } from '../../mol-gl/renderer';
|
||||
import { asciiWrite } from '../../mol-io/common/ascii';
|
||||
import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
@@ -15,7 +14,7 @@ import { RuntimeContext } from '../../mol-task';
|
||||
import { Color } from '../../mol-util/color/color';
|
||||
import { fillSerial } from '../../mol-util/array';
|
||||
import { NumberArray } from '../../mol-util/type-helpers';
|
||||
import { MeshExporter, AddMeshInput } from './mesh-exporter';
|
||||
import { MeshExporter, AddMeshInput, MeshGeoData } from './mesh-exporter';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3fromArray = Vec3.fromArray;
|
||||
@@ -30,6 +29,12 @@ const FLOAT = 5126;
|
||||
const ARRAY_BUFFER = 34962;
|
||||
const ELEMENT_ARRAY_BUFFER = 34963;
|
||||
|
||||
const GLTF_MAGIC_BYTE = 0x46546C67;
|
||||
const JSON_CHUNK_TYPE = 0x4E4F534A;
|
||||
const BIN_CHUNK_TYPE = 0x004E4942;
|
||||
const JSON_PAD_CHAR = 0x20;
|
||||
const BIN_PAD_CHAR = 0x00;
|
||||
|
||||
export type GlbData = {
|
||||
glb: Uint8Array
|
||||
}
|
||||
@@ -38,6 +43,8 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
readonly fileExtension = 'glb';
|
||||
private nodes: Record<string, any>[] = [];
|
||||
private meshes: Record<string, any>[] = [];
|
||||
private materials: Record<string, any>[] = [];
|
||||
private materialMap = new Map<string, number>();
|
||||
private accessors: Record<string, any>[] = [];
|
||||
private bufferViews: Record<string, any>[] = [];
|
||||
private binaryBuffer: ArrayBuffer[] = [];
|
||||
@@ -53,7 +60,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
max[j] = Math.max(a[i + j], max[j]);
|
||||
}
|
||||
}
|
||||
return [ min, max ];
|
||||
return [min, max];
|
||||
}
|
||||
|
||||
private addBuffer(buffer: ArrayBuffer, componentType: number, type: string, count: number, target: number, min?: any, max?: any, normalized?: boolean) {
|
||||
@@ -108,7 +115,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
indexArray = indices!.slice(0, drawCount);
|
||||
}
|
||||
|
||||
const [ vertexMin, vertexMax ] = GlbExporter.vec3MinMax(vertexArray);
|
||||
const [vertexMin, vertexMax] = GlbExporter.vec3MinMax(vertexArray);
|
||||
|
||||
let vertexBuffer = vertexArray.buffer;
|
||||
let normalBuffer = normalArray.buffer;
|
||||
@@ -126,57 +133,17 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
};
|
||||
}
|
||||
|
||||
private addColorBuffer(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array) {
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const uColor = values.uColor.ref.value;
|
||||
const tColor = values.tColor.ref.value.array;
|
||||
private addColorBuffer(geoData: MeshGeoData, interpolatedColors: Uint8Array | undefined, interpolatedOverpaint: Uint8Array | undefined, interpolatedTransparency: Uint8Array | undefined) {
|
||||
const { values, vertexCount } = geoData;
|
||||
const uAlpha = values.uAlpha.ref.value;
|
||||
const dTransparency = values.dTransparency.ref.value;
|
||||
const tTransparency = values.tTransparency.ref.value;
|
||||
|
||||
const colorArray = new Uint8Array(vertexCount * 4);
|
||||
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
let color: Color;
|
||||
switch (colorType) {
|
||||
case 'uniform':
|
||||
color = Color.fromNormalizedArray(uColor, 0);
|
||||
break;
|
||||
case 'instance':
|
||||
color = Color.fromArray(tColor, instanceIndex * 3);
|
||||
break;
|
||||
case 'group': {
|
||||
const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
|
||||
color = Color.fromArray(tColor, group * 3);
|
||||
break;
|
||||
}
|
||||
case 'groupInstance': {
|
||||
const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
|
||||
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
|
||||
break;
|
||||
}
|
||||
case 'vertex':
|
||||
color = Color.fromArray(tColor, i * 3);
|
||||
break;
|
||||
case 'vertexInstance':
|
||||
color = Color.fromArray(tColor, (instanceIndex * vertexCount + i) * 3);
|
||||
break;
|
||||
case 'volume':
|
||||
color = Color.fromArray(interpolatedColors!, i * 3);
|
||||
break;
|
||||
case 'volumeInstance':
|
||||
color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + i) * 3);
|
||||
break;
|
||||
default: throw new Error('Unsupported color type.');
|
||||
}
|
||||
let color = GlbExporter.getColor(i, geoData, interpolatedColors, interpolatedOverpaint);
|
||||
|
||||
let alpha = uAlpha;
|
||||
if (dTransparency) {
|
||||
const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
|
||||
const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
|
||||
alpha *= 1 - transparency;
|
||||
}
|
||||
const transparency = GlbExporter.getTransparency(i, geoData, interpolatedTransparency);
|
||||
const alpha = uAlpha * (1 - transparency);
|
||||
|
||||
color = Color.sRGBToLinear(color);
|
||||
Color.toArray(color, colorArray, i * 4);
|
||||
@@ -191,20 +158,53 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
return this.addBuffer(colorBuffer, UNSIGNED_BYTE, 'VEC4', vertexCount, ARRAY_BUFFER, undefined, undefined, true);
|
||||
}
|
||||
|
||||
private addMaterial(metalness: number, roughness: number) {
|
||||
const hash = `${metalness}|${roughness}`;
|
||||
if (!this.materialMap.has(hash)) {
|
||||
this.materialMap.set(hash, this.materials.length);
|
||||
this.materials.push({
|
||||
pbrMetallicRoughness: {
|
||||
baseColorFactor: [1, 1, 1, 1],
|
||||
metallicFactor: metalness,
|
||||
roughnessFactor: roughness
|
||||
}
|
||||
});
|
||||
}
|
||||
return this.materialMap.get(hash)!;
|
||||
}
|
||||
|
||||
protected async addMeshWithColors(input: AddMeshInput) {
|
||||
const { mesh, values, isGeoTexture, webgl, ctx } = input;
|
||||
|
||||
const t = Mat4();
|
||||
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const overpaintType = values.dOverpaintType.ref.value;
|
||||
const transparencyType = values.dTransparencyType.ref.value;
|
||||
const dTransparency = values.dTransparency.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
const metalness = values.uMetalness.ref.value;
|
||||
const roughness = values.uRoughness.ref.value;
|
||||
|
||||
let interpolatedColors: Uint8Array;
|
||||
const material = this.addMaterial(metalness, roughness);
|
||||
|
||||
let interpolatedColors: Uint8Array | undefined;
|
||||
if (colorType === 'volume' || colorType === 'volumeInstance') {
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
interpolatedColors = GlbExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
|
||||
interpolatedColors = GlbExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
|
||||
}
|
||||
|
||||
let interpolatedOverpaint: Uint8Array | undefined;
|
||||
if (overpaintType === 'volumeInstance') {
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
interpolatedOverpaint = GlbExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
|
||||
}
|
||||
|
||||
let interpolatedTransparency: Uint8Array | undefined;
|
||||
if (transparencyType === 'volumeInstance') {
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
interpolatedTransparency = GlbExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
|
||||
}
|
||||
|
||||
// instancing
|
||||
@@ -235,7 +235,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
|
||||
// create a color buffer if needed
|
||||
if (instanceIndex === 0 || !sameColorBuffer) {
|
||||
colorAccessorIndex = this.addColorBuffer(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors!);
|
||||
colorAccessorIndex = this.addColorBuffer({ values, groups, vertexCount, instanceIndex, isGeoTexture }, interpolatedColors, interpolatedOverpaint, interpolatedTransparency);
|
||||
}
|
||||
|
||||
// glTF mesh
|
||||
@@ -248,7 +248,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
COLOR_0: colorAccessorIndex!
|
||||
},
|
||||
indices: indexAccessorIndex,
|
||||
material: 0
|
||||
material
|
||||
}]
|
||||
});
|
||||
}
|
||||
@@ -264,7 +264,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
}
|
||||
}
|
||||
|
||||
getData() {
|
||||
async getData() {
|
||||
const binaryBufferLength = this.byteOffset;
|
||||
|
||||
const gltf = {
|
||||
@@ -282,13 +282,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
}],
|
||||
bufferViews: this.bufferViews,
|
||||
accessors: this.accessors,
|
||||
materials: [{
|
||||
pbrMetallicRoughness: {
|
||||
baseColorFactor: [1, 1, 1, 1],
|
||||
metallicFactor: this.style.metalness,
|
||||
roughnessFactor: this.style.roughness
|
||||
}
|
||||
}]
|
||||
materials: this.materials
|
||||
};
|
||||
|
||||
const createChunk = (chunkType: number, data: ArrayBuffer[], byteLength: number, padChar: number): [ArrayBuffer[], number] => {
|
||||
@@ -307,19 +301,19 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
if (padding) {
|
||||
chunk.push(padding.buffer);
|
||||
}
|
||||
return [ chunk, 8 + byteLength ];
|
||||
return [chunk, 8 + byteLength];
|
||||
};
|
||||
const jsonString = JSON.stringify(gltf);
|
||||
const jsonBuffer = new Uint8Array(jsonString.length);
|
||||
asciiWrite(jsonBuffer, jsonString);
|
||||
|
||||
const [ jsonChunk, jsonChunkLength ] = createChunk(0x4E4F534A, [jsonBuffer.buffer], jsonBuffer.length, 0x20);
|
||||
const [ binaryChunk, binaryChunkLength ] = createChunk(0x004E4942, this.binaryBuffer, binaryBufferLength, 0x00);
|
||||
const [jsonChunk, jsonChunkLength] = createChunk(JSON_CHUNK_TYPE, [jsonBuffer.buffer], jsonBuffer.length, JSON_PAD_CHAR);
|
||||
const [binaryChunk, binaryChunkLength] = createChunk(BIN_CHUNK_TYPE, this.binaryBuffer, binaryBufferLength, BIN_PAD_CHAR);
|
||||
|
||||
const glbBufferLength = 12 + jsonChunkLength + binaryChunkLength;
|
||||
const header = new ArrayBuffer(12);
|
||||
const headerDataView = new DataView(header);
|
||||
headerDataView.setUint32(0, 0x46546C67, true); // magic number "glTF"
|
||||
headerDataView.setUint32(0, GLTF_MAGIC_BYTE, true); // magic number "glTF"
|
||||
headerDataView.setUint32(4, 2, true); // version
|
||||
headerDataView.setUint32(8, glbBufferLength, true); // length
|
||||
const glbBuffer = [header, ...jsonChunk, ...binaryChunk];
|
||||
@@ -334,10 +328,10 @@ export class GlbExporter extends MeshExporter<GlbData> {
|
||||
}
|
||||
|
||||
async getBlob(ctx: RuntimeContext) {
|
||||
return new Blob([this.getData().glb], { type: 'model/gltf-binary' });
|
||||
return new Blob([(await this.getData()).glb], { type: 'model/gltf-binary' });
|
||||
}
|
||||
|
||||
constructor(private style: Style, boundingBox: Box3D) {
|
||||
constructor(boundingBox: Box3D) {
|
||||
super();
|
||||
const tmpV = Vec3();
|
||||
Vec3.add(tmpV, boundingBox.min, boundingBox.max);
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { sort, arraySwap } from '../../mol-data/util';
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
import { MeshValues } from '../../mol-gl/renderable/mesh';
|
||||
import { LinesValues } from '../../mol-gl/renderable/lines';
|
||||
@@ -22,8 +24,10 @@ import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { sizeDataFactor } from '../../mol-geo/geometry/size-data';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { decodeFloatRGB } from '../../mol-util/float-packing';
|
||||
import { Color } from '../../mol-util/color/color';
|
||||
import { unpackRGBToInt } from '../../mol-util/number-packing';
|
||||
import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
|
||||
import { readAlphaTexture, readTexture } from '../../mol-gl/compute/util';
|
||||
|
||||
const GeoExportName = 'geo-export';
|
||||
|
||||
@@ -46,6 +50,14 @@ export interface AddMeshInput {
|
||||
ctx: RuntimeContext
|
||||
}
|
||||
|
||||
export type MeshGeoData = {
|
||||
values: BaseValues,
|
||||
groups: Float32Array | Uint8Array,
|
||||
vertexCount: number,
|
||||
instanceIndex: number,
|
||||
isGeoTexture: boolean
|
||||
}
|
||||
|
||||
export abstract class MeshExporter<D extends RenderObjectExportData> implements RenderObjectExporter<D> {
|
||||
abstract readonly fileExtension: string;
|
||||
|
||||
@@ -53,7 +65,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
|
||||
const r = tSize.array[i * 3];
|
||||
const g = tSize.array[i * 3 + 1];
|
||||
const b = tSize.array[i * 3 + 2];
|
||||
return decodeFloatRGB(r, g, b) / sizeDataFactor;
|
||||
return unpackRGBToInt(r, g, b) / sizeDataFactor;
|
||||
}
|
||||
|
||||
private static getSize(values: BaseValues & SizeValues, instanceIndex: number, group: number): number {
|
||||
@@ -83,34 +95,115 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
|
||||
const g = groups[i4 + 1];
|
||||
const b = groups[i4 + 2];
|
||||
if (groups instanceof Float32Array) {
|
||||
return decodeFloatRGB(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
|
||||
return unpackRGBToInt(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
|
||||
}
|
||||
return decodeFloatRGB(r, g, b);
|
||||
return unpackRGBToInt(r, g, b);
|
||||
}
|
||||
|
||||
protected static getInterpolatedColors(vertices: Float32Array, vertexCount: number, values: BaseValues, stride: number, colorType: 'volume' | 'volumeInstance', webgl: WebGLContext) {
|
||||
protected static getInterpolatedColors(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volume' | 'volumeInstance' }) {
|
||||
const { values, vertexCount, vertices, colorType, stride } = input;
|
||||
const colorGridTransform = values.uColorGridTransform.ref.value;
|
||||
const colorGridDim = values.uColorGridDim.ref.value;
|
||||
const colorTexDim = values.uColorTexDim.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
|
||||
if (!webgl.namedFramebuffers[GeoExportName]) {
|
||||
webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
|
||||
}
|
||||
const framebuffer = webgl.namedFramebuffers[GeoExportName];
|
||||
|
||||
const [ width, height ] = colorTexDim;
|
||||
const colorGrid = new Uint8Array(width * height * 4);
|
||||
|
||||
framebuffer.bind();
|
||||
values.tColorGrid.ref.value.attachFramebuffer(framebuffer, 0);
|
||||
webgl.readPixels(0, 0, width, height, colorGrid);
|
||||
|
||||
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4 });
|
||||
const colorGrid = readTexture(webgl, values.tColorGrid.ref.value).array;
|
||||
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4, outputStride: 3 });
|
||||
return interpolated.array;
|
||||
}
|
||||
|
||||
protected static getInterpolatedOverpaint(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) {
|
||||
const { values, vertexCount, vertices, colorType, stride } = input;
|
||||
const overpaintGridTransform = values.uOverpaintGridTransform.ref.value;
|
||||
const overpaintGridDim = values.uOverpaintGridDim.ref.value;
|
||||
const overpaintTexDim = values.uOverpaintTexDim.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
|
||||
const overpaintGrid = readTexture(webgl, values.tOverpaintGrid.ref.value).array;
|
||||
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: overpaintGrid, gridDim: overpaintGridDim, gridTexDim: overpaintTexDim, gridTransform: overpaintGridTransform, vertexStride: stride, colorStride: 4, outputStride: 4 });
|
||||
return interpolated.array;
|
||||
}
|
||||
|
||||
protected static getInterpolatedTransparency(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) {
|
||||
const { values, vertexCount, vertices, colorType, stride } = input;
|
||||
const transparencyGridTransform = values.uTransparencyGridTransform.ref.value;
|
||||
const transparencyGridDim = values.uTransparencyGridDim.ref.value;
|
||||
const transparencyTexDim = values.uTransparencyTexDim.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
|
||||
const transparencyGrid = readAlphaTexture(webgl, values.tTransparencyGrid.ref.value).array;
|
||||
const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: transparencyGrid, gridDim: transparencyGridDim, gridTexDim: transparencyTexDim, gridTransform: transparencyGridTransform, vertexStride: stride, colorStride: 4, outputStride: 1, itemOffset: 3 });
|
||||
|
||||
return interpolated.array;
|
||||
}
|
||||
|
||||
protected static quantizeColors(colorArray: Uint8Array, vertexCount: number) {
|
||||
if (vertexCount <= 1024) return;
|
||||
const rgb = Vec3();
|
||||
const min = Vec3();
|
||||
const max = Vec3();
|
||||
const sum = Vec3();
|
||||
const colorMap = new Map<Color, Color>();
|
||||
const colorComparers = [
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[0] - Color.toVec3(rgb, colors[j])[0]),
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[1] - Color.toVec3(rgb, colors[j])[1]),
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[2] - Color.toVec3(rgb, colors[j])[2]),
|
||||
];
|
||||
|
||||
const medianCut = (colors: Color[], l: number, r: number, depth: number) => {
|
||||
if (l > r) return;
|
||||
if (l === r || depth >= 10) {
|
||||
// Find the average color.
|
||||
Vec3.set(sum, 0, 0, 0);
|
||||
for (let i = l; i <= r; ++i) {
|
||||
Color.toVec3(rgb, colors[i]);
|
||||
Vec3.add(sum, sum, rgb);
|
||||
}
|
||||
Vec3.round(rgb, Vec3.scale(rgb, sum, 1 / (r - l + 1)));
|
||||
const averageColor = Color.fromArray(rgb, 0);
|
||||
for (let i = l; i <= r; ++i) colorMap.set(colors[i], averageColor);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the color channel with the greatest range.
|
||||
Vec3.set(min, 255, 255, 255);
|
||||
Vec3.set(max, 0, 0, 0);
|
||||
for (let i = l; i <= r; ++i) {
|
||||
Color.toVec3(rgb, colors[i]);
|
||||
for (let j = 0; j < 3; ++j) {
|
||||
Vec3.min(min, min, rgb);
|
||||
Vec3.max(max, max, rgb);
|
||||
}
|
||||
}
|
||||
let k = 0;
|
||||
if (max[1] - min[1] > max[k] - min[k]) k = 1;
|
||||
if (max[2] - min[2] > max[k] - min[k]) k = 2;
|
||||
|
||||
sort(colors, l, r + 1, colorComparers[k], arraySwap);
|
||||
|
||||
const m = (l + r) >> 1;
|
||||
medianCut(colors, l, m, depth + 1);
|
||||
medianCut(colors, m + 1, r, depth + 1);
|
||||
};
|
||||
|
||||
// Create an array of unique colors and use the median cut algorithm.
|
||||
const colorSet = new Set<Color>();
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
colorSet.add(Color.fromArray(colorArray, i * 3));
|
||||
}
|
||||
const colors = Array.from(colorSet);
|
||||
medianCut(colors, 0, colors.length - 1, 0);
|
||||
|
||||
// Map actual colors to quantized colors.
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
const color = colorMap.get(Color.fromArray(colorArray, i * 3));
|
||||
Color.toArray(color!, colorArray, i * 3);
|
||||
}
|
||||
}
|
||||
|
||||
protected static getInstance(input: AddMeshInput, instanceIndex: number) {
|
||||
const { mesh, meshes } = input;
|
||||
if (mesh !== undefined) {
|
||||
@@ -128,6 +221,114 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
|
||||
}
|
||||
}
|
||||
|
||||
protected static getColor(vertexIndex: number, geoData: MeshGeoData, interpolatedColors?: Uint8Array, interpolatedOverpaint?: Uint8Array): Color {
|
||||
const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const uColor = values.uColor.ref.value;
|
||||
const tColor = values.tColor.ref.value.array;
|
||||
const overpaintType = values.dOverpaintType.ref.value;
|
||||
const dOverpaint = values.dOverpaint.ref.value;
|
||||
const tOverpaint = values.tOverpaint.ref.value.array;
|
||||
|
||||
let color: Color;
|
||||
switch (colorType) {
|
||||
case 'uniform':
|
||||
color = Color.fromNormalizedArray(uColor, 0);
|
||||
break;
|
||||
case 'instance':
|
||||
color = Color.fromArray(tColor, instanceIndex * 3);
|
||||
break;
|
||||
case 'group': {
|
||||
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
|
||||
color = Color.fromArray(tColor, group * 3);
|
||||
break;
|
||||
}
|
||||
case 'groupInstance': {
|
||||
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
|
||||
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
|
||||
break;
|
||||
}
|
||||
case 'vertex':
|
||||
color = Color.fromArray(tColor, vertexIndex * 3);
|
||||
break;
|
||||
case 'vertexInstance':
|
||||
color = Color.fromArray(tColor, (instanceIndex * vertexCount + vertexIndex) * 3);
|
||||
break;
|
||||
case 'volume':
|
||||
color = Color.fromArray(interpolatedColors!, vertexIndex * 3);
|
||||
break;
|
||||
case 'volumeInstance':
|
||||
color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + vertexIndex) * 3);
|
||||
break;
|
||||
default: throw new Error('Unsupported color type.');
|
||||
}
|
||||
|
||||
if (dOverpaint) {
|
||||
let overpaintColor: Color;
|
||||
let overpaintAlpha: number;
|
||||
switch (overpaintType) {
|
||||
case 'groupInstance': {
|
||||
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
|
||||
const idx = (instanceIndex * groupCount + group) * 4;
|
||||
overpaintColor = Color.fromArray(tOverpaint, idx);
|
||||
overpaintAlpha = tOverpaint[idx + 3] / 255;
|
||||
break;
|
||||
}
|
||||
case 'vertexInstance': {
|
||||
const idx = (instanceIndex * vertexCount + vertexIndex) * 4;
|
||||
overpaintColor = Color.fromArray(tOverpaint, idx);
|
||||
overpaintAlpha = tOverpaint[idx + 3] / 255;
|
||||
break;
|
||||
}
|
||||
case 'volumeInstance': {
|
||||
const idx = (instanceIndex * vertexCount + vertexIndex) * 4;
|
||||
overpaintColor = Color.fromArray(interpolatedOverpaint!, idx);
|
||||
overpaintAlpha = interpolatedOverpaint![idx + 3] / 255;
|
||||
break;
|
||||
}
|
||||
default: throw new Error('Unsupported overpaint type.');
|
||||
}
|
||||
// interpolate twice to avoid darkening due to empty overpaint
|
||||
overpaintColor = Color.interpolate(color, overpaintColor, overpaintAlpha);
|
||||
color = Color.interpolate(color, overpaintColor, overpaintAlpha);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
protected static getTransparency(vertexIndex: number, geoData: MeshGeoData, interpolatedTransparency?: Uint8Array): number {
|
||||
const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
const dTransparency = values.dTransparency.ref.value;
|
||||
const tTransparency = values.tTransparency.ref.value.array;
|
||||
const transparencyType = values.dTransparencyType.ref.value;
|
||||
|
||||
let transparency: number = 0;
|
||||
if (dTransparency) {
|
||||
switch (transparencyType) {
|
||||
case 'groupInstance': {
|
||||
const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
|
||||
const idx = (instanceIndex * groupCount + group);
|
||||
transparency = tTransparency[idx] / 255;
|
||||
break;
|
||||
}
|
||||
case 'vertexInstance': {
|
||||
const idx = (instanceIndex * vertexCount + vertexIndex);
|
||||
transparency = tTransparency[idx] / 255;
|
||||
break;
|
||||
}
|
||||
case 'volumeInstance': {
|
||||
const idx = (instanceIndex * vertexCount + vertexIndex);
|
||||
transparency = interpolatedTransparency![idx] / 255;
|
||||
break;
|
||||
}
|
||||
default: throw new Error('Unsupported transparency type.');
|
||||
}
|
||||
}
|
||||
return transparency;
|
||||
}
|
||||
|
||||
protected abstract addMeshWithColors(input: AddMeshInput): void;
|
||||
|
||||
private async addMesh(values: MeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
|
||||
@@ -240,7 +441,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
|
||||
}
|
||||
const framebuffer = webgl.namedFramebuffers[GeoExportName];
|
||||
|
||||
const [ width, height ] = values.uGeoTexDim.ref.value;
|
||||
const [width, height] = values.uGeoTexDim.ref.value;
|
||||
const vertices = new Float32Array(width * height * 4);
|
||||
const normals = new Float32Array(width * height * 4);
|
||||
const groups = webgl.isWebGL2 ? new Uint8Array(width * height * 4) : new Float32Array(width * height * 4);
|
||||
@@ -278,7 +479,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
|
||||
}
|
||||
}
|
||||
|
||||
abstract getData(): D;
|
||||
abstract getData(ctx: RuntimeContext): Promise<D>;
|
||||
|
||||
abstract getBlob(ctx: RuntimeContext): Promise<Blob>;
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { sort, arraySwap } from '../../mol-data/util';
|
||||
import { asciiWrite } from '../../mol-io/common/ascii';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
@@ -48,7 +48,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
StringBuilder.newline(this.obj);
|
||||
if (!this.materialSet.has(material)) {
|
||||
this.materialSet.add(material);
|
||||
const [r, g, b] = Color.toRgbNormalized(color);
|
||||
const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
|
||||
const mtl = this.mtl;
|
||||
StringBuilder.writeSafe(mtl, `newmtl ${material}\n`);
|
||||
StringBuilder.writeSafe(mtl, 'illum 2\n'); // illumination model
|
||||
@@ -69,70 +69,6 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
}
|
||||
}
|
||||
|
||||
private static quantizeColors(colorArray: Uint8Array, vertexCount: number) {
|
||||
if (vertexCount <= 1024) return;
|
||||
const rgb = Vec3();
|
||||
const min = Vec3();
|
||||
const max = Vec3();
|
||||
const sum = Vec3();
|
||||
const colorMap = new Map<Color, Color>();
|
||||
const colorComparers = [
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[0] - Color.toVec3(rgb, colors[j])[0]),
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[1] - Color.toVec3(rgb, colors[j])[1]),
|
||||
(colors: Color[], i: number, j: number) => (Color.toVec3(rgb, colors[i])[2] - Color.toVec3(rgb, colors[j])[2]),
|
||||
];
|
||||
|
||||
const medianCut = (colors: Color[], l: number, r: number, depth: number) => {
|
||||
if (l > r) return;
|
||||
if (l === r || depth >= 10) {
|
||||
// Find the average color.
|
||||
Vec3.set(sum, 0, 0, 0);
|
||||
for (let i = l; i <= r; ++i) {
|
||||
Color.toVec3(rgb, colors[i]);
|
||||
Vec3.add(sum, sum, rgb);
|
||||
}
|
||||
Vec3.round(rgb, Vec3.scale(rgb, sum, 1 / (r - l + 1)));
|
||||
const averageColor = Color.fromArray(rgb, 0);
|
||||
for (let i = l; i <= r; ++i) colorMap.set(colors[i], averageColor);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the color channel with the greatest range.
|
||||
Vec3.set(min, 255, 255, 255);
|
||||
Vec3.set(max, 0, 0, 0);
|
||||
for (let i = l; i <= r; ++i) {
|
||||
Color.toVec3(rgb, colors[i]);
|
||||
for (let j = 0; j < 3; ++j) {
|
||||
Vec3.min(min, min, rgb);
|
||||
Vec3.max(max, max, rgb);
|
||||
}
|
||||
}
|
||||
let k = 0;
|
||||
if (max[1] - min[1] > max[k] - min[k]) k = 1;
|
||||
if (max[2] - min[2] > max[k] - min[k]) k = 2;
|
||||
|
||||
sort(colors, l, r + 1, colorComparers[k], arraySwap);
|
||||
|
||||
const m = (l + r) >> 1;
|
||||
medianCut(colors, l, m, depth + 1);
|
||||
medianCut(colors, m + 1, r, depth + 1);
|
||||
};
|
||||
|
||||
// Create an array of unique colors and use the median cut algorithm.
|
||||
const colorSet = new Set<Color>();
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
colorSet.add(Color.fromArray(colorArray, i * 3));
|
||||
}
|
||||
const colors = Array.from(colorSet);
|
||||
medianCut(colors, 0, colors.length - 1, 0);
|
||||
|
||||
// Map actual colors to quantized colors.
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
const color = colorMap.get(Color.fromArray(colorArray, i * 3));
|
||||
Color.toArray(color!, colorArray, i * 3);
|
||||
}
|
||||
}
|
||||
|
||||
protected async addMeshWithColors(input: AddMeshInput) {
|
||||
const { mesh, values, isGeoTexture, webgl, ctx } = input;
|
||||
|
||||
@@ -142,19 +78,27 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
const tmpV = Vec3();
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const tColor = values.tColor.ref.value.array;
|
||||
const overpaintType = values.dOverpaintType.ref.value;
|
||||
const transparencyType = values.dTransparencyType.ref.value;
|
||||
const uAlpha = values.uAlpha.ref.value;
|
||||
const dTransparency = values.dTransparency.ref.value;
|
||||
const tTransparency = values.tTransparency.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
|
||||
let interpolatedColors: Uint8Array;
|
||||
let interpolatedColors: Uint8Array | undefined;
|
||||
if (colorType === 'volume' || colorType === 'volumeInstance') {
|
||||
interpolatedColors = ObjExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
|
||||
ObjExporter.quantizeColors(interpolatedColors, mesh!.vertexCount);
|
||||
interpolatedColors = ObjExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
|
||||
}
|
||||
|
||||
let interpolatedOverpaint: Uint8Array | undefined;
|
||||
if (overpaintType === 'volumeInstance') {
|
||||
interpolatedOverpaint = ObjExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
|
||||
}
|
||||
|
||||
let interpolatedTransparency: Uint8Array | undefined;
|
||||
if (transparencyType === 'volumeInstance') {
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
interpolatedTransparency = ObjExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
|
||||
}
|
||||
|
||||
await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
|
||||
@@ -192,47 +136,23 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
StringBuilder.newline(obj);
|
||||
}
|
||||
|
||||
const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
|
||||
|
||||
// color
|
||||
const quantizedColors = new Uint8Array(drawCount * 3);
|
||||
for (let i = 0; i < drawCount; i += 3) {
|
||||
const v = isGeoTexture ? i : indices![i];
|
||||
const color = ObjExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
|
||||
Color.toArray(color, quantizedColors, i);
|
||||
}
|
||||
ObjExporter.quantizeColors(quantizedColors, vertexCount);
|
||||
|
||||
// face
|
||||
for (let i = 0; i < drawCount; i += 3) {
|
||||
let color: Color;
|
||||
switch (colorType) {
|
||||
case 'uniform':
|
||||
color = Color.fromNormalizedArray(values.uColor.ref.value, 0);
|
||||
break;
|
||||
case 'instance':
|
||||
color = Color.fromArray(tColor, instanceIndex * 3);
|
||||
break;
|
||||
case 'group': {
|
||||
const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
|
||||
color = Color.fromArray(tColor, group * 3);
|
||||
break;
|
||||
}
|
||||
case 'groupInstance': {
|
||||
const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
|
||||
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
|
||||
break;
|
||||
}
|
||||
case 'vertex':
|
||||
color = Color.fromArray(tColor, indices![i] * 3);
|
||||
break;
|
||||
case 'vertexInstance':
|
||||
color = Color.fromArray(tColor, (instanceIndex * vertexCount + indices![i]) * 3);
|
||||
break;
|
||||
case 'volume':
|
||||
color = Color.fromArray(interpolatedColors!, (isGeoTexture ? i : indices![i]) * 3);
|
||||
break;
|
||||
case 'volumeInstance':
|
||||
color = Color.fromArray(interpolatedColors!, (instanceIndex * vertexCount + (isGeoTexture ? i : indices![i])) * 3);
|
||||
break;
|
||||
default: throw new Error('Unsupported color type.');
|
||||
}
|
||||
const color = Color.fromArray(quantizedColors, i);
|
||||
|
||||
let alpha = uAlpha;
|
||||
if (dTransparency) {
|
||||
const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
|
||||
const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
|
||||
alpha *= 1 - transparency;
|
||||
}
|
||||
const transparency = ObjExporter.getTransparency(i, geoData, interpolatedTransparency);
|
||||
const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
|
||||
|
||||
this.updateMaterial(color, alpha);
|
||||
|
||||
@@ -256,7 +176,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
}
|
||||
}
|
||||
|
||||
getData() {
|
||||
async getData() {
|
||||
return {
|
||||
obj: StringBuilder.getString(this.obj),
|
||||
mtl: StringBuilder.getString(this.mtl)
|
||||
@@ -264,7 +184,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
|
||||
}
|
||||
|
||||
async getBlob(ctx: RuntimeContext) {
|
||||
const { obj, mtl } = this.getData();
|
||||
const { obj, mtl } = await this.getData();
|
||||
const objData = new Uint8Array(obj.length);
|
||||
asciiWrite(objData, obj);
|
||||
const mtlData = new Uint8Array(mtl.length);
|
||||
|
||||
@@ -9,12 +9,12 @@ import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
|
||||
export type RenderObjectExportData = {
|
||||
[k: string]: string | Uint8Array | undefined
|
||||
[k: string]: string | Uint8Array | ArrayBuffer | undefined
|
||||
}
|
||||
|
||||
export interface RenderObjectExporter<D extends RenderObjectExportData> {
|
||||
readonly fileExtension: string
|
||||
add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext): Promise<void> | undefined
|
||||
getData(): D
|
||||
getData(ctx: RuntimeContext): Promise<D>
|
||||
getBlob(ctx: RuntimeContext): Promise<Blob>
|
||||
}
|
||||
@@ -89,7 +89,7 @@ export class StlExporter extends MeshExporter<StlData> {
|
||||
}
|
||||
}
|
||||
|
||||
getData() {
|
||||
async getData() {
|
||||
const stl = new Uint8Array(84 + 50 * this.triangleCount);
|
||||
|
||||
asciiWrite(stl, `Exported from Mol* ${PLUGIN_VERSION}`);
|
||||
@@ -106,7 +106,7 @@ export class StlExporter extends MeshExporter<StlData> {
|
||||
}
|
||||
|
||||
async getBlob(ctx: RuntimeContext) {
|
||||
return new Blob([this.getData().stl], { type: 'model/stl' });
|
||||
return new Blob([(await this.getData()).stl], { type: 'model/stl' });
|
||||
}
|
||||
|
||||
constructor(boundingBox: Box3D) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { merge } from 'rxjs';
|
||||
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
|
||||
import { Button } from '../../mol-plugin-ui/controls/common';
|
||||
import { GetAppSvg, CubeSendSvg } from '../../mol-plugin-ui/controls/icons';
|
||||
import { GetAppSvg, CubeScanSvg, CubeSendSvg } from '../../mol-plugin-ui/controls/icons';
|
||||
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { GeometryParams, GeometryControls } from './controls';
|
||||
@@ -18,6 +18,7 @@ interface State {
|
||||
|
||||
export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
private _controls: GeometryControls | undefined;
|
||||
private isARSupported: boolean | undefined;
|
||||
|
||||
get controls() {
|
||||
return this._controls || (this._controls = new GeometryControls(this.plugin));
|
||||
@@ -32,6 +33,9 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
}
|
||||
|
||||
protected renderControls(): JSX.Element {
|
||||
if (this.isARSupported === undefined) {
|
||||
this.isARSupported = !!document.createElement('a').relList?.supports?.('ar');
|
||||
}
|
||||
const ctrl = this.controls;
|
||||
return <>
|
||||
<ParameterControls
|
||||
@@ -45,6 +49,13 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
|
||||
Save
|
||||
</Button>
|
||||
{this.isARSupported && ctrl.behaviors.params.value.format === 'usdz' &&
|
||||
<Button icon={CubeScanSvg}
|
||||
onClick={this.viewInAR} style={{ marginTop: 1 }}
|
||||
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
|
||||
View in AR
|
||||
</Button>
|
||||
}
|
||||
</>;
|
||||
}
|
||||
|
||||
@@ -60,6 +71,7 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
this._controls?.dispose();
|
||||
this._controls = void 0;
|
||||
}
|
||||
@@ -68,11 +80,30 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
try {
|
||||
this.setState({ busy: true });
|
||||
const data = await this.controls.exportGeometry();
|
||||
this.setState({ busy: false });
|
||||
|
||||
download(data.blob, data.filename);
|
||||
} catch {
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
viewInAR = async () => {
|
||||
try {
|
||||
this.setState({ busy: true });
|
||||
const data = await this.controls.exportGeometry();
|
||||
const a = document.createElement('a');
|
||||
a.rel = 'ar';
|
||||
a.href = URL.createObjectURL(data.blob);
|
||||
// For in-place viewing of USDZ on iOS, the link must contain a single child that is either an img or picture.
|
||||
// https://webkit.org/blog/8421/viewing-augmented-reality-assets-in-safari-for-ios/
|
||||
a.appendChild(document.createElement('img'));
|
||||
setTimeout(() => URL.revokeObjectURL(a.href), 4E4); // 40s
|
||||
setTimeout(() => a.dispatchEvent(new MouseEvent('click')));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
};
|
||||
}
|
||||
247
src/extensions/geo-export/usdz-exporter.ts
Normal file
247
src/extensions/geo-export/usdz-exporter.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { asciiWrite } from '../../mol-io/common/ascii';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { PLUGIN_VERSION } from '../../mol-plugin/version';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
import { Color } from '../../mol-util/color/color';
|
||||
import { zip } from '../../mol-util/zip/zip';
|
||||
import { MeshExporter, AddMeshInput } from './mesh-exporter';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3fromArray = Vec3.fromArray;
|
||||
const v3transformMat4 = Vec3.transformMat4;
|
||||
const v3transformMat3 = Vec3.transformMat3;
|
||||
const mat3directionTransform = Mat3.directionTransform;
|
||||
|
||||
// https://graphics.pixar.com/usd/docs/index.html
|
||||
|
||||
export type UsdzData = {
|
||||
usdz: ArrayBuffer
|
||||
}
|
||||
|
||||
export class UsdzExporter extends MeshExporter<UsdzData> {
|
||||
readonly fileExtension = 'usdz';
|
||||
private meshes: string[] = [];
|
||||
private materials: string[] = [];
|
||||
private materialMap = new Map<string, number>();
|
||||
private centerTransform: Mat4;
|
||||
|
||||
private addMaterial(color: Color, alpha: number, metalness: number, roughness: number): number {
|
||||
const hash = `${color}|${alpha}|${metalness}|${roughness}`;
|
||||
if (this.materialMap.has(hash)) return this.materialMap.get(hash)!;
|
||||
|
||||
const materialKey = this.materialMap.size;
|
||||
this.materialMap.set(hash, materialKey);
|
||||
|
||||
const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
|
||||
this.materials.push(`
|
||||
def Material "material${materialKey}"
|
||||
{
|
||||
token outputs:surface.connect = </material${materialKey}/shader.outputs:surface>
|
||||
def Shader "shader"
|
||||
{
|
||||
uniform token info:id = "UsdPreviewSurface"
|
||||
color3f inputs:diffuseColor = (${r},${g},${b})
|
||||
float inputs:opacity = ${alpha}
|
||||
float inputs:metallic = ${metalness}
|
||||
float inputs:roughness = ${roughness}
|
||||
token outputs:surface
|
||||
}
|
||||
}
|
||||
`);
|
||||
return materialKey;
|
||||
}
|
||||
|
||||
protected async addMeshWithColors(input: AddMeshInput) {
|
||||
const { mesh, values, isGeoTexture, webgl, ctx } = input;
|
||||
|
||||
const t = Mat4();
|
||||
const n = Mat3();
|
||||
const tmpV = Vec3();
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const overpaintType = values.dOverpaintType.ref.value;
|
||||
const transparencyType = values.dTransparencyType.ref.value;
|
||||
const uAlpha = values.uAlpha.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
const instanceCount = values.uInstanceCount.ref.value;
|
||||
const metalness = values.uMetalness.ref.value;
|
||||
const roughness = values.uRoughness.ref.value;
|
||||
|
||||
let interpolatedColors: Uint8Array | undefined;
|
||||
if (colorType === 'volume' || colorType === 'volumeInstance') {
|
||||
interpolatedColors = UsdzExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
|
||||
}
|
||||
|
||||
let interpolatedOverpaint: Uint8Array | undefined;
|
||||
if (overpaintType === 'volumeInstance') {
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
interpolatedOverpaint = UsdzExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
|
||||
}
|
||||
|
||||
let interpolatedTransparency: Uint8Array | undefined;
|
||||
if (transparencyType === 'volumeInstance') {
|
||||
const stride = isGeoTexture ? 4 : 3;
|
||||
interpolatedTransparency = UsdzExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
|
||||
}
|
||||
|
||||
await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
|
||||
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
|
||||
|
||||
const { vertices, normals, indices, groups, vertexCount, drawCount } = UsdzExporter.getInstance(input, instanceIndex);
|
||||
|
||||
Mat4.fromArray(t, aTransform, instanceIndex * 16);
|
||||
Mat4.mul(t, this.centerTransform, t);
|
||||
mat3directionTransform(n, t);
|
||||
|
||||
const vertexBuilder = StringBuilder.create();
|
||||
const normalBuilder = StringBuilder.create();
|
||||
const indexBuilder = StringBuilder.create();
|
||||
|
||||
// position
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
|
||||
StringBuilder.writeSafe(vertexBuilder, (i === 0) ? '(' : ',(');
|
||||
StringBuilder.writeFloat(vertexBuilder, tmpV[0], 10000);
|
||||
StringBuilder.writeSafe(vertexBuilder, ',');
|
||||
StringBuilder.writeFloat(vertexBuilder, tmpV[1], 10000);
|
||||
StringBuilder.writeSafe(vertexBuilder, ',');
|
||||
StringBuilder.writeFloat(vertexBuilder, tmpV[2], 10000);
|
||||
StringBuilder.writeSafe(vertexBuilder, ')');
|
||||
}
|
||||
|
||||
// normal
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
|
||||
StringBuilder.writeSafe(normalBuilder, (i === 0) ? '(' : ',(');
|
||||
StringBuilder.writeFloat(normalBuilder, tmpV[0], 100);
|
||||
StringBuilder.writeSafe(normalBuilder, ',');
|
||||
StringBuilder.writeFloat(normalBuilder, tmpV[1], 100);
|
||||
StringBuilder.writeSafe(normalBuilder, ',');
|
||||
StringBuilder.writeFloat(normalBuilder, tmpV[2], 100);
|
||||
StringBuilder.writeSafe(normalBuilder, ')');
|
||||
}
|
||||
|
||||
const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
|
||||
|
||||
// face
|
||||
for (let i = 0; i < drawCount; ++i) {
|
||||
const v = isGeoTexture ? i : indices![i];
|
||||
if (i > 0) StringBuilder.writeSafe(indexBuilder, ',');
|
||||
StringBuilder.writeInteger(indexBuilder, v);
|
||||
}
|
||||
|
||||
// color
|
||||
const quantizedColors = new Uint8Array(drawCount * 3);
|
||||
for (let i = 0; i < drawCount; i += 3) {
|
||||
const v = isGeoTexture ? i : indices![i];
|
||||
const color = UsdzExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
|
||||
Color.toArray(color, quantizedColors, i);
|
||||
}
|
||||
UsdzExporter.quantizeColors(quantizedColors, vertexCount);
|
||||
|
||||
// material
|
||||
const faceIndicesByMaterial = new Map<number, number[]>();
|
||||
for (let i = 0; i < drawCount; i += 3) {
|
||||
const color = Color.fromArray(quantizedColors, i);
|
||||
|
||||
const transparency = UsdzExporter.getTransparency(i, geoData, interpolatedTransparency);
|
||||
const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
|
||||
|
||||
const materialKey = this.addMaterial(color, alpha, metalness, roughness);
|
||||
let faceIndices = faceIndicesByMaterial.get(materialKey);
|
||||
if (faceIndices === undefined) {
|
||||
faceIndices = [];
|
||||
faceIndicesByMaterial.set(materialKey, faceIndices);
|
||||
}
|
||||
faceIndices.push(i / 3);
|
||||
}
|
||||
|
||||
// If this mesh uses only one material, bind it to the material directly.
|
||||
// Otherwise, use GeomSubsets to bind it to multiple materials.
|
||||
let materialBinding: string;
|
||||
if (faceIndicesByMaterial.size === 1) {
|
||||
const materialKey = faceIndicesByMaterial.keys().next().value;
|
||||
materialBinding = `rel material:binding = </material${materialKey}>`;
|
||||
} else {
|
||||
const geomSubsets: string[] = [];
|
||||
faceIndicesByMaterial.forEach((faceIndices: number[], materialKey: number) => {
|
||||
geomSubsets.push(`
|
||||
def GeomSubset "g${materialKey}"
|
||||
{
|
||||
uniform token elementType = "face"
|
||||
uniform token familyName = "materialBind"
|
||||
int[] indices = [${faceIndices.join(',')}]
|
||||
rel material:binding = </material${materialKey}>
|
||||
}
|
||||
`);
|
||||
});
|
||||
materialBinding = geomSubsets.join('');
|
||||
}
|
||||
|
||||
this.meshes.push(`
|
||||
def Mesh "mesh${this.meshes.length}"
|
||||
{
|
||||
int[] faceVertexCounts = [${new Array(drawCount / 3).fill(3).join(',')}]
|
||||
int[] faceVertexIndices = [${StringBuilder.getString(indexBuilder)}]
|
||||
point3f[] points = [${StringBuilder.getString(vertexBuilder)}]
|
||||
normal3f[] primvars:normals = [${StringBuilder.getString(normalBuilder)}] (
|
||||
interpolation = "vertex"
|
||||
)
|
||||
uniform token subdivisionScheme = "none"
|
||||
${materialBinding}
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
async getData(ctx: RuntimeContext) {
|
||||
const header = `#usda 1.0
|
||||
(
|
||||
customLayerData = {
|
||||
string creator = "Mol* ${PLUGIN_VERSION}"
|
||||
}
|
||||
metersPerUnit = 1
|
||||
)
|
||||
`;
|
||||
const usda = [header, ...this.materials, ...this.meshes].join('');
|
||||
const usdaData = new Uint8Array(usda.length);
|
||||
asciiWrite(usdaData, usda);
|
||||
const zipDataObj = {
|
||||
['model.usda']: usdaData
|
||||
};
|
||||
return {
|
||||
usdz: await zip(ctx, zipDataObj, true)
|
||||
};
|
||||
}
|
||||
|
||||
async getBlob(ctx: RuntimeContext) {
|
||||
const { usdz } = await this.getData(ctx);
|
||||
return new Blob([usdz], { type: 'model/vnd.usdz+zip' });
|
||||
}
|
||||
|
||||
constructor(boundingBox: Box3D, radius: number) {
|
||||
super();
|
||||
const t = Mat4();
|
||||
// scale the model so that it fits within 1 meter
|
||||
Mat4.fromUniformScaling(t, Math.min(1 / (radius * 2), 1));
|
||||
// translate the model so that it sits on the ground plane (y = 0)
|
||||
Mat4.translate(t, t, Vec3.create(
|
||||
-(boundingBox.min[0] + boundingBox.max[0]) / 2,
|
||||
-boundingBox.min[1],
|
||||
-(boundingBox.min[2] + boundingBox.max[2]) / 2
|
||||
));
|
||||
this.centerTransform = t;
|
||||
}
|
||||
}
|
||||
212
src/extensions/model-archive/quality-assessment/behavior.ts
Normal file
212
src/extensions/model-archive/quality-assessment/behavior.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
|
||||
import { Loci } from '../../../mol-model/loci';
|
||||
import { DefaultQueryRuntimeTable } from '../../../mol-script/runtime/query/compiler';
|
||||
import { PLDDTConfidenceColorThemeProvider } from './color/plddt';
|
||||
import { QualityAssessment, QualityAssessmentProvider } from './prop';
|
||||
import { StructureSelectionCategory, StructureSelectionQuery } from '../../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { cantorPairing } from '../../../mol-data/util';
|
||||
import { QmeanScoreColorThemeProvider } from './color/qmean';
|
||||
import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StateObjectRef } from '../../../mol-state';
|
||||
|
||||
export const MAQualityAssessment = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
|
||||
name: 'ma-quality-assessment-prop',
|
||||
category: 'custom-props',
|
||||
display: {
|
||||
name: 'Quality Assessment',
|
||||
description: 'Data included in Model Archive files.'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
|
||||
private provider = QualityAssessmentProvider;
|
||||
|
||||
private labelProvider = {
|
||||
label: (loci: Loci): string | undefined => {
|
||||
if (!this.params.showTooltip) return;
|
||||
return [
|
||||
plddtLabel(loci),
|
||||
qmeanLabel(loci),
|
||||
].filter(l => !!l).join('</br>');
|
||||
}
|
||||
};
|
||||
|
||||
register(): void {
|
||||
DefaultQueryRuntimeTable.addCustomProp(this.provider.descriptor);
|
||||
|
||||
this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
|
||||
|
||||
this.ctx.managers.lociLabels.addProvider(this.labelProvider);
|
||||
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(PLDDTConfidenceColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(QmeanScoreColorThemeProvider);
|
||||
|
||||
this.ctx.query.structure.registry.add(confidentPLDDT);
|
||||
|
||||
this.ctx.builders.structure.representation.registerPreset(QualityAssessmentPLDDTPreset);
|
||||
this.ctx.builders.structure.representation.registerPreset(QualityAssessmentQmeanPreset);
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean, showTooltip: boolean }) {
|
||||
const updated = this.params.autoAttach !== p.autoAttach;
|
||||
this.params.autoAttach = p.autoAttach;
|
||||
this.params.showTooltip = p.showTooltip;
|
||||
this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
|
||||
return updated;
|
||||
}
|
||||
|
||||
unregister() {
|
||||
DefaultQueryRuntimeTable.removeCustomProp(this.provider.descriptor);
|
||||
|
||||
this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
|
||||
|
||||
this.ctx.managers.lociLabels.removeProvider(this.labelProvider);
|
||||
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(PLDDTConfidenceColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(QmeanScoreColorThemeProvider);
|
||||
|
||||
this.ctx.query.structure.registry.remove(confidentPLDDT);
|
||||
|
||||
this.ctx.builders.structure.representation.unregisterPreset(QualityAssessmentPLDDTPreset);
|
||||
this.ctx.builders.structure.representation.unregisterPreset(QualityAssessmentQmeanPreset);
|
||||
}
|
||||
},
|
||||
params: () => ({
|
||||
autoAttach: PD.Boolean(false),
|
||||
showTooltip: PD.Boolean(true),
|
||||
})
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
function plddtCategory(score: number) {
|
||||
if (score > 50 && score <= 70) return 'Low';
|
||||
if (score > 70 && score <= 90) return 'Confident';
|
||||
if (score > 90) return 'Very high';
|
||||
return 'Very low';
|
||||
}
|
||||
|
||||
function plddtLabel(loci: Loci): string | undefined {
|
||||
return metricLabel(loci, 'pLDDT', (scoreAvg: number, countInfo: string) => `pLDDT Score ${countInfo}: ${scoreAvg.toFixed(2)} <small>(${plddtCategory(scoreAvg)})</small>`);
|
||||
}
|
||||
|
||||
function qmeanLabel(loci: Loci): string | undefined {
|
||||
return metricLabel(loci, 'qmean', (scoreAvg: number, countInfo: string) => `QMEAN Score ${countInfo}: ${scoreAvg.toFixed(2)}`);
|
||||
}
|
||||
|
||||
function metricLabel(loci: Loci, name: 'qmean' | 'pLDDT', label: (scoreAvg: number, countInfo: string) => string): string | undefined {
|
||||
if (loci.kind === 'element-loci') {
|
||||
if (loci.elements.length === 0) return;
|
||||
|
||||
const seen = new Set<number>();
|
||||
const scoreSeen = new Set<number>();
|
||||
let scoreSum = 0;
|
||||
|
||||
for (const { indices, unit } of loci.elements) {
|
||||
const metric = QualityAssessmentProvider.get(unit.model).value?.[name];
|
||||
if (!metric) continue;
|
||||
|
||||
const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index;
|
||||
const { elements } = unit;
|
||||
|
||||
OrderedSet.forEach(indices, idx => {
|
||||
const eI = elements[idx];
|
||||
const rI = residueIndex[eI];
|
||||
|
||||
const residueKey = cantorPairing(rI, unit.id);
|
||||
if (!seen.has(residueKey)) {
|
||||
const score = metric.get(residueIndex[eI]) ?? -1;
|
||||
if (score !== -1) {
|
||||
scoreSum += score;
|
||||
scoreSeen.add(residueKey);
|
||||
}
|
||||
seen.add(residueKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (seen.size === 0) return;
|
||||
|
||||
const summary: string[] = [];
|
||||
|
||||
if (scoreSeen.size) {
|
||||
const countInfo = `<small>(${scoreSeen.size} ${scoreSeen.size > 1 ? 'Residues avg.' : 'Residue'})</small>`;
|
||||
const scoreAvg = scoreSum / scoreSeen.size;
|
||||
summary.push(label(scoreAvg, countInfo));
|
||||
}
|
||||
|
||||
if (summary.length) {
|
||||
return summary.join('</br>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const confidentPLDDT = StructureSelectionQuery('Confident pLDDT (> 70)', MS.struct.modifier.union([
|
||||
MS.struct.modifier.wholeResidues([
|
||||
MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
|
||||
'residue-test': MS.core.rel.gr([QualityAssessment.symbols.pLDDT.symbol(), 70]),
|
||||
})
|
||||
])
|
||||
])
|
||||
]), {
|
||||
description: 'Select residues with a pLDDT > 70 (confident).',
|
||||
category: StructureSelectionCategory.Validation,
|
||||
ensureCustomProperties: async (ctx, structure) => {
|
||||
for (const m of structure.models) {
|
||||
await QualityAssessmentProvider.attach(ctx, m, void 0, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
export const QualityAssessmentPLDDTPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-ma-quality-assessment-plddt',
|
||||
display: {
|
||||
name: 'Quality Assessment (pLDDT)', group: 'Annotation',
|
||||
description: 'Color structure based on pLDDT Confidence.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return !!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'));
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
const colorTheme = PLDDTConfidenceColorThemeProvider.name as any;
|
||||
return await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
|
||||
}
|
||||
});
|
||||
|
||||
export const QualityAssessmentQmeanPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-ma-quality-assessment-qmean',
|
||||
display: {
|
||||
name: 'Quality Assessment (QMEAN)', group: 'Annotation',
|
||||
description: 'Color structure based on QMEAN Score.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return !!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean'));
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
const colorTheme = QmeanScoreColorThemeProvider.name as any;
|
||||
return await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
|
||||
}
|
||||
});
|
||||
106
src/extensions/model-archive/quality-assessment/color/plddt.ts
Normal file
106
src/extensions/model-archive/quality-assessment/color/plddt.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Mandar Deshpande <mandar@ebi.ac.uk>
|
||||
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { QualityAssessment, QualityAssessmentProvider } from '../prop';
|
||||
import { Location } from '../../../../mol-model/location';
|
||||
import { Bond, StructureElement, Unit } from '../../../../mol-model/structure';
|
||||
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
|
||||
import { ThemeDataContext } from '../../../../mol-theme/theme';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
|
||||
import { TableLegend } from '../../../../mol-util/legend';
|
||||
|
||||
const DefaultColor = Color(0xaaaaaa);
|
||||
const ConfidenceColors = {
|
||||
'No Score': DefaultColor,
|
||||
'Very Low': Color(0xff7d45),
|
||||
'Low': Color(0xffdb13),
|
||||
'Confident': Color(0x65cbf3),
|
||||
'Very High': Color(0x0053d6)
|
||||
};
|
||||
|
||||
const ConfidenceColorLegend = TableLegend(Object.entries(ConfidenceColors));
|
||||
|
||||
export function getPLDDTConfidenceColorThemeParams(ctx: ThemeDataContext) {
|
||||
return {};
|
||||
}
|
||||
export type PLDDTConfidenceColorThemeParams = ReturnType<typeof getPLDDTConfidenceColorThemeParams>
|
||||
|
||||
export function PLDDTConfidenceColorTheme(ctx: ThemeDataContext, props: PD.Values<PLDDTConfidenceColorThemeParams>): ColorTheme<PLDDTConfidenceColorThemeParams> {
|
||||
let color: LocationColor = () => DefaultColor;
|
||||
|
||||
if (ctx.structure) {
|
||||
const l = StructureElement.Location.create(ctx.structure.root);
|
||||
|
||||
const getColor = (location: StructureElement.Location): Color => {
|
||||
const { unit, element } = location;
|
||||
if (!Unit.isAtomic(unit)) return DefaultColor;
|
||||
const qualityAssessment = QualityAssessmentProvider.get(unit.model).value;
|
||||
const score = qualityAssessment?.pLDDT?.get(unit.model.atomicHierarchy.residueAtomSegments.index[element]) ?? -1;
|
||||
if (score < 0) {
|
||||
return DefaultColor;
|
||||
} else if (score <= 50) {
|
||||
return Color(0xff7d45);
|
||||
} else if (score <= 70) {
|
||||
return Color(0xffdb13);
|
||||
} else if (score <= 90) {
|
||||
return Color(0x65cbf3);
|
||||
} else {
|
||||
return Color(0x0053d6);
|
||||
}
|
||||
};
|
||||
|
||||
color = (location: Location) => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
return getColor(location);
|
||||
} else if (Bond.isLocation(location)) {
|
||||
l.unit = location.aUnit;
|
||||
l.element = location.aUnit.elements[location.aIndex];
|
||||
return getColor(l);
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
factory: PLDDTConfidenceColorTheme,
|
||||
granularity: 'group',
|
||||
preferSmoothing: true,
|
||||
color,
|
||||
props,
|
||||
description: 'Assigns residue colors according to the pLDDT Confidence score.',
|
||||
legend: ConfidenceColorLegend
|
||||
};
|
||||
}
|
||||
|
||||
export const PLDDTConfidenceColorThemeProvider: ColorTheme.Provider<PLDDTConfidenceColorThemeParams, 'plddt-confidence'> = {
|
||||
name: 'plddt-confidence',
|
||||
label: 'pLDDT Confidence',
|
||||
category: ColorTheme.Category.Validation,
|
||||
factory: PLDDTConfidenceColorTheme,
|
||||
getParams: getPLDDTConfidenceColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(getPLDDTConfidenceColorThemeParams({})),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure?.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')),
|
||||
ensureCustomProperties: {
|
||||
attach: async (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
if (data.structure) {
|
||||
for (const m of data.structure.models) {
|
||||
await QualityAssessmentProvider.attach(ctx, m, void 0, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
detach: async (data: ThemeDataContext) => {
|
||||
if (data.structure) {
|
||||
for (const m of data.structure.models) {
|
||||
QualityAssessmentProvider.ref(m, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { QualityAssessment, QualityAssessmentProvider } from '../prop';
|
||||
import { Location } from '../../../../mol-model/location';
|
||||
import { Bond, StructureElement, Unit } from '../../../../mol-model/structure';
|
||||
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
|
||||
import { ThemeDataContext } from '../../../../mol-theme/theme';
|
||||
import { Color, ColorScale } from '../../../../mol-util/color';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
|
||||
|
||||
const DefaultColor = Color(0xaaaaaa);
|
||||
|
||||
export function getQmeanScoreColorThemeParams(ctx: ThemeDataContext) {
|
||||
return {};
|
||||
}
|
||||
export type QmeanScoreColorThemeParams = ReturnType<typeof getQmeanScoreColorThemeParams>
|
||||
|
||||
export function QmeanScoreColorTheme(ctx: ThemeDataContext, props: PD.Values<QmeanScoreColorThemeParams>): ColorTheme<QmeanScoreColorThemeParams> {
|
||||
let color: LocationColor = () => DefaultColor;
|
||||
|
||||
const scale = ColorScale.create({
|
||||
domain: [0, 1],
|
||||
listOrName: [
|
||||
[Color(0xFF5000), 0.5], [Color(0x025AFD), 1.0]
|
||||
]
|
||||
});
|
||||
|
||||
if (ctx.structure) {
|
||||
const l = StructureElement.Location.create(ctx.structure.root);
|
||||
|
||||
const getColor = (location: StructureElement.Location): Color => {
|
||||
const { unit, element } = location;
|
||||
if (!Unit.isAtomic(unit)) return DefaultColor;
|
||||
const qualityAssessment = QualityAssessmentProvider.get(unit.model).value;
|
||||
const score = qualityAssessment?.qmean?.get(unit.model.atomicHierarchy.residueAtomSegments.index[element]) ?? -1;
|
||||
if (score < 0) {
|
||||
return DefaultColor;
|
||||
} else {
|
||||
return scale.color(score);
|
||||
}
|
||||
};
|
||||
|
||||
color = (location: Location) => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
return getColor(location);
|
||||
} else if (Bond.isLocation(location)) {
|
||||
l.unit = location.aUnit;
|
||||
l.element = location.aUnit.elements[location.aIndex];
|
||||
return getColor(l);
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
factory: QmeanScoreColorTheme,
|
||||
granularity: 'group',
|
||||
preferSmoothing: true,
|
||||
color,
|
||||
props,
|
||||
description: 'Assigns residue colors according to the QMEAN score.',
|
||||
legend: scale.legend
|
||||
};
|
||||
}
|
||||
|
||||
export const QmeanScoreColorThemeProvider: ColorTheme.Provider<QmeanScoreColorThemeParams, 'qmean-score'> = {
|
||||
name: 'qmean-score',
|
||||
label: 'QMEAN Score',
|
||||
category: ColorTheme.Category.Validation,
|
||||
factory: QmeanScoreColorTheme,
|
||||
getParams: getQmeanScoreColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(getQmeanScoreColorThemeParams({})),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure?.models.some(m => QualityAssessment.isApplicable(m, 'qmean')),
|
||||
ensureCustomProperties: {
|
||||
attach: async (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
if (data.structure) {
|
||||
for (const m of data.structure.models) {
|
||||
await QualityAssessmentProvider.attach(ctx, m, void 0, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
detach: async (data: ThemeDataContext) => {
|
||||
if (data.structure) {
|
||||
for (const m of data.structure.models) {
|
||||
QualityAssessmentProvider.ref(m, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
131
src/extensions/model-archive/quality-assessment/prop.ts
Normal file
131
src/extensions/model-archive/quality-assessment/prop.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Unit } from '../../../mol-model/structure';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
|
||||
import { Model, ResidueIndex } from '../../../mol-model/structure/model';
|
||||
import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
|
||||
import { CustomPropSymbol } from '../../../mol-script/language/symbol';
|
||||
import { Type } from '../../../mol-script/language/type';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
|
||||
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
|
||||
export { QualityAssessment };
|
||||
|
||||
interface QualityAssessment {
|
||||
localMetrics: Map<string, Map<ResidueIndex, number>>
|
||||
pLDDT?: Map<ResidueIndex, number>
|
||||
qmean?: Map<ResidueIndex, number>
|
||||
}
|
||||
|
||||
namespace QualityAssessment {
|
||||
const Empty = {
|
||||
value: {
|
||||
localMetrics: new Map()
|
||||
}
|
||||
};
|
||||
|
||||
export function isApplicable(model?: Model, localMetricName?: 'pLDDT' | 'qmean'): boolean {
|
||||
if (!model || !MmcifFormat.is(model.sourceData)) return false;
|
||||
const { db } = model.sourceData.data;
|
||||
const hasLocalMetric = (
|
||||
db.ma_qa_metric.id.isDefined &&
|
||||
db.ma_qa_metric_local.ordinal_id.isDefined
|
||||
);
|
||||
if (localMetricName && hasLocalMetric) {
|
||||
for (let i = 0, il = db.ma_qa_metric._rowCount; i < il; i++) {
|
||||
if (db.ma_qa_metric.mode.value(i) !== 'local') continue;
|
||||
if (localMetricName === db.ma_qa_metric.name.value(i)) return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return hasLocalMetric;
|
||||
}
|
||||
}
|
||||
|
||||
export async function obtain(ctx: CustomProperty.Context, model: Model, props: QualityAssessmentProps): Promise<CustomProperty.Data<QualityAssessment>> {
|
||||
if (!model || !MmcifFormat.is(model.sourceData)) return Empty;
|
||||
const { ma_qa_metric, ma_qa_metric_local } = model.sourceData.data.db;
|
||||
const { model_id, label_asym_id, label_seq_id, metric_id, metric_value } = ma_qa_metric_local;
|
||||
const { index } = model.atomicHierarchy;
|
||||
|
||||
// for simplicity we assume names in ma_qa_metric for mode 'local' are unique
|
||||
const localMetrics = new Map<string, Map<ResidueIndex, number>>();
|
||||
const localNames = new Map<number, string>();
|
||||
|
||||
for (let i = 0, il = ma_qa_metric._rowCount; i < il; i++) {
|
||||
if (ma_qa_metric.mode.value(i) !== 'local') continue;
|
||||
|
||||
const name = ma_qa_metric.name.value(i);
|
||||
if (localMetrics.has(name)) {
|
||||
console.warn(`local ma_qa_metric with name '${name}' already added`);
|
||||
continue;
|
||||
}
|
||||
|
||||
localMetrics.set(name, new Map());
|
||||
localNames.set(ma_qa_metric.id.value(i), name);
|
||||
}
|
||||
|
||||
for (let i = 0, il = ma_qa_metric_local._rowCount; i < il; i++) {
|
||||
if (model_id.value(i) !== model.modelNum) continue;
|
||||
|
||||
const labelAsymId = label_asym_id.value(i);
|
||||
const entityIndex = index.findEntity(labelAsymId);
|
||||
const rI = index.findResidue(model.entities.data.id.value(entityIndex), labelAsymId, label_seq_id.value(i));
|
||||
const name = localNames.get(metric_id.value(i))!;
|
||||
localMetrics.get(name)!.set(rI, metric_value.value(i));
|
||||
}
|
||||
|
||||
return {
|
||||
value: {
|
||||
localMetrics,
|
||||
pLDDT: localMetrics.get('pLDDT'),
|
||||
qmean: localMetrics.get('qmean'),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const symbols = {
|
||||
pLDDT: QuerySymbolRuntime.Dynamic(CustomPropSymbol('ma', 'quality-assessment.pLDDT', Type.Num),
|
||||
ctx => {
|
||||
const { unit, element } = ctx.element;
|
||||
if (!Unit.isAtomic(unit)) return -1;
|
||||
const qualityAssessment = QualityAssessmentProvider.get(unit.model).value;
|
||||
return qualityAssessment?.pLDDT?.get(unit.model.atomicHierarchy.residueAtomSegments.index[element]) ?? -1;
|
||||
}
|
||||
),
|
||||
qmean: QuerySymbolRuntime.Dynamic(CustomPropSymbol('ma', 'quality-assessment.qmean', Type.Num),
|
||||
ctx => {
|
||||
const { unit, element } = ctx.element;
|
||||
if (!Unit.isAtomic(unit)) return -1;
|
||||
const qualityAssessment = QualityAssessmentProvider.get(unit.model).value;
|
||||
return qualityAssessment?.qmean?.get(unit.model.atomicHierarchy.residueAtomSegments.index[element]) ?? -1;
|
||||
}
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export const QualityAssessmentParams = { };
|
||||
export type QualityAssessmentParams = typeof QualityAssessmentParams
|
||||
export type QualityAssessmentProps = PD.Values<QualityAssessmentParams>
|
||||
|
||||
export const QualityAssessmentProvider: CustomModelProperty.Provider<QualityAssessmentParams, QualityAssessment> = CustomModelProperty.createProvider({
|
||||
label: 'QualityAssessment',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'ma_quality_assessment',
|
||||
symbols: QualityAssessment.symbols
|
||||
}),
|
||||
type: 'static',
|
||||
defaultParams: QualityAssessmentParams,
|
||||
getParams: (data: Model) => QualityAssessmentParams,
|
||||
isApplicable: (data: Model) => QualityAssessment.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<QualityAssessmentProps>) => {
|
||||
const p = { ...PD.getDefaultValues(QualityAssessmentParams), ...props };
|
||||
return await QualityAssessment.obtain(ctx, data, p);
|
||||
}
|
||||
});
|
||||
87
src/extensions/model-export/export.ts
Normal file
87
src/extensions/model-export/export.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { utf8ByteCount, utf8Write } from '../../mol-io/common/utf8';
|
||||
import { to_mmCIF, Unit } from '../../mol-model/structure';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Task } from '../../mol-task';
|
||||
import { getFormattedTime } from '../../mol-util/date';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { zip } from '../../mol-util/zip/zip';
|
||||
|
||||
export async function exportHierarchy(plugin: PluginContext, options?: { format?: 'cif' | 'bcif' }) {
|
||||
try {
|
||||
await plugin.runTask(_exportHierarchy(plugin, options), { useOverlay: true });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(`Model export failed. See console for details.`);
|
||||
}
|
||||
}
|
||||
|
||||
function _exportHierarchy(plugin: PluginContext, options?: { format?: 'cif' | 'bcif' }) {
|
||||
return Task.create('Export', async ctx => {
|
||||
await ctx.update({ message: 'Exporting...', isIndeterminate: true, canAbort: false });
|
||||
|
||||
const format = options?.format ?? 'cif';
|
||||
const { structures } = plugin.managers.structure.hierarchy.current;
|
||||
|
||||
const files: [name: string, data: string | Uint8Array][] = [];
|
||||
const entryMap = new Map<string, number>();
|
||||
|
||||
for (const _s of structures) {
|
||||
const s = _s.transform?.cell.obj?.data ?? _s.cell.obj?.data;
|
||||
if (!s) continue;
|
||||
if (s.models.length > 1) {
|
||||
plugin.log.warn(`[Export] Skipping ${_s.cell.obj?.label}: Multimodel exports not supported.`);
|
||||
continue;
|
||||
}
|
||||
if (s.units.some(u => !Unit.isAtomic(u))) {
|
||||
plugin.log.warn(`[Export] Skipping ${_s.cell.obj?.label}: Non-atomic model exports not supported.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = entryMap.has(s.model.entryId)
|
||||
? `${s.model.entryId}_${entryMap.get(s.model.entryId)! + 1}.${format}`
|
||||
: `${s.model.entryId}.${format}`;
|
||||
entryMap.set(s.model.entryId, (entryMap.get(s.model.entryId) ?? 0) + 1);
|
||||
|
||||
await ctx.update({ message: `Exporting ${s.model.entryId}...`, isIndeterminate: true, canAbort: false });
|
||||
if (s.elementCount > 100000) {
|
||||
// Give UI chance to update, only needed for larger structures.
|
||||
await new Promise(res => setTimeout(res, 50));
|
||||
}
|
||||
|
||||
try {
|
||||
files.push([name, to_mmCIF(s.model.entryId, s, format === 'bcif', { copyAllCategories: true })]);
|
||||
} catch (e) {
|
||||
if (format === 'cif' && s.elementCount > 2000000) {
|
||||
plugin.log.warn(`[Export] The structure might be too big to be exported as Text CIF, consider using the BinaryCIF format instead.`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (files.length === 1) {
|
||||
download(new Blob([files[0][1]]), files[0][0]);
|
||||
} else if (files.length > 1) {
|
||||
const zipData: Record<string, Uint8Array> = {};
|
||||
for (const [fn, data] of files) {
|
||||
if (data instanceof Uint8Array) {
|
||||
zipData[fn] = data;
|
||||
} else {
|
||||
const bytes = new Uint8Array(utf8ByteCount(data));
|
||||
utf8Write(bytes, 0, data);
|
||||
zipData[fn] = bytes;
|
||||
}
|
||||
}
|
||||
await ctx.update({ message: `Compressing Data...`, isIndeterminate: true, canAbort: false });
|
||||
const buffer = await zip(ctx, zipData);
|
||||
download(new Blob([new Uint8Array(buffer, 0, buffer.byteLength)]), `structures_${getFormattedTime()}.zip`);
|
||||
}
|
||||
|
||||
plugin.log.info(`[Export] Done.`);
|
||||
});
|
||||
}
|
||||
30
src/extensions/model-export/index.ts
Normal file
30
src/extensions/model-export/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
|
||||
import { ModelExportUI } from './ui';
|
||||
|
||||
export const ModelExport = PluginBehavior.create<{}>({
|
||||
name: 'extension-model-export',
|
||||
category: 'misc',
|
||||
display: {
|
||||
name: 'Model Export'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{}> {
|
||||
register(): void {
|
||||
this.ctx.customStructureControls.set('model-export', ModelExportUI as any);
|
||||
}
|
||||
|
||||
update() {
|
||||
return false;
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.ctx.customStructureControls.delete('model-export');
|
||||
}
|
||||
},
|
||||
params: () => ({})
|
||||
});
|
||||
69
src/extensions/model-export/ui.tsx
Normal file
69
src/extensions/model-export/ui.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
|
||||
import { Button } from '../../mol-plugin-ui/controls/common';
|
||||
import { GetAppSvg } from '../../mol-plugin-ui/controls/icons';
|
||||
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
|
||||
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { exportHierarchy } from './export';
|
||||
|
||||
export class ModelExportUI extends CollapsableControls<{}, {}> {
|
||||
protected defaultState(): CollapsableState {
|
||||
return {
|
||||
header: 'Export Models',
|
||||
isCollapsed: true,
|
||||
brand: { accent: 'cyan', svg: GetAppSvg }
|
||||
};
|
||||
}
|
||||
protected renderControls(): JSX.Element | null {
|
||||
return <ExportControls plugin={this.plugin} />;
|
||||
}
|
||||
}
|
||||
|
||||
const Params = {
|
||||
format: PD.Select<'cif' | 'bcif'>('cif', [['cif', 'mmCIF'], ['bcif', 'Binary mmCIF']])
|
||||
};
|
||||
const DefaultParams = PD.getDefaultValues(Params);
|
||||
|
||||
function ExportControls({ plugin }: { plugin: PluginContext }) {
|
||||
const [params, setParams] = useState(DefaultParams);
|
||||
const [exporting, setExporting] = useState(false);
|
||||
useBehavior(plugin.managers.structure.hierarchy.behaviors.selection); // triggers UI update
|
||||
const isBusy = useBehavior(plugin.behaviors.state.isBusy);
|
||||
const hierarchy = plugin.managers.structure.hierarchy.current;
|
||||
|
||||
let label: string = 'Nothing to Export';
|
||||
if (hierarchy.structures.length === 1) {
|
||||
label = 'Export';
|
||||
} if (hierarchy.structures.length > 1) {
|
||||
label = 'Export (as ZIP)';
|
||||
}
|
||||
|
||||
const onExport = async () => {
|
||||
setExporting(true);
|
||||
try {
|
||||
await exportHierarchy(plugin, { format: params.format });
|
||||
} finally {
|
||||
setExporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return <>
|
||||
<ParameterControls params={Params} values={params} onChangeValues={setParams} isDisabled={isBusy || exporting} />
|
||||
<Button
|
||||
onClick={onExport}
|
||||
style={{ marginTop: 1 }}
|
||||
disabled={isBusy || hierarchy.structures.length === 0 || exporting}
|
||||
commit={hierarchy.structures.length ? 'on' : 'off'}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
</>;
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export class Mp4Controls extends PluginComponent {
|
||||
canApply: this.ev.behavior<PluginStateAnimation.CanApply>({ canApply: false }),
|
||||
info: this.ev.behavior<Mp4AnimationInfo>({ width: 0, height: 0 }),
|
||||
params: this.ev.behavior<PD.Values<typeof Mp4AnimationParams>>(PD.getDefaultValues(Mp4AnimationParams))
|
||||
}
|
||||
};
|
||||
|
||||
setCurrent(name?: string) {
|
||||
const anim = this.animations.find(a => a.name === name);
|
||||
@@ -73,7 +73,7 @@ export class Mp4Controls extends PluginComponent {
|
||||
const filename = anim.anim.display.name.toLowerCase().replace(/\s/g, '-').replace(/[^a-z0-9_\-]/g, '');
|
||||
return { movie, filename: `${this.plugin.helpers.viewportScreenshot?.getFilename('')}_${filename}.mp4` };
|
||||
} catch (e) {
|
||||
this.plugin.log.error('' + e);
|
||||
this.plugin.log.error('Error during animation export');
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
@@ -125,17 +125,20 @@ export class Mp4Controls extends PluginComponent {
|
||||
this.subscribe(this.plugin.canvas3d?.resized!, () => this.syncInfo());
|
||||
this.subscribe(this.plugin.helpers.viewportScreenshot?.events.previewed!, () => this.syncInfo());
|
||||
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, b => {
|
||||
const anim = this.current;
|
||||
if (!b && anim) {
|
||||
this.behaviors.canApply.next(anim.anim.canApply?.(this.plugin) ?? { canApply: true });
|
||||
}
|
||||
});
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, b => this.updateCanApply(b));
|
||||
this.subscribe(this.plugin.managers.snapshot.events.changed, b => this.updateCanApply(b));
|
||||
|
||||
this.sync();
|
||||
this.syncInfo();
|
||||
}
|
||||
|
||||
private updateCanApply(b?: any) {
|
||||
const anim = this.current;
|
||||
if (!b && anim) {
|
||||
this.behaviors.canApply.next(anim.anim.canApply?.(this.plugin) ?? { canApply: true });
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private plugin: PluginContext) {
|
||||
super();
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export async function encodeMp4Animation<A extends PluginStateAnimation>(plugin:
|
||||
await plugin.managers.animation.play(params.animation.definition, params.animation.params);
|
||||
stoppedAnimation = false;
|
||||
for (let i = 0; i <= N; i++) {
|
||||
await loop.tick(i * dt, { isSynchronous: true, manualDraw: true });
|
||||
await loop.tick(i * dt, { isSynchronous: true, animation: { currentFrame: i, frameCount: N }, manualDraw: true });
|
||||
|
||||
const image = params.pass.getImageData(width, height, normalizedViewport);
|
||||
encoder.addFrameRgba(image.data);
|
||||
|
||||
@@ -102,21 +102,23 @@ export class Mp4EncoderUI extends CollapsableControls<{}, State> {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
this._controls?.dispose();
|
||||
this._controls = void 0;
|
||||
}
|
||||
|
||||
save = () => {
|
||||
download(new Blob([this.state.data!.movie]), this.state.data!.filename);
|
||||
}
|
||||
};
|
||||
|
||||
generate = async () => {
|
||||
try {
|
||||
this.setState({ busy: true });
|
||||
const data = await this.controls.render();
|
||||
this.setState({ busy: false, data });
|
||||
} catch {
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export namespace PDBePreferredAssembly {
|
||||
if (model.customProperties.has(Descriptor)) return true;
|
||||
|
||||
let asmName: string | undefined = fromCifData(model);
|
||||
if (asmName === void 0 && params.PDBe_apiSourceJson) {
|
||||
if (asmName === void 0 && params.PDBe_apiSourceJson) {
|
||||
const data = await params.PDBe_apiSourceJson(model);
|
||||
if (!data) return false;
|
||||
asmName = asmNameFromJson(model, data);
|
||||
|
||||
@@ -21,7 +21,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
|
||||
|
||||
private provider = StructureQualityReportProvider
|
||||
private provider = StructureQualityReportProvider;
|
||||
|
||||
private labelPDBeValidation = {
|
||||
label: (loci: Loci): string | undefined => {
|
||||
@@ -42,7 +42,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
|
||||
default: return void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
register(): void {
|
||||
this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
|
||||
@@ -52,7 +52,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean, showTooltip: boolean }) {
|
||||
let updated = this.params.autoAttach !== p.autoAttach;
|
||||
const updated = this.params.autoAttach !== p.autoAttach;
|
||||
this.params.autoAttach = p.autoAttach;
|
||||
this.params.showTooltip = p.showTooltip;
|
||||
this.ctx.customModelProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
|
||||
|
||||
@@ -87,7 +87,7 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
|
||||
};
|
||||
}
|
||||
|
||||
export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Params, 'pdbe-structure-quality-report'> = {
|
||||
export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Params, 'pdbe-structure-quality-report'> = {
|
||||
name: 'pdbe-structure-quality-report',
|
||||
label: 'Structure Quality Report',
|
||||
category: ColorTheme.Category.Validation,
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace StructureQualityReport {
|
||||
}
|
||||
|
||||
export function isApplicable(model?: Model): boolean {
|
||||
return !!model && Model.isFromPdbArchive(model);
|
||||
return !!model && Model.hasPdbId(model);
|
||||
}
|
||||
|
||||
export const Schema = {
|
||||
@@ -73,7 +73,7 @@ namespace StructureQualityReport {
|
||||
}
|
||||
|
||||
export function fromCif(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): StructureQualityReport | undefined {
|
||||
let info = PropertyWrapper.tryGetInfoFromCif('pdbe_structure_quality_report', model);
|
||||
const info = PropertyWrapper.tryGetInfoFromCif('pdbe_structure_quality_report', model);
|
||||
if (!info) return;
|
||||
const data = getCifData(model);
|
||||
const issueMap = createIssueMapFromCif(model, data.residues, data.groups);
|
||||
|
||||
@@ -27,7 +27,7 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
|
||||
description: 'Assembly Symmetry data calculated with BioJava, obtained via RCSB PDB.'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> {
|
||||
private provider = AssemblySymmetryProvider
|
||||
private provider = AssemblySymmetryProvider;
|
||||
|
||||
register(): void {
|
||||
this.ctx.state.data.actions.add(InitAssemblySymmetry3D);
|
||||
@@ -47,7 +47,7 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean }) {
|
||||
let updated = this.params.autoAttach !== p.autoAttach;
|
||||
const updated = this.params.autoAttach !== p.autoAttach;
|
||||
this.params.autoAttach = p.autoAttach;
|
||||
this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
|
||||
return updated;
|
||||
@@ -85,7 +85,7 @@ export const InitAssemblySymmetry3D = StateAction.build({
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(a.data).value;
|
||||
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
|
||||
await AssemblySymmetryProvider.attach(propCtx, a.data, { symmetryIndex });
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
plugin.log.error(`Assembly Symmetry: ${e}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export namespace AssemblySymmetry {
|
||||
export function isApplicable(structure?: Structure): boolean {
|
||||
return (
|
||||
!!structure && structure.models.length === 1 &&
|
||||
Model.isFromPdbArchive(structure.models[0]) &&
|
||||
Model.hasPdbId(structure.models[0]) &&
|
||||
isBiologicalAssembly(structure)
|
||||
);
|
||||
}
|
||||
@@ -130,7 +130,7 @@ export function getSymmetrySelectParam(structure?: Structure) {
|
||||
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
|
||||
const { symbol, kind } = assemblySymmetryData[i];
|
||||
if (symbol !== 'C1') {
|
||||
options.push([ i, `${i + 1}: ${symbol} ${kind}` ]);
|
||||
options.push([i, `${i + 1}: ${symbol} ${kind}`]);
|
||||
}
|
||||
}
|
||||
if (options.length > 1) {
|
||||
|
||||
@@ -310,7 +310,7 @@ function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.Rota
|
||||
}
|
||||
}
|
||||
|
||||
const unitCircleDirections = (function() {
|
||||
const unitCircleDirections = (function () {
|
||||
const dirs: Vec3[] = [];
|
||||
const circle = polygon(12, false, 1);
|
||||
for (let i = 0, il = circle.length; i < il; i += 3) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
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 { 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';
|
||||
@@ -118,7 +118,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
|
||||
|
||||
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);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
schema: https://data.rcsb.org/graphql
|
||||
documents: './src/extensions/rcsb/graphql/symmetry.gql.ts'
|
||||
generates:
|
||||
'./src/extensions/rcsb/graphql/types.ts':
|
||||
plugins:
|
||||
- add: '/* eslint-disable */'
|
||||
- time
|
||||
- typescript
|
||||
- typescript-operations
|
||||
config:
|
||||
immutableTypes: true
|
||||
skipTypename: true
|
||||
'./src/extensions/rcsb/graphql/types.ts':
|
||||
plugins:
|
||||
- add:
|
||||
content: '/* eslint-disable */'
|
||||
- time
|
||||
- typescript
|
||||
- typescript-operations
|
||||
config:
|
||||
immutableTypes: true
|
||||
skipTypename: true
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user