Compare commits
1940 Commits
clamp-cube
...
v5.0.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f879519700 | ||
|
|
c6e175e5da | ||
|
|
add75bf9c9 | ||
|
|
57cbcd5fbf | ||
|
|
0a33936e06 | ||
|
|
7291025e09 | ||
|
|
86da258280 | ||
|
|
477a80d1ca | ||
|
|
86b68018a9 | ||
|
|
da095d6ef9 | ||
|
|
dc304b9e08 | ||
|
|
c905fa17c4 | ||
|
|
a06c64e8e0 | ||
|
|
f5441290dd | ||
|
|
9f23124317 | ||
|
|
8299cd638c | ||
|
|
50cb08e74d | ||
|
|
89552652ba | ||
|
|
37ce577813 | ||
|
|
4d9a003141 | ||
|
|
6f0311a53f | ||
|
|
bfd2d6b055 | ||
|
|
3072e60709 | ||
|
|
62ed8d10e3 | ||
|
|
13d3c34864 | ||
|
|
cac433efca | ||
|
|
b25ffe7151 | ||
|
|
31074dc74c | ||
|
|
c98c01a076 | ||
|
|
8966fc9396 | ||
|
|
fdbdc551e8 | ||
|
|
bb232ac3a4 | ||
|
|
735c25ef8d | ||
|
|
298043313a | ||
|
|
77cd181b91 | ||
|
|
b5bee042e8 | ||
|
|
4faf17ddc7 | ||
|
|
28774b2277 | ||
|
|
6a7444f44e | ||
|
|
15bfa8416a | ||
|
|
e6895ec833 | ||
|
|
2099ad728a | ||
|
|
72ae3fae65 | ||
|
|
bb5ad78681 | ||
|
|
f10e88612f | ||
|
|
a2e582d4a9 | ||
|
|
572874f4ae | ||
|
|
b9c0347497 | ||
|
|
089148198f | ||
|
|
6fc04c3294 | ||
|
|
dc55577e22 | ||
|
|
f7ba7c0511 | ||
|
|
ed5374fab9 | ||
|
|
9a04b4f0df | ||
|
|
9350e539b6 | ||
|
|
c38377af46 | ||
|
|
9804febd95 | ||
|
|
7936dc1840 | ||
|
|
a033a8be36 | ||
|
|
4b84c6dcba | ||
|
|
309d792fdb | ||
|
|
c437254680 | ||
|
|
6fbf7c7a22 | ||
|
|
86a7520b90 | ||
|
|
cd10043447 | ||
|
|
146e95cb23 | ||
|
|
13b1e5d59c | ||
|
|
ae3efa53d6 | ||
|
|
2e67fbe870 | ||
|
|
56df6f82a7 | ||
|
|
fdd874b7a6 | ||
|
|
f142c3ef1b | ||
|
|
978b53e7d8 | ||
|
|
2f3197479d | ||
|
|
6536d0ab91 | ||
|
|
3bee224e7d | ||
|
|
3e63137977 | ||
|
|
38d6bc6c27 | ||
|
|
fafe22d56b | ||
|
|
a6a92bcf91 | ||
|
|
82c681f445 | ||
|
|
fbbd58b4db | ||
|
|
2dc13f082c | ||
|
|
ab5eb5993d | ||
|
|
2384003f5d | ||
|
|
3675c0afe0 | ||
|
|
d9bae488e9 | ||
|
|
e31e5321ba | ||
|
|
8c7f8b8a56 | ||
|
|
e4dfb5148c | ||
|
|
39e2591b60 | ||
|
|
f8a5237024 | ||
|
|
6c2d5b9da7 | ||
|
|
e128d85356 | ||
|
|
08a929bb2f | ||
|
|
5a54b3ef66 | ||
|
|
a0c897547a | ||
|
|
89ce8394fd | ||
|
|
ea0331e95c | ||
|
|
9f220b55c2 | ||
|
|
acf248d58f | ||
|
|
c83b859766 | ||
|
|
33a2564893 | ||
|
|
d409c4f5ea | ||
|
|
ab61e31230 | ||
|
|
ae9c2dd9d8 | ||
|
|
c17edb4928 | ||
|
|
528377eb47 | ||
|
|
c9819369d0 | ||
|
|
cdbbbfa6dd | ||
|
|
a1e31c79e9 | ||
|
|
e027fe46c1 | ||
|
|
05c4006e9d | ||
|
|
191ea65c9d | ||
|
|
3c1ee16376 | ||
|
|
9ac34ee13b | ||
|
|
6778452d07 | ||
|
|
7e01af1e0d | ||
|
|
85469cbf28 | ||
|
|
299bdc72cd | ||
|
|
ae9f879139 | ||
|
|
b50d83d6ea | ||
|
|
2d99d8a1d0 | ||
|
|
ea00cca1c8 | ||
|
|
27c3b4e698 | ||
|
|
52942e7021 | ||
|
|
5904f694b5 | ||
|
|
92c0b82784 | ||
|
|
e1226fa384 | ||
|
|
ac2f7d1c38 | ||
|
|
ae1742f68e | ||
|
|
04e2da86fd | ||
|
|
510182ff60 | ||
|
|
4e1da19bdd | ||
|
|
a3eae15446 | ||
|
|
4334f4d1fa | ||
|
|
e33ed54121 | ||
|
|
ae8f037192 | ||
|
|
01271941dd | ||
|
|
7f8be5b8c6 | ||
|
|
2ab6e4b2e7 | ||
|
|
aa22840b12 | ||
|
|
c1e33fac94 | ||
|
|
a7336095ca | ||
|
|
4a88546181 | ||
|
|
edbc70cf6e | ||
|
|
c22ad2910c | ||
|
|
28a2b52e3c | ||
|
|
449d572ed5 | ||
|
|
470227af43 | ||
|
|
a0ccf46939 | ||
|
|
0ce8931fc5 | ||
|
|
3ddb29fc6f | ||
|
|
1a0c65df21 | ||
|
|
daad1923ea | ||
|
|
f34f879cf1 | ||
|
|
f47b76c8af | ||
|
|
6ee9eb8b60 | ||
|
|
915703a46d | ||
|
|
61c3c19ae3 | ||
|
|
6da9557531 | ||
|
|
29e6d69d21 | ||
|
|
6b2b87e6c5 | ||
|
|
5299d5c0c4 | ||
|
|
7bab95f4cc | ||
|
|
35e78ce638 | ||
|
|
3abbcb6949 | ||
|
|
c3fc893ad0 | ||
|
|
80415a2771 | ||
|
|
bfef69e2e4 | ||
|
|
a265a579be | ||
|
|
c2af1b0b22 | ||
|
|
b739876726 | ||
|
|
56851cc328 | ||
|
|
b719568555 | ||
|
|
f842f19912 | ||
|
|
d6b0dd910c | ||
|
|
395eb8e8d0 | ||
|
|
e4376c6737 | ||
|
|
6f23fcba8a | ||
|
|
7d19bfdb9b | ||
|
|
6e4065f779 | ||
|
|
c42a68b560 | ||
|
|
c86d913b83 | ||
|
|
0f6fa5fe15 | ||
|
|
f664cb02b1 | ||
|
|
00a53de6e2 | ||
|
|
b1ce5c158e | ||
|
|
26826b61f9 | ||
|
|
f44f954a76 | ||
|
|
78aae8a2b4 | ||
|
|
92267d3264 | ||
|
|
73ed45564e | ||
|
|
8bc2ebbeff | ||
|
|
5306d5d15a | ||
|
|
3c3fb461c8 | ||
|
|
62b3281282 | ||
|
|
e85fadf15b | ||
|
|
a8a84e1dbf | ||
|
|
153599ef89 | ||
|
|
b67eda7cb5 | ||
|
|
8b431c50be | ||
|
|
e195b048a1 | ||
|
|
ae5bb81b27 | ||
|
|
8c04c57bc5 | ||
|
|
ec46a444f1 | ||
|
|
559e0326b6 | ||
|
|
82b93bc2a8 | ||
|
|
62f940bc48 | ||
|
|
4e0be8e7b4 | ||
|
|
128502edf0 | ||
|
|
aad4d4a86c | ||
|
|
9bc7e27243 | ||
|
|
a5111356c1 | ||
|
|
9b11f7ffde | ||
|
|
93ce6d2807 | ||
|
|
5c9d5d3a3d | ||
|
|
f40307db39 | ||
|
|
4e6000fa6c | ||
|
|
26e5817bf2 | ||
|
|
8469be80d0 | ||
|
|
029edc95c8 | ||
|
|
dd9aaf055f | ||
|
|
fcfb2d940c | ||
|
|
f5b5109d0f | ||
|
|
ca99c800f1 | ||
|
|
dbd5570370 | ||
|
|
3f805c7a82 | ||
|
|
12c71dc5ba | ||
|
|
2fe3a926aa | ||
|
|
60c2096575 | ||
|
|
f4e9df5e4d | ||
|
|
c304b82772 | ||
|
|
9edd171350 | ||
|
|
f7d1bd7c04 | ||
|
|
7422c255ab | ||
|
|
5497215784 | ||
|
|
577bf1c77c | ||
|
|
c9bddccaf7 | ||
|
|
ac292f9267 | ||
|
|
f0b8d75b10 | ||
|
|
0dacbcb3bc | ||
|
|
0789241ea3 | ||
|
|
ddb0799dc4 | ||
|
|
cbfa341fa3 | ||
|
|
1c19bd90df | ||
|
|
300e5c8985 | ||
|
|
0861a78db6 | ||
|
|
a8e403ad85 | ||
|
|
4e350496b2 | ||
|
|
dd21ddcc80 | ||
|
|
a88121f779 | ||
|
|
1ab91d1979 | ||
|
|
267788388d | ||
|
|
43c0333be3 | ||
|
|
3b90a269b0 | ||
|
|
4aa5e1d7fc | ||
|
|
679db48938 | ||
|
|
2f96b42df7 | ||
|
|
dd6f3bd76e | ||
|
|
f1b7e478c7 | ||
|
|
416442aa27 | ||
|
|
a5f65b6e6f | ||
|
|
938ac0cc8f | ||
|
|
7dacf60478 | ||
|
|
9cdb8a3a92 | ||
|
|
242982e661 | ||
|
|
6da20a6989 | ||
|
|
f27b651230 | ||
|
|
7c818c0cc9 | ||
|
|
e7d7ba26b0 | ||
|
|
7e64121059 | ||
|
|
894bba1d3a | ||
|
|
d9db775fe8 | ||
|
|
a7fbc7b4c4 | ||
|
|
c0596298d6 | ||
|
|
8f32dde599 | ||
|
|
4d8f00900d | ||
|
|
9f3c617945 | ||
|
|
f920188cdc | ||
|
|
68b73503bb | ||
|
|
e776138ecd | ||
|
|
bbacd5a9dd | ||
|
|
289ecef1d7 | ||
|
|
52fc3ef750 | ||
|
|
dffe40ac1d | ||
|
|
f834e39ce4 | ||
|
|
2bc9c6fb57 | ||
|
|
6e42c11f5e | ||
|
|
d48feeaa94 | ||
|
|
fd0ca75fc1 | ||
|
|
a270dcb5f5 | ||
|
|
917de1175c | ||
|
|
65945fb904 | ||
|
|
3b7afc6037 | ||
|
|
05d9ca6e68 | ||
|
|
12ee0e0f38 | ||
|
|
dbd29e749e | ||
|
|
a8085111dc | ||
|
|
93798554ac | ||
|
|
ce07c52d9f | ||
|
|
fb7a247f6c | ||
|
|
e9dfe6322d | ||
|
|
079187326a | ||
|
|
4dc9d037a4 | ||
|
|
f36ad9ac28 | ||
|
|
6d392de628 | ||
|
|
d7cd957b42 | ||
|
|
de36612bf1 | ||
|
|
d5154bcff2 | ||
|
|
b44a6fa660 | ||
|
|
5cc28c9471 | ||
|
|
b42a6d4636 | ||
|
|
efd405f44b | ||
|
|
4b3932e9e2 | ||
|
|
dcb8eca29a | ||
|
|
ac0177aef5 | ||
|
|
316013aafd | ||
|
|
040d83e8d4 | ||
|
|
b31ed50b3a | ||
|
|
2a9c4db97f | ||
|
|
fbeda779ac | ||
|
|
89e60cfde9 | ||
|
|
0845f5fd75 | ||
|
|
918b67482f | ||
|
|
3ff3ea2912 | ||
|
|
b2e1d069ba | ||
|
|
0a409c6fdf | ||
|
|
5ce552d2cc | ||
|
|
8bda510378 | ||
|
|
ad1923f57b | ||
|
|
ba38fe2474 | ||
|
|
c53b651472 | ||
|
|
2eb4f77504 | ||
|
|
c09f30a135 | ||
|
|
c60c52f563 | ||
|
|
7e67678dcd | ||
|
|
4ee33c9dcd | ||
|
|
8a0d5eb366 | ||
|
|
e18a3b452a | ||
|
|
38a508fd87 | ||
|
|
0b1fd14e09 | ||
|
|
b883ddd10e | ||
|
|
30557d13ca | ||
|
|
85b72ae3b0 | ||
|
|
2ed165f9a5 | ||
|
|
8c5388a6ea | ||
|
|
703ef6c273 | ||
|
|
0a1c5537d2 | ||
|
|
e65f5b270e | ||
|
|
9185c4592f | ||
|
|
fbe44bfab7 | ||
|
|
f4d44621d6 | ||
|
|
05a87fded9 | ||
|
|
195f7284b5 | ||
|
|
c4a900e2ea | ||
|
|
e1eb686355 | ||
|
|
54b4a01cc3 | ||
|
|
f68a01183d | ||
|
|
057d605135 | ||
|
|
a391bbf786 | ||
|
|
fdc1054060 | ||
|
|
b4238f574a | ||
|
|
965c6a37a9 | ||
|
|
35a9056368 | ||
|
|
fd96973e82 | ||
|
|
8812b0d264 | ||
|
|
597c0dbbe1 | ||
|
|
768d7a2a4d | ||
|
|
30ec53ffa4 | ||
|
|
b79ffd9cfc | ||
|
|
cc7f88fd53 | ||
|
|
57c84d0159 | ||
|
|
4daf409337 | ||
|
|
a17e886ab9 | ||
|
|
ebb9046184 | ||
|
|
cb41c0c7f9 | ||
|
|
bdbc9eab64 | ||
|
|
5f76620ef5 | ||
|
|
b5c1c4d32e | ||
|
|
6e4777355a | ||
|
|
7526535a8b | ||
|
|
2bd84b7e7c | ||
|
|
84e292b3e2 | ||
|
|
152cef9c5b | ||
|
|
d76bc583c0 | ||
|
|
071fb21dd0 | ||
|
|
db8943bcfb | ||
|
|
2d1ce14f2e | ||
|
|
33760b0d37 | ||
|
|
1aa6f30780 | ||
|
|
86c8dd5d74 | ||
|
|
1435a5e6e6 | ||
|
|
c123e55a8d | ||
|
|
c37a7ebf79 | ||
|
|
00e228a834 | ||
|
|
55f40738f2 | ||
|
|
4ffd69750f | ||
|
|
295608baae | ||
|
|
4429b7185f | ||
|
|
84fadc2e5c | ||
|
|
0b3bd885ca | ||
|
|
51d9eda168 | ||
|
|
abe10d5c7c | ||
|
|
e7da2333fe | ||
|
|
3899a95c97 | ||
|
|
12add4d66b | ||
|
|
e16c073639 | ||
|
|
3c5dc56bb2 | ||
|
|
ad2106e6f6 | ||
|
|
dd5aa061b8 | ||
|
|
f69ad14296 | ||
|
|
277254b78e | ||
|
|
3c4f2806e7 | ||
|
|
79612833d4 | ||
|
|
b4772e0cb9 | ||
|
|
003c5a9437 | ||
|
|
ff9fb450fa | ||
|
|
136e996e4f | ||
|
|
a93b53c413 | ||
|
|
0f25421db1 | ||
|
|
cde3a73bba | ||
|
|
c19130c9eb | ||
|
|
54c8801951 | ||
|
|
8371a3e349 | ||
|
|
cca289728c | ||
|
|
d9a44daa5d | ||
|
|
48ee9ef8cb | ||
|
|
ba84081888 | ||
|
|
45d8059ed2 | ||
|
|
6e2d8653ec | ||
|
|
cca6210076 | ||
|
|
9f926757b2 | ||
|
|
87d83d8f9e | ||
|
|
d16076b170 | ||
|
|
cccdc53fd0 | ||
|
|
a312799361 | ||
|
|
60c81e79ba | ||
|
|
bd22db4252 | ||
|
|
36b5a9e181 | ||
|
|
809cca5261 | ||
|
|
7a81ea3ba1 | ||
|
|
afa51b4416 | ||
|
|
95792dd3c8 | ||
|
|
e2bc15ac6b | ||
|
|
4e565808c6 | ||
|
|
b2e2b46280 | ||
|
|
462e675237 | ||
|
|
6e77b4ce71 | ||
|
|
e8bd67c069 | ||
|
|
fe502539f9 | ||
|
|
fe5afa8935 | ||
|
|
20452e762b | ||
|
|
bc2d19338b | ||
|
|
719e141dd9 | ||
|
|
5d9d01d251 | ||
|
|
39ad2f0719 | ||
|
|
4f06f724a4 | ||
|
|
d5a4b266dd | ||
|
|
e1d92a58be | ||
|
|
05ff705c25 | ||
|
|
f1cfb29a03 | ||
|
|
d2f354d949 | ||
|
|
481c6926e7 | ||
|
|
f15da87e13 | ||
|
|
c34aaf7c31 | ||
|
|
fb6815bb7d | ||
|
|
9c78dc76e1 | ||
|
|
62a0a40a49 | ||
|
|
8d61fa17c8 | ||
|
|
a460869d4a | ||
|
|
a9e0d8236c | ||
|
|
fc47276fc3 | ||
|
|
c60334b97b | ||
|
|
36d58d0ff0 | ||
|
|
73529a890b | ||
|
|
b9e88d61a1 | ||
|
|
04bfe71131 | ||
|
|
b16c51825a | ||
|
|
12630dd9f5 | ||
|
|
880b73a3c4 | ||
|
|
63e7ba57bc | ||
|
|
bc2d8a4ce1 | ||
|
|
9f951dbeac | ||
|
|
cba1c23b4d | ||
|
|
d63663a2ea | ||
|
|
41c5ebf1f3 | ||
|
|
757cf0cd13 | ||
|
|
ad8d07cfaa | ||
|
|
d9f7aafd72 | ||
|
|
e0b307d1a8 | ||
|
|
729306f142 | ||
|
|
dc7f745dbe | ||
|
|
8568656d44 | ||
|
|
4dea8849be | ||
|
|
a2056d31bf | ||
|
|
c14344d465 | ||
|
|
b7ba8322d1 | ||
|
|
818a0dac0d | ||
|
|
3f96ba92ce | ||
|
|
b356f217ab | ||
|
|
a968fb0984 | ||
|
|
745d8b80d7 | ||
|
|
12ff3aad93 | ||
|
|
e8501b73a5 | ||
|
|
9c07da6de6 | ||
|
|
8c2e58b67c | ||
|
|
7242494123 | ||
|
|
adfea9f336 | ||
|
|
80d7649dbb | ||
|
|
6e63bb4283 | ||
|
|
ba7a4137fe | ||
|
|
2ca0a4291b | ||
|
|
32a1a35a96 | ||
|
|
df129d8ce3 | ||
|
|
c346da9f6d | ||
|
|
de15a3d05d | ||
|
|
392b42f6f0 | ||
|
|
46a9b587b4 | ||
|
|
1b4d42cc1e | ||
|
|
7c8f6255c5 | ||
|
|
3334a636c5 | ||
|
|
94d52dddda | ||
|
|
b35a73b50f | ||
|
|
7fde6a810d | ||
|
|
843eae1e49 | ||
|
|
8513183684 | ||
|
|
790bebf302 | ||
|
|
0fb76261e8 | ||
|
|
53c69640b7 | ||
|
|
d70cef8ad3 | ||
|
|
a84a23cbcc | ||
|
|
736f2dc657 | ||
|
|
06295fd586 | ||
|
|
e9f4d95dc3 | ||
|
|
223e3b6fbf | ||
|
|
16c967b674 | ||
|
|
a6a1f0621e | ||
|
|
60cb722343 | ||
|
|
2569fe9577 | ||
|
|
be717133ef | ||
|
|
231d585236 | ||
|
|
098faf129c | ||
|
|
0b39ad8341 | ||
|
|
c0117c41e6 | ||
|
|
0ce41e989a | ||
|
|
b6885a0d76 | ||
|
|
125120fcab | ||
|
|
2147a5c3fb | ||
|
|
8c7d5b9585 | ||
|
|
aa4c36885d | ||
|
|
4ee4788378 | ||
|
|
47aea2b12f | ||
|
|
490bc82ee6 | ||
|
|
0d24c636a3 | ||
|
|
5a81b4f375 | ||
|
|
73b90ffb5c | ||
|
|
02e795b265 | ||
|
|
325aa74331 | ||
|
|
1efe2eb329 | ||
|
|
1ba00c7fa8 | ||
|
|
1bfc2fe511 | ||
|
|
1e895f3c8c | ||
|
|
028c283043 | ||
|
|
144ed51100 | ||
|
|
e3c2ec4561 | ||
|
|
84dd957983 | ||
|
|
1093a4f6ad | ||
|
|
c4fdc43aa0 | ||
|
|
15da722af5 | ||
|
|
eec2d2a720 | ||
|
|
1766fad6f7 | ||
|
|
d4775812ad | ||
|
|
6cf887d44d | ||
|
|
bbb2bee2ae | ||
|
|
73763b444e | ||
|
|
9508e01e59 | ||
|
|
375db11e9b | ||
|
|
b1b1972684 | ||
|
|
ce0d4cbc4e | ||
|
|
127d9bc94e | ||
|
|
860df1a898 | ||
|
|
51b36e90f0 | ||
|
|
48b19e149b | ||
|
|
5a87d9dbf5 | ||
|
|
c07b4ba550 | ||
|
|
8a99e3e3fd | ||
|
|
571f54f4e6 | ||
|
|
15cd7b9c13 | ||
|
|
0d21b399b5 | ||
|
|
94ad0bf75c | ||
|
|
2c44286ca5 | ||
|
|
23705727ac | ||
|
|
0a173d230c | ||
|
|
f8987af0e8 | ||
|
|
e046b80bf2 | ||
|
|
f8d6f1d010 | ||
|
|
579190b9ce | ||
|
|
e44e29eb9f | ||
|
|
589cec24e5 | ||
|
|
fd999953f9 | ||
|
|
523dfe7928 | ||
|
|
b2f26e6b1d | ||
|
|
dc45bf3915 | ||
|
|
96e22e25cf | ||
|
|
051beb3c3c | ||
|
|
2ba3d67520 | ||
|
|
cd30d9c1a3 | ||
|
|
7d32aa8276 | ||
|
|
f837b46da1 | ||
|
|
c6107ff694 | ||
|
|
2e7228f88b | ||
|
|
e8825eac5d | ||
|
|
1a88126af8 | ||
|
|
c4a6eba448 | ||
|
|
fc7e9501b2 | ||
|
|
1dfd52db43 | ||
|
|
5510b28656 | ||
|
|
e94abdb159 | ||
|
|
7015607244 | ||
|
|
7ff37d7dcc | ||
|
|
3abc2da106 | ||
|
|
f9c498177a | ||
|
|
872c6483be | ||
|
|
53288e4e9d | ||
|
|
d6b045594c | ||
|
|
aa86111de7 | ||
|
|
040473388e | ||
|
|
f474615729 | ||
|
|
92559e456e | ||
|
|
b2434ea0d0 | ||
|
|
6cf0ce5574 | ||
|
|
518a40f0ba | ||
|
|
387e87bfda | ||
|
|
4fac2a5cd6 | ||
|
|
f5f3ea84d4 | ||
|
|
4b3d470dde | ||
|
|
8513a44e8c | ||
|
|
84b54d97df | ||
|
|
34606f258e | ||
|
|
2c10dd46a0 | ||
|
|
d4c80fc995 | ||
|
|
e1c00f65a5 | ||
|
|
012bc9e8e8 | ||
|
|
a99083107c | ||
|
|
7e93bb0dda | ||
|
|
9735cce043 | ||
|
|
78e1d76f5e | ||
|
|
18b1492d54 | ||
|
|
6116b2fea5 | ||
|
|
6ef8fd2b64 | ||
|
|
9319805d36 | ||
|
|
5027ad37d7 | ||
|
|
70bd0c25c4 | ||
|
|
1a5c7f5437 | ||
|
|
4a9505c334 | ||
|
|
b43ec9ed45 | ||
|
|
eb9c6d542b | ||
|
|
2ec0911821 | ||
|
|
bbb34c8a27 | ||
|
|
1bcb8d6486 | ||
|
|
630b5ca203 | ||
|
|
5b2ed784e1 | ||
|
|
11c9a83ee7 | ||
|
|
2d1b61647a | ||
|
|
fa3797a738 | ||
|
|
fc60c0c980 | ||
|
|
372ca20980 | ||
|
|
b0aad9f1ff | ||
|
|
40e45adbb0 | ||
|
|
5b43a2cee9 | ||
|
|
a319a0daa8 | ||
|
|
79f812d0e1 | ||
|
|
45611a25a5 | ||
|
|
77be659915 | ||
|
|
5986250ed9 | ||
|
|
8156c672b0 | ||
|
|
a443512102 | ||
|
|
4b921319a8 | ||
|
|
6329820a87 | ||
|
|
91e4b0c3d6 | ||
|
|
7666617857 | ||
|
|
19be1090b3 | ||
|
|
354438052e | ||
|
|
b72444b213 | ||
|
|
179078f45c | ||
|
|
1dbc23fe91 | ||
|
|
ff4dec9fea | ||
|
|
6ea51c07b4 | ||
|
|
f4cebb9195 | ||
|
|
cbe5f0dc7c | ||
|
|
0ac8b565b5 | ||
|
|
4f38d4d943 | ||
|
|
0af84eb6b5 | ||
|
|
aa2d19478b | ||
|
|
e035b834a6 | ||
|
|
2b2dfd9245 | ||
|
|
678790efa3 | ||
|
|
a121c5e2cd | ||
|
|
47b242244e | ||
|
|
2b9d3fd33a | ||
|
|
81404036a2 | ||
|
|
e2dc15cf0f | ||
|
|
365a91879f | ||
|
|
e47e0eb51a | ||
|
|
390046e38f | ||
|
|
b8eb5191a2 | ||
|
|
4cc416ca28 | ||
|
|
f4b2458390 | ||
|
|
1cad6eef74 | ||
|
|
2923be6006 | ||
|
|
70959641a1 | ||
|
|
051608f56c | ||
|
|
71c1a4e85b | ||
|
|
ba06c9e413 | ||
|
|
8e4dfd1ffd | ||
|
|
fac8aa529f | ||
|
|
d35b4b5e62 | ||
|
|
dd1789478b | ||
|
|
af27a00a01 | ||
|
|
17cea8f99c | ||
|
|
4f6d5a7dc7 | ||
|
|
857972653e | ||
|
|
98ff0f5c55 | ||
|
|
43803a91ea | ||
|
|
8a1bab8bcb | ||
|
|
17a47faaff | ||
|
|
f793167e91 | ||
|
|
d1c2c8e837 | ||
|
|
5d7ef8196e | ||
|
|
e0715cbf5c | ||
|
|
1af8522de3 | ||
|
|
c6becd5741 | ||
|
|
0ca368f29f | ||
|
|
5039a448ad | ||
|
|
9ef38f02c9 | ||
|
|
66f4ff1140 | ||
|
|
cc077656a9 | ||
|
|
3ef1a2ec0a | ||
|
|
144bf6954e | ||
|
|
50e5538148 | ||
|
|
efe95f92c7 | ||
|
|
09f858a755 | ||
|
|
e7082d4ccc | ||
|
|
732a8f4bd0 | ||
|
|
82ca06b29e | ||
|
|
a05429f13f | ||
|
|
377de7ad40 | ||
|
|
74f4d00c8d | ||
|
|
be3825372e | ||
|
|
d62c5c9050 | ||
|
|
9ba5112beb | ||
|
|
048658ee39 | ||
|
|
2918081dd9 | ||
|
|
1a67868c07 | ||
|
|
525dfaddd2 | ||
|
|
8aa12c0d31 | ||
|
|
8cb464a686 | ||
|
|
e0371d7e32 | ||
|
|
e7da6bc194 | ||
|
|
933869b5e1 | ||
|
|
5d6adc46fe | ||
|
|
b8a98efcaf | ||
|
|
53b358f70a | ||
|
|
bce9e5b0ad | ||
|
|
cf7f9d6aba | ||
|
|
6af1bc5def | ||
|
|
936808e271 | ||
|
|
8b985d0424 | ||
|
|
b290cf121a | ||
|
|
89df6cec42 | ||
|
|
fc5fc7fcdb | ||
|
|
a1de5bb304 | ||
|
|
25c8a41e91 | ||
|
|
959249b572 | ||
|
|
44610b8b1a | ||
|
|
4070453209 | ||
|
|
cf2193f4fc | ||
|
|
bfc0a3d1fe | ||
|
|
53a2155d8c | ||
|
|
f22121521b | ||
|
|
755655d067 | ||
|
|
41ab186fd2 | ||
|
|
9039c653cb | ||
|
|
dde3f4ecff | ||
|
|
64cd05cc14 | ||
|
|
593e8f4993 | ||
|
|
d2d9eb622f | ||
|
|
ce9883517f | ||
|
|
bc2afe1d68 | ||
|
|
5a2ee03b48 | ||
|
|
2d86c76788 | ||
|
|
7f8995a4d8 | ||
|
|
64ab8bf78d | ||
|
|
bf9663e177 | ||
|
|
fb729446e2 | ||
|
|
3e4082bf6e | ||
|
|
5af6a3e967 | ||
|
|
e718835042 | ||
|
|
70f0804e26 | ||
|
|
e613a90754 | ||
|
|
dca6affc84 | ||
|
|
af33516107 | ||
|
|
7148c7197b | ||
|
|
d2192d609a | ||
|
|
46ad8f495f | ||
|
|
65b2b69a64 | ||
|
|
0d9d173ef4 | ||
|
|
6eaf8e1911 | ||
|
|
b11e24cd06 | ||
|
|
1a1bce8193 | ||
|
|
b67f7271fc | ||
|
|
837b838766 | ||
|
|
7dd42421e2 | ||
|
|
258dc637fc | ||
|
|
d6c594395c | ||
|
|
746173fe33 | ||
|
|
e9de12e6a2 | ||
|
|
3bfebceaea | ||
|
|
443fc9c60c | ||
|
|
3220ab6118 | ||
|
|
5c882f1aa5 | ||
|
|
2320518b87 | ||
|
|
2124bead5e | ||
|
|
a6077c7263 | ||
|
|
d98350cfb2 | ||
|
|
ae96b6cdac | ||
|
|
7ba6f5bf4c | ||
|
|
cede59c282 | ||
|
|
87db52c339 | ||
|
|
b3d4500505 | ||
|
|
2935f1340d | ||
|
|
fda3481d15 | ||
|
|
24ad3c875a | ||
|
|
dd31c3041b | ||
|
|
8f3a3dd2be | ||
|
|
6f478a3eb3 | ||
|
|
a36c2feee4 | ||
|
|
a5e2946aa6 | ||
|
|
00428254a8 | ||
|
|
53a57530c5 | ||
|
|
d9d378b249 | ||
|
|
98119787e9 | ||
|
|
b00f650066 | ||
|
|
5ee6bee130 | ||
|
|
734b6001c2 | ||
|
|
24e7456d6d | ||
|
|
0bb596e255 | ||
|
|
31a6eef1a4 | ||
|
|
1deead40a5 | ||
|
|
628f72903d | ||
|
|
5d8a569aef | ||
|
|
9604b89ee0 | ||
|
|
4627d436a6 | ||
|
|
10cdd6a1f0 | ||
|
|
db9cb955b0 | ||
|
|
00ad11dd6b | ||
|
|
932b59d62c | ||
|
|
86b889d48b | ||
|
|
9521d47b4e | ||
|
|
032cf3ad8b | ||
|
|
b39f9a772d | ||
|
|
ebe727ba24 | ||
|
|
e88c2df42f | ||
|
|
79e6a4c95d | ||
|
|
2f56b9c491 | ||
|
|
b8551824bf | ||
|
|
8340fc0a98 | ||
|
|
10a3f92697 | ||
|
|
da9f5927d8 | ||
|
|
990dac6f8d | ||
|
|
1aa9c2fb22 | ||
|
|
77d5c71e61 | ||
|
|
ae1d1a44f6 | ||
|
|
904a409665 | ||
|
|
ab70536820 | ||
|
|
c9ef0171b4 | ||
|
|
682b551b6e | ||
|
|
49cd8f7904 | ||
|
|
a9e1373203 | ||
|
|
368fd3a1d5 | ||
|
|
51ba661e54 | ||
|
|
eb86b83aa5 | ||
|
|
16d5299fbd | ||
|
|
c85aa6f979 | ||
|
|
dfab137696 | ||
|
|
629fc65b81 | ||
|
|
f06064a0c6 | ||
|
|
fa70a0e5eb | ||
|
|
f1a2c5c4ee | ||
|
|
169d6323ca | ||
|
|
c5b3db9960 | ||
|
|
fefed0a5f0 | ||
|
|
d04311a989 | ||
|
|
0273df2c16 | ||
|
|
05494953df | ||
|
|
d6b79de86d | ||
|
|
20327871a8 | ||
|
|
dd0845038d | ||
|
|
d5d3977506 | ||
|
|
1675e18f57 | ||
|
|
d8bfe78b30 | ||
|
|
589d3465ef | ||
|
|
ebc63099b4 | ||
|
|
512a099a28 | ||
|
|
d056bf3549 | ||
|
|
e2c929fa33 | ||
|
|
a619ae6687 | ||
|
|
e40fc34a6f | ||
|
|
2734377d0a | ||
|
|
86575a7a15 | ||
|
|
31227d2050 | ||
|
|
2e73a37de2 | ||
|
|
32c3ef0801 | ||
|
|
b70f2e073d | ||
|
|
03b4aa1b33 | ||
|
|
0a4643ebe4 | ||
|
|
8dc3dae81e | ||
|
|
a1e2b0e5ae | ||
|
|
46af7d03bf | ||
|
|
60550cfea1 | ||
|
|
5de4569ad9 | ||
|
|
908fb0eba9 | ||
|
|
e7e191a907 | ||
|
|
de46c82c78 | ||
|
|
d8f3ab767e | ||
|
|
d34a9be20f | ||
|
|
78628c217f | ||
|
|
d29bf2eec2 | ||
|
|
5db60c2882 | ||
|
|
737846e093 | ||
|
|
2ad551bdc7 | ||
|
|
46fa581f07 | ||
|
|
17dbe4b60e | ||
|
|
aeaf2e799a | ||
|
|
ef16b718c4 | ||
|
|
4b4f6d34a3 | ||
|
|
ab4130d42d | ||
|
|
9e73de89fb | ||
|
|
02cec6f8e6 | ||
|
|
e97a02473f | ||
|
|
827e75ce0e | ||
|
|
22e5c9d65b | ||
|
|
10d9120d37 | ||
|
|
e6c20d35bd | ||
|
|
27bf66038d | ||
|
|
3ce6d89521 | ||
|
|
24608ac355 | ||
|
|
da034d9502 | ||
|
|
581673fb9b | ||
|
|
29cf97e6cf | ||
|
|
f65773d654 | ||
|
|
d11c8c166a | ||
|
|
ae6bd743a8 | ||
|
|
c1654574d0 | ||
|
|
6f506351cd | ||
|
|
db83b97ff9 | ||
|
|
5c818b35ad | ||
|
|
b1ce20f5f5 | ||
|
|
d1cbebf8a7 | ||
|
|
c771cfef99 | ||
|
|
6ee72d5e8c | ||
|
|
b859b3c597 | ||
|
|
8281909620 | ||
|
|
f3c30ae4b3 | ||
|
|
9ad53eb0d5 | ||
|
|
a15e9158b4 | ||
|
|
6bfedc038e | ||
|
|
966293994f | ||
|
|
09f4c857de | ||
|
|
e05bd5f0c5 | ||
|
|
a3cdc2844e | ||
|
|
a51947637c | ||
|
|
72ee428f51 | ||
|
|
4fe203d3ea | ||
|
|
2ec32641ee | ||
|
|
8d5198f15c | ||
|
|
d5154c7391 | ||
|
|
6ee750e6bd | ||
|
|
ea8aa10704 | ||
|
|
19ba76a1f1 | ||
|
|
fc28046df4 | ||
|
|
20f0416a8c | ||
|
|
1298baf60d | ||
|
|
999b86ab81 | ||
|
|
75f2acd8eb | ||
|
|
1541ba10f1 | ||
|
|
00ca25ffd7 | ||
|
|
302e1c659f | ||
|
|
8b4d987f94 | ||
|
|
c383012fd0 | ||
|
|
66b6ddc527 | ||
|
|
15d60c51a9 | ||
|
|
fe82cd4f2c | ||
|
|
b3e792cbc3 | ||
|
|
2f0230dc84 | ||
|
|
45522ad410 | ||
|
|
3fe80fe61a | ||
|
|
ede1a8da07 | ||
|
|
0199afd5f3 | ||
|
|
f8bda3617f | ||
|
|
bffd7d75e0 | ||
|
|
e1315b6642 | ||
|
|
205413e696 | ||
|
|
2c0a486531 | ||
|
|
90a4e019ac | ||
|
|
640426ae16 | ||
|
|
d1e620eed1 | ||
|
|
1a2bad7c77 | ||
|
|
b6e4d7721e | ||
|
|
e5902d15e2 | ||
|
|
4e2b7b5ffc | ||
|
|
bcbab9cb9d | ||
|
|
9a4b0116eb | ||
|
|
60d0de8d88 | ||
|
|
6a520d2877 | ||
|
|
6edbae80db | ||
|
|
aac0abed32 | ||
|
|
6805dd7947 | ||
|
|
6c9254f2b6 | ||
|
|
897d443873 | ||
|
|
f276ea2258 | ||
|
|
58d735996e | ||
|
|
72b66367f3 | ||
|
|
c592a3b93d | ||
|
|
3e6d0c8c62 | ||
|
|
60ed853c02 | ||
|
|
ba58b8e559 | ||
|
|
2c3d122820 | ||
|
|
47f4b3a051 | ||
|
|
daed91e1ea | ||
|
|
3d0b9c98e4 | ||
|
|
966e8e764f | ||
|
|
14441a4ee3 | ||
|
|
e773824fb5 | ||
|
|
cc6a990b85 | ||
|
|
ffa7d66c66 | ||
|
|
abc29ceefb | ||
|
|
e2f137a495 | ||
|
|
ebbe78fb1d | ||
|
|
5d139b6db8 | ||
|
|
8d5aad2be1 | ||
|
|
e8601d3f5e | ||
|
|
c63622677a | ||
|
|
163cee6d66 | ||
|
|
cd1d2010da | ||
|
|
8f0ab2fdf9 | ||
|
|
7dbc8ab4f1 | ||
|
|
6a730a6ce7 | ||
|
|
ca606284b3 | ||
|
|
396c530e13 | ||
|
|
4cc3072165 | ||
|
|
4598841ddc | ||
|
|
728414366d | ||
|
|
1b7b38b47e | ||
|
|
dd15b1ed29 | ||
|
|
446ea9173b | ||
|
|
70f8515e51 | ||
|
|
90fac967f2 | ||
|
|
1796dfd73a | ||
|
|
a73cb1648d | ||
|
|
b715205a04 | ||
|
|
ef17cb2cca | ||
|
|
b7b52f5c7d | ||
|
|
79ed8e7de4 | ||
|
|
60a2eb8a22 | ||
|
|
b833d41c1e | ||
|
|
85d2ed0132 | ||
|
|
2552a5218f | ||
|
|
c4a4562d82 | ||
|
|
a3b9d30c7c | ||
|
|
a3572961ac | ||
|
|
3118a46250 | ||
|
|
d238b8aee9 | ||
|
|
22a57d8f48 | ||
|
|
119e8f63eb | ||
|
|
e6a4122d1c | ||
|
|
9ba5f1f540 | ||
|
|
0a0ac7ee63 | ||
|
|
7670df04ae | ||
|
|
98744af872 | ||
|
|
f4eb509887 | ||
|
|
cbb8c0536d | ||
|
|
d7d798746c | ||
|
|
c39d092fc1 | ||
|
|
102b69961c | ||
|
|
681d64dcd9 | ||
|
|
2083407a84 | ||
|
|
a07f90a405 | ||
|
|
c7ca528778 | ||
|
|
ca36264cf8 | ||
|
|
7e5b181d92 | ||
|
|
c538e1d0c8 | ||
|
|
75e23d73b0 | ||
|
|
39f077b066 | ||
|
|
78874c0024 | ||
|
|
7069760d07 | ||
|
|
df0af2b1ca | ||
|
|
edd6419b2b | ||
|
|
c6210ae1a0 | ||
|
|
446455494e | ||
|
|
c4bc16fe5d | ||
|
|
988cee0047 | ||
|
|
39fa185f3d | ||
|
|
cb0484bca1 | ||
|
|
bf0707a2aa | ||
|
|
9a2191e1cc | ||
|
|
36ead9dda3 | ||
|
|
71e1bb849e | ||
|
|
975cceed77 | ||
|
|
82065dc5b7 | ||
|
|
39ae03ff8c | ||
|
|
45ab88f0a1 | ||
|
|
f0d649f265 | ||
|
|
44ce5df136 | ||
|
|
b00bce69fd | ||
|
|
e2e9e5f6fc | ||
|
|
36cf2853b2 | ||
|
|
efc322a375 | ||
|
|
7e792a4f90 | ||
|
|
44b3a9b121 | ||
|
|
55f72e65dc | ||
|
|
745746f243 | ||
|
|
b5f229ba6d | ||
|
|
d5a47e617a | ||
|
|
a200ca5b21 | ||
|
|
65b52c8ecd | ||
|
|
203fb2f7fe | ||
|
|
492494033f | ||
|
|
9de6d86a0f | ||
|
|
23fdbdea54 | ||
|
|
8fa9c19346 | ||
|
|
ab10007d60 | ||
|
|
7a6094298b | ||
|
|
ab34a59677 | ||
|
|
7a96cdd52d | ||
|
|
65cad5ea4d | ||
|
|
fe433fc7b0 | ||
|
|
ba173978d3 | ||
|
|
b3aed2ead7 | ||
|
|
c57774a62e | ||
|
|
199b767bbd | ||
|
|
0c3131ff56 | ||
|
|
7433fe07f1 | ||
|
|
a765ba8e3b | ||
|
|
8594ce80a9 | ||
|
|
915797c4a4 | ||
|
|
70ebdc6b80 | ||
|
|
44c69f538b | ||
|
|
b53a52b04d | ||
|
|
e4396039fd | ||
|
|
296b6902b1 | ||
|
|
0795f06f28 | ||
|
|
a01f159889 | ||
|
|
4ea2eadb78 | ||
|
|
e548a3ed85 | ||
|
|
0e326bd4de | ||
|
|
c1de7df9fb | ||
|
|
3addc567a9 | ||
|
|
4d2d127dcc | ||
|
|
fc44e66b26 | ||
|
|
98f3f5a23b | ||
|
|
f2f10d0cb5 | ||
|
|
aed1056d6c | ||
|
|
be47ac09c9 | ||
|
|
d5e7797a40 | ||
|
|
0aeac628c7 | ||
|
|
668d617cd7 | ||
|
|
62ed993f0d | ||
|
|
aa0a008a41 | ||
|
|
89f01f202d | ||
|
|
733190f7a0 | ||
|
|
50429aacfa | ||
|
|
fa541bdbd3 | ||
|
|
77d173afed | ||
|
|
1415a21c09 | ||
|
|
dd2593475a | ||
|
|
a934001ae8 | ||
|
|
e5d4606437 | ||
|
|
350dddd83a | ||
|
|
7566dd4e9a | ||
|
|
cb04c74c60 | ||
|
|
ad937f8fe5 | ||
|
|
66017cf166 | ||
|
|
898f0d6462 | ||
|
|
cf90418f89 | ||
|
|
fb16cd0070 | ||
|
|
5ca126be94 | ||
|
|
7a8de81e6f | ||
|
|
143760f7db | ||
|
|
c427549b8d | ||
|
|
310300bde8 | ||
|
|
11604b9e8f | ||
|
|
973b326157 | ||
|
|
c37c63d990 | ||
|
|
cc1bf482f2 | ||
|
|
61a351b3d4 | ||
|
|
9e91a242bf | ||
|
|
c3daa1a162 | ||
|
|
fe086fb62e | ||
|
|
c2217829a3 | ||
|
|
f30686917f | ||
|
|
7b18545e1d | ||
|
|
6333c8073f | ||
|
|
2801bcf111 | ||
|
|
8a2461e157 | ||
|
|
0a081e2a8a | ||
|
|
700a3fe95c | ||
|
|
febc634d8b | ||
|
|
0105f75bb6 | ||
|
|
4cc2073eaa | ||
|
|
b657fa15f5 | ||
|
|
72446d06ed | ||
|
|
dd7499b7f6 | ||
|
|
9ac204cb6e | ||
|
|
73378bbe9d | ||
|
|
9b5fd2595c | ||
|
|
bca2073ed0 | ||
|
|
f264e4d6b8 | ||
|
|
c86c00c958 | ||
|
|
bbce807d5e | ||
|
|
795222b5b4 | ||
|
|
25eb4450ad | ||
|
|
140df13dae | ||
|
|
8b132d599e | ||
|
|
cbaf5129f9 | ||
|
|
117e2b2a1a | ||
|
|
301b9287ea | ||
|
|
81a738c6a6 | ||
|
|
f4d15a5a31 | ||
|
|
792cd513a8 | ||
|
|
14e6172c33 | ||
|
|
17e278cefb | ||
|
|
911433e056 | ||
|
|
6b585cf0d6 | ||
|
|
86211aaf3a | ||
|
|
071623f5b6 | ||
|
|
21e514ec1e | ||
|
|
9bc0ab12e7 | ||
|
|
1d1bd05400 | ||
|
|
faa750bbf9 | ||
|
|
e92e5c5cef | ||
|
|
b49230ea1f | ||
|
|
44ebc1d39a | ||
|
|
8d8e45f4ce | ||
|
|
898d877aa1 | ||
|
|
85dba9b1a4 | ||
|
|
6b5e90c5fa | ||
|
|
e231fbf3d7 | ||
|
|
0ee8525b2d | ||
|
|
106ee614e7 | ||
|
|
34056751f9 | ||
|
|
cc737192ca | ||
|
|
1afea8a86a | ||
|
|
96d5bf2447 | ||
|
|
f9265a7049 | ||
|
|
5c57137890 | ||
|
|
4e71618d0f | ||
|
|
de660cc233 | ||
|
|
616a1dabfa | ||
|
|
46ea39703f | ||
|
|
6cf20d0c44 | ||
|
|
0737e23b70 | ||
|
|
70d0c15d28 | ||
|
|
9272c8c5ec | ||
|
|
a3349f82fc | ||
|
|
11aeb6daf4 | ||
|
|
daec7e31ff | ||
|
|
c98cdc9360 | ||
|
|
4d399edbdd | ||
|
|
64598eba96 | ||
|
|
aa25874775 | ||
|
|
dccc06d497 | ||
|
|
c000526cf8 | ||
|
|
2166ab455c | ||
|
|
4de9ce01fc | ||
|
|
f543fd5683 | ||
|
|
8535013ee5 | ||
|
|
320ab77f8e | ||
|
|
982feef0c6 | ||
|
|
bd6d04cefb | ||
|
|
250013570d | ||
|
|
5e1c351efc | ||
|
|
35739ab71b | ||
|
|
bb765968f1 | ||
|
|
eb25d66359 | ||
|
|
3db28708c3 | ||
|
|
4fb18b9afd | ||
|
|
28693177cd | ||
|
|
cd2ac77469 | ||
|
|
8777f907ee | ||
|
|
5a6ac41b28 | ||
|
|
61a294c889 | ||
|
|
71fbd6baab | ||
|
|
33430a836a | ||
|
|
2ac34f96f5 | ||
|
|
de5d60f4d2 | ||
|
|
f428e9f39e | ||
|
|
72e3a31750 | ||
|
|
5bcb0b19e5 | ||
|
|
0277581684 | ||
|
|
d508988ba4 | ||
|
|
7352799270 | ||
|
|
7b524dda7c | ||
|
|
2d26425cbe | ||
|
|
f6030aee25 | ||
|
|
609e03f7d2 | ||
|
|
ba12a8bbee | ||
|
|
947f293844 | ||
|
|
fbff0e769c | ||
|
|
3798223d39 | ||
|
|
ac9c23dc65 | ||
|
|
096f492ccb | ||
|
|
ba96da9354 | ||
|
|
6c1d17bac5 | ||
|
|
ad2ccf4e07 | ||
|
|
dc1b7b4693 | ||
|
|
59e4e2b31d | ||
|
|
d2483dc449 | ||
|
|
86dcf22728 | ||
|
|
f47823577d | ||
|
|
671a982463 | ||
|
|
4244c6f5e1 | ||
|
|
d26946e9ee | ||
|
|
cd045a6b48 | ||
|
|
2407729d27 | ||
|
|
1aa22b9fa0 | ||
|
|
b00b0827c8 | ||
|
|
fe4df71d02 | ||
|
|
8f03e07632 | ||
|
|
5958b31a6d | ||
|
|
35c9f39a69 | ||
|
|
7dd420cc18 | ||
|
|
ace991da5d | ||
|
|
5e9bc89c75 | ||
|
|
1d434c259a | ||
|
|
6d193edd68 | ||
|
|
8d0c80f270 | ||
|
|
2ae36e181d | ||
|
|
52692a405d | ||
|
|
9bf859d6ed | ||
|
|
207230d565 | ||
|
|
b7a673f38e | ||
|
|
2204e4e0d0 | ||
|
|
6276365766 | ||
|
|
505b04c92d | ||
|
|
0a91a35cf3 | ||
|
|
fc84dcb037 | ||
|
|
103b0dcebd | ||
|
|
2f29ff7314 | ||
|
|
b37f043876 | ||
|
|
f0e725f65c | ||
|
|
23a34e2df1 | ||
|
|
d11e242b70 | ||
|
|
d9af0ca068 | ||
|
|
b7f10acbf0 | ||
|
|
43749ccdbd | ||
|
|
3bf4a8f8e6 | ||
|
|
f0ae1b3347 | ||
|
|
99809d25b9 | ||
|
|
e83c0af67c | ||
|
|
2ddf94313e | ||
|
|
da5965c956 | ||
|
|
31be0af3c9 | ||
|
|
38c550b245 | ||
|
|
95a7a2cef9 | ||
|
|
5934f355c2 | ||
|
|
225d051dd6 | ||
|
|
1a1ec51736 | ||
|
|
299aae56c1 | ||
|
|
781824c961 | ||
|
|
fae4aae99f | ||
|
|
6acc87abc6 | ||
|
|
930cfa2590 | ||
|
|
35439f01aa | ||
|
|
af489e8cc5 | ||
|
|
a15f2ed456 | ||
|
|
9c921fd061 | ||
|
|
e719c54e2b | ||
|
|
71121e52af | ||
|
|
c08155717f | ||
|
|
de4164e7a4 | ||
|
|
e34d1242a9 | ||
|
|
5b82641018 | ||
|
|
de2f0c27b2 | ||
|
|
71e2afe781 | ||
|
|
3cba621fcf | ||
|
|
d79a2077c1 | ||
|
|
6925547b5f | ||
|
|
84aae8cf0a | ||
|
|
bdb42e39ec | ||
|
|
6edd54ee6d | ||
|
|
198f884d8b | ||
|
|
2c7d0a6721 | ||
|
|
7ef15ede0d | ||
|
|
3d96298b55 | ||
|
|
964f045e56 | ||
|
|
d3364ac109 | ||
|
|
a5b963c919 | ||
|
|
d6fcbbf543 | ||
|
|
f2e7e2eaf2 | ||
|
|
01c4c63114 | ||
|
|
22f9bc4ff1 | ||
|
|
c6c4350638 | ||
|
|
a17a0c4527 | ||
|
|
8e507012c1 | ||
|
|
beb4351dc9 | ||
|
|
afbb940721 | ||
|
|
f5c619a4c7 | ||
|
|
1b0401dff5 | ||
|
|
649e779100 | ||
|
|
18c4de4c0f | ||
|
|
f61e0e72a8 | ||
|
|
803f75fdde | ||
|
|
718d314eda | ||
|
|
adab6b0a6a | ||
|
|
d295ed2eca | ||
|
|
d18cbfa8cf | ||
|
|
59f881c4be | ||
|
|
0295e0ef63 | ||
|
|
dcdb95a055 | ||
|
|
e379d27722 | ||
|
|
41fbe0d2b7 | ||
|
|
1231666b06 | ||
|
|
b302bb8455 | ||
|
|
6e82405600 | ||
|
|
a678893bdb | ||
|
|
430348a3cd | ||
|
|
315401c166 | ||
|
|
b309c545f5 | ||
|
|
60b5d2d39b | ||
|
|
eb749a2a16 | ||
|
|
6db96001a3 | ||
|
|
257370ad58 | ||
|
|
a12820bab9 | ||
|
|
557bf63b55 | ||
|
|
0e32e0a785 | ||
|
|
f2c607a4b2 | ||
|
|
0d12a9e118 | ||
|
|
02f418c8c5 | ||
|
|
c59ae908b8 | ||
|
|
ba618c9e4a | ||
|
|
de5b7f31f9 | ||
|
|
e264abb57c | ||
|
|
46a84799b1 | ||
|
|
ebd3ebe7b2 | ||
|
|
af451db432 | ||
|
|
804117475b | ||
|
|
09ab8d6219 | ||
|
|
f3a5369690 | ||
|
|
8bf2fe624d | ||
|
|
5ac435c6d1 | ||
|
|
aab3759da2 | ||
|
|
db1174bd32 | ||
|
|
50c1b667c5 | ||
|
|
360031d37c | ||
|
|
9ec873e0db | ||
|
|
c830a720b0 | ||
|
|
1aa7d1e0f7 | ||
|
|
c5c8de8628 | ||
|
|
74c6d6f5a1 | ||
|
|
2bff0faff7 | ||
|
|
4df028aa77 | ||
|
|
47c2d153aa | ||
|
|
18be09e9d5 | ||
|
|
55e940e88c | ||
|
|
7a3fc2047e | ||
|
|
e246f4e5ca | ||
|
|
5e1bb4b106 | ||
|
|
0b2889bb99 | ||
|
|
2994caf411 | ||
|
|
e157993a0f | ||
|
|
6c7c9afc34 | ||
|
|
2d0b17d93c | ||
|
|
033c613c89 | ||
|
|
1985eb59dd | ||
|
|
1cf6cbf8a3 | ||
|
|
0b42379c34 | ||
|
|
414c349974 | ||
|
|
cf6d5f7194 | ||
|
|
949f5207b4 | ||
|
|
a1da374b32 | ||
|
|
5460322d4a | ||
|
|
8b2da0b787 | ||
|
|
f2e5c8a30f | ||
|
|
3eaf4dacaf | ||
|
|
d66d9b4dd7 | ||
|
|
cc52279e01 | ||
|
|
34b9a46f2d | ||
|
|
0def474f6d | ||
|
|
e0ea9a2855 | ||
|
|
2bc381fe05 | ||
|
|
95d84b9dbe | ||
|
|
1f78994ac0 | ||
|
|
fb3cd3bf52 | ||
|
|
c4414c7cc4 | ||
|
|
e2f2ceb7a9 | ||
|
|
641e7efb11 | ||
|
|
e5e02e966f | ||
|
|
11f2ef50ef | ||
|
|
869ecfaf71 | ||
|
|
cb8731815c | ||
|
|
a9177ad362 | ||
|
|
ad116df73b | ||
|
|
f30b3a410c | ||
|
|
c440ba2d4b | ||
|
|
a3267dafdb | ||
|
|
7a1e83733c | ||
|
|
7cb96ce983 | ||
|
|
a73633d0c3 | ||
|
|
b2f8e8dd4e | ||
|
|
291d7abb78 | ||
|
|
32873d787b | ||
|
|
e243d71abf | ||
|
|
2689d3f21a | ||
|
|
c1bc008114 | ||
|
|
254578460a | ||
|
|
f5467dd3b9 | ||
|
|
9eb8714e11 | ||
|
|
847678ea56 | ||
|
|
f08729a402 | ||
|
|
a7c91257a7 | ||
|
|
835369a91e | ||
|
|
62554b522f | ||
|
|
fd041cd4c3 | ||
|
|
cfbb68c8ef | ||
|
|
d7acec4f7d | ||
|
|
7da46bca8b | ||
|
|
66b4fcdc2c | ||
|
|
c480579ca8 | ||
|
|
e1a5ad16f1 | ||
|
|
512ec4f8ea | ||
|
|
bc46b07ee1 | ||
|
|
f750f1581e | ||
|
|
00ff1a1eae | ||
|
|
ae795f8ad3 | ||
|
|
9d3c071689 | ||
|
|
01cb23f566 | ||
|
|
fe8a9799ab | ||
|
|
4f18154681 | ||
|
|
2114c4a3ad | ||
|
|
2ca41b2b51 | ||
|
|
6605a2019e | ||
|
|
8b1ed5f183 | ||
|
|
f11a1b788f | ||
|
|
7928e24c54 | ||
|
|
5dbca41da6 | ||
|
|
f3fa54addf | ||
|
|
e24dc0a680 | ||
|
|
e636397f90 | ||
|
|
1f3e20704d | ||
|
|
cc9bdd4f14 | ||
|
|
6d76bf120d | ||
|
|
a50e81551f | ||
|
|
94cd5abf30 | ||
|
|
cec33fa6a7 | ||
|
|
7b14a45582 | ||
|
|
86512bcea1 | ||
|
|
f0efffceaa | ||
|
|
8b37a9509f | ||
|
|
de61956bf7 | ||
|
|
c1bc8fc262 | ||
|
|
797e6a5318 | ||
|
|
bd41f45370 | ||
|
|
7946b7403c | ||
|
|
eccf6e21e0 | ||
|
|
41c40da6c7 | ||
|
|
975f45eb01 | ||
|
|
f2399d3179 | ||
|
|
b26d62a067 | ||
|
|
926d6cbd46 | ||
|
|
7ea47d2a99 | ||
|
|
89ad8cfc15 | ||
|
|
302a309aff | ||
|
|
fbc74c0012 | ||
|
|
27a953795c | ||
|
|
c3e62bc2e5 | ||
|
|
c2ab322bd2 | ||
|
|
aeab0f235c | ||
|
|
ae2285599f | ||
|
|
104ab757d2 | ||
|
|
6ada52bc0b | ||
|
|
c526cb9f08 | ||
|
|
a1662d76fb | ||
|
|
25a4c18dce | ||
|
|
0fe4eda8ae | ||
|
|
de84a8c8c5 | ||
|
|
4fa135daf0 | ||
|
|
e244a17b57 | ||
|
|
7b61d78a9e | ||
|
|
b5540c7fe9 | ||
|
|
1c48509a17 | ||
|
|
9870cb4082 | ||
|
|
b2924761ab | ||
|
|
bf08b24991 | ||
|
|
0151ee1387 | ||
|
|
eb6d5586cd | ||
|
|
f66f1f545e | ||
|
|
5a3f245ac0 | ||
|
|
9c5f451878 | ||
|
|
6a5fb85c5a | ||
|
|
2aef5fb3e5 | ||
|
|
63a9aef5eb | ||
|
|
e36fe8c707 | ||
|
|
fc7af1f60c | ||
|
|
258cc1ef66 | ||
|
|
b3727f3774 | ||
|
|
b627d6a612 | ||
|
|
918b83133e | ||
|
|
0585f7b9ca | ||
|
|
25ab0d7799 | ||
|
|
ea313a442c | ||
|
|
6aed941fc9 | ||
|
|
57a63e0381 | ||
|
|
c525812aee | ||
|
|
e602705e6d | ||
|
|
b7d126b39b | ||
|
|
80b2864da8 | ||
|
|
36ba6f035a | ||
|
|
b22e5bb7dd | ||
|
|
eca70d7536 | ||
|
|
b84bbdace2 | ||
|
|
750b2f69fc | ||
|
|
6ffe051a8e | ||
|
|
bc3640b264 | ||
|
|
8746b6e2f4 | ||
|
|
445977d99b | ||
|
|
0f0185e18c | ||
|
|
adf8e2932f | ||
|
|
a748b1581e | ||
|
|
e35cde3d6b | ||
|
|
bbbb960b50 | ||
|
|
47a63e8906 | ||
|
|
af1e06203b | ||
|
|
e9cbd06652 | ||
|
|
356cf008ce | ||
|
|
07284e7e3d | ||
|
|
72055442b7 | ||
|
|
5fa7d84c23 | ||
|
|
de46f08bf4 | ||
|
|
fde395e2fa | ||
|
|
da1e55250e | ||
|
|
a4ab117d14 | ||
|
|
4cd7088eb8 | ||
|
|
8e1876fc25 | ||
|
|
29693ebe8c | ||
|
|
a8ee7bfcbe | ||
|
|
97fb6c044c | ||
|
|
85c5575b96 | ||
|
|
2037dad03d | ||
|
|
0a9fb603f6 | ||
|
|
0b7dadd345 | ||
|
|
a3645f5acc | ||
|
|
92f409d6fc | ||
|
|
4ada0a0d29 | ||
|
|
54714c06af | ||
|
|
c28dd8135c | ||
|
|
d3e674b135 | ||
|
|
253d872599 | ||
|
|
b8ca8a9b34 | ||
|
|
d180c26ca1 | ||
|
|
b51e50978e | ||
|
|
b897cfd028 | ||
|
|
2ce8863709 | ||
|
|
37362968d9 | ||
|
|
800393bc2b | ||
|
|
7a111c49f4 | ||
|
|
756ea140e2 | ||
|
|
f9400c2547 | ||
|
|
3baa03ccdc | ||
|
|
659e96d93c | ||
|
|
1a52c2bab4 | ||
|
|
365d7d46fd | ||
|
|
f8284508e1 | ||
|
|
e9c36c2375 | ||
|
|
5d269fd77c | ||
|
|
0064293e01 | ||
|
|
ba4b6f70d3 | ||
|
|
b4b79a6102 | ||
|
|
9d056a85ec | ||
|
|
3ed17fce6b | ||
|
|
d656dd0d18 | ||
|
|
b9054723d7 | ||
|
|
ed37890519 | ||
|
|
22dc6bb1ea | ||
|
|
0917713613 | ||
|
|
0b6be09678 | ||
|
|
27c92085c7 | ||
|
|
94e9462af8 | ||
|
|
2e52ccf5d8 | ||
|
|
b15196a284 | ||
|
|
d29cc85439 | ||
|
|
30367cf239 | ||
|
|
500fdd31d7 | ||
|
|
dcf9d1c3bb | ||
|
|
c0c2e4ce4a | ||
|
|
3557f4e738 | ||
|
|
6595c00cff | ||
|
|
ca9a566c25 | ||
|
|
c612018ba8 | ||
|
|
ec06ef2dfb | ||
|
|
2febf45700 | ||
|
|
3e6066a1a1 | ||
|
|
b61e5c76db | ||
|
|
bdee4859f2 | ||
|
|
8386bf2ec6 | ||
|
|
a50443589f | ||
|
|
fd74bfe9c2 | ||
|
|
f82693103a | ||
|
|
cff72bcb67 | ||
|
|
e3326fca29 | ||
|
|
b5e45aae95 | ||
|
|
7fd12c5622 | ||
|
|
47442be989 | ||
|
|
3a484dc41a | ||
|
|
9a2dfd7e57 | ||
|
|
c9a168a138 | ||
|
|
3726f28eeb | ||
|
|
ead25d6a9c | ||
|
|
55f4abb6be | ||
|
|
7ccf376beb | ||
|
|
8678ac301c | ||
|
|
dde6f10b22 | ||
|
|
f9f41dac6a | ||
|
|
af6ab0ec9e | ||
|
|
485e91796b | ||
|
|
2451ad3f0a | ||
|
|
b0125d139a | ||
|
|
2e013fafc8 | ||
|
|
d0069b9684 | ||
|
|
0809379f91 | ||
|
|
0d4a95f5af | ||
|
|
ed6a6f71ce | ||
|
|
2e9129a71c | ||
|
|
7384bebf4e | ||
|
|
a042e72f28 | ||
|
|
0e197b1885 | ||
|
|
54697ae0d5 | ||
|
|
7b6eb9337e | ||
|
|
a94fb84052 | ||
|
|
528aeb1873 | ||
|
|
e7b35daf45 | ||
|
|
7d10971617 | ||
|
|
6ac12fd457 | ||
|
|
0abedba665 | ||
|
|
8bc688a491 | ||
|
|
46524b19ab | ||
|
|
a9bcd4c1c6 | ||
|
|
a355be9c0f | ||
|
|
3d83211503 | ||
|
|
cd8c8fa020 | ||
|
|
4b663baa88 | ||
|
|
4c03009357 | ||
|
|
047f547863 | ||
|
|
83c62c614b | ||
|
|
c39b3569de | ||
|
|
22402280a7 | ||
|
|
7e9872871d | ||
|
|
6c595377a3 | ||
|
|
7fea62aa46 | ||
|
|
d816f510ea | ||
|
|
56ed5a784d | ||
|
|
c2064aa318 | ||
|
|
109709ce8b | ||
|
|
b200725762 | ||
|
|
9cf34bf987 | ||
|
|
be3a91bb14 | ||
|
|
fc948d8b27 | ||
|
|
8774658f4e | ||
|
|
0bf32148af | ||
|
|
97d158b615 | ||
|
|
b772dea188 | ||
|
|
5c8f0b35ec | ||
|
|
1b382653f2 | ||
|
|
c7cee63c97 | ||
|
|
fbb9f09536 | ||
|
|
d8e5b9a5c6 | ||
|
|
ba9474fa62 | ||
|
|
b7ec7ea686 | ||
|
|
74560adb08 | ||
|
|
e6a303bdda | ||
|
|
7afcf0bb68 | ||
|
|
7aae2d0616 | ||
|
|
9255505a3b | ||
|
|
6c0dfd338d | ||
|
|
c306e988c8 | ||
|
|
86f7a8b273 | ||
|
|
2428a8efab | ||
|
|
cba12e487f | ||
|
|
e98d7931ca | ||
|
|
f8e2d2e3d0 | ||
|
|
4455bab6ac | ||
|
|
b21fb27041 | ||
|
|
5402ee0019 | ||
|
|
02654ea57b | ||
|
|
9cd4aeabaa | ||
|
|
d788511a83 | ||
|
|
378b5b8096 | ||
|
|
e26eb2f751 | ||
|
|
26264b15f7 | ||
|
|
309e3dd981 | ||
|
|
4ff5ed3b5d | ||
|
|
270a757756 | ||
|
|
79e4030ab4 | ||
|
|
fd86927e04 | ||
|
|
df3a7e5037 | ||
|
|
fefc6f14c9 | ||
|
|
923fbef0e4 | ||
|
|
601bd5ba7a | ||
|
|
5798f20c41 | ||
|
|
f2e26f91a8 | ||
|
|
e2cdc45be6 | ||
|
|
96493bc75e | ||
|
|
d78ad4a78e | ||
|
|
0c54b0dd6e | ||
|
|
65310e52de | ||
|
|
285d3cd9b8 | ||
|
|
10486abb64 | ||
|
|
8c1a9fc988 | ||
|
|
b067b0eed5 | ||
|
|
7bdd0b470b | ||
|
|
b7a51f12bf | ||
|
|
e002ac5474 | ||
|
|
211d339ce0 | ||
|
|
509fb14473 | ||
|
|
ef838a1b83 | ||
|
|
f84b5b633d | ||
|
|
8ec8c1170d | ||
|
|
920b98e4d1 | ||
|
|
c80f52d4bc | ||
|
|
0b6aa42daf | ||
|
|
04dc6427ef | ||
|
|
77e366b484 | ||
|
|
add7cfa0f2 | ||
|
|
14d4fa142c | ||
|
|
7fe1617f25 | ||
|
|
cbe4cb521c | ||
|
|
d6dffe89d6 | ||
|
|
0107159b84 | ||
|
|
c106b99d5d | ||
|
|
2f695ca68f | ||
|
|
461592f6e3 | ||
|
|
ccf3b5c75d | ||
|
|
a40702cda4 | ||
|
|
7c394525c1 | ||
|
|
12ed051fea | ||
|
|
014913c635 | ||
|
|
369e45c282 | ||
|
|
2892caec0c | ||
|
|
eb609e918a | ||
|
|
9217e58845 | ||
|
|
a5ad456b52 | ||
|
|
26b633618a | ||
|
|
e3054d6ee1 | ||
|
|
0b6294f4ec | ||
|
|
7b130dc0f3 | ||
|
|
d93c128c25 | ||
|
|
e1dd74775e | ||
|
|
e61e706607 | ||
|
|
a57d46deaa | ||
|
|
60efdc0071 | ||
|
|
4dc1155a9e | ||
|
|
5cabe6fb42 | ||
|
|
42239c305f | ||
|
|
fd3c763349 | ||
|
|
daa9bbf2c9 | ||
|
|
c7dad00908 | ||
|
|
24317717e8 | ||
|
|
dd3eac6db6 | ||
|
|
1606f8517f | ||
|
|
3a98401ada | ||
|
|
4764251241 | ||
|
|
3de04b7b9d | ||
|
|
04908495e9 | ||
|
|
357243c717 | ||
|
|
23f1d57064 | ||
|
|
3c835c848e | ||
|
|
0f1788f122 | ||
|
|
3d72e700a4 | ||
|
|
a5cf41e65f | ||
|
|
7029bc41d7 | ||
|
|
b87d40c844 | ||
|
|
9e154376d3 | ||
|
|
4a9fdbce57 | ||
|
|
775e335292 | ||
|
|
e553bf4deb | ||
|
|
db0e09ec6e | ||
|
|
ba50760f92 | ||
|
|
a56a2500d4 | ||
|
|
4950bb9e0a | ||
|
|
7fc409a8d1 | ||
|
|
f52872718c | ||
|
|
abca6e3bf7 | ||
|
|
524e6d4f81 | ||
|
|
db93a669ab | ||
|
|
423f5b0502 | ||
|
|
2bc45c25fe | ||
|
|
4fedef90c7 | ||
|
|
69ca72ff86 | ||
|
|
d341463f67 | ||
|
|
19d1ecb36a | ||
|
|
ffe4047f97 | ||
|
|
15a3c29e7a | ||
|
|
542a4725ad | ||
|
|
5d8bd6f474 | ||
|
|
d8b98cc39d | ||
|
|
1d558c32d5 | ||
|
|
72c560e5a2 | ||
|
|
268246e1ef | ||
|
|
8957f8a55f | ||
|
|
cdf7a1dfe8 | ||
|
|
398d82f359 | ||
|
|
11fb1b655f | ||
|
|
5032a2538a | ||
|
|
cabc0e5344 | ||
|
|
fe1414dd6b | ||
|
|
42ff593004 | ||
|
|
5140af4e6f | ||
|
|
0f5b4c00a9 | ||
|
|
feb69f4987 | ||
|
|
84eb9a58ca | ||
|
|
fbd96f473a | ||
|
|
69228157dc | ||
|
|
6808f32b8d | ||
|
|
13cee09a1c | ||
|
|
eb5c6bd30a | ||
|
|
6f5e6f2fe2 | ||
|
|
fc8c932874 | ||
|
|
784523e635 | ||
|
|
56db418949 | ||
|
|
31d6e81a59 | ||
|
|
6356628c80 | ||
|
|
509e6bc2d8 | ||
|
|
9e69f5dcfa | ||
|
|
409ed9ad67 | ||
|
|
06e78e19d0 | ||
|
|
9a1d746170 | ||
|
|
ef87ce3507 | ||
|
|
2b9053eac4 | ||
|
|
116ef719be |
@@ -1,3 +0,0 @@
|
||||
node_modules/*
|
||||
build/*
|
||||
lib/*
|
||||
120
.eslintrc.json
@@ -1,120 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"impliedStrict": true
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"indent": "off",
|
||||
"arrow-parens": [
|
||||
"off",
|
||||
"as-needed"
|
||||
],
|
||||
"brace-style": "off",
|
||||
"comma-spacing": "off",
|
||||
"space-infix-ops": "error",
|
||||
"comma-dangle": "off",
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
"smart"
|
||||
],
|
||||
"import/order": "off",
|
||||
"no-eval": "warn",
|
||||
"no-new-wrappers": "warn",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-unsafe-finally": "warn",
|
||||
"no-var": "error",
|
||||
"spaced-comment": "error",
|
||||
"semi": "warn",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
"selector": "ExportDefaultDeclaration",
|
||||
"message": "Default exports are not allowed"
|
||||
}
|
||||
],
|
||||
"no-throw-literal": "error",
|
||||
"key-spacing": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"array-bracket-spacing": "error",
|
||||
"space-in-parens": "error",
|
||||
"computed-property-spacing": "error",
|
||||
"prefer-const": ["error", {
|
||||
"destructuring": "all",
|
||||
"ignoreReadBeforeAssign": false
|
||||
}],
|
||||
"space-before-function-paren": "off",
|
||||
"func-call-spacing": "off",
|
||||
"no-multi-spaces": "error",
|
||||
"block-spacing": "error",
|
||||
"keyword-spacing": "off",
|
||||
"space-before-blocks": "error",
|
||||
"semi-spacing": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.ts", "**/*.tsx"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": ["tsconfig.json", "tsconfig.commonjs.json"],
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/class-name-casing": "off",
|
||||
"@typescript-eslint/indent": [
|
||||
"error",
|
||||
4
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"off",
|
||||
{
|
||||
"multiline": {
|
||||
"delimiter": "none",
|
||||
"requireLast": true
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/prefer-namespace-keyword": "warn",
|
||||
"@typescript-eslint/quotes": [
|
||||
"error",
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/semi": [
|
||||
"off",
|
||||
null
|
||||
],
|
||||
"@typescript-eslint/type-annotation-spacing": "error",
|
||||
"@typescript-eslint/brace-style": [
|
||||
"error",
|
||||
"1tbs", { "allowSingleLine": true }
|
||||
],
|
||||
"@typescript-eslint/comma-spacing": "error",
|
||||
"@typescript-eslint/space-before-function-paren": ["error", {
|
||||
"anonymous": "always",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}],
|
||||
"@typescript-eslint/func-call-spacing": ["error"],
|
||||
"@typescript-eslint/keyword-spacing": ["error"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
3
.github/pull_request_template.md
vendored
@@ -7,4 +7,5 @@
|
||||
|
||||
- [ ] Added description of changes to the `[Unreleased]` section of `CHANGELOG.md`
|
||||
- [ ] Updated headers of modified files
|
||||
- [ ] Added my name to `package.json`'s `contributors`
|
||||
- [ ] Added my name to `package.json`'s `contributors`
|
||||
- [ ] (Optional but encouraged) Improved documentation in `docs`
|
||||
62
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: Build & Deploy Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
paths:
|
||||
- docs/**
|
||||
- ".github/workflows/docs.yml"
|
||||
pull_request:
|
||||
branches: master
|
||||
paths:
|
||||
- docs/**
|
||||
- ".github/workflows/docs.yml"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 👨🏼💻 checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🐍 python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: ⛓️ dependencies
|
||||
run: |
|
||||
pip install mkdocs-material
|
||||
- name: 🔧 build site
|
||||
run: |
|
||||
cd docs
|
||||
mkdocs build
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- name: 👨🏼💻 checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🐍 python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: ⛓️ dependencies
|
||||
run: |
|
||||
pip install mkdocs-material
|
||||
- name: 🔧 build site
|
||||
run: |
|
||||
cd docs
|
||||
mkdocs build
|
||||
|
||||
- name: 🚢 deploy docs
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
|
||||
external_repository: molstar/docs
|
||||
publish_branch: gh-pages
|
||||
publish_dir: ./docs/site
|
||||
8
.github/workflows/node.yml
vendored
@@ -1,6 +1,10 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
pull_request:
|
||||
branches: master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -9,12 +13,12 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- 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
|
||||
run: npm install --no-save "gl@^6.0.2" && xvfb-run --auto-servernum npm run jest
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
8
.gitignore
vendored
@@ -1,5 +1,7 @@
|
||||
build/
|
||||
deploy/
|
||||
lib/
|
||||
docs/site/
|
||||
|
||||
node_modules/
|
||||
debug.log
|
||||
@@ -10,4 +12,8 @@ tsconfig.commonjs.tsbuildinfo
|
||||
*.sublime-workspace
|
||||
.idea
|
||||
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
tmp/
|
||||
|
||||
dev.pem
|
||||
dev-key.pem
|
||||
|
||||
18
.travis.yml
@@ -1,18 +0,0 @@
|
||||
language: node_js
|
||||
os: linux
|
||||
sudo: required
|
||||
dist: trusty
|
||||
before_install:
|
||||
- sudo apt-get install -y mesa-utils
|
||||
- sudo apt-get install -y xvfb
|
||||
- sudo apt-get install -y libgl1-mesa-dri
|
||||
- sudo apt-get install -y libglapi-mesa
|
||||
- sudo apt-get install -y libosmesa6
|
||||
- sudo apt-get install -y gcc-4.9
|
||||
- sudo apt-get install -y libstdc++6
|
||||
- sudo apt-get install -y libxi-dev
|
||||
node_js:
|
||||
- "12"
|
||||
- "10"
|
||||
before_script:
|
||||
- export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start
|
||||
5
.vscode/settings.json
vendored
@@ -6,9 +6,4 @@
|
||||
"*.vert.ts": "glsl",
|
||||
"*.gql.ts": "graphql"
|
||||
},
|
||||
"eslint.options": {
|
||||
"overrideConfig": {
|
||||
"ignorePatterns": ["webpack.config.js", "scripts/*"],
|
||||
},
|
||||
}
|
||||
}
|
||||
856
CHANGELOG.md
@@ -3,21 +3,871 @@ All notable changes to this project will be documented in this file, following t
|
||||
|
||||
Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here.
|
||||
|
||||
|
||||
## [Unreleased]
|
||||
- [Breaking] Renamed some color schemes ('inferno' -> 'inferno-no-black', 'magma' -> 'magma-no-black', 'turbo' -> 'turbo-no-black', 'rainbow' -> 'simple-rainbow')
|
||||
- [Breaking] `Box3D.nearestIntersectionWithRay` -> `Ray3D.intersectBox3D`
|
||||
- [Breaking] `Plane3D.distanceToSpher3D` -> `distanceToSphere3D` (fix spelling)
|
||||
- [Breaking] fix typo `MarchinCubes` -> `MarchingCubes`
|
||||
- [Breaking] `PluginContext.initViewer/initContainer/mount` are now async and have been renamed to include `Async` postfix
|
||||
- [Breaking] Add `Volume.instances` support and a `VolumeInstances` transform to dynamically assign it
|
||||
- This change is breaking because all volume objects require the `instances` field now.
|
||||
- [Breaking] `Canvas3D.identify` now expects `Vec2` or `Ray3D`
|
||||
- Update production build to use `esbuild`
|
||||
- Emit explicit paths in `import`s in `lib/`
|
||||
- Fix outlines on opaque elements using illumination mode
|
||||
- Change `Representation.Empty` to a lazy property to avoid issue with some bundlers
|
||||
- MolViewSpec extension:
|
||||
- Generic color schemes (`palette` parameter for color_from_* nodes)
|
||||
- Annotation field remapping (`field_remapping` parameter for color_from_* nodes)
|
||||
- Representation node: support custom property `molstar_reprepresentation_params`,
|
||||
- Canvas node: support custom properties `molstar_enable_outline`, `molstar_enable_shadow`, `molstar_enable_ssao`
|
||||
- `clip` node support for structure and volume representations
|
||||
- `grid_slice` representation support for volumes
|
||||
- Support tethers and background for primitive labels
|
||||
- Support `snapshot_key` parameter on primitives that enables transition between states via clicking on 3D objects
|
||||
- Inline selectors and MVS annotations support `instance_id`
|
||||
- Support `matrix` on transform params
|
||||
- Add `instance` node type
|
||||
- Support transforming and instancing of structures, components, and volumes
|
||||
- Added new color schemes, synchronized with D3.js ('inferno', 'magma', 'turbo', 'rainbow', 'sinebow', 'warm', 'cool', 'cubehelix-default', 'category-10', 'observable-10', 'tableau-10')
|
||||
- Snapshot Markdown improvements
|
||||
- Add `MarkdownExtensionManager` (`PluginContext.managers.markdownExtensions`)
|
||||
- Support custom markdown commands to control the plugin via the `[link](!command)` pattern
|
||||
- Support rendering custom elements via the `` pattern
|
||||
- Support tables
|
||||
- Support loading images from MVSX files
|
||||
- Indicate external links with ⤴
|
||||
- Avoid calculating rings for coarse-grained structures
|
||||
- Fix isosurface compute shader normals when transformation matrix is applied to volume
|
||||
- Symmetry operator naming for spacegroup symmetry - parenthesize multi-character indices (1_111-1 -> 1_(11)1(-1))
|
||||
- Add `SymmetryOperator.instanceId` that corresponds to a canonical operator name (e.g. ASM-1, ASM-X0-1 for assemblies, 1_555, 1_(11)1(-1) for crystals)
|
||||
- Mol2 Reader
|
||||
- Fix column count parsing
|
||||
- Add support for substructure
|
||||
- Fix shader error when clipping flags are set without clip objects present
|
||||
- Fix wrong group count calculation on geometry update (#1562)
|
||||
- Fix wrong instance index in `calcMeshColorSmoothing`
|
||||
- Add `Ray3D` object and helpers
|
||||
- Volume slice representation: add `relativeX/Y/Z` options for dimension
|
||||
- Add `StructureInstances` transform
|
||||
- `mvs-stories` app
|
||||
- Add `story-id` URL arg support
|
||||
- Add "Download MVS State" link
|
||||
- Add ray-based picking
|
||||
- Render narrow view of scene scene from ray origin & direction to a few pixel sized viewport
|
||||
- Cast ray on every input as opposed to the standard "whole screen" picking
|
||||
- Can be enabled with new `Canvas3dInteractionHelperParams.convertCoordsToRay` param
|
||||
- Allows to have input methods that are 3D pointers in the scene
|
||||
- Add `ray: Ray3D` property to `DragInput`, `ClickInput`, and `MoveInput`
|
||||
- Add async, non-blocking picking (only WebGL2)
|
||||
- Refactor `Canvas3dInteractionHelper` internals to use async picking for move events
|
||||
- Add `enable` param for post-processing effects. If false, no effects are applied.
|
||||
|
||||
## [v4.18.0] - 2025-06-08
|
||||
- MolViewSpec extension:
|
||||
- Support for label_comp_id and auth_comp_id in annotations
|
||||
- Geometric primitives - do not render if position refers to empty substructure
|
||||
- Primitive arrow - nicer default cap size (relative to tube_radius)
|
||||
- Primitive angle_measurement - added vector_radius param
|
||||
- Fix MVSX file assets being disposed in multi-snapshot states
|
||||
- Add `mol-utils/camera.ts` with `fovAdjustedPosition` and `fovNormalizedCameraPosition`
|
||||
- Show FOV normalized position in `CameraInfo` UI and use it in "Copy MVS State"
|
||||
- Support static resources in `AssetManager`
|
||||
- General:
|
||||
- Use `isolatedModules` tsconfig flag
|
||||
- Fix TurboPack build when using ES6 modules
|
||||
- Support `pickingAlphaThreshold` when `xrayShaded` is enabled
|
||||
- Support sampling from arbitrary planes for structure plane and volume slice representations
|
||||
- Refactor SCSS to not use `@import` (fixes deprecation warnings)
|
||||
|
||||
## [v4.17.0] - 2025-05-22
|
||||
- Remove `xhr2` dependency for NodeJS, use `fetch`
|
||||
- Add `mvs-stories` app included in the `molstar` NPM package
|
||||
- Use the app in the corresponding example
|
||||
- Interactions extension: remove `salt-bridge` interaction kind (since `ionic` is supported too)
|
||||
|
||||
## [v4.16.0] - 2025-05-20
|
||||
- Load potentially big text files as `StringLike` to bypass string size limit
|
||||
- MolViewSpec extension:
|
||||
- Load single-state MVS as if it were multi-state with one state
|
||||
- Merged `loadMVS` options `keepCamera` and `keepSnapshotCamera` -> `keepCamera`
|
||||
- Removed `loadMVS` option `replaceExisting` (is now default)
|
||||
- Added `loadMVS` option `appendSnapshots`
|
||||
- Fix camera not being interpolated in MP4 export due to updates in WebGL ContextLost handling
|
||||
|
||||
## [v4.15.0] - 2025-05-19
|
||||
- IHM improvements:
|
||||
- Disable volume streaming
|
||||
- Disable validation report visualization
|
||||
- Enable assembly symmetry for integrative models
|
||||
- Fix transparency rendering with occlusion in NodeJS
|
||||
- mmCIF Support
|
||||
- Add custom `molstar_bond_site` category that enables serializing explicit bonds by referencing `atom_site.id`
|
||||
- Add `includeCategoryNames`, `keepAtomSiteId`, `exportExplicitBonds`, `encoder` properties to `to_mmCIF` exporter
|
||||
- Add support for attachment points property (`M APO`) to the MOL V2000 parser
|
||||
- Add `json-cif` extension that should pave way towards structure editing capabilities in Mol\*
|
||||
- JSON-based encoding of the CIF data format
|
||||
- `JSONCifLigandGraph` that enables editing of small molecules via modifying `atom_site` and `molstar_bond_site` categories
|
||||
- Add `ligand-editor` example that showcases possible use-cases of the `json-cif` extension
|
||||
- Breaking (minor): Changed `atom_site.id` indexing to 1-based in `mol-model-formats/structure/mol.ts::getMolModels`.
|
||||
- WebGL ContextLost handling
|
||||
- Fix missing framebuffer & drawbuffer re-attachments
|
||||
- Fix missing cube texture re-initialization
|
||||
- Fix missing extensions reset
|
||||
- Fix timer clearing edge case
|
||||
- Add reset support for geometry generated on the GPU
|
||||
|
||||
## [v4.14.1] - 2025-05-09
|
||||
- Do not raise error when creating duplicate state transformers and print console warning instead
|
||||
|
||||
## [v4.14.0] - 2025-05-07
|
||||
- Fix `Viewer.loadTrajectory` when loading a topology file
|
||||
- Fix `StructConn.residueCantorPairs` to not include identity pairs
|
||||
- Add format selection option to image export UI (PNG, WebP, JPEG)
|
||||
- Add `StateBuilder.To.updateState`
|
||||
- MVS:
|
||||
- Support updating transform states
|
||||
- Add support for `is_hidden` custom state as an extension
|
||||
- Add `queryMVSRef` and `createMVSRefMap` utility functions
|
||||
- Adjust max resolution of surfaces for auto quality (#1501)
|
||||
- Fix switching representation type in Volume UI
|
||||
- VolumeServer: Avoid grid expansion when requiring unit cell (avoids including an extra layer of cells outside the unit cell query box)
|
||||
|
||||
## [v4.13.0] - 2025-04-14
|
||||
- Support `--host` option for build-dev.mjs script
|
||||
- Add `Viewer.loadFiles` to open supported files
|
||||
- Support installing the viewer as a Progressive Web App (PWA)
|
||||
- `ihm-restraints` example: show entity labels
|
||||
- Fix `element-point` visual not using child unit
|
||||
- Ignore `renderables` with empty draw count
|
||||
- Add experimental support for `esbuild` for development
|
||||
- Use `npm run dev` for faster development builds
|
||||
- Use `StructureElement.Bundle` instead of expressions to serialize measurement elements
|
||||
- Fixes measurements not being supported for coarse models
|
||||
- Implementation of `ColorScale.createDiscrete` (#1458)
|
||||
- Add `ColorScale.createDiscrete` to the `uncertainty` color theme
|
||||
- Fix color palette shown in the UI (for non-gradient palettes)
|
||||
- Fix colors description in the UI (when using custom thresholds)
|
||||
- Fix an edge case in the UI when the user deletes all colors from the color list
|
||||
- Add `interactions` extension and a corresponding example that utilizes it
|
||||
- Add element source index to default atomic granularity hover labels
|
||||
- Add `StructureElement.Schema` based on corresponding MolViewSpec implementation that allows data-driven selection of structural elements
|
||||
- Add `StructureElement.Loci/Bundle.fromExpression/Query/Schema` helper functions
|
||||
- Add `addLinkCylinderMesh` (from `createLinkCylinderMesh`)
|
||||
- Add `Unit.transientCache` and `Unit.getCopy`
|
||||
- Fix `ElementBondIterator` indices mapping logic for inter-unit bonds
|
||||
- Fix `pickPadding` and `pickScale` not updating `PickHelper`
|
||||
- MolViewSpec extension: support loading extensions when loading multistate files
|
||||
- Do not add bonds for pairs of residues that have a `struct_conn` entry
|
||||
- Improved `ma_qa_metric` support
|
||||
- Parse all local metrics
|
||||
- Ability to select alternate metrics in the pLDDT/qmean themes
|
||||
- Do not assume PAE plot is symmetric
|
||||
- Added `PluginConfig.Viewport.ShowScreenshotControls` to control visibility of screenshot controls
|
||||
- Fix MolViewSpec builder for volumes.
|
||||
- Generalize `mvs-kinase-story` example to `mvs-stories`
|
||||
- Add TATA-binding protein story
|
||||
- Improve the Kinase story
|
||||
- Fix alpha orbitals example
|
||||
|
||||
## [v4.12.0] - 2025-02-28
|
||||
|
||||
- Fix PDBj structure data URL
|
||||
- Improve logic when to cull in renderer
|
||||
- Add `atom.ihm.has-seq-id` and `atom.ihm.overlaps-seq-id-range` symbol to the query language
|
||||
- MolViewSpec extension:
|
||||
- Add box, arrow, ellipse, ellipsoid, angle primitives
|
||||
- Add basic support for volumetric data (map, Volume Server)
|
||||
- Add support for `molstar_color_theme_name` custom extension
|
||||
- Better IH/M support:
|
||||
- Support `coarse` components
|
||||
- Support `spacefill` representation
|
||||
- Support `carbohydrate` representation
|
||||
- Support for `custom.molstar_use_default_coloring` property on Color node.
|
||||
- Use `atom.ihm.has-seq-id` and `atom.ihm.overlaps-seq-id-range` for matching `label_seq_id` locations to support querying coarse elements.
|
||||
- Add ihm-restraints example
|
||||
- Add `mvs-kinase-story` example
|
||||
- Remove static uses of `ColorTheme` and `SizeTheme` fields. Should resolvent "undefined" errors in certain builds
|
||||
- Add `transform` property to clip objects
|
||||
- Add support for trimming `image` geometry to a box
|
||||
- Improve/fix iso-level support of `slice` representation
|
||||
- Add support for rotating `slice` representation around an axis
|
||||
- Add default color support for palette based themes
|
||||
- Add `plane` structure representation
|
||||
- Can be colored with any structure theme
|
||||
- Can be colored with the `external-volume` theme
|
||||
- Can show atoms as a cutout
|
||||
- Supports principal axes and bounding box as a reference frame
|
||||
- Add `Camera` section to "Screenshot / State" controls
|
||||
- Add `CoarseIndex` for fast lookup of coarse elements
|
||||
|
||||
## [v4.11.0] - 2025-01-26
|
||||
|
||||
- Fix for tubular helices issue (Fixes #1422)
|
||||
- Volume UI improvements
|
||||
- Render all volume entries instead of selecting them one-by-one
|
||||
- Toggle visibility of all volumes
|
||||
- More accessible iso value control
|
||||
- Support wheel event on sliders
|
||||
- MolViewSpec extension:
|
||||
- Add validation for discriminated union params
|
||||
- Primitives: remove triangle_colors, line_colors, have implicit grouping instead; rename many parameters
|
||||
- UI configuration options
|
||||
- Support removal of independent selection controls in the viewport
|
||||
- Support custom selection controls
|
||||
- Support for custom granularity dropdown options
|
||||
- Support for custom Sequence Viewer mode options
|
||||
- Add `external-structure` theme that colors any geometry by structure properties
|
||||
- Support float and half-float data type for direct-volume rendering and GPU isosurface extraction
|
||||
- Minor documentation updates
|
||||
- Add support for position-location to `volume-value` color theme
|
||||
- Add support for color themes to `slice` representation
|
||||
- Improve/fix palette support in volume color themes
|
||||
- Fix `Plane3D.projectPoint`
|
||||
- Fix marking related `image` rendering issues
|
||||
- Handle pixels without a group
|
||||
- Take fog into account
|
||||
- MolViewSpec extension: Initial support for customizable representation parameters
|
||||
- Quick Styles section reorganized
|
||||
- UI color improvements (scrollbar contrast, toggle button hover color)
|
||||
- Add `overrideWater` param for entity-id color theme
|
||||
- Renames PDB-Dev to PDB-IHM and adjusts data source
|
||||
- Fix vertex based themes for spheres shader
|
||||
- Add volume dot representation
|
||||
- Add volume-value size theme
|
||||
- Sequence panel: Mark focused loci (bold+underline)
|
||||
- Change modifier key behavior in Normal Mode (default = select only, Ctrl/Cmd = add to selection, Shift = extend last selected range)
|
||||
- Handle Firefox's limit on vertex ids per draw (#1116)
|
||||
- Fix behavior of `Vec3.makeRotation(out, a, b)` when `a ≈ -b`
|
||||
|
||||
## [v4.10.0] - 2024-12-15
|
||||
|
||||
- Add `ModelWithCoordinates` decorator transform.
|
||||
- Fix outlines on transparent background using illumination mode (#1364)
|
||||
- Fix transparent depth texture artifacts using illumination mode
|
||||
- Fix marking of consecutive gap elements (#876)
|
||||
- Allow React 19 in dependencies
|
||||
- Fix missing deflate header if `CompressionStream` is available
|
||||
- Fix is_iOS check for NodeJS
|
||||
- Added PluginCommands.Camera.FocusObject
|
||||
- Plugin state snapshot can have instructions to focus objects (PluginState.Snapshot.camera.focus)
|
||||
- MolViewSpec extension: Support for multi-state files (animations)
|
||||
- Fix units transform data not fully updated when structure child changes
|
||||
- Fix `addIndexPairBonds` quadratic runtime case
|
||||
- Use adjoint matrix to transform normals in shaders
|
||||
- Fix resize handling in `tests/browser`
|
||||
|
||||
## [v4.9.1] - 2024-12-05
|
||||
|
||||
- Fix iOS check when running on Node
|
||||
|
||||
## [v4.9.0] - 2024-12-01
|
||||
|
||||
- Fix artifacts when using xray shading with high xrayEdgeFalloff values
|
||||
- Enable double rounded capping on tubular helices
|
||||
- Fix single residue tubular helices not showing up
|
||||
- Fix outlines on volume and surface reps that do not disappear (#1326)
|
||||
- Add example `glb-export`
|
||||
- Membrane orientation: Improve `isApplicable` check and error handling (#1316)
|
||||
- Fix set fenceSync to null after deleteSync.
|
||||
- Fix operator key-based `IndexPairBonds` assignment
|
||||
- Don't add bonds twice
|
||||
- Add `IndexPairs.bySameOperator` to avoid looping over all bonds for each unit
|
||||
- Add `Structure.intraUnitBondMapping`
|
||||
- Add more structure-based visuals to avoid too many (small) render-objects
|
||||
- `structure-intra-bond`, `structure-ellipsoid-mesh`, `structure-element-point`, `structure-element-cross`
|
||||
- Upgrade to express v5 (#1311)
|
||||
- Fix occupancy check using wrong index for inter-unit bond computation (@rxht, #1321)
|
||||
- Fix transparent SSAO for image rendering, e.g., volumne slices (#1332)
|
||||
- Fix bonds not shown with `ignoreHydrogens` on (#1315)
|
||||
- Better handle mmCIF files with no entities defined by using `label_asym_id`
|
||||
- Show bonds in water chains when `ignoreHydorgensVariant` is `non-polar`
|
||||
- Add MembraneServer API, generating data to be consumed in the context of MolViewSpec
|
||||
- Fix `StructConn.isExhaustive` for partial models (e.g., returned by the model server)
|
||||
- Refactor value swapping in molstar-math to fix SWC (Next.js) build (#1345)
|
||||
- Fix transform data not updated when structure child changes
|
||||
- Fix `PluginStateSnapshotManager.syncCurrent` to work as expected on re-loaded states.
|
||||
- Fix do not compute implicit hydrogens when unit is explicitly protonated (#1257)
|
||||
- ModelServer and VolumeServer: support for input files from Google Cloud Storage (gs://)
|
||||
- Fix color of missing partial charges for SB partial charges extension
|
||||
|
||||
## [v4.8.0] - 2024-10-27
|
||||
|
||||
- Add SSAO support for transparent geometry
|
||||
- Fix SSAO color not updating
|
||||
- Improve blending of overlapping outlines from transparent & opaque geometries
|
||||
- Default to `blended` transparency on iOS due to `wboit` not being supported.
|
||||
- Fix direct-volume with fog off (and on with `dpoit`) and transparent background on (#1286)
|
||||
- Fix missing pre-multiplied alpha for `blended` & `wboit` with no fog (#1284)
|
||||
- Fix backfaces visible using blended transparency on impostors (#1285)
|
||||
- Fix StructureElement.Loci.isSubset() only considers common units (#1292)
|
||||
- Fix `Scene.opacityAverage` calculation never 1
|
||||
- Fix bloom in illumination mode
|
||||
- Fix `findPredecessorIndex` bug when repeating values
|
||||
- MolViewSpec: Support for transparency and custom properties
|
||||
- MolViewSpec: MVP Support for geometrical primitives (mesh, lines, line, label, distance measurement)
|
||||
- Mesoscale Explorer: Add support for 4-character PDB IDs (e.g., 8ZZC) in PDB-IHM/PDB-Dev loader
|
||||
- Fix Sequence View in Safari 18
|
||||
- Improve performance of `IndexPairBonds` assignment when operator keys are available
|
||||
- ModelArchive QualityAssessment extension:
|
||||
- Add support for ma_qa_metric_local_pairwise mmCIF category
|
||||
- Add PAE plot component
|
||||
- Add new AlphaFoldDB-PAE example app
|
||||
- Add support for LAMMPS data and dump formats
|
||||
- Remove extra anti-aliasing from text shader (fixes #1208 & #1306)
|
||||
|
||||
## [v4.7.1] - 2024-09-30
|
||||
|
||||
- Improve `resolutionMode` (#1279)
|
||||
- Add `auto` that picks `scaled` for mobile devices and `native` elsewhere
|
||||
- Add `resolution-mode` Viewer GET param
|
||||
- Add `PluginConfig.General.ResolutionMode` config item
|
||||
|
||||
## [v4.7.0] - 2024-09-29
|
||||
|
||||
- Add illumination mode
|
||||
- Path-traced SSGI
|
||||
- Automatic thickness (estimate)
|
||||
- Base thickness as max(backface depth) - min(frontface depth)
|
||||
- Per object density factor to adjust thickness
|
||||
- Progressively trace samples to keep viewport interactive
|
||||
- Toggle on/off by pressing "G"
|
||||
- `illumination` Viewer GET param
|
||||
- Enables dXrayShaded define when rendering depth
|
||||
- Fix handling of PDB files that have chains with same id separated by TER record (#1245)
|
||||
- Sequence Panel: Improve visuals of unmodeled sequence positions (#1248)
|
||||
- Fix no-compression xtc parser (#1258)
|
||||
- Mol2 Reader: Fix mol2 status_bit read error (#1251)
|
||||
- Fix shadows with multiple lights
|
||||
- Fix impostor sphere interior normal when using orthographic projection
|
||||
- Add `resolutionMode` parameter to `Canvas3DContext`
|
||||
- `scaled`, divides by `devicePixelRatio`
|
||||
- `native`, no changes
|
||||
- Add `CustomProperty.Context.errorContext` to support reporting errors during loading of custom properties (#1254)
|
||||
- Use in MolViewSpec extension
|
||||
- Mesoscale Explorer: fix color & style issues
|
||||
- Remove use of deprecated SASS explicit color functions
|
||||
- Allow "Components" section to display nested components created by "Apply Action > Selection".
|
||||
|
||||
## [v4.6.0] - 2024-08-28
|
||||
|
||||
- Add round-caps option on tubular alpha helices
|
||||
- Fix missing Sequence UI update on state object removal (#1219)
|
||||
- Improved prmtop format support (CTITLE, %COMMENT)
|
||||
- Avoid calculating bonds for water units when `ignoreHydrogens` is on
|
||||
- Add `Water` trait to `Unit`
|
||||
- Improve entity-id coloring for structures with multiple models from the same source (#1221)
|
||||
- Wrap screenshot & image generation in a `Task`
|
||||
- AlphaFold DB: Add BinaryCIF support when fetching data
|
||||
- PDB-IHM/PDB-Dev: Add support for 4-character PDB IDs (e.g., 8ZZC)
|
||||
- Fix polymer-gap visual coloring with cartoon theme
|
||||
- Add formal-charge color theme (#328)
|
||||
- Add more coloring options to cartoon theme
|
||||
- Use `CompressionStream` Browser API when available
|
||||
- Add `pdbx_structure_determination_methodology` mmcif field and `Model` helpers
|
||||
- Fix cartoon representation not updated when secondary structure changes
|
||||
- Add Zhang-Skolnick secondary-structure assignment method which handles coarse-grained models (#49)
|
||||
- Calculate bonds for coarse-grained models
|
||||
- VolumeServer: Add `health-check` endpoint + `healthCheckPath` config prop to report service health
|
||||
- ModelServer: Add `health-check` endpoint + `healthCheckPath` config prop to report service health
|
||||
|
||||
## [v4.5.0] - 2024-07-28
|
||||
|
||||
- Separated postprocessing passes
|
||||
- Take into account explicit hydrogens when computing hydrogen bonds
|
||||
- Fix DoF with pixel ratios =! 1
|
||||
- Fix DoF missing transparent depth
|
||||
- Fix trackball pinch zoom and add pan
|
||||
- Fix aromatic link rendering when `adjustCylinderLength` is true
|
||||
- Change trackball animate spin speed unit to radians per second
|
||||
- Fix `mol-plugin-ui/skin/base/components/misc.scss` syntax to be in line with latest Sass syntax
|
||||
- Handle missing theme updates
|
||||
- Fix trajectory-index color-theme not always updated (#896)
|
||||
- Fix bond cylinders not updated on size-theme change with `adjustCylinderLength` enabled (#1215)
|
||||
- Use `OES_texture_float_linear` for SSAO when available
|
||||
|
||||
## [v4.4.1] - 2024-06-30
|
||||
|
||||
- Clean `solidInterior` transparent cylinders
|
||||
- Create a transformer to deflate compressed data
|
||||
- Adjust Quick Styles panel button labels
|
||||
- Improve camera interpolation code (interpolate camera rotation instead of just position)
|
||||
- Mesoscale Explorer
|
||||
- Add `illustrative` coloring option
|
||||
- Press 'C' to toggle between center and zoom & center on click
|
||||
- Add entities selection description
|
||||
- Clicking a leaf node in the right panel tree will center each instance in turn
|
||||
- Add measurement controls to right panel
|
||||
- Mouse left click on label with snapshot key will load snapshot
|
||||
- Mouse hover over label with protein name highlight entities with the same name
|
||||
- Custom ViewportSnapshotDescription with custom MarkdowAnchor
|
||||
- \# other snapshots with a given key \[...](#key)
|
||||
- i highlight a protein with a given NAME \[...](iNAME)
|
||||
- g highlight a group with a given group type and group name \[...](ggrouptype.groupname)
|
||||
- h URLs with a given link \[...](http...)
|
||||
- Snapshot description panel window size and text can be resized and hidden with new icons
|
||||
- Add styles controls to right panel
|
||||
- Add viewport settings to left panel
|
||||
- Add app info component to left panel with interactive tour and doc link
|
||||
- Fixes SSAO edge artifacts (#1122)
|
||||
- Add `reuseOcclusion` parameter to multi-sample pass
|
||||
- Add `blurDepthBias` parameter to occlusion pass
|
||||
- Handle near clip in SSAO blur
|
||||
- Support reading score from B-factor in pLDDT color theme
|
||||
- Add Cel-shading support
|
||||
- `celShaded` geometry parameter
|
||||
- `celSteps` renderer parameter
|
||||
- Add the ability to customize the Snapshot Description component via `PluginUISpec.components.viewport.snapshotDescription`
|
||||
- Add `doNotDisposeCanvas3DContext` option to `PluginContext.dispose`
|
||||
- Remove support for density data from edmaps.rcsb.org
|
||||
|
||||
## [v4.3.0] - 2024-05-26
|
||||
|
||||
- Fix State Snapshots export animation (#1140)
|
||||
- Add depth of field (dof) postprocessing effect
|
||||
- Add `SbNcbrTunnels` extension for for visualizing tunnels in molecular structures from ChannelsDB (more info in [tunnels.md](./docs/docs/extensions/tunnels.md))
|
||||
- Fix edge case in minimizing RMSD transform computation
|
||||
|
||||
## [v4.2.0] - 2024-05-04
|
||||
|
||||
- Add emissive material support
|
||||
- Add bloom post-processing
|
||||
- MolViewSpec extension: `loadMVS` supports `keepCamera` parameter
|
||||
- Return StateTransform selectors from measurements API (addDistance, addAngle, etc.)
|
||||
- Refactor transparency rendering
|
||||
- More uniform behavior for blended, wboit, dpoit
|
||||
- Fix issues with text & image geometry
|
||||
- Fix render-spheres example (#1100)
|
||||
- Wrong step size in sphere geometry boundingSphere & groupmapping
|
||||
- Handle empty `instanceGrid` in renderer & renderable
|
||||
- Fix bond assignment from `IndexPairBonds`
|
||||
- Can not always be cached in `ElementSetIntraBondCache`
|
||||
- Wrong operator checks in `findPairBonds`
|
||||
- Fix SSAO artifacts (@corredD, #1082)
|
||||
- Fix bumpiness artifacts (#1107, #1084)
|
||||
|
||||
## [v4.1.0] - 2024-03-31
|
||||
|
||||
- Add `VolumeTransform` to translate/rotate a volume like in a structure superposition
|
||||
- Fix BinaryCIF encoder edge cases caused by re-encoding an existing BinaryCIF file
|
||||
- Fix edge-case where width/height in InputObserver are not correct
|
||||
- Fix transparency rendering fallback (#1058)
|
||||
- Fix SSAO broken when `OES_texture_float_linear` is unavailable
|
||||
- Add `normalOffset` to `external-volume` color theme
|
||||
- This can give results similar to pymol's surface_ramp_above_mode=1
|
||||
- Add `rotation` parameter to skybox background
|
||||
|
||||
## [v4.0.1] - 2024-02-19
|
||||
|
||||
- Fix BinaryCIF decoder edge cases. Fixes mmCIF model export from data provided by ModelServer.
|
||||
- MolViewSpec extension: support for MVSX file format
|
||||
- Revert "require WEBGL_depth_texture extension" & "remove renderbuffer use"
|
||||
|
||||
## [v4.0.0] - 2024-02-04
|
||||
|
||||
- Add Mesoscale Explorer app for investigating large systems
|
||||
- [Breaking] Remove `cellpack` extension (superseded by Mesoscale Explorer app)
|
||||
- [Breaking] Set minimal node.js version to 18
|
||||
- [Breaking] Generalize rcsb/assembly-symmetry/ extension
|
||||
- Move to assembly-symmetry/
|
||||
- Remove RCSB specific dependencies and prefixes
|
||||
- [Breaking] Require `WEBGL_depth_texture` webgl extension
|
||||
- Remove `renderbuffer` use
|
||||
- [Breaking] Change build target to ES2018
|
||||
- Custom builds only require ES6 for dependencies like immer.js
|
||||
- [Breaking] Changed `createPluginUI`
|
||||
- The function now takes a single `options` argument
|
||||
- The caller must specify a `render` method that mounts the Mol* react component to DOM
|
||||
- A default `renderReact18` method is provided, but needs to be imported separately
|
||||
- To support React 16 and 17, `ReactDOM.render` can be passed
|
||||
- Improve `SetUtils` performance using ES6 features
|
||||
- [Breaking] Reduce memory usage of `SymmetryOperator.ArrayMapping`
|
||||
- Requires calling methods from instance
|
||||
- [Breaking] Fix `mol-model/structure/model/properties/seconday-structure.ts` file name (#938)
|
||||
- [Breaking] Add `Canvas3DContext` runtime props
|
||||
- Props: pixelScale, pickScale, transparency (blended, wboit, dpoit)
|
||||
- Replaces instantiation-time attribs
|
||||
- [Breaking] Change default compile target to ES2018
|
||||
- [Breaking] Add culling & LOD support
|
||||
- Cull per-object and per-instance
|
||||
- Cull based on frustum and camera distance
|
||||
- LOD visibility based on camera distance
|
||||
- Special LOD mode for spheres with automatic levels
|
||||
- Occlusion culling (only WebGL2)
|
||||
- Hi-Z pass
|
||||
- Cull based on previous frame's Hi-Z buffer
|
||||
- Add stochastic/dithered transparency to fade overlapping LODs in and out
|
||||
- Add "Automatic Detail" preset that shows surface/cartoon/ball & stick based on camera distance
|
||||
|
||||
## [v3.45.0] - 2024-02-03
|
||||
|
||||
- Add color interpolation to impostor cylinders
|
||||
- MolViewSpec components are applicable only when the model has been loaded from MolViewSpec
|
||||
- Add `snapshotKey` and `tooltip` params to loci `LabelRepresentation`
|
||||
- Update `FocusLoci` behavior to support `snapshotKey` param
|
||||
- Clicking a visual with `snapshotKey` will trigger that snapshot
|
||||
- Render multiline loci label tooltips as Markdown
|
||||
- `ParamDefinition.Text` updates:
|
||||
- Support `multiline` inputs
|
||||
- Support `placeholder` parameter
|
||||
- Support `disableInteractiveUpdates` to only trigger updates once the control loses focus
|
||||
- Move dependencies related to the headless context from optional deps to optional peer deps
|
||||
|
||||
## [v3.44.0] - 2024-01-06
|
||||
|
||||
- Add new `cartoon` visuals to support atomic nucleotide base with sugar
|
||||
- Add `thicknessFactor` to `cartoon` representation for scaling nucleotide block/ring/atomic-fill visuals
|
||||
- Use bonds from `_struct_conn` in mmCIF files that use `label_seq_id`
|
||||
- Fix measurement label `offsetZ` default: not needed when `scaleByRadius` is enbaled
|
||||
- Support for label rendering in HeadlessPluginContext
|
||||
- MolViewSpec extension
|
||||
- Support all X11 colors
|
||||
- Support relative URIs
|
||||
- CLI tools: mvs-validate, mvs-render, mvs-print-schema
|
||||
- Labels applied in one node
|
||||
- ModelServer SDF/MOL2 ligand export: fix atom indices when additional atoms are present
|
||||
- Avoid showing (and calculating) inter-unit bonds for huge structures
|
||||
- Fixed `DragOverlay` on WebKit/Safari browsers
|
||||
|
||||
## [v3.43.1] - 2023-12-04
|
||||
|
||||
- Fix `react-markdown` dependency
|
||||
|
||||
## [v3.43.0] - 2023-12-02
|
||||
|
||||
- Fix `State.tryGetCellData` (return type & data check)
|
||||
- Don't change camera.target unless flyMode or pointerLock are enabled
|
||||
- Handle empty CIF files
|
||||
- Snapshot improvements:
|
||||
- Add `key` property
|
||||
- Ability to existing snapshot name, key, and description
|
||||
- Support markdown in descriptions (ignores all HTML tags)
|
||||
- Ability to link to snapshots by key from descriptions
|
||||
- Separate UI control showing description of the current snapshot
|
||||
- Do not activate drag overlay for non-file content
|
||||
- Add `structure-element-sphere` visual to `spacefill` representation
|
||||
- Fix missing `await` in `HeadlessPluginContext.saveStateSnapshot`
|
||||
- Added support for providing custom sequence viewers to the plugin spec
|
||||
- MolViewSpec extension (MVS)
|
||||
- Add URL parameters `mvs-url`, `mvs-data`, `mvs-format`
|
||||
- Add drag&drop for `.mvsj` files
|
||||
- Fix `bumpiness` scaling with `ignoreLight` enabled
|
||||
- Add `transforms` & `label` params to `ShapeFromPly`
|
||||
- Optimize `LociSelectManager.selectOnly` to avoid superfluous loci set operations
|
||||
- Dispose of viewer on `unload` event to aid GC
|
||||
|
||||
## [v3.42.0] - 2023-11-05
|
||||
|
||||
- Fix handling of PDB files with insertion codes (#945)
|
||||
- Fix de-/saturate of colors with no hue
|
||||
- Improve `distinctColors` function
|
||||
- Add `sort` and `sampleCountFactor` parameters
|
||||
- Fix clustering issues
|
||||
- Add `clipPrimitive` option to spheres geometry, clipping whole spheres instead of cutting them
|
||||
- Add `DragAndDropManager`
|
||||
- Add `options` support for default bond labels
|
||||
|
||||
## [v3.41.0] - 2023-10-15
|
||||
|
||||
- Add `PluginContext.initialized` promise & support for it in the `Plugin` UI component.
|
||||
- Fix undesired interaction between settings panel and the panel on the right.
|
||||
- Add ability to customize server parameters for `RCSBAssemblySymmetry`.
|
||||
|
||||
## [v3.40.1] - 2023-09-30
|
||||
|
||||
- Do not call `updateFocusRepr` if default `StructureFocusRepresentation` isn't present.
|
||||
- Treat "tap" as a click in `InputObserver`
|
||||
- ModelServer ligand queries: fix atom count reported by SDF/MOL/MOL2 export
|
||||
- CCD extension: Make visuals for aromatic bonds configurable
|
||||
- Add optional `file?: CifFile` to `MmcifFormat.data`
|
||||
- Add support for webgl extensions
|
||||
- `WEBGL_clip_cull_distance`
|
||||
- `EXT_conservative_depth`
|
||||
- `WEBGL_stencil_texturing`
|
||||
- `EXT_clip_control`
|
||||
- Add `MultiSampleParams.reduceFlicker` (to be able to switch it off)
|
||||
- Add `alphaThickness` parameter to adjust alpha of spheres for radius
|
||||
- Ability to hide "right" panel from simplified viewport controls
|
||||
- Add `blockIndex` parameter to TrajectoryFromMmCif
|
||||
- Fix bounding sphere calculation for "element-like" visuals
|
||||
- Fix RCSB PDB validation report URL
|
||||
- Add sharpening postprocessing option
|
||||
- Take pixel-ratio into account for outline scale
|
||||
- Gracefully handle missing HTMLImageElement
|
||||
- Fix pixel-ratio changes not applied to all render passes
|
||||
|
||||
## [v3.39.0] - 2023-09-02
|
||||
|
||||
- Add some elements support for `guessElementSymbolString` function
|
||||
- Faster bounding rectangle calculation for imposter spheres
|
||||
- Allow toggling of hydrogens as part of `LabelTextVisual`
|
||||
|
||||
## [v3.38.3] - 2023-07-29
|
||||
|
||||
- Fix imposter spheres not updating, e.g. in trajectories (broke in v3.38.0)
|
||||
|
||||
## [v3.38.2] - 2023-07-24
|
||||
|
||||
- Don't rely solely on `chem_comp_atom` when detecting CCD files (#877)
|
||||
- Actually support non-physical keys in `Bindings.Trigger.code`
|
||||
|
||||
## [v3.38.1] - 2023-07-22
|
||||
|
||||
- Fix pixel-scale not updated in SSAO pass
|
||||
|
||||
## [v3.38.0] - 2023-07-18
|
||||
|
||||
- Fix display issue with SIFTS mapping
|
||||
- Support non-physical keys in `Bindings.Trigger.code`
|
||||
- Update `getStateSnapshot` to only overwrite current snapshot if it was created automatically
|
||||
- Fix distinct palette's `getSamples` infinite loop
|
||||
- Add 'NH2', 'FOR', 'FMT' to `CommonProteinCaps`
|
||||
- Add `opened` event to `PluginStateSnapshotManager`
|
||||
- Properly switch-off fog
|
||||
- Add `approximate` option for spheres rendering
|
||||
- Reduce `Spheres` memory usage
|
||||
- Derive mapping from VertexID
|
||||
- Pull position and group from texture
|
||||
- Add `Euler` math primitive
|
||||
- Add stride option to element sphere & point visuals
|
||||
- Add `disabledExtensions` field to default viewer's options
|
||||
- Add `LRUCache.remove`
|
||||
- Add 'Chain Instance' and 'Uniform' options for 'Carbon Color' param (in Color Theme: Element Symbol)
|
||||
|
||||
## [v3.37.1] - 2023-06-20
|
||||
|
||||
- Fix issues with wboit/dpoit in large scenes
|
||||
- Fix lines, text, points rendering (broken in v3.37.0)
|
||||
|
||||
## [v3.37.0] - 2023-06-17
|
||||
|
||||
- Add `inverted` option to `xrayShaded` parameter
|
||||
- Model-export extension: Add ability to set a file name for structures
|
||||
- Add `contextHash` to `SizeTheme`
|
||||
- Add mipmap-based blur for image backgrounds
|
||||
|
||||
## [v3.36.1] - 2023-06-11
|
||||
|
||||
- Allow parsing of CCD ligand files
|
||||
- Add dedicated wwPDB CCD extension to align and visualize ideal & model CCD coordinates
|
||||
- Make operators in `IndexPairBonds` a directed property
|
||||
- Remove erroneous bounding-box overlap test in `Structure.eachUnitPair`
|
||||
- Fix `EdgeBuilder.addNextEdge` for loop edges
|
||||
- Optimize inter unit bond compute
|
||||
- Ensure consistent state for volume representation (#210)
|
||||
- Improve SSAO for thin geometry (e.g. lines)
|
||||
- Add snapshot support for structure selections
|
||||
- Add `nucleicProfile` parameter to cartoon representation
|
||||
- Add `cartoon` theme with separate colorings for for mainchain and sidechain visuals
|
||||
|
||||
## [v3.35.0] - 2023-05-14
|
||||
|
||||
- Enable odd dash count (1,3,5)
|
||||
- Add principal axes spec and fix edge cases
|
||||
- Add a uniform color theme for NtC tube that still paints residue and segment dividers in a different color
|
||||
- Mesh exporter improvements
|
||||
- Support points & lines in glTF export
|
||||
- Set alphaMode and doubleSided in glTF export
|
||||
- Fix flipped cylinder caps
|
||||
- Fix bond assignments `struct_conn` records referencing waters
|
||||
- Add StructConn extension providing functions for inspecting struct_conns
|
||||
- Fix `PluginState.setSnapshot` triggering unnecessary state updates
|
||||
- Fix an edge case in the `mol-state`'s `State` when trying to apply a transform to an existing Null object
|
||||
- Add `SbNcbrPartialCharges` extension for coloring and labeling atoms and residues by partial atomic charges
|
||||
- uses custom mmcif categories `_sb_ncbr_partial_atomic_charges_meta` and `_sb_ncbr_partial_atomic_charges` (more info in [README.md](./src/extensions/sb-ncbr/README.md))
|
||||
- Parse HEADER record when reading PDB file
|
||||
- Support `ignoreHydrogens` in interactions representation
|
||||
- Add hydroxyproline (HYP) commonly present in collagen molecules to the list of amino acids
|
||||
- Fix assemblies for Archive PDB files (do not generate unique `label_asym_id` if `REMARK 350` is present)
|
||||
- Add additional functions to `core.math` in `mol-script`
|
||||
- `cantorPairing`, `sortedCantorPairing`, `invertCantorPairing`,
|
||||
- `trunc`, `sign`
|
||||
|
||||
## [v3.34.0] - 2023-04-16
|
||||
|
||||
- Avoid `renderMarkingDepth` for fully transparent renderables
|
||||
- Remove `camera.far` doubling workaround
|
||||
- Add `ModifiersKeys.areNone` helper function
|
||||
- Do not render NtC tube segments unless all required atoms are present in the structure
|
||||
- Fix rendering issues caused by VAO reuse
|
||||
- Add "Zoom All", "Orient Axes", "Reset Axes" buttons to the "Reset Camera" button
|
||||
- Improve trackball move-state handling when key bindings use modifiers
|
||||
- Fix rendering with very small viewport and SSAO enabled
|
||||
- Fix `.getAllLoci` for structure representations with `structure.child`
|
||||
- Fix `readAllLinesAsync` refering to dom length property
|
||||
- Make mol-util/file-info node compatible
|
||||
- Add `eachLocation` to representation/visual interface
|
||||
|
||||
## [v3.33.0] - 2023-04-02
|
||||
|
||||
- Handle resizes of viewer element even when window remains the same size
|
||||
- Throttle canvas resize events
|
||||
- Selection toggle buttons hidden if selection mode is off
|
||||
- Camera focus loci bindings allow reset on click-away to be overridden
|
||||
- Input/controls improvements
|
||||
- Move or fly around the scene using keys
|
||||
- Pointer lock to look around scene
|
||||
- Toggle spin/rock animation using keys
|
||||
- Apply bumpiness as lightness variation with `ignoreLight`
|
||||
- Remove `JSX` reference from `loci-labels.ts`
|
||||
- Fix overpaint/transparency/substance smoothing not updated when geometry changes
|
||||
- Fix camera project/unproject when using offset viewport
|
||||
- Add support for loading all blocks from a mmcif file as a trajectory
|
||||
- Add `Frustum3D` and `Plane3D` math primitives
|
||||
- Include `occupancy` and `B_iso_or_equiv` when creating `Conformation` from `Model`
|
||||
- Remove LazyImports (introduced in v3.31.1)
|
||||
|
||||
## [v3.32.0] - 2023-03-20
|
||||
|
||||
- Avoid rendering of fully transparent renderables
|
||||
- Add occlusion color parameter
|
||||
- Fix issue with outlines and orthographic camera
|
||||
- Reduce over-blurring occlusion at larger view distances
|
||||
- Fix occlusion artefact with non-canvas viewport and pixel-ratio > 1
|
||||
- Update nodejs-shims conditionals to handle polyfilled document object in NodeJS environment.
|
||||
- Ensure marking edges are at least one pixel wide
|
||||
- Add exposure parameter to renderer
|
||||
- Only trigger marking when mouse is directly over canvas
|
||||
- Fix blurry occlusion in screenshots
|
||||
- [Breaking] Add `setFSModule` to `mol-util/data-source` instead of trying to trick WebPack
|
||||
|
||||
## [v3.31.4] - 2023-02-24
|
||||
|
||||
- Allow link cylinder/line `dashCount` set to '0'
|
||||
- Stop animation loop when disposing `PluginContext` (thanks @gfrn for identifying the issue)
|
||||
|
||||
## [v3.31.3] - 2023-02-22
|
||||
|
||||
- Fix impostor bond visuals not correctly updating on `sizeFactor` changes
|
||||
- Fix degenerate case in PCA
|
||||
- Fix near clipping avoidance in impostor shaders
|
||||
- Update `fs` import in `data-source.ts`
|
||||
|
||||
## [v3.31.2] - 2023-02-12
|
||||
|
||||
- Fix exit code of volume pack executable (pack.ts). Now exits with non-0 status when an error happens
|
||||
- Remove pca transform from components ui focus (too distracting)
|
||||
- Fix artefacts with opaque outlines behind transparent objects
|
||||
- Fix polymer trace visual not updating
|
||||
- Fix use of `WEBGL_provoking_vertex`
|
||||
|
||||
## [v3.31.1] - 2023-02-05
|
||||
|
||||
- Improve Component camera focus based on the PCA of the structure and the following rules:
|
||||
- The first residue should be in first quadrant if there is only one chain
|
||||
- The average position of the residues of the first chain should be in the first quadrant if there is more than one chain
|
||||
- Add `HeadlessPluginContext` and `HeadlessScreenshotHelper` to be used in Node.js
|
||||
- Add example `image-renderer`
|
||||
- Fix wrong offset when rendering text with orthographic projection
|
||||
- Update camera/handle helper when `devicePixelRatio` changes
|
||||
- Add various options to customize the axes camera-helper
|
||||
- Fix issue with texture-mesh color smoothing when changing themes
|
||||
- Add fast boundary helper and corresponding unit trait
|
||||
- Add Observable for Canvas3D commits
|
||||
|
||||
## [v3.30.0] - 2023-01-29
|
||||
|
||||
- Improve `Dnatco` extension
|
||||
- Factor out common code in `Dnatco` extension
|
||||
- Add `NtC tube` visual. Applicable for structures with NtC annotation
|
||||
- [Breaking] Rename `DnatcoConfalPyramids` to `DnatcoNtCs`
|
||||
- Improve boundary calculation performance
|
||||
- Add option to create & include images in state snapshots
|
||||
- Fix SSAO artefacts with high bias values
|
||||
- Fix SSAO resolution scale parameter handling
|
||||
- Improve outlines, visually more stable at different view distances
|
||||
|
||||
## [v3.29.0] - 2023-01-15
|
||||
|
||||
- `meshes` extension: Fixed a bug in mesh visualization (show backfaces when opacity < 1)
|
||||
- Add color quick select control to Volume controls
|
||||
- Fix `dropFiles` bug
|
||||
- Fix some cyclic imports and reduce the use of const enums. This should make it easier to use the library with the `isolatedModules: true` TS config.
|
||||
- Fix `dropFiles` bug (#679)
|
||||
- Add `input type='color'` picker to `CombinedColorControl`
|
||||
- Set `ParameterMappingControl` disabled when state is updating
|
||||
- Performance tweaks
|
||||
- Update clip `defines` only when changed
|
||||
- Check for identity in structure/unit areEqual methods
|
||||
- Avoid cloning of structure representation parameters
|
||||
- Make SymmetryOperator.createMapping monomorphic
|
||||
- Improve bonding-sphere calculation
|
||||
- Defer Scene properties calculation (markerAverage, opacityAverage, hasOpaque)
|
||||
- Improve checks in in UnitsRepresentation setVisualState
|
||||
- Add StructureElement.Loci.forEachLocation
|
||||
- Add RepresentationRegistry.clear and ThemeRegistry.clear
|
||||
- Add generic Loci support for overpaint, substance, clipping themes
|
||||
- Add `.getCenter` and `.center` to `Camera`
|
||||
- Add support to dim unmarked groups
|
||||
- Add support for marker edge strength
|
||||
|
||||
## [v3.28.0] - 2022-12-20
|
||||
|
||||
- Show histogram in direct volume control point settings
|
||||
- Add `solidInterior` parameter to sphere/cylinder impostors
|
||||
- [Breaking] Tweak `ignoreHydrogens` non-polar handling (introduced in 3.27.0)
|
||||
- Add `meshes` and `volumes-and-segmentations` extensions
|
||||
- See https://molstarvolseg.ncbr.muni.cz/ for more info
|
||||
- Fix missing support for info in `ParamDefinition.Converted`
|
||||
- Add support for multi-visual volume representations
|
||||
- Improve volume isosurface bounding-sphere
|
||||
- Add basic volume segmentation support to core
|
||||
- Add `Volume.Segment` model
|
||||
- Add `Segmentation` custom volume property
|
||||
- Add `SegmentRepresentation` representation
|
||||
- Add `volume-segment` color theme
|
||||
- Fix GPU marching cubes failing for large meshes with webgl2 (due to use of float16)
|
||||
|
||||
## [v3.27.0] - 2022-12-15
|
||||
|
||||
- Add an `includeTransparent` parameter to hide/show outlines of components that are transparent
|
||||
- Fix 'once' for animations of systems with many frames
|
||||
- Better guard against issue (black fringes) with bumpiness in impostors
|
||||
- Improve impostor shaders
|
||||
- Fix sphere near-clipping with orthographic projection
|
||||
- Fix cylinder near-clipping
|
||||
- Add interior cylinder caps
|
||||
- Add per-pixel object clipping
|
||||
- Fix `QualityAssessment` assignment bug for structures with different auth vs label sequence numbering
|
||||
- Refresh `ApplyActionControl`'s param definition when toggling expanded state
|
||||
- Fix `struct_conn` bond assignment for ions
|
||||
- Ability to show only polar hydrogens
|
||||
|
||||
## [v3.26.0] - 2022-12-04
|
||||
|
||||
- Support for ``powerPreference`` webgl attribute. Add ``PluginConfig.General.PowerPreference`` and ``power-preference`` Viewer GET param.
|
||||
- Excluded common protein caps `NME` and `ACE` from the ligand selection query
|
||||
- Add screen-space shadow post-processing effect
|
||||
- Add "Structure Molecular Surface" visual
|
||||
- Add `external-volume` theme (coloring of arbitrary geometries by user-selected volume)
|
||||
|
||||
## [v3.25.1] - 2022-11-20
|
||||
|
||||
- Fix edge-case in `Structure.eachUnitPair` with single-element units
|
||||
- Fix 'auto' structure-quality for coarse models
|
||||
|
||||
## [v3.25.0] - 2022-11-16
|
||||
|
||||
- Fix handling of gzipped assets (reverts #615)
|
||||
|
||||
## [v3.24.0] - 2022-11-13
|
||||
|
||||
- Make `PluginContext.initContainer` checkered canvas background optional
|
||||
- Store URL of downloaded assets to detect zip/gzip based on extension (#615)
|
||||
- Add optional `operator.key`; can be referenced in `IndexPairBonds`
|
||||
- Add overpaint/transparency/substance theme strength to representations
|
||||
- Fix viewport color for transparent background
|
||||
|
||||
## [v3.23.0] - 2022-10-19
|
||||
|
||||
- Add `PluginContext.initContainer/mount/unmount` methods; these should make it easier to reuse a plugin context with both custom and built-in UI
|
||||
- Add `PluginContext.canvas3dInitialized`
|
||||
- `createPluginUI` now resolves after the 3d canvas has been initialized
|
||||
- Change EM Volume Streaming default from `Whote Structure` to `Auto`
|
||||
- Change EM Volume Streaming default from `Whole Structure` to `Auto`
|
||||
|
||||
## [v3.22.0] - 2022-10-17
|
||||
|
||||
- Replace `VolumeIsosurfaceParams.pickingGranularity` param with `Volume.PickingGranuality`
|
||||
- Replace `VolumeIsosurfaceParams.pickingGranularity` param with `Volume.PickingGranuality`
|
||||
|
||||
## [v3.21.0] - 2022-10-17
|
||||
|
||||
|
||||
32
README.md
@@ -1,6 +1,6 @@
|
||||
[](./LICENSE)
|
||||
[](https://www.npmjs.com/package/molstar)
|
||||
[](https://travis-ci.org/molstar/molstar)
|
||||
[](https://github.com/molstar/molstar/actions/workflows/node.yml)
|
||||
[](https://gitter.im/molstar/Lobby)
|
||||
|
||||
# Mol*
|
||||
@@ -84,7 +84,16 @@ Wipes the `build` and `lib` directories and `.tsbuildinfo` files.
|
||||
|
||||
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.
|
||||
Use these commands to resolve occasional 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.
|
||||
|
||||
### Develop with `esbuild`
|
||||
|
||||
Experimental support for faster builds with `esbuild`
|
||||
- `npm run dev:all` - watch mode for all apps and examples
|
||||
- `npm run dev:viewer` - watch mode for viewer
|
||||
- `npm run dev:apps` - watch mode for all apps
|
||||
- `npm run dev:examples` - watch mode for all examples
|
||||
- `npm run dev -- -a <app name 1> <app name 2> -e <example name 1> ...` - watch mode for specified apps/examples. `-a`/`-e` with without any names will build everything.
|
||||
|
||||
### Build for production:
|
||||
NODE_ENV=production npm run build
|
||||
@@ -103,7 +112,6 @@ From the root of the project:
|
||||
|
||||
and navigate to `build/viewer`
|
||||
|
||||
|
||||
### Code generation
|
||||
**CIF schemas**
|
||||
|
||||
@@ -124,10 +132,6 @@ and navigate to `build/viewer`
|
||||
|
||||
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**
|
||||
|
||||
node node_modules/@graphql-codegen/cli/cjs/bin -c src/extensions/rcsb/graphql/codegen.yml
|
||||
|
||||
### Other scripts
|
||||
**Create chem comp bond table**
|
||||
|
||||
@@ -167,13 +171,12 @@ If node complains about a missing acorn peer dependency, run the following comma
|
||||
|
||||
### Editor
|
||||
|
||||
To get syntax highlighting for shader and graphql files add the following to Visual Code's settings files and make sure relevant extensions are installed in the editor.
|
||||
To get syntax highlighting for shader files add the following to Visual Code's settings files and make sure relevant extensions are installed in the editor.
|
||||
|
||||
"files.associations": {
|
||||
"*.glsl.ts": "glsl",
|
||||
"*.frag.ts": "glsl",
|
||||
"*.vert.ts": "glsl",
|
||||
"*.gql.ts": "graphql"
|
||||
"*.vert.ts": "glsl"
|
||||
},
|
||||
|
||||
## Publish
|
||||
@@ -187,9 +190,14 @@ To get syntax highlighting for shader and graphql files add the following to Vis
|
||||
npm publish
|
||||
|
||||
## Deploy
|
||||
To prepare apps and demos for https://molstar.org deploy, run:
|
||||
|
||||
npm run test
|
||||
npm run build
|
||||
node ./scripts/deploy.js # currently updates the viewer on molstar.org/viewer
|
||||
npm run deploy:local
|
||||
|
||||
To commit these changes remotely to the `molstar/molstar.github.io` repo:
|
||||
|
||||
npm run deploy:remote
|
||||
|
||||
## Contributing
|
||||
Just open an issue or make a pull request. All contributions are welcome.
|
||||
|
||||
@@ -107,6 +107,7 @@ entity.id
|
||||
entity.type
|
||||
entity.src_method
|
||||
entity.pdbx_description
|
||||
entity.pdbx_parent_entity_id
|
||||
entity.formula_weight
|
||||
entity.pdbx_number_of_molecules
|
||||
entity.details
|
||||
@@ -262,6 +263,7 @@ software.version
|
||||
struct.entry_id
|
||||
struct.title
|
||||
struct.pdbx_descriptor
|
||||
struct.pdbx_structure_determination_methodology
|
||||
|
||||
struct_asym.id
|
||||
struct_asym.pdbx_blank_PDB_chainid_flag
|
||||
@@ -366,18 +368,43 @@ struct_site.details
|
||||
|
||||
struct_site_gen.id
|
||||
struct_site_gen.site_id
|
||||
struct_site_gen.pdbx_num_res
|
||||
struct_site_gen.label_comp_id
|
||||
struct_site_gen.auth_asym_id
|
||||
struct_site_gen.auth_atom_id
|
||||
struct_site_gen.auth_comp_id
|
||||
struct_site_gen.auth_seq_id
|
||||
struct_site_gen.details
|
||||
struct_site_gen.label_alt_id
|
||||
struct_site_gen.label_asym_id
|
||||
struct_site_gen.label_atom_id
|
||||
struct_site_gen.label_comp_id
|
||||
struct_site_gen.label_seq_id
|
||||
struct_site_gen.pdbx_auth_ins_code
|
||||
struct_site_gen.auth_comp_id
|
||||
struct_site_gen.auth_asym_id
|
||||
struct_site_gen.auth_seq_id
|
||||
struct_site_gen.label_atom_id
|
||||
struct_site_gen.label_alt_id
|
||||
struct_site_gen.pdbx_num_res
|
||||
struct_site_gen.symmetry
|
||||
struct_site_gen.details
|
||||
|
||||
struct_site_keywords.site_id
|
||||
struct_site_keywords.text
|
||||
|
||||
struct_mon_prot_cis.pdbx_id
|
||||
struct_mon_prot_cis.auth_asym_id
|
||||
struct_mon_prot_cis.auth_comp_id
|
||||
struct_mon_prot_cis.auth_seq_id
|
||||
struct_mon_prot_cis.label_alt_id
|
||||
struct_mon_prot_cis.label_asym_id
|
||||
struct_mon_prot_cis.label_comp_id
|
||||
struct_mon_prot_cis.label_seq_id
|
||||
struct_mon_prot_cis.pdbx_PDB_ins_code
|
||||
struct_mon_prot_cis.pdbx_PDB_ins_code_2
|
||||
struct_mon_prot_cis.pdbx_PDB_model_num
|
||||
struct_mon_prot_cis.pdbx_auth_asym_id_2
|
||||
struct_mon_prot_cis.pdbx_auth_comp_id_2
|
||||
struct_mon_prot_cis.pdbx_auth_ins_code
|
||||
struct_mon_prot_cis.pdbx_auth_ins_code_2
|
||||
struct_mon_prot_cis.pdbx_auth_seq_id_2
|
||||
struct_mon_prot_cis.pdbx_label_asym_id_2
|
||||
struct_mon_prot_cis.pdbx_label_comp_id_2
|
||||
struct_mon_prot_cis.pdbx_label_seq_id_2
|
||||
struct_mon_prot_cis.pdbx_omega_angle
|
||||
|
||||
symmetry.entry_id
|
||||
symmetry.cell_setting
|
||||
@@ -849,6 +876,17 @@ ma_qa_metric_local.metric_value
|
||||
ma_qa_metric_local.model_id
|
||||
ma_qa_metric_local.ordinal_id
|
||||
|
||||
ma_qa_metric_local_pairwise.ordinal_id
|
||||
ma_qa_metric_local_pairwise.model_id
|
||||
ma_qa_metric_local_pairwise.label_asym_id_1
|
||||
ma_qa_metric_local_pairwise.label_comp_id_1
|
||||
ma_qa_metric_local_pairwise.label_seq_id_1
|
||||
ma_qa_metric_local_pairwise.label_asym_id_2
|
||||
ma_qa_metric_local_pairwise.label_comp_id_2
|
||||
ma_qa_metric_local_pairwise.label_seq_id_2
|
||||
ma_qa_metric_local_pairwise.metric_id
|
||||
ma_qa_metric_local_pairwise.metric_value
|
||||
|
||||
ma_software_group.group_id
|
||||
ma_software_group.ordinal_id
|
||||
ma_software_group.software_id
|
||||
|
||||
|
15
data/pwa/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
The files in this directory are used for deploying the Mol* Viewer as a PWA (Progressive Web App) at https://molstar.org/viewer/. They may serve as an example for creating your own PWA but wont work as-is. See `/script/deploy.js` for where these files are copied and how they are transformed during deployment.
|
||||
|
||||
|
||||
## PWA features
|
||||
|
||||
- The Service Worker will cache static resources so the Viewer can be used without internet access. This works without installing, i.e., also in Firefox.
|
||||
- Once installed, file types listed in the Manifest can be opened from, e.g., the Windows File Explorer.
|
||||
|
||||
|
||||
## Notes for development
|
||||
|
||||
In Chrome you can see a list of installed PWAs at chrome://apps/. A right-click opens a menu with an option uninstall.
|
||||
|
||||
The Chrome Dev Tools have a section 'Application' to inspect and manage PWA aspects like the Manifest and Service Workers.
|
||||
BIN
data/pwa/logo-144.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
39
data/pwa/manifest.webmanifest
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"id": "https://molstar.org/viewer/",
|
||||
"name": "Mol* Viewer",
|
||||
"short_name": "Mol*",
|
||||
"description": "Mol* Viewer: a modern web app for 3D visualization and analysis of large biomolecular structures.",
|
||||
"start_url": "./index.html",
|
||||
"theme_color": "#eeece7",
|
||||
"background_color": "#eeece7",
|
||||
"display": "standalone",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "48x48"
|
||||
},
|
||||
{
|
||||
"src": "logo-144.png",
|
||||
"sizes": "144x144"
|
||||
}
|
||||
],
|
||||
"file_handlers": [
|
||||
{
|
||||
"action": "./index.html",
|
||||
"accept": {
|
||||
"application/vnd.molstar": [".molx", ".molj"],
|
||||
"text/plain": [
|
||||
".mol", ".mol2", ".sdf", ".sd", ".pdb", ".ent", ".pdbqt", ".cif", ".mcif", ".mmcif", ".xyz", ".gro", ".lammpstrj",
|
||||
".cub", ".cube", ".dx"
|
||||
],
|
||||
"application/octet-stream": [
|
||||
".bcif",
|
||||
".dxbin", ".ccp4", ".mrc", ".map", ".dsn6", ".brix"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"launch_handler": {
|
||||
"client_mode": ["auto"]
|
||||
}
|
||||
}
|
||||
44
data/pwa/pwa.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Andy Turner <agdturner@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
window.addEventListener('molstarViewerCreated', e => {
|
||||
const viewer = e.detail.viewer;
|
||||
|
||||
// Handle incoming files
|
||||
if ('launchQueue' in window) {
|
||||
launchQueue.setConsumer((launchParams) => {
|
||||
if (!launchParams.files.length) return;
|
||||
|
||||
const files = [];
|
||||
for (const fileHandle of launchParams.files) {
|
||||
files.push(fileHandle.getFile());
|
||||
}
|
||||
|
||||
Promise.all(files).then((files) => {
|
||||
viewer.loadFiles(files);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Register Progressive Web App service worker.
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('./sw.js')
|
||||
.then(function (registration) {
|
||||
// Registration was successful
|
||||
if (molstar.isDebugMode) {
|
||||
console.log('ServiceWorker registration successful with scope: ', registration.scope);
|
||||
}
|
||||
}, function (err) {
|
||||
// registration failed :(
|
||||
if (molstar.isDebugMode) {
|
||||
console.error('ServiceWorker registration failed: ', err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
60
data/pwa/sw.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Andy Turner <agdturner@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
/** version from package.json, to be filled in during deployment */
|
||||
const VERSION = '__MOLSTAR_VERSION__';
|
||||
|
||||
const CACHE_NAME = `molstar-viewer-${VERSION}`;
|
||||
|
||||
// The static resources that the app needs to function.
|
||||
const APP_STATIC_RESOURCES = [
|
||||
'favicon.ico',
|
||||
'index.html',
|
||||
'molstar.css',
|
||||
'molstar.js',
|
||||
'manifest.webmanifest',
|
||||
'logo-144.png',
|
||||
'pwa.js'
|
||||
];
|
||||
|
||||
async function cacheStaticResources() {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
await cache.addAll(APP_STATIC_RESOURCES);
|
||||
await self.skipWaiting(); // Ensures the new service worker takes control immediately.
|
||||
}
|
||||
|
||||
async function deleteOldCaches() {
|
||||
const keys = await caches.keys();
|
||||
await Promise.all(
|
||||
keys.map((key) => {
|
||||
if (key !== CACHE_NAME) {
|
||||
return caches.delete(key);
|
||||
}
|
||||
}),
|
||||
);
|
||||
await self.clients.claim(); // Ensures the new service worker takes control immediately.
|
||||
}
|
||||
|
||||
async function respondWithCacheFirst(request) {
|
||||
// Try to match the request with the cache
|
||||
const cachedResponse = await caches.match(request);
|
||||
return cachedResponse || fetch(request);
|
||||
}
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
// console.log(`Service Worker version ${VERSION} installed.`);
|
||||
event.waitUntil(cacheStaticResources());
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
// console.log(`Service Worker version ${VERSION} activated.`);
|
||||
event.waitUntil(deleteOldCaches());
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
event.respondWith(respondWithCacheFirst(event.request));
|
||||
});
|
||||
11
docs/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Mol* Developer Documentation
|
||||
|
||||
Contributions to the documentations are highly welcome! Please make a pull request with your changes.
|
||||
|
||||
Requires Python 3.x to build. From this directory:
|
||||
|
||||
```
|
||||
pip install mkdocs-material
|
||||
mkdocs serve
|
||||
```
|
||||
|
||||
41
docs/docs/data-access-tools/convert-to-bcif.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Convert CIF to BinaryCIF
|
||||
BinaryCIF is an efficient, binary flavor of the CIF format. See [specification](https://github.com/molstar/BinaryCIF) and [publication](https://doi.org/10.1371/journal.pcbi.1008247) for further details.
|
||||
|
||||
This script reads data in CIF format and converts it lossless to a BinaryCIF file that can be read by Mol* or other
|
||||
applications.
|
||||
|
||||
## Example
|
||||
```sh
|
||||
node lib/commonjs/cli/cif2bcif/index.js file.cif file.bcif
|
||||
```
|
||||
|
||||
## Usage
|
||||
| Argument | Description |
|
||||
| --- | --- |
|
||||
| `src` | Source CIF to convert (can be gzipped) |
|
||||
| `out` | Generated BinaryCIF output path |
|
||||
| `-c` | Path to optional config file |
|
||||
| `-f` | Path to optional filter file |
|
||||
|
||||
```sh
|
||||
index.js [-h] [-c CONFIG] [-f FILTER] src out
|
||||
```
|
||||
|
||||
### Config file
|
||||
Controls how certain columns will be encoded. This is a JSON array of instructions:
|
||||
```ts
|
||||
interface EncodingStrategyHint {
|
||||
categoryName: string,
|
||||
columnName: string,
|
||||
encoding: 'pack' | 'rle' | 'delta' | 'delta-rle',
|
||||
precision?: number
|
||||
}
|
||||
```
|
||||
Identify a particular CIF columns by its name and override the encoding by Integer Packing, Run-Length Encoding, Delta
|
||||
Encoding, or Delta & Run-Length Encoding. You can optionally control the precision if dealing with float values.
|
||||
|
||||
### Filter file
|
||||
Specifies which categories and columns will be written. This is a plain text file, each line represents one entry.
|
||||
You can specify explicitly which categories or columns to include by adding `category_name` or
|
||||
`category_name.field_name`. You can also choose to ignore some categories or columns by adding `!category_name` or
|
||||
`!category_name.field_name`.
|
||||
25
docs/docs/data-access-tools/create-ccd-table.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Create Table from CCD
|
||||
The [Chemical Component Dictionary (CCD)](https://www.wwpdb.org/data/ccd) is as an external reference file describing
|
||||
all residue and small molecule components found in PDB entries. The
|
||||
[Protonation Variants Companion Dictionary (PVCD)](https://www.wwpdb.org/data/ccd) enumerates protonation variants of
|
||||
canonical amino acids.
|
||||
|
||||
This script bundles all `chem_comp_bond` information from the CCD and the PVCD into a single file for later use.
|
||||
Optionally, it can also generate a second output file that contains all `chem_comp_atom` information.
|
||||
|
||||
## Example
|
||||
```sh
|
||||
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-table.js build/data/ccb.bcif -b
|
||||
```
|
||||
|
||||
## Usage
|
||||
| Argument | Description |
|
||||
| --- | --- |
|
||||
| `out` | Generated file output path |
|
||||
| `--forceDownload`, `-f` | Force download of CCD and PVCD |
|
||||
| `--binary`, `-b` | Output as BinaryCIF |
|
||||
| `--ccaOut`, `-a` | File output path of optionally generated chem_comp_atom |
|
||||
|
||||
```sh
|
||||
create-table.js [-h] [--forceDownload] [--binary] [--ccaOut CCAOUT] out
|
||||
```
|
||||
20
docs/docs/data-access-tools/extract-ccd-ions.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Extract Ions from CCD
|
||||
The [Chemical Component Dictionary (CCD)](https://www.wwpdb.org/data/ccd) is as an external reference file describing
|
||||
all residue and small molecule components found in PDB entries.
|
||||
|
||||
This script extracts all ions from the CCD and provides their names as TypeScript set.
|
||||
|
||||
## Example
|
||||
```sh
|
||||
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-ions.js src/mol-model/structure/model/types/ions.ts
|
||||
```
|
||||
|
||||
## Usage
|
||||
| Argument | Description |
|
||||
| --- | --- |
|
||||
| `out` | Generated file output path |
|
||||
| `--forceDownload`, `-f` | Force download of CCD |
|
||||
|
||||
```sh
|
||||
create-ions.js [-h] [--forceDownload] out
|
||||
```
|
||||
119
docs/docs/data-access-tools/model-server.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Model Server
|
||||
|
||||
Provides access to molecular 1D, 2D, and 3D (sub-)structure models of molecules. Substructures are described by the
|
||||
mol-script (MolQL) language. It has the ability to include additional data to mmCIF “on the fly”, e.g. integrate
|
||||
primary PDB archival data from [Chemical Component Dictionary (CCD)](https://www.wwpdb.org/data/ccd),
|
||||
[Protonation Variants Companion Dictionary (PVCD)](https://www.wwpdb.org/data/ccd) and
|
||||
[Biologically Interesting moleculeReference Dictionary (BIRD)](https://www.wwpdb.org/data/bird).
|
||||
|
||||
## Example
|
||||
```sh
|
||||
node lib/commonjs/servers/model/server --sourceMap pdb-bcif '/opt/data/bcif/${id}.bcif'
|
||||
```
|
||||
|
||||
## Usage
|
||||
| Argument | Description |
|
||||
| --- | --- |
|
||||
| `--version`, `-v` | Show program's version number and exit. |
|
||||
| `--cfg` | JSON config file path. If a property is not specified, cmd line param/OS variable/default value are used. |
|
||||
| `--printCfg` | Print current config for validation and exit. |
|
||||
| `--cfgTemplate` | Prints default JSON config template to be modified and exit. |
|
||||
| `--apiPrefix` | Specify the prefix of the API, i.e. <host>/<apiPrefix>/<API queries> |
|
||||
| `--defaultPort` | Specify the port the server is running on |
|
||||
| `--cacheMaxSizeInBytes` | Read structures are cached, this specifies the cache size, 0 for off. |
|
||||
| `--cacheEntryTimeoutMs` | Specify in ms how long to keep entries in cache. |
|
||||
| `--requestTimeoutMs` | The maximum number of ms the server spends on a request. |
|
||||
| `--queryTimeoutMs` | The maximum time the server dedicates to executing a query in ms. Does not include the time it takes to read and export the data. |
|
||||
| `--shutdownTimeoutMinutes` | Server will shut down after this amount of minutes, 0 for off. |
|
||||
| `--shutdownTimeoutVarianceMinutes` | Modifies the shutdown timer by +/- `timeoutVarianceMinutes` (to avoid multiple instances shutting at the same time) |
|
||||
| `--maxQueryManyQueries` | Maximum number of queries allowed by the query-many at a time |
|
||||
| `--defaultSource` | modifies which 'sourceMap' source to use by default |
|
||||
| `--sourceMap` | Map `id`s for a `source` to a file path. Example: `pdb-bcif '../../data/bcif/${id}.bcif'` - JS expressions can be used inside `${}`, e.g. `${id.substr(1, 2)}/${id}.mdb` Can be specified multiple times. The `SOURCE` variable (e.g. `pdb-bcif`) is arbitrary and depends on how you plan to use the server. Supported formats: cif, bcif, cif.gz, bcif.gz |
|
||||
| `--sourceMapUrl` | Same as `--sourceMap` but for URL. `--sourceMapUrl src url format` Example: `pdb-cif 'https://www.ebi.ac.uk/pdbe/entry-files/download/${id}_updated.cif' cif` Supported formats: cif, bcif, cif.gz, bcif.gz. Supported protocols: http://, https://, gs:// |
|
||||
|
||||
```sh
|
||||
node lib/commonjs/servers/model/server [-h] [-v]
|
||||
[--cfg CFG]
|
||||
[--printCfg]
|
||||
[--cfgTemplate]
|
||||
[--apiPrefix PREFIX]
|
||||
[--defaultPort PORT]
|
||||
[--cacheMaxSizeInBytes CACHE_SIZE]
|
||||
[--cacheEntryTimeoutMs CACHE_TIMEOUT]
|
||||
[--requestTimeoutMs REQUEST_TIMEOUT]
|
||||
[--queryTimeoutMs QUERY_TIMEOUT]
|
||||
[--shutdownTimeoutMinutes TIME]
|
||||
[--shutdownTimeoutVarianceMinutes VARIANCE]
|
||||
[--maxQueryManyQueries QUERY_MANY_LIMIT]
|
||||
[--defaultSource DEFAULT_SOURCE]
|
||||
[--sourceMap SOURCE PATH]
|
||||
[--sourceMapUrl SOURCE PATH SOURCE_MAP_FORMAT]
|
||||
```
|
||||
|
||||
### Production Use
|
||||
In production, it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
|
||||
### Memory Issues
|
||||
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
|
||||
|
||||
### Preprocessor Example
|
||||
The preprocessor application allows addiing custom data to CIF files and/or
|
||||
[convert CIF to BinaryCIF](./convert-to-bcif.md).
|
||||
```sh
|
||||
node lib/commonjs/servers/model/preprocess
|
||||
```
|
||||
|
||||
### Preprocessor Usage
|
||||
| Argument | Description |
|
||||
| --- | --- |
|
||||
| `--input`, `-i` | Input filename |
|
||||
| `--outCIF`, `-oc` | Output CIF filename |
|
||||
| `--outBCIF`, `-ob` | Output BinaryCIF filename |
|
||||
| `--cfg`, `-c` | Config file path |
|
||||
| `--folderIn`, `-fin` | Convert folder |
|
||||
| `--folderOutCIF`, `-foc` | Convert folder text output |
|
||||
| `--folderOutBCIF`, `-fob` | Convert folder binary output |
|
||||
| `--folderNumProcesses`, `-fp` | Convert folder number processes |
|
||||
|
||||
Example cfg.json:
|
||||
```ts
|
||||
{
|
||||
"numProcesses": 1,
|
||||
"customProperties": {
|
||||
"sources": [ "wwpdb" ],
|
||||
"params": {
|
||||
"wwPDB": {
|
||||
"chemCompBondTablePath": "./build/data/ccb.bcif"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Local Mode
|
||||
The server can be run in local/file based mode using
|
||||
```sh
|
||||
node lib/commonjs/servers/model/query
|
||||
```
|
||||
|
||||
### Custom Properties
|
||||
This feature is still in development.
|
||||
|
||||
It is possible to provide property descriptors that transform data to internal representation and define how it should
|
||||
be exported into one or mode CIF categories. Examples of this are located in the ``mol-model-props`` module and are
|
||||
linked to the server in the config and ``servers/model/properties``.
|
||||
|
||||
## From NPM
|
||||
|
||||
```
|
||||
npm install --production molstar
|
||||
cd ./model-server
|
||||
```
|
||||
|
||||
(or ``node node_modules\.bin\model-server`` in Windows).
|
||||
|
||||
The NPM package contains all the tools mentioned in the previous sections as "binaries":
|
||||
|
||||
- ``model-server``
|
||||
- ``model-server-query``
|
||||
- ``model-server-preprocess``
|
||||
22
docs/docs/data-access-tools/plugin-state-server.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Plugin State Server
|
||||
|
||||
Provides a simple backend for online storing and sharing of Mol* sessions used by
|
||||
[``mol-plugin``](https://github.com/molstar/molstar/tree/master/src/mol-plugin) and
|
||||
[``mol-state``](https://github.com/molstar/molstar/tree/master/src/mol-state) modules.
|
||||
|
||||
## Example
|
||||
```sh
|
||||
node lib/commonjs/servers/plugin-state --workding-folder ~
|
||||
```
|
||||
|
||||
## Usage
|
||||
| Argument | Description |
|
||||
| --- | --- |
|
||||
| `--working-folder` | Working folder path |
|
||||
| `--port` | Server port. Alternatively, use ENV variable PORT. |
|
||||
| `--api-prefix` | Server API prefix |
|
||||
| `--max-states` | Maximum number of states to save |
|
||||
|
||||
```sh
|
||||
node lib/commonjs/servers/plugin-state [-h] --working-folder WORKING_FOLDER [--port PORT] [--api-prefix API_PREFIX] [--max-states MAX_STATES]
|
||||
```
|
||||
@@ -1,9 +1,9 @@
|
||||
Zika Virus
|
||||
==========
|
||||
# VolumeServer Examples
|
||||
|
||||
## Zika Virus
|
||||
|
||||

|
||||
|
||||
1TQN
|
||||
====
|
||||
## 1TQN
|
||||
|
||||

|
||||
@@ -1,5 +1,4 @@
|
||||
How it works
|
||||
============
|
||||
## VolumeServer: How it works
|
||||
|
||||
This document provides a high level overview of how the DensityServer works.
|
||||
|
||||
|
Before Width: | Height: | Size: 292 KiB After Width: | Height: | Size: 292 KiB |
|
Before Width: | Height: | Size: 310 KiB After Width: | Height: | Size: 310 KiB |
126
docs/docs/data-access-tools/volume-server/index.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# VolumeServer
|
||||
|
||||
## What is VolumeServer
|
||||
|
||||
Provides near-instantaneous access to volumetric data including density maps (for instance, from X-ray crystallography
|
||||
or cryo-electron microscopy experiments), spatial distribution data, output from electrostatic calculations. It works by
|
||||
utilizing adaptive downsampling (similar to how Google Earth works).
|
||||
|
||||
It uses the text based CIF and BinaryCIF formats to deliver the data to the client.
|
||||
|
||||
For quick info about the benefits of using the server, check out the [examples](examples.md).
|
||||
|
||||
## Installing and Running
|
||||
|
||||
Requires nodejs 8+.
|
||||
|
||||
### From GitHub
|
||||
|
||||
```
|
||||
git clone https://github.com/molstar/molstar
|
||||
npm install
|
||||
```
|
||||
|
||||
Afterwards, build the project source:
|
||||
|
||||
```
|
||||
npm run build:lib
|
||||
```
|
||||
|
||||
and run the server by
|
||||
|
||||
```
|
||||
node lib/commonjs/servers/volume/server
|
||||
```
|
||||
|
||||
### From NPM
|
||||
|
||||
```
|
||||
npm install --production molstar
|
||||
./volume-server
|
||||
```
|
||||
|
||||
(or ``node node_modules\.bin\volume-server`` in Windows).
|
||||
|
||||
The NPM package contains all the tools mentioned here as "binaries":
|
||||
|
||||
- ``volume-server``
|
||||
- ``volume-server-pack``
|
||||
- ``volume-server-query``
|
||||
|
||||
|
||||
#### Production use
|
||||
|
||||
In production it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
|
||||
|
||||
#### Memory issues
|
||||
|
||||
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
|
||||
|
||||
|
||||
### Preparing the Data
|
||||
|
||||
For the server to work, CCP4/MAP (models 0, 1, 2 are supported) input data need to be converted into a custom block format.
|
||||
To achieve this, use the ``pack`` application (``node lib/commonjs/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
|
||||
|
||||
### Local Mode
|
||||
|
||||
The program ``lib/commonjs/servers/volume/query`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
|
||||
|
||||
### Navigating the Source Code
|
||||
|
||||
The source code is split into 2 mains parts: ``pack`` and ``server``:
|
||||
|
||||
- The ``pack`` part provides the means of converting CCP4 files into the internal block format.
|
||||
- The ``server`` includes
|
||||
- ``query``: the main part of the server that handles a query. ``execute.ts`` is the "entry point".
|
||||
- ``algebra``: linear, "coordinate", and "box" algebra provides the means for calculations necessary to concent a user query into a menaningful response.
|
||||
- API wrapper that handles the requests.
|
||||
|
||||
## Consuming the Data
|
||||
|
||||
|
||||
The data can be consumed in any (modern) browser using the [ciftools library](https://github.com/molstar/ciftools) (or any other piece of code that can read text or binary CIF).
|
||||
|
||||
The [Data Format](./response-data-format.md) document gives a detailed description of the server response format.
|
||||
|
||||
As a reference/example of the server usage is available in Mol* ``mol-plugin`` module.
|
||||
|
||||
## Hosting the server
|
||||
|
||||
### Example
|
||||
|
||||
```sh
|
||||
node lib/commonjs/servers/volume/server --idMap x-ray '/opt/data/xray/${id}.mdb'
|
||||
```
|
||||
|
||||
### Usage
|
||||
| Argument= | Description |
|
||||
| --- | --- |
|
||||
| `--cfg` | JSON config file path. If a property is not specified, cmd line param/OS variable/default value are used. |
|
||||
| `--printCfg` | Print current config for validation and exit. |
|
||||
| `--cfgTemplate` | Prints default JSON config template to be modified and exit. |
|
||||
| `--apiPrefix` | Specify the prefix of the API, i.e. <host>/<apiPrefix>/<API queries> |
|
||||
| `--defaultPort` | Specify the port the server is running on |
|
||||
| `--shutdownTimeoutMinutes` | Server will shut down after this amount of minutes, 0 for off. |
|
||||
| `--shutdownTimeoutVarianceMinutes` | Modifies the shutdown timer by +/- `timeoutVarianceMinutes` (to avoid multiple instances shutting at the same time) |
|
||||
| `--idMap` | Map `id`s for a `type` to a file path. Example: `x-ray '../../data/mdb/xray/${id}-ccp4.mdb'` - JS expressions can be used inside `${}`, e.g. `${id.substr(1, 2)}/${id}.mdb` - Can be specified multiple times. - The `TYPE` variable (e.g. `x-ray`) is arbitrary and depends on how you plan to use the server. By default, Mol* Viewer uses `x-ray` and `em`, but any particular use case may vary. - If using URL, it can be http://, https://, gs:// or file:// protocol.|
|
||||
| `--maxRequestBlockCount` | Maximum number of blocks that could be read in 1 query. This is somewhat tied to the ``maxOutputSizeInVoxelCountByPrecisionLevel`` in that the `<maximum number of voxel> = maxRequestBlockCount * <block size>^3`. The default block size is 96 which corresponds to 28,311,552 voxels with 32 max blocks. |
|
||||
| `--maxFractionalBoxVolume` | The maximum fractional volume of the query box (to prevent queries that are too big). |
|
||||
| `--maxOutputSizeInVoxelCountByPrecisionLevel` | What is the (approximate) maximum desired size in voxel count by precision level - Rule of thumb: `<response gzipped size>` in `[<voxel count> / 8, <voxel count> / 4]`. The maximum number of voxels is tied to maxRequestBlockCount. |
|
||||
|
||||
```sh
|
||||
node lib/commonjs/servers/volume/server [-h] [-v]
|
||||
[--cfg CFG]
|
||||
[--printCfg]
|
||||
[--cfgTemplate]
|
||||
[--apiPrefix PREFIX]
|
||||
[--defaultPort PORT]
|
||||
[--shutdownTimeoutMinutes TIME]
|
||||
[--shutdownTimeoutVarianceMinutes VARIANCE]
|
||||
[--idMap TYPE PATH]
|
||||
[--maxRequestBlockCount COUNT]
|
||||
[--maxFractionalBoxVolume VOLUME]
|
||||
[--maxOutputSizeInVoxelCountByPrecisionLevel LEVEL [LEVEL ...]]
|
||||
```
|
||||
@@ -1,10 +1,8 @@
|
||||
Data Format
|
||||
===========
|
||||
# VolumeServer: Response Data Format
|
||||
|
||||
This document describes the CIF categories and fields generated by the server.
|
||||
|
||||
Query info
|
||||
----------
|
||||
## Query info
|
||||
|
||||
The reponse always contains a data block called ``SERVER`` with this format:
|
||||
|
||||
@@ -28,8 +26,7 @@ _density_server_result.query_box_b[1] 35.737
|
||||
_density_server_result.query_box_b[2] 32.037001
|
||||
```
|
||||
|
||||
Query data
|
||||
----------
|
||||
## Query data
|
||||
|
||||
If the query completed successfully with a non-empty result the response will contain one or more data blocks that correpond to the
|
||||
"channels" present in the data (e.g. for x-ray data there will be ``2Fo-Fc`` and ``Fo-Fc``) channels.
|
||||
@@ -41,6 +38,7 @@ data_2FO-FC
|
||||
#
|
||||
_volume_data_3d_info.name 2Fo-Fc
|
||||
```
|
||||
|
||||
### Axis order
|
||||
|
||||
Axis order determines the order of axes of ``origin``, ``dimensions`` and ``sample_count`` fields. It also specifies
|
||||
4
docs/docs/extensions/interactions.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Interactions extension
|
||||
|
||||
The Interactions extension enables computing or providing custom interactions between multiple selections/structures.
|
||||
For usage, see the [example source code](https://github.com/molstar/molstar/tree/master/src/examples/interactions).
|
||||
3
docs/docs/extensions/mvs/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# MolViewSpec
|
||||
|
||||
Please see the [standalone MolViewSpec documentation](https://molstar.org/mol-view-spec-docs/).
|
||||
112
docs/docs/extensions/mvs/integration-examples.html
Normal file
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<!-- Replace "latest" by the specific version you want to use, e.g. "4.0.0" -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.js"></script>
|
||||
<!-- Replace "latest" by the specific version you want to use, e.g. "4.0.0" -->
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Integration of Mol* with MolViewSpec Extension</h1>
|
||||
<p>
|
||||
This page demonstrates several methods to integrate Mol* Viewer in a web page and use MolViewSpec functionality.
|
||||
See the source HTML to see the actual code.
|
||||
</p>
|
||||
|
||||
|
||||
<h2>Method 1: Get MVS view from a server and pass to the viewer</h2>
|
||||
<p>
|
||||
The recommended method is to serve the MVS view files by your server (either as static files or generated by the
|
||||
server on-demand) and call the <code>loadMvsFromUrl</code> method to retrieve and load them.
|
||||
This example uses a MVS view file from the address specified in the <code>sourceUrl</code> variable.
|
||||
If the MVS view file contains relative references, they will be resolved as relative to <code>sourceUrl</code>.
|
||||
</p>
|
||||
|
||||
<div id="viewer1" style="position: relative; width: 500px; height: 500px;"></div>
|
||||
<script>
|
||||
const sourceUrl = 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1h9t_domain_labels.mvsj';
|
||||
molstar.Viewer.create('viewer1', { layoutIsExpanded: false, layoutShowControls: false })
|
||||
.then(viewer => viewer.loadMvsFromUrl(sourceUrl, 'mvsj'));
|
||||
</script>
|
||||
|
||||
|
||||
<p>
|
||||
A variation of this method uses <code>molstar.PluginExtensions.mvs.loadMVS</code> instead of
|
||||
<code>loadMvsFromUrl</code> and allows replacing the MVS view after it has been loaded.
|
||||
</p>
|
||||
|
||||
<div id="viewer1b" style="position: relative; width: 500px; height: 500px;"></div>
|
||||
<button onclick="loadView1();">View 1</button>
|
||||
<button onclick="loadView2();">View 2</button>
|
||||
<script>
|
||||
let theViewer;
|
||||
function load(viewer, url, replace) {
|
||||
fetch(url)
|
||||
.then(response => response.text())
|
||||
.then(text => molstar.PluginExtensions.mvs.MVSData.fromMVSJ(text))
|
||||
.then(mvsData => molstar.PluginExtensions.mvs.loadMVS(viewer.plugin, mvsData, { sourceUrl: url, sanityChecks: true, replaceExisting: replace }));
|
||||
}
|
||||
function loadView1() {
|
||||
load(theViewer, 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs.mvsj', true);
|
||||
}
|
||||
function loadView2() {
|
||||
load(theViewer, 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs-focus.mvsj', true);
|
||||
}
|
||||
molstar.Viewer.create('viewer1b', { layoutIsExpanded: false, layoutShowControls: false })
|
||||
.then(viewer => {
|
||||
theViewer = viewer;
|
||||
loadView1();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<h2>Method 2: Construct MVS view on frontend and pass to the viewer</h2>
|
||||
<p>
|
||||
Another option is to utilize the MVS builder provided by the extension to build the view on frontend and then
|
||||
pass it to the viewer. This example builds the view in plain JavaScript, directly in a <script> tag in
|
||||
HTML. However, for a better developer experience consider writing the code in TypeScript.
|
||||
If the built MVS view contains relative references, they will be resolved as relative to the URL of this HTML
|
||||
page.
|
||||
</p>
|
||||
|
||||
<div id="viewer2" style="position: relative; width: 500px; height: 500px;"></div>
|
||||
<script>
|
||||
// Build an ad-hoc MVS view
|
||||
const builder = molstar.PluginExtensions.mvs.MVSData.createBuilder();
|
||||
const structure = builder
|
||||
.download({ url: 'https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif' })
|
||||
.parse({ format: 'bcif' })
|
||||
.modelStructure({});
|
||||
structure
|
||||
.component({ selector: 'polymer' })
|
||||
.representation({ type: 'cartoon' })
|
||||
.color({ color: 'green' });
|
||||
structure
|
||||
.component({ selector: 'ligand' })
|
||||
.label({ text: 'Retinoic acid' })
|
||||
.focus({})
|
||||
.representation({ type: 'ball_and_stick' })
|
||||
.color({ color: '#cc3399' });
|
||||
const mvsData = builder.getState();
|
||||
|
||||
// Initialize viewer and load MVSJ
|
||||
const mvsj = molstar.PluginExtensions.mvs.MVSData.toMVSJ(mvsData);
|
||||
molstar.Viewer.create('viewer2', { layoutIsExpanded: false, layoutShowControls: false })
|
||||
.then(viewer => viewer.loadMvsData(mvsj, 'mvsj'));
|
||||
|
||||
// // Alternative initialization and loading (avoids encoding and again decoding the data, allows changing the view by using `replaceExisting: true`):
|
||||
// molstar.Viewer.create('viewer2', { layoutIsExpanded: false, layoutShowControls: false })
|
||||
// .then(viewer => molstar.PluginExtensions.mvs.loadMVS(viewer.plugin, mvsData, { sourceUrl: undefined, sanityChecks: true, replaceExisting: false }));
|
||||
</script>
|
||||
|
||||
|
||||
<p>
|
||||
Again, there is variation with using <code>molstar.PluginExtensions.mvs.loadMVS</code> instead of
|
||||
<code>loadMvsData</code>.
|
||||
</p>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
118
docs/docs/extensions/struct-conn.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# wwPDB StructConn extension
|
||||
|
||||
The STRUCT_CONN category in the mmCIF file format contains details about the connections between portions of the structure. These can be hydrogen bonds, salt bridges, disulfide bridges and so on (see more at <https://mmcif.wwpdb.org/dictionaries/mmcif_pdbx_v40.dic/Categories/struct_conn.html>).
|
||||
|
||||
**wwPDB StructConn extension** in Mol* provides functionality to retrieve and visualize these connections.
|
||||
|
||||
The extension exposes three functions, located in `src/extensions/wwpdb/struct-conn/index.ts`.
|
||||
|
||||
- `getStructConns` - to retrieve struct_conn records from a loaded structure
|
||||
- `inspectStructConn` - to visualize a struct_conn
|
||||
- `clearStructConnInspections` - to remove visulizations created by `inspectStructConn`
|
||||
|
||||
|
||||
## Example 1
|
||||
|
||||
The following example is a minimal HTML using this functionality:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<title>Mol* Viewer</title>
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
</head>
|
||||
<body style="margin: 0px;">
|
||||
<div style="position: absolute; width: 100%; height: 10%; padding-block: 10px;">
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'disulf1');">disulf1</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'disulf2');">disulf2</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'covale1');">covale1</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'covale2');">covale2</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'covale3');">covale3</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'covale4');">covale4</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'metalc1');">metalc1</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'metalc2');">metalc2</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'metalc3');">metalc3</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, '5elb', 'metalc4');">metalc4</button>
|
||||
<button onclick="molstar.PluginExtensions.wwPDBStructConn.clearStructConnInspections(molstarViewer.plugin, '5elb');">CLEAR</button>
|
||||
</div>
|
||||
<div id="app" style="position: absolute; top: 10%; width: 100%; height: 90%;"></div>
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
var molstarViewer;
|
||||
molstar.Viewer.create('app', { layoutIsExpanded: false }).then(viewer => {
|
||||
molstarViewer = viewer;
|
||||
viewer.loadPdb('5elb');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
The PDB ID (`'5elb'`) can be replaced be `undefined`, in which case the functions will apply to the first loaded structure.
|
||||
|
||||
|
||||
## Example 2
|
||||
|
||||
This is a more elaborated example, which automatically loads `5elb` (or any PDB entry given in the URL after `?pdb=`), retrieves the list of struct_conns, and creates a button for each struct_conn.
|
||||
|
||||
Be aware that some of the struct_conns may be present in the deposited model but not in the preferred assembly (default view). The presented example will raise a dialog window with error message in such cases, e.g. `disulf6` in entry `5elb`.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<title>Mol* Viewer - StructConn Extension Demo</title>
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
</head>
|
||||
<style>
|
||||
body { margin: 0px; }
|
||||
#app { position: absolute; width: 85%; height: 100%; }
|
||||
#controls { position: absolute; right: 0; width: 15%; height: 100%; display: flex; flex-direction: column; overflow-y: scroll; }
|
||||
h1 { text-align: center; margin: 12px; font-weight: bold; font-size: 120%; }
|
||||
button { margin: 4px; margin-top: 0px; }
|
||||
</style>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="controls">
|
||||
<h1 id="pdb-id">Loading...</h1>
|
||||
<button onclick="clearInspections();">CLEAR</button>
|
||||
</div>
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
var pdbId = window.location.search.match(/[?&]pdb=(\w+)/i)?.[1]?.toLowerCase() ?? '5elb';
|
||||
var molstarViewer;
|
||||
function inspect(structConnId) {
|
||||
if (molstarViewer?.plugin) {
|
||||
molstar.PluginExtensions.wwPDBStructConn.inspectStructConn(molstarViewer.plugin, pdbId, structConnId).then(nSelectedAtoms => {
|
||||
if (nSelectedAtoms < 2) alert('Some of the interacting atoms were not found :(\n(maybe not present in the viewed assembly)');
|
||||
});
|
||||
}
|
||||
}
|
||||
function clearInspections() {
|
||||
if (molstarViewer?.plugin) {
|
||||
molstar.PluginExtensions.wwPDBStructConn.clearStructConnInspections(molstarViewer.plugin, pdbId);
|
||||
}
|
||||
}
|
||||
molstar.Viewer.create('app', { layoutIsExpanded: false }).then(viewer => {
|
||||
molstarViewer = viewer;
|
||||
return viewer.loadPdb(pdbId);
|
||||
}).then(() => {
|
||||
const structConns = molstar.PluginExtensions.wwPDBStructConn.getStructConns(molstarViewer.plugin, pdbId);
|
||||
const controls = document.getElementById('controls');
|
||||
for (const structConnId in structConns) {
|
||||
const button = document.createElement('button');
|
||||
button.innerText = structConnId;
|
||||
button.addEventListener('click', () => inspect(structConnId));
|
||||
controls.appendChild(button);
|
||||
};
|
||||
document.getElementById('pdb-id').innerHTML = pdbId;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
118
docs/docs/extensions/tunnels.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Tunnel Visualization Extension
|
||||
This documentation outlines the usage of the Mol* extension for visualizing tunnels in molecular structures. The extension integrates with Mol* to render 3D representations of tunnels using specified data sources and properties.
|
||||
|
||||
The extension is a key component in ChannelsDB (https://channelsdb2.biodata.ceitec.cz/), enabling users to visualize tunnels within molecules directly from the database. While it is used with ChannelsDB, users can also input their own data or connect to different databases, ensuring versatility across various research environments.
|
||||
|
||||
## Data Types
|
||||
The primary data types involved in tunnel visualization are:
|
||||
|
||||
### Tunnel
|
||||
A Tunnel object contains the actual tunnel data necessary for visualization. It consists of:
|
||||
|
||||
- `data`: An array of `Profile` objects that describe the tunnel at various points.
|
||||
- `props`: Properties such as the tunnel's type, ID, and optional labels or descriptions.
|
||||
|
||||
### Profile
|
||||
A `Profile` object in a `Tunnel` holds detailed geometric and physical properties of a tunnel at specific points along its length. These properties include:
|
||||
|
||||
- `Charge`: The electric charge at a specific point in the tunnel.
|
||||
- `Radius`: The overall radius of the tunnel at this point.
|
||||
- `FreeRadius`: The radius of the tunnel not obstructed by any molecular elements.
|
||||
- `T`: Temperature factor or a similar property related to the point.
|
||||
- `Distance`: Distance along the tunnel's path from the start.
|
||||
- `X`, `Y`, `Z`: Coordinates of the point in 3D space.
|
||||
|
||||
These profiles are crucial for understanding the physical and chemical environment inside the tunnel, allowing for detailed analysis and visualization.
|
||||
|
||||
Example:
|
||||
```json
|
||||
"Profile": [
|
||||
{
|
||||
"Radius": 1.49,
|
||||
"FreeRadius": 1.49,
|
||||
"T": 0,
|
||||
"Distance": 0,
|
||||
"X": -19.152,
|
||||
"Y": -22.654,
|
||||
"Z": -13.034,
|
||||
"Charge": 0
|
||||
},
|
||||
{
|
||||
"Radius": 1.524,
|
||||
"FreeRadius": 1.524,
|
||||
"T": 0.00625,
|
||||
"Distance": 0.087,
|
||||
"X": -19.162,
|
||||
"Y": -22.596,
|
||||
"Z": -12.969,
|
||||
"Charge": 0
|
||||
},
|
||||
{
|
||||
"Radius": 1.56,
|
||||
"FreeRadius": 1.56,
|
||||
"T": 0.0125,
|
||||
"Distance": 0.174,
|
||||
"X": -19.171,
|
||||
"Y": -22.539,
|
||||
"Z": -12.905,
|
||||
"Charge": 0
|
||||
}
|
||||
]
|
||||
```
|
||||
## Transformers Usage
|
||||
The extension uses several transformations to process and visualize tunnel data:
|
||||
|
||||
### Tunnels Data Transformer
|
||||
- `Purpose`: Converts a collection of Tunnel data into a state object.
|
||||
- `Usage`:
|
||||
```typescript
|
||||
update.toRoot().apply(TunnelsFromRawData, { data: tunnels });
|
||||
```
|
||||
|
||||
### Tunnel Data Provider
|
||||
- `Purpose`: Converts single Tunnel data into a state object for individual processing.
|
||||
- `Usage`:
|
||||
```typescript
|
||||
update.toRoot().apply(TunnelFromRawData, {
|
||||
data: {
|
||||
data: tunnel.Profile,
|
||||
props: { id: tunnel.Id, type: tunnel.Type }
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Tunnel Shape Provider
|
||||
- `Purpose`: Provides the shapes for rendering the tunnel based on WebGL context and shape parameters.
|
||||
- `Usage`:
|
||||
```typescript
|
||||
update.apply(TunnelShapeProvider, {
|
||||
webgl,
|
||||
}).apply(StateTransforms.Representation.ShapeRepresentation3D);
|
||||
```
|
||||
|
||||
## Visualization Examples
|
||||
To help users understand how to use these transformations in practice, include detailed examples:
|
||||
|
||||
### Visualizing Multiple Tunnels
|
||||
This example (see `src/extensions/sb-ncbr/tunnels/examples.ts#L19`) demonstrates how to visualize multiple tunnels from a fetched dataset.
|
||||
```typescript
|
||||
update.toRoot()
|
||||
.apply(TunnelsFromRawData, { data: tunnels })
|
||||
.apply(SelectTunnel)
|
||||
.apply(TunnelShapeProvider, { webgl })
|
||||
.apply(StateTransforms.Representation.ShapeRepresentation3D);
|
||||
```
|
||||
|
||||
### Visualizing a Single Tunnel
|
||||
This example (see `src/extensions/sb-ncbr/tunnels/examples.ts#L46`) shows how to visualize a single tunnel.
|
||||
```typescript
|
||||
update.toRoot()
|
||||
.apply(TunnelFromRawData, {
|
||||
data: {
|
||||
data: tunnel.Profile,
|
||||
props: { id: tunnel.Id, type: tunnel.Type }
|
||||
}
|
||||
})
|
||||
.apply(TunnelShapeProvider, { webgl })
|
||||
.apply(StateTransforms.Representation.ShapeRepresentation3D);
|
||||
```
|
||||
43
docs/docs/index.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Installation
|
||||
|
||||
## NPM Package
|
||||
|
||||
```
|
||||
yarn add molstar
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
npm install molstar
|
||||
```
|
||||
|
||||
Mol* code can then be imported from the ``molstar/lib/...`` namespace, e.g.
|
||||
|
||||
```ts
|
||||
import { PluginContext } from 'molstar/lib/mol-plugin/context';
|
||||
```
|
||||
|
||||
## Clone from GitHub
|
||||
|
||||
|
||||
```
|
||||
git clone https://github.com/molstar/molstar.git
|
||||
cd molstar
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
--------------------
|
||||
|
||||
For a watch task to automatically rebuild the source code on changes, run
|
||||
|
||||
```
|
||||
npm run watch
|
||||
```
|
||||
|
||||
or if working just with the Viewer app for better performance
|
||||
|
||||
```
|
||||
npm run watch-viewer
|
||||
```
|
||||
59
docs/docs/misc/exporting-components.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Exporting components
|
||||
|
||||
Export components data can be useful to reproduce the same view in a different visualization software.
|
||||
To do that, one would need to loop over all components, extract its selection (for example by using atom indices) and its representations (type, coloring and sizing).
|
||||
|
||||
### Getting assets / molecular files
|
||||
|
||||
```js
|
||||
for (const { asset, file } of plugin.managers.asset.assets) {
|
||||
const isFile = asset.asset.kind === 'url'
|
||||
console.log(asset.asset.id)
|
||||
console.log(isFile)
|
||||
const data = await file.arrayBuffer()
|
||||
}
|
||||
```
|
||||
|
||||
### Getting components per structure
|
||||
|
||||
```js
|
||||
import { PluginStateObject as PSO } from 'molstar/lib/mol-plugin-state/objects';
|
||||
//...
|
||||
|
||||
const componentManager = plugin.managers.structure.component;
|
||||
for (const structure of componentManager.currentStructures) {
|
||||
if (!structure.properties) {
|
||||
continue;
|
||||
}
|
||||
const cell = plugin.state.data.select(structure.properties.cell.transform.ref)[0];
|
||||
if (!cell || !cell.obj) {
|
||||
continue;
|
||||
}
|
||||
const structureData = (cell.obj as PSO.Molecule.Structure).data;
|
||||
for (const component of structure.components) {
|
||||
if (!component.cell.obj) {
|
||||
continue;
|
||||
}
|
||||
// For each component in each structure, display the content of the selection
|
||||
Structure.eachAtomicHierarchyElement(component.cell.obj.data, {
|
||||
atom: location => console.log(location.element)
|
||||
});
|
||||
for (const rep of component.representations) {
|
||||
// For each representation of the component, display its type
|
||||
console.log(rep.cell?.transform?.params?.type?.name)
|
||||
|
||||
// Also display the color for each atom
|
||||
const colorThemeName = rep.cell.transform.params?.colorTheme.name;
|
||||
const colorThemeParams = rep.cell.transform.params?.colorTheme.params;
|
||||
const theme = plugin.representation.structure.themes.colorThemeRegistry.create(
|
||||
colorThemeName || '',
|
||||
{ structure: structureData },
|
||||
colorThemeParams
|
||||
) as ColorTheme<typeof colorThemeParams>;
|
||||
Structure.eachAtomicHierarchyElement(component.cell.obj.data, {
|
||||
atom: loc => console.log(theme.color(loc, false))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,3 +1,5 @@
|
||||
# Interesting PDB Entries
|
||||
|
||||
* Cyclic polymers (1sfi, 6dny, 1HVZ)
|
||||
* B-DNA (1bna)
|
||||
* Missing carbonyl oxygen (1gfl)
|
||||
@@ -20,12 +22,13 @@
|
||||
* Discontinuous chains, i.e. gaps in the sequence (3sn6)
|
||||
* Lots of sheets (1cbs)
|
||||
* DNA (2np2, 1d66)
|
||||
* C-alpha only (2rcj)
|
||||
* C-alpha only (2RCJ, 6ZIG, 5AJ2)
|
||||
* Not cyclic, but termini are backbone-only and within distance but seqIds are not compatible (6SW3)
|
||||
* Close backbone atoms but not linked (e.g. 4HIV)
|
||||
* Non-standard residues
|
||||
* Protein (1BRR, 5Z6Y)
|
||||
* DNA (5D3G)
|
||||
* Collagen (6JEC)
|
||||
* Multiple models with different sets of ligands or missing ligands (1J6T, 1VRC, 2ICY, 1O2F)
|
||||
* Long linear sugar chain (4HG6)
|
||||
* Anisotropic B-factors/Ellipsoids (1EJG)
|
||||
@@ -43,7 +46,6 @@
|
||||
* 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)
|
||||
* 1RB8 (Assembly 1, I global)
|
||||
* Assembly symmetries
|
||||
* 5M30 (Assembly 1, C3 local and pseudo)
|
||||
* 1RB8 (Assembly 1, I global)
|
||||
193
docs/docs/plugin/custom-library.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# Building a Custom Library
|
||||
|
||||
This page goes over creating a custom Mol\* based library usable inside a `<script>` tag in an HTML page.
|
||||
|
||||
## Setup
|
||||
|
||||
- Create a new npm/yarn package
|
||||
- Install `molstar` and `esbuild` packages
|
||||
|
||||
```
|
||||
mkdir molstar-lib
|
||||
cd molstar-lib
|
||||
npm init
|
||||
npm install molstar
|
||||
npm install esbuild --save-dev
|
||||
```
|
||||
|
||||
## Example Library Code
|
||||
|
||||
Create new file `src/index.ts` (or `.js` if you don't want to use TypeScript):
|
||||
|
||||
```ts
|
||||
import { DefaultPluginSpec, PluginSpec } from 'molstar/lib/mol-plugin/spec';
|
||||
import { PluginContext } from 'molstar/lib/mol-plugin/context';
|
||||
|
||||
export async function initViewer(element: string | HTMLDivElement, options?: { spec?: PluginSpec }) {
|
||||
const parent = typeof element === 'string' ? document.getElementById(element)! as HTMLDivElement : element;
|
||||
const canvas = document.createElement('canvas') as HTMLCanvasElement;
|
||||
parent.appendChild(canvas);
|
||||
|
||||
const spec = options?.spec ?? DefaultPluginSpec();
|
||||
|
||||
const plugin = new PluginContext(spec);
|
||||
await plugin.init();
|
||||
|
||||
plugin.initViewer(canvas, parent);
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
export async function loadStructure(
|
||||
plugin: PluginContext,
|
||||
url: string,
|
||||
options?: { format?: string, isBinary?: boolean }
|
||||
) {
|
||||
const data = await plugin.builders.data.download(
|
||||
{ url, isBinary: options?.isBinary }
|
||||
);
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(
|
||||
data,
|
||||
options?.format ?? 'mmcif' as any
|
||||
);
|
||||
const preset = await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
|
||||
return preset;
|
||||
}
|
||||
```
|
||||
|
||||
## Building the Library
|
||||
|
||||
Add new commands to the `scripts` section of the `package.json` file
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"build": "esbuild src/index.ts --bundle --outfile=./build/js/index.js --global-name=molstarLib",
|
||||
"watch": "esbuild src/index.ts --bundle --outfile=./build/js/index.js --global-name=molstarLib --watch"
|
||||
}
|
||||
```
|
||||
|
||||
and run the command `npm run build` (or `watch` for interactive development experience). This will create `build/js/index.js` file which can be imported with a `<script>` tag and the exported functions called view the `molstarLib` prefix (you can customize this parameter).
|
||||
|
||||
## Using the Library
|
||||
|
||||
Create file `build/index.html`:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<title>Mol* Library Example</title>
|
||||
</head>
|
||||
<style>
|
||||
#viewer {
|
||||
position: absolute;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" src="./js/index.js"></script>
|
||||
<body>
|
||||
<div id="viewer"></div>
|
||||
<script type="text/javascript">
|
||||
async function init() {1
|
||||
const plugin = await molstarLib.initViewer("viewer");
|
||||
await molstarLib.loadStructure(
|
||||
plugin,
|
||||
"https://models.rcsb.org/4hhb.bcif",
|
||||
{ isBinary: true }
|
||||
);
|
||||
}
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
After opening `index.html` in a browser, you should see
|
||||
|
||||

|
||||
|
||||
## Using Mol* React UI
|
||||
|
||||
The above example does not make use of the default Mol\* React UI and any UI components are therefore the author's responsibility. The below examples show how to (re)use the Mol\* React UI.
|
||||
|
||||
- Create `src/ui.tsx`:
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { DefaultPluginUISpec, PluginUISpec } from 'molstar/lib/mol-plugin-ui/spec';
|
||||
import { PluginUIContext } from 'molstar/lib/mol-plugin-ui/context';
|
||||
import { Plugin } from 'molstar/lib/mol-plugin-ui/plugin';
|
||||
|
||||
export async function initViewerUI(element: string | HTMLDivElement, options?: { spec?: PluginUISpec }) {
|
||||
const parent = typeof element === 'string' ? document.getElementById(element)! as HTMLDivElement : element;
|
||||
const spec = { ...DefaultPluginUISpec(), ...options?.spec };
|
||||
const plugin = new PluginUIContext(spec);
|
||||
await plugin.init();
|
||||
|
||||
createRoot(parent).render(<Plugin plugin={plugin} />)
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
export async function loadStructure(plugin: PluginUIContext, url: string, options?: { format?: string, isBinary?: boolean }) {
|
||||
const data = await plugin.builders.data.download({ url, isBinary: options?.isBinary });
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(data, options?.format ?? 'mmcif' as any);
|
||||
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
|
||||
}
|
||||
```
|
||||
- Create `src/style.scss`:
|
||||
```scss
|
||||
@use '../node_modules/molstar/lib/mol-plugin-ui/skin/light.scss';
|
||||
```
|
||||
- Create `build/ui.html`:
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<title>Mol* UI Library Example</title>
|
||||
</head>
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css" />
|
||||
<style>
|
||||
#viewer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" src="./js/ui.js"></script>
|
||||
<body>
|
||||
<div id="viewer"></div>
|
||||
<script type="text/javascript">
|
||||
async function init() {
|
||||
const plugin = await molstarLib.initViewerUI("viewer", {
|
||||
spec: {
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: true,
|
||||
showControls: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
await molstarLib.loadStructure(plugin, "https://models.rcsb.org/4hhb.bcif", { isBinary: true });
|
||||
}
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
- Install `sass`: `npm install sass -save-dev` (or use [`esbuild` plugin](https://www.npmjs.com/package/esbuild-sass-plugin) and `import` the scss file in `ui.tsx`)
|
||||
- Add scripts to `package.json`:
|
||||
```json
|
||||
"build-ui": "esbuild src/ui.tsx --bundle --outfile=./build/js/ui.js --global-name=molstarLib",
|
||||
"css": "sass src/style.scss ./build/css/style.css"
|
||||
```
|
||||
- Run `npm run build-ui` and `npm run css` (skip if using `esbuild-sass-plugin`)
|
||||
- Opening `build/ui.html`:
|
||||

|
||||
@@ -23,7 +23,7 @@ interface Snapshot {
|
||||
|
||||
When defining the state object, all components are optional, i.e., it is possible to define just the ``data`` component.
|
||||
|
||||
Example state is available [here](example-state.json). In the plugin, it is possible to create and load these objects using ``Download JSON``
|
||||
Example state is available [here](./example-state.json). In the plugin, it is possible to create and load these objects using ``Download JSON``
|
||||
and ``Open JSON`` buttons in the ``State Snapshots`` section.
|
||||
|
||||
# State Tree
|
||||
@@ -69,7 +69,7 @@ interface Transform.Props {
|
||||
}
|
||||
```
|
||||
|
||||
"Built-in" data state transforms and description of their parameters are defined in ``mol-plugin/state/transforms``. Behavior transforms are defined in ``mol-plugin/behavior``. Auto-generated documentation for the transforms is also [available](transforms.md).
|
||||
"Built-in" data state transforms and description of their parameters are defined in ``mol-plugin/state/transforms``. Behavior transforms are defined in ``mol-plugin/behavior``.
|
||||
|
||||
# Animation State
|
||||
|
||||
3
docs/docs/plugin/examples.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Plugin Examples
|
||||
|
||||
Refer to Mol* [Apps](https://github.com/molstar/molstar/tree/master/src/apps) and [Examples](https://github.com/molstar/molstar/tree/master/src/examples).
|
||||
274
docs/docs/plugin/instance.md
Normal file
@@ -0,0 +1,274 @@
|
||||
# Creating Plugin Instance
|
||||
|
||||
|
||||
## Intro
|
||||
|
||||
What is a plugin? A plugin is a collection of modules that provide functionality to the `Mol*` UI. The plugin is responsible for managing the state of the viewer, internal and user interactions. It has been a previous point of confusion for new users of `Mol*` to associate the __viewer__ part of the library with what is further referred to as the __plugin__. These two are closely connected in the `molstar-plugin-ui` module, which is the user-facing part of the library and ultimately provides the viewer, but they are ultimately distinct.
|
||||
|
||||
|
||||
It is recommended that you inspect the general class structure of [`PluginInitWrapper`](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-ui/plugin.tsx#L41), [`PluginUIContext`](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-ui/context.ts#L12) and [`PluginUIComponent`](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-ui/base.tsx#L16) to better understand the flow of data and events in the plugin.
|
||||
A passing analogy is that a [ `PluginContext` ](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin/context.ts#L71) is the engine that powers computation, rendering, events and subscriptions inside the molstar UI. All UI components depend on `PluginContext`.
|
||||
|
||||
|
||||
|
||||
There are 4 basic ways of instantiating the Mol* plugin.
|
||||
|
||||
## ``Viewer`` wrapper
|
||||
|
||||
- The most basic usage is to use the ``Viewer`` wrapper. This is best suited for use cases that do not require much custom behavior and are mostly about just displaying a structure.
|
||||
- See ``Viewer`` class is defined in [src/apps/viewer/app.ts](https://github.com/molstar/molstar/blob/master/src/apps/viewer/app.ts) for available methods and options.
|
||||
|
||||
Example usage without using WebPack:
|
||||
|
||||
```HTML
|
||||
<style>
|
||||
#app {
|
||||
position: absolute;
|
||||
left: 100px;
|
||||
top: 100px;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
}
|
||||
</style>
|
||||
<!--
|
||||
molstar.js and .css are obtained from
|
||||
- the folder build/viewer after cloning and building the molstar package
|
||||
- from the build/viewer folder in the Mol* NPM package
|
||||
-->
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
molstar.Viewer.create('app', {
|
||||
layoutIsExpanded: false,
|
||||
layoutShowControls: false,
|
||||
layoutShowRemoteState: false,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: false,
|
||||
layoutShowLeftPanel: true,
|
||||
|
||||
viewportShowExpand: true,
|
||||
viewportShowSelectionMode: false,
|
||||
viewportShowAnimation: false,
|
||||
|
||||
pdbProvider: 'rcsb',
|
||||
emdbProvider: 'rcsb',
|
||||
}).then(viewer => {
|
||||
viewer.loadPdb('7bv2');
|
||||
viewer.loadEmdb('EMD-30210', { detail: 6 });
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
When using WebPack (or possibly other build tool) with the Mol* NPM package installed, the viewer class can be imported using
|
||||
|
||||
```ts
|
||||
import { Viewer } from 'molstar/build/viewer/molstar'
|
||||
|
||||
function initViewer(target: string | HTMLElement) {
|
||||
return new Viewer(target, { /* options */})
|
||||
}
|
||||
```
|
||||
|
||||
## ``PluginContext`` with built-in React UI
|
||||
|
||||
- For more customization options it is possible to use the [``PluginContext``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/context.ts) directly.
|
||||
- When creating the plugin instance it is possible to customize the [``PluginSpec``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/spec.ts).
|
||||
- The default [``PluginSpec``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/spec.ts) is available [here](https://github.com/molstar/molstar/blob/master/src/mol-plugin/spec.ts).
|
||||
- [``PluginConfig``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/config.ts) object provides additional customization options.
|
||||
- See the [Viewer State Management](viewer-state.md) section for more information on customizing things like background.
|
||||
- See the [Data State Management](data-state.md) section for more information on build the state.
|
||||
|
||||
```ts
|
||||
import { DefaultPluginUISpec, PluginUISpec } from 'molstar/lib/mol-plugin-ui/spec';
|
||||
import { createPluginUI } from 'molstar/lib/mol-plugin-ui';
|
||||
import { renderReact18 } from 'molstar/lib/mol-plugin-ui/react18';
|
||||
import { PluginConfig } from 'molstar/lib/mol-plugin/config';
|
||||
|
||||
const MySpec: PluginUISpec = {
|
||||
...DefaultPluginUISpec(),
|
||||
config: [
|
||||
[PluginConfig.VolumeStreaming.Enabled, false]
|
||||
]
|
||||
}
|
||||
|
||||
async function createPlugin(parent: HTMLElement) {
|
||||
const plugin = await createPluginUI({
|
||||
target: parent,
|
||||
spec: MySpec,
|
||||
render: renderReact18
|
||||
});
|
||||
|
||||
const data = await plugin.builders.data.download({ url: '...' }, { state: { isGhost: true } });
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
|
||||
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
createPlugin(document.getElementById('app')!); // app is a <div> element with position: relative
|
||||
```
|
||||
|
||||
To use the plugin (with the React UI) inside another React app:
|
||||
|
||||
A single-plugin setup is shown the example below. In order to initialize multiple
|
||||
plugins, each with its own context and viewport, some extra steps are required (docs section to be added).
|
||||
|
||||
```ts
|
||||
import { useEffect, createRef } from "react";
|
||||
import { createPluginUI } from "molstar/lib/mol-plugin-ui";
|
||||
import { renderReact18 } from "molstar/lib/mol-plugin-ui/react18";
|
||||
import { PluginUIContext } from "molstar/lib/mol-plugin-ui/context";
|
||||
/* Might require extra configuration,
|
||||
see https://webpack.js.org/loaders/sass-loader/ for example.
|
||||
create-react-app should support this natively. */
|
||||
import "molstar/lib/mol-plugin-ui/skin/light.scss";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
molstar?: PluginUIContext;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function MolStarWrapper() {
|
||||
const parent = createRef<HTMLDivElement>();
|
||||
|
||||
// In debug mode of react's strict mode, this code will
|
||||
// be called twice in a row, which might result in unexpected behavior.
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
window.molstar = await createPluginUI({
|
||||
target: parent.current as HTMLDivElement,
|
||||
render: renderReact18
|
||||
});
|
||||
|
||||
const data = await window.molstar.builders.data.download(
|
||||
{ url: "https://files.rcsb.org/download/3PTB.pdb" }, /* replace with your URL */
|
||||
{ state: { isGhost: true } }
|
||||
);
|
||||
const trajectory =
|
||||
await window.molstar.builders.structure.parseTrajectory(data, "pdb");
|
||||
await window.molstar.builders.structure.hierarchy.applyPreset(
|
||||
trajectory,
|
||||
"default"
|
||||
);
|
||||
}
|
||||
init();
|
||||
return () => {
|
||||
window.molstar?.dispose();
|
||||
window.molstar = undefined;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <div ref={parent} style={{ width: 640, height: 480 }}/>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
Furthermore, if it is desirable in your project to use the `molstar`'s React UI components, but you wish to alter or rearrange the layout, you should take a look at the signatures of [ `PluginUIComponent` ](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-ui/base.tsx#L16) which every "control" subclasses.
|
||||
|
||||
|
||||
[ `SequenceView` ](https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-ui/sequence.tsx#L221C4-L221C4), for example, can be used separately from the `PluginUI`. Yet you would need to pass the `PluginUIContext` to it in order for it to observe the changes in the state of the plugin. This can be done via a `PluginContextContainer`:
|
||||
```typescript
|
||||
// your_app.plugin: PluginUIContext
|
||||
...
|
||||
<div className="your_custom_ui">
|
||||
<PluginContextContainer plugin={your_app.plugin}>
|
||||
<SequenceView />
|
||||
</PluginContextContainer>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Directly using Mol* React UI
|
||||
|
||||
```ts
|
||||
class MolStarWrapper {
|
||||
private resolveInit: () => void;
|
||||
initialized = new Promise<boolean>(res => { this.resolveInit = () => res(true); });
|
||||
|
||||
private initCalled = false;
|
||||
plugin: PluginUIContext;
|
||||
async init() {
|
||||
if (this.initCalled) return;
|
||||
this.initCalled = true;
|
||||
this.plugin = ...;
|
||||
await this.plugin.init();
|
||||
this.resolveInit();
|
||||
}
|
||||
}
|
||||
|
||||
function MolStar({ model }: { model: MolStarWrapper }) {
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
await model.init();
|
||||
setInitialized(true);
|
||||
}
|
||||
init();
|
||||
}, [model]);
|
||||
|
||||
if (!initialized) return <>Loading</>;
|
||||
return <div style={{ ..., position: 'relative' }}>
|
||||
<Plugin plugin={model.plugin} />
|
||||
</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## ``PluginContext`` without built-in React UI
|
||||
|
||||
- The [``PluginContext``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/context.ts) can be instantiated without using the default React UI.
|
||||
|
||||
```HTML
|
||||
<div id='molstar-parent' style='position: absolute; top: 0; left: 0; right: 0; bottom: 0'>
|
||||
<canvas id='molstar-canvas' style='position: absolute; top: 0; left: 0; right: 0; bottom: 0'></canvas>
|
||||
</div>
|
||||
```
|
||||
|
||||
```ts
|
||||
import { DefaultPluginSpec, PluginSpec } from 'molstar/lib/mol-plugin/spec';
|
||||
import { PluginContext } from 'molstar/lib/mol-plugin/context';
|
||||
import { PluginConfig } from 'molstar/lib/mol-plugin/config';
|
||||
|
||||
const MySpec: PluginSpec = {
|
||||
...DefaultPluginSpec(),
|
||||
config: [
|
||||
[PluginConfig.VolumeStreaming.Enabled, false]
|
||||
]
|
||||
}
|
||||
|
||||
async function init() {
|
||||
const plugin = new PluginContext(MySpec);
|
||||
await plugin.init();
|
||||
|
||||
const canvas = <HTMLCanvasElement> document.getElementById('molstar-canvas');
|
||||
const parent = <HTMLDivElement> document.getElementById('molstar-parent');
|
||||
|
||||
if (!(await plugin.initViewer(canvas, parent))) {
|
||||
console.error('Failed to init Mol*');
|
||||
return;
|
||||
}
|
||||
|
||||
// Example url:"https://files.rcsb.org/download/3j7z.pdb"
|
||||
// Example url:"https://files.rcsb.org/download/5AFI.cif"
|
||||
const data = await plugin.builders.data.download({ url: '...' }, { state: { isGhost: true } });
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(data, format); //format is 'mmcif' or 'pdb' etc.
|
||||
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## ``Canvas3D`` without built-in state management
|
||||
|
||||
- The ``PluginContext`` object from the above examples can be completely omitted.
|
||||
- See [Browser Tests](https://github.com/molstar/molstar/tree/master/src/tests/browser) for example usage.
|
||||
|
||||
```ts
|
||||
const canvas = document.getElementById('canvas'); // parent <canvas> element
|
||||
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
|
||||
canvas3d.animate();
|
||||
// use the canvas3d object here
|
||||
```
|
||||
BIN
docs/docs/plugin/lib-example.png
Normal file
|
After Width: | Height: | Size: 255 KiB |
94
docs/docs/plugin/managers/markdown-extensions.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Markdown Extension Manager
|
||||
|
||||
The `markdownExtensions` manager in `PluginContext.manager` allows customizing
|
||||
the `Markdown` React component to enable executing commands and rendering custom content.
|
||||
|
||||
The main use case of this is enriching [MolViewSpec](`https://molstar.org/mol-view-spec`) support.
|
||||
|
||||
## API
|
||||
|
||||
- `PluginContext.manager.markdownExtensions.register*` functions can be used to register extensions and state/data resolvers to make the the manager work with plugin extension
|
||||
- `PluginContext.manager.markdownExtensions.remove*` can be used to dynamically remove the above
|
||||
|
||||
## Commands
|
||||
|
||||
Extends Markdown Hyperlink syntax to support expressions of the form `[title](!c1=v1&c2=v2&...)` into an executable command. The command can be executed either on click, mouse enter, or mouse leave.
|
||||
|
||||
### Built-in Commands
|
||||
|
||||
- `center-camera` - Centers the camera
|
||||
- `apply-snapshot=key` - Loads snapshots with the provided key
|
||||
- `focus-refs=ref1,ref2,...` - On click, focuses nodes with the provided refs
|
||||
- `highlight-refs=ref1,ref2,...` - On mouse over, highlights the provided refs
|
||||
|
||||
## Custom Content
|
||||
|
||||
Extends Markdown Image syntax to support expressions of the form `` to render custom elements instead.
|
||||
|
||||
### Built-in Custom Content
|
||||
- `color-swatch=color` - Renders a box with the provided color
|
||||
- Color palettes:
|
||||
- `color-palette-name=name` - Renders a gradient with the provivided named color palette (see `mol-util/color/lists.ts` for supported color schemes)
|
||||
- `color-palette-colors=color1,color2` - Renders a gradient with the provided colors
|
||||
- `color-palette-width=CCS-value` - Specifies the width of the element, defaults to `150px`
|
||||
- `color-palette-height=CCS-value` - Specified the height of the element, defaults to `0.5em`
|
||||
- `color-palette-discrete` - Renders discrete color list instead of interpolating
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
```markdown
|
||||
### Highlight/Focus:
|
||||
-  [polymer](!highlight-refs=polymer&focus-refs=polymer)
|
||||
-  [ligand](!highlight-refs=ligand&focus-refs=ligand)
|
||||
- [both](!highlight-refs=polymer,ligand&focus-refs=polymer,ligand)
|
||||
|
||||
### Color Palettes
|
||||
|name|visual|
|
||||
|---:|---|
|
||||
|viridis||
|
||||
|rainbow (discrete)||
|
||||
|custom|)|
|
||||
|
||||
### Camera controls
|
||||
- [center](!center-camera)
|
||||
|
||||
### Image embedded in MVSX file
|
||||

|
||||
```
|
||||
|
||||
This works with the MolViewSpec state built by:
|
||||
|
||||
```py
|
||||
import molviewspec as mvs
|
||||
|
||||
builder = mvs.create_builder()
|
||||
|
||||
assets = {
|
||||
"1cbs.cif": "https://files.wwpdb.org/download/1cbs.cif",
|
||||
"logo.png": "https://molstar.org/img/molstar-logo.png",
|
||||
}
|
||||
|
||||
model = (
|
||||
builder.download(url="1cbs.cif")
|
||||
.parse(format="mmcif")
|
||||
.model_structure()
|
||||
)
|
||||
(
|
||||
model.component(selector="polymer")
|
||||
.representation(ref="polymer")
|
||||
.color(color="blue")
|
||||
)
|
||||
(
|
||||
model.component(selector="ligand")
|
||||
.representation(ref="ligand")
|
||||
.color(color="red")
|
||||
)
|
||||
|
||||
mvsx = mvs.MVSX(
|
||||
data=builder.get_state(
|
||||
description="""...""" # inline the code above
|
||||
),
|
||||
assets=assets
|
||||
)
|
||||
```
|
||||
148
docs/docs/plugin/selections.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# Selections
|
||||
|
||||
|
||||
Assuming you have a model already loaded into the plugin (see [Creating Plugin Instance](./instance.md)), these are some of the following method you can select structural data.
|
||||
|
||||
### Selecting directly from the `hierarchy` manager
|
||||
|
||||
One can select a subcomponent's data directly from the plugin manager.
|
||||
|
||||
```typescript
|
||||
import { Structure } from '../mol-model/structure';
|
||||
|
||||
const ligandData = plugin.managers.structure.hierarchy.selection.structures[0]?.components[0]?.cell.obj?.data;
|
||||
const ligandLoci = Structure.toStructureElementLoci(ligandData as any);
|
||||
|
||||
plugin.managers.camera.focusLoci(ligandLoci);
|
||||
plugin.managers.interactivity.lociSelects.select({ loci: ligandLoci });
|
||||
```
|
||||
|
||||
## Selection callbacks
|
||||
If you want to subscribe to selection events (e.g. to change external state in your application based on a user selection), you can use: `plugin.behaviors.interaction.click.subscribe`
|
||||
|
||||
Here's an example of passing in a React "set" function to update selected residue positions.
|
||||
```typescript
|
||||
import {
|
||||
Structure,
|
||||
StructureProperties,
|
||||
} from "molstar/lib/mol-model/structure"
|
||||
// setSelected is assumed to be a "set" function returned by useState
|
||||
// (selected: any[]) => void
|
||||
plugin.behaviors.interaction.click.subscribe(
|
||||
(event: InteractivityManager.ClickEvent) => {
|
||||
const selections = Array.from(
|
||||
plugin.managers.structure.selection.entries.values()
|
||||
);
|
||||
// This bit can be customized to record any piece information you want
|
||||
const localSelected: any[] = [];
|
||||
for (const { structure } of selections) {
|
||||
if (!structure) continue;
|
||||
Structure.eachAtomicHierarchyElement(structure, {
|
||||
residue: (loc) => {
|
||||
const position = StructureProperties.residue.label_seq_id(loc);
|
||||
localSelected.push({ position });
|
||||
},
|
||||
});
|
||||
}
|
||||
setSelected(localSelected);
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### `Molscript` language
|
||||
|
||||
Molscript is a language for addressing crystallographic structures and is a part of the Mol* library found at `https://github.com/molstar/molstar/tree/master/src/mol-script`. It can be used against the Molstar plugin as a query language and transpiled against multiple external molecular visualization libraries(see [here](https://github.com/molstar/molstar/tree/master/src/mol-script/transpilers)).
|
||||
|
||||
### Querying a structure for a specific chain and residue range (select residues with 12<res_id<200 of chain with auth_asym_id==A) :
|
||||
|
||||
```typescript
|
||||
import { compileIdListSelection } from 'molstar/lib/mol-script/util/id-list'
|
||||
|
||||
const query = compileIdListSelection('A 12-200', 'auth');
|
||||
window.molstar?.managers.structure.selection.fromCompiledQuery('add',query);
|
||||
```
|
||||
|
||||
## Selection Queries
|
||||
|
||||
Another way to create a selection is via a `SelectionQuery` object. This is a more programmatic way to create a selection. The following example shows how to select a chain and a residue range using a `SelectionQuery` object.
|
||||
This relies on the concept of `Expression` which is basically a intermediate representation between a Molscript statement and a selection query.
|
||||
|
||||
### Select residues 10-15 of chains A and F in a structure using a `SelectionQuery` object:
|
||||
|
||||
```typescript
|
||||
import { MolScriptBuilder as MS, MolScriptBuilder } from 'molstar/lib/mol-script/language/builder';
|
||||
import { Expression } from 'molstar/lib/mol-script/language/expression';
|
||||
import { StructureSelectionQuery } from 'molstar/lib/mol-plugin-state/helpers/structure-selection-query'
|
||||
|
||||
|
||||
export function select_multiple() {
|
||||
|
||||
const args = [['A', 10, 15], ['F', 10, 15]]
|
||||
const groups: Expression[] = [];
|
||||
for (var chain of args) {
|
||||
groups.push(MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MolScriptBuilder.struct.atomProperty.macromolecular.auth_asym_id(), chain[0]]),
|
||||
'residue-test': MS.core.rel.inRange([MolScriptBuilder.struct.atomProperty.macromolecular.label_seq_id(), chain[1], chain[2]])
|
||||
}));
|
||||
}
|
||||
var sq = StructureSelectionQuery('residue_range_10_15_in_A_and_F', MS.struct.combinator.merge(groups))
|
||||
mstar.managers.structure.selection.fromSelectionQuery('set', sq)
|
||||
}
|
||||
```
|
||||
|
||||
Complex queries can be constructed by combining primitive queries at the level of [`chain-test`, `residue-test`, `entity-test`, etc] (https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-script/language/symbol-table/structure-query.ts#L88C4-L94C112) by combining them via logical connectives provided in the `MolscriptBuilder.core.rel` as above.
|
||||
|
||||
Inspect these examples to get a better feeling for this syntax: `https://github.com/molstar/molstar/blob/6edbae80db340134341631f669eec86543a0f1a8/src/mol-plugin-state/helpers/structure-selection-query.ts#L88-L580`
|
||||
|
||||
|
||||
Furthermore, a query made this way can be converted to a `Loci` object which is important in many parts of the libary:
|
||||
```typescript
|
||||
// Select residue 124 of chain A and convert to Loci
|
||||
const Q = MolScriptBuilder;
|
||||
var sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
|
||||
'chain-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.auth_asym_id(), A]),
|
||||
'residue-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), 124]),
|
||||
}), objdata)
|
||||
|
||||
let loci = StructureSelection.toLociWithSourceUnits(sel);
|
||||
```
|
||||
|
||||
## Query Functions
|
||||
|
||||
Instead of building expressions, query functions can be created directly, e.g.:
|
||||
|
||||
```ts
|
||||
import { atoms } from 'mol-model/structure/query/queries/generators';
|
||||
|
||||
const query = atoms({
|
||||
residueTest: ctx => {
|
||||
const seqId = StructureProperties.residue.label_seq_id(ctx.element);
|
||||
return seqId > 10 && seqId < 25;
|
||||
},
|
||||
});
|
||||
|
||||
const selection = query(new QueryContext(structure));
|
||||
// ...
|
||||
```
|
||||
|
||||
## Selection Schema
|
||||
|
||||
For simple selections, the `StructureElement.Schema` can be used to reference elements within a protein structure using mmCIF `atom_site` field names, e.g.:
|
||||
|
||||
```ts
|
||||
const ala121: StructureElement.Schema = { label_asym_id: 'A', label_seq_id: 121 };
|
||||
const residues: StructureElement.Schema = {
|
||||
items: {
|
||||
auth_asym_id: ['A', 'B'],
|
||||
auth_seq_id: [10, 11],
|
||||
}
|
||||
};
|
||||
|
||||
const loci = StructureElement.Loci.fromSchema(structure, residues);
|
||||
```
|
||||
|
||||
Usually, a code editor such as VS Code will auto-suggest all the available field names.
|
||||
|
||||
## Helper Functions
|
||||
|
||||
Given an `Expression`, `QueryFn`, or `StructureElement.Schema` it is possible to use `fromExpression/Query/Schema` functions on `StructureElement.Loci` and `StructureElement.Bundle`.
|
||||
45
docs/docs/plugin/transforms/custom-conformation.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Assign custom conformation to a Model
|
||||
|
||||
This document shows how to update model conformation dynamically using the `ModelWithCoordinates` transforms. If this does not work well with your particular use case, it is suggested to write a custom version of `ModelWithCoordinates` with similar usage as outlined in this document.
|
||||
|
||||
```ts
|
||||
async function animateFirstXCoordinateExample(plugin: PluginContext, url: string, format: BuiltInTrajectoryFormat) {
|
||||
// Load data
|
||||
const _data = await plugin.builders.data.download({ url });
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(_data, format);
|
||||
const hierarchy = await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
|
||||
if (!hierarchy) return;
|
||||
|
||||
// Insert ModelWithCoordinates cell to be updated in the loop bellow
|
||||
const coordinatesNode = await plugin.build().to(hierarchy!.model).insert(ModelWithCoordinates).commit();
|
||||
|
||||
const x0 = hierarchy!.model.data!.atomicConformation.x[0];
|
||||
let xOffset = 0;
|
||||
async function animateFirstXCoord() {
|
||||
// Normally, the whole conformation would come from an API/library call, but here we fake it:
|
||||
const { x, y, z } = hierarchy!.model.data!.atomicConformation;
|
||||
const nextX = [...(x as number[])];
|
||||
nextX[0] = x0 + xOffset;
|
||||
xOffset += 0.05;
|
||||
if (xOffset > 1) xOffset = 0;
|
||||
|
||||
// Construct new coodinate frame from the data and commit the update.
|
||||
// Rest of the state tree will reconcile automatically.
|
||||
await plugin.build().to(coordinatesNode).update({
|
||||
atomicCoordinateFrame: {
|
||||
elementCount: x.length,
|
||||
time: { value: 0, unit: 'step' },
|
||||
xyzOrdering: { isIdentity: true },
|
||||
x: nextX,
|
||||
y,
|
||||
z,
|
||||
}
|
||||
}).commit();
|
||||
|
||||
requestAnimationFrame(animateFirstXCoord);
|
||||
}
|
||||
animateFirstXCoord();
|
||||
}
|
||||
|
||||
// animateFirstXCoordinateExample('https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/CID/2244/record/SDF/?record_type=3d', 'sdf');
|
||||
```
|
||||
72
docs/docs/plugin/transforms/custom-trajectory.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Load Trajectory from a Custom Format
|
||||
|
||||
This section shows a high level example for loading trajectory from custom data in specialized plugin instances. A more complete solution is available for example in form of the [G3D format extension](https://github.com/molstar/molstar/tree/master/src/extensions/g3d).
|
||||
|
||||
## Defining and Using a Custom Transformer
|
||||
|
||||
```ts
|
||||
import { StateTransformer } from 'molstar/lib/mol-state';
|
||||
|
||||
const CreateTransformer = StateTransformer.builderFactory('custom-namespace');
|
||||
|
||||
export interface CustomTrajectoryData {
|
||||
// ...
|
||||
}
|
||||
|
||||
export const TrajectoryFromCustomData = CreateTransformer({
|
||||
name: 'trajectory-from-custom-data',
|
||||
display: 'Trajectory',
|
||||
from: PluginStateObject.Root,
|
||||
to: PluginStateObject.Molecule.Trajectory,
|
||||
params: {
|
||||
data: PD.Value<CustomTrajectoryData>(void 0 as any, { isHidden: true }),
|
||||
},
|
||||
})({
|
||||
apply({ params }) {
|
||||
return Task.create('Trajectory', async (ctx) => {
|
||||
const models = await customParse(params.data, ctx);
|
||||
return new PluginStateObject.Molecule.Trajectory(models, {
|
||||
label: 'Trajectory',
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The ``customParse`` function can usually be implemented
|
||||
by modifying/extending an [existing parser already available in Mol*](https://github.com/molstar/molstar/tree/master/src/mol-model-formats/structure).
|
||||
|
||||
To use the transformer:
|
||||
|
||||
```ts
|
||||
const data: CustomTrajectoryData = await (await fetch(url)).json();
|
||||
const trajectory = await plugin.build().toRoot().apply(TrajectoryFromCustomData, { data }).commit();
|
||||
// Create the representation
|
||||
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
|
||||
```
|
||||
|
||||
## Using Mol* to Download the Data
|
||||
|
||||
```ts
|
||||
export const TrajectoryFromCustomData = CreateTransformer({
|
||||
name: 'trajectory-from-custom-data',
|
||||
display: 'Trajectory',
|
||||
from: PluginStateObject.Data.String, // or PluginStateObject.Data.Binary
|
||||
to: PluginStateObject.Molecule.Trajectory,
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Trajectory', async (ctx) => {
|
||||
const models = await customParse(a.data, ctx);
|
||||
return new PluginStateObject.Molecule.Trajectory(models, {
|
||||
label: 'Trajectory',
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
//////////////
|
||||
|
||||
const data = await plugin.builders.data.download({ url, isBinary });
|
||||
const trajectory = await plugin.build().to(data).apply(TrajectoryFromCustomData, { data }).commit();
|
||||
await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
|
||||
```
|
||||
BIN
docs/docs/plugin/ui-example.png
Normal file
|
After Width: | Height: | Size: 420 KiB |
132
docs/docs/plugin/viewer-state.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Viewer State Management
|
||||
|
||||
## ``Canvas3D`` Properties
|
||||
Properties of the [``Canvas3D``](https://github.com/molstar/molstar/blob/master/src/mol-canvas3d/canvas3d.ts) can be
|
||||
changed using [``PluginCommands``](https://github.com/molstar/molstar/blob/master/src/mol-plugin/commands.ts).
|
||||
|
||||
|
||||
### Change background, highlight, or select color
|
||||
```ts
|
||||
import { ColorNames } from 'molstar/lib/mol-util/color/names';
|
||||
import { PluginCommands } from 'molstar/lib/mol-plugin/commands';
|
||||
|
||||
const renderer = plugin.canvas3d!.props.renderer;
|
||||
PluginCommands.Canvas3D.SetSettings(plugin, { settings: { renderer: { ...renderer, backgroundColor: ColorNames.red /* or: 0xff0000 as Color */ } } });
|
||||
```
|
||||
Similarly, `highlightColor` and `selectColor` can be updated.
|
||||
|
||||
|
||||
## Interactivity
|
||||
|
||||
Interactivity in Mol* is based on the concept of ``Loci``. A ``Loci`` usually references a collection of objects and can be created by a [``Selection``](selections.md). For example, the
|
||||
``Loci`` captures all atoms in the chain with label_asym_id B of a protein:
|
||||
```ts
|
||||
import { Script } from 'molstar/lib/mol-script/script';
|
||||
import { StructureSelection } from 'molstar/lib/mol-model/structure/query';
|
||||
|
||||
const data = plugin.managers.structure.hierarchy.current.structures[0]?.cell.obj?.data;
|
||||
if (!data) return;
|
||||
|
||||
const selection = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
|
||||
'chain-test': Q.core.rel.eq(['B', Q.ammp('label_asym_id')])
|
||||
}), data);
|
||||
const loci = StructureSelection.toLociWithSourceUnits(selection);
|
||||
```
|
||||
A ``Loci`` can be used to trigger custom [``Behaviors``](#behaviors).
|
||||
|
||||
|
||||
### Log message to Mol* console
|
||||
The built-in console in the bottom center of the plugin shows log entries.
|
||||
```ts
|
||||
plugin.log.message('This message will appear in the Mol* console');
|
||||
```
|
||||
Other log levels are: `info`, `warn`, and `error`.
|
||||
|
||||
|
||||
### Show toast message
|
||||
Toast messages will appear in the bottom right of the plugin and will linger for a limited time before disappearing.
|
||||
```ts
|
||||
import { PluginCommands } from 'molstar/lib/mol-plugin/commands';
|
||||
|
||||
PluginCommands.Toast.Show(plugin, {
|
||||
title: 'Custom Message',
|
||||
message: 'A custom toast message that will disappear after 2 seconds.',
|
||||
key: 'toast-custom',
|
||||
timeoutMs: 2000
|
||||
});
|
||||
```
|
||||
|
||||
## Behaviors
|
||||
|
||||
The state of the Mol* plugin is usually governed by dynamic behaviors which can be set up in initial plugin specification or updated during the plugin runtime. This allows for high modularity and customizability of individual plugin instances.
|
||||
|
||||
|
||||
### Highlight ``Loci``
|
||||
Highlighting adds a transient overpaint to a representation that will linger until the mouse enters hovers over another
|
||||
object. Highlights can be applied to a previously defined ``Loci`` by:
|
||||
```ts
|
||||
plugin.managers.interactivity.lociHighlights.highlightOnly({ loci }); // loci: Loci
|
||||
```
|
||||
Reset all highlights by:
|
||||
```ts
|
||||
plugin.managers.interactivity.clearHighlights();
|
||||
```
|
||||
|
||||
|
||||
### Select ``Loci``
|
||||
|
||||
Selected elements will appear with distinct visuals and, if applicable, the corresponding sequence positions will be
|
||||
shown in the Sequence Viewer panel. Selections persist until removed, for example by clicking the background. A ``Loci``
|
||||
is selected by:
|
||||
```ts
|
||||
plugin.managers.interactivity.lociSelects.select({ loci }); // loci: Loci
|
||||
```
|
||||
|
||||
Deselect a specific ``Loci`` by:
|
||||
```ts
|
||||
plugin.managers.interactivity.lociSelects.deselect({ loci }); // loci: Loci
|
||||
```
|
||||
To deselect everything:
|
||||
```ts
|
||||
plugin.managers.interactivity.lociSelects.deselectAll();
|
||||
```
|
||||
|
||||
|
||||
### Focus ``Loci``
|
||||
The focus representation shows a ``Loci`` in ball-and-stick representation and, additionally, visualizes non-covalent
|
||||
interactions between atoms of the ``Loci`` as well as interactions with surrounding residues (default: 5 Å).
|
||||
```ts
|
||||
plugin.managers.structure.focus.setFromLoci(loci);
|
||||
```
|
||||
Extend an existing focus representation by:
|
||||
```ts
|
||||
plugin.managers.structure.focus.addFromLoci(loci); // loci: Loci
|
||||
```
|
||||
Reset by:
|
||||
```ts
|
||||
plugin.managers.structure.focus.clear();
|
||||
```
|
||||
|
||||
|
||||
### Zoom ``Loci``
|
||||
A ``Loci`` can also be used to manipulate the camera. Zoom in by:
|
||||
```ts
|
||||
plugin.managers.camera.focusLoci(loci); // loci: Loci
|
||||
```
|
||||
|
||||
Restore the default camera position by:
|
||||
```ts
|
||||
plugin.managers.camera.reset();
|
||||
```
|
||||
|
||||
### Turn off view resetting on new representations
|
||||
A new representation via something like
|
||||
```ts
|
||||
.apply(StateTransforms.Representation.VolumeRepresentation3D, ...)
|
||||
```
|
||||
can reset the view to make the whole representation visible.
|
||||
When one wants to keep the view the same instead of having the rep reset the view,
|
||||
keep the view constant by:
|
||||
```ts
|
||||
plugin.canvas3d?.setProps({ camera: { manualReset: true } });
|
||||
```
|
||||
64
docs/mkdocs.yml
Normal file
@@ -0,0 +1,64 @@
|
||||
site_name: Mol* Developer Documentation
|
||||
theme:
|
||||
name: material
|
||||
|
||||
# 404 page
|
||||
static_templates:
|
||||
- 404.html
|
||||
|
||||
# Necessary for search to work properly
|
||||
include_search_page: false
|
||||
search_index_only: true
|
||||
|
||||
# Default values, taken from mkdocs_theme.yml
|
||||
language: en
|
||||
font:
|
||||
text: Roboto
|
||||
code: Roboto Mono
|
||||
favicon: assets/favicon.png
|
||||
icon:
|
||||
logo: logo
|
||||
markdown_extensions:
|
||||
- pymdownx.highlight
|
||||
- pymdownx.superfences
|
||||
- pymdownx.arithmatex:
|
||||
generic: true
|
||||
# Scripts for rendering Latex equations (in addition to pymdownx.arithmatex):
|
||||
extra_javascript:
|
||||
- https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
|
||||
nav:
|
||||
- 'index.md'
|
||||
- Plugin:
|
||||
- Creating Instance: 'plugin/instance.md'
|
||||
- Examples: plugin/examples.md
|
||||
- Custom Library: 'plugin/custom-library.md'
|
||||
- Selections: 'plugin/selections.md'
|
||||
- Viewer State: 'plugin/viewer-state.md'
|
||||
- Data State: 'plugin/data-state.md'
|
||||
- File Formats: 'plugin/file-formats.md'
|
||||
- CIF Schemas: 'plugin/cif-schemas.md'
|
||||
- Managers:
|
||||
- Markdown Extensions: 'plugin/managers/markdown-extensions.md'
|
||||
- State Transforms:
|
||||
- Custom Trajectory: 'plugin/transforms/custom-trajectory.md'
|
||||
- Custom Conformation: 'plugin/transforms/custom-conformation.md'
|
||||
- Data Access Tools:
|
||||
- 'data-access-tools/model-server.md'
|
||||
- Volume Server:
|
||||
- Overview: 'data-access-tools/volume-server/index.md'
|
||||
- Examples: 'data-access-tools/volume-server/examples.md'
|
||||
- How it Works: 'data-access-tools/volume-server/how-it-works.md'
|
||||
- Data Format: 'data-access-tools/volume-server/response-data-format.md'
|
||||
- 'data-access-tools/plugin-state-server.md'
|
||||
- 'data-access-tools/convert-to-bcif.md'
|
||||
- 'data-access-tools/create-ccd-table.md'
|
||||
- 'data-access-tools/extract-ccd-ions.md'
|
||||
- Extensions:
|
||||
- MolViewSpec: 'extensions/mvs/index.md'
|
||||
- wwPDB StructConn: 'extensions/struct-conn.md'
|
||||
- Tunnels: 'extensions/tunnels.md'
|
||||
- Interactions: 'extensions/interactions.md'
|
||||
- Misc:
|
||||
- Interesting PDB entries: misc/interesting-pdb-entries.md
|
||||
- Exporting component data: misc/exporting-components.md
|
||||
repo_url: https://github.com/molstar/docs
|
||||
@@ -1,69 +0,0 @@
|
||||
Model Server
|
||||
============
|
||||
|
||||
Model Server is a tool for preprocessing and querying macromolecular structure data.
|
||||
|
||||
Installing and Running
|
||||
=====================
|
||||
|
||||
Requires nodejs 8+.
|
||||
|
||||
## From GitHub
|
||||
|
||||
```
|
||||
git clone https://github.com/molstar/molstar
|
||||
npm install
|
||||
```
|
||||
|
||||
Afterwards, build the project source:
|
||||
|
||||
```
|
||||
npm run build-tsc
|
||||
```
|
||||
|
||||
and run the server by
|
||||
|
||||
```
|
||||
node lib/commonjs/servers/model/server/server
|
||||
```
|
||||
|
||||
## From NPM
|
||||
|
||||
```
|
||||
npm install --production molstar
|
||||
./model-server
|
||||
```
|
||||
|
||||
(or ``node node_modules\.bin\model-server`` in Windows).
|
||||
|
||||
The NPM package contains all the tools mentioned here as "binaries":
|
||||
|
||||
- ``model-server``
|
||||
- ``model-server-query``
|
||||
- ``model-server-preprocess``
|
||||
|
||||
|
||||
### Production use
|
||||
|
||||
In production it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
|
||||
|
||||
### Memory issues
|
||||
|
||||
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
|
||||
|
||||
## Preprocessor
|
||||
|
||||
The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. ``node lib/commonjs/servers/model/preprocess`` or ``model-server-preprocess`` binary from the NPM package.
|
||||
|
||||
|
||||
## Local Mode
|
||||
|
||||
The server can be run in local/file based mode using ``node lib/commonjs/servers/model/query`` (``model-server-query`` binary from the NPM package).
|
||||
|
||||
Custom Properties
|
||||
=================
|
||||
|
||||
This feature is still in development.
|
||||
|
||||
It is possible to provide property descriptors that transform data to internal representation and define how it should be exported into one or mode CIF categories. Examples of this are located in the ``mol-model-props`` module and are linked to the server in the config and ``servers/model/properties``.
|
||||
@@ -1,738 +0,0 @@
|
||||
# Mol* Plugin State Transformer Reference
|
||||
|
||||
* [build-in.root](#build-in-root)
|
||||
* [ms-plugin.download](#ms-plugin-download)
|
||||
* [ms-plugin.read-file](#ms-plugin-read-file)
|
||||
* [ms-plugin.parse-cif](#ms-plugin-parse-cif)
|
||||
* [ms-plugin.parse-ccp4](#ms-plugin-parse-ccp4)
|
||||
* [ms-plugin.parse-dsn6](#ms-plugin-parse-dsn6)
|
||||
* [ms-plugin.trajectory-from-mmcif](#ms-plugin-trajectory-from-mmcif)
|
||||
* [ms-plugin.trajectory-from-pdb](#ms-plugin-trajectory-from-pdb)
|
||||
* [ms-plugin.model-from-trajectory](#ms-plugin-model-from-trajectory)
|
||||
* [ms-plugin.structure-from-model](#ms-plugin-structure-from-model)
|
||||
* [ms-plugin.structure-assembly-from-model](#ms-plugin-structure-assembly-from-model)
|
||||
* [ms-plugin.structure-symmetry-from-model](#ms-plugin-structure-symmetry-from-model)
|
||||
* [ms-plugin.structure-selection](#ms-plugin-structure-selection)
|
||||
* [ms-plugin.structure-complex-element](#ms-plugin-structure-complex-element)
|
||||
* [ms-plugin.custom-model-properties](#ms-plugin-custom-model-properties)
|
||||
* [ms-plugin.volume-from-ccp4](#ms-plugin-volume-from-ccp4)
|
||||
* [ms-plugin.volume-from-dsn6](#ms-plugin-volume-from-dsn6)
|
||||
* [ms-plugin.representation-highlight-loci](#ms-plugin-representation-highlight-loci)
|
||||
* [ms-plugin.representation-select-loci](#ms-plugin-representation-select-loci)
|
||||
* [ms-plugin.default-loci-label-provider](#ms-plugin-default-loci-label-provider)
|
||||
* [ms-plugin.structure-representation-3d](#ms-plugin-structure-representation-3d)
|
||||
* [ms-plugin.explode-structure-representation-3d](#ms-plugin-explode-structure-representation-3d)
|
||||
* [ms-plugin.volume-representation-3d](#ms-plugin-volume-representation-3d)
|
||||
* [ms-plugin.focus-loci-on-select](#ms-plugin-focus-loci-on-select)
|
||||
* [ms-plugin.pdbe-structure-quality-report-prop](#ms-plugin-pdbe-structure-quality-report-prop)
|
||||
* [ms-plugin.rcsb-assembly-symmetry-prop](#ms-plugin-rcsb-assembly-symmetry-prop)
|
||||
* [ms-plugin.structure-animation](#ms-plugin-structure-animation)
|
||||
* [ms-plugin.scene-labels](#ms-plugin-scene-labels)
|
||||
|
||||
----------------------------
|
||||
## <a name="build-in-root"></a>build-in.root :: () -> ()
|
||||
*For internal use.*
|
||||
|
||||
----------------------------
|
||||
## <a name="ms-plugin-download"></a>ms-plugin.download :: Root -> String | Binary
|
||||
*Download string or binary data from the specified URL*
|
||||
|
||||
### Parameters
|
||||
- **url**: String *(Resource URL. Must be the same domain or support CORS.)*
|
||||
- **label**?: String
|
||||
- **isBinary**?: true/false *(If true, download data as binary (string otherwise))*
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"url": "https://www.ebi.ac.uk/pdbe/static/entry/1cbs_updated.cif"
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-read-file"></a>ms-plugin.read-file :: Root -> String | Binary
|
||||
*Read string or binary data from the specified file*
|
||||
|
||||
### Parameters
|
||||
- **file**: JavaScript File Handle
|
||||
- **label**?: String
|
||||
- **isBinary**?: true/false *(If true, open file as as binary (string otherwise))*
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-parse-cif"></a>ms-plugin.parse-cif :: String | Binary -> Cif
|
||||
*Parse CIF from String or Binary data*
|
||||
|
||||
----------------------------
|
||||
## <a name="ms-plugin-parse-ccp4"></a>ms-plugin.parse-ccp4 :: Binary -> Ccp4
|
||||
*Parse CCP4/MRC/MAP from Binary data*
|
||||
|
||||
----------------------------
|
||||
## <a name="ms-plugin-parse-dsn6"></a>ms-plugin.parse-dsn6 :: Binary -> Dsn6
|
||||
*Parse CCP4/BRIX from Binary data*
|
||||
|
||||
----------------------------
|
||||
## <a name="ms-plugin-trajectory-from-mmcif"></a>ms-plugin.trajectory-from-mmcif :: Cif -> Trajectory
|
||||
*Identify and create all separate models in the specified CIF data block*
|
||||
|
||||
### Parameters
|
||||
- **blockHeader**?: String *(Header of the block to parse. If none is specifed, the 1st data block in the file is used.)*
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-trajectory-from-pdb"></a>ms-plugin.trajectory-from-pdb :: String -> Trajectory
|
||||
|
||||
----------------------------
|
||||
## <a name="ms-plugin-model-from-trajectory"></a>ms-plugin.model-from-trajectory :: Trajectory -> Model
|
||||
*Create a molecular structure from the specified model.*
|
||||
|
||||
### Parameters
|
||||
- **modelIndex**: Numeric value *(Zero-based index of the model)*
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"modelIndex": 0
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-structure-from-model"></a>ms-plugin.structure-from-model :: Model -> Structure
|
||||
*Create a molecular structure from the specified model.*
|
||||
|
||||
----------------------------
|
||||
## <a name="ms-plugin-structure-assembly-from-model"></a>ms-plugin.structure-assembly-from-model :: Model -> Structure
|
||||
*Create a molecular structure assembly.*
|
||||
|
||||
### Parameters
|
||||
- **id**?: String *(Assembly Id. Value 'deposited' can be used to specify deposited asymmetric unit.)*
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-structure-symmetry-from-model"></a>ms-plugin.structure-symmetry-from-model :: Model -> Structure
|
||||
*Create a molecular structure symmetry.*
|
||||
|
||||
### Parameters
|
||||
- **ijkMin**: 3D vector [x, y, z]
|
||||
- **ijkMax**: 3D vector [x, y, z]
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"ijkMin": [
|
||||
-1,
|
||||
-1,
|
||||
-1
|
||||
],
|
||||
"ijkMax": [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
]
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-structure-selection"></a>ms-plugin.structure-selection :: Structure -> Structure
|
||||
*Create a molecular structure from the specified query expression.*
|
||||
|
||||
### Parameters
|
||||
- **query**: Value
|
||||
- **label**?: String
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-structure-complex-element"></a>ms-plugin.structure-complex-element :: Structure -> Structure
|
||||
*Create a molecular structure from the specified model.*
|
||||
|
||||
### Parameters
|
||||
- **type**: One of 'atomic-sequence', 'water', 'atomic-het', 'spheres'
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"type": "atomic-sequence"
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-custom-model-properties"></a>ms-plugin.custom-model-properties :: Model -> Model
|
||||
|
||||
### Parameters
|
||||
- **properties**: Array of *(A list of property descriptor ids.)*
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"properties": []
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-volume-from-ccp4"></a>ms-plugin.volume-from-ccp4 :: Ccp4 -> Data
|
||||
*Create Volume from CCP4/MRC/MAP data*
|
||||
|
||||
### Parameters
|
||||
- **voxelSize**: 3D vector [x, y, z]
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"voxelSize": [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
]
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-volume-from-dsn6"></a>ms-plugin.volume-from-dsn6 :: Dsn6 -> Data
|
||||
*Create Volume from DSN6/BRIX data*
|
||||
|
||||
### Parameters
|
||||
- **voxelSize**: 3D vector [x, y, z]
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"voxelSize": [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
]
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-representation-highlight-loci"></a>ms-plugin.representation-highlight-loci :: Root -> Behavior
|
||||
|
||||
----------------------------
|
||||
## <a name="ms-plugin-representation-select-loci"></a>ms-plugin.representation-select-loci :: Root -> Behavior
|
||||
|
||||
----------------------------
|
||||
## <a name="ms-plugin-default-loci-label-provider"></a>ms-plugin.default-loci-label-provider :: Root -> Behavior
|
||||
|
||||
----------------------------
|
||||
## <a name="ms-plugin-structure-representation-3d"></a>ms-plugin.structure-representation-3d :: Structure -> Representation3D
|
||||
|
||||
### Parameters
|
||||
- **type**: Object { name: string, params: object } where name+params are:
|
||||
- **cartoon**:
|
||||
Object with:
|
||||
- **alpha**: Numeric value
|
||||
- **useFog**: true/false
|
||||
- **highlightColor**: Color as 0xrrggbb
|
||||
- **selectColor**: Color as 0xrrggbb
|
||||
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
|
||||
- **doubleSided**: true/false
|
||||
- **flipSided**: true/false
|
||||
- **flatShaded**: true/false
|
||||
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
|
||||
- **sizeFactor**: Numeric value
|
||||
- **linearSegments**: Numeric value
|
||||
- **radialSegments**: Numeric value
|
||||
- **aspectRatio**: Numeric value
|
||||
- **arrowFactor**: Numeric value
|
||||
- **visuals**: Array of 'polymer-trace', 'polymer-gap', 'nucleotide-block', 'direction-wedge'
|
||||
|
||||
- **ball-and-stick**:
|
||||
Object with:
|
||||
- **alpha**: Numeric value
|
||||
- **useFog**: true/false
|
||||
- **highlightColor**: Color as 0xrrggbb
|
||||
- **selectColor**: Color as 0xrrggbb
|
||||
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
|
||||
- **doubleSided**: true/false
|
||||
- **flipSided**: true/false
|
||||
- **flatShaded**: true/false
|
||||
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
|
||||
- **sizeFactor**: Numeric value
|
||||
- **detail**: Numeric value
|
||||
- **linkScale**: Numeric value
|
||||
- **linkSpacing**: Numeric value
|
||||
- **radialSegments**: Numeric value
|
||||
- **sizeAspectRatio**: Numeric value
|
||||
- **visuals**: Array of 'element-sphere', 'intra-link', 'inter-link'
|
||||
|
||||
- **carbohydrate**:
|
||||
Object with:
|
||||
- **alpha**: Numeric value
|
||||
- **useFog**: true/false
|
||||
- **highlightColor**: Color as 0xrrggbb
|
||||
- **selectColor**: Color as 0xrrggbb
|
||||
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
|
||||
- **doubleSided**: true/false
|
||||
- **flipSided**: true/false
|
||||
- **flatShaded**: true/false
|
||||
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
|
||||
- **detail**: Numeric value
|
||||
- **sizeFactor**: Numeric value
|
||||
- **linkScale**: Numeric value
|
||||
- **linkSpacing**: Numeric value
|
||||
- **radialSegments**: Numeric value
|
||||
- **linkSizeFactor**: Numeric value
|
||||
- **visuals**: Array of 'carbohydrate-symbol', 'carbohydrate-link', 'carbohydrate-terminal-link'
|
||||
|
||||
- **distance-restraint**:
|
||||
Object with:
|
||||
- **alpha**: Numeric value
|
||||
- **useFog**: true/false
|
||||
- **highlightColor**: Color as 0xrrggbb
|
||||
- **selectColor**: Color as 0xrrggbb
|
||||
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
|
||||
- **doubleSided**: true/false
|
||||
- **flipSided**: true/false
|
||||
- **flatShaded**: true/false
|
||||
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
|
||||
- **linkScale**: Numeric value
|
||||
- **linkSpacing**: Numeric value
|
||||
- **radialSegments**: Numeric value
|
||||
- **sizeFactor**: Numeric value
|
||||
|
||||
- **molecular-surface**:
|
||||
Object with:
|
||||
- **alpha**: Numeric value
|
||||
- **useFog**: true/false
|
||||
- **highlightColor**: Color as 0xrrggbb
|
||||
- **selectColor**: Color as 0xrrggbb
|
||||
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
|
||||
- **doubleSided**: true/false
|
||||
- **flipSided**: true/false
|
||||
- **flatShaded**: true/false
|
||||
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
|
||||
- **resolution**: Numeric value
|
||||
- **radiusOffset**: Numeric value
|
||||
- **smoothness**: Numeric value
|
||||
- **useGpu**: true/false
|
||||
- **ignoreCache**: true/false
|
||||
- **sizeFactor**: Numeric value
|
||||
- **lineSizeAttenuation**: true/false
|
||||
- **visuals**: Array of 'gaussian-surface', 'gaussian-wireframe'
|
||||
|
||||
- **molecular-volume**:
|
||||
Object with:
|
||||
- **alpha**: Numeric value
|
||||
- **useFog**: true/false
|
||||
- **highlightColor**: Color as 0xrrggbb
|
||||
- **selectColor**: Color as 0xrrggbb
|
||||
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
|
||||
- **isoValueNorm**: Numeric value *(Normalized Isolevel Value)*
|
||||
- **renderMode**: One of 'isosurface', 'volume'
|
||||
- **controlPoints**: A list of 2d vectors [xi, yi][]
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
|
||||
- **resolution**: Numeric value
|
||||
- **radiusOffset**: Numeric value
|
||||
- **smoothness**: Numeric value
|
||||
|
||||
- **point**:
|
||||
Object with:
|
||||
- **alpha**: Numeric value
|
||||
- **useFog**: true/false
|
||||
- **highlightColor**: Color as 0xrrggbb
|
||||
- **selectColor**: Color as 0xrrggbb
|
||||
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
|
||||
- **sizeFactor**: Numeric value
|
||||
- **pointSizeAttenuation**: true/false
|
||||
- **pointFilledCircle**: true/false
|
||||
- **pointEdgeBleach**: Numeric value
|
||||
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
|
||||
|
||||
- **spacefill**:
|
||||
Object with:
|
||||
- **alpha**: Numeric value
|
||||
- **useFog**: true/false
|
||||
- **highlightColor**: Color as 0xrrggbb
|
||||
- **selectColor**: Color as 0xrrggbb
|
||||
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
|
||||
- **doubleSided**: true/false
|
||||
- **flipSided**: true/false
|
||||
- **flatShaded**: true/false
|
||||
- **unitKinds**: Array of 'atomic', 'spheres', 'gaussians'
|
||||
- **sizeFactor**: Numeric value
|
||||
- **detail**: Numeric value
|
||||
|
||||
|
||||
- **colorTheme**: Object { name: string, params: object } where name+params are:
|
||||
- **carbohydrate-symbol**:
|
||||
Object with:
|
||||
|
||||
- **chain-id**:
|
||||
Object with:
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **cross-link**:
|
||||
Object with:
|
||||
- **domain**: Interval [min, max]
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **element-index**:
|
||||
Object with:
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **element-symbol**:
|
||||
Object with:
|
||||
|
||||
- **molecule-type**:
|
||||
Object with:
|
||||
|
||||
- **polymer-id**:
|
||||
Object with:
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **polymer-index**:
|
||||
Object with:
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **residue-name**:
|
||||
Object with:
|
||||
|
||||
- **secondary-structure**:
|
||||
Object with:
|
||||
|
||||
- **sequence-id**:
|
||||
Object with:
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **shape-group**:
|
||||
Object with:
|
||||
|
||||
- **unit-index**:
|
||||
Object with:
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **uniform**:
|
||||
Object with:
|
||||
- **value**: Color as 0xrrggbb
|
||||
|
||||
|
||||
- **sizeTheme**: Object { name: string, params: object } where name+params are:
|
||||
- **physical**:
|
||||
Object with:
|
||||
|
||||
- **shape-group**:
|
||||
Object with:
|
||||
|
||||
- **uniform**:
|
||||
Object with:
|
||||
- **value**: Numeric value
|
||||
|
||||
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"type": {
|
||||
"name": "cartoon",
|
||||
"params": {
|
||||
"alpha": 1,
|
||||
"useFog": true,
|
||||
"highlightColor": 16737945,
|
||||
"selectColor": 3407641,
|
||||
"quality": "auto",
|
||||
"doubleSided": false,
|
||||
"flipSided": false,
|
||||
"flatShaded": false,
|
||||
"unitKinds": [
|
||||
"atomic",
|
||||
"spheres"
|
||||
],
|
||||
"sizeFactor": 0.2,
|
||||
"linearSegments": 8,
|
||||
"radialSegments": 16,
|
||||
"aspectRatio": 5,
|
||||
"arrowFactor": 1.5,
|
||||
"visuals": [
|
||||
"polymer-trace"
|
||||
]
|
||||
}
|
||||
},
|
||||
"colorTheme": {
|
||||
"name": "polymer-id",
|
||||
"params": {
|
||||
"list": "RedYellowBlue"
|
||||
}
|
||||
},
|
||||
"sizeTheme": {
|
||||
"name": "uniform",
|
||||
"params": {
|
||||
"value": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-explode-structure-representation-3d"></a>ms-plugin.explode-structure-representation-3d :: Representation3D -> Obj
|
||||
|
||||
### Parameters
|
||||
- **t**: Numeric value
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"t": 0
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-volume-representation-3d"></a>ms-plugin.volume-representation-3d :: Data -> Representation3D
|
||||
|
||||
### Parameters
|
||||
- **type**: Object { name: string, params: object } where name+params are:
|
||||
- **isosurface**:
|
||||
Object with:
|
||||
- **alpha**: Numeric value
|
||||
- **useFog**: true/false
|
||||
- **highlightColor**: Color as 0xrrggbb
|
||||
- **selectColor**: Color as 0xrrggbb
|
||||
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
|
||||
- **doubleSided**: true/false
|
||||
- **flipSided**: true/false
|
||||
- **flatShaded**: true/false
|
||||
- **isoValue**: - **absolute**: Numeric value
|
||||
- **relative**: Numeric value
|
||||
|
||||
- **sizeFactor**: Numeric value
|
||||
- **lineSizeAttenuation**: true/false
|
||||
- **visuals**: Array of 'solid', 'wireframe'
|
||||
|
||||
- **direct-volume**:
|
||||
Object with:
|
||||
- **alpha**: Numeric value
|
||||
- **useFog**: true/false
|
||||
- **highlightColor**: Color as 0xrrggbb
|
||||
- **selectColor**: Color as 0xrrggbb
|
||||
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
|
||||
- **isoValueNorm**: Numeric value *(Normalized Isolevel Value)*
|
||||
- **renderMode**: One of 'isosurface', 'volume'
|
||||
- **controlPoints**: A list of 2d vectors [xi, yi][]
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
|
||||
- **colorTheme**: Object { name: string, params: object } where name+params are:
|
||||
- **carbohydrate-symbol**:
|
||||
Object with:
|
||||
|
||||
- **chain-id**:
|
||||
Object with:
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **cross-link**:
|
||||
Object with:
|
||||
- **domain**: Interval [min, max]
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **element-index**:
|
||||
Object with:
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **element-symbol**:
|
||||
Object with:
|
||||
|
||||
- **molecule-type**:
|
||||
Object with:
|
||||
|
||||
- **polymer-id**:
|
||||
Object with:
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **polymer-index**:
|
||||
Object with:
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **residue-name**:
|
||||
Object with:
|
||||
|
||||
- **secondary-structure**:
|
||||
Object with:
|
||||
|
||||
- **sequence-id**:
|
||||
Object with:
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **shape-group**:
|
||||
Object with:
|
||||
|
||||
- **unit-index**:
|
||||
Object with:
|
||||
- **list**: One of 'OrangeRed', 'PurpleBlue', 'BluePurple', 'Oranges', 'BlueGreen', 'YellowOrangeBrown', 'YellowGreen', 'Reds', 'RedPurple', 'Greens', 'YellowGreenBlue', 'Purples', 'GreenBlue', 'Greys', 'YellowOrangeRed', 'PurpleRed', 'Blues', 'PurpleBlueGreen', 'Spectral', 'RedYellowGreen', 'RedBlue', 'PinkYellowGreen', 'PurpleGreen', 'RedYellowBlue', 'BrownWhiteGreen', 'RedGrey', 'PurpleOrange', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1', 'Magma', 'Inferno', 'Plasma', 'Viridis', 'Cividis', 'Twilight', 'Rainbow', 'RedWhiteBlue'
|
||||
|
||||
- **uniform**:
|
||||
Object with:
|
||||
- **value**: Color as 0xrrggbb
|
||||
|
||||
|
||||
- **sizeTheme**: Object { name: string, params: object } where name+params are:
|
||||
- **physical**:
|
||||
Object with:
|
||||
|
||||
- **shape-group**:
|
||||
Object with:
|
||||
|
||||
- **uniform**:
|
||||
Object with:
|
||||
- **value**: Numeric value
|
||||
|
||||
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"type": {
|
||||
"name": "isosurface",
|
||||
"params": {
|
||||
"alpha": 1,
|
||||
"useFog": true,
|
||||
"highlightColor": 16737945,
|
||||
"selectColor": 3407641,
|
||||
"quality": "auto",
|
||||
"doubleSided": false,
|
||||
"flipSided": false,
|
||||
"flatShaded": false,
|
||||
"isoValue": {
|
||||
"kind": "relative",
|
||||
"stats": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"mean": 0,
|
||||
"sigma": 0
|
||||
},
|
||||
"relativeValue": 2
|
||||
},
|
||||
"sizeFactor": 1,
|
||||
"lineSizeAttenuation": false,
|
||||
"visuals": [
|
||||
"solid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"colorTheme": {
|
||||
"name": "uniform",
|
||||
"params": {
|
||||
"value": 13421772
|
||||
}
|
||||
},
|
||||
"sizeTheme": {
|
||||
"name": "uniform",
|
||||
"params": {
|
||||
"value": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-focus-loci-on-select"></a>ms-plugin.focus-loci-on-select :: Root -> Behavior
|
||||
|
||||
### Parameters
|
||||
- **minRadius**: Numeric value
|
||||
- **extraRadius**: Numeric value *(Value added to the boundning sphere radius of the Loci.)*
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"minRadius": 10,
|
||||
"extraRadius": 4
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-pdbe-structure-quality-report-prop"></a>ms-plugin.pdbe-structure-quality-report-prop :: Root -> Behavior
|
||||
|
||||
### Parameters
|
||||
- **autoAttach**: true/false
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"autoAttach": false
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-rcsb-assembly-symmetry-prop"></a>ms-plugin.rcsb-assembly-symmetry-prop :: Root -> Behavior
|
||||
|
||||
### Parameters
|
||||
- **autoAttach**: true/false
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"autoAttach": false
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-structure-animation"></a>ms-plugin.structure-animation :: Root -> Behavior
|
||||
|
||||
### Parameters
|
||||
- **rotate**: true/false
|
||||
- **rotateValue**: Numeric value
|
||||
- **explode**: true/false
|
||||
- **explodeValue**: Numeric value
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"rotate": false,
|
||||
"rotateValue": 0,
|
||||
"explode": false,
|
||||
"explodeValue": 0
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
## <a name="ms-plugin-scene-labels"></a>ms-plugin.scene-labels :: Root -> Behavior
|
||||
|
||||
### Parameters
|
||||
- **alpha**: Numeric value
|
||||
- **useFog**: true/false
|
||||
- **highlightColor**: Color as 0xrrggbb
|
||||
- **selectColor**: Color as 0xrrggbb
|
||||
- **quality**: One of 'custom', 'auto', 'highest', 'higher', 'high', 'medium', 'low', 'lower', 'lowest'
|
||||
- **fontFamily**: One of 'sans-serif', 'monospace', 'serif', 'cursive'
|
||||
- **fontQuality**: One of '0', '1', '2', '3', '4'
|
||||
- **fontStyle**: One of 'normal', 'italic', 'oblique'
|
||||
- **fontVariant**: One of 'normal', 'small-caps'
|
||||
- **fontWeight**: One of 'normal', 'bold'
|
||||
- **sizeFactor**: Numeric value
|
||||
- **borderWidth**: Numeric value
|
||||
- **borderColor**: Color as 0xrrggbb
|
||||
- **offsetX**: Numeric value
|
||||
- **offsetY**: Numeric value
|
||||
- **offsetZ**: Numeric value
|
||||
- **background**: true/false
|
||||
- **backgroundMargin**: Numeric value
|
||||
- **backgroundColor**: Color as 0xrrggbb
|
||||
- **backgroundOpacity**: Numeric value
|
||||
- **attachment**: One of 'bottom-left', 'bottom-center', 'bottom-right', 'middle-left', 'middle-center', 'middle-right', 'top-left', 'top-center', 'top-right'
|
||||
- **levels**: Array of 'structure', 'polymer', 'ligand'
|
||||
|
||||
### Default Parameters
|
||||
```js
|
||||
{
|
||||
"alpha": 1,
|
||||
"useFog": true,
|
||||
"highlightColor": 16737945,
|
||||
"selectColor": 3407641,
|
||||
"quality": "auto",
|
||||
"fontFamily": "sans-serif",
|
||||
"fontQuality": 3,
|
||||
"fontStyle": "normal",
|
||||
"fontVariant": "normal",
|
||||
"fontWeight": "normal",
|
||||
"sizeFactor": 1,
|
||||
"borderWidth": 0,
|
||||
"borderColor": 8421504,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"offsetZ": 0,
|
||||
"background": true,
|
||||
"backgroundMargin": 0.2,
|
||||
"backgroundColor": 16775930,
|
||||
"backgroundOpacity": 0.9,
|
||||
"attachment": "middle-center",
|
||||
"levels": []
|
||||
}
|
||||
```
|
||||
----------------------------
|
||||
@@ -1,86 +0,0 @@
|
||||
What is VolumeServer
|
||||
=====================
|
||||
|
||||
VolumeServer is a service for accessing subsets of volumetric density data. It automatically downsamples the data depending on the volume of the requested region to reduce the bandwidth requirements and provide near-instant access to even the largest data sets.
|
||||
|
||||
It uses the text based CIF and BinaryCIF formats to deliver the data to the client.
|
||||
|
||||
For quick info about the benefits of using the server, check out the [examples](examples.md).
|
||||
|
||||
Installing and Running
|
||||
=====================
|
||||
|
||||
Requires nodejs 8+.
|
||||
|
||||
## From GitHub
|
||||
|
||||
```
|
||||
git clone https://github.com/molstar/molstar
|
||||
npm install
|
||||
```
|
||||
|
||||
Afterwards, build the project source:
|
||||
|
||||
```
|
||||
npm run build-tsc
|
||||
```
|
||||
|
||||
and run the server by
|
||||
|
||||
```
|
||||
node lib/commonjs/servers/volume/server
|
||||
```
|
||||
|
||||
## From NPM
|
||||
|
||||
```
|
||||
npm install --production molstar
|
||||
./volume-server
|
||||
```
|
||||
|
||||
(or ``node node_modules\.bin\volume-server`` in Windows).
|
||||
|
||||
The NPM package contains all the tools mentioned here as "binaries":
|
||||
|
||||
- ``volume-server``
|
||||
- ``volume-server-pack``
|
||||
- ``volume-server-query``
|
||||
|
||||
|
||||
### Production use
|
||||
|
||||
In production it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
|
||||
|
||||
### Memory issues
|
||||
|
||||
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
|
||||
|
||||
|
||||
## Preparing the Data
|
||||
|
||||
For the server to work, CCP4/MAP (models 0, 1, 2 are supported) input data need to be converted into a custom block format.
|
||||
To achieve this, use the ``pack`` application (``node lib/commonjs/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
|
||||
|
||||
## Local Mode
|
||||
|
||||
The program ``lib/commonjs/servers/volume/pack`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
|
||||
|
||||
## Navigating the Source Code
|
||||
|
||||
The source code is split into 2 mains parts: ``pack`` and ``server``:
|
||||
|
||||
- The ``pack`` part provides the means of converting CCP4 files into the internal block format.
|
||||
- The ``server`` includes
|
||||
- ``query``: the main part of the server that handles a query. ``execute.ts`` is the "entry point".
|
||||
- ``algebra``: linear, "coordinate", and "box" algebra provides the means for calculations necessary to concent a user query into a menaningful response.
|
||||
- API wrapper that handles the requests.
|
||||
|
||||
Consuming the Data
|
||||
==================
|
||||
|
||||
The data can be consumed in any (modern) browser using the [ciftools library](https://github.com/molstar/ciftools) (or any other piece of code that can read text or binary CIF).
|
||||
|
||||
The [Data Format](DataFormat.md) document gives a detailed description of the server response format.
|
||||
|
||||
As a reference/example of the server usage is available in Mol* ``mol-plugin`` module.
|
||||
110
eslint.config.mjs
Normal file
@@ -0,0 +1,110 @@
|
||||
import { defineConfig } from "eslint/config";
|
||||
import globals from "globals";
|
||||
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
|
||||
export default defineConfig([{
|
||||
ignores: [
|
||||
"node_modules/*",
|
||||
"build/*",
|
||||
"deploy/*",
|
||||
"docs/site/*",
|
||||
"lib/*",
|
||||
"eslint.config.mjs",
|
||||
"build.mjs",
|
||||
]
|
||||
},{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
|
||||
ecmaVersion: 2018,
|
||||
sourceType: "module",
|
||||
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
impliedStrict: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
indent: "off",
|
||||
"arrow-parens": ["off", "as-needed"],
|
||||
"brace-style": ["error", "1tbs", {
|
||||
allowSingleLine: true,
|
||||
}],
|
||||
"comma-spacing": "off",
|
||||
"space-infix-ops": "off",
|
||||
"comma-dangle": "off",
|
||||
eqeqeq: ["error", "smart"],
|
||||
"import/order": "off",
|
||||
"no-eval": "warn",
|
||||
"no-extend-native": "warn",
|
||||
"no-new-wrappers": "warn",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-unsafe-finally": "warn",
|
||||
"no-self-compare": "warn",
|
||||
"no-var": "error",
|
||||
"spaced-comment": "error",
|
||||
semi: "warn",
|
||||
"no-restricted-syntax": ["error", {
|
||||
selector: "ExportDefaultDeclaration",
|
||||
message: "Default exports are not allowed",
|
||||
}],
|
||||
"no-throw-literal": "error",
|
||||
"key-spacing": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"array-bracket-spacing": "error",
|
||||
"space-in-parens": "error",
|
||||
"computed-property-spacing": "error",
|
||||
"prefer-const": ["error", {
|
||||
destructuring: "all",
|
||||
ignoreReadBeforeAssign: false,
|
||||
}],
|
||||
"space-before-function-paren": "off",
|
||||
"func-call-spacing": "off",
|
||||
"no-multi-spaces": "error",
|
||||
"block-spacing": "error",
|
||||
"keyword-spacing": "warn",
|
||||
"space-before-blocks": "error",
|
||||
"semi-spacing": "error",
|
||||
"no-constant-binary-expression": "error",
|
||||
},
|
||||
}, {
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
|
||||
plugins: {
|
||||
"@typescript-eslint": typescriptEslint,
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
ecmaVersion: 5,
|
||||
sourceType: "module",
|
||||
|
||||
parserOptions: {
|
||||
project: ["tsconfig.eslint.json"],
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/class-name-casing": "off",
|
||||
"@typescript-eslint/member-delimiter-style": ["off", {
|
||||
multiline: {
|
||||
delimiter: "none",
|
||||
requireLast: true,
|
||||
},
|
||||
|
||||
singleline: {
|
||||
delimiter: "semi",
|
||||
requireLast: false,
|
||||
},
|
||||
}],
|
||||
"@typescript-eslint/prefer-namespace-keyword": "warn",
|
||||
"@typescript-eslint/semi": ["off", null],
|
||||
},
|
||||
}]);
|
||||
75130
examples/7qpd.fw2.cif
Normal file
456
examples/docking/ligands_1.sdf
Normal file
@@ -0,0 +1,456 @@
|
||||
forcefield_3904
|
||||
RDKit 3D
|
||||
|
||||
49 52 0 0 0 0 0 0 0 0999 V2000
|
||||
7.1950 23.7840 6.1780 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
5.7810 23.6440 6.1320 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
5.0380 24.7500 6.4550 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.8270 25.8570 5.6380 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.0460 26.9150 6.0970 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.4760 26.8670 7.3710 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.6720 25.7630 8.2160 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.0720 25.7780 9.4800 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1.8090 25.4470 9.9650 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.8080 26.1730 9.4160 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.4770 25.9480 9.8040 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.5110 26.6810 9.2540 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.0950 27.9270 8.6960 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.1890 28.9860 8.9110 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.6300 30.4110 8.7600 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.7130 31.4650 8.9780 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.4040 31.2910 10.3270 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.9520 29.8780 10.4990 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.8690 28.8240 10.2810 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.7400 24.9710 10.7610 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.8850 24.4940 11.3660 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.4690 23.5570 12.1930 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.1090 23.4080 12.1480 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.3700 24.3040 11.2380 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1.6480 24.4860 10.8910 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.4600 24.7130 7.7330 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
7.5263 23.8168 7.2264 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
7.6649 22.9280 5.6716 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
7.4879 24.7155 5.6716 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
5.2751 25.8961 4.6342 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.8776 27.7913 5.4538 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
2.8616 27.7104 7.7192 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.6420 26.0930 10.2520 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.1673 28.2555 9.1874 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.9189 27.8009 7.6175 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.9466 28.8274 8.1294 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.8310 30.5615 9.5009 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.2424 30.5222 7.7366 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.2524 32.4632 8.9392 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.4689 31.3496 8.1873 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.6781 31.4924 11.1285 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-4.2475 31.9954 10.3749 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-4.3529 29.7723 11.5179 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-4.7404 29.7213 9.7481 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.1096 28.9239 11.0705 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.3427 27.8318 10.3148 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.1334 22.9663 12.8408 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.4510 22.7600 12.6820 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.6312 23.8345 8.3724 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1 2 1 0
|
||||
2 3 1 0
|
||||
3 4 2 0
|
||||
4 5 1 0
|
||||
5 6 2 0
|
||||
6 7 1 0
|
||||
7 8 1 0
|
||||
8 9 1 0
|
||||
9 10 2 0
|
||||
10 11 1 0
|
||||
11 12 1 0
|
||||
12 13 1 0
|
||||
13 14 1 0
|
||||
14 15 1 0
|
||||
15 16 1 0
|
||||
16 17 1 0
|
||||
17 18 1 0
|
||||
18 19 1 0
|
||||
11 20 2 0
|
||||
20 21 1 0
|
||||
21 22 2 0
|
||||
22 23 1 0
|
||||
23 24 1 0
|
||||
24 25 2 0
|
||||
7 26 2 0
|
||||
26 3 1 0
|
||||
25 9 1 0
|
||||
19 14 1 0
|
||||
24 20 1 0
|
||||
1 27 1 0
|
||||
1 28 1 0
|
||||
1 29 1 0
|
||||
4 30 1 0
|
||||
5 31 1 0
|
||||
6 32 1 0
|
||||
8 33 1 0
|
||||
13 34 1 0
|
||||
13 35 1 0
|
||||
14 36 1 0
|
||||
15 37 1 0
|
||||
15 38 1 0
|
||||
16 39 1 0
|
||||
16 40 1 0
|
||||
17 41 1 0
|
||||
17 42 1 0
|
||||
18 43 1 0
|
||||
18 44 1 0
|
||||
19 45 1 0
|
||||
19 46 1 0
|
||||
22 47 1 0
|
||||
23 48 1 0
|
||||
26 49 1 0
|
||||
M END
|
||||
> <ligandCode> (1)
|
||||
forcefield_3904
|
||||
|
||||
> <ligandName> (1)
|
||||
klr_22
|
||||
|
||||
$$$$
|
||||
forcefield_3905
|
||||
RDKit 3D
|
||||
|
||||
49 52 0 0 0 0 0 0 0 0999 V2000
|
||||
6.5460 25.1350 3.8360 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
5.2550 25.5560 3.9960 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.5630 25.8360 3.0240 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.7190 25.6170 5.3820 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.0120 26.7570 5.7730 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.4880 26.8570 7.0690 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.6630 25.8340 8.0050 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.1640 25.8720 9.3110 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1.9510 25.5250 9.9030 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.8900 26.1930 9.3960 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.3560 25.9480 9.8850 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.4490 26.6220 9.3800 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.1050 27.7730 8.6070 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.7010 29.0180 9.2810 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.5340 30.2520 8.3770 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.5490 31.3650 8.6570 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.0430 31.3400 10.0950 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.7320 30.0170 10.4330 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.1730 28.8170 9.6630 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.5160 25.0110 10.9050 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.5980 24.5310 11.6140 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.0930 23.6490 12.4490 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.2630 23.5390 12.3140 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.6490 24.4020 11.3310 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1.8910 24.6070 10.8850 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.3890 24.7010 7.5990 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.9170 24.5890 6.3060 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
7.0340 24.6220 4.5560 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
6.8360 24.9760 2.8790 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.8658 27.5811 5.0592 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
2.9275 27.7591 7.3554 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.7800 26.2290 10.0270 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.0105 27.8693 8.5543 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.5056 27.6720 7.5875 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.1446 29.1858 10.2149 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.5231 30.6584 8.5280 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.7027 29.9170 7.3429 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.0722 32.3370 8.4626 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.4156 31.2068 7.9983 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.1848 31.4776 10.7693 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.7762 32.1504 10.2201 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.6091 29.8266 11.5094 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-4.7879 30.1213 10.1428 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.2575 27.9194 10.2932 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.7539 28.7064 8.7355 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.6897 23.0698 13.1690 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.8800 22.9330 12.8360 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.5467 23.8809 8.3150 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
5.4866 23.6927 6.0193 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1 2 1 0
|
||||
2 3 2 0
|
||||
2 4 1 0
|
||||
4 5 2 0
|
||||
5 6 1 0
|
||||
6 7 2 0
|
||||
7 8 1 0
|
||||
8 9 1 0
|
||||
9 10 2 0
|
||||
10 11 1 0
|
||||
11 12 1 0
|
||||
12 13 1 0
|
||||
13 14 1 0
|
||||
14 15 1 0
|
||||
15 16 1 0
|
||||
16 17 1 0
|
||||
17 18 1 0
|
||||
18 19 1 0
|
||||
11 20 2 0
|
||||
20 21 1 0
|
||||
21 22 2 0
|
||||
22 23 1 0
|
||||
23 24 1 0
|
||||
24 25 2 0
|
||||
7 26 1 0
|
||||
26 27 2 0
|
||||
27 4 1 0
|
||||
25 9 1 0
|
||||
19 14 1 0
|
||||
24 20 1 0
|
||||
1 28 1 0
|
||||
1 29 1 0
|
||||
5 30 1 0
|
||||
6 31 1 0
|
||||
8 32 1 0
|
||||
13 33 1 0
|
||||
13 34 1 0
|
||||
14 35 1 0
|
||||
15 36 1 0
|
||||
15 37 1 0
|
||||
16 38 1 0
|
||||
16 39 1 0
|
||||
17 40 1 0
|
||||
17 41 1 0
|
||||
18 42 1 0
|
||||
18 43 1 0
|
||||
19 44 1 0
|
||||
19 45 1 0
|
||||
22 46 1 0
|
||||
23 47 1 0
|
||||
26 48 1 0
|
||||
27 49 1 0
|
||||
M END
|
||||
> <ligandCode> (2)
|
||||
forcefield_3905
|
||||
|
||||
> <ligandName> (2)
|
||||
1oiy-1
|
||||
|
||||
$$$$
|
||||
forcefield_14264
|
||||
RDKit 3D
|
||||
|
||||
50 53 0 0 0 0 0 0 0 0999 V2000
|
||||
4.9220 23.4040 3.0090 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
5.8970 24.7200 3.4040 S 0 0 0 0 0 6 0 0 0 0 0 0
|
||||
7.2120 24.2200 3.7260 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
5.6670 25.6980 2.3670 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
5.1310 25.2890 4.8970 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.3720 26.4580 4.8910 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.7730 26.8990 6.0760 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.9290 26.1970 7.2780 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.3430 26.5790 8.4890 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
2.1400 26.2820 9.1290 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1.0830 26.9670 8.6370 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.1520 26.7740 9.1730 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.2430 27.4630 8.6800 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.9280 28.7750 8.2140 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.2060 29.6320 8.2420 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.2120 29.0810 9.2730 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-4.1810 30.1570 9.7450 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.4520 31.3060 10.4440 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.0250 31.5210 9.9360 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.8770 31.1190 8.4740 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.3060 25.8730 10.2240 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.3770 25.4460 10.9810 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.8720 24.5730 11.8270 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.4750 24.4190 11.6520 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.8530 25.2430 10.6320 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
2.0880 25.3990 10.1410 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.7090 25.0260 7.2550 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
5.3130 24.5730 6.0780 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.1960 23.3080 3.7210 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
5.4460 22.5390 2.8810 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.2447 27.0304 3.9603 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.1666 27.8167 6.0634 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.8380 27.2640 9.0420 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.1661 29.2261 8.8668 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.5358 28.7227 7.1876 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.6885 29.5690 7.2555 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.7847 28.2635 8.8106 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.6467 28.7218 10.1456 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-4.7231 30.5555 8.8747 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-4.8753 29.7014 10.4664 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-4.0232 32.2317 10.2804 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.3803 31.0434 11.5098 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.7667 32.5851 10.0403 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.3511 30.8895 10.5336 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.5603 31.7316 7.8675 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.8284 31.2816 8.1841 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.4619 24.0333 12.5825 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1.0910 23.8110 12.1720 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.8468 24.4538 8.1843 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
5.9261 23.6598 6.0846 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1 2 1 0
|
||||
2 3 2 0
|
||||
2 4 2 0
|
||||
2 5 1 0
|
||||
5 6 2 0
|
||||
6 7 1 0
|
||||
7 8 2 0
|
||||
8 9 1 0
|
||||
9 10 1 0
|
||||
10 11 2 0
|
||||
11 12 1 0
|
||||
12 13 1 0
|
||||
13 14 1 0
|
||||
14 15 1 0
|
||||
15 16 1 0
|
||||
16 17 1 0
|
||||
17 18 1 0
|
||||
18 19 1 0
|
||||
19 20 1 0
|
||||
12 21 2 0
|
||||
21 22 1 0
|
||||
22 23 2 0
|
||||
23 24 1 0
|
||||
24 25 1 0
|
||||
25 26 2 0
|
||||
8 27 1 0
|
||||
27 28 2 0
|
||||
28 5 1 0
|
||||
26 10 1 0
|
||||
20 15 1 0
|
||||
25 21 1 0
|
||||
1 29 1 0
|
||||
1 30 1 0
|
||||
6 31 1 0
|
||||
7 32 1 0
|
||||
9 33 1 0
|
||||
14 34 1 0
|
||||
14 35 1 0
|
||||
15 36 1 0
|
||||
16 37 1 0
|
||||
16 38 1 0
|
||||
17 39 1 0
|
||||
17 40 1 0
|
||||
18 41 1 0
|
||||
18 42 1 0
|
||||
19 43 1 0
|
||||
19 44 1 0
|
||||
20 45 1 0
|
||||
20 46 1 0
|
||||
23 47 1 0
|
||||
24 48 1 0
|
||||
27 49 1 0
|
||||
28 50 1 0
|
||||
M END
|
||||
> <ligandCode> (3)
|
||||
forcefield_14264
|
||||
|
||||
> <ligandName> (3)
|
||||
1h1s
|
||||
|
||||
$$$$
|
||||
forcefield_14265
|
||||
RDKit 3D
|
||||
|
||||
50 53 0 0 0 0 0 0 0 0999 V2000
|
||||
5.9560 25.0880 4.1850 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
5.6880 24.2300 5.6100 S 0 0 0 0 0 6 0 0 0 0 0 0
|
||||
4.9010 23.0800 5.2270 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
6.9470 24.1160 6.3060 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.6500 25.3510 6.5050 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.3190 26.5790 5.9340 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.4970 27.4490 6.6410 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.0220 27.0950 7.9100 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.3680 25.8770 8.5100 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
2.9060 25.4620 9.7630 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1.6520 25.1280 10.2760 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.6590 25.9320 9.8350 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.6150 25.7270 10.2650 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.6410 26.5420 9.8330 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.2650 27.5510 8.8940 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.4150 28.9670 9.4830 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.0650 29.8960 8.4420 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.7130 31.1080 9.0990 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.8410 30.6980 10.0490 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.6490 29.3080 10.6570 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.1750 28.9600 10.8270 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.8720 24.6910 11.1610 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.0060 24.2140 11.7870 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.5910 23.2030 12.5210 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.2430 23.0050 12.3980 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.2320 23.9470 11.5330 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1.5000 24.1080 11.1380 N 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.1890 25.0030 7.7800 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
6.4350 24.4790 3.5180 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
5.1080 25.4920 3.7910 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.7020 26.8549 4.9404 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.2187 28.4186 6.2023 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
2.3612 27.7900 8.4488 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.5640 25.4960 10.5280 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.2150 27.3950 8.6054 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.9312 27.4715 8.0223 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.4134 29.3591 9.7135 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.2920 30.2413 7.7397 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.8482 29.3296 7.9168 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.9484 31.6568 9.6685 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.1423 31.7400 8.3076 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.8921 31.4320 10.8667 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-4.7682 30.6682 9.4579 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-4.1367 29.2806 11.6426 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-4.0960 28.5721 9.9724 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-1.7122 29.6974 11.4994 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.1140 27.9439 11.2440 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.2486 22.5915 13.1563 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.3120 22.2980 12.8550 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.4733 24.0335 8.2150 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1 2 1 0
|
||||
2 3 2 0
|
||||
2 4 2 0
|
||||
2 5 1 0
|
||||
5 6 2 0
|
||||
6 7 1 0
|
||||
7 8 2 0
|
||||
8 9 1 0
|
||||
9 10 1 0
|
||||
10 11 1 0
|
||||
11 12 2 0
|
||||
12 13 1 0
|
||||
13 14 1 0
|
||||
14 15 1 0
|
||||
15 16 1 0
|
||||
16 17 1 0
|
||||
17 18 1 0
|
||||
18 19 1 0
|
||||
19 20 1 0
|
||||
20 21 1 0
|
||||
13 22 2 0
|
||||
22 23 1 0
|
||||
23 24 2 0
|
||||
24 25 1 0
|
||||
25 26 1 0
|
||||
26 27 2 0
|
||||
9 28 2 0
|
||||
28 5 1 0
|
||||
27 11 1 0
|
||||
21 16 1 0
|
||||
26 22 1 0
|
||||
1 29 1 0
|
||||
1 30 1 0
|
||||
6 31 1 0
|
||||
7 32 1 0
|
||||
8 33 1 0
|
||||
10 34 1 0
|
||||
15 35 1 0
|
||||
15 36 1 0
|
||||
16 37 1 0
|
||||
17 38 1 0
|
||||
17 39 1 0
|
||||
18 40 1 0
|
||||
18 41 1 0
|
||||
19 42 1 0
|
||||
19 43 1 0
|
||||
20 44 1 0
|
||||
20 45 1 0
|
||||
21 46 1 0
|
||||
21 47 1 0
|
||||
24 48 1 0
|
||||
25 49 1 0
|
||||
28 50 1 0
|
||||
M END
|
||||
> <ligandCode> (4)
|
||||
forcefield_14265
|
||||
|
||||
> <ligandName> (4)
|
||||
1oiu
|
||||
|
||||
$$$$
|
||||
9013
examples/docking/receptor_1.pdb
Normal file
28000
examples/long_animation.sdf
Normal file
115
examples/mvs/1cbs-focus.mvsj
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "Example MolViewSpec - 1cbs with labelled and zoomed ligand",
|
||||
"version": "1",
|
||||
"timestamp": "2023-11-24T10:45:49.873Z"
|
||||
},
|
||||
"root": {
|
||||
"kind": "root",
|
||||
"children": [
|
||||
{
|
||||
"kind": "download",
|
||||
"params": {
|
||||
"url": "https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "parse",
|
||||
"params": {
|
||||
"format": "bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "structure",
|
||||
"params": {
|
||||
"type": "model"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "polymer"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "cartoon"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"color": "green"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"end_label_seq_id": 50
|
||||
},
|
||||
"color": "#6688ff"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Protein"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "ligand"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "focus",
|
||||
"params": {
|
||||
"direction": [0.5, 0, -1],
|
||||
"up": [0.365, 0.913, 0.183]
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "ball_and_stick"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"color": "#cc3399"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Retinoic Acid"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "canvas",
|
||||
"params": {
|
||||
"background_color": "#ffffee"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
117
examples/mvs/1cbs.mvsj
Normal file
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "Example MolViewSpec - 1cbs with labelled protein and ligand",
|
||||
"version": "1",
|
||||
"timestamp": "2023-11-24T10:38:17.483Z"
|
||||
},
|
||||
"root": {
|
||||
"kind": "root",
|
||||
"children": [
|
||||
{
|
||||
"kind": "download",
|
||||
"params": {
|
||||
"url": "https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "parse",
|
||||
"params": {
|
||||
"format": "bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "structure",
|
||||
"params": {
|
||||
"type": "model"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "polymer"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "cartoon"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"color": "green"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 1,
|
||||
"end_label_seq_id": 50
|
||||
},
|
||||
"color": "#6688ff"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Protein"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "ligand"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "ball_and_stick"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"color": "#cc3399"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Retinoic Acid"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "canvas",
|
||||
"params": {
|
||||
"background_color": "#ffffee"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "camera",
|
||||
"params": {
|
||||
"target": [17, 21, 27],
|
||||
"position": [41, 34, 69],
|
||||
"up": [-0.129,0.966,-0.224]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
examples/mvs/1h9t.mvsx
Normal file
67
examples/mvs/1h9t_domain_colors.mvsj
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "Example MolViewSpec - 1h9t colored by external annotation",
|
||||
"version": "1",
|
||||
"timestamp": "2023-11-24T10:47:33.182Z"
|
||||
},
|
||||
"root": {
|
||||
"kind": "root",
|
||||
"children": [
|
||||
{
|
||||
"kind": "download",
|
||||
"params": {
|
||||
"url": "https://www.ebi.ac.uk/pdbe/entry-files/1h9t.bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "parse",
|
||||
"params": {
|
||||
"format": "bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "structure",
|
||||
"params": {
|
||||
"type": "model"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "polymer"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "cartoon"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"selector": "all",
|
||||
"color": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "color_from_uri",
|
||||
"params": {
|
||||
"uri": "./1h9t_domains.json",
|
||||
"format": "json",
|
||||
"schema": "all_atomic"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
583
examples/mvs/1h9t_domain_labels.mvsj
Normal file
@@ -0,0 +1,583 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "Example MolViewSpec - 1h9t colored and labelled by external annotation",
|
||||
"version": "1",
|
||||
"timestamp": "2023-11-24T10:48:28.677Z"
|
||||
},
|
||||
"root": {
|
||||
"kind": "root",
|
||||
"children": [
|
||||
{
|
||||
"kind": "download",
|
||||
"params": {
|
||||
"url": "https://www.ebi.ac.uk/pdbe/entry-files/1h9t.bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "parse",
|
||||
"params": {
|
||||
"format": "bcif"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "structure",
|
||||
"params": {
|
||||
"type": "model"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "protein"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "cartoon"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"selector": "all",
|
||||
"color": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "color_from_uri",
|
||||
"params": {
|
||||
"uri": "./1h9t_domains.json",
|
||||
"format": "json",
|
||||
"schema": "all_atomic"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "nucleic"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "ball_and_stick"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color",
|
||||
"params": {
|
||||
"selector": "all",
|
||||
"color": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "color_from_uri",
|
||||
"params": {
|
||||
"uri": "./1h9t_domains.json",
|
||||
"format": "json",
|
||||
"schema": "all_atomic"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "ion"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "representation",
|
||||
"params": {
|
||||
"type": "surface"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "color_from_uri",
|
||||
"params": {
|
||||
"uri": "./1h9t_domains.json",
|
||||
"format": "json",
|
||||
"schema": "all_atomic"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 9,
|
||||
"end_label_seq_id": 83
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "DNA-binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"beg_label_seq_id": 9,
|
||||
"end_label_seq_id": 83
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "DNA-binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 84,
|
||||
"end_label_seq_id": 231
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Acyl-CoA\nbinding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"beg_label_seq_id": 84,
|
||||
"end_label_seq_id": 231
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Acyl-CoA binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "C"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "DNA X"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "D"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "DNA Y"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "D",
|
||||
"atom_id": 4016
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "DNA Y O5'"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "D",
|
||||
"atom_id": 4391
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "DNA Y O3'"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "E"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Gold"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "H"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Gold"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "F"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Chloride"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "G"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Chloride"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "I"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Chloride"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 57
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 67
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 121
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 125
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 129
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 178
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 203,
|
||||
"end_label_seq_id": 205
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 67
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 121
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 125
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 129
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 178
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": {
|
||||
"label_asym_id": "B",
|
||||
"beg_label_seq_id": 203,
|
||||
"end_label_seq_id": 205
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "label",
|
||||
"params": {
|
||||
"text": "Ligand binding"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "component",
|
||||
"params": {
|
||||
"selector": "all"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"kind": "focus",
|
||||
"params": {
|
||||
"direction": [-0.3, -0.1, -1]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "canvas",
|
||||
"params": {
|
||||
"background_color": "#eeffee"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
155
examples/mvs/1h9t_domains.json
Normal file
@@ -0,0 +1,155 @@
|
||||
[
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 9,
|
||||
"end_label_seq_id": 83,
|
||||
"color": "#dd6600",
|
||||
"tooltip": "DNA-binding"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 84,
|
||||
"end_label_seq_id": 231,
|
||||
"color": "#008800",
|
||||
"tooltip": "Acyl-CoA binding"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"beg_label_seq_id": 9,
|
||||
"end_label_seq_id": 83,
|
||||
"color": "#cc8800",
|
||||
"tooltip": "DNA-binding"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"beg_label_seq_id": 84,
|
||||
"end_label_seq_id": 231,
|
||||
"color": "#008888",
|
||||
"tooltip": "Acyl-CoA binding"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "C",
|
||||
"color": "#1100aa",
|
||||
"tooltip": "DNA X"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "D",
|
||||
"color": "#dddddd",
|
||||
"tooltip": "DNA Y"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "D",
|
||||
"atom_id": 4016,
|
||||
"color": "#ff0044",
|
||||
"tooltip": "DNA Y - O5'"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "D",
|
||||
"atom_id": 4391,
|
||||
"color": "#4400ff",
|
||||
"tooltip": "DNA Y - O3'"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"label_asym_id": "E",
|
||||
"color": "#ffff00",
|
||||
"tooltip": "Gold"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "H",
|
||||
"color": "#ffff00",
|
||||
"tooltip": "Gold"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "F",
|
||||
"color": "#00dd00",
|
||||
"tooltip": "Chloride"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "G",
|
||||
"color": "#00dd00",
|
||||
"tooltip": "Chloride"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "I",
|
||||
"color": "#00dd00",
|
||||
"tooltip": "Chloride"
|
||||
},
|
||||
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 57,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 67,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 121,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 125,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 129,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"label_seq_id": 178,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "A",
|
||||
"beg_label_seq_id": 203,
|
||||
"end_label_seq_id": 205,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 67,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 121,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 125,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"label_seq_id": 129,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
},
|
||||
{
|
||||
"label_asym_id": "B",
|
||||
"beg_label_seq_id": 203,
|
||||
"end_label_seq_id": 205,
|
||||
"color": "#ff0000",
|
||||
"tooltip": "Ligand binding site"
|
||||
}
|
||||
]
|
||||
4052
examples/mvs/kinase-story.mvsj
Normal file
31451
package-lock.json
generated
194
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.23.0",
|
||||
"version": "5.0.0-dev.2",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -10,29 +10,30 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/molstar/molstar/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"lint-fix": "eslint . --fix",
|
||||
"test": "npm install --no-save \"gl@^5.0.0\" && npm run lint && jest",
|
||||
"test": "npm install --no-save \"gl@^6.0.2\" && npm run lint && jest",
|
||||
"jest": "jest",
|
||||
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
|
||||
"clean": "node ./scripts/clean.js",
|
||||
"clean": "node ./scripts/clean.js --all",
|
||||
"clean:build": "node ./scripts/clean.js --build",
|
||||
"build": "npm run build:apps && npm run build:lib",
|
||||
"build:apps": "node ./scripts/build.mjs -a -e --prd",
|
||||
"build:lib": "concurrently \"tsc --incremental\" \"tsc --build tsconfig.commonjs.json --incremental\" && npm run build:lib-extra",
|
||||
"build:lib-extra": "node scripts/write-version.mjs && cpx \"src/**/*.{scss,html,ico,jpg}\" lib/ && cpx \"src/**/*.{scss,html,ico,jpg}\" lib/commonjs/ && tsc-alias -p tsconfig.json",
|
||||
"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,jpg}\" lib/",
|
||||
"build-webpack": "webpack --mode production --config ./webpack.config.production.js",
|
||||
"build-webpack-viewer": "webpack --mode production --config ./webpack.config.viewer.js",
|
||||
"watch": "concurrently -c \"green,green,gray,gray\" --names \"tsc,srv,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-servers\" \"npm:watch-extra\" \"npm:watch-webpack\"",
|
||||
"watch-viewer": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer\"",
|
||||
"watch-viewer-debug": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer-debug\"",
|
||||
"watch-tsc": "tsc --watch --incremental",
|
||||
"watch-servers": "tsc --build tsconfig.commonjs.json --watch --incremental",
|
||||
"watch-extra": "cpx \"src/**/*.{scss,html,ico,jpg}\" lib/ --watch",
|
||||
"watch-webpack": "webpack -w --mode development --stats minimal",
|
||||
"watch-webpack-viewer": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.js",
|
||||
"watch-webpack-viewer-debug": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.debug.js",
|
||||
"dev": "node ./scripts/build.mjs",
|
||||
"dev:all": "node ./scripts/build.mjs -a -e -bt",
|
||||
"dev:viewer": "node ./scripts/build.mjs -a viewer",
|
||||
"dev:apps": "node ./scripts/build.mjs -a",
|
||||
"dev:examples": "node ./scripts/build.mjs -e",
|
||||
"dev:browser-tests": "node ./scripts/build.mjs -bt",
|
||||
"serve": "http-server -p 1338 -g",
|
||||
"deploy:local": "npm run clean:build && npm run build:apps && node ./scripts/deploy.js --local",
|
||||
"deploy:remote": "npm run clean:build && npm run build:apps && node ./scripts/deploy.js",
|
||||
"model-server": "node lib/commonjs/servers/model/server.js",
|
||||
"model-server-watch": "nodemon --watch lib lib/commonjs/servers/model/server.js",
|
||||
"volume-server-test": "node lib/commonjs/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
|
||||
@@ -43,11 +44,15 @@
|
||||
},
|
||||
"files": [
|
||||
"lib/",
|
||||
"build/viewer/"
|
||||
"build/viewer/",
|
||||
"build/mvs-stories/"
|
||||
],
|
||||
"bin": {
|
||||
"cif2bcif": "lib/commonjs/cli/cif2bcif/index.js",
|
||||
"cifschema": "lib/commonjs/cli/cifschema/index.js",
|
||||
"mvs-validate": "lib/commonjs/cli/mvs/mvs-validate.js",
|
||||
"mvs-render": "lib/commonjs/cli/mvs/mvs-render.js",
|
||||
"mvs-print-schema": "lib/commonjs/cli/mvs/mvs-print-schema.js",
|
||||
"model-server": "lib/commonjs/servers/model/server.js",
|
||||
"model-server-query": "lib/commonjs/servers/model/query.js",
|
||||
"model-server-preprocess": "lib/commonjs/servers/model/preprocess.js",
|
||||
@@ -92,76 +97,109 @@
|
||||
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>",
|
||||
"Adam Midlik <midlik@gmail.com>",
|
||||
"Koya Sakuma <koya.sakuma.work@gmail.com>",
|
||||
"Gianluca Tomasello <giagitom@gmail.com>"
|
||||
"Gianluca Tomasello <giagitom@gmail.com>",
|
||||
"Ke Ma <mark.ma@rcsb.org>",
|
||||
"Jason Pattle <jpattle@exscientia.co.uk>",
|
||||
"David Williams <dwilliams@nobiastx.com>",
|
||||
"Zhenyu Zhang <jump2cn@gmail.com>",
|
||||
"Russell Parker <russell@benchling.com>",
|
||||
"Dominik Tichy <tichydominik451@gmail.com>",
|
||||
"Yana Rose <yana.v.rose@gmail.com>",
|
||||
"Yakov Pechersky <ffxen158@gmail.com>",
|
||||
"Christian Dominguez <christian.99dominguez@gmail.com>",
|
||||
"Cai Huiyu <szmun.caihy@gmail.com>",
|
||||
"Ryan DiRisio <rjdiris@gmail.com>",
|
||||
"Dušan Veľký <dvelky@mail.muni.cz>",
|
||||
"Neli Fonseca <neli@ebi.ac.uk>",
|
||||
"Paul Pillot <paul.pillot@tandemai.com>",
|
||||
"Herman Bergwerf <post@hbergwerf.nl>",
|
||||
"Eric E <etongfu@outlook.com>",
|
||||
"Xavier Martinez <xavier.martinez.xm@gmail.com>",
|
||||
"Alex Chan <smalldirkalex@gmail.com>",
|
||||
"Simeon Borko <simeon.borko@gmail.com>",
|
||||
"Ventura Rivera <venturaxrivera@gmail.com>",
|
||||
"Andy Turner <agdturner@gmail.com>",
|
||||
"Lukáš Polák <admin@lukaspolak.cz>",
|
||||
"Chetan Mishra <chetan.s115@gmail.com>",
|
||||
"Zach Charlop-Powers <zach.charlop.powers@gmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^3.2.1",
|
||||
"@graphql-codegen/cli": "^2.13.7",
|
||||
"@graphql-codegen/time": "^3.2.1",
|
||||
"@graphql-codegen/typescript": "^2.7.4",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.6",
|
||||
"@graphql-codegen/typescript-operations": "^2.5.4",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/gl": "^4.1.1",
|
||||
"@types/jest": "^29.1.2",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
||||
"@typescript-eslint/parser": "^5.40.0",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/gl": "^6.0.5",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"@types/react": "^18.3.23",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@typescript-eslint/eslint-plugin": "^8.34.0",
|
||||
"@typescript-eslint/parser": "^8.34.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^7.4.0",
|
||||
"cpx2": "^4.2.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"eslint": "^8.25.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"graphql": "^16.6.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"cpx2": "^8.0.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"esbuild": "^0.25.5",
|
||||
"esbuild-sass-plugin": "^3.3.1",
|
||||
"eslint": "^9.29.0",
|
||||
"fs-extra": "^11.3.0",
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "^29.2.0",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sass": "^1.55.0",
|
||||
"sass-loader": "^13.1.0",
|
||||
"simple-git": "^3.14.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typescript": "^4.8.4",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
"jest": "^29.7.0",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"sass": "^1.89.1",
|
||||
"simple-git": "^3.28.0",
|
||||
"ts-jest": "^29.3.4",
|
||||
"tsc-alias": "^1.8.16",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^2.0.10",
|
||||
"@types/benchmark": "^2.1.2",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/node": "^16.11.66",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"@types/argparse": "^2.0.17",
|
||||
"@types/benchmark": "^2.1.5",
|
||||
"@types/compression": "1.8.1",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/node": "^18.19.111",
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"@types/swagger-ui-dist": "3.30.5",
|
||||
"argparse": "^2.0.1",
|
||||
"body-parser": "^1.20.1",
|
||||
"compression": "^1.7.4",
|
||||
"compression": "^1.8.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"express": "^5.1.0",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^9.0.15",
|
||||
"immutable": "^4.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"rxjs": "^7.5.7",
|
||||
"swagger-ui-dist": "^4.14.3",
|
||||
"tslib": "^2.4.0",
|
||||
"util.promisify": "^1.1.1",
|
||||
"xhr2": "^0.2.1"
|
||||
"immer": "^10.1.1",
|
||||
"immutable": "^5.1.2",
|
||||
"io-ts": "^2.2.22",
|
||||
"node-fetch": "^2.7.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"swagger-ui-dist": "^5.24.0",
|
||||
"tslib": "^2.8.1",
|
||||
"util.promisify": "^1.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.1.0 || ^17.0.2 || ^16.14.0",
|
||||
"react-dom": "^18.1.0 || ^17.0.2 || ^16.14.0"
|
||||
"@google-cloud/storage": "^7.14.0",
|
||||
"canvas": "^2.11.2",
|
||||
"gl": "^6.0.2",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"pngjs": "^6.0.0",
|
||||
"react": ">=16.14.0",
|
||||
"react-dom": ">=16.14.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@google-cloud/storage": {
|
||||
"optional": true
|
||||
},
|
||||
"canvas": {
|
||||
"optional": true
|
||||
},
|
||||
"gl": {
|
||||
"optional": true
|
||||
},
|
||||
"jpeg-js": {
|
||||
"optional": true
|
||||
},
|
||||
"pngjs": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
307
scripts/build.mjs
Normal file
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Eric E <etongfu@@outlook.com>
|
||||
*/
|
||||
import * as esbuild from 'esbuild';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as argparse from 'argparse';
|
||||
import { sassPlugin } from 'esbuild-sass-plugin';
|
||||
import * as os from 'os';
|
||||
|
||||
const Apps = [
|
||||
// Apps
|
||||
{ kind: 'app', name: 'viewer' },
|
||||
{ kind: 'app', name: 'docking-viewer' },
|
||||
{ kind: 'app', name: 'mesoscale-explorer' },
|
||||
{ kind: 'app', name: 'mvs-stories', globalName: 'mvsStories', filename: 'mvs-stories.js' },
|
||||
|
||||
// Examples
|
||||
{ kind: 'example', name: 'proteopedia-wrapper' },
|
||||
{ kind: 'example', name: 'basic-wrapper' },
|
||||
{ kind: 'example', name: 'lighting' },
|
||||
{ kind: 'example', name: 'alpha-orbitals' },
|
||||
{ kind: 'example', name: 'alphafolddb-pae' },
|
||||
{ kind: 'example', name: 'mvs-stories' },
|
||||
{ kind: 'example', name: 'ihm-restraints' },
|
||||
{ kind: 'example', name: 'interactions' },
|
||||
{ kind: 'example', name: 'ligand-editor' },
|
||||
];
|
||||
|
||||
function findApp(name, kind) {
|
||||
return Apps.find(a => a.name === name && a.kind === kind);
|
||||
}
|
||||
|
||||
function mkDir(dir) {
|
||||
try {
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to create directory ${dir}:`, error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function handleFileError(error, operation, path) {
|
||||
console.error(`Failed to ${operation} ${path}:`, error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function fileLoaderPlugin(options) {
|
||||
mkDir(options.out);
|
||||
|
||||
return {
|
||||
name: 'file-loader',
|
||||
setup(build) {
|
||||
build.onLoad({ filter: /\.jpg$/ }, async (args) => {
|
||||
try {
|
||||
const name = path.basename(args.path);
|
||||
mkDir(path.resolve(options.out, 'images'));
|
||||
await fs.promises.copyFile(args.path, path.resolve(options.out, 'images', name));
|
||||
return {
|
||||
contents: `images/${name}`,
|
||||
loader: 'text',
|
||||
};
|
||||
} catch (error) {
|
||||
handleFileError(error, 'copy', args.path);
|
||||
}
|
||||
});
|
||||
build.onLoad({ filter: /\.(html|ico)$/ }, async (args) => {
|
||||
const name = path.basename(args.path);
|
||||
await fs.promises.copyFile(args.path, path.resolve(options.out, name));
|
||||
return {
|
||||
contents: '',
|
||||
loader: 'empty',
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function examplesCssRenamePlugin({ root }) {
|
||||
return {
|
||||
name: 'example-css-rename',
|
||||
setup(build) {
|
||||
build.onEnd(async () => {
|
||||
if (fs.existsSync(path.resolve(root, 'index.css'))) {
|
||||
await fs.promises.rename(
|
||||
path.resolve(root, 'index.css'),
|
||||
path.resolve(root, 'molstar.css')
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function resolveEntryPath(path) {
|
||||
if (!fs.existsSync(path)) {
|
||||
return path + 'x'; // fallback to .tsx
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
function getPaths(app) {
|
||||
if (app.kind === 'app') {
|
||||
return {
|
||||
prefix: `./build/${app.name}`,
|
||||
entry: resolveEntryPath(`./src/apps/${app.name}/index.ts`),
|
||||
outfile: `./build/${app.name}/${app.filename || 'molstar.js'}`,
|
||||
};
|
||||
}
|
||||
if (app.kind === 'example') {
|
||||
return {
|
||||
prefix: `./build/examples/${app.name}`,
|
||||
entry: resolveEntryPath(`./src/examples/${app.name}/index.ts`),
|
||||
outfile: `./build/examples/${app.name}/${app.filename || 'index.js'}`,
|
||||
};
|
||||
}
|
||||
if (app.kind === 'browser-test') {
|
||||
return {
|
||||
prefix: `./build/tests/browser`,
|
||||
entry: resolveEntryPath(`./src/tests/browser/${app.name}.ts`),
|
||||
outfile: `./build/tests/browser/${app.name}.js`,
|
||||
};
|
||||
}
|
||||
throw new Error(`Unknown app kind: ${app.kind}`);
|
||||
}
|
||||
|
||||
async function createBundle(app) {
|
||||
const { name, kind } = app;
|
||||
|
||||
const { prefix, entry, outfile } = getPaths(app);
|
||||
|
||||
const ctx = await esbuild.context({
|
||||
entryPoints: [entry],
|
||||
tsconfig: './tsconfig.json',
|
||||
bundle: true,
|
||||
minify: isProduction,
|
||||
minifyIdentifiers: false,
|
||||
sourcemap: includeSourceMap,
|
||||
globalName: app.globalName || 'molstar',
|
||||
outfile,
|
||||
plugins: [
|
||||
fileLoaderPlugin({ out: prefix }),
|
||||
sassPlugin({
|
||||
type: 'css',
|
||||
silenceDeprecations: ['import'],
|
||||
logger: {
|
||||
warn: (msg) => console.warn(msg),
|
||||
debug: () => { },
|
||||
}
|
||||
}),
|
||||
...(kind === 'example' ? [examplesCssRenamePlugin({ root: prefix })] : []),
|
||||
],
|
||||
external: ['crypto', 'fs', 'path', 'stream'],
|
||||
loader: {
|
||||
},
|
||||
color: true,
|
||||
logLevel: 'info',
|
||||
define: {
|
||||
'process.env.DEBUG': JSON.stringify(process.env.DEBUG || false),
|
||||
__MOLSTAR_PLUGIN_VERSION__: JSON.stringify(VERSION),
|
||||
__MOLSTAR_BUILD_TIMESTAMP__: `${TIMESTAMP}`,
|
||||
},
|
||||
});
|
||||
|
||||
await ctx.rebuild();
|
||||
|
||||
if (!isProduction) await ctx.watch();
|
||||
}
|
||||
|
||||
function findBrowserTests(names) {
|
||||
const dir = path.resolve('./src', 'tests', 'browser');
|
||||
let files = fs.readdirSync(dir).filter(file => file.endsWith('.ts')).map(file => file.replace('.ts', ''));
|
||||
if (names.length) {
|
||||
files = files.filter(file => names.includes(file));
|
||||
}
|
||||
return files.map(name => ({ kind: 'browser-test', name }));
|
||||
}
|
||||
|
||||
const argParser = new argparse.ArgumentParser({
|
||||
add_help: true,
|
||||
description: 'Mol* Build'
|
||||
});
|
||||
argParser.add_argument('--prd', {
|
||||
help: 'Create a production build.',
|
||||
required: false,
|
||||
action: 'store_true',
|
||||
});
|
||||
argParser.add_argument('--no-src-map', {
|
||||
help: 'Do not include source map.',
|
||||
required: false,
|
||||
action: 'store_true',
|
||||
});
|
||||
argParser.add_argument('--apps', '-a', {
|
||||
help: 'Apps to build.',
|
||||
required: false,
|
||||
nargs: '*',
|
||||
});
|
||||
argParser.add_argument('--examples', '-e', {
|
||||
help: 'Examples to build.',
|
||||
required: false,
|
||||
nargs: '*',
|
||||
});
|
||||
argParser.add_argument('--browser-tests', '-bt', {
|
||||
help: 'Browser Tests to build.',
|
||||
required: false,
|
||||
nargs: '*',
|
||||
});
|
||||
argParser.add_argument('--port', '-p', {
|
||||
help: 'Port.',
|
||||
required: false,
|
||||
default: 1338,
|
||||
type: 'int',
|
||||
});
|
||||
|
||||
argParser.add_argument('--host', {
|
||||
help: 'Show all available host addresses.',
|
||||
required: false,
|
||||
action: 'store_true',
|
||||
});
|
||||
|
||||
const args = argParser.parse_args();
|
||||
|
||||
|
||||
const isProduction = !!args.prd;
|
||||
const includeSourceMap = !args.no_src_map;
|
||||
|
||||
const VERSION = isProduction ? JSON.parse(fs.readFileSync('./package.json', 'utf8')).version : '(dev build)';
|
||||
const TIMESTAMP = Date.now();
|
||||
|
||||
const apps = (!args.apps ? [] : (args.apps.length ? args.apps.map(a => findApp(a, 'app')).filter(a => a) : Apps.filter(a => a.kind === 'app')));
|
||||
const examples = (!args.examples ? [] : (args.examples.length ? args.examples.map(e => findApp(e, 'example')).filter(a => a) : Apps.filter(a => a.kind === 'example')));
|
||||
const browserTests = (!args.browser_tests ? [] : findBrowserTests(args.browser_tests));
|
||||
|
||||
console.log('Apps:', apps.map(a => a.name));
|
||||
console.log('Examples:', examples.map(e => e.name));
|
||||
console.log('Browser Tests', browserTests.map(e => e.name));
|
||||
console.log('');
|
||||
|
||||
function getLocalIPs() {
|
||||
const interfaces = os.networkInterfaces();
|
||||
const ips = [];
|
||||
|
||||
for (const name of Object.keys(interfaces)) {
|
||||
for (const iface of interfaces[name]) {
|
||||
// Skip internal and non-IPv4 addresses
|
||||
if (iface.internal || iface.family !== 'IPv4') continue;
|
||||
ips.push(iface.address);
|
||||
}
|
||||
}
|
||||
|
||||
return ips;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const promises = [];
|
||||
console.log(isProduction ? 'Building apps...' : 'Initial build...');
|
||||
|
||||
for (const app of apps) promises.push(createBundle(app));
|
||||
for (const example of examples) promises.push(createBundle(example));
|
||||
for (const browserTest of browserTests) promises.push(createBundle(browserTest));
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
if (isProduction) {
|
||||
console.log('Done.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log('Initial build complete.');
|
||||
|
||||
const certfile = './dev.pem';
|
||||
const keyfile = './dev-key.pem';
|
||||
|
||||
const sslEnabled = fs.existsSync(certfile) && fs.existsSync(keyfile);
|
||||
const protocol = sslEnabled ? 'https' : 'http';
|
||||
|
||||
const ctx = await esbuild.context({});
|
||||
ctx.serve({
|
||||
servedir: './',
|
||||
port: args.port,
|
||||
host: '0.0.0.0', // Always listen on all interfaces
|
||||
certfile: sslEnabled ? certfile : undefined,
|
||||
keyfile: sslEnabled ? keyfile : undefined,
|
||||
});
|
||||
|
||||
console.log('');
|
||||
console.log(`Server URL: ${protocol}://localhost:${args.port}`);
|
||||
if (args.host) {
|
||||
console.log('Available host addresses:');
|
||||
const ips = getLocalIPs();
|
||||
ips.forEach(ip => console.log(` ${protocol}://${ip}:${args.port}`));
|
||||
}
|
||||
console.log('');
|
||||
console.log('Watching for changes...');
|
||||
console.log('');
|
||||
console.log('Press Ctrl+C to stop.');
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const argparse = require('argparse');
|
||||
|
||||
function removeDir(dirPath) {
|
||||
for (const ent of fs.readdirSync(dirPath)) {
|
||||
@@ -24,11 +25,29 @@ function remove(entryPath) {
|
||||
fs.unlinkSync(entryPath);
|
||||
}
|
||||
|
||||
const toClean = [
|
||||
path.resolve(__dirname, '../build'),
|
||||
path.resolve(__dirname, '../lib'),
|
||||
path.resolve(__dirname, '../tsconfig.tsbuildinfo'),
|
||||
];
|
||||
const argParser = new argparse.ArgumentParser({
|
||||
add_help: true,
|
||||
description: 'Clean Script'
|
||||
});
|
||||
argParser.add_argument('--build', { required: false, action: 'store_true' });
|
||||
argParser.add_argument('--lib', { required: false, action: 'store_true' });
|
||||
argParser.add_argument('--all', { required: false, action: 'store_true' });
|
||||
const args = argParser.parse_args();
|
||||
|
||||
const toClean = [];
|
||||
|
||||
if (args.build || args.all) {
|
||||
toClean.push(path.resolve(__dirname, '../build'));
|
||||
toClean.push(path.resolve(__dirname, '../deploy/data'));
|
||||
}
|
||||
if (args.lib || args.all) {
|
||||
toClean.push(
|
||||
path.resolve(__dirname, '../lib'),
|
||||
path.resolve(__dirname, '../tsconfig.tsbuildinfo'),
|
||||
);
|
||||
}
|
||||
|
||||
console.log('\n###', 'cleaning', toClean.join(', '));
|
||||
|
||||
toClean.forEach(ph => {
|
||||
if (fs.existsSync(ph)) {
|
||||
|
||||
@@ -1,21 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
const git = require('simple-git');
|
||||
const path = require('path');
|
||||
const fs = require("fs");
|
||||
const fse = require("fs-extra");
|
||||
const argparse = require('argparse');
|
||||
|
||||
const VERSION = require(path.resolve(__dirname, '../package.json')).version;
|
||||
const MVS_STORIES_VERSION = require(path.resolve(__dirname, '../src/apps/mvs-stories/version.ts')).VERSION;
|
||||
|
||||
const remoteUrl = "https://github.com/molstar/molstar.github.io.git";
|
||||
const dataDir = path.resolve(__dirname, '../data/');
|
||||
const buildDir = path.resolve(__dirname, '../build/');
|
||||
const deployDir = path.resolve(buildDir, 'deploy/');
|
||||
const localPath = path.resolve(deployDir, 'molstar.github.io/');
|
||||
const deployDir = path.resolve(__dirname, '../deploy/');
|
||||
const localPath = path.resolve(deployDir, 'data/');
|
||||
const repositoryPath = 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 -->`;
|
||||
const analyticsCode = `<!-- Cloudflare Web Analytics --><script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "c414cbae2d284ea995171a81e4a3e721"}'></script><!-- End Cloudflare Web Analytics --><iframe src="https://web3dsurvey.com/collector-iframe.html" style="width: 1px; height: 1px;"></iframe>`;
|
||||
|
||||
const manifestTag = /<!-- __MOLSTAR_MANIFEST__ -->/g;
|
||||
const manifestCode = `<link rel="manifest" href="./manifest.webmanifest">`;
|
||||
|
||||
const pwaTag = /<!-- __MOLSTAR_PWA__ -->/g;
|
||||
const pwaCode = `<script src='./pwa.js'></script>`;
|
||||
|
||||
function log(command, stdout, stderr) {
|
||||
if (command) {
|
||||
@@ -31,30 +44,117 @@ function addAnalytics(path) {
|
||||
fs.writeFileSync(path, result, 'utf8');
|
||||
}
|
||||
|
||||
function addManifest(path) {
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = data.replace(manifestTag, manifestCode);
|
||||
fs.writeFileSync(path, result, 'utf8');
|
||||
}
|
||||
|
||||
function addPwa(path) {
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = data.replace(pwaTag, pwaCode);
|
||||
fs.writeFileSync(path, result, 'utf8');
|
||||
}
|
||||
|
||||
function addVersion(path) {
|
||||
const data = fs.readFileSync(path, 'utf8');
|
||||
const result = data.replace('__MOLSTAR_VERSION__', VERSION);
|
||||
fs.writeFileSync(path, result, 'utf8');
|
||||
}
|
||||
|
||||
function copyViewer() {
|
||||
console.log('\n###', 'copy viewer files');
|
||||
const viewerBuildPath = path.resolve(buildDir, '../build/viewer/');
|
||||
const viewerBuildPath = path.resolve(buildDir, 'viewer/');
|
||||
const viewerDeployPath = path.resolve(localPath, 'viewer/');
|
||||
fse.copySync(viewerBuildPath, viewerDeployPath, { overwrite: true });
|
||||
addAnalytics(path.resolve(viewerDeployPath, 'index.html'));
|
||||
addManifest(path.resolve(viewerDeployPath, 'index.html'));
|
||||
addPwa(path.resolve(viewerDeployPath, 'index.html'));
|
||||
|
||||
const pwaDataPath = path.resolve(dataDir, 'pwa/');
|
||||
fse.copySync(pwaDataPath, viewerDeployPath, { overwrite: true });
|
||||
addVersion(path.resolve(viewerDeployPath, 'sw.js'));
|
||||
}
|
||||
|
||||
function copyMe() {
|
||||
console.log('\n###', 'copy me files');
|
||||
const meBuildPath = path.resolve(buildDir, 'mesoscale-explorer/');
|
||||
const meDeployPath = path.resolve(localPath, 'me/viewer/');
|
||||
fse.copySync(meBuildPath, meDeployPath, { overwrite: true });
|
||||
addAnalytics(path.resolve(meDeployPath, 'index.html'));
|
||||
}
|
||||
|
||||
function copyMVSStories() {
|
||||
console.log('\n###', 'copy MVS stories files');
|
||||
const mvsStoriesBuildPath = path.resolve(buildDir, 'mvs-stories/');
|
||||
const mvsStoriesDeployPath = path.resolve(localPath, `stories-viewer/v${MVS_STORIES_VERSION}/`);
|
||||
fse.copySync(mvsStoriesBuildPath, mvsStoriesDeployPath, { overwrite: true });
|
||||
addAnalytics(path.resolve(mvsStoriesDeployPath, 'index.html'));
|
||||
|
||||
// TODO: add PWA
|
||||
// addManifest(path.resolve(mvsStoriesDeployPath, 'index.html'));
|
||||
// addPwa(path.resolve(mvsStoriesDeployPath, 'index.html'));
|
||||
}
|
||||
|
||||
function copyDemo(name) {
|
||||
console.log('\n###', `copy demo files for ${name}`);
|
||||
const demoBuildPath = path.resolve(buildDir, `examples/${name}/`);
|
||||
const demoDeployPath = path.resolve(localPath, `demos/${name}/`);
|
||||
fse.copySync(demoBuildPath, demoDeployPath, { overwrite: true });
|
||||
addAnalytics(path.resolve(demoDeployPath, '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'));
|
||||
copyDemo('lighting');
|
||||
copyDemo('alpha-orbitals');
|
||||
copyDemo('mvs-stories');
|
||||
}
|
||||
|
||||
function copyFiles() {
|
||||
copyViewer();
|
||||
copyDemos();
|
||||
try {
|
||||
copyViewer();
|
||||
copyMe();
|
||||
copyMVSStories();
|
||||
copyDemos();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function copyToRepository() {
|
||||
console.log('\n###', 'copy repository files');
|
||||
fse.copySync(localPath, repositoryPath, { overwrite: true });
|
||||
}
|
||||
|
||||
function syncRepository() {
|
||||
console.log('\n###', 'sync repository');
|
||||
if (!fs.existsSync(path.resolve(repositoryPath, '.git/'))) {
|
||||
console.log('\n###', 'clone repository');
|
||||
git()
|
||||
.outputHandler(log)
|
||||
.clone(remoteUrl, repositoryPath)
|
||||
.fetch(['--all'])
|
||||
.exec(copyToRepository);
|
||||
} else {
|
||||
console.log('\n###', 'update repository');
|
||||
git()
|
||||
.outputHandler(log)
|
||||
.fetch(['--all'])
|
||||
.reset(['--hard', 'origin/master'])
|
||||
.exec(copyToRepository);
|
||||
}
|
||||
}
|
||||
|
||||
function commit() {
|
||||
console.log('\n###', 'commit changes');
|
||||
git()
|
||||
.outputHandler(log)
|
||||
.add(['-A'])
|
||||
.commit(`Updated Apps and Demos
|
||||
- Mol* version: ${VERSION}
|
||||
- MVS Stories version: ${MVS_STORIES_VERSION}`)
|
||||
.push();
|
||||
}
|
||||
|
||||
if (!fs.existsSync(localPath)) {
|
||||
@@ -62,26 +162,28 @@ if (!fs.existsSync(localPath)) {
|
||||
fs.mkdirSync(localPath, { recursive: true });
|
||||
}
|
||||
|
||||
process.chdir(localPath);
|
||||
const argParser = new argparse.ArgumentParser({
|
||||
add_help: true,
|
||||
description: 'Mol* Deploy'
|
||||
});
|
||||
argParser.add_argument('--local',{
|
||||
help: 'Do not commit to remote repository.',
|
||||
required: false,
|
||||
action: 'store_true',
|
||||
});
|
||||
const args = argParser.parse_args();
|
||||
|
||||
if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
|
||||
console.log('\n###', 'clone repository');
|
||||
git()
|
||||
.outputHandler(log)
|
||||
.clone(remoteUrl, localPath)
|
||||
.fetch(['--all'])
|
||||
.exec(copyFiles)
|
||||
.add(['-A'])
|
||||
.commit('updated viewer & demos')
|
||||
.push();
|
||||
} else {
|
||||
console.log('\n###', 'update repository');
|
||||
git()
|
||||
.outputHandler(log)
|
||||
.fetch(['--all'])
|
||||
.reset(['--hard', 'origin/master'])
|
||||
.exec(copyFiles)
|
||||
.add(['-A'])
|
||||
.commit('updated viewer & demos')
|
||||
.push();
|
||||
}
|
||||
copyFiles();
|
||||
|
||||
if (args.local) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(repositoryPath)) {
|
||||
console.log('\n###', 'create repositoryPath');
|
||||
fs.mkdirSync(repositoryPath, { recursive: true });
|
||||
}
|
||||
|
||||
process.chdir(repositoryPath);
|
||||
syncRepository();
|
||||
commit();
|
||||
16
scripts/write-version.mjs
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
|
||||
const VERSION = JSON.parse(fs.readFileSync('./package.json', 'utf8')).version;
|
||||
const TIMESTAMP = Date.now();
|
||||
const file = `export var PLUGIN_VERSION = '${VERSION}';\nexport var PLUGIN_VERSION_DATE = new Date(${TIMESTAMP})`;
|
||||
const files = ['./lib/mol-plugin/version.js', './lib/commonjs/mol-plugin/version.js'];
|
||||
for (const f of files) {
|
||||
if (!fs.existsSync(f)) continue;
|
||||
fs.writeFileSync(f, file);
|
||||
}
|
||||
@@ -8,7 +8,8 @@
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
import { createPluginUI } from '../../mol-plugin-ui/react18';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { renderReact18 } from '../../mol-plugin-ui/react18';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
@@ -26,7 +27,7 @@ import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import './index.html';
|
||||
import { ShowButtons, StructurePreset, ViewportComponent } from './viewport';
|
||||
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
import '../../mol-plugin-ui/skin/light.scss';
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
|
||||
@@ -128,7 +129,7 @@ class Viewer {
|
||||
? document.getElementById(elementOrId)
|
||||
: elementOrId;
|
||||
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
|
||||
const plugin = await createPluginUI(element, spec);
|
||||
const plugin = await createPluginUI({ target: element, spec, render: renderReact18 });
|
||||
|
||||
(plugin.customState as any) = {
|
||||
colorPalette: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -31,7 +31,8 @@ function shinyStyle(plugin: PluginContext) {
|
||||
postprocessing: {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
occlusion: { name: 'off', params: {} },
|
||||
outline: { name: 'off', params: {} }
|
||||
shadow: { name: 'off', params: {} },
|
||||
outline: { name: 'off', params: {} },
|
||||
}
|
||||
} });
|
||||
}
|
||||
@@ -43,18 +44,13 @@ function occlusionStyle(plugin: PluginContext) {
|
||||
},
|
||||
postprocessing: {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
occlusion: { name: 'on', params: {
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
radius: 5,
|
||||
samples: 32,
|
||||
resolutionScale: 1
|
||||
} },
|
||||
outline: { name: 'on', params: {
|
||||
scale: 1.0,
|
||||
threshold: 0.33,
|
||||
color: Color(0x0000),
|
||||
} }
|
||||
includeTransparent: true,
|
||||
} },
|
||||
shadow: { name: 'off', params: {} },
|
||||
}
|
||||
} });
|
||||
}
|
||||
|
||||
304
src/apps/mesoscale-explorer/app.ts
Normal file
@@ -0,0 +1,304 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
import { createPluginUI } from '../../mol-plugin-ui';
|
||||
import { renderReact18 } from '../../mol-plugin-ui/react18';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants';
|
||||
import { Backgrounds } from '../../extensions/backgrounds';
|
||||
import { LeftPanel, RightPanel } from './ui/panels';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { SpacefillRepresentationProvider } from '../../mol-repr/structure/representation/spacefill';
|
||||
import { PluginBehaviors } from '../../mol-plugin/behavior';
|
||||
import { MesoFocusLoci } from './behavior/camera';
|
||||
import { GraphicsMode, MesoscaleState } from './data/state';
|
||||
import { MesoSelectLoci } from './behavior/select';
|
||||
import { Transparency } from '../../mol-gl/webgl/render-item';
|
||||
import { LoadModel, loadExampleEntry, loadPdb, loadPdbIhm, loadUrl, openState } from './ui/states';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { AnimateCameraSpin } from '../../mol-plugin-state/animation/built-in/camera-spin';
|
||||
import { AnimateCameraRock } from '../../mol-plugin-state/animation/built-in/camera-rock';
|
||||
import { AnimateStateSnapshots } from '../../mol-plugin-state/animation/built-in/state-snapshots';
|
||||
import { MesoViewportSnapshotDescription } from './ui/entities';
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setDebugMode, setProductionMode, setTimingMode, consoleStats } from '../../mol-util/debug';
|
||||
|
||||
export type ExampleEntry = {
|
||||
id: string,
|
||||
label: string,
|
||||
url: string,
|
||||
type: 'molx' | 'molj' | 'cif' | 'bcif',
|
||||
description?: string,
|
||||
link?: string,
|
||||
}
|
||||
|
||||
export type MesoscaleExplorerState = {
|
||||
examples?: ExampleEntry[],
|
||||
graphicsMode: GraphicsMode,
|
||||
illumination: boolean,
|
||||
stateRef?: string,
|
||||
driver?: any,
|
||||
stateCache: { [k: string]: any },
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const Extensions = {
|
||||
'backgrounds': PluginSpec.Behavior(Backgrounds),
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export),
|
||||
};
|
||||
|
||||
const DefaultMesoscaleExplorerOptions = {
|
||||
customFormats: [] as [string, DataFormatProvider][],
|
||||
extensions: ObjectKeys(Extensions),
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: true,
|
||||
layoutShowRemoteState: true,
|
||||
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: true,
|
||||
layoutShowLeftPanel: true,
|
||||
collapseLeftPanel: false,
|
||||
collapseRightPanel: false,
|
||||
disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue,
|
||||
pixelScale: PluginConfig.General.PixelScale.defaultValue,
|
||||
pickScale: PluginConfig.General.PickScale.defaultValue,
|
||||
transparency: 'blended' as Transparency,
|
||||
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
|
||||
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
|
||||
powerPreference: PluginConfig.General.PowerPreference.defaultValue,
|
||||
resolutionMode: PluginConfig.General.ResolutionMode.defaultValue,
|
||||
illumination: false,
|
||||
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
|
||||
viewportShowSelectionMode: true,
|
||||
viewportShowAnimation: false,
|
||||
viewportShowTrajectoryControls: false,
|
||||
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
|
||||
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
|
||||
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
|
||||
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
|
||||
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
|
||||
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
|
||||
|
||||
graphicsMode: 'quality' as GraphicsMode,
|
||||
driver: undefined
|
||||
};
|
||||
type MesoscaleExplorerOptions = typeof DefaultMesoscaleExplorerOptions;
|
||||
|
||||
export class MesoscaleExplorer {
|
||||
constructor(public plugin: PluginUIContext) {
|
||||
}
|
||||
|
||||
async loadExample(id: string) {
|
||||
const entries = (this.plugin.customState as MesoscaleExplorerState).examples || [];
|
||||
const entry = entries.find(e => e.id === id);
|
||||
if (entry !== undefined) {
|
||||
await loadExampleEntry(this.plugin, entry);
|
||||
}
|
||||
}
|
||||
|
||||
async loadUrl(url: string, type: 'molx' | 'molj' | 'cif' | 'bcif') {
|
||||
await loadUrl(this.plugin, url, type);
|
||||
}
|
||||
|
||||
async loadPdb(id: string) {
|
||||
await loadPdb(this.plugin, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Scheduled for removal in v5. Use {@link loadPdbIhm | loadPdbIhm(id: string)} instead.
|
||||
*/
|
||||
async loadPdbDev(id: string) {
|
||||
await this.loadPdbIhm(id);
|
||||
}
|
||||
|
||||
async loadPdbIhm(id: string) {
|
||||
await loadPdbIhm(this.plugin, id);
|
||||
}
|
||||
|
||||
static async create(elementOrId: string | HTMLElement, options: Partial<MesoscaleExplorerOptions> = {}) {
|
||||
const definedOptions = {} as any;
|
||||
// filter for defined properies only so the default values
|
||||
// are property applied
|
||||
for (const p of Object.keys(options) as (keyof MesoscaleExplorerOptions)[]) {
|
||||
if (options[p] !== void 0) definedOptions[p] = options[p];
|
||||
}
|
||||
|
||||
const o: MesoscaleExplorerOptions = { ...DefaultMesoscaleExplorerOptions, ...definedOptions };
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const spec: PluginUISpec = {
|
||||
actions: defaultSpec.actions,
|
||||
behaviors: [
|
||||
PluginSpec.Behavior(PluginBehaviors.Camera.CameraAxisHelper),
|
||||
PluginSpec.Behavior(PluginBehaviors.Camera.CameraControls),
|
||||
|
||||
PluginSpec.Behavior(MesoFocusLoci),
|
||||
PluginSpec.Behavior(MesoSelectLoci),
|
||||
PluginSpec.Behavior(PluginBehaviors.Representation.SelectLoci),
|
||||
...o.extensions.map(e => Extensions[e]),
|
||||
],
|
||||
animations: [
|
||||
AnimateCameraSpin,
|
||||
AnimateCameraRock,
|
||||
AnimateStateSnapshots,
|
||||
],
|
||||
customParamEditors: defaultSpec.customParamEditors,
|
||||
customFormats: o?.customFormats,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: o.layoutIsExpanded,
|
||||
showControls: o.layoutShowControls,
|
||||
controlsDisplay: o.layoutControlsDisplay,
|
||||
regionState: {
|
||||
bottom: 'full',
|
||||
left: o.collapseLeftPanel ? 'collapsed' : 'full',
|
||||
right: o.collapseRightPanel ? 'hidden' : 'full',
|
||||
top: 'full',
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
...defaultSpec.components,
|
||||
controls: {
|
||||
...defaultSpec.components?.controls,
|
||||
top: 'none',
|
||||
bottom: 'none',
|
||||
left: LeftPanel,
|
||||
right: RightPanel,
|
||||
},
|
||||
remoteState: 'none',
|
||||
viewport: {
|
||||
snapshotDescription: MesoViewportSnapshotDescription,
|
||||
}
|
||||
},
|
||||
config: [
|
||||
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
|
||||
[PluginConfig.General.PixelScale, o.pixelScale],
|
||||
[PluginConfig.General.PickScale, o.pickScale],
|
||||
[PluginConfig.General.Transparency, o.transparency],
|
||||
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
|
||||
[PluginConfig.General.AllowMajorPerformanceCaveat, o.allowMajorPerformanceCaveat],
|
||||
[PluginConfig.General.PowerPreference, o.powerPreference],
|
||||
[PluginConfig.General.ResolutionMode, o.resolutionMode],
|
||||
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
|
||||
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
|
||||
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
|
||||
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
|
||||
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
|
||||
[PluginConfig.Viewport.ShowTrajectoryControls, o.viewportShowTrajectoryControls],
|
||||
[PluginConfig.State.DefaultServer, o.pluginStateServer],
|
||||
[PluginConfig.State.CurrentServer, o.pluginStateServer],
|
||||
[PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
|
||||
[PluginConfig.VolumeStreaming.Enabled, !o.volumeStreamingDisabled],
|
||||
[PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
|
||||
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
|
||||
[PluginConfig.Structure.SaccharideCompIdMapType, o.saccharideCompIdMapType],
|
||||
]
|
||||
};
|
||||
|
||||
const element = typeof elementOrId === 'string'
|
||||
? document.getElementById(elementOrId)
|
||||
: elementOrId;
|
||||
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
|
||||
|
||||
const plugin = await createPluginUI({
|
||||
target: element,
|
||||
spec,
|
||||
render: renderReact18,
|
||||
onBeforeUIRender: async plugin => {
|
||||
let examples: MesoscaleExplorerState['examples'] = undefined;
|
||||
try {
|
||||
examples = await plugin.fetch({ url: '../examples/list.json', type: 'json' }).run();
|
||||
// extend the array with file tour.json if it exists
|
||||
const tour = await plugin.fetch({ url: '../examples/tour.json', type: 'json' }).run();
|
||||
if (tour) {
|
||||
examples = examples?.concat(tour);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
(plugin.customState as MesoscaleExplorerState) = {
|
||||
examples,
|
||||
graphicsMode: o.graphicsMode,
|
||||
illumination: o.illumination,
|
||||
driver: o.driver,
|
||||
stateCache: {},
|
||||
};
|
||||
|
||||
await MesoscaleState.init(plugin);
|
||||
}
|
||||
});
|
||||
|
||||
plugin.canvas3d?.setProps({
|
||||
renderer: {
|
||||
backgroundColor: Color(0x101010),
|
||||
},
|
||||
cameraFog: { name: 'off', params: {} },
|
||||
hiZ: { enabled: true },
|
||||
});
|
||||
|
||||
plugin.representation.structure.registry.clear();
|
||||
plugin.representation.structure.registry.add(SpacefillRepresentationProvider);
|
||||
|
||||
plugin.state.setSnapshotParams({
|
||||
image: true,
|
||||
componentManager: false,
|
||||
structureSelection: true,
|
||||
behavior: true,
|
||||
});
|
||||
|
||||
plugin.managers.lociLabels.clearProviders();
|
||||
|
||||
plugin.managers.dragAndDrop.addHandler('mesoscale-explorer', (files) => {
|
||||
const sessions = files.filter(f => {
|
||||
const fn = f.name.toLowerCase();
|
||||
return fn.endsWith('.molx') || fn.endsWith('.molj');
|
||||
});
|
||||
|
||||
if (sessions.length > 0) {
|
||||
openState(plugin, sessions[0]);
|
||||
} else {
|
||||
plugin.runTask(plugin.state.data.applyAction(LoadModel, {
|
||||
files: files.map(f => Asset.File(f)),
|
||||
}));
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
plugin.state.events.object.created.subscribe(e => {
|
||||
(plugin.customState as MesoscaleExplorerState).stateCache = {};
|
||||
});
|
||||
|
||||
plugin.state.events.object.removed.subscribe(e => {
|
||||
(plugin.customState as MesoscaleExplorerState).stateCache = {};
|
||||
});
|
||||
|
||||
return new MesoscaleExplorer(plugin);
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
this.plugin.layout.events.updated.next(void 0);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.plugin.dispose();
|
||||
}
|
||||
}
|
||||
98
src/apps/mesoscale-explorer/behavior/camera.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Loci } from '../../../mol-model/loci';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior';
|
||||
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
|
||||
import { Binding } from '../../../mol-util/binding';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
|
||||
const B = ButtonsType;
|
||||
const M = ModifiersKeys;
|
||||
const Trigger = Binding.Trigger;
|
||||
const Key = Binding.TriggerKey;
|
||||
|
||||
const DefaultMesoFocusLociBindings = {
|
||||
clickCenter: Binding([
|
||||
Trigger(B.Flag.Primary, M.create()),
|
||||
], 'Camera center', 'Click element using ${triggers}'),
|
||||
clickCenterFocus: Binding([
|
||||
Trigger(B.Flag.Secondary, M.create()),
|
||||
], 'Camera center and focus', 'Click element using ${triggers}'),
|
||||
keyCenterOnly: Binding([Key('C')], 'Center Only Toggle', 'Press ${triggers}'),
|
||||
};
|
||||
|
||||
export const MesoFocusLociParams = {
|
||||
minRadius: PD.Numeric(8, { min: 1, max: 50, step: 1 }),
|
||||
extraRadius: PD.Numeric(4, { min: 1, max: 50, step: 1 }, { description: 'Value added to the bounding-sphere radius of the Loci' }),
|
||||
durationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'Camera transition duration' }),
|
||||
centerOnly: PD.Boolean(true, { description: 'Keep current camera distance' }),
|
||||
bindings: PD.Value(DefaultMesoFocusLociBindings, { isHidden: true }),
|
||||
};
|
||||
type MesoFocusLociProps = PD.Values<typeof MesoFocusLociParams>
|
||||
|
||||
export const MesoFocusLoci = PluginBehavior.create<MesoFocusLociProps>({
|
||||
name: 'camera-meso-focus-loci',
|
||||
category: 'interaction',
|
||||
ctor: class extends PluginBehavior.Handler<MesoFocusLociProps> {
|
||||
register(): void {
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, button, modifiers }) => {
|
||||
const { canvas3d } = this.ctx;
|
||||
if (!canvas3d) return;
|
||||
|
||||
const loci = Loci.normalize(current.loci, this.ctx.managers.interactivity.props.granularity);
|
||||
const sphere = Loci.getBoundingSphere(loci) || Sphere3D();
|
||||
|
||||
const { clickCenter, clickCenterFocus } = this.params.bindings;
|
||||
const { durationMs, extraRadius, minRadius, centerOnly } = this.params;
|
||||
const radius = Math.max(sphere.radius + extraRadius, minRadius);
|
||||
|
||||
if (Binding.match(clickCenter, button, modifiers)) {
|
||||
// left mouse button
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
PluginCommands.Camera.Reset(this.ctx, { });
|
||||
return;
|
||||
}
|
||||
if (StructureElement.Loci.is(current.loci)) {
|
||||
if (centerOnly) {
|
||||
const snapshot = canvas3d.camera.getCenter(sphere.center);
|
||||
canvas3d.requestCameraReset({ durationMs, snapshot });
|
||||
} else {
|
||||
this.ctx.managers.camera.focusSphere(sphere, this.params);
|
||||
}
|
||||
}
|
||||
} else if (Binding.match(clickCenterFocus, button, modifiers)) {
|
||||
// right mouse button
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
PluginCommands.Camera.Reset(this.ctx, { });
|
||||
return;
|
||||
}
|
||||
if (centerOnly) {
|
||||
const snapshot = canvas3d.camera.getCenter(sphere.center, radius);
|
||||
canvas3d.requestCameraReset({ durationMs, snapshot });
|
||||
} else {
|
||||
this.ctx.managers.camera.focusSphere(sphere, this.params);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.key, ({ code, key, modifiers }) => {
|
||||
if (!this.ctx.canvas3d) return;
|
||||
const b = { ...DefaultMesoFocusLociBindings, ...this.params.bindings };
|
||||
const { centerOnly } = this.params;
|
||||
|
||||
if (Binding.matchKey(b.keyCenterOnly, code, modifiers, key)) {
|
||||
this.params.centerOnly = !centerOnly;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
params: () => MesoFocusLociParams,
|
||||
display: { name: 'Camera Meso Focus Loci on Canvas' }
|
||||
});
|
||||
208
src/apps/mesoscale-explorer/behavior/select.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { EveryLoci, Loci } from '../../../mol-model/loci';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior';
|
||||
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
|
||||
import { Binding } from '../../../mol-util/binding';
|
||||
import { PluginStateObject as SO } from '../../../mol-plugin-state/objects';
|
||||
import { Structure, StructureElement } from '../../../mol-model/structure';
|
||||
import { StateSelection } from '../../../mol-state';
|
||||
import { StateTreeSpine } from '../../../mol-state/tree/spine';
|
||||
import { Representation } from '../../../mol-repr/representation';
|
||||
import { MarkerAction } from '../../../mol-util/marker-action';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { MesoscaleState, expandAllGroups, getCellDescription, getEveryEntity } from '../data/state';
|
||||
|
||||
const B = ButtonsType;
|
||||
const M = ModifiersKeys;
|
||||
const Trigger = Binding.Trigger;
|
||||
|
||||
const DefaultMesoSelectLociBindings = {
|
||||
click: Binding([
|
||||
Trigger(B.Flag.Primary, M.create())
|
||||
], 'Click', 'Click element using ${triggers}'),
|
||||
clickToggleSelect: Binding([
|
||||
Trigger(B.Flag.Primary, M.create({ shift: true })),
|
||||
Trigger(B.Flag.Primary, M.create({ control: true })),
|
||||
], 'Toggle select', 'Click element using ${triggers}'),
|
||||
hoverHighlightOnly: Binding([
|
||||
Trigger(B.Flag.None, M.create({ shift: true })),
|
||||
Trigger(B.Flag.None, M.create({ control: true })),
|
||||
], 'Highlight', 'Hover element using ${triggers}'),
|
||||
};
|
||||
const MesoSelectLociParams = {
|
||||
bindings: PD.Value(DefaultMesoSelectLociBindings, { isHidden: true }),
|
||||
};
|
||||
type MesoSelectLociProps = PD.Values<typeof MesoSelectLociParams>
|
||||
|
||||
export const MesoSelectLoci = PluginBehavior.create<MesoSelectLociProps>({
|
||||
name: 'camera-meso-select-loci',
|
||||
category: 'interaction',
|
||||
ctor: class extends PluginBehavior.Handler<MesoSelectLociProps> {
|
||||
private spine: StateTreeSpine.Impl;
|
||||
private lociMarkProvider = (interactionLoci: Representation.Loci, action: MarkerAction) => {
|
||||
if (!this.ctx.canvas3d) return;
|
||||
this.ctx.canvas3d.mark(interactionLoci, action);
|
||||
};
|
||||
private applySelectMark(ref: string, clear?: boolean) {
|
||||
const cell = this.ctx.state.data.cells.get(ref);
|
||||
if (cell && SO.isRepresentation3D(cell.obj)) {
|
||||
this.spine.current = cell;
|
||||
const so = this.spine.getRootOfType(SO.Molecule.Structure);
|
||||
if (so) {
|
||||
if (clear) {
|
||||
this.lociMarkProvider({ loci: Structure.Loci(so.data) }, MarkerAction.Deselect);
|
||||
}
|
||||
const loci = this.ctx.managers.structure.selection.getLoci(so.data);
|
||||
this.lociMarkProvider({ loci }, MarkerAction.Select);
|
||||
}
|
||||
}
|
||||
}
|
||||
register(): void {
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, button, modifiers }) => {
|
||||
if (!this.ctx.canvas3d || this.ctx.isBusy) return;
|
||||
|
||||
const { click, clickToggleSelect } = this.params.bindings;
|
||||
if (Binding.match(clickToggleSelect, button, modifiers)) {
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
this.ctx.managers.interactivity.lociSelects.deselectAll();
|
||||
return;
|
||||
}
|
||||
const loci = Loci.normalize(current.loci, modifiers.control ? 'entity' : this.ctx.managers.interactivity.props.granularity);
|
||||
this.ctx.managers.interactivity.lociSelects.toggle({ loci }, false);
|
||||
if (StructureElement.Loci.is(current.loci)) {
|
||||
const cell = this.ctx.helpers.substructureParent.get(current.loci.structure);
|
||||
const d = getCellDescription(cell!);
|
||||
MesoscaleState.set(this.ctx, { focusInfo: `${d}` });
|
||||
}
|
||||
}
|
||||
if (Binding.match(click, button, modifiers)) {
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
MesoscaleState.set(this.ctx, { focusInfo: '', filter: '' });
|
||||
return;
|
||||
}
|
||||
const snapshotKey = current.repr?.props?.snapshotKey?.trim() ?? '';
|
||||
if (snapshotKey) {
|
||||
this.ctx.managers.snapshot.applyKey(snapshotKey);
|
||||
} else {
|
||||
if (StructureElement.Loci.is(current.loci)) {
|
||||
const cell = this.ctx.helpers.substructureParent.get(current.loci.structure);
|
||||
const d = getCellDescription(cell!);
|
||||
MesoscaleState.set(this.ctx, { focusInfo: `${d}`, filter: `${cell?.obj?.label}` });
|
||||
expandAllGroups(this.ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
this.ctx.managers.interactivity.lociSelects.addProvider(this.lociMarkProvider);
|
||||
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.hover, ({ current, button, modifiers }) => {
|
||||
if (!this.ctx.canvas3d || this.ctx.isBusy) return;
|
||||
|
||||
const pointerLock = !!this.ctx.canvas3dContext?.input.pointerLock;
|
||||
const { hoverHighlightOnly } = this.params.bindings;
|
||||
|
||||
if (!pointerLock && Binding.match(hoverHighlightOnly, button, modifiers)) {
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
this.ctx.managers.interactivity.lociHighlights.clearHighlights();
|
||||
return;
|
||||
}
|
||||
if (StructureElement.Loci.is(current.loci)) {
|
||||
if (modifiers.control) {
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci: EveryLoci }, false);
|
||||
} else {
|
||||
const loci = Loci.normalize(current.loci, this.ctx.managers.interactivity.props.granularity);
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci }, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
this.ctx.behaviors.labels.highlight.next({ labels: [] });
|
||||
this.ctx.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
|
||||
} else {
|
||||
const labels: string[] = [];
|
||||
if (StructureElement.Loci.is(current.loci)) {
|
||||
const cell = this.ctx.helpers.substructureParent.get(current.loci.structure);
|
||||
const d = getCellDescription(cell!);
|
||||
labels.push(d);
|
||||
} else {
|
||||
const loci = Loci.normalize(current.loci, this.ctx.managers.interactivity.props.granularity);
|
||||
if (loci.kind === 'group-loci') {
|
||||
if ('shape' in current.loci && current.loci.shape.geometry.kind === 'text') {
|
||||
const qname = current.repr?.props.customText;
|
||||
// highlight protein with same name
|
||||
const entities = getEveryEntity(this.ctx, qname);
|
||||
for (const r of entities) {
|
||||
const repr = r.obj?.data.repr;
|
||||
if (repr) {
|
||||
this.ctx.canvas3d?.mark({ repr, loci: EveryLoci }, MarkerAction.Highlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
labels.push(loci.shape.getLabel(0, 0));
|
||||
}
|
||||
}
|
||||
this.ctx.behaviors.labels.highlight.next({ labels });
|
||||
}
|
||||
});
|
||||
this.ctx.managers.interactivity.lociHighlights.addProvider(this.lociMarkProvider);
|
||||
|
||||
let dimDisabled = false;
|
||||
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.keyReleased, ({ code, modifiers }) => {
|
||||
if (!this.ctx.canvas3d) return;
|
||||
|
||||
if ((code.startsWith('Shift') && !modifiers.control) || (code.startsWith('Control') && !modifiers.shift)) {
|
||||
if (dimDisabled) {
|
||||
dimDisabled = false;
|
||||
this.ctx.canvas3d?.setProps({ renderer: { dimStrength: 1 } }, true);
|
||||
}
|
||||
this.ctx.managers.interactivity.lociHighlights.clearHighlights();
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.key, ({ modifiers }) => {
|
||||
if (!this.ctx.canvas3d) return;
|
||||
|
||||
if (!dimDisabled && modifiers.control && modifiers.shift) {
|
||||
dimDisabled = true;
|
||||
this.ctx.canvas3d?.setProps({ renderer: { dimStrength: 0 } });
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribeObservable(this.ctx.state.events.object.created, ({ ref }) => this.applySelectMark(ref));
|
||||
|
||||
// re-apply select-mark to all representation of an updated structure
|
||||
this.subscribeObservable(this.ctx.state.events.object.updated, ({ ref, obj, oldObj, oldData, action }) => {
|
||||
const cell = this.ctx.state.data.cells.get(ref);
|
||||
if (cell && SO.Molecule.Structure.is(cell.obj)) {
|
||||
const structure: Structure = obj.data;
|
||||
const oldStructure: Structure | undefined = action === 'recreate' ? oldObj?.data :
|
||||
action === 'in-place' ? oldData : undefined;
|
||||
if (oldStructure &&
|
||||
Structure.areEquivalent(structure, oldStructure) &&
|
||||
Structure.areHierarchiesEqual(structure, oldStructure)) return;
|
||||
|
||||
const reprs = this.ctx.state.data.select(StateSelection.children(ref).ofType(SO.Molecule.Structure.Representation3D));
|
||||
for (const repr of reprs) this.applySelectMark(repr.transform.ref, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
unregister() {
|
||||
this.ctx.managers.interactivity.lociSelects.removeProvider(this.lociMarkProvider);
|
||||
this.ctx.managers.interactivity.lociHighlights.removeProvider(this.lociMarkProvider);
|
||||
}
|
||||
constructor(ctx: PluginContext, params: MesoSelectLociProps) {
|
||||
super(ctx, params);
|
||||
this.spine = new StateTreeSpine.Impl(ctx.state.data.cells);
|
||||
}
|
||||
},
|
||||
params: () => MesoSelectLociParams,
|
||||
display: { name: 'Camera Meso Select Loci on Canvas' }
|
||||
});
|
||||
175
src/apps/mesoscale-explorer/data/cellpack/model.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { SortedArray } from '../../../../mol-data/int';
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { Mat4 } from '../../../../mol-math/linear-algebra';
|
||||
import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry';
|
||||
import { CustomStructureProperty } from '../../../../mol-model-props/common/custom-structure-property';
|
||||
import { ElementIndex, EntityIndex, Model, Structure, Unit } from '../../../../mol-model/structure';
|
||||
import { Assembly, Symmetry } from '../../../../mol-model/structure/model/properties/symmetry';
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
|
||||
function createModelChainMap(model: Model) {
|
||||
const builder = new Structure.StructureBuilder();
|
||||
const units = new Map<string, Unit>();
|
||||
|
||||
const { label_asym_id, _rowCount } = model.atomicHierarchy.chains;
|
||||
const { offsets } = model.atomicHierarchy.chainAtomSegments;
|
||||
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
const elements = SortedArray.ofBounds(offsets[i] as ElementIndex, offsets[i + 1] as ElementIndex);
|
||||
const unit = builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements, Unit.Trait.FastBoundary);
|
||||
units.set(label_asym_id.value(i), unit);
|
||||
}
|
||||
|
||||
return units;
|
||||
}
|
||||
|
||||
function buildCellpackAssembly(model: Model, assembly: Assembly) {
|
||||
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } });
|
||||
const assembler = Structure.Builder({
|
||||
coordinateSystem,
|
||||
label: model.label,
|
||||
});
|
||||
|
||||
const units = createModelChainMap(model);
|
||||
|
||||
for (const g of assembly.operatorGroups) {
|
||||
for (const oper of g.operators) {
|
||||
for (const id of g.asymIds!) {
|
||||
const u = units.get(id);
|
||||
if (u) {
|
||||
assembler.addWithOperator(u, oper);
|
||||
} else {
|
||||
console.log(`missing asymId '${id}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return assembler.getStructure();
|
||||
}
|
||||
|
||||
export { CellpackAssembly };
|
||||
type CellpackAssembly = typeof CellpackAssembly
|
||||
const CellpackAssembly = PluginStateTransform.BuiltIn({
|
||||
name: 'cellpack-assembly',
|
||||
display: { name: 'Cellpack Assembly' },
|
||||
from: PSO.Molecule.Model,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: {
|
||||
id: PD.Text('', { label: 'Asm Id', description: 'Assembly Id (use empty for the 1st assembly)' }),
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
const model = a.data;
|
||||
|
||||
let id = params.id;
|
||||
let asm: Assembly | undefined = void 0;
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
|
||||
// if no id is specified, use the 1st assembly.
|
||||
if (!id && symmetry && symmetry.assemblies.length !== 0) {
|
||||
id = symmetry.assemblies[0].id;
|
||||
}
|
||||
|
||||
if (!symmetry || symmetry.assemblies.length === 0) {
|
||||
plugin.log.warn(`Model '${model.entryId}' has no assembly, returning model structure.`);
|
||||
} else {
|
||||
asm = Symmetry.findAssembly(model, id || '');
|
||||
if (!asm) {
|
||||
plugin.log.warn(`Model '${model.entryId}' has no assembly called '${id}', returning model structure.`);
|
||||
}
|
||||
}
|
||||
|
||||
const base = Structure.ofModel(model);
|
||||
if (!asm) {
|
||||
const label = { label: 'Model', description: Structure.elementDescription(base) };
|
||||
return new PSO.Molecule.Structure(base, label);
|
||||
}
|
||||
|
||||
const s = buildCellpackAssembly(model, asm);
|
||||
|
||||
const objProps = { label: `Assembly ${id}`, description: Structure.elementDescription(s) };
|
||||
return new PSO.Molecule.Structure(s, objProps);
|
||||
});
|
||||
},
|
||||
dispose({ b }) {
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
type UnitsByEntity = Map<EntityIndex, Unit[]>;
|
||||
const UnitsByEntity = CustomStructureProperty.createSimple<UnitsByEntity>('units_by_entity', 'root');
|
||||
|
||||
function getUnitsByEntity(structure: Structure): UnitsByEntity {
|
||||
if (UnitsByEntity.get(structure).value) {
|
||||
return UnitsByEntity.get(structure).value!;
|
||||
}
|
||||
|
||||
const atomicIndex = structure.model.atomicHierarchy.index;
|
||||
const map: UnitsByEntity = new Map();
|
||||
for (const ug of structure.unitSymmetryGroups) {
|
||||
const u = ug.units[0] as Unit.Atomic;
|
||||
const e = atomicIndex.getEntityFromChain(u.chainIndex[u.elements[0]]);
|
||||
|
||||
if (!map.has(e)) map.set(e, []);
|
||||
const entityUnits = map.get(e)!;
|
||||
|
||||
for (let i = 0, il = ug.units.length; i < il; ++i) {
|
||||
entityUnits.push(ug.units[i]);
|
||||
}
|
||||
}
|
||||
|
||||
UnitsByEntity.set(structure, { value: map }, map);
|
||||
return map;
|
||||
}
|
||||
|
||||
export { CellpackStructure };
|
||||
type CellpackStructure = typeof CellpackStructure
|
||||
const CellpackStructure = PluginStateTransform.BuiltIn({
|
||||
name: 'cellpack-structure',
|
||||
display: { name: 'Cellpack Structure' },
|
||||
from: PSO.Root,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: {
|
||||
structureRef: PD.Text(''),
|
||||
entityId: PD.Text('')
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params, dependencies }) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
const parent = dependencies![params.structureRef].data as Structure;
|
||||
const { entities } = parent.model;
|
||||
const idx = entities.getEntityIndex(params.entityId);
|
||||
|
||||
const unitsByEntity = getUnitsByEntity(parent);
|
||||
const units = unitsByEntity.get(idx) || [];
|
||||
|
||||
const structure = Structure.create(units);
|
||||
const description_label = entities.data.pdbx_description.value(idx)[0] || 'model';
|
||||
const label = description_label.split('.').at(-1) || a.label;
|
||||
const description = entities.data.pdbx_parent_entity_id.value(idx) || label;
|
||||
return new PSO.Molecule.Structure(structure, { label, description: description }); // `${a.description}`
|
||||
});
|
||||
},
|
||||
dispose({ b }) {
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
227
src/apps/mesoscale-explorer/data/cellpack/preset.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Ludovic Autin <ludovic.autin@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginStateObject } from '../../../../mol-plugin-state/objects';
|
||||
import { StructureRepresentation3D } from '../../../../mol-plugin-state/transforms/representation';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/representation/spacefill';
|
||||
import { StateObjectRef, StateObjectSelector, StateBuilder } from '../../../../mol-state';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { ColorNames } from '../../../../mol-util/color/names';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getDistinctGroupColors, getGraphicsModeProps, getMesoscaleGroupParams } from '../state';
|
||||
import { CellpackAssembly, CellpackStructure } from './model';
|
||||
|
||||
function getSpacefillParams(color: Color, sizeFactor: number, graphics: GraphicsMode, merge?: boolean) {
|
||||
const gmp = getGraphicsModeProps(graphics === 'custom' ? 'quality' : graphics);
|
||||
return {
|
||||
type: {
|
||||
name: 'spacefill',
|
||||
params: {
|
||||
...SpacefillRepresentationProvider.defaultValues,
|
||||
ignoreHydrogens: false,
|
||||
instanceGranularity: true,
|
||||
ignoreLight: true,
|
||||
lodLevels: gmp.lodLevels,
|
||||
quality: 'lowest', // avoid 'auto', triggers boundary calc
|
||||
sizeFactor,
|
||||
clip: {
|
||||
variant: merge ? 'pixel' : 'instance',
|
||||
objects: [],
|
||||
},
|
||||
clipPrimitive: true,
|
||||
approximate: gmp.approximate,
|
||||
alphaThickness: gmp.alphaThickness,
|
||||
visuals: [merge ? 'structure-element-sphere' : 'element-sphere'],
|
||||
},
|
||||
},
|
||||
colorTheme: {
|
||||
name: 'uniform',
|
||||
params: {
|
||||
value: color,
|
||||
saturation: 0,
|
||||
lightness: 0,
|
||||
}
|
||||
},
|
||||
sizeTheme: {
|
||||
name: 'physical',
|
||||
params: {
|
||||
value: 1,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getSizeFactor(name: string): number {
|
||||
switch (name) {
|
||||
case 'dLDL':
|
||||
return 2.5;
|
||||
case 'iLDL':
|
||||
return 5;
|
||||
case 'NP_CA':
|
||||
case 'POL_CA':
|
||||
case 'FactorH1':
|
||||
case 'iIgM_Antibody_5mer':
|
||||
// case 'MG_271_272_273_274_192MER': // has a coarse and an atomic part
|
||||
return 2;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createCellpackHierarchy(plugin: PluginContext, trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory>) {
|
||||
const builder = plugin.builders.structure;
|
||||
const state = plugin.state.data;
|
||||
|
||||
const model = await builder.createModel(trajectory, { modelIndex: 0 });
|
||||
const entities = model.data!.entities.data;
|
||||
|
||||
const compGroups = new Map<string, StateObjectSelector>();
|
||||
const compIds = new Map<string, { idx: number, members: Map<string, number> }>();
|
||||
const compColors = new Map<string, Color[]>();
|
||||
|
||||
const funcGroups = new Map<string, StateObjectSelector>();
|
||||
const funcIds = new Map<string, { idx: number, size: number }>();
|
||||
const funcColors = new Map<string, Color[]>();
|
||||
|
||||
const graphicsMode = MesoscaleState.get(plugin).graphics;
|
||||
const groupParams = getMesoscaleGroupParams(graphicsMode);
|
||||
|
||||
const base = await state.build()
|
||||
.to(model)
|
||||
.apply(CellpackAssembly, { id: '' })
|
||||
.commit();
|
||||
|
||||
const compRoot = await state.build()
|
||||
.toRoot()
|
||||
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: 'comp:', label: 'compartment', color: { type: 'custom', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:comp:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
const funcRoot = await state.build()
|
||||
.toRoot()
|
||||
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: 'func:', label: 'function', color: { type: 'custom', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:func:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
if (entities._rowCount > 1) {
|
||||
for (let i = 0; i < entities._rowCount; i++) {
|
||||
const description = entities.pdbx_description.value(i)[0] || 'unknown compartment';
|
||||
const d = description.split('.');
|
||||
const n = d.slice(0, -1).join('.');
|
||||
const l = d.at(-1)!;
|
||||
if (!compIds.has(n)) {
|
||||
compIds.set(n, { idx: compIds.size, members: new Map() });
|
||||
}
|
||||
const cm = compIds.get(n)!;
|
||||
cm.members.set(l, cm.members.size);
|
||||
|
||||
const f = entities.details.value(i) || 'unknown function';
|
||||
if (!funcIds.has(f)) {
|
||||
funcIds.set(f, { idx: funcIds.size, size: 0 });
|
||||
}
|
||||
funcIds.get(f)!.size += 1;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const baseCompColors = getDistinctBaseColors(compIds.size, 0);
|
||||
const compIdEntries = Array.from(compIds.entries());
|
||||
for (let i = 0; i < compIdEntries.length; ++i) {
|
||||
const [n, m] = compIdEntries[i];
|
||||
const groupColors = getDistinctGroupColors(m.members.size, baseCompColors[i], 20, 0);
|
||||
compColors.set(n, groupColors);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const baseFuncColors = getDistinctBaseColors(funcIds.size, 0);
|
||||
const funcIdEntries = Array.from(funcIds.entries());
|
||||
for (let i = 0; i < funcIdEntries.length; ++i) {
|
||||
const [n, m] = funcIdEntries[i];
|
||||
const groupColors = getDistinctGroupColors(m.size, baseFuncColors[i], 20, 0);
|
||||
funcColors.set(n, groupColors);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
for (let i = 0; i < entities._rowCount; i++) {
|
||||
const description = entities.pdbx_description.value(i)[0] || 'unknown compartment';
|
||||
const nodes = description.split('.');
|
||||
for (let j = 0, jl = nodes.length - 1; j < jl; ++j) {
|
||||
const n = nodes.slice(0, j + 1).join('.');
|
||||
const p = nodes.slice(0, j).join('.');
|
||||
if (!compGroups.has(n)) {
|
||||
const colorIdx = compIds.get(n)?.idx;
|
||||
const color = colorIdx !== undefined ? baseCompColors[colorIdx] : ColorNames.white;
|
||||
const label = nodes[j];
|
||||
const parent = compGroups.get(p) ?? compRoot;
|
||||
parent.cell!.state.isCollapsed = false;
|
||||
const group = await state.build()
|
||||
.to(parent)
|
||||
.apply(MesoscaleGroup, { ...groupParams, root: parent === compRoot, index: colorIdx, tag: `comp:${n}`, label, color: { type: 'generate', illustrative: false, value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: [`group:comp:${n}`, `comp:${p}`], state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit({ revertOnError: true });
|
||||
compGroups.set(n, group);
|
||||
}
|
||||
}
|
||||
|
||||
const f = entities.details.value(i) || 'unknown function';
|
||||
if (!funcGroups.has(f)) {
|
||||
const colorIdx = funcIds.get(f)?.idx;
|
||||
const color = colorIdx !== undefined ? baseFuncColors[colorIdx] : ColorNames.white;
|
||||
const group = await state.build()
|
||||
.to(funcRoot)
|
||||
.apply(MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `func:${f}`, label: f, color: { type: 'custom', illustrative: false, value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: [`group:func:${f}`, 'func:'], state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit({ revertOnError: true });
|
||||
funcGroups.set(f, group);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
await state.transaction(async () => {
|
||||
try {
|
||||
const dependsOn = [base.ref];
|
||||
plugin.animationLoop.stop({ noDraw: true });
|
||||
let build: StateBuilder.Root | StateBuilder.To<any> = state.build();
|
||||
for (let i = 0; i < entities._rowCount; i++) {
|
||||
const description = entities.pdbx_description.value(i)[0] || 'model';
|
||||
const d = description.split('.');
|
||||
const n = d.slice(0, -1).join('.');
|
||||
const l = d.at(-1)!;
|
||||
|
||||
const f = entities.details.value(i) || 'unknown function';
|
||||
|
||||
const color = compColors.get(n)![compIds.get(n)!.members.get(l)!];
|
||||
const sizeFactor = getSizeFactor(l);
|
||||
|
||||
build = build
|
||||
.toRoot()
|
||||
.apply(CellpackStructure, { structureRef: base.ref, entityId: entities.id.value(i) }, { dependsOn })
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(color, sizeFactor, graphicsMode), { tags: [`comp:${n}`, `func:${f}`] });
|
||||
}
|
||||
await build.commit();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(e);
|
||||
} finally {
|
||||
plugin.animationLoop.start();
|
||||
}
|
||||
}).run();
|
||||
} else {
|
||||
const dependsOn = [base.ref];
|
||||
|
||||
const merge = (
|
||||
base.data &&
|
||||
base.data.model.entities.data._rowCount === 1 &&
|
||||
base.data.unitSymmetryGroups.length > 100 &&
|
||||
base.data.unitSymmetryGroups.some(usg => usg.units.length > 1)
|
||||
);
|
||||
|
||||
await state.build()
|
||||
.toRoot()
|
||||
.apply(CellpackStructure, { structureRef: base.ref, entityId: entities.id.value(0) }, { dependsOn })
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(ColorNames.lightgray, 1, graphicsMode, merge), { tags: [`comp:`, `func:`] })
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
140
src/apps/mesoscale-explorer/data/generic/model.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mat4 } from '../../../../mol-math/linear-algebra/3d/mat4';
|
||||
import { ElementIndex, Model, Structure, Unit } from '../../../../mol-model/structure';
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { StateObject, StateTransformer } from '../../../../mol-state';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { mergeUnits, partitionUnits } from '../util';
|
||||
import { Assembly, Symmetry } from '../../../../mol-model/structure/model/properties/symmetry';
|
||||
import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry';
|
||||
import { SortedArray } from '../../../../mol-data/int';
|
||||
import { GenericInstances, getTransforms } from './preset';
|
||||
import { Asset } from '../../../../mol-util/assets';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { deepEqual } from '../../../../mol-util';
|
||||
|
||||
function createModelChainMap(model: Model) {
|
||||
const builder = new Structure.StructureBuilder();
|
||||
const units = new Map<string, Unit>();
|
||||
|
||||
const { label_asym_id, _rowCount } = model.atomicHierarchy.chains;
|
||||
const { offsets } = model.atomicHierarchy.chainAtomSegments;
|
||||
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
const elements = SortedArray.ofBounds(offsets[i] as ElementIndex, offsets[i + 1] as ElementIndex);
|
||||
const unit = builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements, Unit.Trait.FastBoundary);
|
||||
units.set(label_asym_id.value(i), unit);
|
||||
}
|
||||
|
||||
return units;
|
||||
}
|
||||
|
||||
function buildAssembly(model: Model, assembly: Assembly) {
|
||||
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } });
|
||||
const assembler = Structure.Builder({
|
||||
coordinateSystem,
|
||||
label: model.label,
|
||||
});
|
||||
|
||||
const units = createModelChainMap(model);
|
||||
|
||||
for (const g of assembly.operatorGroups) {
|
||||
for (const oper of g.operators) {
|
||||
for (const id of g.asymIds!) {
|
||||
const u = units.get(id);
|
||||
if (u) {
|
||||
assembler.addWithOperator(u, oper);
|
||||
} else {
|
||||
console.log(`missing asymId '${id}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return assembler.getStructure();
|
||||
}
|
||||
|
||||
const EmptyInstances: GenericInstances<Asset> = {
|
||||
positions: { data: [] },
|
||||
rotations: { variant: 'euler', data: [] }
|
||||
};
|
||||
|
||||
export { StructureFromGeneric };
|
||||
type StructureFromGeneric = typeof StructureFromGeneric
|
||||
const StructureFromGeneric = PluginStateTransform.BuiltIn({
|
||||
name: 'structure-from-generic',
|
||||
display: { name: 'Structure from Generic', description: 'Create a molecular structure from Generic models.' },
|
||||
from: SO.Molecule.Model,
|
||||
to: SO.Molecule.Structure,
|
||||
params: {
|
||||
instances: PD.Value<GenericInstances<Asset>>(EmptyInstances),
|
||||
label: PD.Optional(PD.Text('')),
|
||||
description: PD.Optional(PD.Text('')),
|
||||
cellSize: PD.Numeric(500, { min: 0, max: 10000, step: 100 }),
|
||||
}
|
||||
})({
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
const transforms = await getTransforms(plugin, params.instances);
|
||||
if (transforms.length === 0) return StateObject.Null;
|
||||
|
||||
const model = a.data;
|
||||
const label = params.label || model.label;
|
||||
|
||||
const base = Structure.ofModel(a.data);
|
||||
|
||||
let structure: Structure;
|
||||
if (transforms.length === 1 && Mat4.isIdentity(transforms[0])) {
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
const id = symmetry?.assemblies[0]?.id;
|
||||
const asm = Symmetry.findAssembly(model, id || '');
|
||||
if (asm) {
|
||||
structure = buildAssembly(model, asm);
|
||||
} else {
|
||||
const mergedUnits = partitionUnits(base.units, params.cellSize);
|
||||
structure = Structure.create(mergedUnits, { label });
|
||||
}
|
||||
} else {
|
||||
const assembler = Structure.Builder({ label });
|
||||
const unit = mergeUnits(base.units, 0);
|
||||
for (let i = 0, il = transforms.length; i < il; ++i) {
|
||||
const t = transforms[i];
|
||||
const op = SymmetryOperator.create(`op-${i}`, t);
|
||||
assembler.addWithOperator(unit, op);
|
||||
}
|
||||
structure = assembler.getStructure();
|
||||
}
|
||||
|
||||
const props = { label, description: params.description || Structure.elementDescription(structure) };
|
||||
return new SO.Molecule.Structure(structure, props);
|
||||
});
|
||||
},
|
||||
update({ newParams, oldParams }, plugin: PluginContext) {
|
||||
if (deepEqual(newParams, oldParams)) {
|
||||
return StateTransformer.UpdateResult.Unchanged;
|
||||
}
|
||||
|
||||
if (oldParams.instances) releaseInstances(plugin, oldParams.instances);
|
||||
return StateTransformer.UpdateResult.Recreate;
|
||||
},
|
||||
dispose({ b, params }, plugin: PluginContext) {
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
if (params?.instances) releaseInstances(plugin, params.instances);
|
||||
}
|
||||
});
|
||||
|
||||
function releaseInstances(plugin: PluginContext, instances: GenericInstances<Asset>) {
|
||||
if (!Array.isArray(instances.positions.data)) {
|
||||
plugin.managers.asset.release(instances.positions.data.file);
|
||||
}
|
||||
if (!Array.isArray(instances.rotations.data)) {
|
||||
plugin.managers.asset.release(instances.rotations.data.file);
|
||||
}
|
||||
}
|
||||
580
src/apps/mesoscale-explorer/data/generic/preset.ts
Normal file
@@ -0,0 +1,580 @@
|
||||
/**
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mat4 } from '../../../../mol-math/linear-algebra/3d/mat4';
|
||||
import { StateBuilder, StateObjectSelector } from '../../../../mol-state';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/representation/spacefill';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { utf8Read } from '../../../../mol-io/common/utf8';
|
||||
import { Mat3, Quat, Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getGraphicsModeProps, getMesoscaleGroupParams } from '../state';
|
||||
import { ColorNames } from '../../../../mol-util/color/names';
|
||||
import { ShapeRepresentation3D, StructureRepresentation3D } from '../../../../mol-plugin-state/transforms/representation';
|
||||
import { ParseCif, ParsePly, ReadFile } from '../../../../mol-plugin-state/transforms/data';
|
||||
import { ModelFromTrajectory, ShapeFromPly, TrajectoryFromGRO, TrajectoryFromMOL, TrajectoryFromMOL2, TrajectoryFromMmCif, TrajectoryFromPDB, TrajectoryFromSDF, TrajectoryFromXYZ } from '../../../../mol-plugin-state/transforms/model';
|
||||
import { Euler } from '../../../../mol-math/linear-algebra/3d/euler';
|
||||
import { Asset } from '../../../../mol-util/assets';
|
||||
import { Clip } from '../../../../mol-util/clip';
|
||||
import { StructureFromGeneric } from './model';
|
||||
import { getFileNameInfo } from '../../../../mol-util/file-info';
|
||||
import { NumberArray } from '../../../../mol-util/type-helpers';
|
||||
import { BaseGeometry } from '../../../../mol-geo/geometry/base';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
|
||||
function getSpacefillParams(color: Color, sizeFactor: number, graphics: GraphicsMode, clipVariant: Clip.Variant) {
|
||||
const gmp = getGraphicsModeProps(graphics === 'custom' ? 'quality' : graphics);
|
||||
return {
|
||||
type: {
|
||||
name: 'spacefill',
|
||||
params: {
|
||||
...SpacefillRepresentationProvider.defaultValues,
|
||||
ignoreHydrogens: true,
|
||||
instanceGranularity: true,
|
||||
ignoreLight: true,
|
||||
lodLevels: gmp.lodLevels.map(l => {
|
||||
return {
|
||||
...l,
|
||||
stride: Math.max(1, Math.round(l.stride / Math.pow(sizeFactor, l.scaleBias)))
|
||||
};
|
||||
}),
|
||||
quality: 'lowest', // avoid 'auto', triggers boundary calc
|
||||
sizeFactor,
|
||||
clip: {
|
||||
variant: clipVariant,
|
||||
objects: [],
|
||||
},
|
||||
clipPrimitive: true,
|
||||
approximate: gmp.approximate,
|
||||
alphaThickness: gmp.alphaThickness,
|
||||
},
|
||||
},
|
||||
colorTheme: {
|
||||
name: 'uniform',
|
||||
params: {
|
||||
value: color,
|
||||
saturation: 0,
|
||||
lightness: 0,
|
||||
}
|
||||
},
|
||||
sizeTheme: {
|
||||
name: 'physical',
|
||||
params: {
|
||||
scale: 1,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getPlyShapeParams(color: Color, clipVariant: Clip.Variant) {
|
||||
return {
|
||||
...PD.getDefaultValues(BaseGeometry.Params),
|
||||
instanceGranularity: true,
|
||||
ignoreLight: true,
|
||||
clip: {
|
||||
variant: clipVariant,
|
||||
objects: [],
|
||||
},
|
||||
quality: 'custom',
|
||||
doubleSided: true,
|
||||
coloring: {
|
||||
name: 'uniform',
|
||||
params: { color }
|
||||
},
|
||||
grouping: {
|
||||
name: 'none',
|
||||
params: {}
|
||||
},
|
||||
material: {
|
||||
metalness: 0.0,
|
||||
roughness: 1.0,
|
||||
bumpiness: 1.0,
|
||||
},
|
||||
bumpAmplitude: 0.1,
|
||||
bumpFrequency: 0.1 / 10,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createGenericHierarchy(plugin: PluginContext, file: Asset.File) {
|
||||
const asset = await plugin.runTask(plugin.managers.asset.resolve(file, 'zip'));
|
||||
let manifest: GenericManifest;
|
||||
// TODO: remove special handling for martini prototype
|
||||
if (asset.data['instanced_structure.json']) {
|
||||
const d = asset.data['instanced_structure.json'];
|
||||
const t = utf8Read(d, 0, d.length);
|
||||
const martini = JSON.parse(t) as { model: string, positions: Vec3[], rotations: Vec3[], function: string }[];
|
||||
console.log(martini);
|
||||
manifest = martiniToGeneric(martini);
|
||||
} else if (asset.data['manifest.json']) {
|
||||
const d = asset.data['manifest.json'];
|
||||
const t = utf8Read(d, 0, d.length);
|
||||
manifest = JSON.parse(t) as GenericManifest;
|
||||
} else {
|
||||
throw new Error('no manifest found');
|
||||
}
|
||||
console.log(manifest);
|
||||
|
||||
const state = plugin.state.data;
|
||||
const graphicsMode = MesoscaleState.get(plugin).graphics;
|
||||
const groupParams = getMesoscaleGroupParams(graphicsMode);
|
||||
|
||||
async function addGroup(g: GenericGroup, cell: StateObjectSelector, parent: string) {
|
||||
const group = await state.build()
|
||||
.to(cell)
|
||||
.apply(MesoscaleGroup, { ...groupParams, index: undefined, tag: `${g.root}:${g.id}`, label: g.label || g.id, description: g.description, color: { type: 'custom', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: [`group:${g.root}:${g.id}`, g.root === parent ? `${g.root}:` : `${g.root}:${parent}`], state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
if (g.children) {
|
||||
for (const c of g.children) {
|
||||
await addGroup(c, group, g.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const r of manifest.roots) {
|
||||
const root = await state.build()
|
||||
.toRoot()
|
||||
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `${r.id}:`, label: r.label || r.id, description: r.description, color: { type: 'custom', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `group:${r.id}:`, state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
if (r.children) {
|
||||
for (const c of r.children!) {
|
||||
await addGroup(c, root, r.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const transformAssets = new Map<string, Asset>();
|
||||
const getTransformAsset = (file: string) => {
|
||||
if (!transformAssets.has(file)) {
|
||||
const d = asset.data[file];
|
||||
transformAssets.set(file, Asset.File(new File([d], file)));
|
||||
}
|
||||
return transformAssets.get(file)!;
|
||||
};
|
||||
|
||||
const getAssetInstances = (instances: GenericInstances<string>): GenericInstances<Asset> => {
|
||||
return {
|
||||
positions: {
|
||||
data: Array.isArray(instances.positions.data)
|
||||
? instances.positions.data
|
||||
: {
|
||||
file: getTransformAsset(instances.positions.data.file),
|
||||
view: instances.positions.data.view,
|
||||
},
|
||||
type: instances.positions.type,
|
||||
},
|
||||
rotations: {
|
||||
data: Array.isArray(instances.rotations.data)
|
||||
? instances.rotations.data
|
||||
: {
|
||||
file: getTransformAsset(instances.rotations.data.file),
|
||||
view: instances.rotations.data.view,
|
||||
},
|
||||
variant: instances.rotations.variant,
|
||||
type: instances.rotations.type,
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
await state.transaction(async () => {
|
||||
try {
|
||||
plugin.animationLoop.stop({ noDraw: true });
|
||||
let build: StateBuilder.Root | StateBuilder.To<any> = state.build();
|
||||
for (const ent of manifest.entities) {
|
||||
const d = asset.data[ent.file];
|
||||
const info = getFileNameInfo(ent.file);
|
||||
const isBinary = ['bcif'].includes(info.ext);
|
||||
|
||||
const t = isBinary ? d : utf8Read(d, 0, d.length);
|
||||
const file = Asset.File(new File([t], ent.file));
|
||||
|
||||
const color = (ent.color) ? Color.fromRgb(ent.color[0], ent.color[1], ent.color[2]) : ColorNames.skyblue;
|
||||
|
||||
const sizeFactor = ent.sizeFactor || 1;
|
||||
const tags = ent.groups.map(({ id, root }) => `${root}:${id}`);
|
||||
const instances = ent.instances && getAssetInstances(ent.instances);
|
||||
const description = ent.description;
|
||||
|
||||
const label = ent.label || ent.file.split('.')[0];
|
||||
build = build
|
||||
.toRoot()
|
||||
.apply(ReadFile, { file, label, isBinary });
|
||||
|
||||
if (['gro', 'cif', 'mmcif', 'mcif', 'bcif', 'pdb', 'ent', 'xyz', 'mol', 'sdf', 'sd', 'mol2'].includes(info.ext)) {
|
||||
if (['gro'].includes(info.ext)) {
|
||||
build = build.apply(TrajectoryFromGRO);
|
||||
} else if (['cif', 'mmcif', 'mcif', 'bcif'].includes(info.ext)) {
|
||||
build = build.apply(ParseCif).apply(TrajectoryFromMmCif);
|
||||
} else if (['pdb', 'ent'].includes(info.ext)) {
|
||||
build = build.apply(TrajectoryFromPDB);
|
||||
} else if (['xyz'].includes(info.ext)) {
|
||||
build = build.apply(TrajectoryFromXYZ);
|
||||
} else if (['mol'].includes(info.ext)) {
|
||||
build = build.apply(TrajectoryFromMOL);
|
||||
} else if (['sdf', 'sd'].includes(info.ext)) {
|
||||
build = build.apply(TrajectoryFromSDF);
|
||||
} else if (['mol2'].includes(info.ext)) {
|
||||
build = build.apply(TrajectoryFromMOL2);
|
||||
}
|
||||
|
||||
let clipVariant: Clip.Variant = 'pixel';
|
||||
if (ent.instances) {
|
||||
if (Array.isArray(ent.instances.positions.data)) {
|
||||
clipVariant = ent.instances.positions.data.length <= 3 ? 'pixel' : 'instance';
|
||||
} else {
|
||||
const byteLength = ent.instances.positions.data.view
|
||||
? ent.instances.positions.data.view.byteLength
|
||||
: asset.data[ent.instances.positions.data.file].length;
|
||||
clipVariant = byteLength <= 3 * 4 ? 'pixel' : 'instance';
|
||||
}
|
||||
}
|
||||
|
||||
build = build
|
||||
.apply(ModelFromTrajectory, { modelIndex: 0 })
|
||||
.apply(StructureFromGeneric, { instances, label, description })
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(color, sizeFactor, graphicsMode, clipVariant), { tags });
|
||||
} else if (['ply'].includes(info.ext)) {
|
||||
if (['ply'].includes(info.ext)) {
|
||||
const transforms = await getTransforms(plugin, instances);
|
||||
const clipVariant = transforms.length === 1 ? 'pixel' : 'instance';
|
||||
build = build
|
||||
.apply(ParsePly)
|
||||
.apply(ShapeFromPly, { label, transforms })
|
||||
.apply(ShapeRepresentation3D, getPlyShapeParams(color, clipVariant), { tags });
|
||||
}
|
||||
} else {
|
||||
console.warn(`unknown file format '${info.ext}'`);
|
||||
}
|
||||
}
|
||||
await build.commit();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(e);
|
||||
} finally {
|
||||
plugin.animationLoop.start();
|
||||
}
|
||||
}).run();
|
||||
|
||||
asset.dispose();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type GenericRoot = {
|
||||
id: string
|
||||
label?: string
|
||||
description?: string
|
||||
children: GenericGroup[]
|
||||
}
|
||||
|
||||
type GenericGroup = {
|
||||
id: string
|
||||
/** reference to `${GenericRoot.id}` */
|
||||
root: string
|
||||
label?: string
|
||||
description?: string
|
||||
children?: GenericGroup[]
|
||||
}
|
||||
|
||||
type GenericEntity = {
|
||||
/**
|
||||
* the entity file name
|
||||
*
|
||||
* the following extensions/formats are supported
|
||||
*
|
||||
* structures
|
||||
* - gro
|
||||
* - cif, mmcif, mcif, bcif
|
||||
* - pdb, ent
|
||||
* - xyz
|
||||
* - mol
|
||||
* - sdf, sd
|
||||
* - mol2
|
||||
*
|
||||
* meshes
|
||||
* - ply
|
||||
*/
|
||||
file: string
|
||||
label?: string
|
||||
description?: string
|
||||
color?: number[]
|
||||
groups: {
|
||||
/** reference to `${GenericGroup.id}` */
|
||||
id: string,
|
||||
/** reference to `${GenericGroup.root}` */
|
||||
root: string
|
||||
}[]
|
||||
/**
|
||||
* defaults to a single, untransformed instance
|
||||
*/
|
||||
instances?: GenericInstances<string>
|
||||
/**
|
||||
* defaults to 1 (assuming fully atomic structures)
|
||||
* for C-alpha only structures set to 2
|
||||
* for Martini coarse-grained set to 1.5
|
||||
*/
|
||||
sizeFactor?: number
|
||||
}
|
||||
|
||||
type BinaryData<T extends string | Asset> = {
|
||||
file: T,
|
||||
view?: {
|
||||
byteOffset: number,
|
||||
byteLength: number
|
||||
}
|
||||
}
|
||||
|
||||
export type GenericInstances<T extends string | Asset> = {
|
||||
/**
|
||||
* translation vectors in Angstrom
|
||||
* [x0, y0, z0, ..., xn, yn, zn] with n = count - 1
|
||||
*/
|
||||
positions: {
|
||||
/**
|
||||
* either the data itself or a pointer to binary data
|
||||
*/
|
||||
data: number[] | BinaryData<T>
|
||||
/**
|
||||
* how to interpret the data
|
||||
* defaults to `{ kind: 'Array', type: 'Float32' }`
|
||||
*/
|
||||
type?: { kind: 'Array', type: 'Float32' }
|
||||
// TODO: maybe worthwhile in the future, mirroring encoders from BinaryCIF
|
||||
// | { kind: 'IntegerPackedFixedPoint', byteCount: number, srcSize: number, factor: number, srcType: 'Float32' }
|
||||
}
|
||||
/**
|
||||
* euler angles in XYZ order
|
||||
* [x0, y0, z0, ..., xn, yn, zn] with n = count - 1
|
||||
*
|
||||
* quaternion rotations in XYZW order
|
||||
* [x0, y0, z0, w0, ..., xn, yn, zn, wn] with n = count - 1
|
||||
*
|
||||
* rotation matrices in row-major order
|
||||
* [m00_0, m01_0, m02_0, ..., m20_n, m21_n, m22_n] with n = count - 1
|
||||
*/
|
||||
rotations: {
|
||||
variant: 'euler' | 'quaternion' | 'matrix',
|
||||
/**
|
||||
* either the data itself or a pointer to binary data
|
||||
*/
|
||||
data: number[] | BinaryData<T>
|
||||
/**
|
||||
* how to interpret the data
|
||||
* defaults to `{ kind: 'Array', type: 'Float32' }`
|
||||
*/
|
||||
type?: { kind: 'Array', type: 'Float32' }
|
||||
}
|
||||
}
|
||||
|
||||
type GenericFrame = {
|
||||
time: number
|
||||
entities: {
|
||||
file: string
|
||||
instances: GenericInstances<string>
|
||||
}[]
|
||||
}
|
||||
|
||||
type GenericTrajectory = {
|
||||
label?: string
|
||||
description?: string
|
||||
frames: GenericFrame[]
|
||||
}
|
||||
|
||||
type GenericManifest = {
|
||||
label?: string
|
||||
description?: string
|
||||
roots: GenericRoot[]
|
||||
entities: GenericEntity[]
|
||||
trajectories?: GenericTrajectory[]
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const p = Vec3();
|
||||
const q = Quat();
|
||||
const m = Mat3();
|
||||
const e = Euler();
|
||||
|
||||
async function getPositions(plugin: PluginContext, p: GenericInstances<Asset>['positions']): Promise<NumberArray> {
|
||||
if (Array.isArray(p.data)) {
|
||||
return p.data;
|
||||
} else {
|
||||
const a = await plugin.runTask(plugin.managers.asset.resolve(p.data.file, 'binary'));
|
||||
const o = p.data.view?.byteOffset || 0;
|
||||
const l = p.data.view?.byteLength || a.data.byteLength;
|
||||
return new Float32Array(a.data.buffer, o + a.data.byteOffset, l / 4);
|
||||
}
|
||||
};
|
||||
|
||||
async function getRotations(plugin: PluginContext, r: GenericInstances<Asset>['rotations']): Promise<NumberArray> {
|
||||
if (Array.isArray(r.data)) {
|
||||
return r.data;
|
||||
} else {
|
||||
const a = await plugin.runTask(plugin.managers.asset.resolve(r.data.file, 'binary'));
|
||||
const o = r.data.view?.byteOffset || 0;
|
||||
const l = r.data.view?.byteLength || a.data.byteLength;
|
||||
return new Float32Array(a.data.buffer, o + a.data.byteOffset, l / 4);
|
||||
}
|
||||
};
|
||||
|
||||
export async function getTransforms(plugin: PluginContext, instances?: GenericInstances<Asset>) {
|
||||
const transforms: Mat4[] = [];
|
||||
if (instances) {
|
||||
const positions = await getPositions(plugin, instances.positions);
|
||||
const rotations = await getRotations(plugin, instances.rotations);
|
||||
|
||||
for (let i = 0, il = positions.length / 3; i < il; ++i) {
|
||||
Vec3.fromArray(p, positions, i * 3);
|
||||
if (instances.rotations.variant === 'matrix') {
|
||||
Mat3.fromArray(m, rotations, i * 9);
|
||||
const t = Mat4.fromMat3(Mat4(), m);
|
||||
Mat4.setTranslation(t, p);
|
||||
transforms.push(t);
|
||||
} else if (instances.rotations.variant === 'quaternion') {
|
||||
Quat.fromArray(q, rotations, i * 4);
|
||||
const t = Mat4.fromQuat(Mat4(), q);
|
||||
Mat4.setTranslation(t, p);
|
||||
transforms.push(t);
|
||||
} else if (instances.rotations.variant === 'euler') {
|
||||
Euler.fromArray(e, rotations, i * 3);
|
||||
Quat.fromEuler(q, e, 'XYZ');
|
||||
const t = Mat4.fromQuat(Mat4(), q);
|
||||
Mat4.setTranslation(t, p);
|
||||
transforms.push(t);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
transforms.push(Mat4.identity());
|
||||
}
|
||||
|
||||
return transforms;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type MartiniManifest = {
|
||||
model: string,
|
||||
positions: Vec3[],
|
||||
rotations: Vec3[],
|
||||
function: string
|
||||
}[]
|
||||
|
||||
function martiniToGeneric(martini: MartiniManifest): GenericManifest {
|
||||
const functionRoot: GenericRoot = {
|
||||
id: 'function',
|
||||
label: 'Function',
|
||||
description: 'Functional classification',
|
||||
children: [],
|
||||
};
|
||||
const entities: GenericEntity[] = [];
|
||||
|
||||
const seenGroups = new Set<string>();
|
||||
|
||||
const membraneGroup = {
|
||||
id: 'membane',
|
||||
root: 'function',
|
||||
label: 'Membrane',
|
||||
children: [] as GenericGroup[],
|
||||
};
|
||||
functionRoot.children!.push(membraneGroup);
|
||||
seenGroups.add(membraneGroup.id);
|
||||
|
||||
const lipidsGroup = {
|
||||
id: 'lipid',
|
||||
root: 'function',
|
||||
label: 'Lipid',
|
||||
children: [] as GenericGroup[],
|
||||
};
|
||||
membraneGroup.children!.push(lipidsGroup);
|
||||
seenGroups.add(lipidsGroup.id);
|
||||
|
||||
const upperGroup = {
|
||||
id: 'upper',
|
||||
root: 'function',
|
||||
label: 'Upper Leaflet',
|
||||
};
|
||||
lipidsGroup.children!.push(upperGroup);
|
||||
seenGroups.add(upperGroup.id);
|
||||
|
||||
const lowerGroup = {
|
||||
id: 'lower',
|
||||
root: 'function',
|
||||
label: 'Lower Leaflet',
|
||||
};
|
||||
lipidsGroup.children!.push(lowerGroup);
|
||||
seenGroups.add(lowerGroup.id);
|
||||
|
||||
const memprotGroup = {
|
||||
id: 'memprot',
|
||||
root: 'function',
|
||||
label: 'Transmembrane Protein',
|
||||
};
|
||||
membraneGroup.children!.push(memprotGroup);
|
||||
seenGroups.add(memprotGroup.id);
|
||||
|
||||
for (const e of martini) {
|
||||
const label = e.model.split('.')[0];
|
||||
const group = e.function || 'Metabolite';
|
||||
|
||||
const positions = {
|
||||
data: e.positions.flat().map(x => Math.round((x * 10) * 100) / 100)
|
||||
};
|
||||
const rotations = {
|
||||
data: e.rotations.flat().map(x => Math.round(x * 100) / 100),
|
||||
variant: 'euler' as const,
|
||||
};
|
||||
|
||||
if (group.includes('lower leaflet')) {
|
||||
entities.push({
|
||||
file: e.model,
|
||||
label: label.substring(15),
|
||||
groups: [{ root: 'function', id: 'lower' }],
|
||||
instances: { positions, rotations },
|
||||
sizeFactor: 1.5,
|
||||
});
|
||||
} else if (group.includes('upper leaflet')) {
|
||||
entities.push({
|
||||
file: e.model,
|
||||
label: label.substring(15),
|
||||
groups: [{ root: 'function', id: 'upper' }],
|
||||
instances: { positions, rotations },
|
||||
sizeFactor: 1.5,
|
||||
});
|
||||
} else if (group.length === 4) {
|
||||
entities.push({
|
||||
file: e.model,
|
||||
label: label.substring(17),
|
||||
groups: [{ root: 'function', id: 'memprot' }],
|
||||
instances: { positions, rotations },
|
||||
sizeFactor: 1.5,
|
||||
});
|
||||
} else {
|
||||
if (!seenGroups.has(group)) {
|
||||
functionRoot.children!.push({
|
||||
id: group,
|
||||
root: 'function',
|
||||
label: group,
|
||||
});
|
||||
seenGroups.add(group);
|
||||
}
|
||||
entities.push({
|
||||
file: e.model,
|
||||
label,
|
||||
groups: [{ root: 'function', id: group }],
|
||||
instances: { positions, rotations },
|
||||
sizeFactor: 1.5,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
label: 'Martini',
|
||||
description: 'Martini coarse-grained model',
|
||||
roots: [functionRoot],
|
||||
entities,
|
||||
};
|
||||
}
|
||||
203
src/apps/mesoscale-explorer/data/mmcif/model.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { SortedArray } from '../../../../mol-data/int';
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { Mat4 } from '../../../../mol-math/linear-algebra';
|
||||
import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry';
|
||||
import { CustomStructureProperty } from '../../../../mol-model-props/common/custom-structure-property';
|
||||
import { ElementIndex, EntityIndex, Model, Structure, Unit } from '../../../../mol-model/structure';
|
||||
import { Assembly, Symmetry } from '../../../../mol-model/structure/model/properties/symmetry';
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { StateTransformer } from '../../../../mol-state/transformer';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { deepEqual } from '../../../../mol-util';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { partitionUnits } from '../util';
|
||||
|
||||
function createModelChainMap(model: Model) {
|
||||
const builder = new Structure.StructureBuilder();
|
||||
const units = new Map<string, Unit>();
|
||||
|
||||
const { label_asym_id, _rowCount } = model.atomicHierarchy.chains;
|
||||
const { offsets } = model.atomicHierarchy.chainAtomSegments;
|
||||
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
const elements = SortedArray.ofBounds(offsets[i] as ElementIndex, offsets[i + 1] as ElementIndex);
|
||||
const unit = builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements, Unit.Trait.FastBoundary);
|
||||
units.set(label_asym_id.value(i), unit);
|
||||
}
|
||||
|
||||
return units;
|
||||
}
|
||||
|
||||
function buildAssembly(model: Model, assembly: Assembly) {
|
||||
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } });
|
||||
const assembler = Structure.Builder({
|
||||
coordinateSystem,
|
||||
label: model.label,
|
||||
});
|
||||
|
||||
const units = createModelChainMap(model);
|
||||
|
||||
for (const g of assembly.operatorGroups) {
|
||||
for (const oper of g.operators) {
|
||||
for (const id of g.asymIds!) {
|
||||
const u = units.get(id);
|
||||
if (u) {
|
||||
assembler.addWithOperator(u, oper);
|
||||
} else {
|
||||
console.log(`missing asymId '${id}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return assembler.getStructure();
|
||||
}
|
||||
|
||||
export { MmcifAssembly };
|
||||
type MmcifAssembly = typeof MmcifAssembly
|
||||
const MmcifAssembly = PluginStateTransform.BuiltIn({
|
||||
name: 'mmcif-assembly',
|
||||
display: { name: 'Mmcif Assembly' },
|
||||
from: PSO.Molecule.Model,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: {
|
||||
id: PD.Text('', { label: 'Asm Id', description: 'Assembly Id (use empty for the 1st assembly)' }),
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
const model = a.data;
|
||||
|
||||
let id = params.id;
|
||||
let asm: Assembly | undefined = void 0;
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
|
||||
// if no id is specified, use the 1st assembly.
|
||||
if (!id && symmetry && symmetry.assemblies.length !== 0) {
|
||||
id = symmetry.assemblies[0].id;
|
||||
}
|
||||
|
||||
if (!symmetry || symmetry.assemblies.length === 0) {
|
||||
plugin.log.warn(`Model '${model.entryId}' has no assembly, returning model structure.`);
|
||||
} else {
|
||||
asm = Symmetry.findAssembly(model, id || '');
|
||||
if (!asm) {
|
||||
plugin.log.warn(`Model '${model.entryId}' has no assembly called '${id}', returning model structure.`);
|
||||
}
|
||||
}
|
||||
|
||||
const base = Structure.ofModel(model);
|
||||
if (!asm) {
|
||||
const label = { label: 'Model', description: Structure.elementDescription(base) };
|
||||
return new PSO.Molecule.Structure(base, label);
|
||||
}
|
||||
|
||||
const s = buildAssembly(model, asm);
|
||||
|
||||
const objProps = { label: `Assembly ${id}`, description: Structure.elementDescription(s) };
|
||||
return new PSO.Molecule.Structure(s, objProps);
|
||||
});
|
||||
},
|
||||
update({ newParams, oldParams }) {
|
||||
return deepEqual(newParams, oldParams)
|
||||
? StateTransformer.UpdateResult.Unchanged
|
||||
: StateTransformer.UpdateResult.Recreate;
|
||||
},
|
||||
dispose({ b }) {
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
type UnitsByEntity = Map<EntityIndex, Unit[]>;
|
||||
const UnitsByEntity = CustomStructureProperty.createSimple<UnitsByEntity>('units_by_entity', 'root');
|
||||
|
||||
function getUnitsByEntity(structure: Structure): UnitsByEntity {
|
||||
if (UnitsByEntity.get(structure).value) {
|
||||
return UnitsByEntity.get(structure).value!;
|
||||
}
|
||||
|
||||
const atomicIndex = structure.model.atomicHierarchy.index;
|
||||
const spheresIndex = structure.model.coarseHierarchy.spheres;
|
||||
const map: UnitsByEntity = new Map();
|
||||
for (const ug of structure.unitSymmetryGroups) {
|
||||
const u = ug.units[0];
|
||||
let e: EntityIndex;
|
||||
if (Unit.isAtomic(u)) {
|
||||
e = atomicIndex.getEntityFromChain(u.chainIndex[u.elements[0]]);
|
||||
} else if (Unit.isSpheres(u)) {
|
||||
e = spheresIndex.getEntityFromChain(u.coarseElements.chainElementSegments.index[u.elements[0]]);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!map.has(e)) map.set(e, []);
|
||||
const entityUnits = map.get(e)!;
|
||||
|
||||
for (let i = 0, il = ug.units.length; i < il; ++i) {
|
||||
entityUnits.push(ug.units[i]);
|
||||
}
|
||||
}
|
||||
|
||||
UnitsByEntity.set(structure, { value: map }, map);
|
||||
return map;
|
||||
}
|
||||
|
||||
export { MmcifStructure };
|
||||
type MmcifStructure = typeof MmcifStructure
|
||||
const MmcifStructure = PluginStateTransform.BuiltIn({
|
||||
name: 'mmcif-structure',
|
||||
display: { name: 'Mmcif Structure' },
|
||||
from: PSO.Root,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: {
|
||||
structureRef: PD.Text(''),
|
||||
entityId: PD.Text(''),
|
||||
cellSize: PD.Numeric(500, { min: 0, max: 10000, step: 100 }),
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params, dependencies }) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
const parent = dependencies![params.structureRef].data as Structure;
|
||||
const { entities } = parent.model;
|
||||
const idx = entities.getEntityIndex(params.entityId);
|
||||
|
||||
const unitsByEntity = getUnitsByEntity(parent);
|
||||
const units = unitsByEntity.get(idx) || [];
|
||||
const unitCount = units.length;
|
||||
|
||||
let structure: Structure;
|
||||
if (unitCount > 1 && units.every(u => u.conformation.operator.isIdentity)) {
|
||||
const mergedUnits = partitionUnits(units, params.cellSize);
|
||||
structure = Structure.create(mergedUnits);
|
||||
} else {
|
||||
structure = Structure.create(units);
|
||||
}
|
||||
// could also use _struct_ref.pdbx_db_accession to point to uniprot with _struct_ref.db_name == UNP
|
||||
const label = entities.data.pdbx_description.value(idx).join(', ') || 'model';
|
||||
const description = `*Entity id* ${entities.data.id.value(idx)} *src_method* ${entities.data.src_method.value(idx)} *type* ${entities.data.type.value(idx)}`;
|
||||
return new PSO.Molecule.Structure(structure, { label, description: description });
|
||||
});
|
||||
},
|
||||
update({ newParams, oldParams }) {
|
||||
return deepEqual(newParams, oldParams)
|
||||
? StateTransformer.UpdateResult.Unchanged
|
||||
: StateTransformer.UpdateResult.Recreate;
|
||||
},
|
||||
dispose({ b }) {
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
182
src/apps/mesoscale-explorer/data/mmcif/preset.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { PluginStateObject } from '../../../../mol-plugin-state/objects';
|
||||
import { StructureRepresentation3D } from '../../../../mol-plugin-state/transforms/representation';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/representation/spacefill';
|
||||
import { StateObjectRef, StateObjectSelector, StateBuilder } from '../../../../mol-state';
|
||||
import { Clip } from '../../../../mol-util/clip';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { ColorNames } from '../../../../mol-util/color/names';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getDistinctGroupColors, getGraphicsModeProps, getMesoscaleGroupParams } from '../state';
|
||||
import { MmcifAssembly, MmcifStructure } from './model';
|
||||
|
||||
function getSpacefillParams(color: Color, scaleFactor: number, graphics: GraphicsMode, clipVariant: Clip.Variant) {
|
||||
const gmp = getGraphicsModeProps(graphics === 'custom' ? 'quality' : graphics);
|
||||
return {
|
||||
type: {
|
||||
name: 'spacefill',
|
||||
params: {
|
||||
...SpacefillRepresentationProvider.defaultValues,
|
||||
ignoreHydrogens: false,
|
||||
instanceGranularity: false,
|
||||
ignoreLight: true,
|
||||
lodLevels: gmp.lodLevels.map(l => {
|
||||
return {
|
||||
...l,
|
||||
stride: Math.max(1, Math.round(l.stride / Math.pow(scaleFactor, l.scaleBias)))
|
||||
};
|
||||
}),
|
||||
quality: 'lowest', // avoid 'auto', triggers boundary calc
|
||||
clip: {
|
||||
variant: clipVariant,
|
||||
objects: [],
|
||||
},
|
||||
clipPrimitive: true,
|
||||
approximate: gmp.approximate,
|
||||
alphaThickness: gmp.alphaThickness,
|
||||
},
|
||||
},
|
||||
colorTheme: {
|
||||
name: 'uniform',
|
||||
params: {
|
||||
value: color,
|
||||
saturation: 0,
|
||||
lightness: 0,
|
||||
}
|
||||
},
|
||||
sizeTheme: {
|
||||
name: 'physical',
|
||||
params: {
|
||||
value: 1,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function createMmcifHierarchy(plugin: PluginContext, trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory>) {
|
||||
const builder = plugin.builders.structure;
|
||||
const state = plugin.state.data;
|
||||
|
||||
const model = await builder.createModel(trajectory, { modelIndex: 0 });
|
||||
const { data: entities, subtype } = model.data!.entities;
|
||||
|
||||
const sd = model.data?.sourceData;
|
||||
if (MmcifFormat.is(sd)) {
|
||||
const pdbId = sd.data.db.struct.entry_id.value(0);
|
||||
MesoscaleState.set(plugin, {
|
||||
description: sd.data.db.struct.title.value(0),
|
||||
link: pdbId ? `https://www.rcsb.org/structure/${pdbId}` : ''
|
||||
});
|
||||
}
|
||||
|
||||
const spheresAvgRadius = new Map<string, number>();
|
||||
if (model.data!.coarseHierarchy.isDefined) {
|
||||
const spheresCount = new Map<string, number>();
|
||||
const spheresEntity_id = model.data!.coarseHierarchy.spheres.entity_id;
|
||||
const spheresRadius = model.data!.coarseConformation.spheres.radius;
|
||||
for (let i = 0, il = spheresEntity_id.rowCount; i < il; ++i) {
|
||||
const entitiId = spheresEntity_id.value(i);
|
||||
const radius = spheresRadius[i];
|
||||
if (!spheresCount.has(entitiId)) {
|
||||
spheresCount.set(entitiId, 1);
|
||||
spheresAvgRadius.set(entitiId, radius);
|
||||
} else {
|
||||
spheresCount.set(entitiId, spheresCount.get(entitiId)! + 1);
|
||||
spheresAvgRadius.set(entitiId, spheresAvgRadius.get(entitiId)! + radius);
|
||||
}
|
||||
}
|
||||
spheresAvgRadius.forEach((v, k) => {
|
||||
spheresAvgRadius.set(k, v / spheresCount.get(k)!);
|
||||
});
|
||||
}
|
||||
|
||||
const entGroups = new Map<string, StateObjectSelector>();
|
||||
const entIds = new Map<string, { idx: number, members: Map<number, number> }>();
|
||||
const entColors = new Map<string, Color[]>();
|
||||
|
||||
const graphicsMode = MesoscaleState.get(plugin).graphics;
|
||||
const groupParams = getMesoscaleGroupParams(graphicsMode);
|
||||
|
||||
const base = await state.build()
|
||||
.to(model)
|
||||
.apply(MmcifAssembly, { id: '' })
|
||||
.commit();
|
||||
|
||||
const units = base.data!.units;
|
||||
const willBeMerged = units.length > 1 && units.every(u => u.conformation.operator.isIdentity);
|
||||
const clipVariant = willBeMerged ? 'pixel' : 'instance';
|
||||
|
||||
const entRoot = await state.build()
|
||||
.toRoot()
|
||||
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: 'ent:', label: 'entity', color: { type: 'custom', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
const getEntityType = (i: number) => {
|
||||
if (entities.type.value(i) === 'water') return 'water' as const;
|
||||
return subtype.value(i) || 'unknown type';
|
||||
};
|
||||
|
||||
for (let i = 0; i < entities._rowCount; i++) {
|
||||
const t = getEntityType(i);
|
||||
if (!entIds.has(t)) {
|
||||
entIds.set(t, { idx: entIds.size, members: new Map() });
|
||||
}
|
||||
const cm = entIds.get(t)!;
|
||||
cm.members.set(i, cm.members.size);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const baseEntColors = getDistinctBaseColors(entIds.size, 0);
|
||||
const entIdEntries = Array.from(entIds.entries());
|
||||
for (let i = 0; i < entIdEntries.length; ++i) {
|
||||
const [t, m] = entIdEntries[i];
|
||||
const groupColors = getDistinctGroupColors(m.members.size, baseEntColors[i], 20, 0);
|
||||
entColors.set(t, groupColors);
|
||||
}
|
||||
|
||||
for (let i = 0; i < entities._rowCount; i++) {
|
||||
const t = getEntityType(i);
|
||||
if (!entGroups.has(t)) {
|
||||
const colorIdx = entIds.get(t)?.idx;
|
||||
const color = colorIdx !== undefined ? baseEntColors[colorIdx] : ColorNames.white;
|
||||
const group = await state.build()
|
||||
.to(entRoot)
|
||||
.applyOrUpdateTagged(`group:ent:${t}`, MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `ent:${t}`, label: t, color: { type: 'generate', illustrative: false, value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit({ revertOnError: true });
|
||||
entGroups.set(t, group);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
await state.transaction(async () => {
|
||||
try {
|
||||
const dependsOn = [base.ref];
|
||||
plugin.animationLoop.stop({ noDraw: true });
|
||||
let build: StateBuilder.Root | StateBuilder.To<any> = state.build();
|
||||
for (let i = 0; i < entities._rowCount; i++) {
|
||||
const t = getEntityType(i);
|
||||
const color = entColors.get(t)![entIds.get(t)!.members.get(i)!];
|
||||
const scaleFactor = spheresAvgRadius.get(entities.id.value(i)) || 1;
|
||||
|
||||
build = build
|
||||
.toRoot()
|
||||
.apply(MmcifStructure, { structureRef: base.ref, entityId: entities.id.value(i) }, { dependsOn })
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(color, scaleFactor, graphicsMode, clipVariant), { tags: [`ent:${t}`] });
|
||||
}
|
||||
await build.commit();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(e);
|
||||
} finally {
|
||||
plugin.animationLoop.start();
|
||||
}
|
||||
}).run();
|
||||
}
|
||||
140
src/apps/mesoscale-explorer/data/petworld/model.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mat4 } from '../../../../mol-math/linear-algebra/3d/mat4';
|
||||
import { getMatrices, operatorGroupsProvider } from '../../../../mol-model-formats/structure/property/assembly';
|
||||
import { Structure, StructureElement, StructureProperties, Trajectory, Unit } from '../../../../mol-model/structure';
|
||||
import { Assembly } from '../../../../mol-model/structure/model/properties/symmetry';
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { Table } from '../../../../mol-data/db';
|
||||
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { arrayFind } from '../../../../mol-data/util';
|
||||
import { StateObject, StateTransformer } from '../../../../mol-state';
|
||||
import { CifField } from '../../../../mol-io/reader/cif';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { mergeUnits } from '../util';
|
||||
import { deepEqual } from '../../../../mol-util';
|
||||
|
||||
export { StructureFromPetworld };
|
||||
type StructureFromPetworld = typeof StructureFromPetworld
|
||||
const StructureFromPetworld = PluginStateTransform.BuiltIn({
|
||||
name: 'structure-from-petworld',
|
||||
display: { name: 'Structure from PetWorld', description: 'Create a molecular structure from PetWorld models.' },
|
||||
from: SO.Molecule.Trajectory,
|
||||
to: SO.Molecule.Structure,
|
||||
params: {
|
||||
modelIndex: PD.Numeric(0),
|
||||
entityIds: PD.Value<string[]>([]),
|
||||
}
|
||||
})({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
const s = await buildModelsAssembly(a.data, '1', params.modelIndex, params.entityIds).runInContext(ctx);
|
||||
if (!s || !MmcifFormat.is(s.model.sourceData)) return StateObject.Null;
|
||||
|
||||
const { frame } = s.model.sourceData.data;
|
||||
const pdbx_model = frame.categories.pdbx_model.getField('name')!;
|
||||
const pdbx_description = frame.categories.pdbx_model.getField('description')!;
|
||||
const description = pdbx_description ? pdbx_description.str(params.modelIndex) : Structure.elementDescription(s);
|
||||
const label = pdbx_model.str(params.modelIndex);
|
||||
const props = { label, description: description };
|
||||
return new SO.Molecule.Structure(s, props);
|
||||
});
|
||||
},
|
||||
update({ newParams, oldParams }) {
|
||||
return deepEqual(newParams, oldParams)
|
||||
? StateTransformer.UpdateResult.Unchanged
|
||||
: StateTransformer.UpdateResult.Recreate;
|
||||
},
|
||||
dispose({ b }) {
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
function buildModelsAssembly(trajectory: Trajectory, asmName: string, modelIndex: number, entitiyIds: string[]) {
|
||||
return Task.create('Build Models Assembly', async ctx => {
|
||||
const model = await Task.resolveInContext(trajectory.getFrameAtIndex(modelIndex), ctx);
|
||||
if (!MmcifFormat.is(model.sourceData)) return;
|
||||
|
||||
const { db, frame } = model.sourceData.data;
|
||||
const PDB_model_num = frame.categories.pdbx_struct_assembly_gen.getField('PDB_model_num')!;
|
||||
|
||||
// hack to cache models assemblies
|
||||
if (!(trajectory as any).__modelsAssemblies) {
|
||||
(trajectory as any).__modelsAssemblies = createModelsAssemblies(db.pdbx_struct_assembly, db.pdbx_struct_assembly_gen as StructAssemblyGen, db.pdbx_struct_oper_list, PDB_model_num);
|
||||
}
|
||||
const modelsAssemblies = (trajectory as any).__modelsAssemblies as ModelsAssembly[];
|
||||
|
||||
const modelsAssembly = arrayFind(modelsAssemblies, ma => ma.assembly.id.toLowerCase() === asmName);
|
||||
if (!modelsAssembly) throw new Error(`Models Assembly '${asmName}' is not defined.`);
|
||||
|
||||
const { assembly } = modelsAssembly;
|
||||
const assembler = Structure.Builder();
|
||||
const g = assembly.operatorGroups[modelIndex];
|
||||
|
||||
const structure = Structure.ofModel(model);
|
||||
const l = StructureElement.Location.create(structure);
|
||||
const units = structure.units.filter(u => {
|
||||
l.unit = u;
|
||||
l.element = u.elements[0];
|
||||
return entitiyIds.includes(StructureProperties.entity.id(l));
|
||||
});
|
||||
const unit = mergeUnits(units, 0);
|
||||
|
||||
for (const oper of g.operators) {
|
||||
assembler.addUnit(unit.kind, unit.model, oper, unit.elements, unit.traits | Unit.Trait.FastBoundary, unit.invariantId);
|
||||
}
|
||||
|
||||
return assembler.getStructure();
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type StructAssembly = Table<mmCIF_Schema['pdbx_struct_assembly']>
|
||||
type StructAssemblyGen = Table<mmCIF_Schema['pdbx_struct_assembly_gen']>
|
||||
type StructOperList = Table<mmCIF_Schema['pdbx_struct_oper_list']>
|
||||
|
||||
type ModelsAssembly = { assembly: Assembly, modelNums: number[] };
|
||||
|
||||
function createModelsAssemblies(pdbx_struct_assembly: StructAssembly, pdbx_struct_assembly_gen: StructAssemblyGen, pdbx_struct_oper_list: StructOperList, PDB_model_num: CifField): ReadonlyArray<ModelsAssembly> {
|
||||
if (!pdbx_struct_assembly._rowCount) return [];
|
||||
|
||||
const matrices = getMatrices(pdbx_struct_oper_list);
|
||||
const assemblies: ModelsAssembly[] = [];
|
||||
for (let i = 0; i < pdbx_struct_assembly._rowCount; i++) {
|
||||
assemblies[assemblies.length] = createModelsAssembly(pdbx_struct_assembly, pdbx_struct_assembly_gen, i, matrices, PDB_model_num);
|
||||
}
|
||||
return assemblies;
|
||||
}
|
||||
|
||||
type Matrices = Map<string, Mat4>
|
||||
type Generator = { assemblyId: string, expression: string, asymIds: string[] }
|
||||
|
||||
function createModelsAssembly(pdbx_struct_assembly: StructAssembly, pdbx_struct_assembly_gen: StructAssemblyGen, index: number, matrices: Matrices, PDB_model_num: CifField): ModelsAssembly {
|
||||
const id = pdbx_struct_assembly.id.value(index);
|
||||
const details = pdbx_struct_assembly.details.value(index);
|
||||
const generators: Generator[] = [];
|
||||
const modelNums: number[] = [];
|
||||
|
||||
const { assembly_id, oper_expression, asym_id_list } = pdbx_struct_assembly_gen;
|
||||
|
||||
for (let i = 0, _i = pdbx_struct_assembly_gen._rowCount; i < _i; i++) {
|
||||
if (assembly_id.value(i) !== id) continue;
|
||||
generators[generators.length] = {
|
||||
assemblyId: id,
|
||||
expression: oper_expression.value(i),
|
||||
asymIds: asym_id_list.value(i)
|
||||
};
|
||||
modelNums[modelNums.length] = PDB_model_num.int(i);
|
||||
}
|
||||
|
||||
const assembly = Assembly.create(id, details, operatorGroupsProvider(generators, matrices));
|
||||
|
||||
return { assembly, modelNums };
|
||||
}
|
||||
134
src/apps/mesoscale-explorer/data/petworld/preset.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateBuilder, StateObjectRef } from '../../../../mol-state';
|
||||
import { StructureFromPetworld } from './model';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/representation/spacefill';
|
||||
import { StructureRepresentation3D } from '../../../../mol-plugin-state/transforms/representation';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { PluginStateObject } from '../../../../mol-plugin-state/objects';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getGraphicsModeProps, getMesoscaleGroupParams } from '../state';
|
||||
import { ColorNames } from '../../../../mol-util/color/names';
|
||||
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { Task } from '../../../../mol-task';
|
||||
|
||||
function getSpacefillParams(color: Color, graphics: GraphicsMode) {
|
||||
const gmp = getGraphicsModeProps(graphics === 'custom' ? 'quality' : graphics);
|
||||
return {
|
||||
type: {
|
||||
name: 'spacefill',
|
||||
params: {
|
||||
...SpacefillRepresentationProvider.defaultValues,
|
||||
ignoreHydrogens: true,
|
||||
instanceGranularity: true,
|
||||
ignoreLight: true,
|
||||
lodLevels: gmp.lodLevels,
|
||||
quality: 'lowest', // avoid 'auto', triggers boundary calc
|
||||
clip: {
|
||||
variant: 'instance',
|
||||
objects: [],
|
||||
},
|
||||
clipPrimitive: true,
|
||||
approximate: gmp.approximate,
|
||||
alphaThickness: gmp.alphaThickness,
|
||||
},
|
||||
},
|
||||
colorTheme: {
|
||||
name: 'uniform',
|
||||
params: {
|
||||
value: color,
|
||||
saturation: 0,
|
||||
lightness: 0,
|
||||
}
|
||||
},
|
||||
sizeTheme: {
|
||||
name: 'physical',
|
||||
params: {
|
||||
scale: 1,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function createPetworldHierarchy(plugin: PluginContext, trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory>) {
|
||||
const cell = StateObjectRef.resolveAndCheck(plugin.state.data, trajectory);
|
||||
const tr = cell?.obj?.data;
|
||||
if (!cell || !tr) return;
|
||||
|
||||
if (!MmcifFormat.is(tr.representative.sourceData)) return;
|
||||
|
||||
const membrane: { modelIndex: number, entityIds: string[] }[] = [];
|
||||
const other: { modelIndex: number, entityIds: string[] }[] = [];
|
||||
for (let i = 0; i < tr.frameCount; ++i) {
|
||||
const m = await Task.resolveInContext(tr.getFrameAtIndex(i));
|
||||
// cannot use m.properties.structAsymMap because petworld models
|
||||
// may assign the same asymId to multiple entities
|
||||
const { label_asym_id, label_entity_id, _rowCount } = m.atomicHierarchy.chains;
|
||||
const membraneIds: string[] = [];
|
||||
const otherIds: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (let i = 0; i < _rowCount; i ++) {
|
||||
const entityId = label_entity_id.value(i);
|
||||
if (seen.has(entityId)) continue;
|
||||
|
||||
const asymId = label_asym_id.value(i);
|
||||
if (asymId.startsWith('MEM')) {
|
||||
membraneIds.push(entityId);
|
||||
} else {
|
||||
otherIds.push(entityId);
|
||||
}
|
||||
seen.add(entityId);
|
||||
}
|
||||
if (membraneIds.length) {
|
||||
membrane.push({ modelIndex: i, entityIds: membraneIds });
|
||||
}
|
||||
if (otherIds.length) {
|
||||
other.push({ modelIndex: i, entityIds: otherIds });
|
||||
}
|
||||
}
|
||||
|
||||
const state = plugin.state.data;
|
||||
const graphicsMode = MesoscaleState.get(plugin).graphics;
|
||||
const groupParams = getMesoscaleGroupParams(graphicsMode);
|
||||
|
||||
const group = await state.build()
|
||||
.toRoot()
|
||||
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'generate', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: ['group:ent:'], state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit({ revertOnError: true });
|
||||
|
||||
await state.build()
|
||||
.to(group)
|
||||
.apply(MesoscaleGroup, { ...groupParams, index: undefined, tag: `ent:mem`, label: 'Membrane', color: { type: 'uniform', illustrative: false, value: ColorNames.lightgrey, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: ['group:ent:mem', 'ent:', '__no_group_color__'], state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
const colors = getDistinctBaseColors(other.length, 0);
|
||||
|
||||
await state.transaction(async () => {
|
||||
try {
|
||||
plugin.animationLoop.stop({ noDraw: true });
|
||||
let build: StateBuilder.Root | StateBuilder.To<any> = state.build();
|
||||
for (let i = 0, il = membrane.length; i < il; ++i) {
|
||||
build = build
|
||||
.to(cell)
|
||||
.apply(StructureFromPetworld, membrane[i])
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(ColorNames.lightgrey, graphicsMode), { tags: ['ent:mem', '__no_group_color__'] });
|
||||
}
|
||||
for (let i = 0, il = other.length; i < il; ++i) {
|
||||
build = build
|
||||
.to(cell)
|
||||
.apply(StructureFromPetworld, other[i])
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(colors[i], graphicsMode), { tags: ['ent:'] });
|
||||
}
|
||||
await build.commit();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(e);
|
||||
} finally {
|
||||
plugin.animationLoop.start();
|
||||
}
|
||||
}).run();
|
||||
}
|
||||
690
src/apps/mesoscale-explorer/data/state.ts
Normal file
@@ -0,0 +1,690 @@
|
||||
/**
|
||||
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { Spheres } from '../../../mol-geo/geometry/spheres/spheres';
|
||||
import { Clip } from '../../../mol-util/clip';
|
||||
import { escapeRegExp, stringToWords } from '../../../mol-util/string';
|
||||
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { ParamMapping } from '../../../mol-util/param-mapping';
|
||||
import { EntityNode } from '../ui/entities';
|
||||
import { DistinctColorsProps, distinctColors } from '../../../mol-util/color/distinct';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { Hcl } from '../../../mol-util/color/spaces/hcl';
|
||||
import { StateObjectCell, StateObjectRef, StateSelection } from '../../../mol-state';
|
||||
import { ShapeRepresentation3D, StructureRepresentation3D } from '../../../mol-plugin-state/transforms/representation';
|
||||
import { SpacefillRepresentationProvider } from '../../../mol-repr/structure/representation/spacefill';
|
||||
import { assertUnreachable } from '../../../mol-util/type-helpers';
|
||||
import { MesoscaleExplorerState } from '../app';
|
||||
import { saturate } from '../../../mol-math/interpolate';
|
||||
import { Material } from '../../../mol-util/material';
|
||||
|
||||
function getHueRange(hue: number, variability: number) {
|
||||
let min = hue - variability;
|
||||
const minOverflow = (min < 0 ? -min : 0);
|
||||
let max = hue + variability;
|
||||
if (max > 360) min -= max - 360;
|
||||
max += minOverflow;
|
||||
return [Math.max(0, min), Math.min(360, max)] as [number, number];
|
||||
}
|
||||
|
||||
function getGrayscaleColors(count: number, luminance: number, variability: number) {
|
||||
const out: Color[] = [];
|
||||
for (let i = 0; i < count; ++ i) {
|
||||
const l = saturate(luminance / 100);
|
||||
const v = saturate(variability / 180) * Math.random();
|
||||
const s = Math.random() > 0.5 ? 1 : -1;
|
||||
const d = Math.abs(l + s * v) % 1;
|
||||
out[i] = Color.fromNormalizedRgb(d, d, d);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function getDistinctGroupColors(count: number, color: Color, variability: number, shift: number, props?: Partial<DistinctColorsProps>) {
|
||||
const hcl = Hcl.fromColor(Hcl(), color);
|
||||
if (isNaN(hcl[0])) {
|
||||
return getGrayscaleColors(count, hcl[2], variability);
|
||||
}
|
||||
|
||||
if (count === 1) {
|
||||
hcl[1] = 65;
|
||||
hcl[2] = 55;
|
||||
return [Hcl.toColor(hcl)];
|
||||
}
|
||||
|
||||
const colors = distinctColors(count, {
|
||||
hue: getHueRange(hcl[0], variability),
|
||||
chroma: [30, 100],
|
||||
luminance: [50, 100],
|
||||
clusteringStepCount: 0,
|
||||
minSampleCount: 1000,
|
||||
sampleCountFactor: 100,
|
||||
sort: 'none',
|
||||
...props,
|
||||
});
|
||||
|
||||
if (shift !== 0) {
|
||||
const offset = Math.floor(shift / 100 * count);
|
||||
return [...colors.slice(offset), ...colors.slice(0, offset)];
|
||||
} else {
|
||||
return colors;
|
||||
}
|
||||
}
|
||||
|
||||
const Colors = [0x377eb8, 0xe41a1c, 0x4daf4a, 0x984ea3, 0xff7f00, 0xffff33, 0xa65628, 0xf781bf] as Color[];
|
||||
|
||||
export function getDistinctBaseColors(count: number, shift: number, props?: Partial<DistinctColorsProps>): Color[] {
|
||||
let colors: Color[];
|
||||
if (count <= Colors.length) {
|
||||
colors = Colors.slice(0, count).map(e => Array.isArray(e) ? e[0] : e);
|
||||
} else {
|
||||
colors = distinctColors(count, {
|
||||
hue: [1, 360],
|
||||
chroma: [25, 100],
|
||||
luminance: [30, 100],
|
||||
clusteringStepCount: 0,
|
||||
minSampleCount: 1000,
|
||||
sampleCountFactor: 100,
|
||||
sort: 'none',
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
if (shift !== 0) {
|
||||
const offset = Math.floor(shift / 100 * count);
|
||||
return [...colors.slice(offset), ...colors.slice(0, offset)];
|
||||
} else {
|
||||
return colors;
|
||||
}
|
||||
}
|
||||
|
||||
export const ColorParams = {
|
||||
type: PD.Select('generate', PD.arrayToOptions(['generate', 'uniform', 'custom'])),
|
||||
illustrative: PD.Boolean(false, { description: 'Illustrative style', hideIf: p => p.type === 'custom' }),
|
||||
value: PD.Color(Color(0xFFFFFF), { hideIf: p => p.type === 'custom' }),
|
||||
variability: PD.Numeric(20, { min: 1, max: 180, step: 1 }, { hideIf: p => p.type !== 'generate' }),
|
||||
shift: PD.Numeric(0, { min: 0, max: 100, step: 1 }, { hideIf: p => !p.type.includes('generate') }),
|
||||
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }, { hideIf: p => p.type === 'custom' }),
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
|
||||
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
|
||||
};
|
||||
export type ColorProps = PD.Values<typeof ColorParams>
|
||||
|
||||
export const ColorValueParam = PD.Color(Color(0xFFFFFF));
|
||||
|
||||
export const RootParams = {
|
||||
type: PD.Select('custom', PD.arrayToOptions(['group-generate', 'group-uniform', 'generate', 'uniform', 'custom'])),
|
||||
illustrative: PD.Boolean(false, { description: 'Illustrative style', hideIf: p => p.type === 'custom' }),
|
||||
value: PD.Color(Color(0xFFFFFF), { hideIf: p => p.type !== 'uniform' }),
|
||||
variability: PD.Numeric(20, { min: 1, max: 180, step: 1 }, { hideIf: p => p.type !== 'group-generate' }),
|
||||
shift: PD.Numeric(0, { min: 0, max: 100, step: 1 }, { hideIf: p => !p.type.includes('generate') }),
|
||||
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }, { hideIf: p => p.type === 'custom' }),
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
|
||||
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
|
||||
};
|
||||
|
||||
export const LightnessParams = {
|
||||
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
|
||||
};
|
||||
export const DimLightness = 6;
|
||||
|
||||
export const IllustrativeParams = {
|
||||
illustrative: PD.Boolean(false, { description: 'Illustrative style' }),
|
||||
};
|
||||
|
||||
export const OpacityParams = {
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
};
|
||||
|
||||
export const EmissiveParams = {
|
||||
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
|
||||
};
|
||||
|
||||
export const celShaded = {
|
||||
celShaded: PD.Boolean(false, { description: 'Cel Shading light for stylized rendering of representations' })
|
||||
};
|
||||
|
||||
export type celShadedProps = PD.Values<typeof celShaded>;
|
||||
|
||||
|
||||
export const PatternParams = {
|
||||
frequency: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
amplitude: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
};
|
||||
|
||||
export const StyleParams = {
|
||||
ignoreLight: PD.Boolean(false, { description: 'Ignore light for stylized rendering of representations' }),
|
||||
materialStyle: Material.getParam(),
|
||||
celShaded: PD.Boolean(false, { description: 'Cel Shading light for stylized rendering of representations' }),
|
||||
};
|
||||
|
||||
export const LodParams = {
|
||||
lodLevels: Spheres.Params.lodLevels,
|
||||
cellSize: Spheres.Params.cellSize,
|
||||
batchSize: Spheres.Params.batchSize,
|
||||
approximate: Spheres.Params.approximate,
|
||||
};
|
||||
|
||||
export const SimpleClipParams = {
|
||||
type: PD.Select('none', PD.objectToOptions(Clip.Type, t => stringToWords(t))),
|
||||
invert: PD.Boolean(false),
|
||||
position: PD.Group({
|
||||
x: PD.Numeric(0, { min: -100, max: 100, step: 1 }, { immediateUpdate: true }),
|
||||
y: PD.Numeric(0, { min: -100, max: 100, step: 1 }, { immediateUpdate: true }),
|
||||
z: PD.Numeric(0, { min: -100, max: 100, step: 1 }, { immediateUpdate: true }),
|
||||
}, { hideIf: g => g.type === 'none', isExpanded: true }),
|
||||
rotation: PD.Group({
|
||||
axis: PD.Vec3(Vec3.create(1, 0, 0)),
|
||||
angle: PD.Numeric(0, { min: -180, max: 180, step: 1 }, { immediateUpdate: true }),
|
||||
}, { hideIf: g => g.type === 'none', isExpanded: true }),
|
||||
scale: PD.Group({
|
||||
x: PD.Numeric(100, { min: 0, max: 100, step: 1 }, { immediateUpdate: true }),
|
||||
y: PD.Numeric(100, { min: 0, max: 100, step: 1 }, { immediateUpdate: true }),
|
||||
z: PD.Numeric(100, { min: 0, max: 100, step: 1 }, { immediateUpdate: true }),
|
||||
}, { hideIf: g => ['none', 'plane'].includes(g.type), isExpanded: true }),
|
||||
};
|
||||
export type SimpleClipParams = typeof SimpleClipParams
|
||||
export type SimpleClipProps = PD.Values<SimpleClipParams>
|
||||
|
||||
export function getClipObjects(values: SimpleClipProps, boundingSphere: Sphere3D): Clip.Props['objects'] {
|
||||
const { center, radius } = boundingSphere;
|
||||
|
||||
const position = Vec3.clone(center);
|
||||
Vec3.add(position, position, Vec3.create(
|
||||
radius * values.position.x / 100,
|
||||
radius * values.position.y / 100,
|
||||
radius * values.position.z / 100
|
||||
));
|
||||
|
||||
const scale = Vec3.create(values.scale.x, values.scale.y, values.scale.z);
|
||||
Vec3.scale(scale, scale, 2 * radius / 100);
|
||||
|
||||
return [{
|
||||
type: values.type,
|
||||
invert: values.invert,
|
||||
position,
|
||||
scale,
|
||||
rotation: values.rotation,
|
||||
transform: Mat4.identity(),
|
||||
}];
|
||||
}
|
||||
|
||||
export function createClipMapping(node: EntityNode) {
|
||||
return ParamMapping({
|
||||
params: SimpleClipParams,
|
||||
target: (ctx: PluginContext) => {
|
||||
return node.clipValue;
|
||||
}
|
||||
})({
|
||||
values(props, ctx) {
|
||||
if (!props || props.objects.length === 0) {
|
||||
return {
|
||||
type: 'none',
|
||||
invert: false,
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
rotation: { axis: Vec3.create(1, 0, 0), angle: 0 },
|
||||
scale: { x: 100, y: 100, z: 100 },
|
||||
};
|
||||
}
|
||||
|
||||
const { center, radius } = node.plugin.canvas3d!.boundingSphere;
|
||||
const { invert, position, scale, rotation, type } = props.objects[0];
|
||||
|
||||
const p = Vec3.clone(position);
|
||||
Vec3.sub(p, p, center);
|
||||
Vec3.scale(p, p, 100 / radius);
|
||||
Vec3.round(p, p);
|
||||
|
||||
const s = Vec3.clone(scale);
|
||||
Vec3.scale(s, s, 100 / radius / 2);
|
||||
Vec3.round(s, s);
|
||||
|
||||
return {
|
||||
type,
|
||||
invert,
|
||||
position: { x: p[0], y: p[1], z: p[2] },
|
||||
rotation,
|
||||
scale: { x: s[0], y: s[1], z: s[2] },
|
||||
};
|
||||
},
|
||||
update: (s, props) => {
|
||||
if (!props) return;
|
||||
|
||||
const clipObjects = getClipObjects(s, node.plugin.canvas3d!.boundingSphere);
|
||||
props.objects = clipObjects;
|
||||
},
|
||||
apply: async (props, ctx) => {
|
||||
if (props) node.updateClip(props);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const MesoscaleGroupParams = {
|
||||
root: PD.Value<boolean>(false, { isHidden: true }),
|
||||
index: PD.Value<number>(-1, { isHidden: true }),
|
||||
tag: PD.Value<string>('', { isHidden: true }),
|
||||
label: PD.Value<string>('', { isHidden: true }),
|
||||
description: PD.Value<string>('', { isHidden: true }),
|
||||
hidden: PD.Boolean(false),
|
||||
color: PD.Group(RootParams),
|
||||
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
|
||||
lod: PD.Group(LodParams),
|
||||
clip: PD.Group(SimpleClipParams),
|
||||
};
|
||||
export type MesoscaleGroupProps = PD.Values<typeof MesoscaleGroupParams>;
|
||||
|
||||
export class MesoscaleGroupObject extends PSO.Create({ name: 'Mesoscale Group', typeClass: 'Object' }) { }
|
||||
|
||||
export const MesoscaleGroup = PluginStateTransform.BuiltIn({
|
||||
name: 'mesoscale-group',
|
||||
display: { name: 'Mesoscale Group' },
|
||||
from: [PSO.Root, MesoscaleGroupObject],
|
||||
to: MesoscaleGroupObject,
|
||||
params: MesoscaleGroupParams,
|
||||
})({
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Apply Mesoscale Group', async () => {
|
||||
return new MesoscaleGroupObject({}, { label: params.label, description: params.description });
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export function getMesoscaleGroupParams(graphicsMode: GraphicsMode): MesoscaleGroupProps {
|
||||
const groupParams = PD.getDefaultValues(MesoscaleGroupParams);
|
||||
if (graphicsMode === 'custom') return groupParams;
|
||||
|
||||
return {
|
||||
...groupParams,
|
||||
lod: {
|
||||
...groupParams.lod,
|
||||
...getGraphicsModeProps(graphicsMode),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export type LodLevels = typeof SpacefillRepresentationProvider.defaultValues['lodLevels']
|
||||
|
||||
export function getLodLevels(graphicsMode: Exclude<GraphicsMode, 'custom'>): LodLevels {
|
||||
switch (graphicsMode) {
|
||||
case 'performance':
|
||||
return [
|
||||
{ minDistance: 1, maxDistance: 300, overlap: 0, stride: 1, scaleBias: 1 },
|
||||
{ minDistance: 300, maxDistance: 2000, overlap: 0, stride: 40, scaleBias: 3 },
|
||||
{ minDistance: 2000, maxDistance: 6000, overlap: 0, stride: 150, scaleBias: 3 },
|
||||
{ minDistance: 6000, maxDistance: 10000000, overlap: 0, stride: 300, scaleBias: 2.5 },
|
||||
];
|
||||
case 'balanced':
|
||||
return [
|
||||
{ minDistance: 1, maxDistance: 500, overlap: 0, stride: 1, scaleBias: 1 },
|
||||
{ minDistance: 500, maxDistance: 2000, overlap: 0, stride: 15, scaleBias: 3 },
|
||||
{ minDistance: 2000, maxDistance: 6000, overlap: 0, stride: 70, scaleBias: 2.7 },
|
||||
{ minDistance: 6000, maxDistance: 10000000, overlap: 0, stride: 200, scaleBias: 2.5 },
|
||||
];
|
||||
case 'quality':
|
||||
return [
|
||||
{ minDistance: 1, maxDistance: 1000, overlap: 0, stride: 1, scaleBias: 1 },
|
||||
{ minDistance: 1000, maxDistance: 4000, overlap: 0, stride: 10, scaleBias: 3 },
|
||||
{ minDistance: 4000, maxDistance: 10000, overlap: 0, stride: 50, scaleBias: 2.7 },
|
||||
{ minDistance: 10000, maxDistance: 10000000, overlap: 0, stride: 200, scaleBias: 2.3 },
|
||||
];
|
||||
case 'ultra':
|
||||
return [
|
||||
{ minDistance: 1, maxDistance: 5000, overlap: 0, stride: 1, scaleBias: 1 },
|
||||
{ minDistance: 5000, maxDistance: 10000, overlap: 0, stride: 10, scaleBias: 3 },
|
||||
{ minDistance: 10000, maxDistance: 30000, overlap: 0, stride: 50, scaleBias: 2.5 },
|
||||
{ minDistance: 30000, maxDistance: 10000000, overlap: 0, stride: 200, scaleBias: 2 },
|
||||
];
|
||||
default:
|
||||
assertUnreachable(graphicsMode);
|
||||
}
|
||||
}
|
||||
|
||||
export type GraphicsMode = 'ultra' | 'quality' | 'balanced' | 'performance' | 'custom';
|
||||
|
||||
export function getGraphicsModeProps(graphicsMode: Exclude<GraphicsMode, 'custom'>) {
|
||||
return {
|
||||
lodLevels: getLodLevels(graphicsMode),
|
||||
approximate: graphicsMode !== 'quality' && graphicsMode !== 'ultra',
|
||||
alphaThickness: graphicsMode === 'performance' ? 15 : 12,
|
||||
};
|
||||
}
|
||||
|
||||
export function setGraphicsCanvas3DProps(ctx: PluginContext, graphics: GraphicsMode) {
|
||||
const pixelScale = graphics === 'balanced' ? 0.75
|
||||
: graphics === 'performance' ? 0.5 : 1;
|
||||
|
||||
ctx.canvas3dContext?.setProps({ pixelScale });
|
||||
|
||||
ctx.canvas3d?.setProps({
|
||||
postprocessing: {
|
||||
sharpening: pixelScale < 1 ? {
|
||||
name: 'on',
|
||||
params: { sharpness: 0.5, denoise: true }
|
||||
} : { name: 'off', params: {} }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export const MesoscaleStateParams = {
|
||||
filter: PD.Value<string>('', { isHidden: true }),
|
||||
graphics: PD.Select('quality', PD.arrayToOptions(['ultra', 'quality', 'balanced', 'performance', 'custom'] as GraphicsMode[])),
|
||||
description: PD.Value<string>('', { isHidden: true }),
|
||||
focusInfo: PD.Value<string>('', { isHidden: true }),
|
||||
link: PD.Value<string>('', { isHidden: true }),
|
||||
textSizeDescription: PD.Numeric(14, { min: 1, max: 100, step: 1 }, { isHidden: true }),
|
||||
index: PD.Value<number>(-1, { isHidden: true })
|
||||
};
|
||||
|
||||
export class MesoscaleStateObject extends PSO.Create<MesoscaleState>({ name: 'Mesoscale State', typeClass: 'Object' }) { }
|
||||
|
||||
const MesoscaleStateTransform = PluginStateTransform.BuiltIn({
|
||||
name: 'mesoscale-state',
|
||||
display: { name: 'Mesoscale State' },
|
||||
from: PSO.Root,
|
||||
to: MesoscaleStateObject,
|
||||
params: MesoscaleStateParams,
|
||||
})({
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Apply Mesoscale State', async () => {
|
||||
return new MesoscaleStateObject(params);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export { MesoscaleState };
|
||||
type MesoscaleState = PD.Values<typeof MesoscaleStateParams>;
|
||||
const MesoscaleState = {
|
||||
async init(ctx: PluginContext) {
|
||||
const cell = ctx.state.data.selectQ(q => q.ofType(MesoscaleStateObject))[0];
|
||||
if (cell) throw new Error('MesoscaleState already initialized');
|
||||
|
||||
const customState = ctx.customState as MesoscaleExplorerState;
|
||||
const state = await ctx.state.data.build().toRoot().apply(MesoscaleStateTransform, {
|
||||
filter: '',
|
||||
graphics: customState.graphicsMode,
|
||||
}).commit();
|
||||
customState.stateRef = state.ref;
|
||||
},
|
||||
get(ctx: PluginContext): MesoscaleState {
|
||||
const ref = this.ref(ctx);
|
||||
return ctx.state.data.tryGetCellData<MesoscaleStateObject>(ref);
|
||||
},
|
||||
async set(ctx: PluginContext, props: Partial<MesoscaleState>) {
|
||||
const ref = this.ref(ctx);
|
||||
await ctx.state.data.build().to(ref).update(MesoscaleStateTransform, old => Object.assign(old, props)).commit();
|
||||
},
|
||||
ref(ctx: PluginContext): string {
|
||||
const ref = (ctx.customState as MesoscaleExplorerState).stateRef;
|
||||
if (!ref) throw new Error('MesoscaleState not initialized');
|
||||
return ref;
|
||||
},
|
||||
has(ctx: PluginContext): boolean {
|
||||
const ref = (ctx.customState as MesoscaleExplorerState).stateRef || '';
|
||||
return ctx.state.data.cells.has(ref) ? true : false;
|
||||
},
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
export function getRoots(plugin: PluginContext): StateSelection.CellSeq<StateObjectCell<MesoscaleGroupObject>> {
|
||||
const s = plugin.customState as MesoscaleExplorerState;
|
||||
if (!s.stateCache.roots) {
|
||||
s.stateCache.roots = plugin.state.data.select(StateSelection.Generators.rootsOfType(MesoscaleGroupObject));
|
||||
}
|
||||
return s.stateCache.roots;
|
||||
}
|
||||
|
||||
export function getGroups(plugin: PluginContext, tag?: string): StateSelection.CellSeq<StateObjectCell<MesoscaleGroupObject>> {
|
||||
const s = plugin.customState as MesoscaleExplorerState;
|
||||
const k = `groups-${tag || ''}`;
|
||||
if (!s.stateCache[k]) {
|
||||
const selector = tag !== undefined
|
||||
? StateSelection.Generators.ofTransformer(MesoscaleGroup).withTag(tag)
|
||||
: StateSelection.Generators.ofTransformer(MesoscaleGroup);
|
||||
s.stateCache[k] = plugin.state.data.select(selector);
|
||||
}
|
||||
return s.stateCache[k];
|
||||
}
|
||||
|
||||
function _getAllGroups(plugin: PluginContext, tag: string | undefined, list: StateObjectCell[]) {
|
||||
const groups = getGroups(plugin, tag);
|
||||
list.push(...groups);
|
||||
for (const g of groups) {
|
||||
_getAllGroups(plugin, g.params?.values.tag, list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
export function getAllGroups(plugin: PluginContext, tag?: string) {
|
||||
return _getAllGroups(plugin, tag, []);
|
||||
}
|
||||
|
||||
export function getAllLeafGroups(plugin: PluginContext, tag: string) {
|
||||
const allGroups = getAllGroups(plugin, tag);
|
||||
allGroups.sort((a, b) => a.params?.values.index - b.params?.values.index);
|
||||
return allGroups.filter(g => {
|
||||
return getEntities(plugin, g.params?.values.tag).length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
type EntityCells = StateSelection.CellSeq<StateObjectCell<PSO.Molecule.Structure.Representation3D | PSO.Shape.Representation3D>>
|
||||
|
||||
export function getEntities(plugin: PluginContext, tag?: string): EntityCells {
|
||||
const s = plugin.customState as MesoscaleExplorerState;
|
||||
const k = `entities-${tag || ''}`;
|
||||
if (!s.stateCache[k]) {
|
||||
const structureSelector = tag !== undefined
|
||||
? StateSelection.Generators.ofTransformer(StructureRepresentation3D).withTag(tag)
|
||||
: StateSelection.Generators.ofTransformer(StructureRepresentation3D);
|
||||
const shapeSelector = tag !== undefined
|
||||
? StateSelection.Generators.ofTransformer(ShapeRepresentation3D).withTag(tag)
|
||||
: StateSelection.Generators.ofTransformer(ShapeRepresentation3D);
|
||||
s.stateCache[k] = [
|
||||
...plugin.state.data.select(structureSelector).filter(c => c.obj!.data.sourceData.elementCount > 0),
|
||||
...plugin.state.data.select(shapeSelector),
|
||||
];
|
||||
}
|
||||
return s.stateCache[k];
|
||||
}
|
||||
|
||||
function getFilterMatcher(filter: string) {
|
||||
return filter.startsWith('"') && filter.endsWith('"')
|
||||
? new RegExp(`^${escapeRegExp(filter.substring(1, filter.length - 1))}$`, 'g')
|
||||
: new RegExp(escapeRegExp(filter), 'gi');
|
||||
}
|
||||
|
||||
export function getFilteredEntities(plugin: PluginContext, tag: string, filter: string) {
|
||||
if (!filter) return getEntities(plugin, tag);
|
||||
const matcher = getFilterMatcher(filter);
|
||||
return getEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
|
||||
}
|
||||
|
||||
function _getAllEntities(plugin: PluginContext, tag: string | undefined, list: EntityCells) {
|
||||
list.push(...getEntities(plugin, tag));
|
||||
for (const g of getGroups(plugin, tag)) {
|
||||
_getAllEntities(plugin, g.params?.values.tag, list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
export function getAllEntities(plugin: PluginContext, tag?: string) {
|
||||
return _getAllEntities(plugin, tag, []);
|
||||
}
|
||||
|
||||
export function getAllFilteredEntities(plugin: PluginContext, tag: string, filter: string) {
|
||||
if (!filter) return getAllEntities(plugin, tag);
|
||||
const matcher = getFilterMatcher(filter);
|
||||
return getAllEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
|
||||
}
|
||||
|
||||
export function getEveryEntity(plugin: PluginContext, filter?: string, tag?: string) {
|
||||
if (filter) {
|
||||
const matcher = getFilterMatcher(filter);
|
||||
return getAllEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
|
||||
} else {
|
||||
return getAllEntities(plugin, tag);
|
||||
}
|
||||
}
|
||||
|
||||
export function getEntityLabel(plugin: PluginContext, cell: StateObjectCell) {
|
||||
return StateObjectRef.resolve(plugin.state.data, cell.transform.parent)?.obj?.label || 'Entity';
|
||||
}
|
||||
|
||||
export function getCellDescription(cell: StateObjectCell) {
|
||||
// markdown style for description
|
||||
return '**' + cell?.obj?.label + '**\n\n' + cell?.obj?.description;
|
||||
}
|
||||
|
||||
export function getEntityDescription(plugin: PluginContext, cell: StateObjectCell) {
|
||||
const s = StateObjectRef.resolve(plugin.state.data, cell.transform.parent);
|
||||
const d = getCellDescription(s!);
|
||||
return d;
|
||||
}
|
||||
|
||||
export async function updateStyle(plugin: PluginContext, options: { ignoreLight: boolean, material: Material, celShaded: boolean, illustrative: boolean }) {
|
||||
const update = plugin.state.data.build();
|
||||
const { ignoreLight, material, celShaded, illustrative } = options;
|
||||
|
||||
const entities = getAllEntities(plugin);
|
||||
|
||||
for (let j = 0; j < entities.length; ++j) {
|
||||
update.to(entities[j]).update(old => {
|
||||
if (old.type) {
|
||||
const value = old.colorTheme.name === 'illustrative'
|
||||
? old.colorTheme.params.style.params.value
|
||||
: old.colorTheme.params.value;
|
||||
const lightness = old.colorTheme.name === 'illustrative'
|
||||
? old.colorTheme.params.style.params.lightness
|
||||
: old.colorTheme.params.lightness;
|
||||
if (illustrative) {
|
||||
old.colorTheme = { name: 'illustrative', params: { style: { name: 'uniform', params: { value, lightness } } } };
|
||||
} else {
|
||||
old.colorTheme = { name: 'uniform', params: { value, lightness } };
|
||||
}
|
||||
old.type.params.ignoreLight = ignoreLight;
|
||||
old.type.params.material = material;
|
||||
old.type.params.celShaded = celShaded;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await update.commit();
|
||||
};
|
||||
|
||||
export async function updateColors(plugin: PluginContext, values: PD.Values, tag: string, filter: string) {
|
||||
const update = plugin.state.data.build();
|
||||
const { type, illustrative, value, shift, lightness, alpha, emissive } = values;
|
||||
if (type === 'group-generate' || type === 'group-uniform') {
|
||||
const leafGroups = getAllLeafGroups(plugin, tag);
|
||||
const rootLeafGroups = getRoots(plugin).filter(g => g.params?.values.tag === tag && getEntities(plugin, g.params?.values.tag).length > 0);
|
||||
const groups = [...leafGroups, ...rootLeafGroups];
|
||||
const baseColors = getDistinctBaseColors(groups.length, shift);
|
||||
|
||||
for (let i = 0; i < groups.length; ++i) {
|
||||
const g = groups[i];
|
||||
const entities = getFilteredEntities(plugin, g.params?.values.tag, filter);
|
||||
let groupColors: Color[] = [];
|
||||
|
||||
if (type === 'group-generate') {
|
||||
const c = g.params?.values.color;
|
||||
groupColors = getDistinctGroupColors(entities.length, baseColors[i], c.variability, c.shift);
|
||||
}
|
||||
|
||||
for (let j = 0; j < entities.length; ++j) {
|
||||
const c = type === 'group-generate' ? groupColors[j] : baseColors[i];
|
||||
update.to(entities[j]).update(old => {
|
||||
if (old.type) {
|
||||
if (illustrative) {
|
||||
old.colorTheme = { name: 'illustrative', params: { style: { name: 'uniform', params: { value: c, lightness: lightness } } } };
|
||||
} else {
|
||||
old.colorTheme = { name: 'uniform', params: { value: c, lightness: lightness } };
|
||||
}
|
||||
old.type.params.alpha = alpha;
|
||||
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
|
||||
old.type.params.emissive = emissive;
|
||||
} else if (old.coloring) {
|
||||
old.coloring.params.color = c;
|
||||
old.coloring.params.lightness = lightness;
|
||||
old.alpha = alpha;
|
||||
old.xrayShaded = alpha < 1 ? true : false;
|
||||
old.emissive = emissive;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update.to(g.transform.ref).update(old => {
|
||||
old.color.type = type === 'group-generate' ? 'generate' : 'uniform';
|
||||
old.color.illustrative = illustrative;
|
||||
old.color.value = baseColors[i];
|
||||
old.color.lightness = lightness;
|
||||
old.color.alpha = alpha;
|
||||
old.color.emissive = emissive;
|
||||
});
|
||||
}
|
||||
} else if (type === 'generate' || type === 'uniform') {
|
||||
const entities = getAllFilteredEntities(plugin, tag, filter);
|
||||
let groupColors: Color[] = [];
|
||||
|
||||
if (type === 'generate') {
|
||||
groupColors = getDistinctBaseColors(entities.length, shift);
|
||||
}
|
||||
|
||||
for (let j = 0; j < entities.length; ++j) {
|
||||
const c = type === 'generate' ? groupColors[j] : value;
|
||||
update.to(entities[j]).update(old => {
|
||||
if (old.type) {
|
||||
if (illustrative) {
|
||||
old.colorTheme = { name: 'illustrative', params: { style: { name: 'uniform', params: { value: c, lightness: lightness } } } };
|
||||
} else {
|
||||
old.colorTheme = { name: 'uniform', params: { value: c, lightness: lightness } };
|
||||
}
|
||||
old.type.params.alpha = alpha;
|
||||
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
|
||||
old.type.params.emissive = emissive;
|
||||
} else if (old.coloring) {
|
||||
old.coloring.params.color = c;
|
||||
old.coloring.params.lightness = lightness;
|
||||
old.alpha = alpha;
|
||||
old.xrayShaded = alpha < 1 ? true : false;
|
||||
old.emissive = emissive;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const others = getAllLeafGroups(plugin, tag);
|
||||
for (const o of others) {
|
||||
update.to(o).update(old => {
|
||||
old.color.type = type === 'generate' ? 'custom' : 'uniform';
|
||||
old.color.illustrative = illustrative;
|
||||
old.color.value = value;
|
||||
old.color.lightness = lightness;
|
||||
old.color.alpha = alpha;
|
||||
old.color.emissive = emissive;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await update.commit();
|
||||
};
|
||||
|
||||
export function expandAllGroups(plugin: PluginContext) {
|
||||
for (const g of getAllGroups(plugin)) {
|
||||
if (g.state.isCollapsed) {
|
||||
plugin.state.data.updateCellState(g.transform.ref, { isCollapsed: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
87
src/apps/mesoscale-explorer/data/util.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { OrderedSet, SortedArray } from '../../../mol-data/int';
|
||||
import { Box3D, GridLookup3D, PositionData, Sphere3D } from '../../../mol-math/geometry';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { ElementIndex, Unit } from '../../../mol-model/structure';
|
||||
|
||||
export function mergeUnits(units: readonly Unit[], id: number): Unit {
|
||||
const u = units[0];
|
||||
|
||||
let start = -1 as ElementIndex, end = -1 as ElementIndex;
|
||||
let elements = SortedArray.Empty as SortedArray<ElementIndex>;
|
||||
|
||||
for (let i = 0, il = units.length; i < il; ++i) {
|
||||
const e = units[i].elements;
|
||||
if (SortedArray.isRange(e)) {
|
||||
if (end !== -1 && e[0] === end + 1) {
|
||||
// extend range
|
||||
end = e[e.length - 1];
|
||||
} else {
|
||||
if (end !== -1) {
|
||||
// pending range
|
||||
elements = SortedArray.union(elements, SortedArray.ofRange(start, end));
|
||||
}
|
||||
// new range
|
||||
start = e[0];
|
||||
end = e[e.length - 1];
|
||||
}
|
||||
} else {
|
||||
if (end !== -1) {
|
||||
// pending range
|
||||
elements = SortedArray.union(elements, SortedArray.ofRange(start, end));
|
||||
start = -1 as ElementIndex, end = -1 as ElementIndex;
|
||||
}
|
||||
elements = SortedArray.union(elements, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (end !== -1) {
|
||||
// pending range
|
||||
elements = SortedArray.union(elements, SortedArray.ofRange(start, end));
|
||||
}
|
||||
|
||||
return Unit.create(id, id, 0, u.traits | Unit.Trait.MultiChain, u.kind, u.model, u.conformation.operator, elements);
|
||||
}
|
||||
|
||||
export function partitionUnits(units: readonly Unit[], cellSize: number) {
|
||||
const unitCount = units.length;
|
||||
const mergedUnits: Unit[] = [];
|
||||
|
||||
const box = Box3D.setEmpty(Box3D());
|
||||
const x = new Float32Array(unitCount);
|
||||
const y = new Float32Array(unitCount);
|
||||
const z = new Float32Array(unitCount);
|
||||
const indices = OrderedSet.ofBounds(0, unitCount);
|
||||
|
||||
for (let i = 0, il = unitCount; i < il; ++i) {
|
||||
const v = units[i].boundary.sphere.center;
|
||||
x[i] = v[0];
|
||||
y[i] = v[1];
|
||||
z[i] = v[2];
|
||||
Box3D.add(box, v);
|
||||
}
|
||||
Box3D.expand(box, box, Vec3.create(1, 1, 1));
|
||||
|
||||
const positionData: PositionData = { x, y, z, indices };
|
||||
const boundary = { box, sphere: Sphere3D.fromBox3D(Sphere3D(), box) };
|
||||
const lookup = GridLookup3D(positionData, boundary, Vec3.create(cellSize, cellSize, cellSize));
|
||||
|
||||
const { array, offset, count } = lookup.buckets;
|
||||
|
||||
for (let i = 0, il = offset.length; i < il; ++i) {
|
||||
const start = offset[i];
|
||||
const size = count[i];
|
||||
const cellUnits: Unit[] = [];
|
||||
for (let j = start, jl = start + size; j < jl; ++j) {
|
||||
cellUnits.push(units[array[j]]);
|
||||
}
|
||||
mergedUnits.push(mergeUnits(cellUnits, i));
|
||||
}
|
||||
|
||||
return mergedUnits;
|
||||
}
|
||||
79
src/apps/mesoscale-explorer/embedded.html
Normal file
@@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
overflow: hidden;
|
||||
}
|
||||
#controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 800px;
|
||||
margin-bottom: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
button {
|
||||
margin: 5px;
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#viewer-container {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
border: 1px solid #ccc;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="./molstar.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="controls">
|
||||
<button onclick="loadExample('cellpack-hiv1')">Load HIV-1 Example</button>
|
||||
<button onclick="loadExample('machineryoflife-tour')">Load Machinery of Life Tour</button>
|
||||
<button onclick="loadExample('petworld-synvesicle')">Load Synaptic Vesicle Example</button>
|
||||
</div>
|
||||
<div id="viewer-container">
|
||||
<div id="meso-viewer" style="position: relative; width: 100%; height: 400px;"></div>
|
||||
</div>
|
||||
|
||||
<script src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
let mesoExplorer;
|
||||
|
||||
function loadExample(example) {
|
||||
if (mesoExplorer) {
|
||||
mesoExplorer.loadExample(example);
|
||||
}
|
||||
}
|
||||
|
||||
molstar.MesoscaleExplorer.create('meso-viewer', {
|
||||
layoutShowControls: false,
|
||||
viewportShowExpand: false,
|
||||
layoutIsExpanded: false,
|
||||
powerPreference: 'high-performance',
|
||||
graphicsMode: 'quality'
|
||||
}).then(me => {
|
||||
mesoExplorer = me;
|
||||
me.loadExample('cellpack-hiv1'); // Load the default example on page load
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
me.dispose();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
src/apps/mesoscale-explorer/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
115
src/apps/mesoscale-explorer/index.html
Normal file
@@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="../extras/driver.css"/>
|
||||
<title>Mol* Mesoscale Explorer</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
hr {
|
||||
margin: 10px;
|
||||
}
|
||||
h1, h2, h3, h4, h5 {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
button {
|
||||
padding: 2px;
|
||||
}
|
||||
#app {
|
||||
position: absolute;
|
||||
left: 100px;
|
||||
top: 100px;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="../extras/driver.js.iife.js"></script>
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
const driver = window.driver ? window.driver.js.driver() : undefined;
|
||||
|
||||
function getParam(name, regex) {
|
||||
var r = new RegExp(name + '=' + '(' + regex + ')[&]?', 'i');
|
||||
return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || '');
|
||||
}
|
||||
|
||||
var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
|
||||
if (debugMode) molstar.setDebugMode(debugMode);
|
||||
|
||||
var timingMode = getParam('timing-mode', '[^&]+').trim() === '1';
|
||||
if (timingMode) molstar.setTimingMode(timingMode);
|
||||
|
||||
var hideControls = getParam('hide-controls', '[^&]+').trim() === '1';
|
||||
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1' || void 0;
|
||||
var allowMajorPerformanceCaveat = getParam('allow-major-performance-caveat', '[^&]+').trim() === '1';
|
||||
var powerPreference = getParam('power-preference', '[^&]+').trim().toLowerCase();
|
||||
var graphicsMode = getParam('graphics-mode', '[^&]+').trim().toLowerCase();
|
||||
var illumination = getParam('illumination', '[^&]+').trim() === '1';
|
||||
var resolutionMode = getParam('resolution-mode', '[^&]+').trim().toLowerCase();
|
||||
|
||||
molstar.MesoscaleExplorer.create('app', {
|
||||
layoutShowControls: !hideControls,
|
||||
viewportShowExpand: false,
|
||||
preferWebgl1: preferWebgl1,
|
||||
allowMajorPerformanceCaveat: allowMajorPerformanceCaveat,
|
||||
powerPreference: powerPreference || 'high-performance',
|
||||
graphicsMode: graphicsMode || 'quality',
|
||||
illumination: illumination,
|
||||
resolutionMode: resolutionMode || 'auto',
|
||||
driver: driver
|
||||
}).then(me => {
|
||||
var example = getParam('example', '[^&]+').trim();
|
||||
if (example) {
|
||||
me.loadExample(example);
|
||||
return;
|
||||
}
|
||||
|
||||
var url = getParam('url', '[^&]+').trim();
|
||||
var type = getParam('type', '[^&]+').trim();
|
||||
if (url && type) {
|
||||
me.loadUrl(url, type);
|
||||
return;
|
||||
}
|
||||
|
||||
var pdb = getParam('pdb', '[^&]+').trim();
|
||||
if (pdb) {
|
||||
me.loadPdb(pdb);
|
||||
return;
|
||||
}
|
||||
|
||||
var pdbihm = getParam('pdbihm', '[^&]+').trim();
|
||||
if (pdbihm) {
|
||||
me.loadPdbIhm(pdbihm);
|
||||
return;
|
||||
}
|
||||
// support for deprecated pdb-dev param
|
||||
var pdbdev = getParam('pdbdev', '[^&]+').trim();
|
||||
if (pdbdev) {
|
||||
me.loadPdbIhm(pdbdev);
|
||||
return;
|
||||
}
|
||||
window.addEventListener('unload', () => {
|
||||
// to aid GC
|
||||
me.dispose();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- __MOLSTAR_ANALYTICS__ -->
|
||||
</body>
|
||||
</html>
|
||||
10
src/apps/mesoscale-explorer/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import './favicon.ico';
|
||||
import './index.html';
|
||||
import './style.scss';
|
||||
export * from './app';
|
||||
64
src/apps/mesoscale-explorer/style.scss
Normal file
@@ -0,0 +1,64 @@
|
||||
@use "sass:color";
|
||||
|
||||
@use '../../mol-plugin-ui/skin/base/colors' with (
|
||||
$default-background: #2D3E50,
|
||||
$font-color: #EDF1F2,
|
||||
$hover-font-color: #3B9AD9,
|
||||
$entity-current-font-color: #FFFFFF,
|
||||
$msp-btn-remove-background: #BF3A31,
|
||||
$msp-btn-remove-hover-font-color:#ffffff,
|
||||
$msp-btn-commit-on-font-color: #ffffff,
|
||||
$entity-badge-font-color: #ccd4e0,
|
||||
|
||||
// used in LOG
|
||||
$log-message: #0CCA5D,
|
||||
$log-info: #5E3673,
|
||||
$log-warning: #FCC937,
|
||||
$log-error: #FD354B,
|
||||
|
||||
$logo-background: rgba(0,0,0,0.75),
|
||||
|
||||
$color-adjust-sign: -1,
|
||||
);
|
||||
|
||||
@use '../../mol-plugin-ui/skin/base/base';
|
||||
@use '../../mol-plugin-ui/skin/base/vars' as *;
|
||||
|
||||
a {
|
||||
color: $font-color;
|
||||
&:hover {
|
||||
color: $hover-font-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.msp-snapshot-description-me {
|
||||
background: color.change($default-background, $alpha: 0.5, $space: rgb);
|
||||
|
||||
position: absolute;
|
||||
height: 50vh; // 50% of the viewport height
|
||||
left: 0;
|
||||
top: $control-spacing + $row-height;
|
||||
padding: (0.66 * $control-spacing) $control-spacing;
|
||||
|
||||
resize: both; /* Allows resizing in both directions */
|
||||
overflow: auto; /* Adjust as needed */
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
color: $font-color;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
padding-left: $control-spacing + 4px;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.shown {
|
||||
display: block; // or 'flex', 'grid', etc. depending on your layout
|
||||
}
|
||||
}
|
||||
1260
src/apps/mesoscale-explorer/ui/entities.tsx
Normal file
157
src/apps/mesoscale-explorer/ui/panels.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mp4EncoderUI } from '../../../extensions/mp4-export/ui';
|
||||
import { CollapsableControls, CollapsableState, PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { SectionHeader } from '../../../mol-plugin-ui/controls/common';
|
||||
import { ParameterControls } from '../../../mol-plugin-ui/controls/parameters';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { StructureMeasurementsControls } from '../../../mol-plugin-ui/structure/measurements';
|
||||
import { MesoscaleExplorerState } from '../app';
|
||||
import { MesoscaleState } from '../data/state';
|
||||
import { EntityControls, FocusInfo, ModelInfo, SelectionInfo } from './entities';
|
||||
import { LoaderControls, ExampleControls, SessionControls, SnapshotControls, DatabaseControls, MesoQuickStylesControls, ExplorerInfo } from './states';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { TuneSvg } from '../../../mol-plugin-ui/controls/icons';
|
||||
import { RendererParams } from '../../../mol-gl/renderer';
|
||||
import { TrackballControlsParams } from '../../../mol-canvas3d/controls/trackball';
|
||||
|
||||
const Spacer = () => <div style={{ height: '2em' }} />;
|
||||
|
||||
const ViewportParams = {
|
||||
renderer: PD.Group(RendererParams),
|
||||
trackball: PD.Group(TrackballControlsParams),
|
||||
};
|
||||
|
||||
class ViewportSettingsUI extends CollapsableControls<{}, {}> {
|
||||
protected defaultState(): CollapsableState {
|
||||
return {
|
||||
header: 'Viewport Settings',
|
||||
isCollapsed: true,
|
||||
brand: { accent: 'cyan', svg: TuneSvg }
|
||||
};
|
||||
}
|
||||
|
||||
protected renderControls(): JSX.Element | null {
|
||||
return <>
|
||||
{this.plugin.canvas3d && this.plugin.canvas3dContext && <>
|
||||
<ParameterControls params={ViewportParams} values={this.plugin.canvas3d.props} onChange={this.setSettings} />
|
||||
</>}
|
||||
</>;
|
||||
}
|
||||
|
||||
private setSettings = (p: { param: PD.Base<any>, name: string, value: any }) => {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { [p.name]: p.value } });
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
|
||||
this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
export class LeftPanel extends PluginUIComponent {
|
||||
render() {
|
||||
const customState = this.plugin.customState as MesoscaleExplorerState;
|
||||
|
||||
return <div className='msp-scrollable-container'>
|
||||
{customState.driver && <>
|
||||
<ExplorerInfo />
|
||||
<Spacer />
|
||||
</>}
|
||||
<SectionHeader title='Database' />
|
||||
<DatabaseControls />
|
||||
<Spacer />
|
||||
|
||||
<SectionHeader title='Open' />
|
||||
<LoaderControls />
|
||||
<Spacer />
|
||||
|
||||
{customState.examples?.length && <>
|
||||
<SectionHeader title='Example' />
|
||||
<ExampleControls />
|
||||
<Spacer />
|
||||
</>}
|
||||
|
||||
<SectionHeader title='Session' />
|
||||
<SessionControls />
|
||||
<Spacer />
|
||||
|
||||
<SectionHeader title='Snapshots' />
|
||||
<SnapshotControls />
|
||||
<Spacer />
|
||||
|
||||
<Mp4EncoderUI />
|
||||
<ViewportSettingsUI />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class RightPanel extends PluginUIComponent<{}, { isDisabled: boolean }> {
|
||||
state = {
|
||||
isDisabled: false,
|
||||
};
|
||||
|
||||
get hasModelInfo() {
|
||||
return (
|
||||
MesoscaleState.has(this.plugin) &&
|
||||
!!(MesoscaleState.get(this.plugin).description ||
|
||||
MesoscaleState.get(this.plugin).link)
|
||||
);
|
||||
}
|
||||
|
||||
get hasFocusInfo() {
|
||||
return (
|
||||
MesoscaleState.has(this.plugin) &&
|
||||
!!(MesoscaleState.get(this.plugin).focusInfo !== '')
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
|
||||
this.setState({ isDisabled: v });
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
|
||||
if (!this.state.isDisabled && MesoscaleState.has(this.plugin) && MesoscaleState.ref(this.plugin) === e.ref) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.managers.structure.selection.events.changed, e => {
|
||||
if (!this.state.isDisabled) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className='msp-scrollable-container'>
|
||||
{this.hasModelInfo && <>
|
||||
<SectionHeader title='Model' />
|
||||
<ModelInfo />
|
||||
<Spacer />
|
||||
</>}
|
||||
|
||||
<>
|
||||
<SectionHeader title='Selection' />
|
||||
<SelectionInfo />
|
||||
<Spacer />
|
||||
<StructureMeasurementsControls initiallyCollapsed={true}/>
|
||||
</>
|
||||
<MesoQuickStylesControls />
|
||||
<Spacer />
|
||||
<SectionHeader title='Entities' />
|
||||
<EntityControls />
|
||||
<Spacer />
|
||||
{this.hasFocusInfo && <>
|
||||
<SectionHeader title='Focus Info' />
|
||||
<FocusInfo />
|
||||
<Spacer />
|
||||
</>}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
783
src/apps/mesoscale-explorer/ui/states.tsx
Normal file
@@ -0,0 +1,783 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
import { MmcifProvider } from '../../../mol-plugin-state/formats/trajectory';
|
||||
import { PluginStateObject } from '../../../mol-plugin-state/objects';
|
||||
import { Button, ExpandGroup, IconButton } from '../../../mol-plugin-ui/controls/common';
|
||||
import { GetAppSvg, HelpOutlineSvg, MagicWandSvg, TourSvg, Icon, OpenInBrowserSvg } from '../../../mol-plugin-ui/controls/icons';
|
||||
import { CollapsableControls, PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
|
||||
import { LocalStateSnapshotList, LocalStateSnapshotParams, LocalStateSnapshots } from '../../../mol-plugin-ui/state/snapshots';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateAction, StateObjectRef, StateTransform } from '../../../mol-state';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { Color } from '../../../mol-util/color/color';
|
||||
import { getFileNameInfo } from '../../../mol-util/file-info';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { ExampleEntry, MesoscaleExplorerState } from '../app';
|
||||
import { createCellpackHierarchy } from '../data/cellpack/preset';
|
||||
import { createGenericHierarchy } from '../data/generic/preset';
|
||||
import { createMmcifHierarchy } from '../data/mmcif/preset';
|
||||
import { createPetworldHierarchy } from '../data/petworld/preset';
|
||||
import { getAllEntities, getEntityLabel, MesoscaleState, MesoscaleStateObject, setGraphicsCanvas3DProps, updateStyle } from '../data/state';
|
||||
import { isTimingMode } from '../../../mol-util/debug';
|
||||
import { now } from '../../../mol-util/now';
|
||||
import { readFromFile } from '../../../mol-util/data-source';
|
||||
|
||||
function adjustPluginProps(ctx: PluginContext) {
|
||||
const customState = ctx.customState as MesoscaleExplorerState;
|
||||
|
||||
ctx.managers.interactivity.setProps({ granularity: 'chain' });
|
||||
ctx.canvas3d?.setProps({
|
||||
multiSample: { mode: 'off' },
|
||||
cameraClipping: { far: false, minNear: 50 },
|
||||
sceneRadiusFactor: 2,
|
||||
renderer: {
|
||||
colorMarker: true,
|
||||
highlightColor: Color(0xffffff),
|
||||
highlightStrength: 0,
|
||||
selectColor: Color(0xffffff),
|
||||
selectStrength: 0,
|
||||
dimColor: Color(0xffffff),
|
||||
dimStrength: 1,
|
||||
markerPriority: 2,
|
||||
interiorColorFlag: false,
|
||||
interiorDarkening: 0.15,
|
||||
exposure: 1.1,
|
||||
xrayEdgeFalloff: 3,
|
||||
},
|
||||
marking: {
|
||||
enabled: true,
|
||||
highlightEdgeColor: Color(0x999999),
|
||||
selectEdgeColor: Color(0xffff00),
|
||||
highlightEdgeStrength: 1,
|
||||
selectEdgeStrength: 1,
|
||||
ghostEdgeStrength: 1,
|
||||
innerEdgeFactor: 2.5,
|
||||
edgeScale: 2,
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
multiScale: {
|
||||
name: 'on',
|
||||
params: {
|
||||
levels: [
|
||||
{ radius: 2, bias: 1.0 },
|
||||
{ radius: 5, bias: 1.0 },
|
||||
{ radius: 8, bias: 1.0 },
|
||||
{ radius: 11, bias: 1.0 },
|
||||
],
|
||||
nearThreshold: 10,
|
||||
farThreshold: 1500,
|
||||
}
|
||||
},
|
||||
radius: 5,
|
||||
bias: 1,
|
||||
blurKernelSize: 11,
|
||||
blurDepthBias: 0.5,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
transparentThreshold: 0.4,
|
||||
}
|
||||
},
|
||||
shadow: {
|
||||
name: 'on',
|
||||
params: {
|
||||
maxDistance: 80,
|
||||
steps: 3,
|
||||
tolerance: 1.0,
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
name: 'on',
|
||||
params: {
|
||||
scale: 1,
|
||||
threshold: 0.15,
|
||||
color: Color(0x000000),
|
||||
includeTransparent: false,
|
||||
}
|
||||
},
|
||||
},
|
||||
illumination: {
|
||||
enabled: customState.illumination,
|
||||
firstStepSize: 0.1,
|
||||
rayDistance: 1024,
|
||||
},
|
||||
});
|
||||
|
||||
const { graphics } = MesoscaleState.get(ctx);
|
||||
setGraphicsCanvas3DProps(ctx, graphics);
|
||||
}
|
||||
|
||||
async function createHierarchy(ctx: PluginContext, ref: string) {
|
||||
const parsed = await MmcifProvider.parse(ctx, ref);
|
||||
|
||||
const tr = StateObjectRef.resolveAndCheck(ctx.state.data, parsed.trajectory)?.obj?.data;
|
||||
if (!tr) throw new Error('no trajectory');
|
||||
|
||||
if (!MmcifFormat.is(tr.representative.sourceData)) {
|
||||
throw new Error('not mmcif');
|
||||
}
|
||||
|
||||
const { frame, db } = tr.representative.sourceData.data;
|
||||
|
||||
let hasCellpackAssemblyMethodDetails = false;
|
||||
const { method_details } = db.pdbx_struct_assembly;
|
||||
for (let i = 0, il = method_details.rowCount; i < il; ++i) {
|
||||
if (method_details.value(i).toUpperCase() === 'CELLPACK') {
|
||||
hasCellpackAssemblyMethodDetails = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame.categories.pdbx_model) {
|
||||
await createPetworldHierarchy(ctx, parsed.trajectory);
|
||||
} else if (
|
||||
frame.header.toUpperCase().includes('CELLPACK') ||
|
||||
hasCellpackAssemblyMethodDetails
|
||||
) {
|
||||
await createCellpackHierarchy(ctx, parsed.trajectory);
|
||||
} else {
|
||||
await createMmcifHierarchy(ctx, parsed.trajectory);
|
||||
}
|
||||
}
|
||||
|
||||
async function reset(ctx: PluginContext) {
|
||||
const customState = ctx.customState as MesoscaleExplorerState;
|
||||
delete customState.stateRef;
|
||||
customState.stateCache = {};
|
||||
ctx.managers.asset.clear();
|
||||
|
||||
await PluginCommands.State.Snapshots.Clear(ctx);
|
||||
await PluginCommands.State.RemoveObject(ctx, { state: ctx.state.data, ref: StateTransform.RootRef });
|
||||
|
||||
await MesoscaleState.init(ctx);
|
||||
adjustPluginProps(ctx);
|
||||
}
|
||||
|
||||
export async function loadExampleEntry(ctx: PluginContext, entry: ExampleEntry) {
|
||||
const { url, type } = entry;
|
||||
await loadUrl(ctx, url, type);
|
||||
MesoscaleState.set(ctx, {
|
||||
description: entry.description || entry.label,
|
||||
link: entry.link,
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadUrl(ctx: PluginContext, url: string, type: 'molx' | 'molj' | 'cif' | 'bcif') {
|
||||
let startTime = 0;
|
||||
if (isTimingMode) {
|
||||
startTime = now();
|
||||
}
|
||||
if (type === 'molx' || type === 'molj') {
|
||||
const customState = ctx.customState as MesoscaleExplorerState;
|
||||
delete customState.stateRef;
|
||||
customState.stateCache = {};
|
||||
ctx.managers.asset.clear();
|
||||
|
||||
await PluginCommands.State.Snapshots.Clear(ctx);
|
||||
await PluginCommands.State.Snapshots.OpenUrl(ctx, { url, type });
|
||||
|
||||
const cell = ctx.state.data.selectQ(q => q.ofType(MesoscaleStateObject))[0];
|
||||
if (!cell) throw new Error('Missing MesoscaleState');
|
||||
|
||||
customState.stateRef = cell.transform.ref;
|
||||
customState.graphicsMode = cell.obj?.data.graphics || customState.graphicsMode;
|
||||
} else {
|
||||
await reset(ctx);
|
||||
const isBinary = type === 'bcif';
|
||||
const data = await ctx.builders.data.download({ url, isBinary });
|
||||
await createHierarchy(ctx, data.ref);
|
||||
}
|
||||
if (isTimingMode) {
|
||||
const endTime = now();
|
||||
// Calculate the elapsed time
|
||||
const timeTaken = endTime - startTime;
|
||||
console.log(`Model loaded in ${timeTaken} milliseconds`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadPdb(ctx: PluginContext, id: string) {
|
||||
await reset(ctx);
|
||||
const url = `https://models.rcsb.org/${id.toUpperCase()}.bcif`;
|
||||
const data = await ctx.builders.data.download({ url, isBinary: true });
|
||||
await createHierarchy(ctx, data.ref);
|
||||
}
|
||||
|
||||
export async function loadPdbIhm(ctx: PluginContext, id: string) {
|
||||
await reset(ctx);
|
||||
let url: string;
|
||||
// 4 character PDB id, TODO: support extended PDB ID
|
||||
if (id.match(/^[1-9][A-Z0-9]{3}$/i) !== null) {
|
||||
url = `https://pdb-ihm.org/bcif/${id.toLowerCase()}.bcif`;
|
||||
} else {
|
||||
const nId = id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`;
|
||||
url = `https://pdb-ihm.org/bcif/${nId.toUpperCase()}.bcif`;
|
||||
}
|
||||
const data = await ctx.builders.data.download({ url, isBinary: true });
|
||||
await createHierarchy(ctx, data.ref);
|
||||
}
|
||||
|
||||
async function loadColors(ctx: PluginContext, file: File) {
|
||||
const colorData = await ctx.runTask(readFromFile(file, 'json'));
|
||||
|
||||
const update = ctx.state.data.build();
|
||||
const allEntities = getAllEntities(ctx);
|
||||
|
||||
for (const entityCell of allEntities) {
|
||||
const label = getEntityLabel(ctx, entityCell);
|
||||
const tags = entityCell.transform.tags;
|
||||
const fullname = (tags?.[0].replace('comp:', '') ?? '') + '.' + label;
|
||||
// test each tag, siwtch to uniform color
|
||||
if (fullname in colorData) {
|
||||
const { x, y, z } = colorData[fullname];
|
||||
const color = Color.fromRgb(x, y, z);
|
||||
update.to(entityCell).update(old => {
|
||||
if (old.type) {
|
||||
old.colorTheme = { name: 'uniform', params: { value: color, lightness: old.colorTheme.params.lightness } };
|
||||
old.type.params.color = color;
|
||||
} else if (old.coloring) {
|
||||
old.coloring.params.color = color;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
await update.commit();
|
||||
}
|
||||
//
|
||||
|
||||
export const LoadDatabase = StateAction.build({
|
||||
display: { name: 'Database', description: 'Load from Database' },
|
||||
params: (a, ctx: PluginContext) => {
|
||||
return {
|
||||
source: PD.Select('pdb', PD.objectToOptions({ pdb: 'PDB', pdbIhm: 'PDB-IHM' })),
|
||||
entry: PD.Text(''),
|
||||
};
|
||||
},
|
||||
from: PluginStateObject.Root
|
||||
})(({ params }, ctx: PluginContext) => Task.create('Loading from database...', async taskCtx => {
|
||||
if (params.source === 'pdb') {
|
||||
await loadPdb(ctx, params.entry);
|
||||
} else if (params.source === 'pdbIhm') {
|
||||
await loadPdbIhm(ctx, params.entry);
|
||||
}
|
||||
}));
|
||||
|
||||
export const LoadExample = StateAction.build({
|
||||
display: { name: 'Load', description: 'Load an example' },
|
||||
params: (a, ctx: PluginContext) => {
|
||||
const entries = (ctx.customState as MesoscaleExplorerState).examples || [];
|
||||
return {
|
||||
entry: PD.Select(0, entries.map((s, i) => [i, s.label])),
|
||||
};
|
||||
},
|
||||
from: PluginStateObject.Root
|
||||
})(({ params }, ctx: PluginContext) => Task.create('Loading example...', async taskCtx => {
|
||||
const entries = (ctx.customState as MesoscaleExplorerState).examples || [];
|
||||
await loadExampleEntry(ctx, entries[params.entry]);
|
||||
}));
|
||||
|
||||
export const LoadModel = StateAction.build({
|
||||
display: { name: 'Load', description: 'Load a model' },
|
||||
params: {
|
||||
files: PD.FileList({ accept: '.cif,.bcif,.cif.gz,.bcif.gz,.zip', multiple: true, description: 'mmCIF or Cellpack- or Petworld-style cif file.', label: 'File(s)' }),
|
||||
},
|
||||
from: PluginStateObject.Root
|
||||
})(({ params }, ctx: PluginContext) => Task.create('Loading model...', async taskCtx => {
|
||||
if (params.files === null || params.files.length === 0) {
|
||||
ctx.log.error('No file(s) selected');
|
||||
return;
|
||||
}
|
||||
|
||||
await reset(ctx);
|
||||
|
||||
const firstFile = params.files[0];
|
||||
const firstInfo = getFileNameInfo(firstFile.file!.name);
|
||||
|
||||
if (firstInfo.name.endsWith('zip')) {
|
||||
try {
|
||||
await createGenericHierarchy(ctx, firstFile);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
ctx.log.error(`Error opening file '${firstFile.name}'`);
|
||||
}
|
||||
} else {
|
||||
for (const file of params.files) {
|
||||
try {
|
||||
const info = getFileNameInfo(file.file!.name);
|
||||
if (!['cif', 'bcif'].includes(info.ext)) continue;
|
||||
|
||||
const isBinary = ctx.dataFormats.binaryExtensions.has(info.ext);
|
||||
const { data } = await ctx.builders.data.readFile({ file, isBinary });
|
||||
await createHierarchy(ctx, data.ref);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
ctx.log.error(`Error opening file '${file.name}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
export class DatabaseControls extends PluginUIComponent {
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div id='database' style={{ margin: '5px' }}>
|
||||
<ApplyActionControl state={this.plugin.state.data} action={LoadDatabase} nodeRef={this.plugin.state.data.tree.root.ref} applyLabel={'Load'} hideHeader />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class LoaderControls extends PluginUIComponent {
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div id='loader' style={{ margin: '5px' }}>
|
||||
<ApplyActionControl state={this.plugin.state.data} action={LoadModel} nodeRef={this.plugin.state.data.tree.root.ref} applyLabel={'Load'} hideHeader />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExampleControls extends PluginUIComponent {
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div id='example' style={{ margin: '5px' }}>
|
||||
<ApplyActionControl state={this.plugin.state.data} action={LoadExample} nodeRef={this.plugin.state.data.tree.root.ref} applyLabel={'Load'} hideHeader />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export function ColorLoaderControls({ plugin }: { plugin: PluginContext }) {
|
||||
const triggerLoadColors = () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.onchange = async (e) => {
|
||||
const input = e.target as HTMLInputElement;
|
||||
if (!input.files || !input.files[0]) return;
|
||||
const file = input.files[0];
|
||||
await loadColors(plugin, new File([file], file.name));
|
||||
};
|
||||
input.click();
|
||||
};
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
svg={OpenInBrowserSvg}
|
||||
title="Load Colors"
|
||||
onClick={triggerLoadColors}
|
||||
small
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function openState(ctx: PluginContext, file: File) {
|
||||
const customState = ctx.customState as MesoscaleExplorerState;
|
||||
delete customState.stateRef;
|
||||
customState.stateCache = {};
|
||||
ctx.managers.asset.clear();
|
||||
|
||||
await PluginCommands.State.Snapshots.Clear(ctx);
|
||||
await PluginCommands.State.Snapshots.OpenFile(ctx, { file });
|
||||
|
||||
const cell = ctx.state.data.selectQ(q => q.ofType(MesoscaleStateObject))[0];
|
||||
if (!cell) throw new Error('Missing MesoscaleState');
|
||||
|
||||
customState.stateRef = cell.transform.ref;
|
||||
customState.graphicsMode = cell.obj?.data.graphics || customState.graphicsMode;
|
||||
}
|
||||
|
||||
export class SessionControls extends PluginUIComponent {
|
||||
downloadToFileZip = () => {
|
||||
PluginCommands.State.Snapshots.DownloadToFile(this.plugin, { type: 'zip' });
|
||||
};
|
||||
|
||||
open = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.files || !e.target.files[0]) {
|
||||
this.plugin.log.error('No state file selected');
|
||||
return;
|
||||
}
|
||||
|
||||
openState(this.plugin, e.target.files[0]);
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div id='session' style={{ margin: '5px' }}>
|
||||
<div className='msp-flex-row'>
|
||||
<Button icon={GetAppSvg} onClick={this.downloadToFileZip} title='Download the state.'>
|
||||
Download
|
||||
</Button>
|
||||
<div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file'>
|
||||
<Icon svg={OpenInBrowserSvg} inline /> Open <input onChange={this.open} type='file' multiple={false} accept='.molx,.molj' />
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class SnapshotControls extends PluginUIComponent<{}> {
|
||||
render() {
|
||||
return <div style={{ margin: '5px' }}>
|
||||
<div id='snaplist' style={{ marginBottom: '10px' }}>
|
||||
<LocalStateSnapshotList />
|
||||
</div>
|
||||
<div id='snap' style={{ marginBottom: '10px' }}>
|
||||
<LocalStateSnapshots />
|
||||
</div>
|
||||
|
||||
<div id='snapoption' style={{ marginBottom: '10px' }}>
|
||||
<ExpandGroup header='Snapshot Options' initiallyExpanded={false}>
|
||||
<LocalStateSnapshotParams />
|
||||
</ExpandGroup>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExplorerInfo extends PluginUIComponent<{}, { isDisabled: boolean, showHelp: boolean }> {
|
||||
state = {
|
||||
isDisabled: false,
|
||||
showHelp: false
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
|
||||
this.setState({ isDisabled: v });
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
|
||||
if (!this.state.isDisabled && MesoscaleState.has(this.plugin) && MesoscaleState.ref(this.plugin) === e.ref) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupDriver = () => {
|
||||
// setup the tour of the interface
|
||||
const driver = (this.plugin.customState as MesoscaleExplorerState).driver;
|
||||
if (!driver) return;
|
||||
|
||||
driver.setSteps([
|
||||
// Left panel
|
||||
{ element: '#explorerinfo', popover: { title: 'Explorer Header Info', description: 'This section displays the explorer header with version information, documentation access, and tour navigation. Use the right and left arrow keys to navigate the tour.', side: 'left', align: 'start' } },
|
||||
{ element: '#database', popover: { title: 'Import from PDB', description: 'Load structures directly from PDB and PDB-IHM databases.', side: 'bottom', align: 'start' } },
|
||||
{ element: '#loader', popover: { title: 'Import from File', description: 'Load local files (.molx, .molj, .zip, .cif, .bcif) using this option.', side: 'bottom', align: 'start' } },
|
||||
{ element: '#example', popover: { title: 'Example Models and Tours', description: 'Select from a range of example models and tours provided.', side: 'left', align: 'start' } },
|
||||
{ element: '#session', popover: { title: 'Session Management', description: 'Download the current session in .molx format.', side: 'top', align: 'start' } },
|
||||
{ element: '#snaplist', popover: { title: 'Snapshot List', description: 'View and manage the list of snapshots. You can reorder them and edit their titles, keys, and descriptions. Snapshot states cannot be edited.', side: 'right', align: 'start' } },
|
||||
{ element: '#snap', popover: { title: 'Add Snapshot', description: 'Save the current state (e.g., camera position, color, visibility, etc.) in a snapshot with an optional title, key, and description.', side: 'right', align: 'start' } },
|
||||
{ element: '#snapoption', popover: { title: 'Snapshot Options', description: 'These options are saved in the snapshot. Set them before adding a snapshot to see their effect during animation playback.', side: 'right', align: 'start' } },
|
||||
{ element: '#exportanimation', popover: { title: 'Export Animation', description: 'Create movies or scenes with rocking, rotating, or snapshots animations.', side: 'right', align: 'start' } },
|
||||
{ element: '#viewportsettings', popover: { title: 'Viewport Settings', description: 'Advanced settings for the renderer and trackball.', side: 'right', align: 'start' } },
|
||||
// Viewport
|
||||
{ element: '#snapinfo', popover: { title: 'Snapshot Description', description: 'Save the current state (e.g., camera position, color, visibility, etc.) in a snapshot with an optional title, key, and description.', side: 'right', align: 'start' } },
|
||||
{ element: '#snapinfoctrl', popover: { title: 'Snapshot Description Control', description: 'Control the visibility and text size of the snapshot description widget.', side: 'right', align: 'start' } },
|
||||
// Right panel
|
||||
{ element: '#modelinfo', popover: { title: 'Model Information', description: 'Summary information about the model, if available.', side: 'right', align: 'start' } },
|
||||
{ element: '#selestyle', popover: { title: 'Selection Style', description: 'Choose the rendering style for entity selection accessed via Shift/Ctrl mouse. Options include: Color & Outline, Color, Outline.', side: 'right', align: 'start' } },
|
||||
{ element: '#seleinfo', popover: { title: 'Selection List', description: 'View the current list of selected entities.', side: 'right', align: 'start' } },
|
||||
{ element: '#measurements', popover: { title: 'Measurements', description: 'Use this widget to create labels, measure distances, angles, dihedral orientations, and planes for the selected entities.', side: 'right', align: 'start' } },
|
||||
{ element: '#quickstyles', popover: { title: 'Quick Styles', description: 'Change between a selection of style presets.', side: 'right', align: 'start' } },
|
||||
{ element: '#graphicsquality', popover: { title: 'Graphics Quality', description: 'Adjust the overall graphics quality. Lower quality improves performance. Options are: Ultra, Quality (Default), Balanced, Performance, Custom. Custom settings use the Culling & LOD values set in the Tree.', side: 'right', align: 'start' } },
|
||||
{ element: '#searchtree', popover: { title: 'Search', description: 'Filter the entity tree based on your queries.', side: 'right', align: 'start' } },
|
||||
{ element: '#grouptree', popover: { title: 'Group By', description: 'Change the grouping of the hierarchy tree, e.g., group by instance or by compartment.', side: 'right', align: 'start' } },
|
||||
{ element: '#tree', popover: { title: 'Tree Hierarchy', description: 'View the hierarchical tree of entity types in the model.', side: 'right', align: 'start' } },
|
||||
{ element: '#focusinfo', popover: { title: 'Selection Description', description: 'Detailed information about the current selection, if present in the loaded file.', side: 'right', align: 'start' } },
|
||||
{ popover: { title: 'Happy Exploring!', description: 'That’s all! Go ahead and start exploring or creating mesoscale tours.' } }
|
||||
]);
|
||||
driver.refresh();
|
||||
};
|
||||
|
||||
openHelp = () => {
|
||||
// open a new page with the documentation
|
||||
window.open('https://molstar.org/me-docs/', '_blank');
|
||||
};
|
||||
|
||||
toggleHelp = () => {
|
||||
const driver = (this.plugin.customState as MesoscaleExplorerState).driver;
|
||||
if (!driver || !driver.hasNextStep()) {
|
||||
this.setupDriver();
|
||||
}
|
||||
this.setState({ showHelp: !this.state.showHelp }, () => {
|
||||
if (this.state.showHelp && driver) {
|
||||
driver.drive(); // start at 0
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const driver = (this.plugin.customState as MesoscaleExplorerState).driver;
|
||||
if (!driver) return;
|
||||
|
||||
const help = <IconButton svg={HelpOutlineSvg} toggleState={false} small onClick={this.openHelp} title='Open the Documentation' />;
|
||||
const tour = <IconButton svg={TourSvg} toggleState={false} small onClick={this.toggleHelp} title='Start the interactive tour' />;
|
||||
return <>
|
||||
<div id='explorerinfo' style={{ display: 'flex', alignItems: 'center', padding: '4px 0 4px 8px' }} className='msp-help-text'>
|
||||
<h2 style={{ flexGrow: 1 }}>Mol* Mesoscale Explorer</h2>
|
||||
{tour}{help}
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
export class MesoQuickStylesControls extends CollapsableControls {
|
||||
defaultState() {
|
||||
return {
|
||||
isCollapsed: true,
|
||||
header: 'Quick Styles',
|
||||
brand: { accent: 'gray' as const, svg: MagicWandSvg }
|
||||
};
|
||||
}
|
||||
|
||||
renderControls() {
|
||||
return <>
|
||||
<MesoQuickStyles />
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
export class MesoQuickStyles extends PluginUIComponent {
|
||||
async default() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const p = this.plugin.canvas3d.props;
|
||||
this.plugin.canvas3d.setProps({
|
||||
renderer: {
|
||||
exposure: 1.1,
|
||||
},
|
||||
postprocessing: {
|
||||
...p.postprocessing,
|
||||
shadow: {
|
||||
name: 'on',
|
||||
params: {
|
||||
maxDistance: 80,
|
||||
steps: 3,
|
||||
tolerance: 1.0,
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
name: 'on',
|
||||
params: {
|
||||
scale: 1,
|
||||
threshold: 0.15,
|
||||
color: Color(0x000000),
|
||||
includeTransparent: false,
|
||||
}
|
||||
},
|
||||
dof: { name: 'off', params: {} },
|
||||
}
|
||||
});
|
||||
await updateStyle(this.plugin, {
|
||||
ignoreLight: true,
|
||||
material: { metalness: 0, roughness: 1.0, bumpiness: 0 },
|
||||
celShaded: false,
|
||||
illustrative: false,
|
||||
});
|
||||
}
|
||||
|
||||
async celshading() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const p = this.plugin.canvas3d.props;
|
||||
this.plugin.canvas3d.setProps({
|
||||
renderer: {
|
||||
exposure: 1.5,
|
||||
},
|
||||
postprocessing: {
|
||||
...p.postprocessing,
|
||||
shadow: {
|
||||
name: 'on',
|
||||
params: {
|
||||
maxDistance: 256,
|
||||
steps: 64,
|
||||
tolerance: 1.0,
|
||||
}
|
||||
},
|
||||
outline: { name: 'off', params: {} },
|
||||
dof: { name: 'off', params: {} },
|
||||
}
|
||||
});
|
||||
await updateStyle(this.plugin, {
|
||||
ignoreLight: false,
|
||||
material: { metalness: 0, roughness: 1.0, bumpiness: 0 },
|
||||
celShaded: true,
|
||||
illustrative: false,
|
||||
});
|
||||
}
|
||||
|
||||
async shinyDof() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const p = this.plugin.canvas3d.props;
|
||||
this.plugin.canvas3d.setProps({
|
||||
renderer: {
|
||||
exposure: 1.1,
|
||||
},
|
||||
postprocessing: {
|
||||
...p.postprocessing,
|
||||
shadow: {
|
||||
name: 'on',
|
||||
params: {
|
||||
maxDistance: 256,
|
||||
steps: 64,
|
||||
tolerance: 1.0,
|
||||
}
|
||||
},
|
||||
outline: { name: 'off', params: {} },
|
||||
dof: {
|
||||
name: 'on',
|
||||
params: {
|
||||
blurSize: 9,
|
||||
blurSpread: 1.0,
|
||||
inFocus: 0.0,
|
||||
PPM: 200.0,
|
||||
center: 'camera-target',
|
||||
mode: 'sphere',
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
await updateStyle(this.plugin, {
|
||||
ignoreLight: false,
|
||||
material: { metalness: 0, roughness: 0.2, bumpiness: 0 },
|
||||
celShaded: false,
|
||||
illustrative: false,
|
||||
});
|
||||
}
|
||||
|
||||
async illustrative() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const p = this.plugin.canvas3d.props;
|
||||
this.plugin.canvas3d.setProps({
|
||||
renderer: {
|
||||
exposure: 1.5,
|
||||
},
|
||||
postprocessing: {
|
||||
...p.postprocessing,
|
||||
shadow: {
|
||||
name: 'on',
|
||||
params: {
|
||||
maxDistance: 256,
|
||||
steps: 64,
|
||||
tolerance: 1.0,
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
name: 'on',
|
||||
params: {
|
||||
scale: 1,
|
||||
threshold: 0.15,
|
||||
color: Color(0x000000),
|
||||
includeTransparent: false,
|
||||
}
|
||||
},
|
||||
dof: { name: 'off', params: {} },
|
||||
}
|
||||
});
|
||||
await updateStyle(this.plugin, {
|
||||
ignoreLight: true,
|
||||
material: { metalness: 0, roughness: 1.0, bumpiness: 0 },
|
||||
celShaded: false,
|
||||
illustrative: true,
|
||||
});
|
||||
}
|
||||
|
||||
async shiny() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const p = this.plugin.canvas3d.props;
|
||||
this.plugin.canvas3d.setProps({
|
||||
renderer: {
|
||||
exposure: 1.5,
|
||||
},
|
||||
postprocessing: {
|
||||
...p.postprocessing,
|
||||
shadow: { name: 'off', params: {} },
|
||||
outline: { name: 'off', params: {} },
|
||||
dof: { name: 'off', params: {} },
|
||||
}
|
||||
});
|
||||
await updateStyle(this.plugin, {
|
||||
ignoreLight: false,
|
||||
material: { metalness: 0, roughness: 0.2, bumpiness: 0 },
|
||||
celShaded: false,
|
||||
illustrative: false,
|
||||
});
|
||||
}
|
||||
|
||||
async stylized() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const p = this.plugin.canvas3d.props;
|
||||
this.plugin.canvas3d.setProps({
|
||||
renderer: {
|
||||
exposure: 1.1,
|
||||
},
|
||||
postprocessing: {
|
||||
...p.postprocessing,
|
||||
shadow: {
|
||||
name: 'on',
|
||||
params: {
|
||||
maxDistance: 256,
|
||||
steps: 64,
|
||||
tolerance: 1.0,
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
name: 'on',
|
||||
params: {
|
||||
scale: 1,
|
||||
threshold: 0.15,
|
||||
color: Color(0x000000),
|
||||
includeTransparent: false,
|
||||
}
|
||||
},
|
||||
dof: { name: 'off', params: {} },
|
||||
}
|
||||
});
|
||||
await updateStyle(this.plugin, {
|
||||
ignoreLight: false,
|
||||
material: { metalness: 0, roughness: 0.2, bumpiness: 0 },
|
||||
celShaded: false,
|
||||
illustrative: true,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return <>
|
||||
<div className='msp-flex-row'>
|
||||
<Button noOverflow title='Applies default representation preset and sets outline and occlusion effects to default' onClick={() => this.default()} style={{ width: 'auto' }}>
|
||||
Default
|
||||
</Button>
|
||||
<Button noOverflow title='Applies celShading' onClick={() => this.celshading()} style={{ width: 'auto' }}>
|
||||
Cel-shaded
|
||||
</Button>
|
||||
<Button noOverflow title='Applies illustrative colors preset' onClick={() => this.illustrative()} style={{ width: 'auto' }}>
|
||||
Illustrative
|
||||
</Button>
|
||||
</div>
|
||||
<div className='msp-flex-row'>
|
||||
<Button noOverflow title='Apply shiny material to default' onClick={() => this.shiny()} style={{ width: 'auto' }}>
|
||||
Shiny
|
||||
</Button>
|
||||
<Button noOverflow title='Enable shiny material, outline, and illustrative colors' onClick={() => this.stylized()} style={{ width: 'auto' }}>
|
||||
Shiny-Illustrative
|
||||
</Button>
|
||||
<Button noOverflow title='Enable DOF and shiny material' onClick={() => this.shinyDof()} style={{ width: 'auto' }}>
|
||||
Shiny-DOF
|
||||
</Button>
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
}
|
||||
39
src/apps/mvs-stories/context.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { MVSData } from '../../extensions/mvs/mvs-data';
|
||||
import type { MVSStoriesViewerModel } from './elements/viewer';
|
||||
|
||||
export type MVSStoriesCommand =
|
||||
| { kind: 'load-mvs', format?: 'mvsj' | 'mvsx', url?: string, data?: MVSData | string | Uint8Array }
|
||||
|
||||
|
||||
export class MVSStoriesContext {
|
||||
commands = new BehaviorSubject<MVSStoriesCommand | undefined>(undefined);
|
||||
state = {
|
||||
viewers: new BehaviorSubject<{ name?: string, model: MVSStoriesViewerModel }[]>([]),
|
||||
currentStoryData: new BehaviorSubject<string | Uint8Array | undefined>(undefined),
|
||||
isLoading: new BehaviorSubject(false),
|
||||
};
|
||||
|
||||
dispatch(command: MVSStoriesCommand) {
|
||||
this.commands.next(command);
|
||||
}
|
||||
|
||||
constructor(public name?: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getMVSStoriesContext(options?: { name?: string, container?: object }): MVSStoriesContext {
|
||||
const container: any = options?.container ?? window;
|
||||
container.componentContexts ??= {};
|
||||
const name = options?.name ?? '<default>';
|
||||
if (!container.componentContexts[name]) {
|
||||
container.componentContexts[name] = new MVSStoriesContext(options?.name);
|
||||
}
|
||||
return container.componentContexts[name];
|
||||
}
|
||||
2
src/apps/mvs-stories/elements/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import './snapshot-markdown';
|
||||
import './viewer';
|
||||
129
src/apps/mvs-stories/elements/snapshot-markdown.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { BehaviorSubject, distinctUntilChanged, map } from 'rxjs';
|
||||
import { PluginComponent } from '../../../mol-plugin-state/component';
|
||||
import { getMVSStoriesContext, MVSStoriesContext } from '../context';
|
||||
import { MVSStoriesViewerModel } from './viewer';
|
||||
import { useBehavior } from '../../../mol-plugin-ui/hooks/use-behavior';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { PluginStateSnapshotManager } from '../../../mol-plugin-state/manager/snapshots';
|
||||
import { PluginReactContext } from '../../../mol-plugin-ui/base';
|
||||
import { CSSProperties } from 'react';
|
||||
import { Markdown } from '../../../mol-plugin-ui/controls/markdown';
|
||||
|
||||
export class MVSStoriesSnapshotMarkdownModel extends PluginComponent {
|
||||
readonly context: MVSStoriesContext;
|
||||
root: HTMLElement | undefined = undefined;
|
||||
|
||||
state = new BehaviorSubject<{
|
||||
entry?: PluginStateSnapshotManager.Entry,
|
||||
index?: number,
|
||||
all: PluginStateSnapshotManager.Entry[],
|
||||
}>({ all: [] });
|
||||
|
||||
get viewer() {
|
||||
return this.context.state.viewers.value?.find(v => this.options?.viewerName === v.name);
|
||||
}
|
||||
|
||||
sync() {
|
||||
const mng = this.viewer?.model.plugin?.managers.snapshot;
|
||||
this.state.next({
|
||||
entry: mng?.current,
|
||||
index: mng?.current ? mng?.getIndex(mng.current) : undefined,
|
||||
all: mng?.state.entries.toArray() ?? [],
|
||||
});
|
||||
}
|
||||
|
||||
async mount(root: HTMLElement) {
|
||||
this.root = root;
|
||||
|
||||
createRoot(root).render(<MVSStoriesSnapshotMarkdownUI model={this} />);
|
||||
|
||||
let currentViewer: MVSStoriesViewerModel | undefined = undefined;
|
||||
let sub: { unsubscribe: () => void } | undefined = undefined;
|
||||
this.subscribe(this.context.state.viewers.pipe(
|
||||
map(xs => xs.find(v => this.options?.viewerName === v.name)),
|
||||
distinctUntilChanged((a, b) => a?.model === b?.model)
|
||||
), viewer => {
|
||||
if (currentViewer !== viewer) {
|
||||
currentViewer = viewer?.model;
|
||||
sub?.unsubscribe();
|
||||
}
|
||||
if (!viewer) return;
|
||||
sub = this.subscribe(viewer.model.plugin?.managers.snapshot.events.changed, () => {
|
||||
this.sync();
|
||||
});
|
||||
this.sync();
|
||||
});
|
||||
|
||||
this.sync();
|
||||
}
|
||||
|
||||
constructor(private options?: { context?: { name?: string, container?: object }, viewerName?: string }) {
|
||||
super();
|
||||
|
||||
this.context = getMVSStoriesContext(options?.context);
|
||||
}
|
||||
}
|
||||
|
||||
export function MVSStoriesSnapshotMarkdownUI({ model }: { model: MVSStoriesSnapshotMarkdownModel }) {
|
||||
const state = useBehavior(model.state);
|
||||
const isLoading = useBehavior(model.context.state.isLoading);
|
||||
|
||||
const style: CSSProperties = { display: 'flex', flexDirection: 'column', height: '100%' };
|
||||
const className = 'mvs-stories-markdown-explanation';
|
||||
|
||||
if (isLoading) {
|
||||
return <div style={style} className={className}>
|
||||
<i>Loading...</i>
|
||||
</div>;
|
||||
}
|
||||
|
||||
if (state.all.length === 0) {
|
||||
return <div style={style} className={className}>
|
||||
<i>No snapshot loaded or no description available</i>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <div style={style} className={className}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', width: '100%', gap: '8px' }}>
|
||||
<span style={{ lineHeight: '38px', minWidth: 60, maxWidth: 60, flexShrink: 0 }}>{typeof state.index === 'number' ? state.index + 1 : '-'}/{state.all.length}</span>
|
||||
<button onClick={() => model.viewer?.model.plugin?.managers.snapshot.applyNext(-1)} style={{ flexGrow: 1, flexShrink: 0 }}>Prev</button>
|
||||
<button onClick={() => model.viewer?.model.plugin?.managers.snapshot.applyNext(1)} style={{ flexGrow: 1, flexShrink: 0 }}>Next</button>
|
||||
</div>
|
||||
<div style={{ flexGrow: 1, overflow: 'hidden', overflowY: 'auto', position: 'relative' }}>
|
||||
<div style={{ position: 'absolute', inset: 0 }}>
|
||||
<PluginReactContext.Provider value={model.viewer?.model.plugin as any}>
|
||||
<Markdown>{state.entry?.description ?? 'Description not available'}</Markdown>
|
||||
</PluginReactContext.Provider>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export class MVSStoriesSnapshotMarkdownViewer extends HTMLElement {
|
||||
private model: MVSStoriesSnapshotMarkdownModel | undefined = undefined;
|
||||
|
||||
async connectedCallback() {
|
||||
this.model = new MVSStoriesSnapshotMarkdownModel({
|
||||
context: { name: this.getAttribute('context-name') ?? undefined },
|
||||
viewerName: this.getAttribute('viewer-name') ?? undefined,
|
||||
});
|
||||
await this.model.mount(this);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.model?.dispose();
|
||||
this.model = undefined;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('mvs-stories-snapshot-markdown', MVSStoriesSnapshotMarkdownViewer);
|
||||
131
src/apps/mvs-stories/elements/viewer.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { MolViewSpec } from '../../../extensions/mvs/behavior';
|
||||
import { loadMVSData } from '../../../extensions/mvs/components/formats';
|
||||
import { MVSData } from '../../../extensions/mvs/mvs-data';
|
||||
import { StringLike } from '../../../mol-io/common/string-like';
|
||||
import { PluginComponent } from '../../../mol-plugin-state/component';
|
||||
import { createPluginUI } from '../../../mol-plugin-ui';
|
||||
import { renderReact18 } from '../../../mol-plugin-ui/react18';
|
||||
import { DefaultPluginUISpec } from '../../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../../mol-plugin/config';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { PluginSpec } from '../../../mol-plugin/spec';
|
||||
import { getMVSStoriesContext, MVSStoriesContext } from '../context';
|
||||
|
||||
export class MVSStoriesViewerModel extends PluginComponent {
|
||||
readonly context: MVSStoriesContext;
|
||||
plugin?: PluginContext = undefined;
|
||||
|
||||
async mount(root: HTMLElement) {
|
||||
const spec = DefaultPluginUISpec();
|
||||
this.plugin = await createPluginUI({
|
||||
target: root,
|
||||
render: renderReact18,
|
||||
spec: {
|
||||
...spec,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false,
|
||||
controlsDisplay: 'landscape',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
remoteState: 'none',
|
||||
viewport: {
|
||||
snapshotDescription: EmptyDescription,
|
||||
}
|
||||
},
|
||||
behaviors: [
|
||||
...spec.behaviors,
|
||||
PluginSpec.Behavior(MolViewSpec)
|
||||
],
|
||||
config: [
|
||||
[PluginConfig.Viewport.ShowAnimation, false],
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribe(this.context.commands, async (cmd) => {
|
||||
if (!cmd || !this.plugin) return;
|
||||
|
||||
try {
|
||||
this.context.state.isLoading.next(true);
|
||||
if (cmd.kind === 'load-mvs') {
|
||||
let loadedData: MVSData | StringLike | Uint8Array | undefined;
|
||||
if (cmd.url) {
|
||||
const data = await this.plugin.runTask(this.plugin.fetch({ url: cmd.url, type: cmd.format === 'mvsx' ? 'binary' : 'string' }));
|
||||
loadedData = await loadMVSData(this.plugin, data, cmd.format ?? 'mvsj', { sourceUrl: cmd.url });
|
||||
} else if (cmd.data) {
|
||||
loadedData = await loadMVSData(this.plugin, cmd.data, cmd.format ?? 'mvsj');
|
||||
}
|
||||
if (StringLike.is(loadedData) || loadedData instanceof Uint8Array) {
|
||||
this.context.state.currentStoryData.next(loadedData as string | Uint8Array);
|
||||
} else if (loadedData) {
|
||||
this.context.state.currentStoryData.next(JSON.stringify(loadedData));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
PluginCommands.Toast.Show(
|
||||
this.plugin,
|
||||
{ key: '<mvsload>', title: 'Error', message: e?.message ? `${e?.message}` : `${e}`, timeoutMs: 10000 }
|
||||
);
|
||||
} finally {
|
||||
this.context.state.isLoading.next(false);
|
||||
}
|
||||
});
|
||||
|
||||
const viewers = this.context.state.viewers.value;
|
||||
const next = [...viewers, { name: this.options?.name, model: this }];
|
||||
this.context.state.viewers.next(next);
|
||||
}
|
||||
|
||||
constructor(private options?: { context?: { name?: string, container?: object }, name?: string }) {
|
||||
super();
|
||||
|
||||
this.context = getMVSStoriesContext(options?.context);
|
||||
|
||||
const viewers = this.context.state.viewers.value;
|
||||
const index = viewers.findIndex(v => v.name === options?.name);
|
||||
if (index >= 0) {
|
||||
const next = [...viewers];
|
||||
next[index].model.dispose();
|
||||
next.splice(index, 0);
|
||||
this.context.state.viewers.next(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function EmptyDescription() {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export class MVSStoriesViewer extends HTMLElement {
|
||||
private model: MVSStoriesViewerModel | undefined = undefined;
|
||||
|
||||
async connectedCallback() {
|
||||
this.model = new MVSStoriesViewerModel({
|
||||
name: this.getAttribute('name') ?? undefined,
|
||||
context: { name: this.getAttribute('context-name') ?? undefined },
|
||||
});
|
||||
await this.model.mount(this);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.model?.dispose();
|
||||
this.model = undefined;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('mvs-stories-viewer', MVSStoriesViewer);
|
||||
BIN
src/apps/mvs-stories/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |