Compare commits

...

127 Commits

Author SHA1 Message Date
Alexander Rose
944d370c14 0.3.2 2019-10-09 16:59:24 -07:00
Alexander Rose
74f9aa6af6 package updates 2019-10-09 16:55:44 -07:00
Alexander Rose
c20c9c9917 fixed setting CollapsableControls default state 2019-10-09 16:54:15 -07:00
Alexander Rose
4801435d72 add controls to create image 2019-10-09 16:10:36 -07:00
Alexander Rose
33fd105ef7 add ImagePass 2019-10-09 16:09:43 -07:00
Alexander Rose
3ea3fb8984 support rendering with transparent background 2019-10-09 16:08:48 -07:00
Alexander Rose
b4bbc544ca 0.3.1 2019-10-07 17:27:35 -07:00
Alexander Rose
5f880e920b add focus button to StructureSelectionControls 2019-10-07 17:26:16 -07:00
Alexander Rose
bcce801dd7 ensure sequence markers are up-to-date 2019-10-07 16:10:21 -07:00
Alexander Rose
00f9dcee4a added StructureSymmetryMatesFromModel transform, fixes for findMatesRadius 2019-10-07 15:31:54 -07:00
Alexander Rose
505af2bc96 fix help for scrollFocus 2019-10-07 12:04:20 -07:00
Alexander Rose
c217aab5fc reduced default unicell cage thickness 2019-10-07 11:23:50 -07:00
Alexander Rose
5d5fd0028f added operator name & hkl color themes 2019-10-07 11:20:10 -07:00
Alexander Rose
c88693dfdd improved color theme legend with labels from data 2019-10-07 11:18:08 -07:00
David Sehnal
0a16ec1bd2 mol-plugin: added TextInput wrapper and RGB color input 2019-10-05 14:52:00 +02:00
Alexander Rose
6f36a3c724 0.3.0 2019-10-04 17:21:16 -07:00
Alexander Rose
71496bd1ef package updates 2019-10-04 17:20:38 -07:00
Alexander Rose
c5a99a7c12 improved lociApply for whole structures 2019-10-04 17:07:08 -07:00
Alexander Rose
cfaf33d696 also look for 4 and 7 member sugar rings 2019-10-04 15:55:47 -07:00
Alexander Rose
e24c76d2bf update modifierkeys on mouse input 2019-10-04 15:40:14 -07:00
Alexander Rose
b6273205a2 removed duplicate type 2019-10-04 15:18:33 -07:00
Alexander Rose
b38193aa19 for unknown carbs draw polygonal prisms 2019-10-04 15:13:00 -07:00
Alexander Rose
f9cfacae23 sequence widget, bind onMouseLeave 2019-10-04 12:26:05 -07:00
Alexander Rose
ac46317dc6 fix lighting for orthographic projection close to camera 2019-10-04 11:53:38 -07:00
Alexander Rose
2b492a5a61 lighter monospace font 2019-10-04 10:44:08 -07:00
Alexander Rose
a2133657f0 chnaged label Unit->Chain in sequence widget 2019-10-04 10:42:07 -07:00
David Sehnal
e8de45789f mol-plugin: optimized sequence control 2019-10-04 16:52:36 +02:00
David Sehnal
3d2bd167ca mol-plugin: use monospace font for sequence 2019-10-04 15:34:59 +02:00
David Sehnal
504c8626dc mol-plugin: fixed PluginCommands.State.Highlight (wip) 2019-10-04 15:22:26 +02:00
Alexander Rose
f4b29dc7e0 ui, offset expanded color param 2019-10-03 17:24:24 -07:00
Alexander Rose
b34c5c743b tweaked ligandPlusConnected selection 2019-10-03 16:40:44 -07:00
Alexander Rose
c57311d6c0 carbohydrate improvements, updated carb table 2019-10-03 16:04:32 -07:00
Alexander Rose
4d786dc697 fix missing awaits in StructureRepresentationHelper.preset 2019-10-03 11:50:18 -07:00
Alexander Rose
062e3e055a show full compId for modified residues in sequence widget 2019-10-03 10:05:31 -07:00
Alexander Rose
1465174a45 Merge branch 'master' of https://github.com/molstar/molstar 2019-10-02 17:57:38 -07:00
Alexander Rose
cc00ada5a3 add model Unitcell representation 2019-10-02 17:57:19 -07:00
Alexander Rose
cbf312b62d calculate symmetry operators for transform around deposited coordinates 2019-10-02 17:56:41 -07:00
Alexander Rose
2be3144086 add .volume to SpacegroupCell 2019-10-02 17:55:03 -07:00
Alexander Rose
da3acd9d19 calculate model center as dynamic prop 2019-10-02 17:53:51 -07:00
Alexander Rose
34b048479b added arrayMinMax util 2019-10-02 17:53:03 -07:00
Alexander Rose
ca92931bf2 mol-geo improvements for mesh, cage and primitive 2019-10-02 17:52:48 -07:00
David Sehnal
0d3daeb823 mol-plugin: semi-transparent viewport controls background 2019-10-02 13:11:50 +02:00
David Sehnal
58b1d7e0eb mol-model & formats: added fallback when chem_comp category is missing 2019-10-02 12:46:23 +02:00
Alexander Rose
c5997ed056 reset buttons and modifier keys state when browser window looses focus 2019-10-01 14:11:01 -07:00
Alexander Rose
da1deee7f3 switched off marker interpolation for webgl2 2019-10-01 10:58:26 -07:00
Alexander Rose
a4eaff3175 add background to canvas icons for legibility with different render background colors 2019-10-01 10:30:44 -07:00
Alexander Rose
211cfc0bd3 fix highlight persisting after rotation 2019-10-01 09:52:18 -07:00
David Sehnal
c0f85b691d mol-plugin: reverted msp-layout-region background !important, because it broke the visual style 2019-10-01 17:42:42 +02:00
David Sehnal
6b93d58ea6 mol-plugin: updated color select control 2019-10-01 17:33:49 +02:00
Alexander Rose
5bce423b49 fully mark carbohydrate terminal links from StructureElement.Loci 2019-09-30 17:38:05 -07:00
Alexander Rose
01b0dde503 set plugin Context.customState: unknown 2019-09-30 16:38:55 -07:00
Alexander Rose
ed1bc8cb7d ensure single element loci have some volume in volume interaction behavior 2019-09-30 15:52:55 -07:00
Alexander Rose
abe559261b show insertion code in labels 2019-09-30 14:36:42 -07:00
Alexander Rose
b0cdf22cb8 various interaction behaviors improvements 2019-09-30 14:26:59 -07:00
Alexander Rose
a61ba71f1e handle mixed sizes in link visuals 2019-09-30 12:11:07 -07:00
Alexander Rose
7061d57559 tweak some bond distance thresholds 2019-09-30 11:06:49 -07:00
Alexander Rose
83a1e6c87c improved entity subtype assignment (use chem comp type) 2019-09-30 11:06:19 -07:00
Alexander Rose
c18888b8de 0.2.15 2019-09-27 17:19:57 -07:00
Alexander Rose
2d80935e00 ignore non-identity 'given' NCS operators 2019-09-27 17:18:14 -07:00
Alexander Rose
4287d158b6 add Mat3.isIdentity 2019-09-27 17:18:06 -07:00
Alexander Rose
4e9b569178 show logo in empty viewer 2019-09-27 16:48:10 -07:00
Alexander Rose
5d626d291b added viewer favicon 2019-09-27 16:48:00 -07:00
Alexander Rose
afa4a01c44 package updates 2019-09-27 16:41:53 -07:00
Alexander Rose
3c4a23c5a3 ensure msp-layout-region background 2019-09-27 15:22:27 -07:00
Alexander Rose
8d92c976d9 css 'select' tweaks 2019-09-27 13:19:01 -07:00
Alexander Rose
1a9adfad29 0.2.14 2019-09-27 11:17:53 -07:00
Alexander Rose
a9e9a5974d fix volumeserver cmd arg types 2019-09-27 11:10:11 -07:00
Alexander Rose
27160aa8fe fix stale array use 2019-09-26 15:33:52 -07:00
David Sehnal
6a71af00cf mol-plugin: Toast support 2019-09-26 16:18:57 +02:00
David Sehnal
6c0938db50 mol-plugin: added InitVolumeStreaming binding param 2019-09-26 15:11:13 +02:00
Alexander Rose
b76173c82f 0.2.13 2019-09-25 17:25:25 -07:00
Alexander Rose
0bcf2b1ff4 fix wrong style.overflow assignment 2019-09-25 16:07:25 -07:00
Alexander Rose
d074415a26 added more param docs 2019-09-25 11:55:00 -07:00
Alexander Rose
42f3e38026 0.2.12 2019-09-24 14:21:58 -07:00
Alexander Rose
74c16ee7ba package updates 2019-09-24 14:19:44 -07:00
Alexander Rose
f6ef22b917 added link cylinder to ellopsoids repr 2019-09-24 14:16:19 -07:00
Alexander Rose
c3f3d7efda check if atomistic before query modified 2019-09-24 12:20:38 -07:00
Alexander Rose
61d44efdc4 fix typo 2019-09-24 12:12:19 -07:00
Alexander Rose
ad200c86ec Merge branch 'master' of https://github.com/molstar/molstar 2019-09-24 12:10:54 -07:00
Alexander Rose
4aecf4e0b4 added ElementSequenceWrapper & ChainSequenceWrapper 2019-09-24 12:09:54 -07:00
Alexander Rose
d81f37c78b added AtomicUnit.residueCount 2019-09-24 12:07:26 -07:00
Alexander Rose
c2979ce5ab StructureSequence, handle empty coarse hierarchies 2019-09-24 12:06:55 -07:00
Alexander Rose
09c7edce88 smaller terminal links and dashed terminal links to metals 2019-09-24 10:11:42 -07:00
David Sehnal
beefb79258 mol-plugin: better default Structure Complex, extended StructureComplexElement transform 2019-09-24 15:17:05 +02:00
David Sehnal
529c6ac81c mol-plugin: fix SelectionFromScript & volume streaming bugs 2019-09-24 14:07:42 +02:00
Alexander Rose
84b988ea96 fix csv parser choking on newline at end of file 2019-09-23 16:46:35 -07:00
Alexander Rose
35e978efc9 added Table.toArrays 2019-09-23 16:45:58 -07:00
David Sehnal
19559d01f7 mol-state: call canAutoUpdate with correct parameters 2019-09-23 16:53:31 +02:00
David Sehnal
56cec343e2 Proteopedia wrapper fix 2019-09-23 16:12:40 +02:00
David Sehnal
20c9bd0130 mol-plugin: CSS fix 2019-09-23 15:56:48 +02:00
David Sehnal
1336997c58 mol-plugin: volume streaming support for LinkLoci, update "current box" when switching to surroundings, init behavior fix 2019-09-23 15:47:55 +02:00
David Sehnal
b178fdefdc mol-plugin: Camera focus duration default value fix 2019-09-23 14:59:44 +02:00
Alexander Rose
453d60060a ui, print length of sequences that are too long 2019-09-22 19:48:59 -07:00
Alexander Rose
901fac97a0 basic support for .3dg files 2019-09-22 19:44:37 -07:00
Alexander Rose
29b6a88343 check for Performance object capabilities 2019-09-22 19:43:13 -07:00
Alexander Rose
a2217c7fc6 fix csv parser chunking 2019-09-22 19:42:31 -07:00
Alexander Rose
3eec30aa42 sequence improvements, create sequence from coarse elements 2019-09-22 18:29:46 -07:00
Alexander Rose
f352e19e90 config for debugging 2019-09-22 09:32:19 -07:00
Alexander Rose
be65ef89bc 0.2.11 2019-09-20 17:23:25 -07:00
Alexander Rose
cfc46073c0 package updates 2019-09-20 17:22:39 -07:00
Alexander Rose
1819a2bbda add wwpdb provider for emdb contourLevel 2019-09-20 17:14:53 -07:00
Alexander Rose
aa3a42f94e added simple xml parser 2019-09-20 17:13:13 -07:00
Alexander Rose
4bff55b612 improve theme applicability checks 2019-09-20 15:08:55 -07:00
Alexander Rose
dbc4e09909 add 'disorder' to uncertainty theme label/description 2019-09-20 14:46:46 -07:00
Alexander Rose
0a074c8a66 add modelNum to location labels 2019-09-20 11:10:40 -07:00
Alexander Rose
8b314ebb75 add border to .msp-layout-standard 2019-09-20 10:20:12 -07:00
Alexander Rose
4dc0791aeb tweaked slider rail/handle color 2019-09-20 10:06:59 -07:00
Alexander Rose
e3483f11b1 adjusted maxCovalentHydrogenBondingLength 2019-09-20 09:32:45 -07:00
Alexander Rose
244a678ab7 fix Props.residue.label_comp_id for micro het 2019-09-19 19:11:57 -07:00
Alexander Rose
8e1d44fabc fixes, seq wrapper mark every loci, binding docs & cleanup 2019-09-19 18:33:44 -07:00
Alexander Rose
954a5b58e0 tweaked structure and volume surroundings behaviors 2019-09-19 18:10:07 -07:00
Alexander Rose
53223bc72b support EveryLoci in StructureRepresentation.mark 2019-09-19 17:10:28 -07:00
Alexander Rose
a11a1fa07e formating 2019-09-19 17:09:26 -07:00
Alexander Rose
9ab4001544 get Behavior defaultParams from transformer params 2019-09-19 17:09:15 -07:00
Alexander Rose
3e3b71d230 added Interval.ofLength 2019-09-19 17:08:36 -07:00
Alexander Rose
186929269b refactored bindings and interactivity 2019-09-19 17:08:20 -07:00
Alexander Rose
afa7a04af0 various ui tweaks, added CollapsableControls 2019-09-19 17:03:06 -07:00
Alexander Rose
2861d12f04 inline help for params, color theme legend ui 2019-09-16 16:47:58 -07:00
Alexander Rose
65e1212b2f wip, focusZoom action 2019-09-13 16:30:55 -07:00
Alexander Rose
47136c8b71 wip, focus input bindings 2019-09-13 12:57:46 -07:00
Alexander Rose
375b829562 wip, simplified camera class 2019-09-13 10:53:56 -07:00
Alexander Rose
2718d42b01 wip, input bindings, radius-based clipping 2019-09-12 16:47:19 -07:00
Alexander Rose
93d4118c0a Merge branch 'master' of https://github.com/molstar/molstar 2019-09-12 09:37:31 -07:00
David Sehnal
3a2a47af12 removed unused comments 2019-09-12 16:09:47 +02:00
David Sehnal
0eacdfca85 added transition duration effect to camera focus 2019-09-12 16:07:48 +02:00
David Sehnal
a7388be25f mol-canvas3d: fixed camera focus 2019-09-12 15:34:27 +02:00
Alexander Rose
5fcdcb1275 webpack.config cleanup, package updates 2019-09-11 17:57:54 -07:00
197 changed files with 4865 additions and 1622 deletions

View File

@@ -4,7 +4,9 @@
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"firsttris.vscode-jest-runner",
"ms-vscode.vscode-typescript-tslint-plugin",
"msjsdiag.debugger-for-chrome",
"slevesque.shader",
"stpn.vscode-graphql",
"wayou.vscode-todo-highlight"

16
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Mol* Viewer",
"url": "http://localhost:1338/build/viewer/index.html",
"sourceMaps": true,
"webRoot": "${workspaceFolder}"
}
]
}

View File

@@ -143,7 +143,7 @@ To get syntax highlighting for shader and graphql files add the following to Vis
## Deploy
npm run test
NODE_ENV=production npm run build
npm run build
node ./scripts/deploy.js # currently updates the viewer on molstar.org/viewer
## Contributing

153
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.2.10",
"version": "0.3.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1363,14 +1363,14 @@
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw=="
},
"@types/node": {
"version": "12.7.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.4.tgz",
"integrity": "sha512-W0+n1Y+gK/8G2P/piTkBBN38Qc5Q1ZSO6B5H3QmPCUewaiXOo2GCAWZ4ElZCcNhjJuBSUSLGFUJnmlCn5+nxOQ=="
"version": "12.7.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz",
"integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ=="
},
"@types/node-fetch": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.0.tgz",
"integrity": "sha512-TLFRywthBgL68auWj+ziWu+vnmmcHCDFC/sqCOQf1xTz4hRq8cu79z8CtHU9lncExGBsB8fXA4TiLDLt6xvMzw==",
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.2.tgz",
"integrity": "sha512-djYYKmdNRSBtL1x4CiE9UJb9yZhwtI1VC+UxZD0psNznrUj80ywsxKlEGAE+QL1qvLjPbfb24VosjkYM6W4RSQ==",
"requires": {
"@types/node": "*"
}
@@ -1382,9 +1382,9 @@
"dev": true
},
"@types/prop-types": {
"version": "15.7.1",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz",
"integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg=="
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
},
"@types/range-parser": {
"version": "1.2.3",
@@ -1392,18 +1392,18 @@
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA=="
},
"@types/react": {
"version": "16.9.2",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz",
"integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==",
"version": "16.9.5",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.5.tgz",
"integrity": "sha512-jQ12VMiFOWYlp+j66dghOWcmDDwhca0bnlcTxS4Qz/fh5gi6wpaZDthPEu/Gc/YlAuO87vbiUXL8qKstFvuOaA==",
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
}
},
"@types/react-dom": {
"version": "16.9.0",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.0.tgz",
"integrity": "sha512-OL2lk7LYGjxn4b0efW3Pvf2KBVP0y1v3wip1Bp7nA79NkOpElH98q3WdCEdDj93b2b0zaeBG9DvriuKjIK5xDA==",
"version": "16.9.1",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.1.tgz",
"integrity": "sha512-1S/akvkKr63qIUWVu5IKYou2P9fHLb/P2VAwyxVV85JGaGZTcUniMiTuIqM3lXFB25ej6h+CYEQ27ERVwi6eGA==",
"requires": {
"@types/react": "*"
}
@@ -3051,13 +3051,13 @@
}
},
"concurrently": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.1.2.tgz",
"integrity": "sha512-Kim9SFrNr2jd8/0yNYqDTFALzUX1tvimmwFWxmp/D4mRI+kbqIIwE2RkBDrxS2ic25O1UgQMI5AtBqdtX3ynYg==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-5.0.0.tgz",
"integrity": "sha512-1yDvK8mduTIdxIxV9C60KoiOySUl/lfekpdbI+U5GXaPrgdffEavFa9QZB3vh68oWOpbCC+TuvxXV9YRPMvUrA==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
"date-fns": "^1.30.1",
"date-fns": "^2.0.1",
"lodash": "^4.17.15",
"read-pkg": "^4.0.1",
"rxjs": "^6.5.2",
@@ -3099,9 +3099,9 @@
}
},
"date-fns": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
"integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.4.1.tgz",
"integrity": "sha512-2RhmH/sjDSCYW2F3ZQxOUx/I7PvzXpi89aQL2d3OAxSTwLx6NilATeUbe0menFE3Lu5lFkOFci36ivimwYHHxw==",
"dev": true
},
"supports-color": {
@@ -3520,9 +3520,9 @@
}
},
"csstype": {
"version": "2.6.6",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz",
"integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg=="
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.7.tgz",
"integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ=="
},
"currently-unhandled": {
"version": "0.4.1",
@@ -3534,9 +3534,9 @@
}
},
"cyclist": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
"integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
"dev": true
},
"d": {
@@ -5813,9 +5813,9 @@
"dev": true
},
"graphql": {
"version": "14.5.4",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-14.5.4.tgz",
"integrity": "sha512-dPLvHoxy5m9FrkqWczPPRnH0X80CyvRE6e7Fa5AWEqEAzg9LpxHvKh24po/482E6VWHigOkAmb4xCp6P9yT9gw==",
"version": "14.5.8",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-14.5.8.tgz",
"integrity": "sha512-MMwmi0zlVLQKLdGiMfWkgQD7dY/TUKt4L+zgJ/aR0Howebod3aNgP5JkgvAULiR2HPVZaP2VEElqtdidHweLkg==",
"requires": {
"iterall": "^1.2.2"
}
@@ -6586,9 +6586,9 @@
"dev": true
},
"handlebars": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz",
"integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.3.0.tgz",
"integrity": "sha512-7XlnO8yBXOdi7AzowjZssQr47Ctidqm7GbgARapOaqSN9HQhlClnOkR9HieGauIT3A8MBC6u9wPCXs97PCYpWg==",
"dev": true,
"requires": {
"neo-async": "^2.6.0",
@@ -9440,6 +9440,12 @@
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
"dev": true
},
"lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
@@ -10629,12 +10635,12 @@
"dev": true
},
"parallel-transform": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz",
"integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
"integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
"dev": true,
"requires": {
"cyclist": "~0.2.2",
"cyclist": "^1.0.1",
"inherits": "^2.0.3",
"readable-stream": "^2.1.5"
}
@@ -10649,9 +10655,9 @@
}
},
"parse-asn1": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz",
"integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==",
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
"integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==",
"dev": true,
"requires": {
"asn1.js": "^4.0.0",
@@ -11335,9 +11341,9 @@
}
},
"react": {
"version": "16.9.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.9.0.tgz",
"integrity": "sha512-+7LQnFBwkiw+BobzOF6N//BdoNw0ouwmSJTEm9cglOOmsg/TMiFHZLe2sEoN5M7LgJTj9oHH0gxklfnQe66S1w==",
"version": "16.10.2",
"resolved": "https://registry.npmjs.org/react/-/react-16.10.2.tgz",
"integrity": "sha512-MFVIq0DpIhrHFyqLU0S3+4dIcBhhOvBE8bJ/5kHPVOVaGdo0KuiQzpcjCPsf585WvhypqtrMILyoE2th6dT+Lw==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
@@ -11345,14 +11351,14 @@
}
},
"react-dom": {
"version": "16.9.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.9.0.tgz",
"integrity": "sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ==",
"version": "16.10.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.10.2.tgz",
"integrity": "sha512-kWGDcH3ItJK4+6Pl9DZB16BXYAZyrYQItU4OMy0jAkv5aNqc+mAKb4TpFtAteI6TJZu+9ZlNhaeNQSVQDHJzkw==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.15.0"
"scheduler": "^0.16.2"
}
},
"react-is": {
@@ -12472,9 +12478,9 @@
"dev": true
},
"scheduler": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz",
"integrity": "sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==",
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-BqYVWqwz6s1wZMhjFvLfVR5WXP7ZY32M/wYPo04CcuPM7XZEbV2TBNW7Z0UkguPTl0dWMA59VbNXxK6q+pHItg==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@@ -13274,9 +13280,9 @@
}
},
"swagger-ui-dist": {
"version": "3.23.9",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.23.9.tgz",
"integrity": "sha512-YfE6T0ls96vZhziGlEwdlqRH5S5IrpLSG3XWYHRHtOQ8npd7xGEEOUJL3xOypvB4/iE6yvxodc4SALZ5nILqkw=="
"version": "3.23.11",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.23.11.tgz",
"integrity": "sha512-ipENHHH/sqpngTpHXUwg55eAOZ7b2UVayUwwuWPA6nQSPhjBVXX4zOPpNKUwQIFOl3oIwVvZF7mqoxH7pMgVzA=="
},
"swap-case": {
"version": "1.1.2",
@@ -13318,9 +13324,9 @@
}
},
"terser": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.2.1.tgz",
"integrity": "sha512-cGbc5utAcX4a9+2GGVX4DsenG6v0x3glnDi5hx8816X1McEAwPlPgRtXPJzSBsbpILxZ8MQMT0KvArLuE0HP5A==",
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.3.2.tgz",
"integrity": "sha512-obxk4x19Zlzj9zY4QeXj9iPCb5W8YGn4v3pn4/fHj0Nw8+R7N02Kvwvz9VpOItCZZD8RC+vnYCDL0gP6FAJ7Xg==",
"dev": true,
"requires": {
"commander": "^2.20.0",
@@ -13657,15 +13663,16 @@
}
},
"ts-jest": {
"version": "24.0.2",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-24.0.2.tgz",
"integrity": "sha512-h6ZCZiA1EQgjczxq+uGLXQlNgeg02WWJBbeT8j6nyIBRQdglqbvzDoHahTEIiS6Eor6x8mK6PfZ7brQ9Q6tzHw==",
"version": "24.1.0",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-24.1.0.tgz",
"integrity": "sha512-HEGfrIEAZKfu1pkaxB9au17b1d9b56YZSqz5eCVE8mX68+5reOvlM93xGOzzCREIov9mdH7JBG+s0UyNAqr0tQ==",
"dev": true,
"requires": {
"bs-logger": "0.x",
"buffer-from": "1.x",
"fast-json-stable-stringify": "2.x",
"json5": "2.x",
"lodash.memoize": "4.x",
"make-error": "1.x",
"mkdirp": "0.x",
"resolve": "1.x",
@@ -13695,9 +13702,9 @@
"dev": true
},
"semver": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"yargs-parser": {
@@ -13825,9 +13832,9 @@
"dev": true
},
"typescript": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
"integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==",
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz",
"integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==",
"dev": true
},
"uglify-js": {
@@ -14519,9 +14526,9 @@
"dev": true
},
"webpack": {
"version": "4.39.3",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.39.3.tgz",
"integrity": "sha512-BXSI9M211JyCVc3JxHWDpze85CvjC842EvpRsVTc/d15YJGlox7GIDd38kJgWrb3ZluyvIjgenbLDMBQPDcxYQ==",
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.0.tgz",
"integrity": "sha512-yNV98U4r7wX1VJAj5kyMsu36T8RPPQntcb5fJLOsMz/pt/WrKC0Vp1bAlqPLkA1LegSwQwf6P+kAbyhRKVQ72g==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.8.5",
@@ -14909,9 +14916,9 @@
}
},
"webpack-cli": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.8.tgz",
"integrity": "sha512-RANYSXwikSWINjHMd/mtesblNSpjpDLoYTBtP99n1RhXqVI/wxN40Auqy42I7y4xrbmRBoA5Zy5E0JSBD5XRhw==",
"version": "3.3.9",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.9.tgz",
"integrity": "sha512-xwnSxWl8nZtBl/AFJCOn9pG7s5CYUYdZxmmukv+fAHLcBIHM36dImfpQg3WfShZXeArkWlf6QRw24Klcsv8a5A==",
"dev": true,
"requires": {
"chalk": "2.4.2",

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.2.10",
"version": "0.3.2",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -15,11 +15,11 @@
"test": "npm run lint && jest",
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
"build-tsc": "tsc",
"build-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html}\" lib/",
"build-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/",
"build-webpack": "webpack --mode production",
"watch": "concurrently --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack\"",
"watch-tsc": "tsc -watch",
"watch-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html}\" lib/ --watch",
"watch-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/ --watch",
"watch-webpack": "webpack -w --mode development --display minimal",
"serve": "http-server -p 1338",
"model-server": "node lib/servers/model/server.js",
@@ -65,7 +65,7 @@
"devDependencies": {
"benchmark": "^2.1.4",
"circular-dependency-plugin": "^5.2.0",
"concurrently": "^4.1.2",
"concurrently": "^5.0.0",
"cpx": "^1.5.0",
"css-loader": "^3.2.0",
"extra-watch-webpack-plugin": "^1.0.3",
@@ -84,11 +84,11 @@
"sass-loader": "^8.0.0",
"simple-git": "^1.126.0",
"style-loader": "^1.0.0",
"ts-jest": "^24.0.2",
"ts-jest": "^24.1.0",
"tslint": "^5.20.0",
"typescript": "^3.6.3",
"webpack": "^4.39.3",
"webpack-cli": "^3.3.8"
"typescript": "^3.6.4",
"webpack": "^4.41.0",
"webpack-cli": "^3.3.9"
},
"dependencies": {
"@types/argparse": "^1.0.36",
@@ -96,22 +96,22 @@
"@types/compression": "1.0.1",
"@types/express": "^4.17.1",
"@types/jest": "^24.0.18",
"@types/node": "^12.7.4",
"@types/node-fetch": "^2.5.0",
"@types/react": "^16.9.2",
"@types/react-dom": "^16.9.0",
"@types/node": "^12.7.12",
"@types/node-fetch": "^2.5.2",
"@types/react": "^16.9.5",
"@types/react-dom": "^16.9.1",
"@types/swagger-ui-dist": "3.0.3",
"@types/webgl2": "0.0.5",
"argparse": "^1.0.10",
"compression": "^1.7.4",
"express": "^4.17.1",
"graphql": "^14.5.4",
"graphql": "^14.5.8",
"immutable": "^3.8.2",
"node-fetch": "^2.6.0",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"react": "^16.10.2",
"react-dom": "^16.10.2",
"rxjs": "^6.5.3",
"swagger-ui-dist": "^3.23.9",
"swagger-ui-dist": "^3.23.11",
"util.promisify": "^1.0.0",
"xhr2": "^0.2.0"
}

View File

@@ -19,4 +19,12 @@ export class BasicWrapperControls extends PluginUIComponent {
<TransformUpdaterControl nodeRef='ihm-visual' header={{ name: 'I/HM Visual' }} initiallyCollapsed={true} />
</div>;
}
}
export class CustomToastMessage extends PluginUIComponent {
render() {
return <>
Custom <i>Toast</i> content. No timeout.
</>;
}
}

View File

@@ -110,6 +110,9 @@
addControl('Static Superposition', () => BasicMolStarWrapper.tests.staticSuperposition());
addControl('Dynamic Superposition', () => BasicMolStarWrapper.tests.dynamicSuperposition());
addControl('Validation Tooltip', () => BasicMolStarWrapper.tests.toggleValidationTooltip());
addControl('Show Toasts', () => BasicMolStarWrapper.tests.showToasts());
addControl('Hide Toasts', () => BasicMolStarWrapper.tests.hideToasts());
////////////////////////////////////////////////////////

View File

@@ -18,6 +18,7 @@ import { StripedResidues } from './coloring';
// import { BasicWrapperControls } from './controls';
import { StaticSuperpositionTestData, buildStaticSuperposition, dynamicSuperpositionTest } from './superposition';
import { PDBeStructureQualityReport } from '../../mol-plugin/behavior/dynamic/custom-props';
import { CustomToastMessage } from './controls';
require('mol-plugin/skin/light.scss')
type SupportedFormats = 'cif' | 'pdb'
@@ -158,6 +159,23 @@ class BasicWrapper {
const state = this.plugin.state.behaviorState;
const tree = state.build().to(PDBeStructureQualityReport.id).update(PDBeStructureQualityReport, p => ({ ...p, showTooltip: !p.showTooltip }));
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
},
showToasts: () => {
PluginCommands.Toast.Show.dispatch(this.plugin, {
title: 'Toast 1',
message: 'This is an example text, timeout 3s',
key: 'toast-1',
timeoutMs: 3000
});
PluginCommands.Toast.Show.dispatch(this.plugin, {
title: 'Toast 2',
message: CustomToastMessage,
key: 'toast-2'
});
},
hideToasts: () => {
PluginCommands.Toast.Hide.dispatch(this.plugin, { key: 'toast-1' });
PluginCommands.Toast.Hide.dispatch(this.plugin, { key: 'toast-2' });
}
}
}

View File

@@ -15,6 +15,7 @@ import { OrderedSet } from '../../mol-data/int';
import { openCif, downloadCif } from './helpers';
import { Vec3 } from '../../mol-math/linear-algebra';
import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
import { Sequence } from '../../mol-model/sequence';
async function downloadFromPdb(pdb: string) {
@@ -110,9 +111,10 @@ export function printSequence(model: Model) {
console.log('\nSequence\n=============');
const { byEntityKey } = model.sequence;
for (const key of Object.keys(byEntityKey)) {
const seq = byEntityKey[+key];
console.log(`${seq.entityId} (${seq.sequence.kind} ${seq.num.value(0)} (offset ${seq.sequence.offset}), ${seq.num.value(seq.num.rowCount - 1)}) (${seq.compId.value(0)}, ${seq.compId.value(seq.compId.rowCount - 1)})`);
console.log(`${seq.sequence.sequence}`);
const { sequence, entityId } = byEntityKey[+key];
const { seqId, compId } = sequence
console.log(`${entityId} (${sequence.kind} ${seqId.value(0)} (offset ${sequence.offset}), ${seqId.value(seqId.rowCount - 1)}) (${compId.value(0)}, ${compId.value(compId.rowCount - 1)})`);
console.log(`${Sequence.getSequenceString(sequence)}`);
}
console.log();
}
@@ -159,14 +161,14 @@ export function printUnits(structure: Structure) {
console.log(`Coarse unit ${unit.id} ${unit.conformation.operator.name} (${Unit.isSpheres(l.unit) ? 'spheres' : 'gaussians'}): ${size} elements.`);
const props = StructureProperties.coarse;
const seq = l.unit.model.sequence;
const modelSeq = l.unit.model.sequence;
for (let j = 0, _j = Math.min(size, 3); j < _j; j++) {
l.element = OrderedSet.getAt(elements, j);
const residues: string[] = [];
const start = props.seq_id_begin(l), end = props.seq_id_end(l);
const compId = seq.byEntityKey[props.entityKey(l)].compId.value;
const compId = modelSeq.byEntityKey[props.entityKey(l)].sequence.compId.value;
for (let e = start; e <= end; e++) residues.push(compId(e));
console.log(`${props.asym_id(l)}:${start}-${end} (${residues.join('-')}) ${props.asym_id(l)} [${props.x(l).toFixed(2)}, ${props.y(l).toFixed(2)}, ${props.z(l).toFixed(2)}]`);
}

View File

@@ -138,23 +138,19 @@ function buildSnapshot(plugin: PluginContext, template: { tree: StateTree, struc
}
function getCameraSnapshot(e: JoleculeSnapshot['camera']): Camera.Snapshot {
const direction = Vec3.sub(Vec3.zero(), e.pos, e.in);
const direction = Vec3.sub(Vec3(), e.pos, e.in);
Vec3.normalize(direction, direction);
const up = Vec3.sub(Vec3.zero(), e.pos, e.up);
const up = Vec3.sub(Vec3(), e.pos, e.up);
Vec3.normalize(up, up);
const s: Camera.Snapshot = {
mode: 'perspective',
position: Vec3.scaleAndAdd(Vec3.zero(), e.pos, direction, e.slab.zoom),
target: e.pos,
direction,
up,
near: e.slab.zoom + e.slab.z_front,
far: e.slab.zoom + e.slab.z_back,
fogNear: e.slab.zoom + e.slab.z_front,
fogFar: e.slab.zoom + e.slab.z_back,
fov: Math.PI / 4,
zoom: 1
position: Vec3.scaleAndAdd(Vec3(), e.pos, direction, e.slab.zoom),
target: e.pos,
radius: (e.slab.z_back - e.slab.z_front) / 2,
fog: 50,
up,
};
return s;
}

BIN
src/apps/viewer/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link rel="icon" href="./favicon.ico" type="image/x-icon">
<title>Mol* Viewer</title>
<style>
* {

View File

@@ -7,6 +7,7 @@
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import './index.html'
import './favicon.ico'
import { PluginContext } from '../../mol-plugin/context';
import { PluginCommands } from '../../mol-plugin/command';
import { PluginSpec } from '../../mol-plugin/spec';

View File

@@ -1,3 +1,9 @@
== v3.4 ==
* Fixed HET group reset.
* Updated core.
* Removed Camera Cliping.
== v3.3 ==
* Camera Clipping.

View File

@@ -133,8 +133,8 @@
addControl('Reset Position', () => PluginWrapper.camera.resetPosition());
addControl('Toggle Spin', () => PluginWrapper.camera.toggleSpin());
// Same as "wheel icon" and Viewport options
addControl('Clip', () => PluginWrapper.viewport.setSettings({ clip: [33, 66] }));
addControl('Reset Clip', () => PluginWrapper.viewport.setSettings({ clip: [1, 100] }));
// addControl('Clip', () => PluginWrapper.viewport.setSettings({ clip: [33, 66] }));
// addControl('Reset Clip', () => PluginWrapper.viewport.setSettings({ clip: [1, 100] }));
addSeparator();

View File

@@ -37,7 +37,7 @@ require('../../mol-plugin/skin/light.scss')
class MolStarProteopediaWrapper {
static VERSION_MAJOR = 3;
static VERSION_MINOR = 3;
static VERSION_MINOR = 4;
private _ev = RxEventHelper.create();
@@ -335,7 +335,7 @@ class MolStarProteopediaWrapper {
hetGroups = {
reset: () => {
const update = this.state.build().delete(StateElements.HetGroupFocus);
const update = this.state.build().delete(StateElements.HetGroupFocusGroup);
PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update });
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
},

View File

@@ -1,56 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react'
import { ColorTheme } from '../../mol-theme/color';
import { Color } from '../../mol-util/color';
export interface ColorThemeComponentProps {
colorTheme: ColorTheme<any>
}
export interface ColorThemeComponentState {
}
export class ColorThemeComponent extends React.Component<ColorThemeComponentProps, ColorThemeComponentState> {
state = {
}
render() {
const ct = this.props.colorTheme
return <div>
<span>Color Theme Info </span>
{ct.description ? <div><i>{ct.description}</i></div> : ''}
{
ct.legend && ct.legend.kind === 'scale-legend'
? <div
style={{
width: '100%',
height: '30px',
background: `linear-gradient(to right, ${ct.legend.colors.map(c => Color.toStyle(c)).join(', ')})`
}}
>
<span style={{float: 'left', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.minLabel}</span>
<span style={{float: 'right', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.maxLabel}</span>
</div>
: ct.legend && ct.legend.kind === 'table-legend'
? <div>
{ct.legend.table.map((value, i) => {
const [name, color] = value
return <div key={i} style={{minWidth: '60px', marginRight: '5px', display: 'inline-block'}}>
<div style={{width: '30px', height: '20px', backgroundColor: Color.toStyle(color), display: 'inline-block'}}></div>
{name}
</div>
})}
</div>
: ''
}
</div>;
}
}

View File

@@ -7,17 +7,11 @@
import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra'
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
import { Object3D } from '../mol-gl/object3d';
import { BehaviorSubject } from 'rxjs';
import { CameraTransitionManager } from './camera/transition';
export { Camera }
// TODO: slab controls that modify near/far planes?
class Camera implements Object3D {
readonly updatedViewProjection = new BehaviorSubject<Camera>(this);
class Camera {
readonly view: Mat4 = Mat4.identity();
readonly projection: Mat4 = Mat4.identity();
readonly projectionView: Mat4 = Mat4.identity();
@@ -32,14 +26,17 @@ class Camera implements Object3D {
width: 1, height: 1
}
near = 1
far = 10000
fogNear = 5000
fogFar = 10000
zoom = 1
readonly transition: CameraTransitionManager = new CameraTransitionManager(this);
get position() { return this.state.position; }
set position(v: Vec3) { Vec3.copy(this.state.position, v); }
get direction() { return this.state.direction; }
set direction(v: Vec3) { Vec3.copy(this.state.direction, v); }
get up() { return this.state.up; }
set up(v: Vec3) { Vec3.copy(this.state.up, v); }
@@ -51,10 +48,12 @@ class Camera implements Object3D {
private deltaDirection = Vec3.zero();
private newPosition = Vec3.zero();
updateMatrices() {
update() {
const snapshot = this.state as Camera.Snapshot;
const height = 2 * Math.tan(snapshot.fov / 2) * Vec3.distance(snapshot.position, snapshot.target);
snapshot.zoom = this.viewport.height / height;
this.zoom = this.viewport.height / height;
updateClip(this);
switch (this.state.mode) {
case 'orthographic': updateOrtho(this); break;
@@ -64,24 +63,19 @@ class Camera implements Object3D {
const changed = !Mat4.areEqual(this.projection, this.prevProjection, EPSILON) || !Mat4.areEqual(this.view, this.prevView, EPSILON);
Mat4.mul(this.projectionView, this.projection, this.view)
Mat4.invert(this.inverseProjectionView, this.projectionView)
if (changed) {
Mat4.mul(this.projectionView, this.projection, this.view)
Mat4.invert(this.inverseProjectionView, this.projectionView)
Mat4.copy(this.prevView, this.view);
Mat4.copy(this.prevProjection, this.projection);
this.updatedViewProjection.next(this);
}
return changed;
}
setState(snapshot: Partial<Camera.Snapshot>) {
this.transition.apply(snapshot);
setState(snapshot: Partial<Camera.Snapshot>, durationMs?: number) {
this.transition.apply(snapshot, durationMs);
}
getSnapshot() {
@@ -90,41 +84,29 @@ class Camera implements Object3D {
return ret;
}
getFocus(target: Vec3, radius: number, dir?: Vec3): Partial<Camera.Snapshot> {
getFocus(target: Vec3, radius: number): Partial<Camera.Snapshot> {
const fov = this.state.fov
const { width, height } = this.viewport
const aspect = width / height
const aspectFactor = (height < width ? 1 : aspect)
const currentDistance = Vec3.distance(this.state.position, target)
const targetDistance = Math.abs((radius / aspectFactor) / Math.sin(fov / 2))
const deltaDistance = Math.abs(currentDistance - targetDistance)
Vec3.sub(this.deltaDirection, this.target, this.position)
Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance)
Vec3.sub(this.newPosition, target, this.deltaDirection)
if (dir) {
Vec3.setMagnitude(this.deltaDirection, dir, targetDistance)
Vec3.add(this.newPosition, target, this.deltaDirection)
} else {
Vec3.setMagnitude(this.deltaDirection, this.state.direction, deltaDistance)
if (currentDistance < targetDistance) Vec3.negate(this.deltaDirection, this.deltaDirection)
Vec3.add(this.newPosition, this.state.position, this.deltaDirection)
}
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state)
state.target = Vec3.clone(target)
state.radius = radius
state.position = Vec3.clone(this.newPosition)
return { target, position: Vec3.clone(this.newPosition) };
return state
}
focus(target: Vec3, radius: number, dir?: Vec3) {
if (radius > 0) this.setState(this.getFocus(target, radius, dir));
focus(target: Vec3, radius: number, durationMs?: number) {
if (radius > 0) this.setState(this.getFocus(target, radius), durationMs);
}
// lookAt(target: Vec3) {
// cameraLookAt(this.position, this.up, this.direction, target);
// }
// translate(v: Vec3) {
// Vec3.add(this.position, this.position, v);
// cameraLookAt(this.position, this.up, this.direction, this.target);
// }
project(out: Vec4, point: Vec3) {
return cameraProject(out, point, this.viewport, this.projectionView)
}
@@ -133,10 +115,6 @@ class Camera implements Object3D {
return cameraUnproject(out, point, this.viewport, this.inverseProjectionView)
}
dispose() {
this.updatedViewProjection.complete();
}
constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) {
this.viewport = viewport;
Camera.copySnapshot(this.state, state);
@@ -147,13 +125,6 @@ class Camera implements Object3D {
namespace Camera {
export type Mode = 'perspective' | 'orthographic'
export interface ClippingInfo {
near: number,
far: number,
fogNear: number,
fogFar: number
}
/**
* Sets an offseted view in a larger frustum. This is useful for
* - multi-window or multi-monitor/multi-machine setups
@@ -181,64 +152,48 @@ namespace Camera {
export function createDefaultSnapshot(): Snapshot {
return {
mode: 'perspective',
fov: Math.PI / 4,
position: Vec3.create(0, 0, 100),
direction: Vec3.create(0, 0, 1),
up: Vec3.create(0, 1, 0),
target: Vec3.create(0, 0, 0),
near: 1,
far: 10000,
fogNear: 1,
fogFar: 10000,
fov: Math.PI / 4,
zoom: 1,
radius: 10,
fog: 50,
};
}
export interface Snapshot {
mode: Mode,
mode: Mode
fov: number
position: Vec3,
// Normalized camera direction
direction: Vec3,
up: Vec3,
target: Vec3,
position: Vec3
up: Vec3
target: Vec3
near: number,
far: number,
fogNear: number,
fogFar: number,
fov: number,
zoom: number,
radius: number
fog: number
}
export function copySnapshot(out: Snapshot, source?: Partial<Snapshot>) {
if (!source) return;
if (!source) return out;
if (typeof source.mode !== 'undefined') out.mode = source.mode;
if (typeof source.fov !== 'undefined') out.fov = source.fov;
if (typeof source.position !== 'undefined') Vec3.copy(out.position, source.position);
if (typeof source.direction !== 'undefined') Vec3.copy(out.direction, source.direction);
if (typeof source.up !== 'undefined') Vec3.copy(out.up, source.up);
if (typeof source.target !== 'undefined') Vec3.copy(out.target, source.target);
if (typeof source.near !== 'undefined') out.near = source.near;
if (typeof source.far !== 'undefined') out.far = source.far;
if (typeof source.fogNear !== 'undefined') out.fogNear = source.fogNear;
if (typeof source.fogFar !== 'undefined') out.fogFar = source.fogFar;
if (typeof source.radius !== 'undefined') out.radius = source.radius;
if (typeof source.fog !== 'undefined') out.fog = source.fog;
if (typeof source.fov !== 'undefined') out.fov = source.fov;
if (typeof source.zoom !== 'undefined') out.zoom = source.zoom;
return out;
}
}
const _center = Vec3.zero();
function updateOrtho(camera: Camera) {
const { viewport, state: { zoom, near, far }, viewOffset } = camera
const { viewport, zoom, near, far, viewOffset } = camera
const fullLeft = -(viewport.width - viewport.x) / 2
const fullRight = (viewport.width - viewport.x) / 2
@@ -270,16 +225,15 @@ function updateOrtho(camera: Camera) {
Mat4.ortho(camera.projection, left, right, top, bottom, near, far)
// build view matrix
Vec3.add(_center, camera.position, camera.direction)
Mat4.lookAt(camera.view, camera.position, _center, camera.up)
Mat4.lookAt(camera.view, camera.position, camera.target, camera.up)
}
function updatePers(camera: Camera) {
const aspect = camera.viewport.width / camera.viewport.height
const { state: { fov, near, far }, viewOffset } = camera
const { near, far, viewOffset } = camera
let top = near * Math.tan(0.5 * fov)
let top = near * Math.tan(0.5 * camera.state.fov)
let height = 2 * top
let width = aspect * height
let left = -0.5 * width
@@ -295,6 +249,33 @@ function updatePers(camera: Camera) {
Mat4.perspective(camera.projection, left, left + width, top, top - height, near, far)
// build view matrix
Vec3.add(_center, camera.position, camera.direction)
Mat4.lookAt(camera.view, camera.position, _center, camera.up)
Mat4.lookAt(camera.view, camera.position, camera.target, camera.up)
}
function updateClip(camera: Camera) {
const { radius, mode, fog } = camera.state
const cDist = Vec3.distance(camera.position, camera.target)
const bRadius = Math.max(1, radius)
let near = cDist - bRadius
let far = cDist + bRadius
const fogNearFactor = -(50 - fog) / 50
let fogNear = cDist - (bRadius * fogNearFactor)
let fogFar = cDist + bRadius
if (mode === 'perspective') {
// set at least to 5 to avoid slow sphere impostor rendering
near = Math.max(5, near)
far = Math.max(5, far)
} else {
near = Math.max(0, near)
far = Math.max(0, far)
}
camera.near = near;
camera.far = far;
camera.fogNear = fogNear;
camera.fogFar = fogFar;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -22,7 +22,7 @@ class CameraTransitionManager {
private current = Camera.createDefaultSnapshot();
apply(to: Partial<Camera.Snapshot>, durationMs: number = 0, transition?: CameraTransitionManager.TransitionFunc) {
if (durationMs <= 0 || to.mode !== this.camera.state.mode) {
if (durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
this.finish(to);
return;
}
@@ -68,33 +68,21 @@ class CameraTransitionManager {
namespace CameraTransitionManager {
export type TransitionFunc = (out: Camera.Snapshot, t: number, source: Camera.Snapshot, target: Camera.Snapshot) => void
const _rot = Quat.identity();
export function defaultTransition(out: Camera.Snapshot, t: number, source: Camera.Snapshot, target: Camera.Snapshot): void {
Camera.copySnapshot(out, target);
// Rotate direction
const rot = Quat.identity();
Quat.slerp(rot, rot, Quat.rotationTo(Quat.zero(), source.direction, target.direction), t);
Vec3.transformQuat(out.direction, source.direction, rot);
// Rotate up
Quat.setIdentity(rot);
Quat.slerp(rot, rot, Quat.rotationTo(Quat.zero(), source.up, target.up), t);
Vec3.transformQuat(out.up, source.up, rot);
Quat.slerp(_rot, Quat.Identity, Quat.rotationTo(_rot, source.up, target.up), t);
Vec3.transformQuat(out.up, source.up, _rot);
// Lerp target
// Lerp target, position & radius
Vec3.lerp(out.target, source.target, target.target, t);
Vec3.lerp(out.position, source.position, target.position, t);
out.radius = lerp(source.radius, target.radius, t);
// Update position
const dist = -lerp(Vec3.distance(source.position, source.target), Vec3.distance(target.position, target.target), t);
Vec3.scale(out.position, out.direction, dist);
Vec3.add(out.position, out.position, out.target);
// Lerp other props
out.zoom = lerp(source.zoom, target.zoom, t);
// Lerp fov & fog
out.fov = lerp(source.fov, target.fov, t);
out.near = lerp(source.near, target.near, t);
out.far = lerp(source.far, target.far, t);
out.fogNear = lerp(source.fogNear, target.fogNear, t);
out.fogFar = lerp(source.fogFar, target.fogFar, t);
out.fog = lerp(source.fog, target.fog, t);
}
}

View File

@@ -4,7 +4,7 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat4, Vec3, Vec4, EPSILON } from '../../mol-math/linear-algebra'
import { Mat4, Vec3, Vec4 } from '../../mol-math/linear-algebra'
export { Viewport }
@@ -55,31 +55,6 @@ namespace Viewport {
//
const tmpVec3 = Vec3()
/** Modifies the direction & up vectors in place, both are normalized */
export function cameraLookAt(position: Vec3, up: Vec3, direction: Vec3, target: Vec3) {
Vec3.sub(tmpVec3, target, position)
Vec3.normalize(tmpVec3, tmpVec3)
if (!Vec3.isZero(tmpVec3)) {
// change direction vector to look at target
const d = Vec3.dot(tmpVec3, up)
if (Math.abs(d - 1) < EPSILON) { // parallel
Vec3.scale(up, direction, -1)
} else if (Math.abs(d + 1) < EPSILON) { // anti parallel
Vec3.copy(up, direction)
}
Vec3.copy(direction, tmpVec3)
// normalize up vector
Vec3.cross(tmpVec3, direction, up)
Vec3.normalize(tmpVec3, tmpVec3)
Vec3.cross(up, tmpVec3, direction)
Vec3.normalize(up, up)
}
}
const NEAR_RANGE = 0
const FAR_RANGE = 1

View File

@@ -32,14 +32,12 @@ import { readTexture } from '../mol-gl/compute/util';
import { DrawPass } from './passes/draw';
import { PickPass } from './passes/pick';
import { Task } from '../mol-task';
import { ImagePass, ImageProps } from './passes/image';
export const Canvas3DParams = {
// TODO: FPS cap?
// maxFps: PD.Numeric(30),
cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]),
cameraClipDistance: PD.Numeric(0, { min: 0.0, max: 50.0, step: 0.1 }, { description: 'The distance between camera and scene at which to clip regardless of near clipping plane.' }),
clip: PD.Interval([1, 100], { min: 1, max: 100, step: 1 }),
fog: PD.Interval([50, 100], { min: 1, max: 100, step: 1 }),
cameraFog: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
multiSample: PD.Group(MultiSampleParams),
postprocessing: PD.Group(PostprocessingParams),
@@ -77,6 +75,7 @@ interface Canvas3D {
downloadScreenshot: () => void
getPixelData: (variant: GraphicsRenderVariant) => PixelData
setProps: (props: Partial<Canvas3DProps>) => void
getImagePass: () => ImagePass
/** Returns a copy of the current Canvas3D instance props */
readonly props: Readonly<Canvas3DProps>
@@ -91,15 +90,16 @@ const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnim
const DefaultRunTask = (task: Task<unknown>) => task.run()
namespace Canvas3D {
export interface HighlightEvent { current: Representation.Loci, modifiers?: ModifiersKeys }
export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, modifiers: ModifiersKeys }
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, modifiers: ModifiersKeys }
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask) {
const gl = getGLContext(canvas, {
alpha: false,
alpha: true,
antialias: true,
depth: true,
preserveDrawingBuffer: true
preserveDrawingBuffer: true,
premultipliedAlpha: false,
})
if (gl === null) throw new Error('Could not create a WebGL rendering context')
const input = InputObserver.fromElement(canvas)
@@ -116,31 +116,31 @@ namespace Canvas3D {
const startTime = now()
const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp)
const camera = new Camera({
near: 0.1,
far: 10000,
position: Vec3.create(0, 0, 100),
mode: p.cameraMode
})
const webgl = createContext(gl)
let width = gl.drawingBufferWidth
let height = gl.drawingBufferHeight
const scene = Scene.create(webgl)
const camera = new Camera({
position: Vec3.create(0, 0, 100),
mode: p.cameraMode,
fog: p.cameraFog
})
const controls = TrackballControls.create(input, camera, p.trackball)
const renderer = Renderer.create(webgl, camera, p.renderer)
const renderer = Renderer.create(webgl, p.renderer)
const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
const drawPass = new DrawPass(webgl, renderer, scene, debugHelper)
const pickPass = new PickPass(webgl, renderer, scene, 0.5)
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper)
const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5)
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing)
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample)
let drawPending = false
let cameraResetRequested: boolean | Vec3 = false
let cameraResetRequested = false
function getLoci(pickingId: PickingId) {
let loci: Loci = EmptyLoci
@@ -148,7 +148,9 @@ namespace Canvas3D {
reprRenderObjects.forEach((_, _repr) => {
const _loci = _repr.getLoci(pickingId)
if (!isEmptyLoci(_loci)) {
if (!isEmptyLoci(loci)) console.warn('found another loci')
if (!isEmptyLoci(loci)) {
console.warn('found another loci, this should not happen')
}
loci = _loci
repr = _repr
}
@@ -172,47 +174,13 @@ namespace Canvas3D {
}
}
let currentNear = -1, currentFar = -1, currentFogNear = -1, currentFogFar = -1
function setClipping() {
const cDist = Vec3.distance(camera.state.position, camera.state.target)
const bRadius = Math.max(10, scene.boundingSphere.radius)
const nearFactor = (50 - p.clip[0]) / 50
const farFactor = -(50 - p.clip[1]) / 50
let near = cDist - (bRadius * nearFactor)
let far = cDist + (bRadius * farFactor)
const fogNearFactor = (50 - p.fog[0]) / 50
const fogFarFactor = -(50 - p.fog[1]) / 50
let fogNear = cDist - (bRadius * fogNearFactor)
let fogFar = cDist + (bRadius * fogFarFactor)
if (camera.state.mode === 'perspective') {
// set at least to 5 to avoid slow sphere impostor rendering
near = Math.max(5, p.cameraClipDistance, near)
far = Math.max(5, far)
fogNear = Math.max(5, fogNear)
fogFar = Math.max(5, fogFar)
} else if (camera.state.mode === 'orthographic') {
if (p.cameraClipDistance > 0) {
near = Math.max(p.cameraClipDistance, near)
}
}
if (near !== currentNear || far !== currentFar || fogNear !== currentFogNear || fogFar !== currentFogFar) {
camera.setState({ near, far, fogNear, fogFar })
currentNear = near, currentFar = far, currentFogNear = fogNear, currentFogFar = fogFar
}
}
function render(variant: 'pick' | 'draw', force: boolean) {
if (scene.isCommiting) return false
let didRender = false
controls.update(currentTime);
// TODO: is this a good fix? Also, setClipping does not work if the user has manually set a clipping plane.
if (!camera.transition.inTransition) setClipping();
const cameraChanged = camera.updateMatrices();
controls.update(currentTime)
Viewport.set(camera.viewport, 0, 0, width, height)
const cameraChanged = camera.update()
multiSample.update(force || cameraChanged, currentTime)
if (force || cameraChanged || multiSample.enabled) {
@@ -221,9 +189,9 @@ namespace Canvas3D {
pickPass.render()
break;
case 'draw':
renderer.setViewport(0, 0, width, height);
renderer.setViewport(0, 0, width, height)
if (multiSample.enabled) {
multiSample.render()
multiSample.render(true)
} else {
drawPass.render(!postprocessing.enabled)
if (postprocessing.enabled) postprocessing.render(true)
@@ -271,8 +239,7 @@ namespace Canvas3D {
runTask(scene.commit()).then(() => {
if (cameraResetRequested && !scene.isCommiting) {
const dir = typeof cameraResetRequested === 'boolean' ? undefined : cameraResetRequested
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius, dir)
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius)
cameraResetRequested = false
}
if (debugHelper.isEnabled) debugHelper.update()
@@ -345,11 +312,11 @@ namespace Canvas3D {
getLoci,
handleResize,
resetCamera: (dir?: Vec3) => {
resetCamera: () => {
if (scene.isCommiting) {
cameraResetRequested = dir || true
cameraResetRequested = true
} else {
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius, dir)
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius, p.cameraResetDurationMs)
requestDraw(true);
}
},
@@ -372,9 +339,10 @@ namespace Canvas3D {
if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
camera.setState({ mode: props.cameraMode })
}
if (props.cameraClipDistance !== undefined) p.cameraClipDistance = props.cameraClipDistance
if (props.clip !== undefined) p.clip = [props.clip[0], props.clip[1]]
if (props.fog !== undefined) p.fog = [props.fog[0], props.fog[1]]
if (props.cameraFog !== undefined && props.cameraFog !== camera.state.fog) {
camera.setState({ fog: props.cameraFog })
}
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs
if (props.postprocessing) postprocessing.setProps(props.postprocessing)
if (props.multiSample) multiSample.setProps(props.multiSample)
@@ -383,13 +351,15 @@ namespace Canvas3D {
if (props.debug) debugHelper.setProps(props.debug)
requestDraw(true)
},
getImagePass: (props: Partial<ImageProps> = {}) => {
return new ImagePass(webgl, renderer, scene, camera, debugHelper, props)
},
get props() {
return {
cameraMode: camera.state.mode,
cameraClipDistance: p.cameraClipDistance,
clip: p.clip,
fog: p.fog,
cameraFog: camera.state.fog,
cameraResetDurationMs: p.cameraResetDurationMs,
postprocessing: { ...postprocessing.props },
multiSample: { ...multiSample.props },
@@ -413,7 +383,6 @@ namespace Canvas3D {
input.dispose()
controls.dispose()
renderer.dispose()
camera.dispose()
interactionHelper.dispose()
}
}

View File

@@ -9,10 +9,29 @@
*/
import { Quat, Vec2, Vec3, EPSILON } from '../../mol-math/linear-algebra';
import { cameraLookAt, Viewport } from '../camera/util';
import InputObserver, { DragInput, WheelInput, ButtonsType, PinchInput } from '../../mol-util/input/input-observer';
import { Object3D } from '../../mol-gl/object3d';
import { Viewport } from '../camera/util';
import InputObserver, { DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys } from '../../mol-util/input/input-observer';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Camera } from '../camera';
import { absMax } from '../../mol-math/misc';
import { Binding } from '../../mol-util/binding';
const B = ButtonsType
const M = ModifiersKeys
const Trigger = Binding.Trigger
export const DefaultTrackballBindings = {
dragRotate: Binding(Trigger(B.Flag.Primary, M.create()), 'Rotate the 3D scene by dragging using ${trigger}'),
dragRotateZ: Binding(Trigger(B.Flag.Primary, M.create({ shift: true })), 'Rotate the 3D scene around the z-axis by dragging using ${trigger}'),
dragPan: Binding(Trigger(B.Flag.Secondary, M.create()), 'Pan the 3D scene by dragging using ${trigger}'),
dragZoom: Binding.Empty,
dragFocus: Binding(Trigger(B.Flag.Forth, M.create()), 'Focus the 3D scene by dragging using ${trigger}'),
dragFocusZoom: Binding(Trigger(B.Flag.Auxilary, M.create()), 'Focus and zoom the 3D scene by dragging using ${trigger}'),
scrollZoom: Binding(Trigger(B.Flag.Auxilary, M.create()), 'Zoom the 3D scene by scrolling using ${trigger}'),
scrollFocus: Binding(Trigger(B.Flag.Auxilary, M.create({ shift: true })), 'Focus the 3D scene by scrolling using ${trigger}'),
scrollFocusZoom: Binding.Empty,
}
export const TrackballControlsParams = {
noScroll: PD.Boolean(true, { isHidden: true }),
@@ -21,14 +40,16 @@ export const TrackballControlsParams = {
zoomSpeed: PD.Numeric(6.0, { min: 0.1, max: 10, step: 0.1 }),
panSpeed: PD.Numeric(0.8, { min: 0.1, max: 5, step: 0.1 }),
spin: PD.Boolean(false),
spin: PD.Boolean(false, { description: 'Spin the 3D scene around the x-axis in view space' }),
spinSpeed: PD.Numeric(1, { min: -100, max: 100, step: 1 }),
staticMoving: PD.Boolean(true, { isHidden: true }),
dynamicDampingFactor: PD.Numeric(0.2, {}, { isHidden: true }),
minDistance: PD.Numeric(0.01, {}, { isHidden: true }),
maxDistance: PD.Numeric(1e150, {}, { isHidden: true })
maxDistance: PD.Numeric(1e150, {}, { isHidden: true }),
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true })
}
export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams>
@@ -44,11 +65,10 @@ interface TrackballControls {
dispose: () => void
}
namespace TrackballControls {
export function create(input: InputObserver, object: Object3D & { target: Vec3 }, props: Partial<TrackballControlsProps> = {}): TrackballControls {
export function create(input: InputObserver, camera: Camera, props: Partial<TrackballControlsProps> = {}): TrackballControls {
const p = { ...PD.getDefaultValues(TrackballControlsParams), ...props }
const viewport: Viewport = { x: 0, y: 0, width: 0, height: 0 }
const target: Vec3 = object.target
const viewport = Viewport()
let disposed = false
@@ -60,91 +80,116 @@ namespace TrackballControls {
let _isInteracting = false;
// For internal use
const lastPosition = Vec3.zero()
const lastPosition = Vec3()
const _eye = Vec3.zero()
const _eye = Vec3()
const _movePrev = Vec2.zero()
const _moveCurr = Vec2.zero()
const _rotPrev = Vec2()
const _rotCurr = Vec2()
const _rotLastAxis = Vec3()
let _rotLastAngle = 0
const _lastAxis = Vec3.zero()
let _lastAngle = 0
const _zRotPrev = Vec2()
const _zRotCurr = Vec2()
let _zRotLastAngle = 0
const _zoomStart = Vec2.zero()
const _zoomEnd = Vec2.zero()
const _zoomStart = Vec2()
const _zoomEnd = Vec2()
const _panStart = Vec2.zero()
const _panEnd = Vec2.zero()
const _focusStart = Vec2()
const _focusEnd = Vec2()
const _panStart = Vec2()
const _panEnd = Vec2()
// Initial values for reseting
const target0 = Vec3.clone(target)
const position0 = Vec3.clone(object.position)
const up0 = Vec3.clone(object.up)
const target0 = Vec3.clone(camera.target)
const position0 = Vec3.clone(camera.position)
const up0 = Vec3.clone(camera.up)
const mouseOnScreenVec2 = Vec2.zero()
const mouseOnScreenVec2 = Vec2()
function getMouseOnScreen(pageX: number, pageY: number) {
Vec2.set(
return Vec2.set(
mouseOnScreenVec2,
(pageX - viewport.x) / viewport.width,
(pageY - viewport.y) / viewport.height
);
return mouseOnScreenVec2;
}
const mouseOnCircleVec2 = Vec2.zero()
const mouseOnCircleVec2 = Vec2()
function getMouseOnCircle(pageX: number, pageY: number) {
Vec2.set(
return Vec2.set(
mouseOnCircleVec2,
((pageX - viewport.width * 0.5 - viewport.x) / (viewport.width * 0.5)),
((viewport.height + 2 * (viewport.y - pageY)) / viewport.width) // screen.width intentional
(pageX - viewport.width * 0.5 - viewport.x) / (viewport.width * 0.5),
(viewport.height + 2 * (viewport.y - pageY)) / viewport.width // screen.width intentional
);
return mouseOnCircleVec2;
}
const rotAxis = Vec3.zero()
const rotQuat = Quat.zero()
const rotEyeDir = Vec3.zero()
const rotObjUpDir = Vec3.zero()
const rotObjSideDir = Vec3.zero()
const rotMoveDir = Vec3.zero()
const rotAxis = Vec3()
const rotQuat = Quat()
const rotEyeDir = Vec3()
const rotObjUpDir = Vec3()
const rotObjSideDir = Vec3()
const rotMoveDir = Vec3()
function rotateCamera() {
Vec3.set(rotMoveDir, _moveCurr[0] - _movePrev[0], _moveCurr[1] - _movePrev[1], 0);
let angle = Vec3.magnitude(rotMoveDir);
const dx = _rotCurr[0] - _rotPrev[0]
const dy = _rotCurr[1] - _rotPrev[1]
Vec3.set(rotMoveDir, dx, dy, 0);
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed;
if (angle) {
Vec3.copy(_eye, object.position)
Vec3.sub(_eye, _eye, target)
Vec3.sub(_eye, camera.position, camera.target)
Vec3.normalize(rotEyeDir, Vec3.copy(rotEyeDir, _eye))
Vec3.normalize(rotObjUpDir, Vec3.copy(rotObjUpDir, object.up))
Vec3.normalize(rotEyeDir, _eye)
Vec3.normalize(rotObjUpDir, camera.up)
Vec3.normalize(rotObjSideDir, Vec3.cross(rotObjSideDir, rotObjUpDir, rotEyeDir))
Vec3.setMagnitude(rotObjUpDir, rotObjUpDir, _moveCurr[1] - _movePrev[1])
Vec3.setMagnitude(rotObjSideDir, rotObjSideDir, _moveCurr[0] - _movePrev[0])
Vec3.add(rotMoveDir, Vec3.copy(rotMoveDir, rotObjUpDir), rotObjSideDir)
Vec3.setMagnitude(rotObjUpDir, rotObjUpDir, dy)
Vec3.setMagnitude(rotObjSideDir, rotObjSideDir, dx)
Vec3.add(rotMoveDir, rotObjUpDir, rotObjSideDir)
Vec3.normalize(rotAxis, Vec3.cross(rotAxis, rotMoveDir, _eye))
angle *= p.rotateSpeed;
Quat.setAxisAngle(rotQuat, rotAxis, angle)
Vec3.transformQuat(_eye, _eye, rotQuat)
Vec3.transformQuat(object.up, object.up, rotQuat)
Vec3.transformQuat(camera.up, camera.up, rotQuat)
Vec3.copy(_lastAxis, rotAxis)
_lastAngle = angle;
} else if (!p.staticMoving && _lastAngle) {
_lastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
Vec3.sub(_eye, Vec3.copy(_eye, object.position), target)
Quat.setAxisAngle(rotQuat, _lastAxis, _lastAngle)
Vec3.copy(_rotLastAxis, rotAxis)
_rotLastAngle = angle;
} else if (!p.staticMoving && _rotLastAngle) {
_rotLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
Vec3.sub(_eye, camera.position, camera.target)
Quat.setAxisAngle(rotQuat, _rotLastAxis, _rotLastAngle)
Vec3.transformQuat(_eye, _eye, rotQuat)
Vec3.transformQuat(object.up, object.up, rotQuat)
Vec3.transformQuat(camera.up, camera.up, rotQuat)
}
Vec2.copy(_movePrev, _moveCurr)
Vec2.copy(_rotPrev, _rotCurr)
}
const zRotQuat = Quat()
function zRotateCamera() {
const dx = _zRotCurr[0] - _zRotPrev[0]
const dy = _zRotCurr[1] - _zRotPrev[1]
const angle = p.rotateSpeed * (-dx + dy) * -0.05
if (angle) {
Vec3.sub(_eye, camera.position, camera.target)
Quat.setAxisAngle(zRotQuat, _eye, angle)
Vec3.transformQuat(camera.up, camera.up, zRotQuat)
_zRotLastAngle = angle;
} else if (!p.staticMoving && _zRotLastAngle) {
_zRotLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
Vec3.sub(_eye, camera.position, camera.target)
Quat.setAxisAngle(zRotQuat, _eye, _zRotLastAngle)
Vec3.transformQuat(camera.up, camera.up, zRotQuat)
}
Vec2.copy(_zRotPrev, _zRotCurr)
}
function zoomCamera() {
@@ -160,9 +205,23 @@ namespace TrackballControls {
}
}
const panMouseChange = Vec2.zero()
const panObjUp = Vec3.zero()
const panOffset = Vec3.zero()
function focusCamera() {
const factor = (_focusEnd[1] - _focusStart[1]) * p.zoomSpeed
if (factor !== 0.0) {
const radius = Math.max(1, camera.state.radius + 10 * factor)
camera.setState({ radius })
}
if (p.staticMoving) {
Vec2.copy(_focusStart, _focusEnd)
} else {
_focusStart[1] += (_focusEnd[1] - _focusStart[1]) * p.dynamicDampingFactor
}
}
const panMouseChange = Vec2()
const panObjUp = Vec3()
const panOffset = Vec3()
function panCamera() {
Vec2.sub(panMouseChange, Vec2.copy(panMouseChange, _panEnd), _panStart)
@@ -170,14 +229,14 @@ namespace TrackballControls {
if (Vec2.squaredMagnitude(panMouseChange)) {
Vec2.scale(panMouseChange, panMouseChange, Vec3.magnitude(_eye) * p.panSpeed)
Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), object.up)
Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), camera.up)
Vec3.setMagnitude(panOffset, panOffset, panMouseChange[0])
Vec3.setMagnitude(panObjUp, object.up, panMouseChange[1])
Vec3.setMagnitude(panObjUp, camera.up, panMouseChange[1])
Vec3.add(panOffset, panOffset, panObjUp)
Vec3.add(object.position, object.position, panOffset)
Vec3.add(target, target, panOffset)
Vec3.add(camera.position, camera.position, panOffset)
Vec3.add(camera.target, camera.target, panOffset)
if (p.staticMoving) {
Vec2.copy(_panStart, _panEnd)
@@ -193,14 +252,16 @@ namespace TrackballControls {
function checkDistances() {
if (Vec3.squaredMagnitude(_eye) > p.maxDistance * p.maxDistance) {
Vec3.setMagnitude(_eye, _eye, p.maxDistance)
Vec3.add(object.position, target, _eye)
Vec3.add(camera.position, camera.target, _eye)
Vec2.copy(_zoomStart, _zoomEnd)
Vec2.copy(_focusStart, _focusEnd)
}
if (Vec3.squaredMagnitude(_eye) < p.minDistance * p.minDistance) {
Vec3.setMagnitude(_eye, _eye, p.minDistance)
Vec3.add(object.position, target, _eye)
Vec3.add(camera.position, camera.target, _eye)
Vec2.copy(_zoomStart, _zoomEnd)
Vec2.copy(_focusStart, _focusEnd)
}
}
@@ -210,18 +271,19 @@ namespace TrackballControls {
if (lastUpdated === t) return;
if (p.spin) spin(t - lastUpdated);
Vec3.sub(_eye, object.position, target)
Vec3.sub(_eye, camera.position, camera.target)
rotateCamera()
zRotateCamera()
zoomCamera()
focusCamera()
panCamera()
Vec3.add(object.position, target, _eye)
Vec3.add(camera.position, camera.target, _eye)
checkDistances()
cameraLookAt(object.position, object.up, object.direction, target)
if (Vec3.squaredDistance(lastPosition, object.position) > EPSILON) {
Vec3.copy(lastPosition, object.position)
if (Vec3.squaredDistance(lastPosition, camera.position) > EPSILON) {
Vec3.copy(lastPosition, camera.position)
}
lastUpdated = t;
@@ -229,53 +291,82 @@ namespace TrackballControls {
/** Reset object's vectors and the target vector to their initial values */
function reset() {
Vec3.copy(target, target0)
Vec3.copy(object.position, position0)
Vec3.copy(object.up, up0)
Vec3.copy(camera.target, target0)
Vec3.copy(camera.position, position0)
Vec3.copy(camera.up, up0)
Vec3.sub(_eye, object.position, target)
cameraLookAt(object.position, object.up, object.direction, target)
Vec3.copy(lastPosition, object.position)
Vec3.sub(_eye, camera.position, camera.target)
Vec3.copy(lastPosition, camera.position)
}
// listeners
function onDrag({ pageX, pageY, buttons, isStart }: DragInput) {
function onDrag({ pageX, pageY, buttons, modifiers, isStart }: DragInput) {
_isInteracting = true;
const dragRotate = Binding.match(p.bindings.dragRotate, buttons, modifiers)
const dragRotateZ = Binding.match(p.bindings.dragRotateZ, buttons, modifiers)
const dragPan = Binding.match(p.bindings.dragPan, buttons, modifiers)
const dragZoom = Binding.match(p.bindings.dragZoom, buttons, modifiers)
const dragFocus = Binding.match(p.bindings.dragFocus, buttons, modifiers)
const dragFocusZoom = Binding.match(p.bindings.dragFocusZoom, buttons, modifiers)
getMouseOnCircle(pageX, pageY)
getMouseOnScreen(pageX, pageY)
if (isStart) {
if (buttons === ButtonsType.Flag.Primary) {
Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY))
Vec2.copy(_movePrev, _moveCurr)
} else if (buttons === ButtonsType.Flag.Auxilary) {
Vec2.copy(_zoomStart, getMouseOnScreen(pageX, pageY))
if (dragRotate) {
Vec2.copy(_rotCurr, mouseOnCircleVec2)
Vec2.copy(_rotPrev, _rotCurr)
}
if (dragRotateZ) {
Vec2.copy(_zRotCurr, mouseOnCircleVec2)
Vec2.copy(_zRotPrev, _zRotCurr)
}
if (dragZoom || dragFocusZoom) {
Vec2.copy(_zoomStart, mouseOnScreenVec2)
Vec2.copy(_zoomEnd, _zoomStart)
} else if (buttons === ButtonsType.Flag.Secondary) {
Vec2.copy(_panStart, getMouseOnScreen(pageX, pageY))
}
if (dragFocus) {
Vec2.copy(_focusStart, mouseOnScreenVec2)
Vec2.copy(_focusEnd, _focusStart)
}
if (dragPan) {
Vec2.copy(_panStart, mouseOnScreenVec2)
Vec2.copy(_panEnd, _panStart)
}
}
if (buttons === ButtonsType.Flag.Primary) {
Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY))
} else if (buttons === ButtonsType.Flag.Auxilary) {
Vec2.copy(_zoomEnd, getMouseOnScreen(pageX, pageY))
} else if (buttons === ButtonsType.Flag.Secondary) {
Vec2.copy(_panEnd, getMouseOnScreen(pageX, pageY))
if (dragRotate) Vec2.copy(_rotCurr, mouseOnCircleVec2)
if (dragRotateZ) Vec2.copy(_zRotCurr, mouseOnCircleVec2)
if (dragZoom || dragFocusZoom) Vec2.copy(_zoomEnd, mouseOnScreenVec2)
if (dragFocus) Vec2.copy(_focusEnd, mouseOnScreenVec2)
if (dragFocusZoom) {
const dist = Vec3.distance(camera.state.position, camera.state.target);
camera.setState({ radius: dist / 5 })
}
if (dragPan) Vec2.copy(_panEnd, mouseOnScreenVec2)
}
function onInteractionEnd() {
_isInteracting = false;
}
function onWheel({ dy }: WheelInput) {
_zoomEnd[1] += dy * 0.0001
function onWheel({ dx, dy, dz, buttons, modifiers }: WheelInput) {
const delta = absMax(dx, dy, dz)
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_zoomEnd[1] += delta * 0.0001
}
if (Binding.match(p.bindings.scrollFocus, buttons, modifiers)) {
_focusEnd[1] += delta * 0.0001
}
}
function onPinch({ fraction }: PinchInput) {
_isInteracting = true;
_zoomEnd[1] += (fraction - 1) * 0.1
function onPinch({ fraction, buttons, modifiers }: PinchInput) {
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_isInteracting = true;
_zoomEnd[1] += (fraction - 1) * 0.1
}
}
function dispose() {
@@ -292,7 +383,7 @@ namespace TrackballControls {
function spin(deltaT: number) {
const frameSpeed = (p.spinSpeed || 0) / 1000;
_spinSpeed[0] = 60 * Math.min(Math.abs(deltaT), 1000 / 8) / 1000 * frameSpeed;
if (!_isInteracting) Vec2.add(_moveCurr, _movePrev, _spinSpeed);
if (!_isInteracting) Vec2.add(_rotCurr, _rotPrev, _spinSpeed);
}
// force an update at start

View File

@@ -11,13 +11,15 @@ import InputObserver, { ModifiersKeys, ButtonsType } from '../../mol-util/input/
import { RxEventHelper } from '../../mol-util/rx-event-helper';
type Canvas3D = import('../canvas3d').Canvas3D
type HoverEvent = import('../canvas3d').Canvas3D.HoverEvent
type ClickEvent = import('../canvas3d').Canvas3D.ClickEvent
export class Canvas3dInteractionHelper {
private ev = RxEventHelper.create();
readonly events = {
highlight: this.ev<import('../canvas3d').Canvas3D.HighlightEvent>(),
click: this.ev<import('../canvas3d').Canvas3D.ClickEvent>(),
hover: this.ev<HoverEvent>(),
click: this.ev<ClickEvent>(),
};
private cX = -1;
@@ -52,14 +54,14 @@ export class Canvas3dInteractionHelper {
return;
}
// only highlight the latest
if (!this.inside || this.currentIdentifyT !== t) {
return;
}
const loci = this.getLoci(this.id);
// only broadcast the latest hover
if (!Representation.Loci.areEqual(this.prevLoci, loci)) {
this.events.highlight.next({ current: loci, modifiers: this.modifiers });
this.events.hover.next({ current: loci, buttons: this.buttons, modifiers: this.modifiers });
this.prevLoci = loci;
}
}
@@ -76,12 +78,13 @@ export class Canvas3dInteractionHelper {
this.inside = false;
if (this.prevLoci.loci !== EmptyLoci) {
this.prevLoci = Representation.Loci.Empty;
this.events.highlight.next({ current: this.prevLoci });
this.events.hover.next({ current: this.prevLoci, buttons: this.buttons, modifiers: this.modifiers });
}
}
move(x: number, y: number, modifiers: ModifiersKeys) {
move(x: number, y: number, buttons: ButtonsType, modifiers: ModifiersKeys) {
this.inside = true;
this.buttons = buttons;
this.modifiers = modifiers;
this.cX = x;
this.cY = y;
@@ -98,7 +101,7 @@ export class Canvas3dInteractionHelper {
modify(modifiers: ModifiersKeys) {
if (this.prevLoci.loci === EmptyLoci || ModifiersKeys.areEqual(modifiers, this.modifiers)) return;
this.modifiers = modifiers;
this.events.highlight.next({ current: this.prevLoci, modifiers: this.modifiers });
this.events.hover.next({ current: this.prevLoci, buttons: this.buttons, modifiers: this.modifiers });
}
dispose() {
@@ -107,8 +110,8 @@ export class Canvas3dInteractionHelper {
constructor(private canvasIdentify: Canvas3D['identify'], private getLoci: Canvas3D['getLoci'], input: InputObserver, private maxFps: number = 15) {
input.move.subscribe(({x, y, inside, buttons, modifiers }) => {
if (!inside || buttons) { return; }
this.move(x, y, modifiers);
if (!inside) return;
this.move(x, y, buttons, modifiers);
});
input.leave.subscribe(() => {

View File

@@ -10,6 +10,7 @@ import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
import { createTexture, Texture } from '../../mol-gl/webgl/texture';
import { Camera } from '../camera';
export class DrawPass {
colorTarget: RenderTarget
@@ -18,11 +19,11 @@ export class DrawPass {
private depthTarget: RenderTarget | null
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private debugHelper: BoundingSphereHelper) {
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper) {
const { gl, extensions } = webgl
const width = gl.drawingBufferWidth
const height = gl.drawingBufferHeight
this.colorTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight)
this.colorTarget = createRenderTarget(webgl, width, height)
this.packedDepth = !extensions.depthTexture
this.depthTarget = this.packedDepth ? createRenderTarget(webgl, width, height) : null
this.depthTexture = this.depthTarget ? this.depthTarget.texture : createTexture(webgl, 'image-depth', 'depth', 'ushort', 'nearest')
@@ -42,28 +43,28 @@ export class DrawPass {
}
render(toDrawingBuffer: boolean) {
const { webgl, renderer, scene, debugHelper, colorTarget, depthTarget } = this
const { gl } = webgl
const { webgl, renderer, scene, camera, debugHelper, colorTarget, depthTarget } = this
if (toDrawingBuffer) {
webgl.unbindFramebuffer()
} else {
colorTarget.bind()
}
renderer.setViewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)
renderer.render(scene, 'color', true)
renderer.setViewport(0, 0, colorTarget.width, colorTarget.height)
renderer.render(scene, camera, 'color', true)
if (debugHelper.isEnabled) {
debugHelper.syncVisibility()
renderer.render(debugHelper.scene, 'color', false)
renderer.render(debugHelper.scene, camera, 'color', false)
}
// do a depth pass if not rendering to drawing buffer and
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
if (!toDrawingBuffer && depthTarget) {
depthTarget.bind()
renderer.render(scene, 'depth', true)
renderer.render(scene, camera, 'depth', true)
if (debugHelper.isEnabled) {
debugHelper.syncVisibility()
renderer.render(debugHelper.scene, 'depth', false)
renderer.render(debugHelper.scene, camera, 'depth', false)
}
}
}

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { WebGLContext } from '../../mol-gl/webgl/context';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { DrawPass } from './draw'
import { PostprocessingPass, PostprocessingParams } from './postprocessing'
import { MultiSamplePass, MultiSampleParams } from './multi-sample'
import { Camera } from '../camera';
import { Viewport } from '../camera/util';
export const ImageParams = {
multiSample: PD.Group(MultiSampleParams),
postprocessing: PD.Group(PostprocessingParams),
}
export type ImageProps = PD.Values<typeof ImageParams>
export class ImagePass {
private _width = 1024
private _height = 768
private _camera = new Camera()
private _colorTarget: RenderTarget
get colorTarget() { return this._colorTarget }
readonly drawPass: DrawPass
private readonly postprocessing: PostprocessingPass
private readonly multiSample: MultiSamplePass
get width() { return this._width }
get height() { return this._height }
constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, props: Partial<ImageProps>) {
const p = { ...PD.getDefaultValues(ImageParams), ...props }
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper)
this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing)
this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample)
this.setSize(this._width, this._height)
}
setSize(width: number, height: number) {
this._width = width
this._height = height
this.drawPass.setSize(width, height)
this.postprocessing.setSize(width, height)
this.multiSample.setSize(width, height)
}
setProps(props: Partial<ImageProps> = {}) {
if (props.postprocessing) this.postprocessing.setProps(props.postprocessing)
if (props.multiSample) this.multiSample.setProps(props.multiSample)
}
render() {
Camera.copySnapshot(this._camera.state, this.camera.state)
Viewport.set(this._camera.viewport, 0, 0, this._width, this._height)
this._camera.update()
this.renderer.setViewport(0, 0, this._width, this._height);
if (this.multiSample.enabled) {
this.multiSample.render(false)
this._colorTarget = this.multiSample.colorTarget
} else {
this.drawPass.render(false)
if (this.postprocessing.enabled) {
this.postprocessing.render(false)
this._colorTarget = this.postprocessing.target
} else {
this._colorTarget = this.drawPass.colorTarget
}
}
}
getImageData(width: number, height: number) {
this.setSize(width, height)
this.render()
const pd = this.colorTarget.getPixelData()
return new ImageData(new Uint8ClampedArray(pd.array), pd.width, pd.height)
}
}

View File

@@ -54,6 +54,7 @@ export type MultiSampleProps = PD.Values<typeof MultiSampleParams>
export class MultiSamplePass {
props: MultiSampleProps
colorTarget: RenderTarget
private composeTarget: RenderTarget
private holdTarget: RenderTarget
@@ -65,6 +66,7 @@ export class MultiSamplePass {
constructor(private webgl: WebGLContext, private camera: Camera, private drawPass: DrawPass, private postprocessing: PostprocessingPass, props: Partial<MultiSampleProps>) {
const { gl } = webgl
this.colorTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight)
this.composeTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight)
this.holdTarget = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight)
this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture)
@@ -92,6 +94,7 @@ export class MultiSamplePass {
}
setSize(width: number, height: number) {
this.colorTarget.setSize(width, height)
this.composeTarget.setSize(width, height)
this.holdTarget.setSize(width, height)
ValueCell.update(this.compose.values.uTexSize, Vec2.set(this.compose.values.uTexSize.ref.value, width, height))
@@ -102,15 +105,15 @@ export class MultiSamplePass {
if (props.sampleLevel !== undefined) this.props.sampleLevel = props.sampleLevel
}
render() {
render(toDrawingBuffer: boolean) {
if (this.props.mode === 'temporal') {
this.renderTemporalMultiSample()
this.renderTemporalMultiSample(toDrawingBuffer)
} else {
this.renderMultiSample()
this.renderMultiSample(toDrawingBuffer)
}
}
private renderMultiSample() {
private renderMultiSample(toDrawingBuffer: boolean) {
const { camera, compose, composeTarget, drawPass, postprocessing, webgl } = this
const { gl, state } = webgl
@@ -135,7 +138,7 @@ export class MultiSamplePass {
for (let i = 0; i < offsetList.length; ++i) {
const offset = offsetList[i]
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height)
camera.updateMatrices()
camera.update()
// the theory is that equal weights for each sample lead to an accumulation of rounding
// errors. The following equation varies the sampleWeight per sample so that it is uniformly
@@ -168,16 +171,20 @@ export class MultiSamplePass {
ValueCell.update(compose.values.tColor, composeTarget.texture)
compose.update()
webgl.unbindFramebuffer()
if (toDrawingBuffer) {
webgl.unbindFramebuffer()
} else {
this.colorTarget.bind()
}
gl.viewport(0, 0, width, height)
state.disable(gl.BLEND)
compose.render()
camera.viewOffset.enabled = false
camera.updateMatrices()
camera.update()
}
private renderTemporalMultiSample() {
private renderTemporalMultiSample(toDrawingBuffer: boolean) {
const { camera, compose, composeTarget, holdTarget, postprocessing, drawPass, webgl } = this
const { gl, state } = webgl
@@ -223,7 +230,7 @@ export class MultiSamplePass {
for (let i = 0; i < numSamplesPerFrame; ++i) {
const offset = offsetList[this.sampleIndex]
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height)
camera.updateMatrices()
camera.update()
// render scene and optionally postprocess
drawPass.render(false)
@@ -252,7 +259,11 @@ export class MultiSamplePass {
ValueCell.update(compose.values.uWeight, 1.0)
ValueCell.update(compose.values.tColor, composeTarget.texture)
compose.update()
webgl.unbindFramebuffer()
if (toDrawingBuffer) {
webgl.unbindFramebuffer()
} else {
this.colorTarget.bind()
}
gl.viewport(0, 0, width, height)
state.disable(gl.BLEND)
compose.render()
@@ -261,7 +272,11 @@ export class MultiSamplePass {
ValueCell.update(compose.values.uWeight, 1.0 - accumulationWeight)
ValueCell.update(compose.values.tColor, holdTarget.texture)
compose.update()
webgl.unbindFramebuffer()
if (toDrawingBuffer) {
webgl.unbindFramebuffer()
} else {
this.colorTarget.bind()
}
gl.viewport(0, 0, width, height)
if (accumulationWeight === 0) state.disable(gl.BLEND)
else state.enable(gl.BLEND)
@@ -269,7 +284,7 @@ export class MultiSamplePass {
}
camera.viewOffset.enabled = false
camera.updateMatrices()
camera.update()
if (this.sampleIndex >= offsetList.length) this.sampleIndex = -1
}
}

View File

@@ -10,6 +10,7 @@ import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
import { PickingId } from '../../mol-geo/geometry/picking';
import { decodeFloatRGB } from '../../mol-util/float-packing';
import { Camera } from '../camera';
export class PickPass {
pickDirty = true
@@ -26,7 +27,7 @@ export class PickPass {
private pickWidth: number
private pickHeight: number
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private pickBaseScale: number) {
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private pickBaseScale: number) {
const { gl } = webgl
const width = gl.drawingBufferWidth
const height = gl.drawingBufferHeight
@@ -64,14 +65,14 @@ export class PickPass {
}
render() {
const { renderer, scene } = this
const { renderer, scene, camera } = this
renderer.setViewport(0, 0, this.pickWidth, this.pickHeight);
this.objectPickTarget.bind();
renderer.render(scene, 'pickObject', true);
renderer.render(scene, camera, 'pickObject', true);
this.instancePickTarget.bind();
renderer.render(scene, 'pickInstance', true);
renderer.render(scene, camera, 'pickInstance', true);
this.groupPickTarget.bind();
renderer.render(scene, 'pickGroup', true);
renderer.render(scene, camera, 'pickGroup', true);
this.pickDirty = false
}

View File

@@ -160,10 +160,10 @@ export class PostprocessingPass {
}
render(toDrawingBuffer: boolean) {
ValueCell.update(this.renderable.values.uFar, this.camera.state.far)
ValueCell.update(this.renderable.values.uNear, this.camera.state.near)
ValueCell.update(this.renderable.values.uFogFar, this.camera.state.fogFar)
ValueCell.update(this.renderable.values.uFogNear, this.camera.state.fogNear)
ValueCell.update(this.renderable.values.uFar, this.camera.far)
ValueCell.update(this.renderable.values.uNear, this.camera.near)
ValueCell.update(this.renderable.values.uFogFar, this.camera.fogFar)
ValueCell.update(this.renderable.values.uFogNear, this.camera.fogNear)
ValueCell.update(this.renderable.values.dOrthographic, this.camera.state.mode === 'orthographic' ? 1 : 0)
const { gl, state } = this.webgl

View File

@@ -4,16 +4,57 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
/** resize canvas to container element */
/** Set canvas size taking `devicePixelRatio` into account */
export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height: number) {
canvas.width = window.devicePixelRatio * width
canvas.height = window.devicePixelRatio * height
Object.assign(canvas.style, { width: `${width}px`, height: `${height}px` })
}
/** Resize canvas to container element taking `devicePixelRatio` into account */
export function resizeCanvas (canvas: HTMLCanvasElement, container: Element) {
let w = window.innerWidth
let h = window.innerHeight
let width = window.innerWidth
let height = window.innerHeight
if (container !== document.body) {
let bounds = container.getBoundingClientRect()
w = bounds.right - bounds.left
h = bounds.bottom - bounds.top
width = bounds.right - bounds.left
height = bounds.bottom - bounds.top
}
canvas.width = window.devicePixelRatio * w
canvas.height = window.devicePixelRatio * h
Object.assign(canvas.style, { width: `${w}px`, height: `${h}px` })
setCanvasSize(canvas, width, height)
}
function _canvasToBlob(canvas: HTMLCanvasElement, callback: BlobCallback, type?: string, quality?: any) {
const bin = atob(canvas.toDataURL(type, quality).split(',')[1])
const len = bin.length
const len32 = len >> 2
const a8 = new Uint8Array(len)
const a32 = new Uint32Array( a8.buffer, 0, len32 )
let j = 0
for (let i = 0; i < len32; ++i) {
a32[i] = bin.charCodeAt(j++) |
bin.charCodeAt(j++) << 8 |
bin.charCodeAt(j++) << 16 |
bin.charCodeAt(j++) << 24
}
let tailLength = len & 3;
while (tailLength--) a8[j] = bin.charCodeAt(j++)
callback(new Blob([a8], { type: type || 'image/png' }));
}
export async function canvasToBlob(canvas: HTMLCanvasElement, type?: string, quality?: any): Promise<Blob> {
return new Promise((resolve, reject) => {
const callback = (blob: Blob | null) => {
if (blob) resolve(blob)
else reject('no blob returned')
}
if (!HTMLCanvasElement.prototype.toBlob) {
_canvasToBlob(canvas, callback, type, quality)
} else {
canvas.toBlob(callback, type, quality)
}
})
}

View File

@@ -218,6 +218,16 @@ namespace Table {
return ret;
}
export function toArrays<S extends Schema>(table: Table<S>) {
const arrays: { [k: string]: ArrayLike<any> } = {}
const { _columns } = table;
for (let i = 0; i < _columns.length; i++) {
const c = _columns[i]
arrays[c] = table[c].toArray();
}
return arrays as { [k in keyof S]: ArrayLike<S[k]['T']> }
}
export function formatToString<S extends Schema>(table: Table<S>) {
const sb = StringBuilder.create();

View File

@@ -9,6 +9,7 @@ import Tuple from '../tuple'
export const Empty = Tuple.Zero;
export function ofRange(min: number, max: number) { return max < min ? Tuple.create(min, min) : Tuple.create(min, max + 1); }
export function ofBounds(start: number, end: number) { return end <= start ? Tuple.create(start, start) : Tuple.create(start, end); }
export function ofLength(length: number) { return length < 0 ? Tuple.create(0, 0) : Tuple.create(0, length); }
export const is = Tuple.is;
export const start = Tuple.fst;

View File

@@ -14,6 +14,8 @@ namespace Interval {
export const ofRange: <T extends number = number>(min: T, max: T) => Interval<T> = Impl.ofRange as any;
/** Create interval from bounds [start, end), i.e. [start, end - 1] */
export const ofBounds: <T extends number = number>(start: T, end: T) => Interval<T> = Impl.ofBounds as any;
/** Create interval from length [0, length), i.e. [0, length - 1] */
export const ofLength: <T extends number = number>(length: T) => Interval<T> = Impl.ofLength as any;
export const is: <T extends number = number>(v: any) => v is Interval<T> = Impl.is as any;
/** Test if a value is within the bounds of the interval */

View File

@@ -101,9 +101,9 @@ export namespace MeshBuilder {
}
}
export function addCage(state: State, t: Mat4, cage: Cage, radius: number, detail: number) {
export function addCage(state: State, t: Mat4, cage: Cage, radius: number, detail: number, radialSegments: number) {
const { vertices: va, edges: ea } = cage
const cylinderProps = { radiusTop: radius, radiusBottom: radius }
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments }
for (let i = 0, il = ea.length; i < il; i += 2) {
Vec3.fromArray(tmpVecA, va, ea[i] * 3)
Vec3.fromArray(tmpVecB, va, ea[i + 1] * 3)

View File

@@ -4,6 +4,9 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat4, Vec3 } from '../../mol-math/linear-algebra'
import { NumberArray } from '../../mol-util/type-helpers'
export interface Cage {
readonly vertices: ArrayLike<number>
readonly edges: ArrayLike<number>
@@ -11,4 +14,24 @@ export interface Cage {
export function createCage(vertices: ArrayLike<number>, edges: ArrayLike<number>): Cage {
return { vertices, edges }
}
export function copyCage(cage: Cage): Cage {
return {
vertices: new Float32Array(cage.vertices),
edges: new Uint32Array(cage.edges)
}
}
const tmpV = Vec3.zero()
/** Transform primitive in-place */
export function transformCage(cage: Cage, t: Mat4) {
const { vertices } = cage
for (let i = 0, il = vertices.length; i < il; i += 3) {
// position
Vec3.transformMat4(tmpV, Vec3.fromArray(tmpV, vertices, i), t)
Vec3.toArray(tmpV, vertices as NumberArray, i)
}
return cage
}

View File

@@ -30,6 +30,14 @@ export function createPrimitive(vertices: ArrayLike<number>, indices: ArrayLike<
return builder.getPrimitive()
}
export function copyPrimitive(primitive: Primitive): Primitive {
return {
vertices: new Float32Array(primitive.vertices),
normals: new Float32Array(primitive.normals),
indices: new Uint32Array(primitive.indices)
}
}
export interface PrimitiveBuilder {
add(a: Vec3, b: Vec3, c: Vec3): void
getPrimitive(): Primitive

View File

@@ -78,10 +78,22 @@ export function PentagonalPrism() {
let hexagonalPrism: Primitive
export function HexagonalPrism() {
if (!hexagonalPrism) hexagonalPrism = Prism(polygon(6, true))
if (!hexagonalPrism) hexagonalPrism = Prism(polygon(6, false))
return hexagonalPrism
}
let shiftedHexagonalPrism: Primitive
export function ShiftedHexagonalPrism() {
if (!shiftedHexagonalPrism) shiftedHexagonalPrism = Prism(polygon(6, true))
return shiftedHexagonalPrism
}
let heptagonalPrism: Primitive
export function HeptagonalPrism() {
if (!heptagonalPrism) heptagonalPrism = Prism(polygon(7, false))
return heptagonalPrism
}
//
/**
@@ -133,6 +145,6 @@ export function PentagonalPrismCage() {
let hexagonalPrismCage: Cage
export function HexagonalPrismCage() {
if (!hexagonalPrismCage) hexagonalPrismCage = PrismCage(polygon(6, true))
if (!hexagonalPrismCage) hexagonalPrismCage = PrismCage(polygon(6, false))
return hexagonalPrismCage
}

View File

@@ -28,11 +28,9 @@ import { createEmptyTransparency } from '../../mol-geo/geometry/transparency-dat
function createRenderer(gl: WebGLRenderingContext) {
const ctx = createContext(gl)
const camera = new Camera({
near: 0.01,
far: 10000,
position: Vec3.create(0, 0, 50)
})
const renderer = Renderer.create(ctx, camera)
const renderer = Renderer.create(ctx)
return { ctx, camera, renderer }
}

View File

@@ -168,6 +168,7 @@ export const GlobalUniformSchema = {
uFogFar: UniformSpec('f'),
uFogColor: UniformSpec('v3'),
uTransparentBackground: UniformSpec('i'),
uPickingAlphaThreshold: UniformSpec('f'),
uInteriorDarkening: UniformSpec('f'),
}

View File

@@ -38,14 +38,15 @@ interface Renderer {
readonly props: Readonly<RendererProps>
clear: () => void
render: (scene: Scene, variant: GraphicsRenderVariant, clear: boolean) => void
render: (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean) => void
setProps: (props: Partial<RendererProps>) => void
setViewport: (x: number, y: number, width: number, height: number) => void
dispose: () => void
}
export const RendererParams = {
backgroundColor: PD.Color(Color(0x000000)),
backgroundColor: PD.Color(Color(0x000000), { description: 'Background color of the 3D canvas' }),
transparentBackground: PD.Boolean(false, { description: 'Background opacity of the 3D canvas' }),
pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }),
interiorDarkening: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
@@ -59,39 +60,40 @@ export const RendererParams = {
export type RendererProps = PD.Values<typeof RendererParams>
namespace Renderer {
export function create(ctx: WebGLContext, camera: Camera, props: Partial<RendererProps> = {}): Renderer {
export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
const { gl, state, stats } = ctx
const p = deepClone({ ...PD.getDefaultValues(RendererParams), ...props })
const viewport = Viewport()
const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor)
const view = Mat4.clone(camera.view)
const invView = Mat4.invert(Mat4.identity(), view)
const modelView = Mat4.clone(camera.view)
const invModelView = Mat4.invert(Mat4.identity(), modelView)
const invProjection = Mat4.invert(Mat4.identity(), camera.projection)
const modelViewProjection = Mat4.mul(Mat4.identity(), modelView, camera.projection)
const invModelViewProjection = Mat4.invert(Mat4.identity(), modelViewProjection)
const view = Mat4()
const invView = Mat4()
const modelView = Mat4()
const invModelView = Mat4()
const invProjection = Mat4()
const modelViewProjection = Mat4()
const invModelViewProjection = Mat4()
const viewOffset = camera.viewOffset.enabled ? Vec2.create(camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2()
const viewOffset = Vec2()
const globalUniforms: GlobalUniformValues = {
uModel: ValueCell.create(Mat4.identity()),
uView: ValueCell.create(camera.view),
uView: ValueCell.create(view),
uInvView: ValueCell.create(invView),
uModelView: ValueCell.create(modelView),
uInvModelView: ValueCell.create(invModelView),
uInvProjection: ValueCell.create(invProjection),
uProjection: ValueCell.create(Mat4.clone(camera.projection)),
uProjection: ValueCell.create(Mat4()),
uModelViewProjection: ValueCell.create(modelViewProjection),
uInvModelViewProjection: ValueCell.create(invModelViewProjection),
uIsOrtho: ValueCell.create(camera.state.mode === 'orthographic' ? 1 : 0),
uIsOrtho: ValueCell.create(1),
uViewOffset: ValueCell.create(viewOffset),
uPixelRatio: ValueCell.create(ctx.pixelRatio),
uViewportHeight: ValueCell.create(viewport.height),
uViewport: ValueCell.create(Viewport.toVec4(Vec4(), viewport)),
uViewOffset: ValueCell.create(viewOffset),
uLightIntensity: ValueCell.create(p.lightIntensity),
uAmbientIntensity: ValueCell.create(p.ambientIntensity),
@@ -100,13 +102,14 @@ namespace Renderer {
uRoughness: ValueCell.create(p.roughness),
uReflectivity: ValueCell.create(p.reflectivity),
uCameraPosition: ValueCell.create(Vec3.clone(camera.state.position)),
uNear: ValueCell.create(camera.state.near),
uFar: ValueCell.create(camera.state.far),
uFogNear: ValueCell.create(camera.state.fogNear),
uFogFar: ValueCell.create(camera.state.fogFar),
uCameraPosition: ValueCell.create(Vec3()),
uNear: ValueCell.create(1),
uFar: ValueCell.create(10000),
uFogNear: ValueCell.create(1),
uFogFar: ValueCell.create(10000),
uFogColor: ValueCell.create(bgColor),
uTransparentBackground: ValueCell.create(p.transparentBackground ? 1 : 0),
uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
uInteriorDarkening: ValueCell.create(p.interiorDarkening),
}
@@ -158,7 +161,7 @@ namespace Renderer {
}
}
const render = (scene: Scene, variant: GraphicsRenderVariant, clear: boolean) => {
const render = (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean) => {
ValueCell.update(globalUniforms.uModel, scene.view)
ValueCell.update(globalUniforms.uView, camera.view)
ValueCell.update(globalUniforms.uInvView, Mat4.invert(invView, camera.view))
@@ -173,10 +176,10 @@ namespace Renderer {
ValueCell.update(globalUniforms.uViewOffset, camera.viewOffset.enabled ? Vec2.set(viewOffset, camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2.set(viewOffset, 0, 0))
ValueCell.update(globalUniforms.uCameraPosition, camera.state.position)
ValueCell.update(globalUniforms.uFar, camera.state.far)
ValueCell.update(globalUniforms.uNear, camera.state.near)
ValueCell.update(globalUniforms.uFogFar, camera.state.fogFar)
ValueCell.update(globalUniforms.uFogNear, camera.state.fogNear)
ValueCell.update(globalUniforms.uFar, camera.far)
ValueCell.update(globalUniforms.uNear, camera.near)
ValueCell.update(globalUniforms.uFogFar, camera.fogFar)
ValueCell.update(globalUniforms.uFogNear, camera.fogNear)
globalUniformsNeedUpdate = true
state.currentRenderItemId = -1
@@ -191,7 +194,7 @@ namespace Renderer {
if (clear) {
if (variant === 'color') {
state.clearColor(bgColor[0], bgColor[1], bgColor[2], 1.0)
state.clearColor(bgColor[0], bgColor[1], bgColor[2], p.transparentBackground ? 0 : 1)
} else {
state.clearColor(1, 1, 1, 1)
}
@@ -204,7 +207,7 @@ namespace Renderer {
if (r.state.opaque) renderObject(r, variant)
}
state.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE)
state.enable(gl.BLEND)
for (let i = 0, il = renderables.length; i < il; ++i) {
const r = renderables[i]
@@ -224,7 +227,7 @@ namespace Renderer {
clear: () => {
state.depthMask(true)
state.colorMask(true, true, true, true)
state.clearColor(bgColor[0], bgColor[1], bgColor[2], 1.0)
state.clearColor(bgColor[0], bgColor[1], bgColor[2], p.transparentBackground ? 0 : 1)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
},
render,
@@ -243,6 +246,10 @@ namespace Renderer {
Color.toVec3Normalized(bgColor, p.backgroundColor)
ValueCell.update(globalUniforms.uFogColor, Vec3.copy(globalUniforms.uFogColor.ref.value, bgColor))
}
if (props.transparentBackground !== undefined && props.transparentBackground !== p.transparentBackground) {
p.transparentBackground = props.transparentBackground
ValueCell.update(globalUniforms.uTransparentBackground, p.transparentBackground ? 1 : 0)
}
if (props.lightIntensity !== undefined && props.lightIntensity !== p.lightIntensity) {
p.lightIntensity = props.lightIntensity
ValueCell.update(globalUniforms.uLightIntensity, p.lightIntensity)

View File

@@ -2,10 +2,11 @@ export default `
#ifdef dUseFog
float depth = length(vViewPosition);
float fogFactor = smoothstep(uFogNear, uFogFar, depth);
gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
if (fogAlpha < 0.01)
discard;
gl_FragColor = vec4(gl_FragColor.rgb, fogAlpha);
if (uTransparentBackground == 0) {
gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
} else {
float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
gl_FragColor.a = fogAlpha;
}
#endif
`

View File

@@ -36,7 +36,7 @@ geometry.normal = normal;
geometry.viewDir = normalize(vViewPosition);
IncidentLight directLight;
directLight.direction = geometry.viewDir;
directLight.direction = vec3(0.0, 0.0, -1.0);
directLight.color = vec3(uLightIntensity);
RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);

View File

@@ -5,7 +5,11 @@ uniform int uGroupCount;
uniform vec3 uHighlightColor;
uniform vec3 uSelectColor;
varying float vMarker;
#if __VERSION__ != 300
varying float vMarker;
#else
flat in float vMarker;
#endif
varying vec3 vViewPosition;
@@ -18,6 +22,7 @@ uniform vec3 uFogColor;
uniform float uAlpha;
uniform float uPickingAlphaThreshold;
uniform int uPickable;
uniform int uTransparentBackground;
uniform float uInteriorDarkening;
`

View File

@@ -8,7 +8,11 @@ uniform int uGroupCount;
uniform vec2 uMarkerTexDim;
uniform sampler2D tMarker;
varying float vMarker;
#if __VERSION__ != 300
varying float vMarker;
#else
flat out float vMarker;
#endif
varying vec3 vViewPosition;
`

View File

@@ -124,8 +124,8 @@ void main(void){
#elif defined(dColorType_depth)
gl_FragColor = material;
#else
vec3 normal = cameraNormal;
vec3 vViewPosition = -cameraPos;
vec3 normal = -cameraNormal;
vec3 vViewPosition = cameraPos;
#include apply_light_color
if (interior) {

View File

@@ -190,6 +190,7 @@ export interface WebGLContext {
readonly framebufferCache: FramebufferCache
readonly maxTextureSize: number
readonly maxRenderbufferSize: number
readonly maxDrawBuffers: number
unbindFramebuffer: () => void
@@ -212,6 +213,7 @@ export function createContext(gl: GLRenderingContext): WebGLContext {
const parameters = {
maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE) as number,
maxRenderbufferSize: gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) as number,
maxDrawBuffers: isWebGL2(gl) ? gl.getParameter(gl.MAX_DRAW_BUFFERS) as number : 0,
maxVertexTextureImageUnits: gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) as number,
}
@@ -274,6 +276,7 @@ export function createContext(gl: GLRenderingContext): WebGLContext {
framebufferCache,
get maxTextureSize () { return parameters.maxTextureSize },
get maxRenderbufferSize () { return parameters.maxRenderbufferSize },
get maxDrawBuffers () { return parameters.maxDrawBuffers },
unbindFramebuffer: () => unbindFramebuffer(gl),

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ReaderResult as Result } from '../result'
import { Task } from '../../../mol-task'
import { parseCsv } from '../csv/parser';
import { Column, Table } from '../../../mol-data/db';
import { toTable } from '../cif/schema';
import Schema = Column.Schema
import { CsvTable } from '../csv/data-model';
export const Schema3DG = {
/** Chromosome name */
chromosome: Schema.str,
/** Base position */
position: Schema.int,
/** X coordinate */
x: Schema.float,
/** Y coordinate */
y: Schema.float,
/** Z coordinate */
z: Schema.float,
}
export type Schema3DG = typeof Schema3DG
export interface File3DG {
table: Table<Schema3DG>
}
const FieldNames = [ 'chromosome', 'position', 'x', 'y', 'z' ]
function categoryFromTable(name: string, table: CsvTable) {
return {
name,
rowCount: table.rowCount,
fieldNames: FieldNames,
getField: (name: string) => {
return table.getColumn(FieldNames.indexOf(name).toString())
}
}
}
export function parse3DG(data: string) {
return Task.create<Result<File3DG>>('Parse 3DG', async ctx => {
const opts = { quote: '', comment: '#', delimiter: '\t', noColumnNames: true }
const csvFile = await parseCsv(data, opts).runInContext(ctx)
if (csvFile.isError) return Result.error(csvFile.message, csvFile.line)
const category = categoryFromTable('3dg', csvFile.result.table)
const table = toTable(Schema3DG, category)
return Result.success({ table })
});
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { parse3DG } from '../3dg/parser';
const basic3dgString = `1(mat) 1420000 0.791377837067 10.9947291355 -13.1882897693
1(mat) 1440000 -0.268241283699 10.5200875887 -13.0896257278
1(mat) 1460000 -1.3853075236 10.5513787498 -13.1440142173
1(mat) 1480000 -1.55984101733 11.4340829129 -13.6026301209
1(mat) 1500000 -0.770991778399 11.4758488546 -14.5881137222
1(mat) 1520000 -0.0848245107875 12.2624690808 -14.354289628
1(mat) 1540000 -0.458643807046 12.5985791771 -13.4701149287
1(mat) 1560000 -0.810322906201 12.2461643989 -12.3172933413
1(mat) 1580000 -2.08211172035 12.8886838656 -12.8742007778
1(mat) 1600000 -3.52093948201 13.1850935438 -12.4118684428`
describe('3dg reader', () => {
it('basic', async () => {
const parsed = await parse3DG(basic3dgString).run();
expect(parsed.isError).toBe(false)
if (parsed.isError) return;
const { chromosome, position, x, y, z } = parsed.result.table;
expect(chromosome.value(0)).toBe('1(mat)')
expect(position.value(1)).toBe(1440000)
expect(x.value(5)).toBe(-0.0848245107875)
expect(y.value(5)).toBe(12.2624690808)
expect(z.value(5)).toBe(-14.354289628)
});
});

View File

@@ -174,7 +174,7 @@ function moveNextInternal(state: State) {
if (tokenizer.position >= tokenizer.length) {
state.tokenType = CsvTokenType.End;
return true;
return false;
}
tokenizer.tokenStart = tokenizer.position;
@@ -209,22 +209,26 @@ function moveNext(state: State) {
function readRecordsChunk(chunkSize: number, state: State) {
if (state.tokenType === CsvTokenType.End) return 0
let newRecord = moveNext(state);
if (newRecord) ++state.recordCount
let counter = 0;
let newRecord: boolean | undefined
const { tokens, tokenizer } = state;
let counter = 0;
while (state.tokenType === CsvTokenType.Value && counter < chunkSize) {
TokenBuilder.add(tokens[state.fieldCount % state.columnCount], tokenizer.tokenStart, tokenizer.tokenEnd);
++state.fieldCount
newRecord = moveNext(state);
if (newRecord) ++state.recordCount
++counter;
if (newRecord) {
++state.recordCount
++counter;
}
}
return counter;
}
function readRecordsChunks(state: State) {
let newRecord = moveNext(state);
if (newRecord) ++state.recordCount
return chunkedSubtask(state.runtimeCtx, 100000, state, readRecordsChunk,
(ctx, state) => ctx.update({ message: 'Parsing...', current: state.tokenizer.position, max: state.data.length }));
}

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3, Mat4 } from '../../linear-algebra'
@@ -9,9 +10,10 @@ import { SpacegroupName, TransformData, GroupData, getSpacegroupIndex, OperatorD
import { SymmetryOperator } from '../../geometry';
interface SpacegroupCell {
// zero based spacegroup number
/** Zero based spacegroup number */
readonly index: number,
readonly size: Vec3,
readonly volume: number,
readonly anglesInRadians: Vec3,
/** Transfrom cartesian -> fractional coordinates within the cell */
readonly toFractional: Mat4,
@@ -34,7 +36,7 @@ namespace SpacegroupCell {
return cell.index === 0 && cell.size[0] === 1 && cell.size[1] === 1 && cell.size[1] === 1;
}
// returns Zero cell if the spacegroup does not exist
/** Returns Zero cell if the spacegroup does not exist */
export function create(nameOrNumber: number | string | SpacegroupName, size: Vec3, anglesInRadians: Vec3): SpacegroupCell {
const index = getSpacegroupIndex(nameOrNumber);
if (index < 0) {
@@ -42,6 +44,8 @@ namespace SpacegroupCell {
return Zero;
}
const volume = size[0] * size[1] * size[2]
const alpha = anglesInRadians[0];
const beta = anglesInRadians[1];
const gamma = anglesInRadians[2];
@@ -64,13 +68,13 @@ namespace SpacegroupCell {
]);
const toFractional = Mat4.invert(Mat4.zero(), fromFractional)!;
return { index, size, anglesInRadians, toFractional, fromFractional };
return { index, size, volume, anglesInRadians, toFractional, fromFractional };
}
}
namespace Spacegroup {
// P1 with [1, 1, 1] cell.
/** P1 with [1, 1, 1] cell */
export const ZeroP1 = create(SpacegroupCell.Zero);
export function create(cell: SpacegroupCell): Spacegroup {
@@ -78,18 +82,56 @@ namespace Spacegroup {
return { name: SpacegroupNames[cell.index], cell, operators };
}
const _tempVec = Vec3.zero(), _tempMat = Mat4.zero();
export function updateOperatorMatrix(spacegroup: Spacegroup, index: number, i: number, j: number, k: number, target: Mat4) {
_tempVec[0] = i;
_tempVec[1] = j;
_tempVec[2] = k;
const _ijkVec = Vec3();
const _tempMat = Mat4();
export function setOperatorMatrix(spacegroup: Spacegroup, index: number, i: number, j: number, k: number, target: Mat4) {
Vec3.set(_ijkVec, i, j, k);
Mat4.fromTranslation(_tempMat, _tempVec);
return Mat4.mul(target, Mat4.mul(target, Mat4.mul(target, spacegroup.cell.fromFractional, _tempMat), spacegroup.operators[index]), spacegroup.cell.toFractional);
Mat4.fromTranslation(_tempMat, _ijkVec);
return Mat4.mul(
target,
Mat4.mul(
target,
Mat4.mul(target, spacegroup.cell.fromFractional, _tempMat),
spacegroup.operators[index]
),
spacegroup.cell.toFractional
);
}
export function getSymmetryOperator(spacegroup: Spacegroup, index: number, i: number, j: number, k: number): SymmetryOperator {
const operator = updateOperatorMatrix(spacegroup, index, i, j, k, Mat4.zero());
const operator = setOperatorMatrix(spacegroup, index, i, j, k, Mat4.zero());
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k), index);
}
const _translationRef = Vec3()
const _translationRefSymop = Vec3()
const _translationSymop = Vec3()
export function setOperatorMatrixRef(spacegroup: Spacegroup, index: number, i: number, j: number, k: number, ref: Vec3, target: Mat4) {
Vec3.set(_ijkVec, i, j, k);
Vec3.floor(_translationRef, ref)
Mat4.copy(target, spacegroup.operators[index])
Vec3.floor(_translationRefSymop, Vec3.transformMat4(_translationRefSymop, ref, target))
Mat4.getTranslation(_translationSymop, target)
Vec3.sub(_translationSymop, _translationSymop, _translationRefSymop)
Vec3.add(_translationSymop, _translationSymop, _translationRef)
Vec3.add(_translationSymop, _translationSymop, _ijkVec)
Mat4.setTranslation(target, _translationSymop)
Mat4.mul(target, spacegroup.cell.fromFractional, target)
Mat4.mul(target, target, spacegroup.cell.toFractional)
return target
}
/**
* Get Symmetry operator for transformation around the given
* reference point `ref` in fractional coordinates
*/
export function getSymmetryOperatorRef(spacegroup: Spacegroup, index: number, i: number, j: number, k: number, ref: Vec3) {
const operator = setOperatorMatrixRef(spacegroup, index, i, j, k, ref, Mat4.zero());
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k), index);
}

View File

@@ -17,7 +17,7 @@
* furnished to do so, subject to the following conditions:
*/
import { Mat4, Vec3 } from '../3d'
import { Mat4, Vec3, EPSILON } from '../3d'
import { NumberArray } from '../../../mol-util/type-helpers';
interface Mat3 extends Array<number> { [d: number]: number, '@type': 'mat3', length: 9 }
@@ -118,6 +118,11 @@ namespace Mat3 {
return out;
}
const _id = identity();
export function isIdentity(m: Mat3, eps?: number) {
return areEqual(m, _id, typeof eps === 'undefined' ? EPSILON : eps);
}
export function hasNaN(m: Mat3) {
for (let i = 0; i < 9; i++) if (isNaN(m[i])) return true
return false

View File

@@ -28,6 +28,7 @@ import { EPSILON } from './common';
import { NumberArray } from '../../../mol-util/type-helpers';
interface Quat extends Array<number> { [d: number]: number, '@type': 'quat', length: 4 }
interface ReadonlyQuat extends Array<number> { readonly [d: number]: number, '@type': 'quat', length: 4 }
function Quat() {
return Quat.zero();
@@ -440,6 +441,8 @@ namespace Quat {
export function toString(a: Quat, precision?: number) {
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)} ${a[3].toPrecision(precision)}]`;
}
export const Identity: ReadonlyQuat = identity()
}
export default Quat

View File

@@ -14,4 +14,19 @@ export function radToDeg (rad: number) {
export function isPowerOfTwo (x: number) {
return (x !== 0) && (x & (x - 1)) === 0
}
/** return the value that has the largest absolute value */
export function absMax(...values: number[]) {
let max = 0
let absMax = 0
for (let i = 0, il = values.length; i < il; ++i) {
const value = values[i]
const abs = Math.abs(value)
if (abs > absMax) {
max = value
absMax = abs
}
}
return max
}

View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Model } from '../../mol-model/structure/model';
import { Task } from '../../mol-task';
import { ModelFormat } from './format';
import { _parse_mmCif } from './mmcif/parser';
import { CifCategory, CifField } from '../../mol-io/reader/cif';
import { Column } from '../../mol-data/db';
import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
import { EntityBuilder } from './common/entity';
import { File3DG } from '../../mol-io/reader/3dg/parser';
import { fillSerial } from '../../mol-util/array';
import { MoleculeType } from '../../mol-model/structure/model/types';
function getCategories(table: File3DG['table']) {
const entityIds = new Array<string>(table._rowCount)
const entityBuilder = new EntityBuilder()
const seqIdStarts = table.position.toArray({ array: Uint32Array })
const seqIdEnds = new Uint32Array(table._rowCount)
const stride = seqIdStarts[1] - seqIdStarts[0]
const objectRadius = stride / 3500
for (let i = 0, il = table._rowCount; i < il; ++i) {
const chr = table.chromosome.value(i)
const entityId = entityBuilder.getEntityId(chr, MoleculeType.DNA, chr)
entityIds[i] = entityId
seqIdEnds[i] = seqIdStarts[i] + stride - 1
}
const ihm_sphere_obj_site: CifCategory.SomeFields<mmCIF_Schema['ihm_sphere_obj_site']> = {
id: CifField.ofNumbers(fillSerial(new Uint32Array(table._rowCount))),
entity_id: CifField.ofStrings(entityIds),
seq_id_begin: CifField.ofNumbers(seqIdStarts),
seq_id_end: CifField.ofNumbers(seqIdEnds),
asym_id: CifField.ofColumn(table.chromosome),
Cartn_x: CifField.ofNumbers(Column.mapToArray(table.x, x => x * 10, Float32Array)),
Cartn_y: CifField.ofNumbers(Column.mapToArray(table.y, y => y * 10, Float32Array)),
Cartn_z: CifField.ofNumbers(Column.mapToArray(table.z, z => z * 10, Float32Array)),
object_radius: CifField.ofColumn(Column.ofConst(objectRadius, table._rowCount, Column.Schema.float)),
rmsf: CifField.ofColumn(Column.ofConst(0, table._rowCount, Column.Schema.float)),
model_id: CifField.ofColumn(Column.ofConst(1, table._rowCount, Column.Schema.int)),
}
return {
entity: entityBuilder.getEntityCategory(),
ihm_model_list: CifCategory.ofFields('ihm_model_list', {
model_id: CifField.ofNumbers([1]),
model_name: CifField.ofStrings(['3DG Model']),
}),
ihm_sphere_obj_site: CifCategory.ofFields('ihm_sphere_obj_site', ihm_sphere_obj_site)
}
}
async function mmCifFrom3dg(file3dg: File3DG) {
const categories = getCategories(file3dg.table)
return {
header: '3DG',
categoryNames: Object.keys(categories),
categories
};
}
export function trajectoryFrom3DG(file3dg: File3DG): Task<Model.Trajectory> {
return Task.create('Parse 3DG', async ctx => {
await ctx.update('Converting to mmCIF');
const cif = await mmCifFrom3dg(file3dg);
const format = ModelFormat.mmCIF(cif);
return _parse_mmCif(format, ctx);
})
}

View File

@@ -23,7 +23,7 @@ import { getSecondaryStructure } from './secondary-structure';
import { getSequence } from './sequence';
import { sortAtomSite } from './sort';
import { StructConn } from './bonds/struct_conn';
import { getMoleculeType, MoleculeType, getEntityType, getEntitySubtype } from '../../../mol-model/structure/model/types';
import { getMoleculeType, MoleculeType, getEntityType, getEntitySubtype, getDefaultChemicalComponent } from '../../../mol-model/structure/model/types';
import { ModelFormat } from '../format';
import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants';
import mmCIF_Format = ModelFormat.mmCIF
@@ -76,6 +76,8 @@ function getNcsOperators(format: mmCIF_Format) {
const m = Tensor.toMat3(Mat3(), matrixSpace, matrix.value(i));
const v = Tensor.toVec3(Vec3(), vectorSpace, vector.value(i));
if (!SymmetryOperator.checkIfRotationAndTranslation(m, v)) continue;
// ignore non-identity 'given' NCS operators
if (struct_ncs_oper.code.value(i) === 'given' && !Mat3.isIdentity(m) && !Vec3.isZero(v)) continue;
const ncsId = id.value(i)
opers[opers.length] = SymmetryOperator.ofRotationAndOffset(`ncs_${ncsId}`, m, v, ncsId);
}
@@ -125,19 +127,30 @@ function getMissingResidues(format: mmCIF_Format): Model['properties']['missingR
function getChemicalComponentMap(format: mmCIF_Format): Model['properties']['chemicalComponentMap'] {
const map = new Map<string, ChemicalComponent>();
const { chem_comp } = format.data
const { id } = chem_comp
for (let i = 0, il = id.rowCount; i < il; ++i) {
map.set(id.value(i), Table.getRow(chem_comp, i))
if (chem_comp._rowCount > 0) {
const { id } = chem_comp
for (let i = 0, il = id.rowCount; i < il; ++i) {
map.set(id.value(i), Table.getRow(chem_comp, i))
}
} else {
const uniqueNames = getUniqueComponentNames(format);
uniqueNames.forEach(n => {
map.set(n, getDefaultChemicalComponent(n));
});
}
return map
}
function getSaccharideComponentMap(format: mmCIF_Format): SaccharideComponentMap {
const map = new Map<string, SaccharideComponent>();
const { pdbx_chem_comp_identifier } = format.data
if (pdbx_chem_comp_identifier._rowCount > 0) {
const { comp_id, type, identifier } = pdbx_chem_comp_identifier
for (let i = 0, il = pdbx_chem_comp_identifier._rowCount; i < il; ++i) {
if (format.data.pdbx_chem_comp_identifier._rowCount > 0) {
// note that `pdbx_chem_comp_identifier` does not contain
// a 'SNFG CARB SYMBOL' entry for 'Unknown' saccharide components
// so we always need to check `chem_comp` for those
const { comp_id, type, identifier } = format.data.pdbx_chem_comp_identifier
for (let i = 0, il = comp_id.rowCount; i < il; ++i) {
if (type.value(i) === 'SNFG CARB SYMBOL') {
const snfgName = identifier.value(i)
const saccharideComp = SaccharidesSnfgMap.get(snfgName)
@@ -148,21 +161,24 @@ function getSaccharideComponentMap(format: mmCIF_Format): SaccharideComponentMap
}
}
}
} else if (format.data.chem_comp._rowCount > 0) {
}
if (format.data.chem_comp._rowCount > 0) {
const { id, type } = format.data.chem_comp
for (let i = 0, il = id.rowCount; i < il; ++i) {
const _id = id.value(i)
if (map.has(_id)) continue
const _type = type.value(i)
if (SaccharideCompIdMap.has(_id)) {
map.set(_id, SaccharideCompIdMap.get(_id)!)
} else if (!map.has(_id) && getMoleculeType(_type, _id) === MoleculeType.saccharide) {
} else if (getMoleculeType(_type, _id) === MoleculeType.saccharide) {
map.set(_id, UnknownSaccharideComponent)
}
}
} else {
const uniqueNames = getUniqueComponentNames(format)
SaccharideCompIdMap.forEach((v, k) => {
if (uniqueNames.has(k)) map.set(k, v)
if (!map.has(k) && uniqueNames.has(k)) map.set(k, v)
})
}
return map
@@ -225,7 +241,7 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, sourceIn
modelNum,
entities,
symmetry: getSymmetry(format),
sequence: getSequence(format.data, entities, atomic.hierarchy, formatData.modifiedResidues.parentId),
sequence: getSequence(format.data, entities, atomic.hierarchy, coarse.hierarchy, formatData.modifiedResidues.parentId),
atomicHierarchy: atomic.hierarchy,
atomicConformation: atomic.conformation,
coarseHierarchy: coarse.hierarchy,
@@ -262,7 +278,7 @@ function createModelIHM(format: mmCIF_Format, data: IHMData, formatData: FormatD
modelNum: data.model_id,
entities: data.entities,
symmetry: getSymmetry(format),
sequence: getSequence(format.data, data.entities, atomic.hierarchy, formatData.modifiedResidues.parentId),
sequence: getSequence(format.data, data.entities, atomic.hierarchy, coarse.hierarchy, formatData.modifiedResidues.parentId),
atomicHierarchy: atomic.hierarchy,
atomicConformation: atomic.conformation,
coarseHierarchy: coarse.hierarchy,
@@ -372,11 +388,19 @@ function getEntities(format: mmCIF_Format): Entities {
}
if (assignSubtype) {
const chemCompType = new Map<string, string>()
const { id, type } = format.data.chem_comp;
for (let i = 0, il = format.data.chem_comp._rowCount; i < il; i++) {
chemCompType.set(id.value(i), type.value(i))
}
const { label_entity_id, label_comp_id } = format.data.atom_site;
for (let i = 0 as ElementIndex, il = format.data.atom_site._rowCount; i < il; i++) {
const entityId = label_entity_id.value(i);
if (!entityIds.has(entityId)) {
subtypes[getEntityIndex(entityId)] = getEntitySubtype(label_comp_id.value(i))
const compId = label_comp_id.value(i)
const compType = chemCompType.get(compId) || ''
subtypes[getEntityIndex(entityId)] = getEntitySubtype(compId, compType)
entityIds.add(entityId)
}
}

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { mmCIF_Database as mmCIF } from '../../../mol-io/reader/cif/schema/mmcif'
@@ -10,19 +11,12 @@ import { Column } from '../../../mol-data/db';
import { AtomicHierarchy } from '../../../mol-model/structure/model/properties/atomic';
import { Entities } from '../../../mol-model/structure/model/properties/common';
import { Sequence } from '../../../mol-model/sequence';
import { CoarseHierarchy } from '../../../mol-model/structure/model/properties/coarse';
// TODO how to handle microheterogeneity
// see http://mmcif.wwpdb.org/dictionaries/mmcif_pdbx_v50.dic/Categories/entity_poly_seq.html
//
// Data items in the ENTITY_POLY_SEQ category specify the sequence
// of monomers in a polymer. Allowance is made for the possibility
// of microheterogeneity in a sample by allowing a given sequence
// number to be correlated with more than one monomer ID. The
// corresponding ATOM_SITE entries should reflect this
// heterogeneity.
export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHierarchy, modResMap: ReadonlyMap<string, string>): StructureSequence {
if (!cif.entity_poly_seq._rowCount) return StructureSequence.fromAtomicHierarchy(entities, hierarchy, modResMap);
export function getSequence(cif: mmCIF, entities: Entities, atomicHierarchy: AtomicHierarchy, coarseHierarchy: CoarseHierarchy, modResMap: ReadonlyMap<string, string>): StructureSequence {
if (!cif.entity_poly_seq._rowCount) {
return StructureSequence.fromHierarchy(entities, atomicHierarchy, coarseHierarchy, modResMap);
}
const { entity_id, num, mon_id } = cif.entity_poly_seq;
@@ -37,15 +31,13 @@ export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHie
i++;
const id = entity_id.value(start);
const _compId = Column.window(mon_id, start, i);
const _num = Column.window(num, start, i);
const compId = Column.window(mon_id, start, i);
const seqId = Column.window(num, start, i);
const entityKey = entities.getEntityIndex(id);
byEntityKey[entityKey] = {
entityId: id,
compId: _compId,
num: _num,
sequence: Sequence.ofResidueNames(_compId, _num, modResMap)
sequence: Sequence.ofResidueNames(compId, seqId, modResMap)
};
sequences.push(byEntityKey[entityKey]);

View File

@@ -12,7 +12,7 @@ import { Task } from '../../mol-task';
import { ThemeDataContext, ThemeProvider } from '../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../mol-theme/color';
import { Color } from '../../mol-util/color';
import { TableLegend } from '../../mol-util/color/lists';
import { TableLegend } from '../../mol-util/legend';
import { Loci } from '../../mol-model/loci';
import { OrderedSet } from '../../mol-data/int';

View File

@@ -10,7 +10,7 @@ import { StructureElement } from '../../../mol-model/structure';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ThemeDataContext } from '../../../mol-theme/theme';
import { Color } from '../../../mol-util/color';
import { TableLegend } from '../../../mol-util/color/lists';
import { TableLegend } from '../../../mol-util/legend';
const ValidationColors = [
Color.fromRgb(170, 170, 170), // not applicable

View File

@@ -12,10 +12,9 @@ import { AssemblySymmetry } from '../assembly-symmetry';
import { Color } from '../../../mol-util/color';
import { Unit, StructureElement, StructureProperties } from '../../../mol-model/structure';
import { Location } from '../../../mol-model/location';
import { ScaleLegend } from '../../../mol-util/color/scale';
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
import { getSymmetrySelectParam } from '../util';
import { getPalette, getPaletteParams } from '../../../mol-util/color/palette';
import { TableLegend } from '../../../mol-util/color/lists';
const DefaultColor = Color(0xCCCCCC)

View File

@@ -104,7 +104,7 @@ namespace Loci {
export function getBoundingSphere(loci: Loci, boundingSphere?: Sphere3D): Sphere3D | undefined {
if (loci.kind === 'every-loci' || loci.kind === 'empty-loci') return void 0;
if (!boundingSphere) boundingSphere = Sphere3D.zero()
if (!boundingSphere) boundingSphere = Sphere3D()
sphereHelper.reset();
if (loci.kind === 'structure-loci') {

View File

@@ -23,9 +23,15 @@ namespace Sequence {
export interface Base<K extends Kind, Alphabet extends string> {
readonly kind: K,
readonly length: number,
readonly offset: number,
readonly sequence: ArrayLike<Alphabet>
readonly labels: ArrayLike<string>
readonly code: Column<Alphabet>
readonly label: Column<string>
readonly seqId: Column<number>
readonly compId: Column<string>
/** maps seqId to list of compIds */
readonly microHet: ReadonlyMap<number, string[]>
}
@@ -35,65 +41,83 @@ namespace Sequence {
export interface DNA extends Base<Kind.DNA, NuclecicAlphabet> { }
export interface Generic extends Base<Kind.Generic, 'X' | '-'> { }
export function create<K extends Kind, Alphabet extends string>(kind: K, sequence: Alphabet[], labels: string[], microHet: Map<number, string[]>, offset: number = 0): Base<K, Alphabet> {
return { kind: kind, sequence: sequence, labels, microHet, offset };
export function create<K extends Kind, Alphabet extends string>(kind: K, code: Column<Alphabet>, label: Column<string>, seqId: Column<number>, compId: Column<string>, microHet: Map<number, string[]>, offset: number = 0): Base<K, Alphabet> {
const length = code.rowCount
return { kind, code, label, seqId, compId, microHet, offset, length };
}
export function getSequenceString(seq: Sequence) {
return seq.sequence as string;
const array = seq.code.toArray()
return (array instanceof Array ? array : Array.from(array)).join('')
}
function determineKind(names: Column<string>) {
for (let i = 0, _i = Math.min(names.rowCount, 10); i < _i; i++) {
const name = names.value(i) || '';
if (getProteinOneLetterCode(name) !== 'X') return { kind: Kind.Protein, code: getProteinOneLetterCode };
if (getRnaOneLetterCode(name) !== 'X') return { kind: Kind.RNA, code: getRnaOneLetterCode };
if (getDnaOneLetterCode(name) !== 'X') return { kind: Kind.DNA, code: getDnaOneLetterCode };
if (getProteinOneLetterCode(name) !== 'X') return Kind.Protein;
if (getRnaOneLetterCode(name) !== 'X') return Kind.RNA;
if (getDnaOneLetterCode(name) !== 'X') return Kind.DNA;
}
return { kind: Kind.Generic, code: (v: string) => 'X' };
return Kind.Generic;
}
function modCode(code: (name: string) => string, map: ReadonlyMap<string, string>): (name: string) => string {
return n => {
const ret = code(n);
if (ret !== 'X' || !map.has(n)) return ret;
return code(map.get(n)!);
function codeProvider(kind: Kind, map?: ReadonlyMap<string, string>) {
let code: (name: string) => string
switch (kind) {
case Kind.Protein: code = getProteinOneLetterCode; break;
case Kind.DNA: code = getDnaOneLetterCode; break;
case Kind.RNA: code = getRnaOneLetterCode; break;
case Kind.Generic: code = () => 'X'; break;
default: throw new Error(`unknown kind '${kind}'`)
}
if (map && map.size > 0) {
return (name: string) => {
const ret = code(name);
if (ret !== 'X' || !map.has(name)) return ret;
return code(map.get(name)!);
}
}
return code
}
export function ofResidueNames(residueName: Column<string>, seqId: Column<number>, modifiedMap?: ReadonlyMap<string, string>): Sequence {
export function ofResidueNames(compId: Column<string>, seqId: Column<number>, modifiedMap?: ReadonlyMap<string, string>): Sequence {
if (seqId.rowCount === 0) throw new Error('cannot be empty');
const { kind, code } = determineKind(residueName);
if (!modifiedMap || modifiedMap.size === 0) {
return new Impl(kind, residueName, seqId, code) as Sequence;
}
return new Impl(kind, residueName, seqId, modCode(code, modifiedMap)) as Sequence;
const kind = determineKind(compId);
return new ResidueNamesImpl(kind, compId, seqId, modifiedMap) as Sequence;
}
class Impl<K extends Kind, Alphabet extends string> implements Base<K, Alphabet> {
class ResidueNamesImpl<K extends Kind, Alphabet extends string> implements Base<K, Alphabet> {
private _offset = 0;
private _seq: ArrayLike<Alphabet> | undefined = void 0;
private _labels: ArrayLike<string> | undefined = void 0;
private _length = 0;
private _microHet: ReadonlyMap<number, string[]> | undefined = void 0;
private _code: Column<Alphabet> | undefined = undefined
private _label: Column<string> | undefined = undefined
private codeFromName: (name: string) => string
get code(): Column<Alphabet> {
if (this._code !== void 0) return this._code;
this.create();
return this._code!;
}
get label(): Column<string> {
if (this._label !== void 0) return this._label;
this.create();
return this._label!;
}
get offset() {
if (this._seq !== void 0) return this._offset;
if (this._code !== void 0) return this._offset;
this.create();
return this._offset;
}
get sequence(): ArrayLike<Alphabet> {
if (this._seq !== void 0) return this._seq;
get length() {
if (this._code !== void 0) return this._length;
this.create();
return this._seq!;
}
get labels(): ArrayLike<string> {
if (this._labels !== void 0) return this._labels;
this.create();
return this._labels!;
return this._length;
}
get microHet(): ReadonlyMap<number, string[]> {
@@ -126,11 +150,15 @@ namespace Sequence {
for (let i = 0, _i = this.seqId.rowCount; i < _i; i++) {
const seqId = this.seqId.value(i)
const idx = seqId - minSeqId;
const name = this.residueName.value(i);
const code = this.code(name);
const name = this.compId.value(i);
const code = this.codeFromName(name);
// in case of MICROHETEROGENEITY `sequenceArray[idx]` may already be set
if (!sequenceArray[idx] || sequenceArray[idx] === '-') {
sequenceArray[idx] = code;
if (code === 'X' && this.modifiedMap && this.modifiedMap.has(name)) {
sequenceArray[idx] = this.modifiedMap.get(name)!
} else {
sequenceArray[idx] = code;
}
}
labels[idx].push(code === 'X' ? name : code);
compIds[seqId].push(name);
@@ -141,14 +169,63 @@ namespace Sequence {
if (compIds[i].length > 1) microHet.set(i, compIds[i])
}
this._seq = sequenceArray.join('') as unknown as ArrayLike<Alphabet>;
this._labels = labels.map(l => l.length > 1 ? `(${l.join('|')})` : l.join(''));
this._code = Column.ofStringArray(sequenceArray) as Column<Alphabet>
this._label = Column.ofLambda({
value: i => {
const l = labels[i]
return l.length > 1 ? `(${l.join('|')})` : l.join('')
},
rowCount: labels.length,
schema: Column.Schema.str
})
this._microHet = microHet
this._offset = minSeqId - 1;
this._length = count
}
constructor(public kind: K, private residueName: Column<string>, private seqId: Column<number>, private code: (name: string) => string) {
constructor(public kind: K, public compId: Column<string>, public seqId: Column<number>, private modifiedMap?: ReadonlyMap<string, string>) {
this.codeFromName = codeProvider(kind)
}
}
export function ofSequenceRanges(seqIdBegin: Column<number>, seqIdEnd: Column<number>): Sequence {
const kind = Kind.Generic
return new SequenceRangesImpl(kind, seqIdBegin, seqIdEnd) as Sequence;
}
class SequenceRangesImpl<K extends Kind, Alphabet extends string> implements Base<K, Alphabet> {
public offset: number
public length: number
public code: Column<Alphabet>
public label: Column<string>
public seqId: Column<number>
public compId: Column<string>
public microHet: ReadonlyMap<number, string[]>
constructor(public kind: K, private seqIdStart: Column<number>, private seqIdEnd: Column<number>) {
let maxSeqId = 0, minSeqId = Number.MAX_SAFE_INTEGER;
for (let i = 0, _i = this.seqIdStart.rowCount; i < _i; i++) {
const idStart = this.seqIdStart.value(i);
const idEnd = this.seqIdEnd.value(i);
if (idStart < minSeqId) minSeqId = idStart;
if (maxSeqId < idEnd) maxSeqId = idEnd;
}
const count = maxSeqId - minSeqId + 1;
this.code = Column.ofConst('X', count, Column.Schema.str) as Column<Alphabet>
this.label = Column.ofConst('', count, Column.Schema.str)
this.seqId = Column.ofLambda({
value: row => row + minSeqId + 1,
rowCount: count,
schema: Column.Schema.int
})
this.compId = Column.ofConst('', count, Column.Schema.str)
this.offset = minSeqId - 1;
this.length = count
}
}
}

View File

@@ -15,6 +15,8 @@ import { CustomProperties } from '../common/custom-property';
import { SecondaryStructure } from './properties/seconday-structure';
import { SaccharideComponentMap } from '../structure/carbohydrates/constants';
import { ModelFormat } from '../../../mol-model-formats/structure/format';
import { calcModelCenter } from './util';
import { Vec3 } from '../../../mol-math/linear-algebra';
/**
* Interface to the "source data" of the molecule.
@@ -75,4 +77,12 @@ export interface Model extends Readonly<{
export namespace Model {
// TODO: is this enough?
export type Trajectory = ReadonlyArray<Model>
const CenterProp = '__Center__'
export function getCenter(model: Model): Vec3 {
if (model._dynamicPropertyData[CenterProp]) return model._dynamicPropertyData[CenterProp]
const center = calcModelCenter(model.atomicConformation, model.coarseConformation)
model._dynamicPropertyData[CenterProp] = center
return center
}
}

View File

@@ -11,14 +11,17 @@ import { ElementIndex, ChainIndex, EntityIndex } from '../../indexing';
import SortedRanges from '../../../../../mol-data/int/sorted-ranges';
export interface CoarsedElementKeys {
// assign a key to each element
/** Assign a key to each element */
chainKey: ArrayLike<ChainIndex>,
// assign a key to each element, index to the Model.entities.data table
/** Assign a key to each element, index to the Model.entities.data table */
entityKey: ArrayLike<EntityIndex>,
/** find index of the residue/feature element where seq_id is included */
/** Find index of the residue/feature element where seq_id is included */
findSequenceKey(entityId: string, asym_id: string, seq_id: number): ElementIndex
findChainKey(entityId: string, asym_id: string): ChainIndex
/** Returns index or -1 if not present. */
getEntityFromChain(cI: ChainIndex): EntityIndex
}
export interface CoarseElementData {

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Column } from '../../../../mol-data/db'
@@ -9,6 +10,8 @@ import { AtomicHierarchy } from './atomic/hierarchy';
import { Entities } from './common';
import { Sequence } from '../../../sequence';
import { ChainIndex } from '../indexing';
import { CoarseHierarchy } from './coarse';
import { CoarseElements } from './coarse/hierarchy';
interface StructureSequence {
readonly sequences: ReadonlyArray<StructureSequence.Entity>,
@@ -18,19 +21,41 @@ interface StructureSequence {
namespace StructureSequence {
export interface Entity {
readonly entityId: string,
readonly num: Column<number>,
/** Corresponds to _entity_poly_seq.mon_id */
readonly compId: Column<string>,
readonly sequence: Sequence
}
const Empty: StructureSequence = { byEntityKey: {}, sequences: [] }
function merge(...entitySeqs: StructureSequence[]): StructureSequence {
const sequences: StructureSequence.Entity[] = []
const byEntityKey: { [key: number]: StructureSequence.Entity } = {}
for (let i = 0, il = entitySeqs.length; i < il; ++i) {
sequences.push(...entitySeqs[i].sequences)
Object.assign(byEntityKey, entitySeqs[i].byEntityKey)
}
return { sequences, byEntityKey }
}
export function fromHierarchy(entities: Entities, atomicHierarchy: AtomicHierarchy, coarseHierarchy: CoarseHierarchy, modResMap?: ReadonlyMap<string, string>): StructureSequence {
const atomic = fromAtomicHierarchy(entities, atomicHierarchy, modResMap)
const coarse = coarseHierarchy.isDefined ? fromCoarseHierarchy(entities, coarseHierarchy) : Empty
return merge(atomic, coarse)
}
export function fromAtomicHierarchy(entities: Entities, hierarchy: AtomicHierarchy, modResMap?: ReadonlyMap<string, string>): StructureSequence {
const { label_comp_id, label_seq_id } = hierarchy.residues
const { chainAtomSegments, residueAtomSegments } = hierarchy
const { count, offsets } = chainAtomSegments
const byEntityKey: StructureSequence['byEntityKey'] = { };
const sequences: StructureSequence.Entity[] = [];
// check if chain segments are empty
if (count === 1 && offsets[0] === 0 && offsets[1] === 0) {
return { byEntityKey, sequences };
}
for (let cI = 0 as ChainIndex, _cI = hierarchy.chains._rowCount; cI < _cI; cI++) {
const entityKey = hierarchy.index.getEntityFromChain(cI);
// Only for polymers, trying to mirror _entity_poly_seq
@@ -43,16 +68,14 @@ namespace StructureSequence {
}
cI--;
const rStart = residueAtomSegments.index[chainAtomSegments.offsets[start]];
const rEnd = residueAtomSegments.index[chainAtomSegments.offsets[cI + 1]];
const rStart = residueAtomSegments.index[offsets[start]];
const rEnd = residueAtomSegments.index[offsets[cI + 1]];
const compId = Column.window(label_comp_id, rStart, rEnd);
const num = Column.window(label_seq_id, rStart, rEnd);
byEntityKey[entityKey] = {
entityId: entities.data.id.value(entityKey),
compId,
num,
sequence: Sequence.ofResidueNames(compId, num, modResMap)
};
@@ -61,6 +84,52 @@ namespace StructureSequence {
return { byEntityKey, sequences };
}
export function fromCoarseHierarchy(entities: Entities, hierarchy: CoarseHierarchy): StructureSequence {
const spheres = fromCoarseElements(entities, hierarchy.spheres)
const gaussians = fromCoarseElements(entities, hierarchy.gaussians)
return merge(spheres, gaussians)
}
export function fromCoarseElements(entities: Entities, elements: CoarseElements): StructureSequence {
const { chainElementSegments, seq_id_begin, seq_id_end } = elements
const { count, offsets } = chainElementSegments
const byEntityKey: StructureSequence['byEntityKey'] = { };
const sequences: StructureSequence.Entity[] = [];
// check if chain segments are empty
if (count === 1 && offsets[0] === 0 && offsets[1] === 0) {
return { byEntityKey, sequences };
}
for (let cI = 0 as ChainIndex, _cI = count; cI < _cI; cI++) {
const eK = elements.getEntityFromChain(cI);
if (byEntityKey[eK] !== void 0) continue;
let start = cI;
cI++;
while (cI < _cI && eK === elements.getEntityFromChain(cI)) {
cI++;
}
cI--;
const eStart = offsets[start];
const eEnd = offsets[cI + 1];
const seqIdBegin = Column.window(seq_id_begin, eStart, eEnd);
const seqIdEnd = Column.window(seq_id_end, eStart, eEnd);
byEntityKey[eK] = {
entityId: entities.data.id.value(eK),
sequence: Sequence.ofSequenceRanges(seqIdBegin, seqIdEnd)
};
sequences.push(byEntityKey[eK]);
}
return { byEntityKey, sequences };
}
}
export default StructureSequence

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -9,6 +9,7 @@ import { arrayFind } from '../../../../mol-data/util'
import { StructureQuery } from '../../query'
import { Model } from '../../model'
import { Spacegroup } from '../../../../mol-math/geometry';
import { Vec3 } from '../../../../mol-math/linear-algebra';
/** Determine an atom set and a list of operators that should be applied to that set */
export interface OperatorGroup {
@@ -47,8 +48,14 @@ interface ModelSymmetry {
readonly isNonStandardCrytalFrame: boolean,
readonly ncsOperators?: ReadonlyArray<SymmetryOperator>,
// optionally cached operators from [-3, -3, -3] to [3, 3, 3]
_operators_333?: SymmetryOperator[]
/**
* optionally cached operators from [-3, -3, -3] to [3, 3, 3]
* around reference point `ref` in fractional coordinates
*/
_operators_333?: {
ref: Vec3,
operators: SymmetryOperator[]
}
}
namespace ModelSymmetry {

View File

@@ -8,6 +8,8 @@
import { Entities } from '../common';
import { CoarseElementData, CoarsedElementKeys } from '../coarse';
import { ChainIndex, ElementIndex, EntityIndex } from '../../indexing';
import SortedRanges from '../../../../../mol-data/int/sorted-ranges';
import { OrderedSet } from '../../../../../mol-data/int';
function getElementKey(map: Map<string, number>, key: string, counter: { index: number }) {
if (map.has(key)) return map.get(key)!;
@@ -23,7 +25,7 @@ function getElementSubstructureKeyMap(map: Map<number, Map<string, number>>, key
return ret;
}
function createLookUp(entities: Entities, chain: Map<number, Map<string, number>>, seq: Map<number, Map<number, number>>) {
function createLookUp(entities: Entities, chain: Map<number, Map<string, number>>, seq: Map<number, SeqMap>) {
const getEntKey = entities.getEntityIndex;
const findChainKey: CoarsedElementKeys['findChainKey'] = (e, c) => {
const eKey = getEntKey(e);
@@ -32,7 +34,6 @@ function createLookUp(entities: Entities, chain: Map<number, Map<string, number>
if (!cm.has(c)) return -1 as ChainIndex;
return cm.get(c)! as ChainIndex;
}
// TODO consider implementing as binary search
const findSequenceKey: CoarsedElementKeys['findSequenceKey'] = (e, c, s) => {
const eKey = getEntKey(e);
if (eKey < 0) return -1 as ElementIndex;
@@ -41,8 +42,9 @@ function createLookUp(entities: Entities, chain: Map<number, Map<string, number>
const cKey = cm.get(c)
if (cKey === undefined) return -1 as ElementIndex
const sm = seq.get(cKey)!
if (!sm.has(s)) return -1 as ElementIndex;
return sm.get(s)! as ElementIndex
const { elementIndices, seqRanges } = sm
const idx = SortedRanges.firstIntersectionIndex(seqRanges, OrderedSet.ofSingleton(s))
return (idx !== -1 ? elementIndices[idx] : -1) as ElementIndex
}
return { findChainKey, findSequenceKey };
}
@@ -51,39 +53,53 @@ function missingEntity(k: string) {
throw new Error(`Missing entity entry for entity id '${k}'.`);
}
type SeqMap = { elementIndices: number[], seqRanges: SortedRanges }
export function getCoarseKeys(data: CoarseElementData, entities: Entities): CoarsedElementKeys {
const { entity_id, asym_id, seq_id_begin, seq_id_end, count, chainElementSegments } = data;
const seqMaps = new Map<number, Map<number, number>>();
const seqMaps = new Map<number, SeqMap>();
const chainMaps = new Map<number, Map<string, number>>(), chainCounter = { index: 0 };
const chainKey = new Int32Array(count) as any as ChainIndex[];
const entityKey = new Int32Array(count) as any as EntityIndex[];
const chainToEntity = new Int32Array(chainElementSegments.count) as any as EntityIndex[];
for (let i = 0; i < count; i++) {
entityKey[i] = entities.getEntityIndex(entity_id.value(i));
if (entityKey[i] < 0) missingEntity(entity_id.value(i));
}
for (let cI = 0; cI < chainElementSegments.count; cI++) {
const start = chainElementSegments.offsets[cI], end = chainElementSegments.offsets[cI + 1];
const map = getElementSubstructureKeyMap(chainMaps, entityKey[start]);
const start = chainElementSegments.offsets[cI]
const end = chainElementSegments.offsets[cI + 1];
const eK = entityKey[start]
chainToEntity[cI] = eK
const map = getElementSubstructureKeyMap(chainMaps, eK);
const key = getElementKey(map, asym_id.value(start), chainCounter) as ChainIndex;
for (let i = start; i < end; i++) chainKey[i] = key;
// create seq_id map for the ranges defined by seq_id_begin and seq_id_end
const seqMap: Map<number, number> = new Map()
seqMaps.set(key, seqMap)
const elementIndices: number[] = []
const seqRanges: number[] = []
for (let i = start; i < end; i++) {
const seqStart = seq_id_begin.value(i)
const seqEnd = seq_id_end.value(i)
for (let j = seqStart; j <= seqEnd; j++) {
seqMap.set(j, i)
}
elementIndices.push(i)
seqRanges.push(seqStart, seqEnd)
}
const seqMap = { elementIndices, seqRanges: SortedRanges.ofSortedRanges(seqRanges) }
seqMaps.set(key, seqMap)
}
const { findChainKey, findSequenceKey } = createLookUp(entities, chainMaps, seqMaps);
return { chainKey, entityKey, findSequenceKey, findChainKey };
const getEntityFromChain: CoarsedElementKeys['getEntityFromChain'] = c => {
return chainToEntity[c]
}
return { chainKey, entityKey, findSequenceKey, findChainKey, getEntityFromChain };
}

View File

@@ -9,7 +9,7 @@ import BitFlags from '../../../mol-util/bit-flags'
import { SaccharideCompIdMap } from '../structure/carbohydrates/constants';
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
import { SetUtils } from '../../../mol-util/set';
import { EntitySubtype } from './properties/common';
import { EntitySubtype, ChemicalComponent } from './properties/common';
const _esCache = (function () {
const cache = Object.create(null);
@@ -113,19 +113,34 @@ export const NucleicBackboneAtoms = new Set([
'O2*', 'O3*', 'O4*', 'O5*', 'C1*', 'C2*', 'C3*', 'C4*', 'C5*'
])
/** Chemical component type names for protein */
export const ProteinComponentTypeNames = new Set([
'D-PEPTIDE LINKING', 'L-PEPTIDE LINKING', 'D-PEPTIDE NH3 AMINO TERMINUS',
'L-PEPTIDE NH3 AMINO TERMINUS', 'D-PEPTIDE COOH CARBOXY TERMINUS',
'L-PEPTIDE COOH CARBOXY TERMINUS', 'PEPTIDE LINKING', 'PEPTIDE-LIKE',
'L-GAMMA-PEPTIDE, C-DELTA LINKING', 'D-GAMMA-PEPTIDE, C-DELTA LINKING',
'L-BETA-PEPTIDE, C-GAMMA LINKING', 'D-BETA-PEPTIDE, C-GAMMA LINKING',
/** Chemical component type names for D-linked protein */
export const DProteinComponentTypeNames = new Set([
'D-PEPTIDE LINKING', 'D-PEPTIDE NH3 AMINO TERMINUS',
'D-PEPTIDE COOH CARBOXY TERMINUS', 'D-GAMMA-PEPTIDE, C-DELTA LINKING',
'D-BETA-PEPTIDE, C-GAMMA LINKING'
])
/** Chemical component type names for L-linked protein */
export const LProteinComponentTypeNames = new Set([
'L-PEPTIDE LINKING', 'L-PEPTIDE NH3 AMINO TERMINUS',
'L-PEPTIDE COOH CARBOXY TERMINUS', 'L-GAMMA-PEPTIDE, C-DELTA LINKING',
'L-BETA-PEPTIDE, C-GAMMA LINKING'
])
/** Chemical component type names for pepdite-like protein */
export const OtherProteinComponentTypeNames = new Set([
'PEPTIDE LINKING', 'PEPTIDE-LIKE',
])
/** Chemical component type names for protein */
export const ProteinComponentTypeNames = SetUtils.unionMany(
DProteinComponentTypeNames, LProteinComponentTypeNames, OtherProteinComponentTypeNames
)
/** Chemical component type names for DNA */
export const DNAComponentTypeNames = [
export const DNAComponentTypeNames = new Set([
'DNA LINKING', 'L-DNA LINKING', 'DNA OH 5 PRIME TERMINUS', 'DNA OH 3 PRIME TERMINUS',
]
])
/** Chemical component type names for RNA */
export const RNAComponentTypeNames = new Set([
@@ -200,7 +215,7 @@ export function getMoleculeType(compType: string, compId: string) {
return MoleculeType.protein
} else if (RNAComponentTypeNames.has(compType)) {
return MoleculeType.RNA
} else if (DNAComponentTypeNames.includes(compType)) {
} else if (DNAComponentTypeNames.has(compType)) {
return MoleculeType.DNA
} else if (SaccharideComponentTypeNames.has(compType)) {
return MoleculeType.saccharide
@@ -209,7 +224,12 @@ export function getMoleculeType(compType: string, compId: string) {
} else if (IonNames.has(compId)) {
return MoleculeType.ion
} else if (OtherComponentTypeNames.has(compType)) {
return MoleculeType.other
if (SaccharideCompIdMap.has(compId)) {
// trust our saccharide table more than given 'non-polymer' or 'other' component type
return MoleculeType.saccharide
} else {
return MoleculeType.other
}
} else {
return MoleculeType.unknown
}
@@ -230,6 +250,20 @@ export function getComponentType(compId: string): mmCIF_Schema['chem_comp']['typ
}
}
export function getDefaultChemicalComponent(compId: string): ChemicalComponent {
// TODO: this is to make the chem_comp_type property work if chem_comp category is not present.
// should we try to set the formula etc better?
return {
formula: '',
formula_weight: 0,
id: compId,
name: compId,
mon_nstd_flag: 'n',
pdbx_synonyms: [],
type: getComponentType(compId)
};
}
export function getEntityType(compId: string): mmCIF_Schema['entity']['type']['T'] {
compId = compId.toUpperCase()
if (WaterNames.has(compId)) {
@@ -243,9 +277,20 @@ export function getEntityType(compId: string): mmCIF_Schema['entity']['type']['T
}
}
export function getEntitySubtype(compId: string): EntitySubtype {
export function getEntitySubtype(compId: string, compType: string): EntitySubtype {
compId = compId.toUpperCase()
if (SaccharideCompIdMap.has(compId)) {
compType = compType.toUpperCase()
if (LProteinComponentTypeNames.has(compType)) {
return 'polypeptide(L)'
} else if (DProteinComponentTypeNames.has(compType)) {
return 'polypeptide(D)'
} else if (RNAComponentTypeNames.has(compType)) {
return 'polyribonucleotide'
} else if (DNAComponentTypeNames.has(compType)) {
return 'polydeoxyribonucleotide'
} else if (SaccharideComponentTypeNames.has(compType)) {
return 'oligosaccharide'
} else if (SaccharideCompIdMap.has(compId)) {
return 'oligosaccharide'
} else if (PeptideBaseNames.has(compId)) {
return 'peptide nucleic acid'
@@ -639,5 +684,5 @@ export const ResidueHydrophobicity = {
'TRP': [ -1.85, -2.09, -0.24 ],
'TYR': [ -0.94, -0.71, 0.23 ],
'VAL': [ 0.07, -0.46, -0.53 ]
}
export const DefaultResidueHydrophobicity = [ 0.00, 0.00, 0.00 ]
}
export const DefaultResidueHydrophobicity = [ 0.00, 0.00, 0.00 ]

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3 } from '../../../mol-math/linear-algebra';
import { AtomicConformation } from './properties/atomic';
import { CoarseConformation } from './properties/coarse';
import { arrayMinMax } from '../../../mol-util/array';
export function calcModelCenter(atomicConformation: AtomicConformation, coarseConformation?: CoarseConformation) {
let rangesX: number[] = []
let rangesY: number[] = []
let rangesZ: number[] = []
if (atomicConformation.x.length) {
rangesX.push(...arrayMinMax(atomicConformation.x))
rangesY.push(...arrayMinMax(atomicConformation.y))
rangesZ.push(...arrayMinMax(atomicConformation.z))
}
if (coarseConformation) {
if (coarseConformation.spheres.x.length) {
rangesX.push(...arrayMinMax(coarseConformation.spheres.x))
rangesY.push(...arrayMinMax(coarseConformation.spheres.y))
rangesZ.push(...arrayMinMax(coarseConformation.spheres.z))
}
if (coarseConformation.gaussians.x.length) {
rangesX.push(...arrayMinMax(coarseConformation.gaussians.x))
rangesY.push(...arrayMinMax(coarseConformation.gaussians.y))
rangesZ.push(...arrayMinMax(coarseConformation.gaussians.z))
}
}
const [minX, maxX] = arrayMinMax(rangesX)
const [minY, maxY] = arrayMinMax(rangesY)
const [minZ, maxZ] = arrayMinMax(rangesZ)
const x = minX + (maxX - minX) / 2
const y = minY + (maxY - minY) / 2
const z = minZ + (maxZ - minZ) / 2
return Vec3.create(x, y, z)
}

View File

@@ -105,4 +105,4 @@ export function spheres(): StructureQuery {
}
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
};
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 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>
@@ -22,7 +22,12 @@ import { UnitRings, UnitRing } from '../unit/rings';
import { ElementIndex } from '../../model/indexing';
const C = ElementSymbol('C'), O = ElementSymbol('O');
const SugarRingFps = [UnitRing.elementFingerprint([C, C, C, C, C, O]), UnitRing.elementFingerprint([C, C, C, C, O])]
const SugarRingFps = [
UnitRing.elementFingerprint([C, C, C, O]),
UnitRing.elementFingerprint([C, C, C, C, O]),
UnitRing.elementFingerprint([C, C, C, C, C, O]),
UnitRing.elementFingerprint([C, C, C, C, C, C, O]),
]
function getAnomericCarbon(unit: Unit.Atomic, ringAtoms: ArrayLike<StructureElement.UnitIndex>): ElementIndex {
let indexHasTwoOxygen = -1, indexHasOxygenAndCarbon = -1, indexHasC1Name = -1, indexIsCarbon = -1
@@ -203,7 +208,8 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
elements.push({
geometry: { center, normal, direction },
component: saccharideComp,
unit, residueIndex, anomericCarbon, ringAltId
unit, residueIndex, anomericCarbon, ringAltId,
ringMemberCount: ringAtoms.length
})
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 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>
@@ -9,9 +9,13 @@ import { Color, ColorMap } from '../../../../mol-util/color';
// follows community standard from https://www.ncbi.nlm.nih.gov/glycans/snfg.html
export const enum SaccharideShapes {
export const enum SaccharideShape {
// standard shapes
FilledSphere, FilledCube, CrossedCube, DividedDiamond, FilledCone, DevidedCone,
FlatBox, FilledStar, FilledDiamond, FlatDiamond, FlatHexagon, Pentagon
FlatBox, FilledStar, FilledDiamond, FlatDiamond, FlatHexagon, Pentagon,
// generic shapes for rings with 4, 5, 6, or 7 members
DiamondPrism, PentagonalPrism, HexagonalPrism, HeptagonalPrism
}
export const SaccharideColors = ColorMap({
@@ -53,22 +57,30 @@ export function getSaccharideName(type: SaccharideType) {
}
const SaccharideTypeShapeMap = {
[SaccharideType.Hexose]: SaccharideShapes.FilledSphere,
[SaccharideType.HexNAc]: SaccharideShapes.FilledCube,
[SaccharideType.Hexosamine]: SaccharideShapes.CrossedCube,
[SaccharideType.Hexuronate]: SaccharideShapes.DividedDiamond,
[SaccharideType.Deoxyhexose]: SaccharideShapes.FilledCone,
[SaccharideType.DeoxyhexNAc]: SaccharideShapes.DevidedCone,
[SaccharideType.DiDeoxyhexose]: SaccharideShapes.FlatBox,
[SaccharideType.Pentose]: SaccharideShapes.FilledStar,
[SaccharideType.Deoxynonulosonate]: SaccharideShapes.FilledDiamond,
[SaccharideType.DiDeoxynonulosonate]: SaccharideShapes.FlatDiamond,
[SaccharideType.Unknown]: SaccharideShapes.FlatHexagon,
[SaccharideType.Assigned]: SaccharideShapes.Pentagon,
[SaccharideType.Hexose]: SaccharideShape.FilledSphere,
[SaccharideType.HexNAc]: SaccharideShape.FilledCube,
[SaccharideType.Hexosamine]: SaccharideShape.CrossedCube,
[SaccharideType.Hexuronate]: SaccharideShape.DividedDiamond,
[SaccharideType.Deoxyhexose]: SaccharideShape.FilledCone,
[SaccharideType.DeoxyhexNAc]: SaccharideShape.DevidedCone,
[SaccharideType.DiDeoxyhexose]: SaccharideShape.FlatBox,
[SaccharideType.Pentose]: SaccharideShape.FilledStar,
[SaccharideType.Deoxynonulosonate]: SaccharideShape.FilledDiamond,
[SaccharideType.DiDeoxynonulosonate]: SaccharideShape.FlatDiamond,
[SaccharideType.Unknown]: SaccharideShape.FlatHexagon,
[SaccharideType.Assigned]: SaccharideShape.Pentagon,
}
export function getSaccharideShape(type: SaccharideType) {
return SaccharideTypeShapeMap[type]
export function getSaccharideShape(type: SaccharideType, ringMemberCount: number): SaccharideShape {
if (type === SaccharideType.Unknown) {
if (ringMemberCount === 4) return SaccharideShape.DiamondPrism
else if (ringMemberCount === 5) return SaccharideShape.PentagonalPrism
else if (ringMemberCount === 6) return SaccharideShape.HexagonalPrism
else if (ringMemberCount === 7) return SaccharideShape.HeptagonalPrism
else return SaccharideShape.FlatHexagon
} else {
return SaccharideTypeShapeMap[type]
}
}
export type SaccharideComponent = {
@@ -200,33 +212,27 @@ const CommonSaccharideNames: { [k: string]: string[] } = {
// Hexose
Glc: [
'GLC', 'BGC',
'BOG', // via GlyFinder
'TRE', // via GlyFinder, di-saccharide but homomer
'MLR', // via GlyFinder, tri-saccharide but homomer
'TRE', // di-saccharide but homomer
'MLR', // tri-saccharide but homomer
],
Man: ['MAN', 'BMA'],
Gal: [
'GAL', 'GLA',
'GXL' // via PubChem
],
Gul: ['GUP', 'GL0'],
Alt: ['ALT'],
All: ['ALL', 'AFD'],
Tal: ['TAL'],
Ido: ['4N2'],
Gal: ['GLA', 'GAL', 'GZL'],
Gul: ['4GL', 'GL0'],
Alt: ['Z6H', '3MK'],
All: ['AFD', 'ALL'],
Tal: [],
Ido: ['Z0F', '4N2'],
// HexNAc
GlcNAc: ['NAG', 'NDG'],
ManNAc: ['NGA', 'A2G'],
GulNAc: [],
GlcNAc: ['NDG', 'NAG'],
ManNAc: ['BM3', 'BM7'],
GalNAc: ['A2G', 'NGA'],
GulNAc: ['LXB'],
AltNAc: [],
AllNAc: ['NAA'],
TalNAc: [],
IdoNAc: ['HSQ'],
IdoNAc: [],
// Hexosamine
GlcN: [
'GCS', 'PA1',
'IDU', 'SGN', 'SUS', // via GlyFinder
],
GlcN: ['PA1', 'GCS'],
ManN: ['95Z'],
GalN: ['X6X', '1GN'],
GulN: [],
@@ -237,71 +243,70 @@ const CommonSaccharideNames: { [k: string]: string[] } = {
// Hexuronate
GlcA: ['GCU', 'BDP'],
ManA: ['MAV', 'BEM'],
GalA: ['ADA', 'GTR'],
GulA: ['LGU'],
GalA: ['ADA', 'GTR', 'GTK'],
GulA: [],
AltA: [],
AllA: [],
TalA: ['X0X', 'X1X'],
IdoA: [
'IDR',
'IDS', // via GlyFinder
],
TalA: ['X1X', 'X0X'],
IdoA: ['IDR'],
// Deoxyhexose
Qui: ['G6D'],
Qui: ['G6D', 'YYK'],
Rha: ['RAM', 'RM4'],
'6dGul': [],
'6dGul': ['66O'],
'6dAlt': [],
'6dTal': [],
Fuc: ['FUC', 'FUL'],
// DeoxyhexNAc
QuiNAc: [],
QuiNAc: ['Z9W'],
RhaNAc: [],
'6dAltNAc': [],
'6dTalNAc': [],
FucNAc: [],
// Di-deoxyhexose
Oli: ['DDA'],
Oli: ['DDA', 'RAE', 'Z5J'],
Tyv: ['TYV'],
Abe: ['ABE'],
Par: ['PZU'],
Dig: [],
Dig: ['Z3U'],
Col: [],
// Pentose
Ara: ['ARA', 'ARB'],
Lyx: ['LDY'],
Xyl: ['XYS', 'XYP'],
Rib: ['RIP', '0MK'],
Ara: ['ARA', 'ARB', 'AHR', 'FUB'],
Lyx: ['LDY', 'Z4W'],
Xyl: ['XZS', 'XYP', 'XYZ'],
Rib: ['YYM', 'RIP', 'RIB', 'BDR'],
// Deoxynonulosonate
Kdn: ['KDN', 'KDM'],
Kdn: ['KDM', 'KDN'],
Neu5Ac: ['SIA', 'SLB'],
Neu5Gc: ['NGC', 'NGE'],
Neu: [],
Sia: [],
// Di-deoxynonulosonate
Pse: ['6PZ'],
Pse: [],
Leg: [],
Aci: [],
'4eLeg': [],
// Unknown
Bac: ['B6D'],
Bac: [],
LDManHep: ['GMH'],
Kdo: ['KDO'],
Dha: [],
DDManHep: [],
MurNAc: ['AMU'],
DDManHep: ['289'],
MurNAc: ['MUB', 'AMU'],
MurNGc: [],
Mur: ['MUR'],
Mur: ['1S4', 'MUR'],
// Assigned
Api: ['XXM'],
Fru: ['BDF'],
Fru: ['BDF', 'Z9N', 'FRU'],
Tag: ['T6T'],
Sor: ['SOE'],
Psi: [],
Psi: ['PSV'],
}
const UnknownSaccharideNames = [
'NGZ', // via CCD
'LAT', // BETA-LACTOSE, Gal-Glc di-saccharide via GlyFinder
'PUF', 'GDA', '9WJ', // via updated CCD
]
export const SaccharideCompIdMap = (function () {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -30,6 +30,7 @@ export interface CarbohydrateElement {
readonly residueIndex: ResidueIndex,
readonly component: SaccharideComponent,
readonly ringAltId: string,
readonly ringMemberCount: number,
}
/** partial carbohydrate with no ring present */

View File

@@ -54,7 +54,11 @@ export namespace Loci {
}
export function isEmpty(loci: Loci) {
return size(loci) === 0 ? true : false
return size(loci) === 0
}
export function isWholeStructure(loci: Loci) {
return size(loci) === loci.structure.elementCount
}
export function size(loci: Loci) {

View File

@@ -53,10 +53,16 @@ const atom = {
vdw_radius: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element))),
}
function compId(l: StructureElement.Location) {
function _compId(l: StructureElement.Location) {
return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])
}
function compId(l: StructureElement.Location) {
if (!Unit.isAtomic(l.unit)) notAtomic()
if (!hasMicroheterogeneity(l)) return _compId(l)
return l.unit.model.sourceData.data.atom_site.label_comp_id.value(l.element)
}
function seqId(l: StructureElement.Location) {
return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])
}

View File

@@ -10,7 +10,7 @@ import { EquivalenceClasses } from '../../../mol-data/util';
import { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/geometry';
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
import { RuntimeContext, Task } from '../../../mol-task';
import { ModelSymmetry } from '../model';
import { ModelSymmetry, Model } from '../model';
import { QueryContext, StructureSelection } from '../query';
import Structure from './structure';
import Unit from './unit';
@@ -95,7 +95,7 @@ namespace StructureSymmetry {
}
}
function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3) {
function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3, modelCenter: Vec3) {
const { spacegroup, ncsOperators } = symmetry;
const ncsCount = (ncsOperators && ncsOperators.length) || 0
const operators: SymmetryOperator[] = [];
@@ -107,13 +107,17 @@ function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3) {
operators[0] = Spacegroup.getSymmetryOperator(spacegroup, 0, 0, 0, 0)
}
const { toFractional } = spacegroup.cell
const ref = Vec3.transformMat4(Vec3(), modelCenter, toFractional)
for (let op = 0; op < spacegroup.operators.length; op++) {
for (let i = ijkMin[0]; i <= ijkMax[0]; i++) {
for (let j = ijkMin[1]; j <= ijkMax[1]; j++) {
for (let k = ijkMin[2]; k <= ijkMax[2]; k++) {
// check if we have added identity as the 1st operator.
if (!ncsCount && op === 0 && i === 0 && j === 0 && k === 0) continue;
const symOp = Spacegroup.getSymmetryOperator(spacegroup, op, i, j, k);
const symOp = Spacegroup.getSymmetryOperatorRef(spacegroup, op, i, j, k, ref)
if (ncsCount) {
for (let u = 0; u < ncsCount; ++u) {
const ncsOp = ncsOperators![u]
@@ -131,10 +135,15 @@ function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3) {
return operators;
}
function getOperatorsCached333(symmetry: ModelSymmetry) {
if (typeof symmetry._operators_333 !== 'undefined') return symmetry._operators_333;
symmetry._operators_333 = getOperators(symmetry, Vec3.create(-3, -3, -3), Vec3.create(3, 3, 3));
return symmetry._operators_333;
function getOperatorsCached333(symmetry: ModelSymmetry, ref: Vec3) {
if (symmetry._operators_333 && Vec3.equals(ref, symmetry._operators_333.ref)) {
return symmetry._operators_333.operators;
}
symmetry._operators_333 = {
ref: Vec3.clone(ref),
operators: getOperators(symmetry, Vec3.create(-3, -3, -3), Vec3.create(3, 3, 3), ref)
};
return symmetry._operators_333.operators;
}
function assembleOperators(structure: Structure, operators: ReadonlyArray<SymmetryOperator>) {
@@ -164,7 +173,8 @@ async function findSymmetryRange(ctx: RuntimeContext, structure: Structure, ijkM
const { spacegroup } = models[0].symmetry;
if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
const operators = getOperators(models[0].symmetry, ijkMin, ijkMax);
const modelCenter = Model.getCenter(models[0])
const operators = getOperators(models[0].symmetry, ijkMin, ijkMax, modelCenter);
return assembleOperators(structure, operators);
}
@@ -177,10 +187,17 @@ async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius
if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
if (ctx.shouldUpdate) await ctx.update('Initialing...');
const operators = getOperatorsCached333(symmetry);
const modelCenter = Model.getCenter(models[0])
const operators = getOperatorsCached333(symmetry, modelCenter);
const lookup = structure.lookup3d;
const assembler = Structure.Builder();
// keep track of added invariant-unit and operator combinations
const added = new Set<string>()
function hash(unit: Unit, oper: SymmetryOperator) {
return `${unit.invariantId}|${oper.name}`
}
const assembler = Structure.Builder({ label: structure.label });
const { units } = structure;
const center = Vec3.zero();
@@ -193,13 +210,17 @@ async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius
for (let uI = 0, _uI = closeUnits.count; uI < _uI; uI++) {
const closeUnit = units[closeUnits.indices[uI]];
if (!closeUnit.lookup3d.check(center[0], center[1], center[2], boundingSphere.radius + radius)) continue;
assembler.addWithOperator(unit, oper);
const h = hash(unit, oper)
if (!added.has(h)) {
assembler.addWithOperator(unit, oper);
added.add(h)
}
}
}
if (ctx.shouldUpdate) await ctx.update('Building symmetry...');
}
return assembler.getStructure();
}

View File

@@ -14,7 +14,7 @@ import { ValueRef } from '../../../mol-util';
import { UnitRings } from './unit/rings';
import StructureElement from './element'
import { ChainIndex, ResidueIndex, ElementIndex } from '../model/indexing';
import { IntMap, SortedArray } from '../../../mol-data/int';
import { IntMap, SortedArray, Segmentation } from '../../../mol-data/int';
import { hash2, hashFnv32a } from '../../../mol-data/util';
import { getAtomicPolymerElements, getCoarsePolymerElements, getAtomicGapElements, getCoarseGapElements, getNucleotideElements, getProteinElements } from './util/polymer';
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
@@ -202,6 +202,20 @@ namespace Unit {
return this.props.proteinElements.ref;
}
get residueCount(): number {
if (this.props.residueCount.ref !== undefined) return this.props.residueCount.ref;
let residueCount = 0
const residueIt = Segmentation.transientSegments(this.model.atomicHierarchy.residueAtomSegments, this.elements)
while (residueIt.hasNext) {
residueIt.move()
residueCount += 1
}
this.props.residueCount.ref = residueCount;
return this.props.residueCount.ref!;
}
getResidueIndex(elementIndex: StructureElement.UnitIndex) {
return this.model.atomicHierarchy.residueAtomSegments.index[this.elements[elementIndex]];
}
@@ -227,6 +241,7 @@ namespace Unit {
gapElements: ValueRef<SortedArray<ElementIndex> | undefined>
nucleotideElements: ValueRef<SortedArray<ElementIndex> | undefined>
proteinElements: ValueRef<SortedArray<ElementIndex> | undefined>
residueCount: ValueRef<number | undefined>
}
function AtomicProperties(): AtomicProperties {
@@ -238,6 +253,7 @@ namespace Unit {
gapElements: ValueRef.create(void 0),
nucleotideElements: ValueRef.create(void 0),
proteinElements: ValueRef.create(void 0),
residueCount: ValueRef.create(void 0),
};
}

View File

@@ -6,17 +6,46 @@
import { ElementSymbol } from '../../../model/types';
export interface LinkComputationParameters {
maxHbondLength: number,
export interface LinkComputationProps {
/**
* Experimental covalent hydrogen bond lengths
*
* C-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rCH)
* - Average 1.091 (+/- 0.017)
* - Min 0.931
* - Max 1.140
*
* N-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rNH)
* - Average 1.009 (+/- 0.043)
* - Min 0.836
* - Max 1.090
*
* O-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rOH)
* - Average 0.967 (+/- 0.022)
* - Min 0.912
* - Max 1.033
*
* S-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rSH)
* - Average 1.345 (+/- 0.020)
* - Min 1.322
* - Max 1.400
*/
maxCovalentHydrogenBondingLength: number,
forceCompute: boolean
}
export const DefaultLinkComputationProps: LinkComputationProps = {
maxCovalentHydrogenBondingLength: 1.45,
forceCompute: false
}
// H,D,T are all mapped to H
const __ElementIndex: { [e: string]: number | undefined } = { 'H': 0, 'h': 0, 'D': 0, 'd': 0, 'T': 0, 't': 0, 'He': 2, 'HE': 2, 'he': 2, 'Li': 3, 'LI': 3, 'li': 3, 'Be': 4, 'BE': 4, 'be': 4, 'B': 5, 'b': 5, 'C': 6, 'c': 6, 'N': 7, 'n': 7, 'O': 8, 'o': 8, 'F': 9, 'f': 9, 'Ne': 10, 'NE': 10, 'ne': 10, 'Na': 11, 'NA': 11, 'na': 11, 'Mg': 12, 'MG': 12, 'mg': 12, 'Al': 13, 'AL': 13, 'al': 13, 'Si': 14, 'SI': 14, 'si': 14, 'P': 15, 'p': 15, 'S': 16, 's': 16, 'Cl': 17, 'CL': 17, 'cl': 17, 'Ar': 18, 'AR': 18, 'ar': 18, 'K': 19, 'k': 19, 'Ca': 20, 'CA': 20, 'ca': 20, 'Sc': 21, 'SC': 21, 'sc': 21, 'Ti': 22, 'TI': 22, 'ti': 22, 'V': 23, 'v': 23, 'Cr': 24, 'CR': 24, 'cr': 24, 'Mn': 25, 'MN': 25, 'mn': 25, 'Fe': 26, 'FE': 26, 'fe': 26, 'Co': 27, 'CO': 27, 'co': 27, 'Ni': 28, 'NI': 28, 'ni': 28, 'Cu': 29, 'CU': 29, 'cu': 29, 'Zn': 30, 'ZN': 30, 'zn': 30, 'Ga': 31, 'GA': 31, 'ga': 31, 'Ge': 32, 'GE': 32, 'ge': 32, 'As': 33, 'AS': 33, 'as': 33, 'Se': 34, 'SE': 34, 'se': 34, 'Br': 35, 'BR': 35, 'br': 35, 'Kr': 36, 'KR': 36, 'kr': 36, 'Rb': 37, 'RB': 37, 'rb': 37, 'Sr': 38, 'SR': 38, 'sr': 38, 'Y': 39, 'y': 39, 'Zr': 40, 'ZR': 40, 'zr': 40, 'Nb': 41, 'NB': 41, 'nb': 41, 'Mo': 42, 'MO': 42, 'mo': 42, 'Tc': 43, 'TC': 43, 'tc': 43, 'Ru': 44, 'RU': 44, 'ru': 44, 'Rh': 45, 'RH': 45, 'rh': 45, 'Pd': 46, 'PD': 46, 'pd': 46, 'Ag': 47, 'AG': 47, 'ag': 47, 'Cd': 48, 'CD': 48, 'cd': 48, 'In': 49, 'IN': 49, 'in': 49, 'Sn': 50, 'SN': 50, 'sn': 50, 'Sb': 51, 'SB': 51, 'sb': 51, 'Te': 52, 'TE': 52, 'te': 52, 'I': 53, 'i': 53, 'Xe': 54, 'XE': 54, 'xe': 54, 'Cs': 55, 'CS': 55, 'cs': 55, 'Ba': 56, 'BA': 56, 'ba': 56, 'La': 57, 'LA': 57, 'la': 57, 'Ce': 58, 'CE': 58, 'ce': 58, 'Pr': 59, 'PR': 59, 'pr': 59, 'Nd': 60, 'ND': 60, 'nd': 60, 'Pm': 61, 'PM': 61, 'pm': 61, 'Sm': 62, 'SM': 62, 'sm': 62, 'Eu': 63, 'EU': 63, 'eu': 63, 'Gd': 64, 'GD': 64, 'gd': 64, 'Tb': 65, 'TB': 65, 'tb': 65, 'Dy': 66, 'DY': 66, 'dy': 66, 'Ho': 67, 'HO': 67, 'ho': 67, 'Er': 68, 'ER': 68, 'er': 68, 'Tm': 69, 'TM': 69, 'tm': 69, 'Yb': 70, 'YB': 70, 'yb': 70, 'Lu': 71, 'LU': 71, 'lu': 71, 'Hf': 72, 'HF': 72, 'hf': 72, 'Ta': 73, 'TA': 73, 'ta': 73, 'W': 74, 'w': 74, 'Re': 75, 'RE': 75, 're': 75, 'Os': 76, 'OS': 76, 'os': 76, 'Ir': 77, 'IR': 77, 'ir': 77, 'Pt': 78, 'PT': 78, 'pt': 78, 'Au': 79, 'AU': 79, 'au': 79, 'Hg': 80, 'HG': 80, 'hg': 80, 'Tl': 81, 'TL': 81, 'tl': 81, 'Pb': 82, 'PB': 82, 'pb': 82, 'Bi': 83, 'BI': 83, 'bi': 83, 'Po': 84, 'PO': 84, 'po': 84, 'At': 85, 'AT': 85, 'at': 85, 'Rn': 86, 'RN': 86, 'rn': 86, 'Fr': 87, 'FR': 87, 'fr': 87, 'Ra': 88, 'RA': 88, 'ra': 88, 'Ac': 89, 'AC': 89, 'ac': 89, 'Th': 90, 'TH': 90, 'th': 90, 'Pa': 91, 'PA': 91, 'pa': 91, 'U': 92, 'u': 92, 'Np': 93, 'NP': 93, 'np': 93, 'Pu': 94, 'PU': 94, 'pu': 94, 'Am': 95, 'AM': 95, 'am': 95, 'Cm': 96, 'CM': 96, 'cm': 96, 'Bk': 97, 'BK': 97, 'bk': 97, 'Cf': 98, 'CF': 98, 'cf': 98, 'Es': 99, 'ES': 99, 'es': 99, 'Fm': 100, 'FM': 100, 'fm': 100, 'Md': 101, 'MD': 101, 'md': 101, 'No': 102, 'NO': 102, 'no': 102, 'Lr': 103, 'LR': 103, 'lr': 103, 'Rf': 104, 'RF': 104, 'rf': 104, 'Db': 105, 'DB': 105, 'db': 105, 'Sg': 106, 'SG': 106, 'sg': 106, 'Bh': 107, 'BH': 107, 'bh': 107, 'Hs': 108, 'HS': 108, 'hs': 108, 'Mt': 109, 'MT': 109, 'mt': 109 };
const __ElementBondThresholds: { [e: number]: number | undefined } = { 0: 1.42, 1: 1.42, 3: 2.7, 4: 2.7, 6: 1.75, 7: 1.6, 8: 1.52, 11: 2.7, 12: 2.7, 13: 2.7, 14: 1.9, 15: 1.9, 16: 1.9, 17: 1.8, 19: 2.7, 20: 2.7, 21: 2.7, 22: 2.7, 23: 2.7, 24: 2.7, 25: 2.7, 26: 2.7, 27: 2.7, 28: 2.7, 29: 2.7, 30: 2.7, 31: 2.7, 33: 2.68, 37: 2.7, 38: 2.7, 39: 2.7, 40: 2.7, 41: 2.7, 42: 2.7, 43: 2.7, 44: 2.7, 45: 2.7, 46: 2.7, 47: 2.7, 48: 2.7, 49: 2.7, 50: 2.7, 55: 2.7, 56: 2.7, 57: 2.7, 58: 2.7, 59: 2.7, 60: 2.7, 61: 2.7, 62: 2.7, 63: 2.7, 64: 2.7, 65: 2.7, 66: 2.7, 67: 2.7, 68: 2.7, 69: 2.7, 70: 2.7, 71: 2.7, 72: 2.7, 73: 2.7, 74: 2.7, 75: 2.7, 76: 2.7, 77: 2.7, 78: 2.7, 79: 2.7, 80: 2.7, 81: 2.7, 82: 2.7, 83: 2.7, 87: 2.7, 88: 2.7, 89: 2.7, 90: 2.7, 91: 2.7, 92: 2.7, 93: 2.7, 94: 2.7, 95: 2.7, 96: 2.7, 97: 2.7, 98: 2.7, 99: 2.7, 100: 2.7, 101: 2.7, 102: 2.7, 103: 2.7, 104: 2.7, 105: 2.7, 106: 2.7, 107: 2.7, 108: 2.7, 109: 2.88 };
// increased P (15) threshold from 1.9 to 2.0 (e.g. for G16 in 1O08)
const __ElementBondThresholds: { [e: number]: number | undefined } = { 0: 1.42, 1: 1.42, 3: 2.7, 4: 2.7, 6: 1.75, 7: 1.6, 8: 1.52, 11: 2.7, 12: 2.7, 13: 2.7, 14: 1.9, 15: 2.0, 16: 1.9, 17: 1.8, 19: 2.7, 20: 2.7, 21: 2.7, 22: 2.7, 23: 2.7, 24: 2.7, 25: 2.7, 26: 2.7, 27: 2.7, 28: 2.7, 29: 2.7, 30: 2.7, 31: 2.7, 33: 2.68, 37: 2.7, 38: 2.7, 39: 2.7, 40: 2.7, 41: 2.7, 42: 2.7, 43: 2.7, 44: 2.7, 45: 2.7, 46: 2.7, 47: 2.7, 48: 2.7, 49: 2.7, 50: 2.7, 55: 2.7, 56: 2.7, 57: 2.7, 58: 2.7, 59: 2.7, 60: 2.7, 61: 2.7, 62: 2.7, 63: 2.7, 64: 2.7, 65: 2.7, 66: 2.7, 67: 2.7, 68: 2.7, 69: 2.7, 70: 2.7, 71: 2.7, 72: 2.7, 73: 2.7, 74: 2.7, 75: 2.7, 76: 2.7, 77: 2.7, 78: 2.7, 79: 2.7, 80: 2.7, 81: 2.7, 82: 2.7, 83: 2.7, 87: 2.7, 88: 2.7, 89: 2.7, 90: 2.7, 91: 2.7, 92: 2.7, 93: 2.7, 94: 2.7, 95: 2.7, 96: 2.7, 97: 2.7, 98: 2.7, 99: 2.7, 100: 2.7, 101: 2.7, 102: 2.7, 103: 2.7, 104: 2.7, 105: 2.7, 106: 2.7, 107: 2.7, 108: 2.7, 109: 2.88 };
const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.3, 35: 1.3, 44: 1.05, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.55, 113: 1.59, 114: 1.36, 129: 1.45, 144: 1.6, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 };
// increased N-N (112) threshold from 1.55 to 1.6 (e.g. for 0QH in 1BMA)
const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.3, 35: 1.3, 44: 1.05, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.6, 113: 1.59, 114: 1.36, 129: 1.45, 144: 1.6, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 };
const __DefaultBondingRadius = 2.001;

View File

@@ -8,7 +8,7 @@
import { LinkType } from '../../../model/types';
import Structure from '../../structure';
import Unit from '../../unit';
import { getElementIdx, getElementPairThreshold, getElementThreshold, isHydrogen, LinkComputationParameters, MetalsSet } from './common';
import { getElementIdx, getElementPairThreshold, getElementThreshold, isHydrogen, LinkComputationProps, MetalsSet, DefaultLinkComputationProps } from './common';
import { InterUnitBonds } from './data';
import { UniqueArray } from '../../../../../mol-data/generic';
import { SortedArray } from '../../../../../mol-data/int';
@@ -39,7 +39,7 @@ function addLink(indexA: number, indexB: number, order: number, flag: LinkType.F
const _imageTransform = Mat4.zero();
function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, params: LinkComputationParameters, map: Map<number, InterUnitBonds.UnitPairBonds[]>) {
function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, props: LinkComputationProps, map: Map<number, InterUnitBonds.UnitPairBonds[]>) {
const state: PairState = { mapAB: new Map(), mapBA: new Map(), bondedA: UniqueArray.create(), bondedB: UniqueArray.create() };
let bondCount = 0;
@@ -68,7 +68,7 @@ function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, params: LinkCompu
if (isNotIdentity) Vec3.transformMat4(imageA, imageA, imageTransform);
if (Vec3.squaredDistance(imageA, bCenter) > testDistanceSq) continue;
const structConnEntries = params.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI);
const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI);
if (structConnEntries && structConnEntries.length) {
for (const se of structConnEntries) {
if (se.distance < MAX_RADIUS) continue;
@@ -126,7 +126,7 @@ function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, params: LinkCompu
}
if (isHa || isHb) {
if (dist < params.maxHbondLength) {
if (dist < props.maxCovalentHydrogenBondingLength) {
addLink(_aI, _bI, 1, LinkType.Flag.Covalent | LinkType.Flag.Computed, state); // TODO: check if correct
bondCount++;
}
@@ -153,15 +153,15 @@ function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, params: LinkCompu
return bondCount;
}
export interface InterLinkComputationParameters extends LinkComputationParameters {
export interface InterLinkComputationProps extends LinkComputationProps {
validUnitPair: (unitA: Unit, unitB: Unit) => boolean
}
function findLinks(structure: Structure, params: InterLinkComputationParameters) {
function findLinks(structure: Structure, props: InterLinkComputationProps) {
const map = new Map<number, InterUnitBonds.UnitPairBonds[]>();
if (!structure.units.some(u => Unit.isAtomic(u))) return new InterUnitBonds(map);
const { validUnitPair } = params;
const { validUnitPair } = props;
const lookup = structure.lookup3d;
const imageCenter = Vec3.zero();
@@ -175,8 +175,8 @@ function findLinks(structure: Structure, params: InterLinkComputationParameters)
const other = structure.units[closeUnits.indices[i]];
if (!Unit.isAtomic(other) || unit.id >= other.id || !validUnitPair(unit, other)) continue;
if (other.elements.length >= unit.elements.length) findPairLinks(unit, other, params, map);
else findPairLinks(other, unit, params, map);
if (other.elements.length >= unit.elements.length) findPairLinks(unit, other, props, map);
else findPairLinks(other, unit, props, map);
}
}
@@ -192,11 +192,10 @@ function ValidUnitPair(structure: Structure) {
}
}
function computeInterUnitBonds(structure: Structure, params?: Partial<InterLinkComputationParameters>): InterUnitBonds {
function computeInterUnitBonds(structure: Structure, props?: Partial<InterLinkComputationProps>): InterUnitBonds {
return findLinks(structure, {
maxHbondLength: (params && params.maxHbondLength) || 1.15,
forceCompute: !!(params && params.forceCompute),
validUnitPair: (params && params.validUnitPair) || ValidUnitPair(structure),
...DefaultLinkComputationProps,
validUnitPair: (props && props.validUnitPair) || ValidUnitPair(structure),
});
}

View File

@@ -8,7 +8,7 @@ import { LinkType } from '../../../model/types'
import { IntraUnitLinks } from './data'
import Unit from '../../unit'
import { IntAdjacencyGraph } from '../../../../../mol-math/graph';
import { LinkComputationParameters, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, getElementPairThreshold } from './common';
import { LinkComputationProps, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, getElementPairThreshold, DefaultLinkComputationProps } from './common';
import { SortedArray } from '../../../../../mol-data/int';
import { StructConn, ComponentBond } from '../../../../../mol-model-formats/structure/mmcif/bonds';
@@ -25,7 +25,7 @@ function getGraph(atomA: number[], atomB: number[], _order: number[], _flags: nu
return builder.createGraph({ flags, order });
}
function _computeBonds(unit: Unit.Atomic, params: LinkComputationParameters): IntraUnitLinks {
function _computeBonds(unit: Unit.Atomic, props: LinkComputationProps): IntraUnitLinks {
const MAX_RADIUS = 4;
const { x, y, z } = unit.model.atomicConformation;
@@ -50,7 +50,7 @@ function _computeBonds(unit: Unit.Atomic, params: LinkComputationParameters): In
const aI = atoms[_aI];
const raI = residueIndex[aI];
if (!params.forceCompute && raI !== lastResidue) {
if (!props.forceCompute && raI !== lastResidue) {
const resn = label_comp_id.value(raI)!;
if (!!component && component.entries.has(resn)) {
componentMap = component.entries.get(resn)!.map;
@@ -69,7 +69,7 @@ function _computeBonds(unit: Unit.Atomic, params: LinkComputationParameters): In
const thresholdA = getElementThreshold(aeI);
const altA = label_alt_id.value(aI);
const metalA = MetalsSet.has(aeI);
const structConnEntries = params.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI);
const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI);
if (structConnEntries) {
for (const se of structConnEntries) {
@@ -141,7 +141,7 @@ function _computeBonds(unit: Unit.Atomic, params: LinkComputationParameters): In
}
if (isHa || isHb) {
if (dist < params.maxHbondLength) {
if (dist < props.maxCovalentHydrogenBondingLength) {
atomA[atomA.length] = _aI;
atomB[atomB.length] = _bI;
order[order.length] = 1;
@@ -167,11 +167,8 @@ function _computeBonds(unit: Unit.Atomic, params: LinkComputationParameters): In
return getGraph(atomA, atomB, order, flags, atomCount);
}
function computeIntraUnitBonds(unit: Unit.Atomic, params?: Partial<LinkComputationParameters>) {
return _computeBonds(unit, {
maxHbondLength: (params && params.maxHbondLength) || 1.15,
forceCompute: !!(params && params.forceCompute),
});
function computeIntraUnitBonds(unit: Unit.Atomic, props?: Partial<LinkComputationProps>) {
return _computeBonds(unit, { ...DefaultLinkComputationProps, ...props });
}
export { computeIntraUnitBonds }

View File

@@ -12,7 +12,7 @@ import Matrix from '../../mol-math/linear-algebra/matrix/matrix';
export function getCoarseBegCompId(unit: Unit.Spheres | Unit.Gaussians, element: ElementIndex) {
const entityKey = unit.coarseElements.entityKey[element]
const seq = unit.model.sequence.byEntityKey[entityKey]
const seq = unit.model.sequence.byEntityKey[entityKey].sequence
const seq_id_begin = unit.coarseElements.seq_id_begin.value(element)
return seq.compId.value(seq_id_begin - 1) // 1-indexed
}

View File

@@ -152,7 +152,7 @@ namespace PluginBehavior {
this.subs = [];
}
constructor(protected plugin: PluginContext) {
constructor(protected plugin: PluginContext, protected params: P) {
}
}
}

View File

@@ -1,31 +1,51 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Loci } from '../../../mol-model/loci';
import { ParamDefinition } from '../../../mol-util/param-definition';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { PluginBehavior } from '../behavior';
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
import { Binding } from '../../../mol-util/binding';
export const FocusLociOnSelect = PluginBehavior.create<{ minRadius: number, extraRadius: number }>({
name: 'focus-loci-on-select',
const B = ButtonsType
const M = ModifiersKeys
const Trigger = Binding.Trigger
const DefaultFocusLociBindings = {
clickCenterFocus: Binding(Trigger(B.Flag.Primary, M.create()), 'Center and focus the clicked element using ${trigger}.'),
}
const FocusLociParams = {
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.' }),
bindings: PD.Value(DefaultFocusLociBindings, { isHidden: true }),
}
type FocusLociProps = PD.Values<typeof FocusLociParams>
export const FocusLoci = PluginBehavior.create<FocusLociProps>({
name: 'camera-focus-loci',
category: 'interaction',
ctor: class extends PluginBehavior.Handler<{ minRadius: number, extraRadius: number }> {
ctor: class extends PluginBehavior.Handler<FocusLociProps> {
register(): void {
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, buttons, modifiers }) => {
if (!this.ctx.canvas3d || buttons !== ButtonsType.Flag.Primary || !ModifiersKeys.areEqual(modifiers, ModifiersKeys.None)) return;
if (!this.ctx.canvas3d) return;
const sphere = Loci.getBoundingSphere(current.loci);
if (!sphere) return;
this.ctx.canvas3d.camera.focus(sphere.center, Math.max(sphere.radius + this.params.extraRadius, this.params.minRadius));
const p = this.params;
if (Binding.match(this.params.bindings.clickCenterFocus, buttons, modifiers)) {
const sphere = Loci.getBoundingSphere(current.loci);
if (sphere) {
const radius = Math.max(sphere.radius + p.extraRadius, p.minRadius);
this.ctx.canvas3d.camera.focus(sphere.center, radius, p.durationMs);
}
}
});
}
},
params: () => ({
minRadius: ParamDefinition.Numeric(8, { min: 1, max: 50, step: 1 }),
extraRadius: ParamDefinition.Numeric(4, { min: 1, max: 50, step: 1 }, { description: 'Value added to the bounding-sphere radius of the Loci.' })
}),
display: { name: 'Focus Loci on Select' }
params: () => FocusLociParams,
display: { name: 'Focus Loci on Canvas' }
});

View File

@@ -13,29 +13,81 @@ import { PluginBehavior } from '../behavior';
import { Interactivity } from '../../util/interactivity';
import { StateTreeSpine } from '../../../mol-state/tree/spine';
import { StateSelection } from '../../../mol-state';
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
import { Binding } from '../../../mol-util/binding';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { EmptyLoci } from '../../../mol-model/loci';
const B = ButtonsType
const M = ModifiersKeys
const Trigger = Binding.Trigger
//
const DefaultHighlightLociBindings = {
hoverHighlightOnly: Binding(Trigger(B.Flag.None), 'Highlight hovered element using ${trigger}'),
hoverHighlightOnlyExtend: Binding(Trigger(B.Flag.None, M.create({ shift: true })), 'Extend highlight from selected to hovered element along polymer using ${trigger}'),
}
const HighlightLociParams = {
bindings: PD.Value(DefaultHighlightLociBindings, { isHidden: true }),
}
type HighlightLociProps = PD.Values<typeof HighlightLociParams>
export const HighlightLoci = PluginBehavior.create({
name: 'representation-highlight-loci',
category: 'interaction',
ctor: class extends PluginBehavior.Handler {
ctor: class extends PluginBehavior.Handler<HighlightLociProps> {
private lociMarkProvider = (interactionLoci: Interactivity.Loci, action: MarkerAction) => {
if (!this.ctx.canvas3d) return;
this.ctx.canvas3d.mark({ loci: interactionLoci.loci }, action)
}
register() {
this.subscribeObservable(this.ctx.behaviors.interaction.hover, ({ current, buttons, modifiers }) => {
if (!this.ctx.canvas3d) return
let matched = false
if (Binding.match(this.params.bindings.hoverHighlightOnly, buttons, modifiers)) {
this.ctx.interactivity.lociHighlights.highlightOnly(current)
matched = true
}
if (Binding.match(this.params.bindings.hoverHighlightOnlyExtend, buttons, modifiers)) {
this.ctx.interactivity.lociHighlights.highlightOnlyExtend(current)
matched = true
}
if (!matched) {
this.ctx.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci: EmptyLoci })
}
});
this.ctx.interactivity.lociHighlights.addProvider(this.lociMarkProvider)
}
unregister() {
this.ctx.interactivity.lociHighlights.removeProvider(this.lociMarkProvider)
}
},
params: () => HighlightLociParams,
display: { name: 'Highlight Loci on Canvas' }
});
//
const DefaultSelectLociBindings = {
clickSelect: Binding.Empty,
clickSelectExtend: Binding(Trigger(B.Flag.Primary, M.create({ shift: true })), 'Extend selection to clicked element along polymer using ${trigger}.'),
clickSelectOnly: Binding(Trigger(B.Flag.Secondary, M.create({ control: true })), 'Select only the clicked element using ${trigger}.'),
clickSelectToggle: Binding(Trigger(B.Flag.Primary, M.create({ control: true })), 'Toggle selection of clicked element using ${trigger}.'),
clickDeselect: Binding.Empty,
}
const SelectLociParams = {
bindings: PD.Value(DefaultSelectLociBindings, { isHidden: true }),
}
type SelectLociProps = PD.Values<typeof SelectLociParams>
export const SelectLoci = PluginBehavior.create({
name: 'representation-select-loci',
category: 'interaction',
ctor: class extends PluginBehavior.Handler {
ctor: class extends PluginBehavior.Handler<SelectLociProps> {
private spine: StateTreeSpine.Impl
private lociMarkProvider = (interactionLoci: Interactivity.Loci, action: MarkerAction) => {
if (!this.ctx.canvas3d) return;
@@ -53,7 +105,30 @@ export const SelectLoci = PluginBehavior.create({
}
}
register() {
this.ctx.interactivity.lociSelections.addProvider(this.lociMarkProvider)
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, buttons, modifiers }) => {
if (!this.ctx.canvas3d) return
if (Binding.match(this.params.bindings.clickSelect, buttons, modifiers)) {
this.ctx.interactivity.lociSelects.select(current)
}
if (Binding.match(this.params.bindings.clickSelectExtend, buttons, modifiers)) {
this.ctx.interactivity.lociSelects.selectExtend(current)
}
if (Binding.match(this.params.bindings.clickSelectOnly, buttons, modifiers)) {
this.ctx.interactivity.lociSelects.selectOnly(current)
}
if (Binding.match(this.params.bindings.clickSelectToggle, buttons, modifiers)) {
this.ctx.interactivity.lociSelects.selectToggle(current)
}
if (Binding.match(this.params.bindings.clickDeselect, buttons, modifiers)) {
this.ctx.interactivity.lociSelects.deselect(current)
}
});
this.ctx.interactivity.lociSelects.addProvider(this.lociMarkProvider)
this.subscribeObservable(this.ctx.events.state.object.created, ({ ref }) => this.applySelectMark(ref));
@@ -67,13 +142,14 @@ export const SelectLoci = PluginBehavior.create({
});
}
unregister() {
this.ctx.interactivity.lociSelections.removeProvider(this.lociMarkProvider)
this.ctx.interactivity.lociSelects.removeProvider(this.lociMarkProvider)
}
constructor(ctx: PluginContext, params: {}) {
constructor(ctx: PluginContext, params: SelectLociProps) {
super(ctx, params)
this.spine = new StateTreeSpine.Impl(ctx.state.dataState.cells)
}
},
params: () => SelectLociParams,
display: { name: 'Select Loci on Canvas' }
});

View File

@@ -2,12 +2,12 @@
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Structure, StructureElement } from '../../../../mol-model/structure';
import { Structure, StructureElement, Link } from '../../../../mol-model/structure';
import { PluginBehavior } from '../../../../mol-plugin/behavior';
import { PluginCommands } from '../../../../mol-plugin/command';
import { PluginContext } from '../../../../mol-plugin/context';
import { PluginStateObject } from '../../../../mol-plugin/state/objects';
import { StateTransforms } from '../../../../mol-plugin/state/transforms';
import { StructureRepresentation3DHelpers } from '../../../../mol-plugin/state/transforms/representation';
@@ -16,11 +16,22 @@ import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder'
import { StateObjectCell, StateSelection, StateTransform } from '../../../../mol-state';
import { BuiltInColorThemes } from '../../../../mol-theme/color';
import { BuiltInSizeThemes } from '../../../../mol-theme/size';
import { ColorNames } from '../../../../mol-util/color/names';
import { ButtonsType } from '../../../../mol-util/input/input-observer';
import { Representation } from '../../../../mol-repr/representation';
import { ButtonsType, ModifiersKeys } from '../../../../mol-util/input/input-observer';
import { Binding } from '../../../../mol-util/binding';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { isEmptyLoci, Loci, EmptyLoci } from '../../../../mol-model/loci';
type Params = { }
const B = ButtonsType
const M = ModifiersKeys
const Trigger = Binding.Trigger
const DefaultStructureRepresentationInteractionBindings = {
clickInteractionAroundOnly: Binding(Trigger(B.Flag.Secondary, M.create()), 'Show the structure interaction around only the clicked element using ${trigger}.'),
}
const StructureRepresentationInteractionParams = {
bindings: PD.Value(DefaultStructureRepresentationInteractionBindings, { isHidden: true }),
}
type StructureRepresentationInteractionProps = PD.Values<typeof StructureRepresentationInteractionParams>
enum Tags {
Group = 'structure-interaction-group',
@@ -32,7 +43,7 @@ enum Tags {
const TagSet: Set<Tags> = new Set([Tags.Group, Tags.ResidueSel, Tags.ResidueRepr, Tags.SurrSel, Tags.SurrRepr])
export class StructureRepresentationInteractionBehavior extends PluginBehavior.WithSubscribers<Params> {
export class StructureRepresentationInteractionBehavior extends PluginBehavior.WithSubscribers<StructureRepresentationInteractionProps> {
private createResVisualParams(s: Structure) {
return StructureRepresentation3DHelpers.createParams(this.plugin, s, {
@@ -44,7 +55,7 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W
private createSurVisualParams(s: Structure) {
return StructureRepresentation3DHelpers.createParams(this.plugin, s, {
repr: BuiltInStructureRepresentations['ball-and-stick'],
color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
color: [BuiltInColorThemes['element-symbol'], () => ({ saturation: -3, lightness: 0.6 })],
size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
});
}
@@ -105,86 +116,85 @@ export class StructureRepresentationInteractionBehavior extends PluginBehavior.W
}
register(ref: string): void {
let lastLoci: Representation.Loci = Representation.Loci.Empty;
let lastLoci: Loci = EmptyLoci;
this.subscribeObservable(this.plugin.events.state.object.removed, o => {
if (!PluginStateObject.Molecule.Structure.is(o.obj) || lastLoci.loci.kind !== 'element-loci') return;
if (lastLoci.loci.structure === o.obj.data) {
lastLoci = Representation.Loci.Empty;
if (!PluginStateObject.Molecule.Structure.is(o.obj) || !StructureElement.Loci.is(lastLoci)) return;
if (lastLoci.structure === o.obj.data) {
lastLoci = EmptyLoci;
}
});
this.subscribeObservable(this.plugin.events.state.object.updated, o => {
if (!PluginStateObject.Molecule.Structure.is(o.oldObj) || lastLoci.loci.kind !== 'element-loci') return;
if (lastLoci.loci.structure === o.oldObj.data) {
lastLoci = Representation.Loci.Empty;
if (!PluginStateObject.Molecule.Structure.is(o.oldObj) || !StructureElement.Loci.is(lastLoci)) return;
if (lastLoci.structure === o.oldObj.data) {
lastLoci = EmptyLoci;
}
});
this.subscribeObservable(this.plugin.behaviors.interaction.click, ({ current, buttons, modifiers }) => {
if (buttons !== ButtonsType.Flag.Secondary) return;
const { clickInteractionAroundOnly } = this.params.bindings
if (current.loci.kind === 'empty-loci') {
if (modifiers.control && buttons === ButtonsType.Flag.Secondary) {
if (Binding.match(clickInteractionAroundOnly, buttons, modifiers)) {
if (isEmptyLoci(current.loci)) {
this.clear(StateTransform.RootRef);
lastLoci = current.loci;
return;
}
let loci: StructureElement.Loci;
if (StructureElement.Loci.is(current.loci)) {
loci = current.loci;
} else if (Link.isLoci(current.loci)) {
loci = Link.toStructureElementLoci(current.loci);
} else if (Structure.isLoci(current.loci)) {
loci = Structure.toStructureElementLoci(current.loci);
} else {
return;
}
if (StructureElement.Loci.isEmpty(loci)) return;
const parent = this.plugin.helpers.substructureParent.get(loci.structure);
if (!parent || !parent.obj) return;
if (Loci.areEqual(lastLoci, loci)) {
lastLoci = EmptyLoci;
this.clear(parent.transform.ref);
return;
}
lastLoci = loci;
const core = MS.struct.modifier.wholeResidues([
StructureElement.Loci.toExpression(loci)
]);
const surroundings = MS.struct.modifier.includeSurroundings({
0: core,
radius: 5,
'as-whole-residues': true
});
const { state, builder, refs } = this.ensureShape(parent);
builder.to(refs[Tags.ResidueSel]!).update(StateTransforms.Model.StructureSelectionFromExpression, old => ({ ...old, expression: core }));
builder.to(refs[Tags.SurrSel]!).update(StateTransforms.Model.StructureSelectionFromExpression, old => ({ ...old, expression: surroundings }));
PluginCommands.State.Update.dispatch(this.plugin, { state, tree: builder, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
}
// TODO: support link loci as well?
if (!StructureElement.Loci.is(current.loci)) return;
const parent = this.plugin.helpers.substructureParent.get(current.loci.structure);
if (!parent || !parent.obj) return;
if (Representation.Loci.areEqual(lastLoci, current)) {
lastLoci = Representation.Loci.Empty;
this.clear(parent.transform.ref);
return;
}
lastLoci = current;
const core = MS.struct.modifier.wholeResidues([
StructureElement.Loci.toExpression(current.loci)
]);
const surroundings = MS.struct.modifier.includeSurroundings({
0: core,
radius: 5,
'as-whole-residues': true
});
// const surroundings = MS.struct.modifier.exceptBy({
// 0: MS.struct.modifier.includeSurroundings({
// 0: core,
// radius: 5,
// 'as-whole-residues': true
// }),
// by: core
// });
const { state, builder, refs } = this.ensureShape(parent);
builder.to(refs[Tags.ResidueSel]!).update(StateTransforms.Model.StructureSelectionFromExpression, old => ({ ...old, expression: core }));
builder.to(refs[Tags.SurrSel]!).update(StateTransforms.Model.StructureSelectionFromExpression, old => ({ ...old, expression: surroundings }));
PluginCommands.State.Update.dispatch(this.plugin, { state, tree: builder, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
});
}
async update(params: Params) {
async update(params: StructureRepresentationInteractionProps) {
return false;
}
constructor(public plugin: PluginContext) {
super(plugin);
}
}
export const StructureRepresentationInteraction = PluginBehavior.create({
name: 'create-structure-representation-interaction',
display: { name: 'Structure Representation Interaction' },
category: 'interaction',
ctor: StructureRepresentationInteractionBehavior
ctor: StructureRepresentationInteractionBehavior,
params: () => StructureRepresentationInteractionParams
});

View File

@@ -2,6 +2,7 @@
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
@@ -20,9 +21,15 @@ import { volumeFromDensityServerData } from '../../../../mol-model-formats/volum
import { PluginCommands } from '../../../command';
import { StateSelection } from '../../../../mol-state';
import { Representation } from '../../../../mol-repr/representation';
import { ButtonsType } from '../../../../mol-util/input/input-observer';
import { StructureElement } from '../../../../mol-model/structure';
import { ButtonsType, ModifiersKeys } from '../../../../mol-util/input/input-observer';
import { StructureElement, Link, Structure } from '../../../../mol-model/structure';
import { PluginContext } from '../../../context';
import { Binding } from '../../../../mol-util/binding';
import { EmptyLoci, Loci, isEmptyLoci } from '../../../../mol-model/loci';
const B = ButtonsType
const M = ModifiersKeys
const Trigger = Binding.Trigger
export class VolumeStreaming extends PluginStateObject.CreateBehavior<VolumeStreaming.Behavior>({ name: 'Volume Streaming' }) { }
@@ -43,13 +50,18 @@ export namespace VolumeStreaming {
valuesInfo: [{ mean: 0, min: -1, max: 1, sigma: 0.1 }, { mean: 0, min: -1, max: 1, sigma: 0.1 }]
};
export function createParams(data?: VolumeServerInfo.Data, defaultView?: ViewTypes) {
export const DefaultBindings = {
clickVolumeAroundOnly: Binding(Trigger(B.Flag.Secondary, M.create()), 'Show the volume around only the clicked element using ${trigger}.'),
}
export function createParams(data?: VolumeServerInfo.Data, defaultView?: ViewTypes, binding?: typeof DefaultBindings) {
// fake the info
const info = data || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: VolumeIsoValue.relative(0) };
const box = (data && data.structure.boundary.box) || Box3D.empty();
return {
view: PD.MappedStatic(defaultView || (info.kind === 'em' ? 'cell' : 'selection-box'), {
'off': PD.Group({}),
'box': PD.Group({
bottomLeft: PD.Vec3(box.min),
topRight: PD.Vec3(box.max),
@@ -60,8 +72,8 @@ export namespace VolumeStreaming {
topRight: PD.Vec3(Vec3.create(0, 0, 0), { isHidden: true }),
}, { description: 'Box around last-interacted element.', isFlat: true }),
'cell': PD.Group({}),
// 'auto': PD.Group({ }), // based on camera distance/active selection/whatever, show whole structure or slice.
}, { options: [['box', 'Bounded Box'], ['selection-box', 'Selection'], ['cell', 'Whole Structure']] }),
// 'auto': PD.Group({ }), // TODO based on camera distance/active selection/whatever, show whole structure or slice.
}, { options: ViewTypeOptions as any }),
detailLevel: PD.Select<number>(Math.min(3, info.header.availablePrecisions.length - 1),
info.header.availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string])),
channels: info.kind === 'em'
@@ -72,11 +84,14 @@ export namespace VolumeStreaming {
'2fo-fc': channelParam('2Fo-Fc', Color(0x3362B2), VolumeIsoValue.relative(1.5), info.header.sampling[0].valuesInfo[0]),
'fo-fc(+ve)': channelParam('Fo-Fc(+ve)', Color(0x33BB33), VolumeIsoValue.relative(3), info.header.sampling[0].valuesInfo[1]),
'fo-fc(-ve)': channelParam('Fo-Fc(-ve)', Color(0xBB3333), VolumeIsoValue.relative(-3), info.header.sampling[0].valuesInfo[1]),
}, { isFlat: true })
}, { isFlat: true }),
bindings: PD.Value(binding || DefaultBindings, { isHidden: true }),
};
}
export type ViewTypes = 'box' | 'selection-box' | 'cell'
export const ViewTypeOptions = [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Surroundings'], ['cell', 'Whole Structure']];
export type ViewTypes = 'off' | 'box' | 'selection-box' | 'cell'
export type ParamDefinition = typeof createParams extends (...args: any[]) => (infer T) ? T : never
export type Params = ParamDefinition extends PD.Params ? PD.Values<ParamDefinition> : {}
@@ -101,6 +116,8 @@ export namespace VolumeStreaming {
export class Behavior extends PluginBehavior.WithSubscribers<Params> {
private cache = LRUCache.create<ChannelsData>(25);
public params: Params = {} as any;
private lastLoci: StructureElement.Loci | EmptyLoci = EmptyLoci;
private ref: string = '';
channels: Channels = {}
@@ -154,7 +171,7 @@ export namespace VolumeStreaming {
return ret;
}
private updateDynamicBox(ref: string, box: Box3D) {
private updateDynamicBox(box: Box3D) {
if (this.params.view.name !== 'selection-box') return;
const state = this.plugin.state.dataState;
@@ -169,76 +186,112 @@ export namespace VolumeStreaming {
}
}
};
const update = state.build().to(ref).update(newParams);
const update = state.build().to(this.ref).update(newParams);
PluginCommands.State.Update.dispatch(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
}
private getStructureRoot(ref: string) {
return this.plugin.state.dataState.select(StateSelection.Generators.byRef(ref).rootOfType([PluginStateObject.Molecule.Structure]))[0];
private getStructureRoot() {
return this.plugin.state.dataState.select(StateSelection.Generators.byRef(this.ref).rootOfType([PluginStateObject.Molecule.Structure]))[0];
}
register(ref: string): void {
let lastLoci: Representation.Loci = Representation.Loci.Empty;
this.ref = ref;
this.subscribeObservable(this.plugin.events.state.object.removed, o => {
if (!PluginStateObject.Molecule.Structure.is(o.obj) || lastLoci.loci.kind !== 'element-loci') return;
if (lastLoci.loci.structure === o.obj.data) {
lastLoci = Representation.Loci.Empty;
if (!PluginStateObject.Molecule.Structure.is(o.obj) || !StructureElement.Loci.is(this.lastLoci)) return;
if (this.lastLoci.structure === o.obj.data) {
this.lastLoci = EmptyLoci;
}
});
this.subscribeObservable(this.plugin.events.state.object.updated, o => {
if (!PluginStateObject.Molecule.Structure.is(o.oldObj) || lastLoci.loci.kind !== 'element-loci') return;
if (lastLoci.loci.structure === o.oldObj.data) {
lastLoci = Representation.Loci.Empty;
if (!PluginStateObject.Molecule.Structure.is(o.oldObj) || !StructureElement.Loci.is(this.lastLoci)) return;
if (this.lastLoci.structure === o.oldObj.data) {
this.lastLoci = EmptyLoci;
}
});
this.subscribeObservable(this.plugin.behaviors.interaction.click, ({ current, buttons, modifiers }) => {
if (buttons !== ButtonsType.Flag.Secondary || this.params.view.name !== 'selection-box') return;
if (current.loci.kind === 'empty-loci') {
if (modifiers.control && buttons === ButtonsType.Flag.Secondary) {
this.updateDynamicBox(ref, Box3D.empty());
return;
}
if (!Binding.match((this.params.bindings && this.params.bindings.clickVolumeAroundOnly) || DefaultBindings.clickVolumeAroundOnly, buttons, modifiers)) return;
if (this.params.view.name !== 'selection-box') {
this.lastLoci = this.getNormalizedLoci(current.loci);
} else {
this.updateInteraction(current);
}
// TODO: support link loci as well?
// Perhaps structure loci too?
if (!StructureElement.Loci.is(current.loci)) return;
const parent = this.plugin.helpers.substructureParent.get(current.loci.structure);
if (!parent) return;
const root = this.getStructureRoot(ref);
if (!root || !root.obj || root.obj !== parent.obj) return;
if (Representation.Loci.areEqual(lastLoci, current)) {
lastLoci = Representation.Loci.Empty;
this.updateDynamicBox(ref, Box3D.empty());
return;
}
lastLoci = current;
const loci = StructureElement.Loci.extendToWholeResidues(current.loci);
const box = StructureElement.Loci.getBoundary(loci).box;
this.updateDynamicBox(ref, box);
});
}
private getNormalizedLoci(loci: Loci): StructureElement.Loci | EmptyLoci {
if (StructureElement.Loci.is(loci)) {
return loci;
} else if (Link.isLoci(loci)) {
return Link.toStructureElementLoci(loci);
} else if (Structure.isLoci(loci)) {
return Structure.toStructureElementLoci(loci);
} else {
return EmptyLoci;
}
}
private getBoxFromLoci(loci: StructureElement.Loci | EmptyLoci): Box3D {
if (isEmptyLoci(loci) || StructureElement.Loci.isEmpty(loci)) {
return Box3D.empty();
}
const parent = this.plugin.helpers.substructureParent.get(loci.structure);
if (!parent) return Box3D.empty();
const root = this.getStructureRoot();
if (!root || !root.obj || root.obj !== parent.obj) return Box3D.empty();
const extendedLoci = StructureElement.Loci.extendToWholeResidues(loci)
const box = StructureElement.Loci.getBoundary(extendedLoci).box
if (StructureElement.Loci.size(extendedLoci) === 1) {
Box3D.expand(box, box, Vec3.create(1, 1, 1))
}
return box;
}
private updateInteraction(current: Representation.Loci) {
const loci = this.getNormalizedLoci(current.loci)
if (Loci.areEqual(this.lastLoci, loci)) {
this.lastLoci = EmptyLoci;
this.updateDynamicBox(Box3D.empty());
return;
}
this.lastLoci = loci;
if (isEmptyLoci(loci)) {
this.updateDynamicBox(Box3D.empty());
return;
}
const box = this.getBoxFromLoci(loci);
this.updateDynamicBox(box);
}
async update(params: Params) {
const switchedToSelection = params.view.name === 'selection-box' && this.params && this.params.view && this.params.view.name !== 'selection-box';
this.params = params;
let box: Box3D | undefined = void 0, emptyData = false;
switch (params.view.name) {
case 'off':
emptyData = true;
break;
case 'box':
box = Box3D.create(params.view.params.bottomLeft, params.view.params.topRight);
emptyData = Box3D.volume(box) < 0.0001;
break;
case 'selection-box': {
box = Box3D.create(Vec3.clone(params.view.params.bottomLeft), Vec3.clone(params.view.params.topRight));
if (switchedToSelection) {
box = this.getBoxFromLoci(this.lastLoci) || Box3D.empty();
} else {
box = Box3D.create(Vec3.clone(params.view.params.bottomLeft), Vec3.clone(params.view.params.topRight));
}
const r = params.view.params.radius;
emptyData = Box3D.volume(box) < 0.0001;
Box3D.expand(box, box, Vec3.create(r, r, r));
@@ -287,7 +340,7 @@ export namespace VolumeStreaming {
}
constructor(public plugin: PluginContext, public info: VolumeServerInfo.Data) {
super(plugin);
super(plugin, {} as any);
}
}
}

View File

@@ -2,6 +2,7 @@
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { PluginStateObject as SO, PluginStateTransform } from '../../../state/objects';
@@ -13,38 +14,40 @@ import { urlCombine } from '../../../../mol-util/url';
import { createIsoValueParam } from '../../../../mol-repr/volume/isosurface';
import { VolumeIsoValue } from '../../../../mol-model/volume';
import { StateAction, StateObject, StateTransformer } from '../../../../mol-state';
import { getStreamingMethod, getEmdbIdAndContourLevel } from './util';
import { getStreamingMethod, getId, getContourLevel, getEmdbId } from './util';
import { VolumeStreaming } from './behavior';
import { VolumeRepresentation3DHelpers } from '../../../../mol-plugin/state/transforms/representation';
import { BuiltInVolumeRepresentations } from '../../../../mol-repr/volume/registry';
import { createTheme } from '../../../../mol-theme/theme';
import { Box3D } from '../../../../mol-math/geometry';
import { Vec3 } from '../../../../mol-math/linear-algebra';
// import { PluginContext } from '../../../../mol-plugin/context';
export const InitVolumeStreaming = StateAction.build({
display: { name: 'Volume Streaming' },
from: SO.Molecule.Structure,
params(a) {
const method = getStreamingMethod(a && a.data);
const id = getId(a && a.data);
return {
method: PD.Select<VolumeServerInfo.Kind>(method, [['em', 'EM'], ['x-ray', 'X-Ray']]),
id: PD.Text((a && a.data.models.length > 0 && a.data.models[0].entry) || ''),
id: PD.Text(id),
serverUrl: PD.Text('https://ds.litemol.org'),
defaultView: PD.Text<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box'),
behaviorRef: PD.Text('', { isHidden: true })
defaultView: PD.Select<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box', VolumeStreaming.ViewTypeOptions as any),
behaviorRef: PD.Text('', { isHidden: true }),
emContourProvider: PD.Select<'wwpdb' | 'pdbe'>('wwpdb', [['wwpdb', 'wwPDB'], ['pdbe', 'PDBe']], { isHidden: true }),
bindings: PD.Value(VolumeStreaming.DefaultBindings, { isHidden: true }),
};
},
isApplicable: (a) => a.data.models.length === 1
})(({ ref, state, params }, plugin: PluginContext) => Task.create('Volume Streaming', async taskCtx => {
// TODO: custom react view for this and the VolumeStreamingBehavior transformer
let dataId = params.id.toLowerCase(), emDefaultContourLevel: number | undefined;
if (params.method === 'em') {
await taskCtx.update('Getting EMDB info...');
const emInfo = await getEmdbIdAndContourLevel(plugin, taskCtx, dataId);
dataId = emInfo.emdbId;
emDefaultContourLevel = emInfo.contour;
if (!dataId.toUpperCase().startsWith('EMD')) {
dataId = await getEmdbId(plugin, taskCtx, dataId)
}
const contourLevel = await getContourLevel(params.emContourProvider, plugin, taskCtx, dataId);
emDefaultContourLevel = contourLevel || 0;
}
const infoTree = state.build().to(ref)
@@ -59,7 +62,7 @@ export const InitVolumeStreaming = StateAction.build({
const infoObj = await state.updateTree(infoTree).runInContext(taskCtx);
const behTree = state.build().to(infoTree.ref).apply(CreateVolumeStreamingBehavior,
PD.getDefaultValues(VolumeStreaming.createParams(infoObj.data, params.defaultView)),
PD.getDefaultValues(VolumeStreaming.createParams(infoObj.data, params.defaultView, params.bindings)),
{ ref: params.behaviorRef ? params.behaviorRef : void 0 });
if (params.method === 'em') {
@@ -145,7 +148,8 @@ const CreateVolumeStreamingBehavior = PluginStateTransform.BuiltIn({
})({
canAutoUpdate: ({ oldParams, newParams }) => {
return oldParams.view === newParams.view
|| (oldParams.view.name === newParams.view.name && oldParams.view.name === 'selection-box');
|| newParams.view.name === 'selection-box'
|| newParams.view.name === 'off';
},
apply: ({ a, params }, plugin: PluginContext) => Task.create('Volume streaming', async _ => {
const behavior = new VolumeStreaming.Behavior(plugin, a.data);

View File

@@ -2,12 +2,14 @@
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Structure } from '../../../../mol-model/structure';
import { VolumeServerInfo } from './model';
import { PluginContext } from '../../../../mol-plugin/context';
import { RuntimeContext } from '../../../../mol-task';
import { getXMLNodeByName, XMLDocument } from '../../../../mol-util/xml-parser';
export function getStreamingMethod(s?: Structure, defaultKind: VolumeServerInfo.Kind = 'x-ray'): VolumeServerInfo.Kind {
if (!s) return defaultKind;
@@ -23,7 +25,53 @@ export function getStreamingMethod(s?: Structure, defaultKind: VolumeServerInfo.
return 'x-ray';
}
export async function getEmdbIdAndContourLevel(plugin: PluginContext, taskCtx: RuntimeContext, pdbId: string) {
export function getId(s?: Structure): string {
if (!s) return ''
const model = s.models[0]
if (model.sourceData.kind !== 'mmCIF') return ''
const d = model.sourceData.data
for (let i = 0, il = d.pdbx_database_related._rowCount; i < il; ++i) {
if (d.pdbx_database_related.db_name.value(i).toUpperCase() === 'EMDB') {
return d.pdbx_database_related.db_id.value(i)
}
}
return s.models.length > 0 ? s.models[0].entryId : ''
}
export async function getContourLevel(provider: 'wwpdb' | 'pdbe', plugin: PluginContext, taskCtx: RuntimeContext, emdbId: string) {
switch (provider) {
case 'wwpdb': return getContourLevelWwpdb(plugin, taskCtx, emdbId)
case 'pdbe': return getContourLevelPdbe(plugin, taskCtx, emdbId)
}
}
export async function getContourLevelWwpdb(plugin: PluginContext, taskCtx: RuntimeContext, emdbId: string) {
// TODO: parametrize to a differnt URL? in plugin settings perhaps
const header = await plugin.fetch<XMLDocument>({ url: `https://ftp.wwpdb.org/pub/emdb/structures/${emdbId.toUpperCase()}/header/${emdbId.toLowerCase()}.xml`, type: 'xml' }).runInContext(taskCtx);
const map = getXMLNodeByName('map', header!.root!.children!)!
const contourLevel = parseFloat(getXMLNodeByName('contourLevel', map.children!)!.content!)
return contourLevel;
}
export async function getContourLevelPdbe(plugin: PluginContext, taskCtx: RuntimeContext, emdbId: string) {
emdbId = emdbId.toUpperCase()
// TODO: parametrize to a differnt URL? in plugin settings perhaps
const header = await plugin.fetch({ url: `https://www.ebi.ac.uk/pdbe/api/emdb/entry/map/${emdbId}`, type: 'json' }).runInContext(taskCtx);
const emdbEntry = header && header[emdbId];
let contourLevel: number | undefined = void 0;
if (emdbEntry && emdbEntry[0] && emdbEntry[0].map && emdbEntry[0].map.contour_level && emdbEntry[0].map.contour_level.value !== void 0) {
contourLevel = +emdbEntry[0].map.contour_level.value;
}
return contourLevel;
}
export async function getEmdbId(plugin: PluginContext, taskCtx: RuntimeContext, pdbId: string) {
// TODO: parametrize to a differnt URL? in plugin settings perhaps
const summary = await plugin.fetch({ url: `https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary/${pdbId}`, type: 'json' }).runInContext(taskCtx);
@@ -39,13 +87,5 @@ export async function getEmdbIdAndContourLevel(plugin: PluginContext, taskCtx: R
throw new Error(`No related EMDB entry found for '${pdbId}'.`);
}
// TODO: parametrize to a differnt URL? in plugin settings perhaps
const emdb = await plugin.fetch({ url: `https://www.ebi.ac.uk/pdbe/api/emdb/entry/map/${emdbId}`, type: 'json' }).runInContext(taskCtx);
const emdbEntry = emdb && emdb[emdbId];
let contour: number | undefined = void 0;
if (emdbEntry && emdbEntry[0] && emdbEntry[0].map && emdbEntry[0].map.contour_level && emdbEntry[0].map.contour_level.value !== void 0) {
contour = +emdbEntry[0].map.contour_level.value;
}
return { emdbId, contour };
return emdbId
}

View File

@@ -12,6 +12,8 @@ import { PluginStateObject as SO } from '../../state/objects';
import { getFormattedTime } from '../../../mol-util/date';
import { readFromFile } from '../../../mol-util/data-source';
import { download } from '../../../mol-util/download';
import { Structure } from '../../../mol-model/structure';
import { EmptyLoci, EveryLoci } from '../../../mol-model/loci';
export function registerDefault(ctx: PluginContext) {
SyncBehaviors(ctx);
@@ -99,24 +101,25 @@ function setVisibilityVisitor(t: StateTransform, tree: StateTree, ctx: { state:
ctx.state.updateCellState(t.ref, { isHidden: ctx.value });
}
// TODO make isHighlighted and isSelect part of StateObjectCell.State and subscribe from there???
// TODO select structures of subtree
// TODO should also work for volumes and shapes
export function Highlight(ctx: PluginContext) {
PluginCommands.State.Highlight.subscribe(ctx, ({ state, ref }) => {
// const cell = state.select(ref)[0]
// const repr = cell && SO.isRepresentation3D(cell.obj) ? cell.obj.data : undefined
// if (cell && cell.obj && cell.obj.type === PluginStateObject.Molecule.Structure.type) {
// ctx.behaviors.canvas3d.highlight.next({ current: { loci: Structure.Loci(cell.obj.data) } });
// } else if (repr) {
// ctx.behaviors.canvas3d.highlight.next({ current: { loci: EveryLoci, repr } });
// }
const cell = state.select(ref)[0];
if (!cell) return;
if (SO.Molecule.Structure.is(cell.obj)) {
ctx.interactivity.lociHighlights.highlightOnly({ loci: Structure.Loci(cell.obj.data) });
} else if (cell && SO.isRepresentation3D(cell.obj)) {
const loci = SO.Molecule.Structure.is(cell.obj.data.source) ? Structure.Loci(cell.obj.data.source.data) : EveryLoci
ctx.interactivity.lociHighlights.highlightOnly({ loci, repr: cell.obj.data.repr });
}
// TODO: highlight volumes and shapes?
// TODO: select structures of subtree?
});
}
export function ClearHighlight(ctx: PluginContext) {
PluginCommands.State.ClearHighlight.subscribe(ctx, ({ state, ref }) => {
// ctx.behaviors.canvas3d.highlight.next({ current: { loci: EmptyLoci } });
ctx.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
});
}

View File

@@ -12,6 +12,7 @@ import { PluginLayoutStateProps } from './layout';
import { StructureElement } from '../mol-model/structure';
import { PluginState } from './state';
import { Interactivity } from './util/interactivity';
import { PluginToast } from './state/toast';
export * from './command/base';
@@ -53,6 +54,10 @@ export const PluginCommands = {
Layout: {
Update: PluginCommand<{ state: Partial<PluginLayoutStateProps> }>()
},
Toast: {
Show: PluginCommand<PluginToast>(),
Hide: PluginCommand<{ key: string }>()
},
Camera: {
Reset: PluginCommand<{}>(),
SetSnapshot: PluginCommand<{ snapshot: Partial<Camera.Snapshot>, durationMs?: number }>(),

View File

@@ -40,6 +40,7 @@ import { Interactivity } from './util/interactivity';
import { StructureRepresentationHelper } from './util/structure-representation-helper';
import { StructureSelectionHelper } from './util/structure-selection-helper';
import { StructureOverpaintHelper } from './util/structure-overpaint-helper';
import { PluginToastManager } from './state/toast';
interface Log {
entries: List<LogEntry>
@@ -91,7 +92,7 @@ export class PluginContext {
isUpdating: this.ev.behavior<boolean>(false)
},
interaction: {
highlight: this.ev.behavior<Interactivity.HighlightEvent>({ current: Interactivity.Loci.Empty }),
hover: this.ev.behavior<Interactivity.HoverEvent>({ current: Interactivity.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0 }),
click: this.ev.behavior<Interactivity.ClickEvent>({ current: Interactivity.Loci.Empty, modifiers: ModifiersKeys.None, buttons: 0 })
},
labels: {
@@ -100,7 +101,8 @@ export class PluginContext {
} as const
readonly canvas3d: Canvas3D;
readonly layout: PluginLayout = new PluginLayout(this);
readonly layout = new PluginLayout(this);
readonly toasts = new PluginToastManager(this);
readonly interactivity: Interactivity;
readonly lociLabels: LociLabelManager;
@@ -135,7 +137,7 @@ export class PluginContext {
* Used to store application specific custom state which is then available
* to State Actions and similar constructs via the PluginContext.
*/
readonly customState: any = Object.create(null);
readonly customState: unknown = Object.create(null);
initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement) {
try {

View File

@@ -42,6 +42,7 @@ export const DefaultPluginSpec: PluginSpec = {
PluginSpec.Action(StateTransforms.Model.TrajectoryFromPDB),
PluginSpec.Action(StateTransforms.Model.StructureAssemblyFromModel),
PluginSpec.Action(StateTransforms.Model.StructureSymmetryFromModel),
PluginSpec.Action(StateTransforms.Model.StructureSymmetryMatesFromModel),
PluginSpec.Action(TransformStructureConformation),
PluginSpec.Action(StateTransforms.Model.StructureFromModel),
PluginSpec.Action(StateTransforms.Model.StructureFromTrajectory),
@@ -49,6 +50,7 @@ export const DefaultPluginSpec: PluginSpec = {
PluginSpec.Action(StateTransforms.Model.StructureSelectionFromScript),
PluginSpec.Action(StateTransforms.Representation.StructureRepresentation3D),
PluginSpec.Action(StateTransforms.Representation.StructureLabels3D),
PluginSpec.Action(StateTransforms.Representation.ModelUnitcell3D),
PluginSpec.Action(StateTransforms.Representation.ExplodeStructureRepresentation3D),
PluginSpec.Action(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D),
PluginSpec.Action(StateTransforms.Representation.OverpaintStructureRepresentation3DFromScript),
@@ -63,7 +65,7 @@ export const DefaultPluginSpec: PluginSpec = {
PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci),
PluginSpec.Behavior(PluginBehaviors.Representation.SelectLoci),
PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
PluginSpec.Behavior(PluginBehaviors.Camera.FocusLociOnSelect, { minRadius: 8, extraRadius: 4 }),
PluginSpec.Behavior(PluginBehaviors.Camera.FocusLoci),
// PluginSpec.Behavior(PluginBehaviors.Labels.SceneLabels),
PluginSpec.Behavior(PluginBehaviors.CustomProps.MolstarSecondaryStructure, { autoAttach: true }),
PluginSpec.Behavior(PluginBehaviors.CustomProps.PDBeStructureQualityReport, { autoAttach: true, showTooltip: true }),

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ParamDefinition as PD } from '../mol-util/param-definition';
@@ -39,7 +40,7 @@ interface RootState {
position: string | null,
overflow: string | null,
viewports: HTMLElement[],
zindex: string | null
zIndex: string | null
}
export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
@@ -48,7 +49,7 @@ export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
}
private updateProps(state: Partial<PluginLayoutStateProps>) {
let prevExpanded = !!this.state.isExpanded;
const prevExpanded = !!this.state.isExpanded;
this.updateState(state);
if (this.root && typeof state.isExpanded === 'boolean' && state.isExpanded !== prevExpanded) this.handleExpand();
@@ -76,15 +77,15 @@ export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
private handleExpand() {
try {
let body = document.getElementsByTagName('body')[0];
let head = document.getElementsByTagName('head')[0];
const body = document.getElementsByTagName('body')[0];
const head = document.getElementsByTagName('head')[0];
if (!body || !head || !this.root) return;
if (this.state.isExpanded) {
let children = head.children;
const children = head.children;
const viewports: HTMLElement[] = [];
let hasExp = false;
let viewports: HTMLElement[] = [];
for (let i = 0; i < children.length; i++) {
if (children[i] === this.expandedViewport) {
hasExp = true;
@@ -100,14 +101,14 @@ export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
if (!hasExp) head.appendChild(this.expandedViewport);
let s = body.style;
const s = body.style;
let doc = this.getScrollElement();
let scrollLeft = doc.scrollLeft;
let scrollTop = doc.scrollTop;
const doc = this.getScrollElement();
const scrollLeft = doc.scrollLeft;
const scrollTop = doc.scrollTop;
this.rootState = {
top: s.top, bottom: s.bottom, right: s.right, left: s.left, scrollTop, scrollLeft, position: s.position, overflow: s.overflow, viewports, zindex: this.root.style.zIndex,
top: s.top, bottom: s.bottom, right: s.right, left: s.left, scrollTop, scrollLeft, position: s.position, overflow: s.overflow, viewports, zIndex: this.root.style.zIndex,
width: s.width, height: s.height,
maxWidth: s.maxWidth, maxHeight: s.maxHeight,
margin: s.margin, marginLeft: s.marginLeft, marginRight: s.marginRight, marginTop: s.marginTop, marginBottom: s.marginBottom
@@ -133,7 +134,7 @@ export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
// TODO: setting this breaks viewport controls for some reason. Is there a fix?
// this.root.style.zIndex = '100000';
} else {
let children = head.children;
const children = head.children;
for (let i = 0; i < children.length; i++) {
if (children[i] === this.expandedViewport) {
head.removeChild(this.expandedViewport);
@@ -142,10 +143,12 @@ export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
}
if (this.rootState) {
let s = body.style, t = this.rootState;
const t = this.rootState;
for (let v of t.viewports) {
head.appendChild(v);
}
const s = body.style
s.top = t.top;
s.bottom = t.bottom;
s.left = t.left;
@@ -162,17 +165,20 @@ export class PluginLayout extends PluginComponent<PluginLayoutStateProps> {
s.marginBottom = t.marginBottom;
s.position = t.position;
s.overflow = t.overflow || 'fixed';
let doc = this.getScrollElement();
s.overflow = t.overflow || '';
const doc = this.getScrollElement();
doc.scrollTop = t.scrollTop;
doc.scrollLeft = t.scrollLeft;
this.rootState = void 0;
this.root.style.zIndex = t.zindex;
this.root.style.zIndex = t.zIndex;
}
}
} catch (e) {
this.context.log.error('Layout change error, you might have to reload the page.');
console.log('Layout change error, you might have to reload the page.', e);
const msg = 'Layout change error, you might have to reload the page.'
this.context.log.error(msg);
console.error(msg, e);
}
}

View File

@@ -16,29 +16,29 @@
font-family: "Helvetica Neue", "Source Sans Pro", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.42857143;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
@import 'variables';
// for bootstrap
$border-radius-base: 0;
$border-radius-large: 0;
$border-radius-small: 0;
@import 'bootstrap/bootstrap';
@import 'icons';
@import 'icons';
@import 'layout';
@import 'ui';
@import 'logo';
.msp-plugin-content {
color: $font-color;
}
background: $default-background;
}

View File

@@ -4,6 +4,12 @@
border: none;
-moz-box-sizing: border-box;
box-sizing: border-box;
&[disabled] {
background: $default-background;
opacity: 0.35;
}
}
.msp-btn, .msp-btn:active, .msp-btn-link:focus, .msp-btn:hover {
@@ -45,7 +51,7 @@
.msp-btn-link, .msp-btn-link:active, .msp-btn-link:focus {
color: $msp-btn-link-font-color;
text-decoration: none;
text-decoration: none;
}
.msp-btn-link:hover {
@@ -69,13 +75,13 @@
.msp-btn-#{$name}, .msp-btn-#{$name}:active, .msp-btn-#{$name}:focus {
color: $font;
background: $bg;
}
}
.msp-btn-#{$name}:hover {
color: $hover-font-color;
background: color-lower-contrast($bg, 2.5%);
}
.msp-btn-#{$name}[disabled], .msp-btn-#{$name}[disabled]:hover,
.msp-btn-#{$name}[disabled], .msp-btn-#{$name}[disabled]:hover,
.msp-btn-#{$name}[disabled]:active, .msp-btn-#{$name}[disabled]:focus {
color: color-lower-contrast($font, 1%);
}
@@ -98,33 +104,33 @@
line-height: $row-height;
}
.msp-form-control {
.msp-form-control {
width: 100%;
background: $msp-form-control-background;
color: $font-color;
border: none; // !important;
padding: 0 $control-spacing;
line-height: $row-height - 2px;
padding: 0 $control-spacing;
line-height: $row-height - 2px;
height: $row-height;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
appearance: none;
box-shadow: none !important;
&:hover {
color: $hover-font-color;
background-color: color-increase-contrast($msp-form-control-background, 5%);
border: none;
outline-offset: -1px;
outline: 1px solid color-increase-contrast($msp-form-control-background, 20%);
outline: 1px solid color-increase-contrast($msp-form-control-background, 20%);
}
&:active, &:focus {
color: $font-color;
background-color: $msp-form-control-background;
border: none;
outline-offset: 0;
outline: none;
outline: none;
}
}
@@ -132,7 +138,7 @@
margin-top: 1px;
}
.msp-btn-commit {
.msp-btn-commit {
text-align: right;
padding-top: 0;
padding-bottom: 0;
@@ -142,22 +148,23 @@
border: none;
overflow: hidden;
font-weight: bold;
.msp-icon {
display: block-inline;
line-height: $row-height;
width: $row-height;
text-align: center;
}
}
}
select.msp-form-control {
select.msp-form-control {
background: none;
background-color: $msp-form-control-background;
background-size: 8px 12px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC);
background-repeat: no-repeat;
background-position: right $control-spacing top (($row-height - 12px) / 2);
background-position: right $control-spacing top (($row-height - 12px) / 2);
padding-right: ($row-height - 8px);
}
select.msp-form-control:-moz-focusring {

View File

@@ -259,7 +259,7 @@
}
}
.msp-conrol-group-expander {
.msp-control-group-expander {
display: block;
position: absolute;
line-height: $row-height;
@@ -302,5 +302,85 @@
> div {
line-height: $row-height;
text-align: center;
color: color-lower-contrast($font-color, 15%);
}
}
.msp-help span {
display: none;
}
.msp-help:hover span {
display: inline-block;
background: linear-gradient($default-background, change-color($default-background, $alpha: 0.8));
}
.msp-help-text {
height: auto !important;
> div {
padding: ($control-spacing / 2) $control-spacing;
text-align: left;
color: color-lower-contrast($font-color, 15%);
}
}
.msp-help-description {
font-style: italic;
}
.msp-help-legend {
padding-top: $control-spacing;
}
.msp-scale-legend {
> div {
width: 100%;
height: $control-spacing * 3;
> span {
padding: $control-spacing / 2;
color: white;
font-weight: bold;
background-color: rgba(0, 0, 0, 0.2);
}
}
}
.msp-table-legend {
> div {
// min-width: 60px;
margin-right: $control-spacing / 2;
display: inline-flex;
.msp-table-legend-color {
width: $control-spacing * 3;
height: $control-spacing * 2;
}
.msp-table-legend-text {
margin: 0 ($control-spacing / 2);
}
}
}
.msp-image-preview {
position: relative;
background: $default-background;
margin-top: 1px;
display: flex;
justify-content: center;
> canvas {
max-height: 200px;
border-width: 0px 1px 0px 1px;
border-style: solid;
border-color: $border-color;
background-color: $default-background;
background-image: linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey),
linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey);
background-size: 20px 20px;
background-position: 0 0, 10px 10px;
}
}

View File

@@ -70,4 +70,33 @@
.msp-controls-section {
margin-bottom: $control-spacing;
}
.msp-combined-color-button {
border: 4px solid $msp-form-control-background !important;
margin: 0;
text-align: center;
padding-right: $control-spacing;
padding-left: $control-spacing;
&:hover {
border-color: color-increase-contrast($msp-form-control-background, 5%) !important;
border: none;
outline-offset: -1px !important;
outline: 1px solid color-increase-contrast($msp-form-control-background, 20%) !important;
}
}
.msp-combined-color-swatch {
width: 100%;
display: grid;
grid-gap: 1px;
grid-template-columns: repeat(6, auto);
.msp-btn {
&:hover {
outline-offset: -1px !important;
outline: 1px solid color-increase-contrast($msp-form-control-background, 20%) !important;
}
}
}

View File

@@ -22,6 +22,10 @@
font-size: 90%;
}
.msp-sequence-wrapper-non-empty {
font-family: "Courier New", monospace;
}
.msp-sequence-wrapper {
span {
cursor: pointer;

View File

@@ -20,7 +20,7 @@
&-rail {
position: absolute;
width: 100%;
background-color: $border-color;
background-color: $control-background;
height: 4px;
border-radius: 2px;
}
@@ -42,7 +42,7 @@
cursor: pointer;
border-radius: 50%;
background-color: $font-color;
border: 4px solid $border-color;
border: 4px solid $control-background;
&:hover {
background-color: $hover-font-color;

View File

@@ -5,6 +5,7 @@
.msp-section-header {
height: $row-height;
line-height: $row-height;
margin-top: $control-spacing;
margin-bottom: $control-spacing;
text-align: right;
padding: 0 $control-spacing;
@@ -62,6 +63,7 @@
text-align-last: center;
background: none !important;
padding: 0 $control-spacing;
> option[value = _] {
display: none;
@@ -223,13 +225,22 @@
float: left;
margin-right: $control-spacing;
position: relative;
// background-color: $msp-form-control-background;
// background: $default-background;
> div:first-child {
position: relative;
display: inline-block;
> button {
position: relative;
}
}
.msp-animation-viewport-controls-select {
width: 290px;
position: absolute;
left: 0;
top: $row-height + $control-spacing;
margin-top: $control-spacing;
background: $control-background;
.msp-control-row:first-child {

View File

@@ -1,10 +1,9 @@
.msp-toast-container {
position: absolute;
max-width: 100%;
bottom: $control-spacing;
right: $control-spacing;
margin-left: $control-spacing;
position: relative;
// bottom: $control-spacing;
// right: $control-spacing;
// margin-left: $control-spacing;
z-index: 1001;
.msp-toast-entry {

View File

@@ -1,5 +1,5 @@
.msp-transformer {
.msp-transformer {
.msp-entity-badge {
position: absolute;
top: 0;
@@ -15,24 +15,24 @@
}
.msp-transformer-wrapper {
position: relative;
position: relative;
}
.msp-transformer-wrapper {
.msp-transformer-wrapper {
.msp-entity-badge {
left: 0;
top: 0;
}
&:first-child {
.msp-panel-description-content {
top: $row-height + 1;
}
}
&:not(:first-child) {
.msp-panel-description-content {
.msp-panel-description-content {
bottom: $row-height + 1;
}
}

Some files were not shown because too many files have changed in this diff Show More