Compare commits

...

171 Commits

Author SHA1 Message Date
Alexander Rose
ddc1119a80 0.2.6 2019-08-21 13:38:22 -07:00
Alexander Rose
0fe6774f04 fixed comparison and typo 2019-08-21 13:37:04 -07:00
Alexander Rose
2a71e44ae8 updated packages 2019-08-21 13:36:37 -07:00
Alexander Rose
1257f1ce85 label tweak 2019-08-21 12:17:05 -07:00
Alexander Rose
07cd9f4b16 css fix 2019-08-21 12:09:19 -07:00
Alexander Rose
ed5ff1c9ce slider ui tweaks 2019-08-21 10:13:42 -07:00
Alexander Rose
7ffc2db76e fix trackball controls dropping inputs 2019-08-21 09:42:30 -07:00
Alexander Rose
03067ca6d6 wip, structure tools quality option 2019-08-20 16:39:32 -07:00
Alexander Rose
a69f1337d7 color refactoring 2019-08-20 12:51:27 -07:00
Alexander Rose
5e83c3350a added Structure.root 2019-08-20 12:51:16 -07:00
Alexander Rose
f051d2d01e use model.entry in volume-streaming 2019-08-20 12:48:59 -07:00
Alexander Rose
257340283b wip, color list refactoring 2019-08-20 09:53:34 -07:00
Alexander Rose
e769d77ec8 wip, color theme refactoring 2019-08-19 18:50:46 -07:00
Alexander Rose
1923535918 limit max number of generated colors 2019-08-19 12:44:34 -07:00
Alexander Rose
dd3fc5620b label fix 2019-08-19 12:43:52 -07:00
Alexander Rose
7165258431 sequence widget fixes 2019-08-19 12:43:31 -07:00
Alexander Rose
df9b367e0b tweaked structure labeling 2019-08-19 12:43:08 -07:00
Alexander Rose
ed1ae71f71 optimized repr loci iteration, marking and extendToWholeChains 2019-08-18 13:01:31 -07:00
Alexander Rose
1c58bca454 fix wrong props object in Structure creation 2019-08-18 12:58:01 -07:00
Alexander Rose
c01be0644e improved handling of polymer trace termini 2019-08-16 20:01:20 -07:00
Alexander Rose
66b9f6104c improved low-poly geo (cylinder, sheet, tube, ribbon) 2019-08-16 17:14:57 -07:00
Alexander Rose
af4d2c4003 Merge branch 'master' of https://github.com/molstar/molstar 2019-08-15 17:18:02 -07:00
Alexander Rose
3b1a2f19a4 wip, structure tools, repr presets 2019-08-15 17:15:50 -07:00
Alexander Rose
6b874786a8 only extract CrossLinkRestraints when needed 2019-08-15 16:07:58 -07:00
Alexander Rose
f9d2560468 fixed symmetry operator when NCS operators are present 2019-08-15 16:07:18 -07:00
David Sehnal
961034584a model-server: config can be (partially) provided as a separate JSON file 2019-08-15 17:22:23 +02:00
David Sehnal
f6d11a59a3 model-server: wip config refactoring 2019-08-15 17:11:55 +02:00
Alexander Rose
53ee758378 wip, sequence widget 2019-08-14 17:21:53 -07:00
Alexander Rose
c80c630810 param control tweaks 2019-08-14 17:21:40 -07:00
Alexander Rose
13cd6e82ba added StructureFromTrajectory, improved multi-model structures 2019-08-14 17:20:40 -07:00
David Sehnal
f2966032d9 model-server: rest api, swagger ui, response schemas, bug fixes 2019-08-14 15:04:07 +02:00
David Sehnal
76503b52f5 wip model-server 2019-08-14 14:21:13 +02:00
Alexander Rose
aa24be8e9b type fixes 2019-08-13 16:44:19 -07:00
Alexander Rose
5d7bb894d4 wip, structure tools 2019-08-13 16:18:44 -07:00
Alexander Rose
fcf559fa6b toggle hydrogens in structure view tools 2019-08-13 15:55:32 -07:00
Alexander Rose
27963b5aed added option to ignore hydrogens in structure representations 2019-08-13 14:54:38 -07:00
Alexander Rose
40d539c4aa viewport ui improvements: mouse handling, config 2019-08-13 11:42:42 -07:00
David Sehnal
d1433aaf7b mol-state: added StateTree.subtreeHasRef 2019-08-13 17:19:36 +02:00
Alexander Rose
8f4bf9a314 added outsideControls to PluginLayoutStateParams 2019-08-12 17:30:27 -07:00
Alexander Rose
cca0f407f6 improved StructureRepresentationHelper 2019-08-12 16:47:09 -07:00
Alexander Rose
1e9e41754a added isApplicable support to representation provider & registry 2019-08-12 16:46:48 -07:00
David Sehnal
38dc2d6e26 mol-script: macro support 2019-08-12 16:00:53 +02:00
David Sehnal
ea46b1c8c8 mol-script: bond* => link*; make link tests "covalent" by default; wip link tests 2019-08-12 15:29:50 +02:00
Alexander Rose
5846d7c4b4 wip, structure view tools 2019-08-09 18:40:07 -07:00
Alexander Rose
214176ce2e handle incompatibility of StructureElement.Query and Structure 2019-08-09 18:18:32 -07:00
Alexander Rose
ee4cb214e1 updated cif schemas 2019-08-09 18:16:58 -07:00
Alexander Rose
01cca9e8a6 use .componentDidUpdate in SliderBase 2019-08-09 11:08:53 -07:00
Alexander Rose
7a23493e19 package updates 2019-08-09 10:26:41 -07:00
Alexander Rose
e109f069a8 wip, structure ui tools 2019-08-08 17:46:23 -07:00
Alexander Rose
82e667e402 added chemCompType and other mol-script improvements 2019-08-08 17:16:47 -07:00
Alexander Rose
c28feb2d1c added objectPrimitive to unit and mol-script 2019-08-08 13:56:21 -07:00
Alexander Rose
8447a2d4f2 add missing alias for cbrt 2019-08-08 13:54:20 -07:00
Alexander Rose
f9ebf1c399 better renderer defaults (occlusian radius, cmaera dist clipping) 2019-08-08 13:53:49 -07:00
Alexander Rose
286b27720a StructureElement fixes 2019-08-07 17:04:10 -07:00
Alexander Rose
dc0e54c275 Merge branch 'master' of https://github.com/molstar/molstar 2019-08-07 08:37:26 -07:00
David Sehnal
b710291d5e mol-plugin: animation controls tweak 2019-08-07 12:15:36 +02:00
David Sehnal
d84d5f38f5 mol-model: implemented includeConnected query 2019-08-07 12:10:24 +02:00
Alexander Rose
9bea13438f improved picking pass 2019-08-06 12:50:45 -07:00
Alexander Rose
2fc28f6005 improved StructureElement.Query 2019-08-06 11:16:50 -07:00
Alexander Rose
bb07d6ec56 imroved download helper and state download 2019-08-06 10:02:49 -07:00
Alexander Rose
3cdfd04048 improved element loci remap, added SortedArray.indexOfInRange 2019-08-06 08:38:36 -07:00
Alexander Rose
2120a258f9 remap loci per repr not per visual 2019-08-06 08:30:21 -07:00
Alexander Rose
2b5e49d215 use LociStructureSelection in plugin structure tools 2019-08-05 15:09:16 -07:00
Alexander Rose
b88bf9bdf2 LociStructureSelection based on StructureElement.Query 2019-08-05 15:08:43 -07:00
Alexander Rose
df0f15d132 added StructureElement.Query for Loci serialization 2019-08-05 15:08:36 -07:00
Alexander Rose
a6319bfb3d tweakes to avoid cyclic import issues 2019-08-05 12:32:01 -07:00
Alexander Rose
d6278cb3eb sorted-ranges, .areEqual and .forEach 2019-08-05 12:30:53 -07:00
Alexander Rose
560da38687 fixed exceptBy and structureSubtract 2019-08-03 14:55:55 -07:00
Alexander Rose
83ba9d8776 avoid .apply for long arg lists 2019-08-02 21:53:59 -07:00
Alexander Rose
ee776e6e3e wip, structure tools 2019-08-02 17:18:07 -07:00
Alexander Rose
3e52496b4e sequence widget, chain -> unit 2019-08-02 17:17:21 -07:00
Alexander Rose
2659b96008 added math.cbrt to mol-script 2019-08-02 17:16:47 -07:00
Alexander Rose
a8be84701b improved screendoor transparency with multi-sample 2019-08-02 10:23:16 -07:00
Alexander Rose
0c79aa1709 show number of selected elements and structures 2019-08-01 16:09:33 -07:00
Alexander Rose
e57a19857f improved pdb reader, entity 2019-08-01 12:36:10 -07:00
Alexander Rose
469dd05cd9 added Column.ofIntTokens, .ofFloatTokens, .ofStringTokens 2019-08-01 12:34:47 -07:00
Alexander Rose
fb72db61bd code simplification 2019-08-01 12:34:01 -07:00
Alexander Rose
c285e30ee0 improved gro format reading 2019-08-01 10:09:20 -07:00
Alexander Rose
789a327322 improved large chain partitioning 2019-08-01 10:06:50 -07:00
Alexander Rose
b8d2021599 partition very large atomic chains per residue 2019-07-30 16:28:00 -07:00
Alexander Rose
36eae744af ensure that "single atom chains" units have same entity id 2019-07-30 16:24:54 -07:00
Alexander Rose
00df6ae52a don't show sequences > 10000 in widget 2019-07-30 16:23:05 -07:00
Alexander Rose
023b65572e viewer, option to load files without adding visuals 2019-07-30 16:22:34 -07:00
Alexander Rose
904e9b869c wip, cellpack loader improvements 2019-07-30 12:03:26 -07:00
Alexander Rose
6f204b960d Merge branch 'master' into meso 2019-07-30 09:12:08 -07:00
Alexander Rose
6217a51fa5 optimized toScriptExpression 2019-07-29 11:32:02 -07:00
Alexander Rose
f1edb05c5c fixed sorted-ranges spec 2019-07-29 10:03:54 -07:00
Alexander Rose
55bd27bb97 wip, structure selection tools 2019-07-26 17:32:51 -07:00
Alexander Rose
e2c9b601a6 wip, structure tools refactoring 2019-07-26 16:36:27 -07:00
Alexander Rose
238191660e Fixed StructureElement.Loci.toScriptExpression 2019-07-26 16:12:03 -07:00
Alexander Rose
0b175acc25 wip, structure tool controls 2019-07-26 15:50:26 -07:00
Alexander Rose
02865cbece trace-iterator to work with unit subsets 2019-07-26 12:44:40 -07:00
Alexander Rose
a3e14bf579 improved sorted-ranges and docs 2019-07-26 12:44:08 -07:00
Alexander Rose
1d502cbb54 loci remapping 2019-07-25 13:49:04 -07:00
Alexander Rose
4894b110b9 fixes, SortedRanges and AtomicPolymerTraceIterator 2019-07-25 10:58:42 -07:00
Alexander Rose
cefd0440a0 docs 2019-07-25 10:57:26 -07:00
David Sehnal
296bfb343e mol-plugin: updated 'select animation' icon 2019-07-24 07:45:41 +02:00
David Sehnal
bc91f0d3ff Support multiple models in StructureElement.Loci.toScriptExpression 2019-07-24 07:35:03 +02:00
David Sehnal
aaa8215a6d Fixed StructureElement.Loci.toScriptExpression 2019-07-24 07:11:38 +02:00
Alexander Rose
bf0b37895d handle modified base rings (DP, DZ) 2019-07-23 15:51:17 -07:00
Alexander Rose
0810ed411d nucleotide cartoon, detect Purin/Pyrimidin from geometry 2019-07-23 15:16:22 -07:00
Alexander Rose
ff8fec542c wip, overpaint controls, overpaint clearing 2019-07-23 12:04:02 -07:00
Alexander Rose
8fb7308572 wip, OverpaintControls 2019-07-22 17:35:17 -07:00
Alexander Rose
c9b7049532 fixed ExplodeStructureRepresentation3D 2019-07-22 17:34:46 -07:00
Alexander Rose
da71332de1 add 'sel.atom.all' macro 2019-07-22 17:34:30 -07:00
Alexander Rose
a0de8dd9f9 fixed seq widget update on object change 2019-07-22 12:14:03 -07:00
Alexander Rose
e226f27041 fixed wrong property use in seq widget 2019-07-22 12:04:27 -07:00
Alexander Rose
804a04d9f8 fixed coarse trace-iterator direction vectors 2019-07-22 11:52:00 -07:00
Alexander Rose
3be06bb3b6 wip, cellpack loader 2019-07-21 18:28:45 -07:00
Alexander Rose
c7b618c246 lighting demo 2019-07-21 18:13:10 -07:00
Alexander Rose
55d990962f better entity handling for pdb files 2019-07-20 15:58:22 -07:00
Alexander Rose
53e0a36539 improved readme deploy section 2019-07-19 18:38:13 -07:00
Alexander Rose
ed7a5219bf 0.2.5 2019-07-19 17:43:32 -07:00
Alexander Rose
8b49ccdc08 tweaked bottom and top height 2019-07-19 17:41:40 -07:00
Alexander Rose
04fd3ade5f improved canvas3d.resetCamera 2019-07-19 14:52:20 -07:00
Alexander Rose
48985cd49d improved scene commit handling for canvas3d and debug-helper 2019-07-19 14:40:45 -07:00
Alexander Rose
dd0707a8a5 fix renderer.spec test 2019-07-19 10:12:22 -07:00
Alexander Rose
b41ebcbbc8 Merge branch 'master' of https://github.com/molstar/molstar
# Conflicts:
#	src/mol-canvas3d/camera.ts
#	src/mol-canvas3d/canvas3d.ts
2019-07-19 09:50:03 -07:00
Alexander Rose
991d2e3a57 async gl repr object handling 2019-07-19 09:46:27 -07:00
Alexander Rose
7f4ac6782f camera tweaks 2019-07-19 09:45:26 -07:00
Alexander Rose
7e7e30a82e fix: removed useless z-sort 2019-07-19 09:44:11 -07:00
Alexander Rose
08e92f12d3 improved MolecularSurface calc: projectToriiRange 2019-07-19 09:42:09 -07:00
Alexander Rose
d713ea6a76 ModelIndex color theme 2019-07-19 09:41:06 -07:00
Alexander Rose
0924020f24 updated packages 2019-07-19 09:11:53 -07:00
David Sehnal
9f10af3ba6 proteopedia-wrapper: clipping commands; mol-canvas3d: camera.getFocus optional direction 2019-06-26 13:41:53 +02:00
Alexander Rose
f754026cc5 better entity placeholder 2019-06-22 12:26:28 -07:00
Alexander Rose
321d98f4c1 hetero sequence wrapper 2019-06-22 11:35:11 -07:00
Alexander Rose
58a49a8512 fix handling of structure loci selection 2019-06-22 08:12:07 -07:00
Alexander Rose
98bb9575b6 simplified polymer sequence wrapper 2019-06-22 07:45:04 -07:00
Alexander Rose
f10a135dea sequence view, better handling of missing residues, full structure loci 2019-06-22 07:34:20 -07:00
Alexander Rose
f300e524d1 updated packages 2019-06-21 17:36:38 -07:00
Alexander Rose
87028c0a0b 0.2.4 2019-06-21 17:21:59 -07:00
Alexander Rose
8ea23e6965 Merge branch 'master' into seq 2019-06-21 17:19:26 -07:00
Alexander Rose
f9d8942814 basic support for missing residues 2019-06-21 16:55:28 -07:00
Alexander Rose
b40df2f1e3 sequence, on state tree change improvments 2019-06-21 16:55:12 -07:00
Alexander Rose
884cb0d9a4 sequence widget refactoring 2019-06-21 12:54:22 -07:00
Alexander Rose
ef1ccd4286 cif schema updates 2019-06-21 12:44:28 -07:00
Alexander Rose
898abda373 sequence & interactivity tweaks 2019-06-21 11:06:49 -07:00
Alexander Rose
e42c664a8c fixes: StructureElement.Loci.union, Structure.parent 2019-06-21 10:56:06 -07:00
Alexander Rose
987bf47827 save interactivity props in state 2019-06-21 09:29:03 -07:00
Alexander Rose
6201dd1d74 improved xtal symmetry support for props & sequence 2019-06-21 09:11:38 -07:00
David Sehnal
5ed17ce4e5 proteopedia-wrapper: evolutionary coloring on current representation 2019-06-21 12:47:14 +02:00
Alexander Rose
e301eca9c2 wip, sequence view, select options 2019-06-20 15:50:31 -07:00
Alexander Rose
8a4ef015a2 added StructureElement.set 2019-06-20 15:42:45 -07:00
Alexander Rose
67f3f3fdbb improved AtomsQueryParams.unitTest and StructureProperties.unit 2019-06-20 15:42:23 -07:00
Alexander Rose
adc5b559cd various tweaks 2019-06-20 15:35:55 -07:00
Alexander Rose
bbaa637118 wip, sequence selector 2019-06-19 17:45:50 -07:00
Alexander Rose
a3094b4d19 wip, per-chain sequence widget 2019-06-19 17:03:20 -07:00
Alexander Rose
c3f937e113 sequence widget refactoring 2019-06-19 14:17:49 -07:00
Alexander Rose
04df327939 renamed lociExpansion to granularity 2019-06-19 12:30:19 -07:00
Alexander Rose
b1a0f46ade improved link loci handling for interactivity 2019-06-19 12:18:53 -07:00
Alexander Rose
389e249862 use PurePluginUIComponent for Residue 2019-06-19 11:48:36 -07:00
Alexander Rose
cfcf9f6818 wip, plugin interactivity 2019-06-18 17:28:57 -07:00
Alexander Rose
bcb8419f37 init repr3d and seq view marker with global selection 2019-06-18 14:31:22 -07:00
Alexander Rose
7d24bcf1dc tweaked resolution quality settings 2019-06-18 14:24:47 -07:00
Alexander Rose
8d0f7a2dc7 factored-out marker-action 2019-06-18 11:48:59 -07:00
Alexander Rose
a7cb7beaa8 Merge branch 'master' into seq 2019-06-18 09:04:33 -07:00
David Sehnal
3c9b82dc04 proteopedia-plugin: Load asym unit fix 2019-06-18 15:42:29 +02:00
David Sehnal
9dba6d5371 proteopedia-wrapper: tweak 2019-06-18 13:53:04 +02:00
David Sehnal
a65bba0969 proteopedia-wrapper: better HET group focusing 2019-06-18 13:39:23 +02:00
David Sehnal
175e009152 proteopedia-wrapper: fix assembly loading 2019-06-18 13:01:38 +02:00
Alexander Rose
f6b2c0b2ba wip, sequence view 2019-06-17 16:44:38 -07:00
Alexander Rose
ea419c68ae removed old unused ui code 2019-06-17 15:41:03 -07:00
Alexander Rose
40cf348d40 simple (entity) sequence view 2019-06-17 15:40:51 -07:00
Alexander Rose
93ea759a71 StateTreeSpine.current tweaks 2019-06-17 15:39:12 -07:00
Alexander Rose
3e50377eb8 use as const to quickly make class props readonly 2019-06-17 15:29:37 -07:00
Alexander Rose
3fbd1f8dc4 StructureElementSelectionManager.tryGetRange in both directions 2019-06-17 15:27:22 -07:00
Alexander Rose
f03ce68513 plugin interaction helpers refactoring 2019-06-17 14:37:24 -07:00
Alexander Rose
50e2d542df add getModifiers input helper 2019-06-17 14:33:32 -07:00
Alexander Rose
e53e739d18 ignore non StructureElement loci in StructureElementSelectionManager 2019-06-17 14:32:47 -07:00
Alexander Rose
dfb7f7811f support coarse elements in atomGroups generator 2019-06-14 18:59:37 -07:00
269 changed files with 8955 additions and 3668 deletions

View File

@@ -85,7 +85,7 @@ and navigate to `build/viewer`
Install CIFTools `npm install ciftools -g`
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF
cifschema -mip ../../../../mol-data-o src/mol-io/reader/cif/schema/ccd.ts -p CCD
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD
**GraphQL schemas**
@@ -142,6 +142,8 @@ To get syntax highlighting for shader and graphql files add the following to Vis
npm publish
## Deploy
npm run test
NODE_ENV=production npm run build
node ./scripts/deploy.js # currently updates the viewer on molstar.org/viewer
## Contributing

2841
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.2.3",
"version": "0.2.6",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -62,53 +62,53 @@
"license": "MIT",
"devDependencies": {
"benchmark": "^2.1.4",
"circular-dependency-plugin": "^5.0.2",
"concurrently": "^4.1.0",
"circular-dependency-plugin": "^5.2.0",
"concurrently": "^4.1.2",
"cpx": "^1.5.0",
"css-loader": "^3.0.0",
"css-loader": "^3.2.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^4.0.0",
"fs-extra": "^8.0.1",
"file-loader": "^4.2.0",
"fs-extra": "^8.1.0",
"graphql-code-generator": "^0.18.2",
"graphql-codegen-time": "^0.18.2",
"graphql-codegen-typescript-template": "^0.18.2",
"jest": "^24.8.0",
"jest": "^24.9.0",
"jest-raw-loader": "^1.0.1",
"mini-css-extract-plugin": "^0.7.0",
"mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.12.0",
"raw-loader": "^3.0.0",
"raw-loader": "^3.1.0",
"resolve-url-loader": "^3.1.0",
"sass-loader": "^7.1.0",
"simple-git": "^1.113.0",
"style-loader": "^0.23.1",
"sass-loader": "^7.3.1",
"simple-git": "^1.124.0",
"style-loader": "^1.0.0",
"ts-jest": "^24.0.2",
"tslint": "^5.17.0",
"typescript": "^3.5.1",
"webpack": "^4.33.0",
"webpack-cli": "^3.3.4"
"tslint": "^5.19.0",
"typescript": "^3.5.3",
"webpack": "^4.39.2",
"webpack-cli": "^3.3.7"
},
"dependencies": {
"@types/argparse": "^1.0.36",
"@types/benchmark": "^1.0.31",
"@types/compression": "0.0.36",
"@types/express": "^4.17.0",
"@types/jest": "^24.0.13",
"@types/node": "^12.0.8",
"@types/node-fetch": "^2.3.5",
"@types/react": "^16.8.19",
"@types/react-dom": "^16.8.4",
"@types/swagger-ui-dist": "3.0.0",
"@types/webgl2": "0.0.4",
"@types/compression": "1.0.1",
"@types/express": "^4.17.1",
"@types/jest": "^24.0.18",
"@types/node": "^12.7.2",
"@types/node-fetch": "^2.5.0",
"@types/react": "^16.9.2",
"@types/react-dom": "^16.9.0",
"@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.3.1",
"graphql": "^14.4.2",
"immutable": "^3.8.2",
"node-fetch": "^2.6.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"rxjs": "^6.5.2",
"swagger-ui-dist": "^3.22.3",
"swagger-ui-dist": "^3.23.5",
"util.promisify": "^1.0.0",
"xhr2": "^0.2.0"
}

View File

@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* Lighting Demo</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
position: absolute;
left: 160px;
top: 100px;
width: 600px;
height: 600px;
border: 1px solid #ccc;
}
#controls {
position: absolute;
width: 150px;
top: 100px;
left: 780px;
}
#controls > button {
display: block;
width: 100%;
text-align: left;
margin: 5px 0px;
}
#controls > input, #controls > select {
width: 100%;
display: block;
}
</style>
<link rel="stylesheet" type="text/css" href="app.css" />
<script type="text/javascript" src="./index.js"></script>
</head>
<body>
<div id='controls'></div>
<div id="app"></div>
<script>
LightingDemo.init('app')
LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' })
addHeader('Example PDB IDs');
addControl('1M07', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' }));
addControl('6HY0', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6HY0.cif', assemblyId: '1' }));
addControl('6QVK', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6QVK.cif', assemblyId: '1' }));
addControl('1RB8', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1RB8.cif', assemblyId: '1' }));
addSeparator()
addHeader('Lighting Presets');
addControl('Illustrative', () => LightingDemo.setPreset('illustrative'));
addControl('Standard', () => LightingDemo.setPreset('standard'));
addControl('Ambient Occlusion', () => LightingDemo.setPreset('occlusion'));
////////////////////////////////////////////////////////
function $(id) { return document.getElementById(id); }
function addControl(label, action) {
var btn = document.createElement('button');
btn.onclick = action;
btn.innerText = label;
$('controls').appendChild(btn);
}
function addSeparator() {
var hr = document.createElement('br');
$('controls').appendChild(hr);
}
function addHeader(header) {
var h = document.createElement('h3');
h.innerText = header;
$('controls').appendChild(h);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,166 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { createPlugin, DefaultPluginSpec } from '../../../mol-plugin';
import './index.html'
import { PluginContext } from '../../../mol-plugin/context';
import { PluginCommands } from '../../../mol-plugin/command';
import { StateTransforms } from '../../../mol-plugin/state/transforms';
import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation';
import { PluginStateObject as PSO } from '../../../mol-plugin/state/objects';
import { StateBuilder } from '../../../mol-state';
import { Canvas3DProps } from '../../../mol-canvas3d/canvas3d';
require('mol-plugin/skin/light.scss')
type SupportedFormats = 'cif' | 'pdb'
type LoadParams = { url: string, format?: SupportedFormats, assemblyId?: string }
const Canvas3DPresets = {
illustrative: {
multiSample: {
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
},
postprocessing: {
occlusionEnable: true,
occlusionBias: 0.8,
occlusionKernelSize: 6,
outlineEnable: true,
},
renderer: {
ambientIntensity: 1,
lightIntensity: 0,
}
},
occlusion: {
multiSample: {
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
},
postprocessing: {
occlusionEnable: true,
occlusionBias: 0.8,
occlusionKernelSize: 6,
outlineEnable: false,
},
renderer: {
ambientIntensity: 0.4,
lightIntensity: 0.6,
}
},
standard: {
multiSample: {
mode: 'off' as Canvas3DProps['multiSample']['mode']
},
postprocessing: {
occlusionEnable: false,
outlineEnable: false,
},
renderer: {
ambientIntensity: 0.4,
lightIntensity: 0.6,
}
}
}
type Canvas3DPreset = keyof typeof Canvas3DPresets
function getPreset(preset: Canvas3DPreset) {
switch (preset) {
case 'illustrative': return Canvas3DPresets['illustrative']
case 'standard': return Canvas3DPresets['standard']
case 'occlusion': return Canvas3DPresets['occlusion']
}
}
class LightingDemo {
plugin: PluginContext;
init(target: string | HTMLElement) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec,
layout: {
initial: {
isExpanded: false,
showControls: false
},
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
}
});
this.setPreset('illustrative');
}
setPreset(preset: Canvas3DPreset) {
const props = getPreset(preset)
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
...props,
multiSample: {
...this.plugin.canvas3d.props.multiSample,
...props.multiSample
},
renderer: {
...this.plugin.canvas3d.props.renderer,
...props.renderer
},
postprocessing: {
...this.plugin.canvas3d.props.postprocessing,
...props.postprocessing
},
}});
}
private download(b: StateBuilder.To<PSO.Root>, url: string) {
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
}
private parse(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) {
const parsed = format === 'cif'
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
return parsed
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' });
}
private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'spacefill', {}, 'illustrative'), { ref: 'seq-visual' });
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick'), { ref: 'het-visual' });
return visualRoot;
}
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
async load({ url, format = 'cif', assemblyId = '' }: LoadParams) {
let loadType: 'full' | 'update' = 'full';
const state = this.plugin.state.dataState;
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
loadType = 'full';
} else if (this.loadedParams.url === url) {
if (state.select('asm').length > 0) loadType = 'update';
}
let tree: StateBuilder.Root;
if (loadType === 'full') {
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
tree = state.build();
this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId));
} else {
tree = state.build();
tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
}
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
this.loadedParams = { url, format, assemblyId };
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
}
}
(window as any).LightingDemo = new LightingDemo();

View File

@@ -112,7 +112,7 @@ const state: State = {
function formatParams(def: QueryDefinition) {
const prms = Object.create(null);
for (const p of def.params) {
for (const p of def.jsonParams) {
prms[p.name] = p.exampleValues ? p.exampleValues[0] : void 0;
}
return JSON.stringify(prms, void 0, 2);

View File

@@ -19,7 +19,7 @@ function paramInfo(param: PD.Any, offset: number): string {
case 'conditioned': return getParams(param.conditionParams, offset);
case 'multi-select': return `Array of ${oToS(param.options)}`;
case 'color': return 'Color as 0xrrggbb';
case 'color-scale': return `One of ${oToS(param.options)}`;
case 'color-list': return `One of ${oToS(param.options)}`;
case 'vec3': return `3D vector [x, y, z]`;
case 'file': return `JavaScript File Handle`;
case 'select': return `One of ${oToS(param.options)}`;

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 { Vec3, Quat } from '../../../../mol-math/linear-algebra';
export interface CellPack {
cell: Cell
packings: CellPacking[]
}
export interface CellPacking {
name: string,
location: 'surface' | 'interior' | 'cytoplasme',
ingredients: Packing['ingredients']
}
//
export interface Cell {
recipe: Recipe
cytoplasme?: Packing
compartments?: { [key: string]: Compartment }
}
export interface Recipe {
setupfile: string
paths: [string, string][] // [name: string, path: string][]
version: string
name: string
}
export interface Compartment {
surface?: Packing
interior?: Packing
}
export interface Packing {
ingredients: { [key: string]: Ingredient }
}
export interface Ingredient {
source: IngredientSource
results: [Vec3, Quat][]
name: string
positions?: [Vec3[]] // why wrapped in an extra array?
radii?: [number[]] // why wrapped in an extra array?
nbCurve?: number
}
export interface IngredientSource {
pdb: string
transform: { center: boolean, translate?: Vec3 }
biomt?: boolean
}

View File

@@ -0,0 +1,238 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { StateAction } from '../../../../mol-state';
import { PluginContext } from '../../../../mol-plugin/context';
import { PluginStateObject as PSO } from '../../../../mol-plugin/state/objects';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { Ingredient, CellPacking } from './data';
import { getFromPdb, getFromCellPackDB } from './util';
import { Model, Structure, StructureSymmetry, StructureSelection, Queries, QueryContext } from '../../../../mol-model/structure';
import { trajectoryFromMmCIF } from '../../../../mol-model-formats/structure/mmcif';
import { trajectoryFromPDB } from '../../../../mol-model-formats/structure/pdb';
import { Mat4, Vec3, Quat } from '../../../../mol-math/linear-algebra';
import { SymmetryOperator } from '../../../../mol-math/geometry';
import { Task } from '../../../../mol-task';
import { StructureRepresentation3DHelpers } from '../../../../mol-plugin/state/transforms/representation';
import { StateTransforms } from '../../../../mol-plugin/state/transforms';
import { distinctColors } from '../../../../mol-util/color/distinct';
import { ModelIndexColorThemeProvider } from '../../../../mol-theme/color/model-index';
import { Hcl } from '../../../../mol-util/color/spaces/hcl';
import { ParseCellPack, StructureFromCellpack } from './state';
import { formatMolScript } from '../../../../mol-script/language/expression-formatter';
import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
function getCellPackModelUrl(fileName: string, baseUrl: string) {
return `${baseUrl}/cellPACK_database_1.1.0/results/${fileName}`
}
async function getModel(id: string, baseUrl: string) {
let model: Model;
if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
// return
const cif = await getFromPdb(id)
model = (await trajectoryFromMmCIF(cif).run())[0]
} else {
const pdb = await getFromCellPackDB(id, baseUrl)
model = (await trajectoryFromPDB(pdb).run())[0]
}
return model
}
async function getStructure(model: Model, props: { assembly?: string }) {
let structure = Structure.ofModel(model)
const { assembly } = props
if (assembly) {
structure = await StructureSymmetry.buildAssembly(structure, assembly).run()
}
const query = Queries.internal.atomicSequence()
const result = query(new QueryContext(structure))
structure = StructureSelection.unionStructure(result)
return structure
}
function getTransform(trans: Vec3, rot: Quat) {
const q: Quat = Quat.create(-rot[3], rot[0], rot[1], rot[2])
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q)
Mat4.transpose(m, m)
Mat4.scale(m, m, Vec3.create(-1.0, 1.0, -1.0))
Mat4.setTranslation(m, trans)
return m
}
function getTransforms(results: Ingredient['results']) {
return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1]))
}
function getAssembly(transforms: Mat4[], structure: Structure) {
const builder = Structure.Builder()
const { units } = structure;
for (let i = 0, il = transforms.length; i < il; ++i) {
const id = `${i + 1}`
const op = SymmetryOperator.create(id, transforms[i], { id, operList: [ id ] })
for (const unit of units) {
builder.addWithOperator(unit, op)
}
}
return builder.getStructure();
}
async function getIngredientStructure(ingredient: Ingredient, baseUrl: string) {
const { name, source, results, nbCurve } = ingredient
// TODO can these be added to the library?
if (name === 'HIV1_CAhex_0_1_0') return
if (name === 'HIV1_CAhexCyclophilA_0_1_0') return
if (name === 'iLDL') return
if (source.pdb === 'None') return
// TODO handle fibers
if (nbCurve) return
const model = await getModel(source.pdb || name, baseUrl)
if (!model) return
const structure = await getStructure(model, { assembly: source.biomt ? '1' : undefined })
const transforms = getTransforms(results)
const assembly = getAssembly(transforms, structure)
return assembly
}
export function createStructureFromCellPack(packing: CellPacking, baseUrl: string) {
return Task.create('Create Packing Structure', async ctx => {
const { ingredients, name } = packing
const structures: Structure[] = []
for (const iName in ingredients) {
if (ctx.shouldUpdate) ctx.update(iName)
const s = await getIngredientStructure(ingredients[iName], baseUrl)
if (s) structures.push(s)
}
const builder = Structure.Builder({ label: name })
let offsetInvariantId = 0
for (const s of structures) {
let maxInvariantId = 0
for (const u of s.units) {
const invariantId = u.invariantId + offsetInvariantId
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, invariantId)
}
offsetInvariantId += maxInvariantId
}
const s = builder.getStructure()
return s
})
}
export const LoadCellPackModel = StateAction.build({
display: { name: 'Load CellPack Model' },
params: {
id: PD.Select('influenza_model1.json', [
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
['BloodPlasma1.2.apr.json', 'BloodPlasma1.2'],
['BloodSerumfillResult.apr', 'BloodSerumfillResult'],
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
['influenza_model1.json', 'influenza_model1'],
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
['NM_Analysis_FigureC1.4.cpr.json', 'NM_Analysis_FigureC1.4']
]),
baseUrl: PD.Text('https://cdn.jsdelivr.net/gh/mesoscope/cellPACK_data@master/'),
preset: PD.Group({
traceOnly: PD.Boolean(false),
representation: PD.Select('spacefill', [
['spacefill', 'Spacefill'],
['gaussian-surface', 'Gaussian Surface'],
['point', 'Point'],
])
}, { isExpanded: true })
},
from: PSO.Root
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
const url = getCellPackModelUrl(params.id, params.baseUrl)
const root = state.build().toRoot();
const cellPackBuilder = root
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.id })
.apply(StateTransforms.Data.ParseJson)
.apply(ParseCellPack)
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(taskCtx)
const { packings } = cellPackObject.data
let tree = state.build().to(cellPackBuilder.ref);
const colors = distinctColors(packings.length)
for (let i = 0, il = packings.length; i < il; ++i) {
const hcl = Hcl.fromColor(Hcl(), colors[i])
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number]
const p = { packing: i, baseUrl: params.baseUrl }
const expression = params.preset.traceOnly
? MS.struct.generator.atomGroups({
'atom-test': MS.core.logic.or([
MS.core.rel.eq([MS.ammp('label_atom_id'), 'CA']),
MS.core.rel.eq([MS.ammp('label_atom_id'), 'P'])
])
})
: MS.struct.generator.all()
const query = { language: 'mol-script' as const, expression: formatMolScript(expression) }
tree.apply(StructureFromCellpack, p)
.apply(StateTransforms.Model.UserStructureSelection, { query })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.createParams(ctx, Structure.Empty, {
repr: getReprParams(ctx, params.preset),
color: [
ModelIndexColorThemeProvider,
(c, ctx) => {
return {
palette: {
name: 'generate',
params: {
hue, chroma: [30, 80], luminance: [15, 85],
clusteringStepCount: 50, minSampleCount: 800,
maxCount: 75
}
}
}
}
]
}))
}
await state.updateTree(tree).runInContext(taskCtx);
}));
function getReprParams(ctx: PluginContext, params: { representation: 'spacefill' | 'gaussian-surface' | 'point', traceOnly: boolean }) {
const { representation, traceOnly } = params
switch (representation) {
case 'spacefill':
return traceOnly
? [
ctx.structureRepresentation.registry.get('spacefill'),
() => ({ sizeFactor: 2 })
] as [any, any]
: ctx.structureRepresentation.registry.get('spacefill')
case 'gaussian-surface':
return [
ctx.structureRepresentation.registry.get('gaussian-surface'),
() => ({
quality: 'custom', resolution: 10, radiusOffset: 2,
alpha: 1.0, flatShaded: false, doubleSided: false,
})
] as [any, any]
case 'point':
return ctx.structureRepresentation.registry.get('point')
}
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin/state/objects';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { Structure } from '../../../../mol-model/structure';
import { Task } from '../../../../mol-task';
import { CellPack as _CellPack, Cell, CellPacking } from './data';
import { createStructureFromCellPack } from './model';
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
export { ParseCellPack }
type ParseCellPack = typeof ParseCellPack
const ParseCellPack = PluginStateTransform.BuiltIn({
name: 'parse-cellpack',
display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
from: PSO.Format.Json,
to: CellPack
})({
apply({ a }) {
return Task.create('Parse CellPack', async ctx => {
const cell = a.data as Cell
const packings: CellPacking[] = []
const { compartments, cytoplasme } = cell
if (compartments) {
for (const name in compartments) {
const { surface, interior } = compartments[name]
if (surface) packings.push({ name, location: 'surface', ingredients: surface.ingredients })
if (interior) packings.push({ name, location: 'interior', ingredients: interior.ingredients })
}
}
if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients })
return new CellPack({ cell, packings });
});
}
});
export { StructureFromCellpack }
type StructureFromCellpack = typeof ParseCellPack
const StructureFromCellpack = PluginStateTransform.BuiltIn({
name: 'structure-from-cellpack',
display: { name: 'Structure from CellPack', description: 'Create Structure from CellPack Packing' },
from: CellPack,
to: PSO.Molecule.Structure,
params: a => {
if (!a) {
return {
packing: PD.Numeric(0, {}, { description: 'Packing Index' }),
baseUrl: PD.Text('https://cdn.jsdelivr.net/gh/mesoscope/cellPACK_data@master/')
};
}
const options = a.data.packings.map((d, i) => [i, d.name] as [number, string])
return {
packing: PD.Select(0, options),
baseUrl: PD.Text('https://cdn.jsdelivr.net/gh/mesoscope/cellPACK_data@master/')
}
}
})({
apply({ a, params }) {
return Task.create('Structure from CellPack', async ctx => {
const packing = a.data.packings[params.packing]
const structure = await createStructureFromCellPack(packing, params.baseUrl).runInContext(ctx)
return new PSO.Molecule.Structure(structure, { label: packing.name })
});
}
});
export { AddStructure }
type AddStructure = typeof AddStructure
const AddStructure = PluginStateTransform.BuiltIn({
name: 'add-structure',
display: { name: 'Add Structure', description: 'Add existing molecular structure.' },
from: PSO.Root,
to: PSO.Molecule.Structure,
params: {
structure: PD.Value<Structure>(Structure.Empty),
label: PD.Text('Structure')
}
})({
apply({ a, params }) {
return Task.create('Build Structure', async ctx => {
const s = params.structure
const props = { label: params.label, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` };
return new PSO.Molecule.Structure(s, props);
})
}
});

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { CIF } from '../../../../mol-io/reader/cif'
import { parsePDB } from '../../../../mol-io/reader/pdb/parser';
async function parseCif(data: string|Uint8Array) {
const comp = CIF.parse(data);
const parsed = await comp.run();
if (parsed.isError) throw parsed;
return parsed.result;
}
async function parsePDBfile(data: string) {
const comp = parsePDB(data);
const parsed = await comp.run();
if (parsed.isError) throw parsed;
return parsed.result;
}
async function downloadCif(url: string, isBinary: boolean) {
const data = await fetch(url);
return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text());
}
async function downloadPDB(url: string) {
const data = await fetch(url);
return parsePDBfile(await data.text());
}
export async function getFromPdb(id: string) {
const parsed = await downloadCif(`https://files.rcsb.org/download/${id}.cif`, false);
return parsed.blocks[0];
}
function getCellPackDataUrl(id: string, baseUrl: string) {
const url = `${baseUrl}/cellPACK_database_1.1.0/other/${id}`
return url.endsWith('.pdb') ? url : `${url}.pdb`
}
export async function getFromCellPackDB(id: string, baseUrl: string) {
const parsed = await downloadPDB(getCellPackDataUrl(id, baseUrl));
return parsed;
}

View File

@@ -16,7 +16,7 @@ import { PluginStateSnapshotManager } from '../../../mol-plugin/state/snapshots'
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
import { Text } from '../../../mol-geo/geometry/text/text';
import { UUID } from '../../../mol-util';
import { ColorNames } from '../../../mol-util/color/tables';
import { ColorNames } from '../../../mol-util/color/names';
import { Camera } from '../../../mol-canvas3d/camera';
import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation';

View File

@@ -2,6 +2,7 @@
* 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 { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
@@ -10,6 +11,8 @@ import { PluginContext } from '../../mol-plugin/context';
import { PluginCommands } from '../../mol-plugin/command';
import { PluginSpec } from '../../mol-plugin/spec';
import { CreateJoleculeState } from './extensions/jolecule';
import { LoadCellPackModel } from './extensions/cellpack/model';
import { StructureFromCellpack } from './extensions/cellpack/state';
require('mol-plugin/skin/light.scss')
function getParam(name: string, regex: string): string {
@@ -21,7 +24,12 @@ const hideControls = getParam('hide-controls', `[^&]+`) === '1';
function init() {
const spec: PluginSpec = {
actions: [...DefaultPluginSpec.actions, PluginSpec.Action(CreateJoleculeState)],
actions: [
...DefaultPluginSpec.actions,
PluginSpec.Action(CreateJoleculeState),
PluginSpec.Action(LoadCellPackModel),
PluginSpec.Action(StructureFromCellpack),
],
behaviors: [...DefaultPluginSpec.behaviors],
animations: [...DefaultPluginSpec.animations || []],
customParamEditors: DefaultPluginSpec.customParamEditors,

View File

@@ -1,3 +1,12 @@
== v3.3 ==
* Camera Clipping.
== v3.2 ==
* Fixed assembly loading.
* Better HET group focus.
== v3.0 ==
* Fixed initial camera zoom.

View File

@@ -115,5 +115,6 @@ export enum StateElements {
Water = 'water',
WaterVisual = 'water-visual',
HetGroupFocus = 'het-group-focus'
HetGroupFocus = 'het-group-focus',
HetGroupFocusGroup = 'het-group-focus-group'
}

View File

@@ -130,7 +130,11 @@
addSeparator();
addHeader('Camera');
addControl('Toggle Spin', () => PluginWrapper.toggleSpin());
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] }));
addSeparator();
@@ -149,7 +153,8 @@
addSeparator();
addHeader('Misc');
addControl('Apply Evo Cons', () => PluginWrapper.coloring.evolutionaryConservation());
addControl('Apply Evo Cons Style', () => PluginWrapper.coloring.evolutionaryConservation());
addControl('Apply Evo Cons Colors', () => PluginWrapper.coloring.evolutionaryConservation({ sequence: true, het: false, keepStyle: true }));
addControl('Default Visuals', () => PluginWrapper.updateStyle());
addSeparator();

View File

@@ -26,9 +26,10 @@ import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { BuiltInStructureRepresentations } from '../../mol-repr/structure/registry';
import { BuiltInColorThemes } from '../../mol-theme/color';
import { BuiltInSizeThemes } from '../../mol-theme/size';
import { ColorNames } from '../../mol-util/color/tables';
import { ColorNames } from '../../mol-util/color/names';
import { InitVolumeStreaming, CreateVolumeStreamingInfo } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
import { ParamDefinition } from '../../mol-util/param-definition';
import { DefaultCanvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
// import { Vec3 } from 'mol-math/linear-algebra';
// import { ParamDefinition } from 'mol-util/param-definition';
// import { Text } from 'mol-geo/geometry/text/text';
@@ -36,7 +37,7 @@ require('../../mol-plugin/skin/light.scss')
class MolStarProteopediaWrapper {
static VERSION_MAJOR = 3;
static VERSION_MINOR = 1;
static VERSION_MINOR = 3;
private _ev = RxEventHelper.create();
@@ -81,7 +82,7 @@ class MolStarProteopediaWrapper {
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
}
private model(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) {
private model(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats) {
const parsed = format === 'cif'
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
@@ -190,7 +191,7 @@ class MolStarProteopediaWrapper {
}
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
async load({ url, format = 'cif', assemblyId = '', representationStyle }: LoadParams) {
async load({ url, format = 'cif', assemblyId = 'deposited', representationStyle }: LoadParams) {
let loadType: 'full' | 'update' = 'full';
const state = this.plugin.state.dataState;
@@ -203,14 +204,17 @@ class MolStarProteopediaWrapper {
if (loadType === 'full') {
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
const modelTree = this.model(this.download(state.build().toRoot(), url), format, assemblyId);
const modelTree = this.model(this.download(state.build().toRoot(), url), format);
await this.applyState(modelTree);
const info = await this.doInfo(true);
const structureTree = this.structure((assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId);
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
const structureTree = this.structure(asmId);
await this.applyState(structureTree);
} else {
const tree = state.build();
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
const info = await this.doInfo(true);
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: asmId }));
await this.applyState(tree);
}
@@ -238,6 +242,38 @@ class MolStarProteopediaWrapper {
if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { });
}
viewport = {
setSettings: (settings?: Canvas3DProps) => {
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
settings: settings || DefaultCanvas3DParams
});
}
};
camera = {
toggleSpin: () => this.toggleSpin(),
resetPosition: () => PluginCommands.Camera.Reset.dispatch(this.plugin, { }),
// setClip: (options?: { distance?: number, near?: number, far?: number }) => {
// if (!options) {
// PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
// settings: {
// cameraClipDistance: DefaultCanvas3DParams.cameraClipDistance,
// clip: DefaultCanvas3DParams.clip
// }
// });
// return;
// }
// options = options || { };
// const props = this.plugin.canvas3d.props;
// const clipNear = typeof options.near === 'undefined' ? props.clip[0] : options.near;
// const clipFar = typeof options.far === 'undefined' ? props.clip[1] : options.far;
// PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
// settings: { cameraClipDistance: options.distance, clip: [clipNear, clipFar] }
// });
// }
}
animate = {
modelIndex: {
maxFPS: 8,
@@ -250,18 +286,26 @@ class MolStarProteopediaWrapper {
}
coloring = {
evolutionaryConservation: async () => {
await this.updateStyle({ sequence: { kind: 'spacefill' } }, true);
evolutionaryConservation: async (params?: { sequence?: boolean, het?: boolean, keepStyle?: boolean }) => {
if (!params || !params.keepStyle) {
await this.updateStyle({ sequence: { kind: 'spacefill' } }, true);
}
const state = this.state;
// const visuals = state.selectQ(q => q.ofType(PluginStateObject.Molecule.Structure.Representation3D).filter(c => c.transform.transformer === StateTransforms.Representation.StructureRepresentation3D));
// for (const v of visuals) {
// }
const tree = state.build();
const colorTheme = { name: EvolutionaryConservation.Descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.Descriptor.name).defaultValues };
tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
// for (const v of visuals) {
// }
if (!params || !!params.sequence) {
tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
}
if (params && !!params.het) {
tree.to(StateElements.HetVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
}
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
}
@@ -295,30 +339,30 @@ class MolStarProteopediaWrapper {
PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update });
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
},
focusFirst: async (resn: string) => {
focusFirst: async (compId: string) => {
if (!this.state.transforms.has(StateElements.Assembly)) return;
await PluginCommands.Camera.Reset.dispatch(this.plugin, { });
// const asm = (this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure).data;
const update = this.state.build();
update.delete(StateElements.HetGroupFocus);
update.delete(StateElements.HetGroupFocusGroup);
const surroundings = MS.struct.modifier.includeSurroundings({
0: MS.struct.filter.first([
MS.struct.generator.atomGroups({
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), resn]),
'group-by': MS.struct.atomProperty.macromolecular.residueKey()
})
]),
radius: 5,
'as-whole-residues': true
});
const core = MS.struct.filter.first([
MS.struct.generator.atomGroups({
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), compId]),
'group-by': MS.core.str.concat([MS.struct.atomProperty.core.operatorName(), MS.struct.atomProperty.macromolecular.residueKey()])
})
]);
const surroundings = MS.struct.modifier.includeSurroundings({ 0: core, radius: 5, 'as-whole-residues': true });
const sel = update.to(StateElements.Assembly)
.apply(StateTransforms.Model.StructureSelection, { label: resn, query: surroundings }, { ref: StateElements.HetGroupFocus });
const group = update.to(StateElements.Assembly).group(StateTransforms.Misc.CreateGroup, { label: compId }, { ref: StateElements.HetGroupFocusGroup });
sel.apply(StateTransforms.Representation.StructureRepresentation3D, this.createSurVisualParams());
group.apply(StateTransforms.Model.StructureSelection, { label: 'Core', query: core }, { ref: StateElements.HetGroupFocus })
.apply(StateTransforms.Representation.StructureRepresentation3D, this.createCoreVisualParams());
group.apply(StateTransforms.Model.StructureSelection, { label: 'Surroundings', query: surroundings })
.apply(StateTransforms.Representation.StructureRepresentation3D, this.createSurVisualParams());
// sel.apply(StateTransforms.Representation.StructureLabels3D, {
// target: { name: 'residues', params: { } },
// options: {
@@ -338,7 +382,7 @@ class MolStarProteopediaWrapper {
// const position = Vec3.sub(Vec3.zero(), sphere.center, asmCenter);
// Vec3.normalize(position, position);
// Vec3.scaleAndAdd(position, sphere.center, position, sphere.radius);
const snapshot = this.plugin.canvas3d.camera.getFocus(sphere.center, 0.75 * sphere.radius);
const snapshot = this.plugin.canvas3d.camera.getFocus(sphere.center, Math.max(sphere.radius, 5));
PluginCommands.Camera.SetSnapshot.dispatch(this.plugin, { snapshot, durationMs: 250 });
}
}
@@ -352,6 +396,15 @@ class MolStarProteopediaWrapper {
});
}
private createCoreVisualParams() {
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
return StructureRepresentation3DHelpers.createParams(this.plugin, asm.data, {
repr: BuiltInStructureRepresentations['ball-and-stick'],
// color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
// size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
});
}
snapshot = {
get: () => {
return this.plugin.state.getSnapshot();

View File

@@ -1,39 +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 { ParamDefinition as PD } from '../../../mol-util/param-definition';
export interface BooleanParamComponentProps {
label: string
param: PD.BooleanParam
value: boolean
onChange(v: boolean): void
}
export interface BooleanParamComponentState {
value: boolean
}
export class BooleanParamComponent extends React.Component<BooleanParamComponentProps, BooleanParamComponentState> {
state = {
value: this.props.value
}
onChange(value: boolean) {
this.setState({ value })
this.props.onChange(value)
}
render() {
return <div>
<span>{this.props.label} </span>
<button onClick={e => this.onChange(!this.state.value) }>
{this.state.value ? 'Off' : 'On'}
</button>
</div>;
}
}

View File

@@ -1,43 +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 { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { ColorNames } from '../../../mol-util/color/tables';
import { Color } from '../../../mol-util/color';
export interface ColorParamComponentProps {
label: string
param: PD.Color
value: Color
onChange(v: Color): void
}
export interface ColorParamComponentState {
value: Color
}
export class ColorParamComponent extends React.Component<ColorParamComponentProps, ColorParamComponentState> {
state = {
value: this.props.value
}
onChange(value: Color) {
this.setState({ value })
this.props.onChange(value)
}
render() {
return <div>
<span>{this.props.label} </span>
<select value={this.state.value} onChange={e => this.onChange(Color(parseInt(e.target.value))) }>
{Object.keys(ColorNames).map(name => {
return <option key={name} value={(ColorNames as { [k: string]: Color})[name]}>{name}</option>
})}
</select>
</div>;
}
}

View File

@@ -1,45 +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 { ParamDefinition as PD } from '../../../mol-util/param-definition';
export interface MultiSelectParamComponentProps<T extends string> {
label: string
param: PD.MultiSelect<T>
value: T[]
onChange(v: T[]): void
}
export interface MultiSelectParamComponentState<T extends string> {
value: T[]
}
export class MultiSelectParamComponent<T extends string> extends React.Component<MultiSelectParamComponentProps<T>, MultiSelectParamComponentState<T>> {
state = {
value: this.props.value
}
onChange(value: T[]) {
this.setState({ value })
this.props.onChange(value)
}
render() {
return <div>
<span>{this.props.label} </span>
<select multiple value={this.state.value} onChange={e => {
const value = Array.from(e.target.options).filter(option => option.selected).map(option => option.value)
this.onChange(value as T[])
}}>
{this.props.param.options.map(v => {
const [value, label] = v
return <option key={label} value={value}>{label}</option>
})}
</select>
</div>;
}
}

View File

@@ -1,45 +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 { ParamDefinition as PD } from '../../../mol-util/param-definition';
export interface NumberParamComponentProps {
label: string
param: PD.Numeric
value: number
onChange(v: number): void
}
export interface NumberParamComponentState {
value: number
}
export class NumberParamComponent extends React.Component<NumberParamComponentProps, NumberParamComponentState> {
state = {
value: this.props.value
}
onChange(valueStr: string) {
const value = this.props.param.step && Number.isInteger(this.props.param.step) ? parseInt(valueStr) : parseFloat(valueStr)
this.setState({ value })
this.props.onChange(value)
}
render() {
return <div>
<span>{this.props.label} </span>
<input type='range'
value={this.state.value}
min={this.props.param.min}
max={this.props.param.max}
step={this.props.param.step}
onChange={e => this.onChange(e.currentTarget.value)}
>
</input>
</div>;
}
}

View File

@@ -1,42 +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 { ParamDefinition as PD } from '../../../mol-util/param-definition';
export interface SelectParamComponentProps<T extends string> {
label: string
param: PD.Select<T>
value: T
onChange(v: T): void
}
export interface SelectParamComponentState<T extends string> {
value: T
}
export class SelectParamComponent<T extends string> extends React.Component<SelectParamComponentProps<T>, SelectParamComponentState<T>> {
state = {
value: this.props.value
}
onChange(value: T) {
this.setState({ value })
this.props.onChange(value)
}
render() {
return <div>
<span>{this.props.label} </span>
<select value={this.state.value} onChange={e => this.onChange(e.target.value as T) }>
{this.props.param.options.map(v => {
const [value, label] = v
return <option key={label} value={value}>{label}</option>
})}
</select>
</div>;
}
}

View File

@@ -1,41 +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 { ParamDefinition as PD } from '../../../mol-util/param-definition';
export interface TextParamComponentProps {
label: string
param: PD.Text
value: string
onChange(v: string): void
}
export interface TextParamComponentState {
value: string
}
export class TextParamComponent extends React.Component<TextParamComponentProps, TextParamComponentState> {
state = {
value: this.props.value
}
onChange(value: string) {
this.setState({ value })
this.props.onChange(value)
}
render() {
return <div>
<span>{this.props.label} </span>
<input type='text'
value={this.state.value}
onChange={e => this.onChange(e.currentTarget.value)}
>
</input>
</div>;
}
}

View File

@@ -1,64 +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 { ParamDefinition as PD } from '../../mol-util/param-definition';
import { BooleanParamComponent } from './parameter/boolean';
import { NumberParamComponent } from './parameter/number';
import { SelectParamComponent } from './parameter/select';
import { MultiSelectParamComponent } from './parameter/multi-select';
import { TextParamComponent } from './parameter/text';
import { ColorParamComponent } from './parameter/color';
import { camelCaseToWords } from '../../mol-util/string';
interface ParametersProps<P extends PD.Params> {
params: P
values: { [k in keyof P]: P[k]['defaultValue'] }
onChange<K extends keyof P>(k: K, v: P[K]['defaultValue']): void
}
type ParametersState = {}
function getParamComponent<P extends PD.Any>(label: string, param: PD.Any, value: P['defaultValue'], onChange: (v: P['defaultValue']) => void) {
switch (param.type) {
case 'boolean':
return <BooleanParamComponent label={label} param={param} value={value} onChange={onChange} />
case 'number':
return <NumberParamComponent label={label} param={param} value={value} onChange={onChange} />
case 'select':
return <SelectParamComponent label={label} param={param} value={value} onChange={onChange} />
case 'multi-select':
return <MultiSelectParamComponent label={label} param={param} value={value} onChange={onChange} />
case 'text':
return <TextParamComponent label={label} param={param} value={value} onChange={onChange} />
case 'color':
return <ColorParamComponent label={label} param={param} value={value} onChange={onChange} />
}
return ''
}
function getLabel(name: string, param: PD.Base<any>) {
return param.label === undefined ? camelCaseToWords(name) : param.label
}
export class ParametersComponent<P extends PD.Params> extends React.Component<ParametersProps<P>, ParametersState> {
onChange(k: string, value: any) {
this.props.onChange(k, value)
}
render() {
return <div style={{ width: '100%' }}>
{ Object.keys(this.props.params).map(k => {
const param = this.props.params[k]
const value = this.props.values[k]
const label = getLabel(k, param)
return <div key={k}>
{getParamComponent(label, param, value, v => this.onChange(k, v))}
</div>
})}
</div>;
}
}

View File

@@ -1,81 +0,0 @@
// /**
// * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
// *
// * @author David Sehnal <david.sehnal@gmail.com>
// */
// import * as React from 'react'
// import { Structure, StructureSequence, Queries, StructureSelection, StructureProperties, StructureQuery } from 'mol-model/structure';
// import { EmptyLoci } from 'mol-model/loci';
// export class SequenceView extends View<SequenceViewController, {}, {}> {
// render() {
// const s = this.controller.latestState.structure;
// if (!s) return <div className='molstar-sequence-view-wrap'>No structure available.</div>;
// const seqs = s.models[0].sequence.sequences;
// return <div className='molstar-sequence-view-wrap'>
// {seqs.map((seq, i) => <EntitySequence key={i} ctx={this.controller.context} seq={seq} structure={s} /> )}
// </div>;
// }
// }
// function createQuery(entityId: string, label_seq_id: number) {
// return Queries.generators.atoms({
// entityTest: ctx => StructureProperties.entity.id(ctx.element) === entityId,
// residueTest: ctx => StructureProperties.residue.label_seq_id(ctx.element) === label_seq_id
// });
// }
// // TODO: this is really ineffective and should be done using a canvas.
// class EntitySequence extends React.Component<{ ctx: Context, seq: StructureSequence.Entity, structure: Structure }> {
// raiseInteractityEvent(seqId?: number) {
// if (typeof seqId === 'undefined') {
// InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci);
// return;
// }
// const query = createQuery(this.props.seq.entityId, seqId);
// const loci = StructureSelection.toLoci(StructureQuery.run(query, this.props.structure));
// if (loci.elements.length === 0) InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci);
// else InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, loci);
// }
// render() {
// const { ctx, seq } = this.props;
// const { offset, sequence } = seq.sequence;
// const elems: JSX.Element[] = [];
// for (let i = 0, _i = sequence.length; i < _i; i++) {
// elems[elems.length] = <ResidueView ctx={ctx} seqId={offset + i} letter={sequence[i]} parent={this} key={i} />;
// }
// return <div style={{ wordWrap: 'break-word' }}>
// <span style={{ fontWeight: 'bold' }}>{this.props.seq.entityId}:{offset}&nbsp;</span>
// {elems}
// </div>;
// }
// }
// class ResidueView extends React.Component<{ ctx: Context, seqId: number, letter: string, parent: EntitySequence }, { isHighlighted: boolean }> {
// state = { isHighlighted: false }
// mouseEnter = () => {
// this.setState({ isHighlighted: true });
// this.props.parent.raiseInteractityEvent(this.props.seqId);
// }
// mouseLeave = () => {
// this.setState({ isHighlighted: false });
// this.props.parent.raiseInteractityEvent();
// }
// render() {
// return <span onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave}
// style={{ cursor: 'pointer', backgroundColor: this.state.isHighlighted ? 'yellow' : void 0 }}>
// {this.props.letter}
// </span>;
// }
// }

View File

@@ -90,25 +90,30 @@ class Camera implements Object3D {
return ret;
}
getFocus(target: Vec3, radius: number): Partial<Camera.Snapshot> {
getFocus(target: Vec3, radius: number, dir?: Vec3): 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.state.position, target)
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)
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)
}
return { target, position: Vec3.clone(this.newPosition) };
}
focus(target: Vec3, radius: number) {
this.setState(this.getFocus(target, radius));
focus(target: Vec3, radius: number, dir?: Vec3) {
if (radius > 0) this.setState(this.getFocus(target, radius, dir));
}
// lookAt(target: Vec3) {
@@ -177,8 +182,8 @@ namespace Camera {
return {
mode: 'perspective',
position: Vec3.zero(),
direction: Vec3.create(0, 0, -1),
position: Vec3.create(0, 0, 100),
direction: Vec3.create(0, 0, 1),
up: Vec3.create(0, 1, 0),
target: Vec3.create(0, 0, 0),
@@ -250,7 +255,7 @@ function updateOrtho(camera: Camera) {
let top = cy + dy
let bottom = cy - dy
if (viewOffset && viewOffset.enabled) {
if (viewOffset.enabled) {
const zoomW = zoom / (viewOffset.width / viewOffset.fullWidth)
const zoomH = zoom / (viewOffset.height / viewOffset.fullHeight)
const scaleW = (fullRight - fullLeft) / viewOffset.width
@@ -279,7 +284,7 @@ function updatePers(camera: Camera) {
let width = aspect * height
let left = -0.5 * width
if (viewOffset && viewOffset.enabled) {
if (viewOffset.enabled) {
left += viewOffset.offsetX * width / viewOffset.fullWidth
top -= viewOffset.offsetY * height / viewOffset.fullHeight
width *= viewOffset.width / viewOffset.fullWidth

View File

@@ -17,7 +17,7 @@ import { Representation } from '../mol-repr/representation';
import Scene from '../mol-gl/scene';
import { GraphicsRenderVariant } from '../mol-gl/webgl/render-item';
import { PickingId } from '../mol-geo/geometry/picking';
import { MarkerAction } from '../mol-geo/geometry/marker-data';
import { MarkerAction } from '../mol-util/marker-action';
import { Loci, EmptyLoci, isEmptyLoci } from '../mol-model/loci';
import { Camera } from './camera';
import { ParamDefinition as PD } from '../mol-util/param-definition';
@@ -31,6 +31,7 @@ import { PixelData } from '../mol-util/image';
import { readTexture } from '../mol-gl/compute/util';
import { DrawPass } from './passes/draw';
import { PickPass } from './passes/pick';
import { Task } from '../mol-task';
export const Canvas3DParams = {
// TODO: FPS cap?
@@ -46,6 +47,7 @@ export const Canvas3DParams = {
trackball: PD.Group(TrackballControlsParams),
debug: PD.Group(DebugHelperParams)
}
export const DefaultCanvas3DParams = PD.getDefaultValues(Canvas3DParams);
export type Canvas3DProps = PD.Values<typeof Canvas3DParams>
export { Canvas3D }
@@ -66,6 +68,7 @@ interface Canvas3D {
getLoci: (pickingId: PickingId) => Representation.Loci
readonly didDraw: BehaviorSubject<now.Timestamp>
readonly reprCount: BehaviorSubject<number>
handleResize: () => void
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
@@ -85,12 +88,13 @@ interface Canvas3D {
}
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()))
const DefaultRunTask = (task: Task<unknown>) => task.run()
namespace Canvas3D {
export interface HighlightEvent { current: Representation.Loci, modifiers?: ModifiersKeys }
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, modifiers: ModifiersKeys }
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}) {
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask) {
const gl = getGLContext(canvas, {
alpha: false,
antialias: true,
@@ -99,11 +103,11 @@ namespace Canvas3D {
})
if (gl === null) throw new Error('Could not create a WebGL rendering context')
const input = InputObserver.fromElement(canvas)
return Canvas3D.create(gl, input, props)
return Canvas3D.create(gl, input, props, runTask)
}
export function create(gl: GLRenderingContext, input: InputObserver, props: Partial<Canvas3DProps> = {}): Canvas3D {
const p = { ...PD.getDefaultValues(Canvas3DParams), ...props }
export function create(gl: GLRenderingContext, input: InputObserver, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask): Canvas3D {
const p = { ...DefaultCanvas3DParams, ...props }
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>()
const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>()
@@ -115,7 +119,7 @@ namespace Canvas3D {
const camera = new Camera({
near: 0.1,
far: 10000,
position: Vec3.create(0, 0, 10),
position: Vec3.create(0, 0, 100),
mode: p.cameraMode
})
@@ -135,8 +139,8 @@ namespace Canvas3D {
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing)
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample)
let isUpdating = false
let drawPending = false
let cameraResetRequested: boolean | Vec3 = false
function getLoci(pickingId: PickingId) {
let loci: Loci = EmptyLoci
@@ -184,10 +188,11 @@ namespace Canvas3D {
let fogFar = cDist + (bRadius * fogFarFactor)
if (camera.state.mode === 'perspective') {
near = Math.max(1, p.cameraClipDistance, near)
far = Math.max(1, far)
fogNear = Math.max(1, fogNear)
fogFar = Math.max(1, fogFar)
// 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)
@@ -201,7 +206,7 @@ namespace Canvas3D {
}
function render(variant: 'pick' | 'draw', force: boolean) {
if (isUpdating) return false
if (scene.isCommiting) return false
let didRender = false
controls.update(currentTime);
@@ -261,8 +266,22 @@ namespace Canvas3D {
return pickPass.identify(x, y)
}
function commit(renderObjects?: readonly GraphicsRenderObject[]) {
scene.update(renderObjects, false)
runTask(scene.commit()).then(() => {
if (cameraResetRequested && !scene.isCommiting) {
const dir = typeof cameraResetRequested === 'boolean' ? undefined : cameraResetRequested
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius, dir)
cameraResetRequested = false
}
if (debugHelper.isEnabled) debugHelper.update()
requestDraw(true)
reprCount.next(reprRenderObjects.size)
})
}
function add(repr: Representation.Any) {
isUpdating = true
const oldRO = reprRenderObjects.get(repr)
const newRO = new Set<GraphicsRenderObject>()
repr.renderObjects.forEach(o => newRO.add(o))
@@ -276,11 +295,7 @@ namespace Canvas3D {
repr.renderObjects.forEach(o => scene.add(o))
}
reprRenderObjects.set(repr, newRO)
scene.update(repr.renderObjects, false)
if (debugHelper.isEnabled) debugHelper.update()
isUpdating = false
requestDraw(true)
reprCount.next(reprRenderObjects.size)
commit(repr.renderObjects)
}
handleResize()
@@ -301,14 +316,9 @@ namespace Canvas3D {
}
const renderObjects = reprRenderObjects.get(repr)
if (renderObjects) {
isUpdating = true
renderObjects.forEach(o => scene.remove(o))
reprRenderObjects.delete(repr)
scene.update(void 0, false)
if (debugHelper.isEnabled) debugHelper.update()
isUpdating = false
requestDraw(true)
reprCount.next(reprRenderObjects.size)
commit()
}
},
update: (repr, keepSphere) => {
@@ -333,9 +343,13 @@ namespace Canvas3D {
getLoci,
handleResize,
resetCamera: () => {
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius)
requestDraw(true);
resetCamera: (dir?: Vec3) => {
if (scene.isCommiting) {
cameraResetRequested = dir || true
} else {
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius, dir)
requestDraw(true);
}
},
camera,
downloadScreenshot: () => {
@@ -351,6 +365,7 @@ namespace Canvas3D {
}
},
didDraw,
reprCount,
setProps: (props: Partial<Canvas3DProps>) => {
if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
camera.setState({ mode: props.cameraMode })

View File

@@ -17,7 +17,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
export const TrackballControlsParams = {
noScroll: PD.Boolean(true, { isHidden: true }),
rotateSpeed: PD.Numeric(5.0, { min: 0.1, max: 10, step: 0.1 }),
rotateSpeed: PD.Numeric(3.0, { min: 0.1, max: 10, step: 0.1 }),
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 }),
@@ -217,9 +217,7 @@ namespace TrackballControls {
panCamera()
Vec3.add(object.position, target, _eye)
checkDistances()
cameraLookAt(object.position, object.up, object.direction, target)
if (Vec3.squaredDistance(lastPosition, object.position) > EPSILON.Value) {
@@ -259,7 +257,6 @@ namespace TrackballControls {
}
if (buttons === ButtonsType.Flag.Primary) {
Vec2.copy(_movePrev, _moveCurr)
Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY))
} else if (buttons === ButtonsType.Flag.Auxilary) {
Vec2.copy(_zoomEnd, getMouseOnScreen(pageX, pageY))
@@ -273,12 +270,12 @@ namespace TrackballControls {
}
function onWheel({ dy }: WheelInput) {
_zoomStart[1] -= dy * 0.0001
_zoomEnd[1] += dy * 0.0001
}
function onPinch({ fraction }: PinchInput) {
_isInteracting = true;
_zoomStart[1] -= (fraction - 1) * 0.1
_zoomEnd[1] += (fraction - 1) * 0.1
}
function dispose() {

View File

@@ -13,7 +13,7 @@ import Scene from '../../mol-gl/scene';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Sphere3D } from '../../mol-math/geometry';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/tables';
import { ColorNames } from '../../mol-util/color/names';
import { TransformData } from '../../mol-geo/geometry/transform-data';
import { sphereVertexCount } from '../../mol-geo/primitive/sphere';
import { ValueCell } from '../../mol-util';
@@ -81,7 +81,8 @@ export class BoundingSphereHelper {
}
})
this.scene.update(void 0, false);
this.scene.update(void 0, false)
this.scene.syncCommit()
}
syncVisibility() {

View File

@@ -11,14 +11,17 @@ import Scene from '../../mol-gl/scene';
import { PickingId } from '../../mol-geo/geometry/picking';
import { decodeFloatRGB } from '../../mol-util/float-packing';
const readBuffer = new Uint8Array(4)
export class PickPass {
pickDirty = true
objectPickTarget: RenderTarget
instancePickTarget: RenderTarget
groupPickTarget: RenderTarget
private objectBuffer: Uint8Array
private instanceBuffer: Uint8Array
private groupBuffer: Uint8Array
private pickScale: number
private pickWidth: number
private pickHeight: number
@@ -31,18 +34,33 @@ export class PickPass {
this.pickScale = pickBaseScale / webgl.pixelRatio
this.pickWidth = Math.round(width * this.pickScale)
this.pickHeight = Math.round(height * this.pickScale)
this.objectPickTarget = createRenderTarget(webgl, this.pickWidth, this.pickHeight)
this.instancePickTarget = createRenderTarget(webgl, this.pickWidth, this.pickHeight)
this.groupPickTarget = createRenderTarget(webgl, this.pickWidth, this.pickHeight)
this.setupBuffers()
}
private setupBuffers() {
const bufferSize = this.pickWidth * this.pickHeight * 4
if (!this.objectBuffer || this.objectBuffer.length !== bufferSize) {
this.objectBuffer = new Uint8Array(bufferSize)
this.instanceBuffer = new Uint8Array(bufferSize)
this.groupBuffer = new Uint8Array(bufferSize)
}
}
setSize(width: number, height: number) {
this.pickScale = this.pickBaseScale / this.webgl.pixelRatio
this.pickWidth = Math.round(width * this.pickScale)
this.pickHeight = Math.round(height * this.pickScale)
this.objectPickTarget.setSize(this.pickWidth, this.pickHeight)
this.instancePickTarget.setSize(this.pickWidth, this.pickHeight)
this.groupPickTarget.setSize(this.pickWidth, this.pickHeight)
this.setupBuffers()
}
render() {
@@ -54,12 +72,35 @@ export class PickPass {
renderer.render(scene, 'pickInstance', true);
this.groupPickTarget.bind();
renderer.render(scene, 'pickGroup', true);
this.pickDirty = false
}
private syncBuffers() {
const { webgl } = this
this.objectPickTarget.bind()
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.objectBuffer)
this.instancePickTarget.bind()
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.instanceBuffer)
this.groupPickTarget.bind()
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.groupBuffer)
}
private getId(x: number, y: number, buffer: Uint8Array) {
const idx = (y * this.pickWidth + x) * 4
return decodeFloatRGB(buffer[idx], buffer[idx + 1], buffer[idx + 2])
}
identify(x: number, y: number): PickingId | undefined {
const { webgl, pickScale } = this
const { gl } = webgl
if (this.pickDirty) this.render()
if (this.pickDirty) {
this.render()
this.syncBuffers()
}
x *= webgl.pixelRatio
y *= webgl.pixelRatio
@@ -68,19 +109,13 @@ export class PickPass {
const xp = Math.round(x * pickScale)
const yp = Math.round(y * pickScale)
this.objectPickTarget.bind()
webgl.readPixels(xp, yp, 1, 1, readBuffer)
const objectId = decodeFloatRGB(readBuffer[0], readBuffer[1], readBuffer[2])
const objectId = this.getId(xp, yp, this.objectBuffer)
if (objectId === -1) return
this.instancePickTarget.bind()
webgl.readPixels(xp, yp, 1, 1, readBuffer)
const instanceId = decodeFloatRGB(readBuffer[0], readBuffer[1], readBuffer[2])
const instanceId = this.getId(xp, yp, this.instanceBuffer)
if (instanceId === -1) return
this.groupPickTarget.bind()
webgl.readPixels(xp, yp, 1, 1, readBuffer)
const groupId = decodeFloatRGB(readBuffer[0], readBuffer[1], readBuffer[2])
const groupId = this.getId(xp, yp, this.groupBuffer)
if (groupId === -1) return
return { objectId, instanceId, groupId }

View File

@@ -51,7 +51,7 @@ export const PostprocessingParams = {
occlusionEnable: PD.Boolean(false),
occlusionKernelSize: PD.Numeric(4, { min: 1, max: 32, step: 1 }),
occlusionBias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
occlusionRadius: PD.Numeric(64, { min: 0, max: 256, step: 1 }),
occlusionRadius: PD.Numeric(32, { min: 0, max: 256, step: 1 }),
outlineEnable: PD.Boolean(false),
outlineScale: PD.Numeric(1, { min: 0, max: 10, step: 1 }),

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2018 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>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -7,6 +7,8 @@
import * as ColumnHelpers from './column-helpers'
import { Tensor as Tensors } from '../../mol-math/linear-algebra'
import { Tokens } from '../../mol-io/reader/common/text/tokenizer';
import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../../mol-io/reader/common/text/number-parser';
interface Column<T> {
readonly schema: Column.Schema,
@@ -128,6 +130,37 @@ namespace Column {
return arrayColumn({ array, schema: Schema.str });
}
export function ofIntTokens(tokens: Tokens) {
const { count, data, indices } = tokens
return lambdaColumn({
value: (row: number) => fastParseInt(data, indices[2 * row], indices[2 * row + 1]) || 0,
rowCount: count,
schema: Schema.int,
});
}
export function ofFloatTokens(tokens: Tokens) {
const { count, data, indices } = tokens
return lambdaColumn({
value: (row: number) => fastParseFloat(data, indices[2 * row], indices[2 * row + 1]) || 0,
rowCount: count,
schema: Schema.float,
});
}
export function ofStringTokens(tokens: Tokens) {
const { count, data, indices } = tokens
return lambdaColumn({
value: (row: number) => {
const ret = data.substring(indices[2 * row], indices[2 * row + 1]);
if (ret === '.' || ret === '?') return '';
return ret;
},
rowCount: count,
schema: Schema.str,
});
}
export function window<T>(column: Column<T>, start: number, end: number) {
return windowColumn(column, start, end);
}

View File

@@ -17,7 +17,7 @@ describe('rangesArray', () => {
it(`iterator, ${name}`, () => {
const rangesIt = SortedRanges.transientSegments(ranges, set)
const { index, start, end } = expectedValues
let i = 0
while (rangesIt.hasNext) {
const segment = rangesIt.move()
@@ -41,7 +41,7 @@ describe('rangesArray', () => {
testIterator('two ranges',
SortedRanges.ofSortedRanges([1, 2, 3, 4]),
OrderedSet.ofBounds(1, 4),
OrderedSet.ofBounds(1, 5),
{ index: [0, 1], start: [0, 2], end: [2, 4] }
)
testIterator('first range',
@@ -62,7 +62,7 @@ describe('rangesArray', () => {
testIterator('set in second range and beyond',
SortedRanges.ofSortedRanges([1, 2, 3, 4]),
SortedArray.ofSortedArray([3, 10]),
{ index: [1], start: [0], end: [2] }
{ index: [1], start: [0], end: [1] }
)
testIterator('length 1 range',
SortedRanges.ofSortedRanges([1, 1, 3, 4]),

View File

@@ -47,8 +47,10 @@ export function indexOf(xs: Nums, v: number) {
return l === 0 ? -1 : xs[0] <= v && v <= xs[l - 1] ? binarySearchRange(xs, v, 0, l) : -1;
}
export function indexOfInInterval(xs: Nums, v: number, bounds: Interval) {
return indexOfInRange(xs, v, Interval.start(bounds), Interval.end(bounds))
}
export function indexOfInRange(xs: Nums, v: number, s: number, e: number) {
const l = xs.length;
const s = Interval.start(bounds), e = Interval.end(bounds);
return l === 0 || e <= s ? -1 : xs[s] <= v && v <= xs[e - 1] ? binarySearchRange(xs, v, s, e) : -1;
}
export function has(xs: Nums, v: number) { return indexOf(xs, v) >= 0; }
@@ -65,6 +67,11 @@ export function areEqual(a: Nums, b: Nums) {
return true;
}
/**
* Returns 0 if `v` is smaller or equal the first element of `xs`
* Returns length of `xs` if `v` is bigger than the last element of `xs`
* Otherwise returns the first index where the value of `xs` is equal or bigger than `v`
*/
export function findPredecessorIndex(xs: Nums, v: number) {
const len = xs.length;
if (v <= xs[0]) return 0;

View File

@@ -21,12 +21,14 @@ namespace OrderedSet {
export const has: <T extends number = number>(set: OrderedSet<T>, x: T) => boolean = Base.has as any;
/** Returns the index of `x` in `set` or -1 if not found. */
export const indexOf: <T extends number = number>(set: OrderedSet<T>, x: T) => number = Base.indexOf as any;
/** Returns the value in `set` at index `i`. */
export const getAt: <T extends number = number>(set: OrderedSet<T>, i: number) => T = Base.getAt as any;
export const min: <T extends number = number>(set: OrderedSet<T>) => T = Base.min as any;
export const max: <T extends number = number>(set: OrderedSet<T>) => T = Base.max as any;
export const start: <T extends number = number>(set: OrderedSet<T>) => T = Base.start as any;
export const end: <T extends number = number>(set: OrderedSet<T>) => T = Base.end as any;
/** Number of elements in the OrderedSet */
export const size: <T extends number = number>(set: OrderedSet<T>) => number = Base.size as any;
export const hashCode: <T extends number = number>(set: OrderedSet<T>) => number = Base.hashCode as any;
@@ -37,8 +39,14 @@ namespace OrderedSet {
export const union: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.union as any;
export const intersect: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.intersect as any;
/** Returns elements of `a` that are not in `b`, i.e `a` - `b` */
export const subtract: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.subtract as any;
/**
* Returns 0 if `x` is smaller or equal the first element of `set`
* Returns length of `set` if `x` is bigger than the last element of `set`
* Otherwise returns the first index where the value of `set` is equal or bigger than `x`
*/
export const findPredecessorIndex: <T extends number = number>(set: OrderedSet<T>, x: number) => number = Base.findPredecessorIndex as any;
export const findPredecessorIndexInInterval: <T extends number = number>(set: OrderedSet<T>, x: T, range: Interval) => number = Base.findPredecessorIndexInInterval as any;
export const findRange: <T extends number = number>(set: OrderedSet<T>, min: T, max: T) => Interval = Base.findRange as any;

View File

@@ -18,7 +18,7 @@ namespace Segmentation {
export const getSegment: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, value: T) => number = Impl.getSegment as any;
export const projectValue: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, set: OrderedSet<T>, value: T) => Interval = Impl.projectValue as any;
// Segment iterator that mutates a single segment object to mark all the segments.
/** Segment iterator that mutates a single segment object to mark all the segments. */
export const transientSegments: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, set: OrderedSet<T>, segment?: Segment) => Impl.SegmentIterator<I> = Impl.segments as any;
export type SegmentIterator<I extends number = number> = Impl.SegmentIterator<I>

View File

@@ -12,9 +12,9 @@ namespace SortedArray {
export const ofUnsortedArray: <T extends number = number>(xs: ArrayLike<number>) => SortedArray<T> = Impl.ofUnsortedArray as any;
export const ofSingleton: <T extends number = number>(v: number) => SortedArray<T> = Impl.ofSingleton as any;
export const ofSortedArray: <T extends number = number>(xs: ArrayLike<number>) => SortedArray<T> = Impl.ofSortedArray as any;
// create sorted array [min, max] (it DOES contain the max value)
/** create sorted array [min, max] (it DOES contain the max value) */
export const ofRange: <T extends number = number>(min: T, max: T) => SortedArray<T> = Impl.ofRange as any;
// create sorted array [min, max) (it does NOT contain the max value)
/** create sorted array [min, max) (it does NOT contain the max value) */
export const ofBounds: <T extends number = number>(min: T, max: T) => SortedArray<T> = (min, max) => Impl.ofRange(min, max - 1) as any;
export const is: <T extends number = number>(v: any) => v is SortedArray<T> = Impl.is as any;
@@ -22,10 +22,11 @@ namespace SortedArray {
/** Returns the index of `x` in `set` or -1 if not found. */
export const indexOf: <T extends number = number>(array: SortedArray<T>, x: T) => number = Impl.indexOf as any;
export const indexOfInInterval: <T extends number = number>(array: SortedArray<T>, x: number, bounds: Interval) => number = Impl.indexOfInInterval as any;
export const indexOfInRange: <T extends number = number>(array: SortedArray<T>, x: number, start: number, end: number) => number = Impl.indexOfInRange as any;
// array[0]
/** Returns `array[0]` */
export const start: <T extends number = number>(array: SortedArray<T>) => T = Impl.start as any;
// array[array.length - 1] + 1
/** Returns `array[array.length - 1] + 1` */
export const end: <T extends number = number>(array: SortedArray<T>) => T = Impl.end as any;
export const min: <T extends number = number>(array: SortedArray<T>) => T = Impl.min as any;
export const max: <T extends number = number>(array: SortedArray<T>) => T = Impl.max as any;

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>
*/
@@ -23,6 +23,64 @@ namespace SortedRanges {
}
return size
}
export function count<T extends number = number>(ranges: SortedRanges<T>) { return ranges.length / 2 }
export function startAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
return ranges[index * 2]
}
export function endAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
return ranges[index * 2 + 1] + 1
}
export function minAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
return ranges[index * 2]
}
export function maxAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
return ranges[index * 2 + 1]
}
export function areEqual<T extends number = number>(a: SortedRanges<T>, b: SortedRanges<T>) {
if (a.length !== b.length) return false
for (let i = 0, il = a.length; i < il; ++i) {
if (a[i] !== b[i]) return false
}
return true
}
export function forEach<T extends number = number>(ranges: SortedRanges<T>, f: (value: T, i: number) => void) {
let k = 0
for (let i = 0, il = ranges.length; i < il; i += 2) {
for (let j = ranges[i], jl = ranges[i + 1]; j <= jl; ++j) {
f(j, k);
++k
}
}
}
/** Returns if a value of `set` is included in `ranges` */
export function has<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>) {
return firstIntersectionIndex(ranges, set) !== -1
}
/** Returns if a value of `set` is included in `ranges` from given index */
export function hasFrom<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>, from: number) {
return firstIntersectionIndexFrom(ranges, set, from) !== -1
}
export function firstIntersectionIndex<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>): number {
return firstIntersectionIndexFrom(ranges, set, 0)
}
export function firstIntersectionIndexFrom<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>, from: number): number {
if (minAt(ranges, from) > OrderedSet.max(set) || max(ranges) < OrderedSet.min(set)) return -1
for (let i = from, il = count(ranges); i < il; ++i) {
const interval = Interval.ofRange(minAt(ranges, i), maxAt(ranges, i))
if (OrderedSet.areIntersecting(interval, set)) return i
}
return -1
}
export function transientSegments<T extends number = number, I extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>) {
return new Iterator<T, I>(ranges, set)
@@ -32,53 +90,27 @@ namespace SortedRanges {
private value: Segmentation.Segment<I> = { index: 0 as I, start: 0 as T, end: 0 as T }
private curIndex = 0
private maxIndex = 0
private interval: Interval<T>
private curMin: T = 0 as T
hasNext: boolean = false;
updateInterval() {
this.interval = Interval.ofRange(this.ranges[this.curIndex], this.ranges[this.curIndex + 1])
}
updateValue() {
this.value.index = this.curIndex / 2 as I
this.value.start = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex])
this.value.end = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex + 1]) + 1
private updateValue() {
this.value.index = this.curIndex as I
this.value.start = OrderedSet.findPredecessorIndex(this.set, startAt(this.ranges, this.curIndex))
this.value.end = OrderedSet.findPredecessorIndex(this.set, endAt(this.ranges, this.curIndex))
}
move() {
if (this.hasNext) {
this.updateValue()
while (this.curIndex <= this.maxIndex) {
this.curIndex += 2
this.curMin = Interval.end(this.interval)
this.updateInterval()
if (Interval.min(this.interval) >= this.curMin && OrderedSet.areIntersecting(this.interval, this.set)) break
}
this.hasNext = this.curIndex <= this.maxIndex
this.curIndex = firstIntersectionIndexFrom(this.ranges, this.set, this.curIndex + 1)
this.hasNext = this.curIndex !== -1
}
return this.value;
}
getRangeIndex(value: number) {
const index = SortedArray.findPredecessorIndex(this.ranges, value)
return (index % 2 === 1) ? index - 1 : index
}
constructor(private ranges: SortedRanges<T>, private set: OrderedSet<T>) {
// TODO cleanup, refactor to make it clearer
const min = SortedArray.findPredecessorIndex(this.ranges, OrderedSet.min(set))
const max = SortedArray.findPredecessorIndex(this.ranges, OrderedSet.max(set) + 1)
if (ranges.length && min !== max) {
this.curIndex = this.getRangeIndex(OrderedSet.min(set))
this.maxIndex = Math.min(ranges.length - 2, this.getRangeIndex(OrderedSet.max(set)))
this.curMin = this.ranges[this.curIndex]
this.updateInterval()
}
this.hasNext = ranges.length > 0 && min !== max && this.curIndex <= this.maxIndex
this.curIndex = firstIntersectionIndex(ranges, set)
this.hasNext = this.curIndex !== -1
}
}
}

View File

@@ -73,7 +73,7 @@ export function sortedCantorPairing(a: number, b: number) {
/**
* 32 bit FNV-1a hash, see http://isthe.com/chongo/tech/comp/fnv/
*/
export function hashFnv32a(array: number[]) {
export function hashFnv32a(array: ArrayLike<number>) {
let hval = 0x811c9dc5;
for (let i = 0, il = array.length; i < il; ++i) {
hval ^= array[i];

View File

@@ -13,7 +13,7 @@ import { Color } from '../../mol-util/color';
import { Vec3 } from '../../mol-math/linear-algebra';
import { TransformData, createIdentityTransform } from './transform-data';
import { Theme } from '../../mol-theme/theme';
import { ColorNames } from '../../mol-util/color/tables';
import { ColorNames } from '../../mol-util/color/names';
import { NullLocation } from '../../mol-model/location';
import { UniformColorTheme } from '../../mol-theme/color/uniform';
import { UniformSizeTheme } from '../../mol-theme/size/uniform';

View File

@@ -21,7 +21,7 @@ import { transformPositionArray } from '../../../mol-geo/util';
import { calculateBoundingSphere } from '../../../mol-gl/renderable/util';
import { Theme } from '../../../mol-theme/theme';
import { RenderableState } from '../../../mol-gl/renderable';
import { ColorListOptions, ColorListName } from '../../../mol-util/color/scale';
import { ColorListOptions, ColorListName } from '../../../mol-util/color/lists';
import { Color } from '../../../mol-util/color';
import { BaseGeometry } from '../base';
import { createEmptyOverpaint } from '../overpaint-data';
@@ -81,7 +81,7 @@ export namespace DirectVolume {
Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
]),
list: PD.ColorScale<ColorListName>('RedYellowBlue', ColorListOptions),
list: PD.ColorList<ColorListName>('red-yellow-blue', ColorListOptions),
}
export type Params = typeof Params

View File

@@ -9,7 +9,7 @@ import { spline } from '../../../mol-math/interpolate';
import { ColorScale, Color } from '../../../mol-util/color';
import { ValueCell } from '../../../mol-util';
import { Vec2 } from '../../../mol-math/linear-algebra';
import { ColorListName } from '../../../mol-util/color/scale';
import { ColorListName } from '../../../mol-util/color/lists';
export interface ControlPoint { x: number, alpha: number }

View File

@@ -13,57 +13,6 @@ export type MarkerData = {
uMarkerTexDim: ValueCell<Vec2>
}
export enum MarkerAction {
Highlight,
RemoveHighlight,
Select,
Deselect,
Toggle,
Clear
}
export function applyMarkerAction(array: Uint8Array, start: number, end: number, action: MarkerAction) {
let changed = false
for (let i = start; i < end; ++i) {
let v = array[i]
switch (action) {
case MarkerAction.Highlight:
if (v % 2 === 0) {
v += 1
}
break
case MarkerAction.RemoveHighlight:
if (v % 2 !== 0) {
v -= 1
}
break
case MarkerAction.Select:
if (v < 2) v += 2
// v += 2
break
case MarkerAction.Deselect:
// if (v >= 2) {
// v -= 2
// }
v = v % 2
break
case MarkerAction.Toggle:
if (v >= 2) {
v -= 2
} else {
v += 2
}
break
case MarkerAction.Clear:
v = 0
break
}
changed = array[i] !== v || changed
array[i] = v
}
return changed
}
export function createMarkers(count: number, markerData?: MarkerData): MarkerData {
const markers = createTextureImage(Math.max(1, count), 1, Uint8Array, markerData && markerData.tMarker.ref.value.array)
if (markerData) {

View File

@@ -1,13 +1,15 @@
/**
* 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>
*/
import { Vec3, Mat4 } from '../../../../mol-math/linear-algebra';
import { MeshBuilder } from '../mesh-builder';
import { Primitive } from '../../../primitive/primitive';
import { Primitive, transformPrimitive } from '../../../primitive/primitive';
import { Cylinder, CylinderProps } from '../../../primitive/cylinder';
import { Prism } from '../../../primitive/prism';
import { polygon } from '../../../primitive/polygon';
const cylinderMap = new Map<string, Primitive>()
const up = Vec3.create(0, 1, 0)
@@ -34,7 +36,12 @@ function getCylinder(props: CylinderProps) {
const key = JSON.stringify(props)
let cylinder = cylinderMap.get(key)
if (cylinder === undefined) {
cylinder = Cylinder(props)
if (props.radialSegments && props.radialSegments <= 4) {
const box = Prism(polygon(4, true, props.radiusTop), props)
cylinder = transformPrimitive(box, Mat4.rotX90)
} else {
cylinder = Cylinder(props)
}
cylinderMap.set(key, cylinder)
}
return cylinder

View File

@@ -0,0 +1,108 @@
/**
* 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>
*/
import { Vec3 } from '../../../../mol-math/linear-algebra';
import { ChunkedArray } from '../../../../mol-data/util';
import { MeshBuilder } from '../mesh-builder';
const tA = Vec3.zero()
const tB = Vec3.zero()
const tV = Vec3.zero()
const horizontalVector = Vec3.zero()
const verticalVector = Vec3.zero()
const normalOffset = Vec3.zero()
const positionVector = Vec3.zero()
const normalVector = Vec3.zero()
const torsionVector = Vec3.zero()
/** set arrowHeight = 0 for no arrow */
export function addRibbon(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, arrowHeight: number) {
const { currentGroup, vertices, normals, indices, groups } = state
let vertexCount = vertices.elementCount
let offsetLength = 0
if (arrowHeight > 0) {
Vec3.fromArray(tA, controlPoints, 0)
Vec3.fromArray(tB, controlPoints, linearSegments * 3)
offsetLength = arrowHeight / Vec3.magnitude(Vec3.sub(tV, tB, tA))
}
for (let i = 0; i <= linearSegments; ++i) {
const width = widthValues[i]
const height = heightValues[i]
const actualHeight = arrowHeight === 0 ? height : arrowHeight * (1 - i / linearSegments);
const i3 = i * 3
Vec3.fromArray(verticalVector, normalVectors, i3)
Vec3.scale(verticalVector, verticalVector, actualHeight);
Vec3.fromArray(horizontalVector, binormalVectors, i3)
Vec3.scale(horizontalVector, horizontalVector, width);
if (arrowHeight > 0) {
Vec3.fromArray(tA, normalVectors, i3)
Vec3.fromArray(tB, binormalVectors, i3)
Vec3.scale(normalOffset, Vec3.cross(normalOffset, tA, tB), offsetLength)
}
Vec3.fromArray(positionVector, controlPoints, i3)
Vec3.fromArray(normalVector, normalVectors, i3)
Vec3.fromArray(torsionVector, binormalVectors, i3)
Vec3.add(tA, positionVector, verticalVector)
Vec3.negate(tB, torsionVector)
ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
Vec3.sub(tA, positionVector, verticalVector)
ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
Vec3.add(tA, positionVector, verticalVector)
Vec3.copy(tB, torsionVector)
ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
Vec3.sub(tA, positionVector, verticalVector)
ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
}
for (let i = 0; i < linearSegments; ++i) {
ChunkedArray.add3(
indices,
vertexCount + i * 4,
vertexCount + (i + 1) * 4 + 1,
vertexCount + i * 4 + 1
);
ChunkedArray.add3(
indices,
vertexCount + i * 4,
vertexCount + (i + 1) * 4,
vertexCount + (i + 1) * 4 + 1
);
ChunkedArray.add3(
indices,
vertexCount + i * 4 + 2 + 1,
vertexCount + (i + 1) * 4 + 2 + 1,
vertexCount + i * 4 + 2
);
ChunkedArray.add3(
indices,
vertexCount + i * 4 + 2,
vertexCount + (i + 1) * 4 + 2 + 1,
vertexCount + (i + 1) * 4 + 2
);
}
const addedVertexCount = (linearSegments + 1) * 4
for (let i = 0, il = addedVertexCount; i < il; ++i) ChunkedArray.add(groups, currentGroup)
}

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>
@@ -71,7 +71,7 @@ function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLi
}
/** set arrowHeight = 0 for no arrow */
export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, width: number, height: number, arrowHeight: number, startCap: boolean, endCap: boolean) {
export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, arrowHeight: number, startCap: boolean, endCap: boolean) {
const { currentGroup, vertices, normals, indices, groups } = state
let vertexCount = vertices.elementCount
@@ -84,6 +84,9 @@ export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<numb
}
for (let i = 0; i <= linearSegments; ++i) {
const width = widthValues[i]
const height = heightValues[i]
const actualHeight = arrowHeight === 0 ? height : arrowHeight * (1 - i / linearSegments);
const i3 = i * 3
@@ -141,32 +144,55 @@ export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<numb
}
for (let i = 0; i < linearSegments; ++i) {
for (let j = 0; j < 4; j++) {
// the triangles are arranged such that opposing triangles of the sheet align
// which prevents triangle intersection within tight curves
for (let j = 0; j < 2; j++) {
ChunkedArray.add3(
indices,
vertexCount + i * 8 + 2 * j,
vertexCount + (i + 1) * 8 + 2 * j + 1,
vertexCount + i * 8 + 2 * j + 1
vertexCount + i * 8 + 2 * j, // a
vertexCount + (i + 1) * 8 + 2 * j + 1, // c
vertexCount + i * 8 + 2 * j + 1 // b
);
ChunkedArray.add3(
indices,
vertexCount + i * 8 + 2 * j,
vertexCount + (i + 1) * 8 + 2 * j,
vertexCount + (i + 1) * 8 + 2 * j + 1
vertexCount + i * 8 + 2 * j, // a
vertexCount + (i + 1) * 8 + 2 * j, // d
vertexCount + (i + 1) * 8 + 2 * j + 1 // c
);
}
for (let j = 2; j < 4; j++) {
ChunkedArray.add3(
indices,
vertexCount + i * 8 + 2 * j, // a
vertexCount + (i + 1) * 8 + 2 * j, // d
vertexCount + i * 8 + 2 * j + 1, // b
);
ChunkedArray.add3(
indices,
vertexCount + (i + 1) * 8 + 2 * j, // d
vertexCount + (i + 1) * 8 + 2 * j + 1, // c
vertexCount + i * 8 + 2 * j + 1, // b
);
}
}
if (startCap) {
const width = widthValues[0]
const height = heightValues[0]
const h = arrowHeight === 0 ? height : arrowHeight
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, h, h)
} else if (arrowHeight > 0) {
const width = widthValues[0]
const height = heightValues[0]
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, arrowHeight, -height)
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, -arrowHeight, height)
}
if (endCap && arrowHeight === 0) {
addCap(linearSegments * 3, state, controlPoints, normalVectors, binormalVectors, width, height, height)
const width = widthValues[linearSegments]
const height = heightValues[linearSegments]
// use negative height to flip the direction the cap's triangles are facing
addCap(linearSegments * 3, state, controlPoints, normalVectors, binormalVectors, width, -height, -height)
}
const addedVertexCount = (linearSegments + 1) * 8 +

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>
@@ -50,7 +50,15 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
const t = 2 * Math.PI * j / radialSegments;
add3AndScale2(surfacePoint, u, v, controlPoint, h * Math.cos(t), w * Math.sin(t))
add2AndScale2(normalVector, u, v, w * Math.cos(t), h * Math.sin(t))
if (radialSegments === 2) {
// add2AndScale2(normalVector, u, v, w * Math.cos(t), h * Math.sin(t))
Vec3.copy(normalVector, v)
console.log(i, t)
Vec3.normalize(normalVector, normalVector)
if (t !== 0 || i % 2 === 0) Vec3.negate(normalVector, normalVector)
} else {
add2AndScale2(normalVector, u, v, w * Math.cos(t), h * Math.sin(t))
}
Vec3.normalize(normalVector, normalVector)
ChunkedArray.add3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]);

View File

@@ -25,6 +25,7 @@ export function applyOverpaintColor(array: Uint8Array, start: number, end: numbe
export function clearOverpaint(array: Uint8Array, start: number, end: number) {
array.fill(0, start * 4, end * 4)
return true
}
export function createOverpaint(count: number, overpaintData?: OverpaintData): OverpaintData {

View File

@@ -13,7 +13,7 @@ import { Theme } from '../../../mol-theme/theme';
import { createColors } from '../color-data';
import { createSizes, getMaxSize } from '../size-data';
import { createMarkers } from '../marker-data';
import { ColorNames } from '../../../mol-util/color/tables';
import { ColorNames } from '../../../mol-util/color/names';
import { Sphere3D } from '../../../mol-math/geometry';
import { calculateBoundingSphere, TextureImage, createTextureImage } from '../../../mol-gl/renderable/util';
import { TextValues } from '../../../mol-gl/renderable/text';

View File

@@ -8,16 +8,18 @@
* Create 3d points for a polygon:
* 3 for a triangle, 4 for a rectangle, 5 for a pentagon, 6 for a hexagon...
*/
export function polygon(sideCount: number, shift: boolean) {
export function polygon(sideCount: number, shift: boolean, radius = -1) {
const points = new Float32Array(sideCount * 3)
const radius = sideCount <= 4 ? Math.sqrt(2) / 2 : 0.6
const r = radius === -1
? (sideCount <= 4 ? Math.sqrt(2) / 2 : 0.6)
: radius
const offset = shift ? 1 : 0
for (let i = 0, il = sideCount; i < il; ++i) {
const c = (i * 2 + offset) / sideCount * Math.PI
points[i * 3] = Math.cos(c) * radius
points[i * 3 + 1] = Math.sin(c) * radius
points[i * 3] = Math.cos(c) * r
points[i * 3 + 1] = Math.sin(c) * r
points[i * 3 + 2] = 0
}
return points

View File

@@ -1,10 +1,12 @@
/**
* 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>
*/
import { Vec3 } from '../../mol-math/linear-algebra';
import { Vec3, Mat4, Mat3 } from '../../mol-math/linear-algebra';
import { getNormalMatrix } from '../util';
import { NumberArray } from '../../mol-util/type-helpers';
export interface Primitive {
vertices: ArrayLike<number>
@@ -56,4 +58,22 @@ export function PrimitiveBuilder(triangleCount: number): PrimitiveBuilder {
},
getPrimitive: () => ({ vertices, normals, indices })
}
}
const tmpV = Vec3.zero()
const tmpMat3 = Mat3.zero()
/** Transform primitive in-place */
export function transformPrimitive(primitive: Primitive, t: Mat4) {
const { vertices, normals } = primitive
const n = getNormalMatrix(tmpMat3, t)
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)
// normal
Vec3.transformMat3(tmpV, Vec3.fromArray(tmpV, normals, i), n)
Vec3.toArray(tmpV, normals as NumberArray, i)
}
return primitive
}

View File

@@ -9,26 +9,39 @@ import { Primitive, PrimitiveBuilder } from './primitive';
import { polygon } from './polygon'
import { Cage } from './cage';
const on = Vec3.create(0, 0, -0.5), op = Vec3.create(0, 0, 0.5)
const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero(), d = Vec3.zero()
const on = Vec3(), op = Vec3()
const a = Vec3(), b = Vec3(), c = Vec3(), d = Vec3()
export const DefaultPrismProps = {
height: 1,
topCap: true,
bottomCap: true,
}
export type PrismProps = Partial<typeof DefaultPrismProps>
/**
* Create a prism with a base of 4 or more points
*/
export function Prism(points: ArrayLike<number>): Primitive {
export function Prism(points: ArrayLike<number>, props?: PrismProps): Primitive {
const sideCount = points.length / 3
if (sideCount < 4) throw new Error('need at least 4 points to build a prism')
const { height, topCap, bottomCap } = { ...DefaultPrismProps, ...props };
const count = 4 * sideCount
const builder = PrimitiveBuilder(count)
const halfHeight = height * 0.5
Vec3.set(on, 0, 0, -halfHeight)
Vec3.set(op, 0, 0, halfHeight)
// create sides
for (let i = 0; i < sideCount; ++i) {
const ni = (i + 1) % sideCount
Vec3.set(a, points[i * 3], points[i * 3 + 1], -0.5)
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -0.5)
Vec3.set(c, points[ni * 3], points[ni * 3 + 1], 0.5)
Vec3.set(d, points[i * 3], points[i * 3 + 1], 0.5)
Vec3.set(a, points[i * 3], points[i * 3 + 1], -halfHeight)
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -halfHeight)
Vec3.set(c, points[ni * 3], points[ni * 3 + 1], halfHeight)
Vec3.set(d, points[i * 3], points[i * 3 + 1], halfHeight)
builder.add(a, b, c)
builder.add(c, d, a)
}
@@ -36,12 +49,16 @@ export function Prism(points: ArrayLike<number>): Primitive {
// create bases
for (let i = 0; i < sideCount; ++i) {
const ni = (i + 1) % sideCount
Vec3.set(a, points[i * 3], points[i * 3 + 1], -0.5)
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -0.5)
builder.add(on, b, a)
Vec3.set(a, points[i * 3], points[i * 3 + 1], 0.5)
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], 0.5)
builder.add(a, b, op)
if (topCap) {
Vec3.set(a, points[i * 3], points[i * 3 + 1], -halfHeight)
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -halfHeight)
builder.add(on, b, a)
}
if (bottomCap) {
Vec3.set(a, points[i * 3], points[i * 3 + 1], halfHeight)
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], halfHeight)
builder.add(a, b, op)
}
}
return builder.getPrimitive()
@@ -70,20 +87,21 @@ export function HexagonalPrism() {
/**
* Create a prism cage
*/
export function PrismCage(points: ArrayLike<number>): Cage {
export function PrismCage(points: ArrayLike<number>, height = 1): Cage {
const sideCount = points.length / 3
// const count = 4 * sideCount
const vertices: number[] = []
const edges: number[] = []
const halfHeight = height * 0.5
let offset = 0
// vertices and side edges
for (let i = 0; i < sideCount; ++i) {
vertices.push(
points[i * 3], points[i * 3 + 1], -0.5,
points[i * 3], points[i * 3 + 1], 0.5
points[i * 3], points[i * 3 + 1], -halfHeight,
points[i * 3], points[i * 3 + 1], halfHeight
)
edges.push(offset, offset + 1)
offset += 2

View File

@@ -117,7 +117,7 @@ describe('renderer', () => {
expect(ctx.gl.getParameter(ctx.gl.VIEWPORT)[3]).toBe(48)
})
it('points', () => {
it('points', async () => {
const [ width, height ] = [ 32, 32 ]
const gl = createGl(width, height, { preserveDrawingBuffer: true })
const { ctx } = createRenderer(gl)
@@ -126,6 +126,7 @@ describe('renderer', () => {
const points = createPoints()
scene.add(points)
await scene.commit().run()
expect(ctx.stats.bufferCount).toBe(4);
expect(ctx.stats.textureCount).toBe(5);
expect(ctx.stats.vaoCount).toBe(5);
@@ -133,6 +134,7 @@ describe('renderer', () => {
expect(ctx.shaderCache.count).toBe(10);
scene.remove(points)
await scene.commit().run()
expect(ctx.stats.bufferCount).toBe(0);
expect(ctx.stats.textureCount).toBe(0);
expect(ctx.stats.vaoCount).toBe(0);

View File

@@ -159,6 +159,7 @@ export const GlobalUniformSchema = {
uPixelRatio: UniformSpec('f'),
uViewportHeight: UniformSpec('f'),
uViewport: UniformSpec('v4'),
uViewOffset: UniformSpec('v2'),
uCameraPosition: UniformSpec('v3'),
uNear: UniformSpec('f'),

View File

@@ -9,7 +9,7 @@ import { Camera } from '../mol-canvas3d/camera';
import Scene from './scene';
import { WebGLContext } from './webgl/context';
import { Mat4, Vec3, Vec4 } from '../mol-math/linear-algebra';
import { Mat4, Vec3, Vec4, Vec2 } from '../mol-math/linear-algebra';
import { Renderable } from './renderable';
import { Color } from '../mol-util/color';
import { ValueCell } from '../mol-util';
@@ -52,7 +52,7 @@ export const RendererParams = {
ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
roughness: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }),
reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
}
export type RendererProps = PD.Values<typeof RendererParams>
@@ -73,6 +73,8 @@ namespace Renderer {
const modelViewProjection = Mat4.mul(Mat4.identity(), modelView, camera.projection)
const invModelViewProjection = Mat4.invert(Mat4.identity(), modelViewProjection)
const viewOffset = camera.viewOffset.enabled ? Vec2.create(camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2()
const globalUniforms: GlobalUniformValues = {
uModel: ValueCell.create(Mat4.identity()),
uView: ValueCell.create(camera.view),
@@ -88,6 +90,7 @@ namespace Renderer {
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),
@@ -165,6 +168,7 @@ namespace Renderer {
ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection))
ValueCell.update(globalUniforms.uIsOrtho, camera.state.mode === 'orthographic' ? 1 : 0)
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)

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>
*/
@@ -12,6 +12,8 @@ import { Object3D } from './object3d';
import { Sphere3D } from '../mol-math/geometry';
import { Vec3 } from '../mol-math/linear-algebra';
import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
import { RuntimeContext, Task } from '../mol-task';
import { AsyncQueue } from '../mol-util/async-queue';
const boundaryHelper = new BoundaryHelper();
function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
@@ -40,17 +42,11 @@ function renderableSort(a: Renderable<RenderableValues & BaseValues>, b: Rendera
const drawProgramIdB = b.getProgram('color').id
const materialIdA = a.materialId
const materialIdB = b.materialId
const zA = a.values.boundingSphere.ref.value.center[2]
const zB = b.values.boundingSphere.ref.value.center[2]
if (drawProgramIdA !== drawProgramIdB) {
return drawProgramIdA - drawProgramIdB // sort by program id to minimize gl state changes
} else if (materialIdA !== materialIdB) {
return materialIdA - materialIdB // sort by material id to minimize gl state changes
} else if (zA !== zB) {
return a.state.opaque
? zA - zB // when opaque, draw closer elements first to minimize overdraw
: zB - zA // when transparent, draw elements last to maximize partial visibility
} else {
return a.id - b.id;
}
@@ -60,10 +56,13 @@ interface Scene extends Object3D {
readonly count: number
readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
readonly boundingSphere: Sphere3D
readonly isCommiting: boolean
update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean) => void
add: (o: GraphicsRenderObject) => Renderable<any>
add: (o: GraphicsRenderObject) => void // Renderable<any>
remove: (o: GraphicsRenderObject) => void
syncCommit: () => void
commit: () => Task<void>
has: (o: GraphicsRenderObject) => boolean
clear: () => void
forEach: (callbackFn: (value: Renderable<RenderableValues & BaseValues>, key: GraphicsRenderObject) => void) => void
@@ -74,15 +73,63 @@ namespace Scene {
const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>()
const renderables: Renderable<RenderableValues & BaseValues>[] = []
const boundingSphere = Sphere3D.zero()
let boundingSphereDirty = true
const object3d = Object3D.create()
const add = (o: GraphicsRenderObject) => {
if (!renderableMap.has(o)) {
const renderable = createRenderable(ctx, o)
renderables.push(renderable)
renderableMap.set(o, renderable)
boundingSphereDirty = true
return renderable;
} else {
console.warn(`RenderObject with id '${o.id}' already present`)
return renderableMap.get(o)!
}
}
const remove = (o: GraphicsRenderObject) => {
const renderable = renderableMap.get(o)
if (renderable) {
renderable.dispose()
renderables.splice(renderables.indexOf(renderable), 1)
renderableMap.delete(o)
boundingSphereDirty = true
}
}
const commitQueue = new AsyncQueue<any>();
const toAdd: GraphicsRenderObject[] = []
const toRemove: GraphicsRenderObject[] = []
type CommitParams = { toAdd: GraphicsRenderObject[], toRemove: GraphicsRenderObject[] }
const step = 100
const handle = async (ctx: RuntimeContext, arr: GraphicsRenderObject[], fn: (o: GraphicsRenderObject) => void, message: string) => {
for (let i = 0, il = arr.length; i < il; i += step) {
if (ctx.shouldUpdate) await ctx.update({ message, current: i, max: il })
for (let j = i, jl = Math.min(i + step, il); j < jl; ++j) {
fn(arr[j])
}
}
}
const commit = async (ctx: RuntimeContext, p: CommitParams) => {
await handle(ctx, p.toRemove, remove, 'Removing GraphicsRenderObjects')
await handle(ctx, p.toAdd, add, 'Adding GraphicsRenderObjects')
if (ctx.shouldUpdate) await ctx.update({ message: 'Sorting GraphicsRenderObjects' })
renderables.sort(renderableSort)
}
return {
get view () { return object3d.view },
get position () { return object3d.position },
get direction () { return object3d.direction },
get up () { return object3d.up },
get isCommiting () { return commitQueue.length > 0 },
update(objects, keepBoundingSphere) {
Object3D.update(object3d)
@@ -100,27 +147,35 @@ namespace Scene {
if (!keepBoundingSphere) boundingSphereDirty = true
},
add: (o: GraphicsRenderObject) => {
if (!renderableMap.has(o)) {
const renderable = createRenderable(ctx, o)
renderables.push(renderable)
renderables.sort(renderableSort)
renderableMap.set(o, renderable)
boundingSphereDirty = true
return renderable;
} else {
console.warn(`RenderObject with id '${o.id}' already present`)
return renderableMap.get(o)!
}
toAdd.push(o)
},
remove: (o: GraphicsRenderObject) => {
const renderable = renderableMap.get(o)
if (renderable) {
renderable.dispose()
renderables.splice(renderables.indexOf(renderable), 1)
renderables.sort(renderableSort)
renderableMap.delete(o)
boundingSphereDirty = true
}
toRemove.push(o)
},
syncCommit: () => {
for (let i = 0, il = toRemove.length; i < il; ++i) remove(toRemove[i])
toRemove.length = 0
for (let i = 0, il = toAdd.length; i < il; ++i) add(toAdd[i])
toAdd.length = 0
renderables.sort(renderableSort)
},
commit: () => {
const params = { toAdd: [ ...toAdd ], toRemove: [ ...toRemove ] }
toAdd.length = 0
toRemove.length = 0
return Task.create('Commiting GraphicsRenderObjects', async ctx => {
const removed = await commitQueue.enqueue(params);
if (!removed) return;
try {
await commit(ctx, params);
} finally {
commitQueue.handled(params);
}
}, () => {
commitQueue.remove(params);
})
},
has: (o: GraphicsRenderObject) => {
return renderableMap.has(o)

View File

@@ -23,6 +23,9 @@ export default `
float ta = 1.0 - vTransparency;
float at = 0.0;
// shift by view-offset during multi-sample rendering to allow for blending
vec2 coord = gl_FragCoord.xy + uViewOffset * 0.25;
#if defined(dTransparencyVariant_single)
const mat4 thresholdMatrix = mat4(
1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0,
@@ -30,9 +33,9 @@ export default `
4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0,
16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0
);
at = thresholdMatrix[int(intMod(gl_FragCoord.x, 4.0))][int(intMod(gl_FragCoord.y, 4.0))];
at = thresholdMatrix[int(intMod(coord.x, 4.0))][int(intMod(coord.y, 4.0))];
#elif defined(dTransparencyVariant_multi)
at = fract(dot(vec3(gl_FragCoord.xy, vGroup + 0.5), vec3(2.0, 7.0, 23.0) / 17.0f));
at = fract(dot(vec3(coord, vGroup + 0.5), vec3(2.0, 7.0, 23.0) / 17.0f));
#endif
if (ta < 0.99 && (ta < 0.01 || ta < at)) discard;

View File

@@ -9,6 +9,8 @@ varying float vMarker;
varying vec3 vViewPosition;
uniform vec2 uViewOffset;
uniform float uFogNear;
uniform float uFogFar;
uniform vec3 uFogColor;

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.311, IHM 1.0, CARB draft.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.313, IHM 1.01, CARB draft.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.311, IHM 1.0, CARB draft.
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.313, IHM 1.01, CARB draft.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.311, IHM 1.0, CARB draft.
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.313, IHM 1.01, CARB draft.
*
* @author molstar/ciftools package
*/
@@ -1471,6 +1471,62 @@ export const mmCIF_Schema = {
*/
'space_group_name_H-M': str,
},
/**
* These are internal RCSB records to keep track of data processing
* and status of the entry.
*/
pdbx_database_status: {
/**
* Code for status of file.
*/
status_code: Aliased<'PROC' | 'WAIT' | 'REL' | 'HOLD' | 'HPUB' | 'REFI' | 'OBS' | 'WDRN' | 'AUTH' | 'POLC' | 'REPL' | 'AUCO' | 'TRSF' | 'RMVD' | 'DEL' | 'REV' | 'UPD' | 'BIB'>(str),
/**
* Code for status of structure factor file.
*/
status_code_sf: Aliased<'PROC' | 'WAIT' | 'REL' | 'HOLD' | 'HPUB' | 'OBS' | 'WDRN' | 'AUTH' | 'POLC' | 'REPL' | 'RMVD'>(str),
/**
* Code for status of NMR constraints file.
*/
status_code_mr: Aliased<'PROC' | 'WAIT' | 'REL' | 'HOLD' | 'HPUB' | 'OBS' | 'WDRN' | 'AUTH' | 'POLC' | 'REPL' | 'RMVD'>(str),
/**
* The value of _pdbx_database_status.entry_id identifies the data block.
*/
entry_id: str,
/**
* The date of initial deposition. (The first message for
* deposition has been received.)
*/
recvd_initial_deposition_date: str,
/**
* This code indicates whether the entry belongs to
* Structural Genomics Project.
*/
SG_entry: Aliased<'Y' | 'N'>(str),
/**
* The site where the file was deposited.
*/
deposit_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BMRB' | 'BNL'>(str),
/**
* The site where the file was deposited.
*/
process_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BNL'>(str),
/**
* Code for status of chemical shift data file.
*/
status_code_cs: Aliased<'PROC' | 'WAIT' | 'AUTH' | 'POLC' | 'REPL' | 'REL' | 'HOLD' | 'HPUB' | 'OBS' | 'RMVD' | 'WDRN'>(str),
/**
* The methods development category in which this
* entry has been placed.
*/
methods_development_category: Aliased<'CAPRI' | 'CASP' | 'CASD-NMR' | 'FoldIt' | 'GPCR Dock' | 'D3R' | 'RNA-Puzzles'>(str),
/**
* A flag indicating that the entry is compatible with the PDB format.
*
* A value of 'N' indicates that the no PDB format data file is
* corresponding to this entry is available in the PDB archive.
*/
pdb_format_compatible: Aliased<'Y' | 'N'>(str),
},
/**
* The PDBX_NONPOLY_SCHEME category provides residue level nomenclature
* mapping for non-polymer entities.
@@ -1519,6 +1575,28 @@ export const mmCIF_Schema = {
*/
pdb_ins_code: str,
},
/**
* Data items in PDBX_DATABASE_RELATED contain references to entries
* that are related to the this entry.
*/
pdbx_database_related: {
/**
* The name of the database containing the related entry.
*/
db_name: str,
/**
* A description of the related entry.
*/
details: str,
/**
* The identifying code in the related database.
*/
db_id: str,
/**
* The identifying content type of the related entry.
*/
content_type: Aliased<'minimized average structure' | 'representative structure' | 'ensemble' | 'derivative structure' | 'native structure' | 'associated EM volume' | 'other EM volume' | 'associated NMR restraints' | 'associated structure factors' | 'associated SAS data' | 'protein target sequence and/or protocol data' | 'split' | 're-refinement' | 'complete structure' | 'unspecified' | 'other'>(str),
},
/**
* Data items in the CHEM_COMP_IDENTIFIER category provide
* identifiers for chemical components.
@@ -1549,6 +1627,85 @@ export const mmCIF_Schema = {
*/
program_version: str,
},
/**
* Data items in the PDBX_UNOBS_OR_ZERO_OCC_RESIDUES category list the
* residues within the entry that are not observed or have zero occupancy.
*/
pdbx_unobs_or_zero_occ_residues: {
/**
* The value of _pdbx_unobs_or_zero_occ_residues.id must uniquely identify
* each item in the PDBX_UNOBS_OR_ZERO_OCC_RESIDUES list.
*
* This is an integer serial number.
*/
id: int,
/**
* The value of polymer flag indicates whether the unobserved or
* zero occupancy residue is part of a polymer chain or not
*/
polymer_flag: Aliased<'Y' | 'N'>(str),
/**
* The value of occupancy flag indicates whether the residue
* is unobserved (= 1) or the coordinates have an occupancy of zero (=0)
*/
occupancy_flag: Aliased<'1' | '0'>(int),
/**
* Part of the identifier for the unobserved or zero occupancy residue.
*
* This data item is a pointer to _atom_site.pdbx_PDB_model_num in the
* ATOM_SITE category.
*/
PDB_model_num: int,
/**
* Part of the identifier for the unobserved or zero occupancy residue.
*
* This data item is a pointer to _atom_site.auth_asym_id in the
* ATOM_SITE category.
*/
auth_asym_id: str,
/**
* Part of the identifier for the unobserved or zero occupancy residue.
*
* This data item is a pointer to _atom_site.auth_comp_id in the
* ATOM_SITE category.
*/
auth_comp_id: str,
/**
* Part of the identifier for the unobserved or zero occupancy residue.
*
* This data item is a pointer to _atom_site.auth_seq_id in the
* ATOM_SITE category.
*/
auth_seq_id: str,
/**
* Part of the identifier for the unobserved or zero occupancy residue.
*
* This data item is a pointer to _atom_site.pdbx_PDB_ins_code in the
* ATOM_SITE category.
*/
PDB_ins_code: str,
/**
* Part of the identifier for the unobserved or zero occupancy residue.
*
* This data item is a pointer to _atom_site.label_asym_id in the
* ATOM_SITE category.
*/
label_asym_id: str,
/**
* Part of the identifier for the unobserved or zero occupancy residue.
*
* This data item is a pointer to _atom_site.label_comp_id in the
* ATOM_SITE category.
*/
label_comp_id: str,
/**
* Part of the identifier for the unobserved or zero occupancy residue.
*
* This data item is a pointer to _atom_site.label_seq_id in the
* ATOM_SITE category.
*/
label_seq_id: int,
},
/**
* Data items in the PDBX_STRUCT_MOD_RESIDUE category list the
* modified polymer components in the entry and provide some
@@ -2079,10 +2236,10 @@ export const mmCIF_Schema = {
pdbx_end_seq_num: int,
},
/**
* Data items in the PDBX_ENTITY_DESCRIPTOR category provide
* Data items in the PDBX_ENTITY_BRANCH_DESCRIPTOR category provide
* string descriptors of entity chemical structure.
*/
pdbx_entity_descriptor: {
pdbx_entity_branch_descriptor: {
/**
* This data item is a pointer to _entity_poly.entity_id in the ENTITY
* category.
@@ -2892,7 +3049,7 @@ export const mmCIF_Schema = {
/**
* The name of the database containing the dataset entry.
*/
db_name: Aliased<'PDB' | 'BMRB' | 'EMDB' | 'EMPIAR' | 'SASBDB' | 'PRIDE' | 'MODEL ARCHIVE' | 'MASSIVE' | 'BioGRID' | 'Other'>(str),
db_name: Aliased<'PDB' | 'PDB-Dev' | 'BMRB' | 'EMDB' | 'EMPIAR' | 'SASBDB' | 'PRIDE' | 'MODEL ARCHIVE' | 'MASSIVE' | 'BioGRID' | 'Other'>(str),
/**
* The accession code for the database entry.
*/

View File

@@ -17,7 +17,9 @@ export interface PositionData {
/** subset of indices into the x/y/z/radius arrays */
indices: OrderedSet,
/** optional element radius */
radius?: ArrayLike<number>
radius?: ArrayLike<number>,
/** optional element id */
id?: ArrayLike<number>,
}
export type DensityData = {

View File

@@ -15,7 +15,7 @@ export async function GaussianDensityCPU(ctx: RuntimeContext, position: Position
const { resolution, radiusOffset, smoothness } = props
const scaleFactor = 1 / resolution
const { indices, x, y, z } = position
const { indices, x, y, z, id } = position
const n = OrderedSet.size(indices)
const radii = new Float32Array(n)
@@ -100,7 +100,7 @@ export async function GaussianDensityCPU(ctx: RuntimeContext, position: Position
data[idx] += dens
if (dens > densData[idx]) {
densData[idx] = dens
idData[idx] = i
idData[idx] = id ? id[i] : i
}
}
}

View File

@@ -224,7 +224,7 @@ function prepareGaussianDensityData(position: PositionData, box: Box3D, radius:
const { resolution, radiusOffset } = props
const scaleFactor = 1 / resolution
const { indices, x, y, z } = position
const { indices, x, y, z, id } = position
const n = OrderedSet.size(indices)
const positions = new Float32Array(n * 3)
@@ -242,7 +242,7 @@ function prepareGaussianDensityData(position: PositionData, box: Box3D, radius:
const r = radius(j) + radiusOffset
if (maxRadius < r) maxRadius = r
radii[i] = r
groups[i] = i
groups[i] = id ? id[i] : i
}
const pad = maxRadius * 2 + resolution * 4

View File

@@ -169,7 +169,7 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
const dd = rad - d
if (dd < data[idx]) {
data[idx] = dd
idData[idx] = i
idData[idx] = id[i]
}
}
}
@@ -282,7 +282,7 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
// Is this grid point closer to a or b?
// Take dot product of atob and gridpoint->p (dx, dy, dz)
const dp = dx * atob[0] + dy * atob[1] + dz * atob[2]
idData[idx] = OrderedSet.indexOf(position.indices, dp < 0.0 ? b : a)
idData[idx] = id[OrderedSet.indexOf(position.indices, dp < 0.0 ? b : a)]
}
}
}
@@ -291,16 +291,22 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
}
}
async function projectTorii () {
for (let i = 0; i < n; ++i) {
function projectToriiRange (begI: number, endI: number) {
for (let i = begI; i < endI; ++i) {
const k = OrderedSet.getAt(indices, i)
lookup3d.find(px[k], py[k], pz[k], radius[k])
for (let j = 0, jl = neighbours.count; j < jl; ++j) {
const l = OrderedSet.getAt(indices, neighbours.indices[j])
if (k < l) projectTorus(k, l)
}
}
}
if (i % updateChunk === 0 && ctx.shouldUpdate) {
async function projectTorii() {
for (let i = 0; i < n; i += updateChunk) {
projectToriiRange(i, Math.min(i + updateChunk, n))
if (ctx.shouldUpdate) {
await ctx.update({ message: 'projecting torii', current: i, max: n })
}
}
@@ -317,7 +323,7 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
const lookup3d = GridLookup3D(position, cellSize)
const neighbours = lookup3d.result
const box = lookup3d.boundary.box
const { indices, x: px, y: py, z: pz, radius } = position
const { indices, x: px, y: py, z: pz, id, radius } = position
const n = OrderedSet.size(indices)
const pad = maxRadius * 2 + resolution

View File

@@ -90,7 +90,7 @@ namespace Spacegroup {
export function getSymmetryOperator(spacegroup: Spacegroup, index: number, i: number, j: number, k: number): SymmetryOperator {
const operator = updateOperatorMatrix(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));
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k), index);
}
function getOperatorMatrix(ids: number[]) {

View File

@@ -6,6 +6,7 @@
import { Vec3, Mat4, Mat3, Quat } from '../linear-algebra/3d'
import { lerp as scalar_lerp } from '../../mol-math/interpolate';
import { defaults } from '../../mol-util';
interface SymmetryOperator {
readonly name: string,
@@ -21,6 +22,8 @@ interface SymmetryOperator {
readonly ncsId: string,
readonly hkl: Vec3,
/** spacegroup symmetry operator index, -1 if not applicable */
readonly spgrOp: number,
readonly matrix: Mat4,
// cache the inverse of the transform
@@ -35,12 +38,13 @@ namespace SymmetryOperator {
export const RotationTranslationEpsilon = 0.005;
export function create(name: string, matrix: Mat4, assembly: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3): SymmetryOperator {
export function create(name: string, matrix: Mat4, assembly: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3, spgrOp?: number): SymmetryOperator {
const _hkl = hkl ? Vec3.clone(hkl) : Vec3.zero();
spgrOp = defaults(spgrOp, -1)
ncsId = ncsId || ''
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, ncsId };
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId };
if (!Mat4.isRotationAndTranslation(matrix, RotationTranslationEpsilon)) throw new Error(`Symmetry operator (${name}) must be a composition of rotation and translation.`);
return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, ncsId };
return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, spgrOp, ncsId };
}
export function checkIfRotationAndTranslation(rot: Mat3, offset: Vec3) {
@@ -106,11 +110,11 @@ namespace SymmetryOperator {
/**
* Apply the 1st and then 2nd operator. ( = second.matrix * first.matrix).
* Keep `name`, `assembly`, `ncsId` and `hkl` properties from second.
* Keep `name`, `assembly`, `ncsId`, `hkl` and `spgrOpId` properties from second.
*/
export function compose(first: SymmetryOperator, second: SymmetryOperator) {
const matrix = Mat4.mul(Mat4.zero(), second.matrix, first.matrix);
return create(second.name, matrix, second.assembly, second.ncsId, second.hkl);
return create(second.name, matrix, second.assembly, second.ncsId, second.hkl, second.spgrOp);
}
export interface CoordinateMapper<T extends number> { (index: T, slot: Vec3): Vec3 }

View File

@@ -16,7 +16,7 @@ import { ChunkedArray } from '../../mol-data/util';
import { arrayMax, fillSerial } from '../../mol-util/array';
import { Column } from '../../mol-data/db';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ColorNames } from '../../mol-util/color/tables';
import { ColorNames } from '../../mol-util/color/names';
import { deepClone } from '../../mol-util/object';
// TODO support 'edge' element, see https://www.mathworks.com/help/vision/ug/the-ply-format.html

View File

@@ -0,0 +1,156 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { _parse_mmCif } from '../mmcif/parser';
import { CifCategory, CifField } from '../../../mol-io/reader/cif';
import { Table, Column } from '../../../mol-data/db';
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
import { WaterNames } from '../../../mol-model/structure/model/types';
import { SetUtils } from '../../../mol-util/set';
type Component = Table.Row<Pick<mmCIF_Schema['chem_comp'], 'id' | 'name' | 'type'>>
const ProteinAtomIdsList = [
new Set([ 'CA' ]),
new Set([ 'C' ]),
new Set([ 'N' ])
]
const RnaAtomIdsList = [
new Set([ 'P', 'O3\'', 'O3*' ]),
new Set([ 'C4\'', 'C4*' ]),
new Set([ 'O2\'', 'O2*', 'F2\'', 'F2*' ])
]
const DnaAtomIdsList = [
new Set([ 'P', 'O3\'', 'O3*' ]),
new Set([ 'C3\'', 'C3*' ]),
new Set([ 'O2\'', 'O2*', 'F2\'', 'F2*' ])
]
const StandardComponents = (function() {
const map = new Map<string, Component>()
const components: Component[] = [
{ id: 'HIS', name: 'HISTIDINE', type: 'L-peptide linking' },
{ id: 'ARG', name: 'ARGININE', type: 'L-peptide linking' },
{ id: 'LYS', name: 'LYSINE', type: 'L-peptide linking' },
{ id: 'ILE', name: 'ISOLEUCINE', type: 'L-peptide linking' },
{ id: 'PHE', name: 'PHENYLALANINE', type: 'L-peptide linking' },
{ id: 'LEU', name: 'LEUCINE', type: 'L-peptide linking' },
{ id: 'TRP', name: 'TRYPTOPHAN', type: 'L-peptide linking' },
{ id: 'ALA', name: 'ALANINE', type: 'L-peptide linking' },
{ id: 'MET', name: 'METHIONINE', type: 'L-peptide linking' },
{ id: 'CYS', name: 'CYSTEINE', type: 'L-peptide linking' },
{ id: 'ASN', name: 'ASPARAGINE', type: 'L-peptide linking' },
{ id: 'VAL', name: 'VALINE', type: 'L-peptide linking' },
{ id: 'GLY', name: 'GLYCINE', type: 'peptide linking' },
{ id: 'SER', name: 'SERINE', type: 'L-peptide linking' },
{ id: 'GLN', name: 'GLUTAMINE', type: 'L-peptide linking' },
{ id: 'TYR', name: 'TYROSINE', type: 'L-peptide linking' },
{ id: 'ASP', name: 'ASPARTIC ACID', type: 'L-peptide linking' },
{ id: 'GLU', name: 'GLUTAMIC ACID', type: 'L-peptide linking' },
{ id: 'THR', name: 'THREONINE', type: 'L-peptide linking' },
{ id: 'SEC', name: 'SELENOCYSTEINE', type: 'L-peptide linking' },
{ id: 'PYL', name: 'PYRROLYSINE', type: 'L-peptide linking' },
{ id: 'A', name: 'ADENOSINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
{ id: 'C', name: 'CYTIDINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
{ id: 'T', name: 'THYMIDINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
{ id: 'G', name: 'GUANOSINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
{ id: 'I', name: 'INOSINIC ACID', type: 'RNA linking' },
{ id: 'U', name: 'URIDINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
{ id: 'DA', name: '2\'-DEOXYADENOSINE-5\'-MONOPHOSPHATE', type: 'DNA linking' },
{ id: 'DC', name: '2\'-DEOXYCYTIDINE-5\'-MONOPHOSPHATE', type: 'DNA linking' },
{ id: 'DT', name: 'THYMIDINE-5\'-MONOPHOSPHATE', type: 'DNA linking' },
{ id: 'DG', name: '2\'-DEOXYGUANOSINE-5\'-MONOPHOSPHATE', type: 'DNA linking' },
{ id: 'DI', name: '2\'-DEOXYINOSINE-5\'-MONOPHOSPHATE', type: 'DNA linking' },
{ id: 'DU', name: '2\'-DEOXYURIDINE-5\'-MONOPHOSPHATE', type: 'DNA linking' },
]
components.forEach(c => map.set(c.id, c))
return map
})()
export class ComponentBuilder {
private namesMap = new Map<string, string>()
private comps = new Map<string, Component>()
private ids: string[] = []
private names: string[] = []
private types: mmCIF_Schema['chem_comp']['type']['T'][] = []
private set(c: Component) {
this.comps.set(c.id, c)
this.ids.push(c.id)
this.names.push(c.name)
this.types.push(c.type)
}
private getAtomIds(index: number) {
const atomIds = new Set<string>()
let prevSeqId = this.seqId.value(index)
while (index < this.seqId.rowCount) {
const seqId = this.seqId.value(index)
if (seqId !== prevSeqId) break
atomIds.add(this.atomId.value(index))
prevSeqId - seqId
index += 1
}
return atomIds
}
private hasAtomIds (atomIds: Set<string>, atomIdsList: Set<string>[]) {
for (let i = 0, il = atomIdsList.length; i < il; ++i) {
if (!SetUtils.areIntersecting(atomIds, atomIdsList[i])) {
return false
}
}
return true
}
private getType(atomIds: Set<string>): Component['type'] {
if (this.hasAtomIds(atomIds, ProteinAtomIdsList)) {
return 'peptide linking'
} else if (this.hasAtomIds(atomIds, RnaAtomIdsList)) {
return 'RNA linking'
} else if (this.hasAtomIds(atomIds, DnaAtomIdsList)) {
return 'DNA linking'
} else {
return 'other'
}
}
has(compId: string) { return this.comps.has(compId) }
get(compId: string) { return this.comps.get(compId) }
add(compId: string, index: number) {
if (!this.has(compId)) {
if (StandardComponents.has(compId)) {
this.set(StandardComponents.get(compId)!)
} else if (WaterNames.has(compId)) {
this.set({ id: compId, name: 'WATER', type: 'non-polymer' })
} else {
const type = this.getType(this.getAtomIds(index))
this.set({ id: compId, name: this.namesMap.get(compId) || compId, type })
}
}
return this.get(compId)!
}
getChemCompCategory() {
const chemComp: CifCategory.SomeFields<mmCIF_Schema['chem_comp']> = {
id: CifField.ofStrings(this.ids),
name: CifField.ofStrings(this.names),
type: CifField.ofStrings(this.types),
}
return CifCategory.ofFields('chem_comp', chemComp)
}
setNames(names: [string, string][]) {
names.forEach(n => this.namesMap.set(n[0], n[1]))
}
constructor(private seqId: Column<number>, private atomId: Column<string>) {
}
}

View File

@@ -0,0 +1,80 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { CifCategory, CifField } from '../../../mol-io/reader/cif';
import { MoleculeType, isPolymer } from '../../../mol-model/structure/model/types';
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
export type EntityCompound = { chains: string[], description: string }
export class EntityBuilder {
private count = 0
private ids: string[] = []
private types: string[] = []
private descriptions: string[] = []
private compoundsMap = new Map<string, string>()
private namesMap = new Map<string, string>()
private heteroMap = new Map<string, string>()
private chainMap = new Map<string, string>()
private waterId?: string
private set(type: string, description: string) {
this.count += 1
this.ids.push(`${this.count}`)
this.types.push(type)
this.descriptions.push(description)
}
getEntityId(compId: string, moleculeType: MoleculeType, chainId: string): string {
if (moleculeType === MoleculeType.water) {
if (this.waterId === undefined) {
this.set('water', 'Water')
this.waterId = `${this.count}`
}
return this.waterId;
} else if (isPolymer(moleculeType)) {
if (this.compoundsMap.has(chainId)) {
return this.compoundsMap.get(chainId)!
} else {
if (!this.chainMap.has(chainId)) {
this.set('polymer', `Polymer ${this.chainMap.size + 1}`)
this.chainMap.set(chainId, `${this.count}`)
}
return this.chainMap.get(chainId)!
}
} else {
if (!this.heteroMap.has(compId)) {
this.set('non-polymer', this.namesMap.get(compId) || compId)
this.heteroMap.set(compId, `${this.count}`)
}
return this.heteroMap.get(compId)!
}
}
getEntityCategory() {
const entity: CifCategory.SomeFields<mmCIF_Schema['entity']> = {
id: CifField.ofStrings(this.ids),
type: CifField.ofStrings(this.types),
pdbx_description: CifField.ofStrings(this.descriptions),
}
return CifCategory.ofFields('entity', entity)
}
setCompounds(compounds: EntityCompound[]) {
for (let i = 0, il = compounds.length; i < il; ++i) {
const { chains, description } = compounds[i]
this.set('polymer', description)
for (let j = 0, jl = chains.length; j < jl; ++j) {
this.compoundsMap.set(chains[j], `${this.count}`)
}
}
}
setNames(names: [string, string][]) {
names.forEach(n => this.namesMap.set(n[0], n[1]))
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { memoize1 } from '../../../mol-util/memoize';
const ChainIdAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
function _getChainId (index: number) {
const n = ChainIdAlphabet.length
let j = index
let k = 0
let chainId = ChainIdAlphabet[j % n]
while (j >= n) {
j = Math.floor(j / n)
chainId += ChainIdAlphabet[j % n]
k += 1
}
if (k >= 5) {
console.warn('getChainId overflow')
}
return chainId
}
export const getChainId = memoize1(_getChainId)

View File

@@ -13,62 +13,103 @@ 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 { guessElementSymbolString } from './util';
import { MoleculeType, getMoleculeType } from '../../mol-model/structure/model/types';
import { ComponentBuilder } from './common/component';
import { getChainId } from './common/util';
import { EntityBuilder } from './common/entity';
// TODO multi model files
// TODO seperate chains
// TODO better entity handling
// TODO improve performance
function _entity(): { [K in keyof mmCIF_Schema['entity']]?: CifField } {
return {
id: CifField.ofStrings(['1', '2', '3']),
type: CifField.ofStrings(['polymer', 'non-polymer', 'water'])
}
}
function _atom_site(atoms: GroAtoms): { [K in keyof mmCIF_Schema['atom_site']]?: CifField } {
const auth_asym_id = CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str))
function getCategories(atoms: GroAtoms) {
const auth_atom_id = CifField.ofColumn(atoms.atomName)
const auth_comp_id = CifField.ofColumn(atoms.residueName)
const auth_seq_id = CifField.ofColumn(atoms.residueNumber)
return {
const entityIds = new Array<string>(atoms.count)
const asymIds = new Array<string>(atoms.count)
const seqIds = new Uint32Array(atoms.count)
const ids = new Uint32Array(atoms.count)
const entityBuilder = new EntityBuilder()
const componentBuilder = new ComponentBuilder(atoms.residueNumber, atoms.atomName)
let currentEntityId = ''
let currentAsymIndex = 0
let currentAsymId = ''
let currentSeqId = 0
let prevMoleculeType = MoleculeType.unknown
let prevResidueNumber = -1
for (let i = 0, il = atoms.count; i < il; ++i) {
const residueNumber = atoms.residueNumber.value(i)
if (residueNumber !== prevResidueNumber) {
const compId = atoms.residueName.value(i)
const moleculeType = getMoleculeType(componentBuilder.add(compId, i).type, compId)
if (moleculeType !== prevMoleculeType || (
residueNumber !== prevResidueNumber + 1 && !(
// gro format allows only for 5 character residueNumbers, handle overflow here
prevResidueNumber === 99999 && residueNumber === 0
)
)) {
currentAsymId = getChainId(currentAsymIndex)
currentAsymIndex += 1
currentSeqId = 0
}
currentEntityId = entityBuilder.getEntityId(compId, moleculeType, currentAsymId)
currentSeqId += 1
prevResidueNumber = residueNumber
prevMoleculeType = moleculeType
}
entityIds[i] = currentEntityId
asymIds[i] = currentAsymId
seqIds[i] = currentSeqId
ids[i] = i
}
const auth_asym_id = CifField.ofColumn(Column.ofStringArray(asymIds))
const atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = {
auth_asym_id,
auth_atom_id,
auth_comp_id,
auth_seq_id,
auth_seq_id: CifField.ofColumn(atoms.residueNumber),
B_iso_or_equiv: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)),
Cartn_x: CifField.ofNumbers(Column.mapToArray(atoms.x, x => x * 10, Float32Array)),
Cartn_y: CifField.ofNumbers(Column.mapToArray(atoms.y, y => y * 10, Float32Array)),
Cartn_z: CifField.ofNumbers(Column.mapToArray(atoms.z, z => z * 10, Float32Array)),
group_PDB: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
id: CifField.ofColumn(atoms.atomNumber),
id: CifField.ofColumn(Column.ofIntArray(ids)),
label_alt_id: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
label_asym_id: auth_asym_id,
label_atom_id: auth_atom_id,
label_comp_id: auth_comp_id,
label_seq_id: auth_seq_id,
label_entity_id: CifField.ofColumn(Column.ofConst('1', atoms.count, Column.Schema.str)),
label_seq_id: CifField.ofColumn(Column.ofIntArray(seqIds)),
label_entity_id: CifField.ofColumn(Column.ofStringArray(entityIds)),
occupancy: CifField.ofColumn(Column.ofConst(1, atoms.count, Column.Schema.float)),
type_symbol: CifField.ofStrings(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))),
// type_symbol: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
pdbx_PDB_ins_code: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst('1', atoms.count, Column.Schema.str)),
}
return {
entity: entityBuilder.getEntityCategory(),
chem_comp: componentBuilder.getChemCompCategory(),
atom_site: CifCategory.ofFields('atom_site', atom_site)
}
}
async function groToMmCif(gro: GroFile) {
const categories = {
entity: CifCategory.ofFields('entity', _entity()),
atom_site: CifCategory.ofFields('atom_site', _atom_site(gro.structures[0].atoms))
} as any;
const categories = getCategories(gro.structures[0].atoms)
return {
header: 'GRO',
header: gro.structures[0].header.title,
categoryNames: Object.keys(categories),
categories
};

View File

@@ -12,7 +12,7 @@ import { Tensor, Vec3 } from '../../../mol-math/linear-algebra';
import { RuntimeContext } from '../../../mol-task';
import UUID from '../../../mol-util/uuid';
import { Model } from '../../../mol-model/structure/model/model';
import { Entities } from '../../../mol-model/structure/model/properties/common';
import { Entities, ChemicalComponent, MissingResidue } from '../../../mol-model/structure/model/properties/common';
import { CustomProperties } from '../../../mol-model/structure';
import { ModelSymmetry } from '../../../mol-model/structure/model/properties/symmetry';
import { createAssemblies } from './assembly';
@@ -23,7 +23,6 @@ import { getSecondaryStructure } from './secondary-structure';
import { getSequence } from './sequence';
import { sortAtomSite } from './sort';
import { StructConn } from './bonds/struct_conn';
import { ChemicalComponent } from '../../../mol-model/structure/model/properties/chemical-component';
import { getMoleculeType, MoleculeType, getEntityType } from '../../../mol-model/structure/model/types';
import { ModelFormat } from '../format';
import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants';
@@ -98,14 +97,36 @@ function getModifiedResidueNameMap(format: mmCIF_Format): Model['properties']['m
return { parentId, details };
}
function getMissingResidues(format: mmCIF_Format): Model['properties']['missingResidues'] {
const map = new Map<string, MissingResidue>();
const c = format.data.pdbx_unobs_or_zero_occ_residues
const getKey = (model_num: number, asym_id: string, seq_id: number) => {
return `${model_num}|${asym_id}|${seq_id}`
}
for (let i = 0, il = c._rowCount; i < il; ++i) {
const key = getKey(c.PDB_model_num.value(i), c.label_asym_id.value(i), c.label_seq_id.value(i))
map.set(key, { polymer_flag: c.polymer_flag.value(i), occupancy_flag: c.occupancy_flag.value(i) })
}
return {
has: (model_num: number, asym_id: string, seq_id: number) => {
return map.has(getKey(model_num, asym_id, seq_id))
},
get: (model_num: number, asym_id: string, seq_id: number) => {
return map.get(getKey(model_num, asym_id, seq_id))
},
size: map.size
}
}
function getChemicalComponentMap(format: mmCIF_Format): Model['properties']['chemicalComponentMap'] {
const map = new Map<string, ChemicalComponent>();
const { chem_comp } = format.data
if (chem_comp._rowCount > 0) {
const { id } = format.data.chem_comp
for (let i = 0, il = id.rowCount; i < il; ++i) {
map.set(id.value(i), Table.getRow(format.data.chem_comp, i))
}
const { id } = chem_comp
for (let i = 0, il = id.rowCount; i < il; ++i) {
map.set(id.value(i), Table.getRow(chem_comp, i))
}
return map
}
@@ -158,6 +179,7 @@ const getUniqueComponentNames = memoize1((format: mmCIF_Format) => {
export interface FormatData {
modifiedResidues: Model['properties']['modifiedResidues']
missingResidues: Model['properties']['missingResidues']
chemicalComponentMap: Model['properties']['chemicalComponentMap']
saccharideComponentMap: Model['properties']['saccharideComponentMap']
}
@@ -165,6 +187,7 @@ export interface FormatData {
function getFormatData(format: mmCIF_Format): FormatData {
return {
modifiedResidues: getModifiedResidueNameMap(format),
missingResidues: getMissingResidues(format),
chemicalComponentMap: getChemicalComponentMap(format),
saccharideComponentMap: getSaccharideComponentMap(format)
}
@@ -172,27 +195,32 @@ function getFormatData(format: mmCIF_Format): FormatData {
function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, formatData: FormatData, previous?: Model): Model {
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, formatData, previous);
const modelNum = atom_site.pdbx_PDB_model_num.value(0)
if (previous && atomic.sameAsPrevious) {
return {
...previous,
id: UUID.create22(),
modelNum: atom_site.pdbx_PDB_model_num.value(0),
modelNum,
atomicConformation: atomic.conformation,
_dynamicPropertyData: Object.create(null)
};
}
const coarse = EmptyIHMCoarse;
const label = format.data.entry.id.valueKind(0) === Column.ValueKind.Present
const entry = format.data.entry.id.valueKind(0) === Column.ValueKind.Present
? format.data.entry.id.value(0)
: format.data._name;
const label: string[] = []
if (entry) label.push(entry)
if (format.data.struct.title.valueKind(0) === Column.ValueKind.Present) label.push(format.data.struct.title.value(0))
return {
id: UUID.create22(),
label,
entry: label,
label: label.join(' | '),
entry,
sourceData: format,
modelNum: atom_site.pdbx_PDB_model_num.value(0),
modelNum,
entities,
symmetry: getSymmetry(format),
sequence: getSequence(format.data, entities, atomic.hierarchy, formatData.modifiedResidues.parentId),
@@ -216,11 +244,16 @@ function createModelIHM(format: mmCIF_Format, data: IHMData, formatData: FormatD
const entry = format.data.entry.id.valueKind(0) === Column.ValueKind.Present
? format.data.entry.id.value(0)
: format.data._name;
const label = data.model_group_name ? `${data.model_name}: ${data.model_group_name}` : data.model_name
const label: string[] = []
if (entry) label.push(entry)
if (format.data.struct.title.valueKind(0) === Column.ValueKind.Present) label.push(format.data.struct.title.value(0))
if (data.model_group_name) label.push(data.model_name)
if (data.model_group_name) label.push(data.model_group_name)
return {
id: UUID.create22(),
label,
label: label.join(' | '),
entry,
sourceData: format,
modelNum: data.model_id,
@@ -259,14 +292,16 @@ function getEntities(format: mmCIF_Format): Entities {
if (!format.data.entity.id.isDefined) {
const entityIds = new Set<string>()
const entityList: Partial<Table.Row<mmCIF_Schema['entity']>>[] = []
const ids: mmCIF_Schema['entity']['id']['T'][] = []
const types: mmCIF_Schema['entity']['type']['T'][] = []
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)) {
entityList.push({ id: entityId, type: getEntityType(label_comp_id.value(i)) })
entityIds.add(entityId)
ids.push(entityId)
types.push(getEntityType(label_comp_id.value(i)))
}
}
@@ -274,8 +309,8 @@ function getEntities(format: mmCIF_Format): Entities {
for (let i = 0 as ElementIndex, il = format.data.ihm_sphere_obj_site._rowCount; i < il; i++) {
const entityId = sphere_entity_id.value(i);
if (!entityIds.has(entityId)) {
entityList.push({ id: entityId, type: 'polymer' })
entityIds.add(entityId)
ids.push(entityId)
types.push('polymer')
}
}
@@ -283,12 +318,16 @@ function getEntities(format: mmCIF_Format): Entities {
for (let i = 0 as ElementIndex, il = format.data.ihm_gaussian_obj_site._rowCount; i < il; i++) {
const entityId = gaussian_entity_id.value(i);
if (!entityIds.has(entityId)) {
entityList.push({ id: entityId, type: 'polymer' })
entityIds.add(entityId)
ids.push(entityId)
types.push('polymer')
}
}
entityData = Table.ofRows(mmCIF_Schema.entity, entityList)
entityData = Table.ofColumns(mmCIF_Schema.entity, {
...format.data.entity,
id: Column.ofArray({ array: ids, schema: mmCIF_Schema.entity.id }),
type: Column.ofArray({ array: types, schema: mmCIF_Schema.entity.type }),
})
} else {
entityData = format.data.entity;
}

View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Tokens } from '../../../mol-io/reader/common/text/tokenizer';
import { EntityCompound } from '../common/entity';
const Spec = {
'MOL_ID': '',
'MOLECULE': '',
'CHAIN': '',
'FRAGMENT': '',
'SYNONYM': '',
'EC': '',
'ENGINEERED': '',
'MUTATION': '',
'OTHER_DETAILS': ''
}
type Spec = keyof typeof Spec
export function parseCmpnd(lines: Tokens, lineStart: number, lineEnd: number) {
const getLine = (n: number) => lines.data.substring(lines.indices[2 * n], lines.indices[2 * n + 1])
let currentSpec: Spec | undefined
let currentCompound: EntityCompound = { chains: [], description: '' }
const Compounds: EntityCompound[] = []
for (let i = lineStart; i < lineEnd; i++) {
let line = getLine(i)
// COLUMNS DATA TYPE FIELD DEFINITION
// ----------------------------------------------------------------------------------
// 1 - 6 Record name "COMPND"
// 8 - 10 Continuation continuation Allows concatenation of multiple records.
// 11 - 80 Specification compound Description of the molecular components.
// list
const cmpnd = line.substr(10, 70).trim()
const cmpndSpecEnd = cmpnd.indexOf(':')
const cmpndSpec = cmpnd.substring(0, cmpndSpecEnd)
let value: string
if (cmpndSpec in Spec) {
currentSpec = cmpndSpec as Spec
value = cmpnd.substring(cmpndSpecEnd + 2)
} else {
value = cmpnd
}
value = value.replace(/;$/, '')
if (currentSpec === 'MOL_ID') {
currentCompound = {
chains: [],
description: ''
}
Compounds.push(currentCompound)
} else if (currentSpec === 'MOLECULE') {
if (currentCompound.description) currentCompound.description += ' '
currentCompound.description += value
} else if (currentSpec === 'CHAIN') {
Array.prototype.push.apply(currentCompound.chains, value.split(/\s*,\s*/))
}
}
return Compounds
}
export function parseHetnam(lines: Tokens, lineStart: number, lineEnd: number) {
const getLine = (n: number) => lines.data.substring(lines.indices[2 * n], lines.indices[2 * n + 1])
const hetnams = new Map<string, string>()
for (let i = lineStart; i < lineEnd; i++) {
let line = getLine(i)
// COLUMNS DATA TYPE FIELD DEFINITION
// ----------------------------------------------------------------------------
// 1 - 6 Record name "HETNAM"
// 9 - 10 Continuation continuation Allows concatenation of multiple records.
// 12 - 14 LString(3) hetID Het identifier, right-justified.
// 16 - 70 String text Chemical name.
const het = line.substr(11, 3).trim()
const name = line.substr(15).trim()
if (hetnams.has(het)) {
hetnams.set(het, `${hetnams.get(het)!} ${name}`)
} else {
hetnams.set(het, name)
}
}
return hetnams
}

View File

@@ -11,19 +11,16 @@ import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
import { TokenBuilder, Tokenizer } from '../../../mol-io/reader/common/text/tokenizer';
import { PdbFile } from '../../../mol-io/reader/pdb/schema';
import { parseCryst1, parseRemark350, parseMtrix } from './assembly';
import { WaterNames } from '../../../mol-model/structure/model/types';
import { parseHelix, parseSheet } from './secondary-structure';
import { guessElementSymbolTokens } from '../util';
import { parseCmpnd, parseHetnam } from './entity';
import { ComponentBuilder } from '../common/component';
import { EntityBuilder } from '../common/entity';
import { Column } from '../../../mol-data/db';
import { getMoleculeType } from '../../../mol-model/structure/model/types';
function _entity(): { [K in keyof mmCIF_Schema['entity']]?: CifField } {
return {
id: CifField.ofStrings(['1', '2', '3']),
type: CifField.ofStrings(['polymer', 'non-polymer', 'water'])
}
}
type AtomSiteTemplate = typeof atom_site_template extends (...args: any) => infer T ? T : never
function atom_site_template(data: string, count: number) {
type AtomSiteTemplate = typeof getAtomSiteTemplate extends (...args: any) => infer T ? T : never
function getAtomSiteTemplate(data: string, count: number) {
const str = () => [] as string[];
const ts = () => TokenBuilder.create(data, 2 * count);
return {
@@ -48,7 +45,7 @@ function atom_site_template(data: string, count: number) {
};
}
function _atom_site(sites: AtomSiteTemplate): { [K in keyof mmCIF_Schema['atom_site']]?: CifField } {
function getAomSite(sites: AtomSiteTemplate): { [K in keyof mmCIF_Schema['atom_site']]?: CifField } {
const auth_asym_id = CifField.ofTokens(sites.auth_asym_id);
const auth_atom_id = CifField.ofTokens(sites.auth_atom_id);
const auth_comp_id = CifField.ofTokens(sites.auth_comp_id);
@@ -82,15 +79,7 @@ function _atom_site(sites: AtomSiteTemplate): { [K in keyof mmCIF_Schema['atom_s
};
}
function getEntityId(residueName: string, isHet: boolean) {
if (isHet) {
if (WaterNames.has(residueName)) return '3';
return '2';
}
return '1';
}
function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: number, e: number, isHet: boolean) {
function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: number, e: number) {
const { data: str } = data;
const length = e - s;
@@ -118,7 +107,6 @@ function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: num
// 18 - 20 Residue name Residue name.
TokenBuilder.addToken(sites.auth_comp_id, Tokenizer.trim(data, s + 17, s + 20));
const residueName = str.substring(data.tokenStart, data.tokenEnd);
// 22 Character Chain identifier.
TokenBuilder.add(sites.auth_asym_id, s + 21, s + 22);
@@ -169,7 +157,6 @@ function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: num
guessElementSymbolTokens(sites.type_symbol, str, s + 12, s + 16)
}
sites.label_entity_id[sites.index] = getEntityId(residueName, isHet);
sites.pdbx_PDB_model_num[sites.index] = model;
sites.index++;
@@ -194,9 +181,10 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
}
}
const atom_site = atom_site_template(data, atomCount);
const atomSite = getAtomSiteTemplate(data, atomCount);
const entityBuilder = new EntityBuilder();
const helperCategories: CifCategory[] = [];
const heteroNames: [string, string][] = [];
let modelNum = 0, modelStr = '';
@@ -206,19 +194,28 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
case 'A':
if (!substringStartsWith(data, s, e, 'ATOM ')) continue;
if (!modelNum) { modelNum++; modelStr = '' + modelNum; }
addAtom(atom_site, modelStr, tokenizer, s, e, false);
addAtom(atomSite, modelStr, tokenizer, s, e);
break;
case 'C':
if (substringStartsWith(data, s, e, 'CRYST1')) {
helperCategories.push(...parseCryst1(pdb.id || '?', data.substring(s, e)));
} else if (substringStartsWith(data, s, e, 'CONNECT')) {
// TODO: CONNECT records => struct_conn
} else if (substringStartsWith(data, s, e, 'COMPND')) {
let j = i + 1;
while (true) {
s = indices[2 * j]; e = indices[2 * j + 1];
if (!substringStartsWith(data, s, e, 'COMPND')) break;
j++;
}
entityBuilder.setCompounds(parseCmpnd(lines, i, j))
i = j - 1;
}
// TODO CONNECT records => struct_conn
// TODO COMPND records => entity
break;
case 'H':
if (substringStartsWith(data, s, e, 'HETATM')) {
if (!modelNum) { modelNum++; modelStr = '' + modelNum; }
addAtom(atom_site, modelStr, tokenizer, s, e, true);
addAtom(atomSite, modelStr, tokenizer, s, e);
} else if (substringStartsWith(data, s, e, 'HELIX')) {
let j = i + 1;
while (true) {
@@ -228,8 +225,16 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
}
helperCategories.push(parseHelix(lines, i, j));
i = j - 1;
} else if (substringStartsWith(data, s, e, 'HETNAM')) {
let j = i + 1;
while (true) {
s = indices[2 * j]; e = indices[2 * j + 1];
if (!substringStartsWith(data, s, e, 'HETNAM')) break;
j++;
}
heteroNames.push(...Array.from(parseHetnam(lines, i, j).entries()))
i = j - 1;
}
// TODO HETNAM records => chem_comp (at least partially, needs to be completed with common bases and amino acids)
break;
case 'M':
if (substringStartsWith(data, s, e, 'MODEL ')) {
@@ -246,10 +251,10 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
helperCategories.push(...parseMtrix(lines, i, j));
i = j - 1;
}
// TODO MODRES records => pdbx_struct_mod_residue
// TODO: MODRES records => pdbx_struct_mod_residue
break;
case 'O':
// TODO ORIGX record => cif.database_PDB_matrix.origx, cif.database_PDB_matrix.origx_vector
// TODO: ORIGX record => cif.database_PDB_matrix.origx, cif.database_PDB_matrix.origx_vector
break;
case 'R':
if (substringStartsWith(data, s, e, 'REMARK 350')) {
@@ -274,14 +279,29 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
helperCategories.push(parseSheet(lines, i, j));
i = j - 1;
}
// TODO SCALE record => cif.atom_sites.fract_transf_matrix, cif.atom_sites.fract_transf_vector
// TODO: SCALE record => cif.atom_sites.fract_transf_matrix, cif.atom_sites.fract_transf_vector
break;
}
}
// build entity and chem_comp categories
const seqIds = Column.ofIntTokens(atomSite.auth_seq_id)
const atomIds = Column.ofStringTokens(atomSite.auth_atom_id)
const compIds = Column.ofStringTokens(atomSite.auth_comp_id)
const asymIds = Column.ofStringTokens(atomSite.auth_asym_id)
const componentBuilder = new ComponentBuilder(seqIds, atomIds)
componentBuilder.setNames(heteroNames)
entityBuilder.setNames(heteroNames)
for (let i = 0, il = compIds.rowCount; i < il; ++i) {
const compId = compIds.value(i)
const moleculeType = getMoleculeType(componentBuilder.add(compId, i).type, compId)
atomSite.label_entity_id[i] = entityBuilder.getEntityId(compId, moleculeType, asymIds.value(i))
}
const categories = {
entity: CifCategory.ofFields('entity', _entity()),
atom_site: CifCategory.ofFields('atom_site', _atom_site(atom_site))
entity: entityBuilder.getEntityCategory(),
chem_comp: componentBuilder.getChemCompCategory(),
atom_site: CifCategory.ofFields('atom_site', getAomSite(atomSite))
} as any;
for (const c of helperCategories) {

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/tables';
import { TableLegend } from '../../mol-util/color/lists';
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/tables';
import { TableLegend } from '../../../mol-util/color/lists';
const ValidationColors = [
Color.fromRgb(170, 170, 170), // not applicable

View File

@@ -1,4 +1,4 @@
// Generated in 2019-06-04T12:44:49-07:00
// Generated in 2019-08-09T10:26:08-07:00
export type Maybe<T> = T | null;
/** Built-in scalar representing an instant in time */

View File

@@ -31,7 +31,7 @@ export const AssemblySymmetryAxesParams = {
sizeFactor: PD.Numeric(0.4, { min: 0, max: 3, step: 0.01 }),
...ComplexMeshParams,
radialSegments: PD.Numeric(16, { min: 3, max: 56, step: 1 }),
radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }),
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }),
}
export type AssemblySymmetryAxesParams = typeof AssemblySymmetryAxesParams
@@ -54,7 +54,8 @@ export const AssemblySymmetryAxesRepresentationProvider: StructureRepresentation
getParams: getAssemblySymmetryAxesParams,
defaultValues: PD.getDefaultValues(AssemblySymmetryAxesParams),
defaultColorTheme: 'uniform',
defaultSizeTheme: 'uniform'
defaultSizeTheme: 'uniform',
isApplicable: (structure: Structure) => AssemblySymmetry.get(structure.models[0]) !== undefined
}
//

View File

@@ -14,8 +14,8 @@ import { Unit, StructureElement, StructureProperties } from '../../../mol-model/
import { Location } from '../../../mol-model/location';
import { ScaleLegend } from '../../../mol-util/color/scale';
import { getSymmetrySelectParam } from '../util';
import { getPalette, getPaletteParams } from '../../../mol-theme/color/util';
import { TableLegend } from '../../../mol-util/color/tables';
import { getPalette, getPaletteParams } from '../../../mol-util/color/palette';
import { TableLegend } from '../../../mol-util/color/lists';
const DefaultColor = Color(0xCCCCCC)
@@ -34,7 +34,7 @@ function clusterMemberKey(assemblyId: string, asymId: string, operList: string[]
}
export const AssemblySymmetryClusterColorThemeParams = {
...getPaletteParams({ scaleList: 'RedYellowBlue' }),
...getPaletteParams({ scaleList: 'red-yellow-blue' }),
symmetryId: getSymmetrySelectParam(),
}
export type AssemblySymmetryClusterColorThemeParams = typeof AssemblySymmetryClusterColorThemeParams

View File

@@ -28,7 +28,7 @@ const pdbx_struct_mod_residue_fields: CifField<number, StructureElement[]>[] = [
];
function getModifiedResidues({ structures }: CifExportContext): StructureElement[] {
// TODO: can different models have differnt modified residues?
// TODO: can different models (in the same mmCIF file) have different modified residues?
const structure = structures[0], model = structure.model;
const map = model.properties.modifiedResidues.parentId;
if (!map.size) return [];

View File

@@ -10,12 +10,11 @@ import StructureSequence from './properties/sequence';
import { AtomicHierarchy, AtomicConformation } from './properties/atomic';
import { ModelSymmetry } from './properties/symmetry';
import { CoarseHierarchy, CoarseConformation } from './properties/coarse';
import { Entities } from './properties/common';
import { Entities, ChemicalComponentMap, MissingResidues } from './properties/common';
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 { ChemicalComponentMap } from './properties/chemical-component';
/**
* Interface to the "source data" of the molecule.
@@ -49,6 +48,8 @@ export interface Model extends Readonly<{
parentId: ReadonlyMap<string, string>,
details: ReadonlyMap<string, string>
}>,
/** map that holds details about unobserved or zero occurrence residues */
readonly missingResidues: MissingResidues,
/** maps residue name to `ChemicalComponent` data */
readonly chemicalComponentMap: ChemicalComponentMap
/** maps residue name to `SaccharideComponent` data */

View File

@@ -238,4 +238,8 @@ export namespace AtomicHierarchy {
export function chainEndResidueIndexExcl(segs: AtomicSegments, cI: ChainIndex) {
return segs.residueAtomSegments.index[segs.chainAtomSegments.offsets[cI + 1] - 1] + 1 as ResidueIndex;
}
export function chainResidueCount(segs: AtomicSegments, cI: ChainIndex) {
return chainEndResidueIndexExcl(segs, cI) - chainStartResidueIndex(segs, cI)
}
}

View File

@@ -1,13 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
import { Table } from '../../../../mol-data/db';
export type ChemicalComponent = Table.Row<mmCIF_Schema['chem_comp']>
export type ChemicalComponentMap = ReadonlyMap<string, ChemicalComponent>
// TODO add data for common chemical components

View File

@@ -1,13 +1,28 @@
/**
* 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>
*/
import { mmCIF_Database as mmCIF } from '../../../../mol-io/reader/cif/schema/mmcif'
import { mmCIF_Database, mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif'
import { Table } from '../../../../mol-data/db';
import { EntityIndex } from '../indexing';
export interface Entities {
data: mmCIF['entity'],
data: mmCIF_Database['entity'],
getEntityIndex(id: string): EntityIndex
}
export type ChemicalComponent = Table.Row<mmCIF_Schema['chem_comp']>
export type ChemicalComponentMap = ReadonlyMap<string, ChemicalComponent>
export type MissingResidue = Table.Row<Pick<
mmCIF_Schema['pdbx_unobs_or_zero_occ_residues'],
'polymer_flag' | 'occupancy_flag'>
>
export interface MissingResidues {
has(model_num: number, asym_id: string, seq_id: number): boolean
get(model_num: number, asym_id: string, seq_id: number): MissingResidue | undefined
readonly size: number
}

View File

@@ -5,11 +5,11 @@
*/
import { AtomicData } from '../atomic';
import { ChemicalComponentMap } from '../chemical-component';
import { AtomicIndex, AtomicDerivedData } from '../atomic/hierarchy';
import { ElementIndex, ResidueIndex } from '../../indexing';
import { MoleculeType, getMoleculeType, getComponentType } from '../../types';
import { getAtomIdForAtomRole } from '../../../../../mol-model/structure/util';
import { ChemicalComponentMap } from '../common';
export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemicalComponentMap: ChemicalComponentMap): AtomicDerivedData {
const { label_comp_id, _rowCount: n } = data.residues
@@ -31,6 +31,7 @@ export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemi
molType = getMoleculeType(chemCompMap.get(compId)!.type, compId)
moleculeTypeMap.set(compId, molType)
} else {
console.log('chemComp not found', compId)
molType = getMoleculeType(getComponentType(compId), compId)
// TODO if unknown molecule type, use atom names to guess molecule type
moleculeTypeMap.set(compId, molType)

View File

@@ -7,8 +7,8 @@
import { CoarseRanges, CoarseElementData } from '../coarse/hierarchy';
import { Segmentation, Interval } from '../../../../../mol-data/int';
import SortedRanges from '../../../../../mol-data/int/sorted-ranges';
import { ChemicalComponent } from '../chemical-component';
import { ElementIndex } from '../../indexing';
import { ChemicalComponent } from '../common';
// TODO assumes all coarse elements are part of a polymer
// TODO add gaps at the ends of the chains by comparing to the polymer sequence data

View File

@@ -178,13 +178,15 @@ export const AminoAcidNames = new Set([
export const RnaBaseNames = new Set([ 'A', 'C', 'T', 'G', 'I', 'U' ])
export const DnaBaseNames = new Set([ 'DA', 'DC', 'DT', 'DG', 'DI', 'DU' ])
export const PeptideBaseNames = new Set([ 'APN', 'CPN', 'TPN', 'GPN' ])
export const PurinBaseNames = new Set([ 'A', 'G', 'DA', 'DG', 'DI', 'APN', 'GPN' ])
export const PurineBaseNames = new Set([ 'A', 'G', 'DA', 'DG', 'DI', 'APN', 'GPN' ])
export const PyrimidineBaseNames = new Set([ 'C', 'T', 'U', 'DC', 'DT', 'DU', 'CPN', 'TPN' ])
export const BaseNames = SetUtils.unionMany(RnaBaseNames, DnaBaseNames, PeptideBaseNames)
export const isPurinBase = (compId: string) => PurinBaseNames.has(compId.toUpperCase())
export const isPurineBase = (compId: string) => PurineBaseNames.has(compId.toUpperCase())
export const isPyrimidineBase = (compId: string) => PyrimidineBaseNames.has(compId.toUpperCase())
export const PolymerNames = SetUtils.unionMany(AminoAcidNames, BaseNames)
/** get the molecule type from component type and id */
export function getMoleculeType(compType: string, compId: string) {
compType = compType.toUpperCase()
@@ -227,19 +229,19 @@ export function getComponentType(compId: string): mmCIF_Schema['chem_comp']['typ
export function getEntityType(compId: string): mmCIF_Schema['entity']['type']['T'] {
compId = compId.toUpperCase()
if (AminoAcidNames.has(compId) || RnaBaseNames.has(compId) || DnaBaseNames.has(compId)) {
if (WaterNames.has(compId)) {
return 'water'
} else if (PolymerNames.has(compId)) {
return 'polymer'
} else if (SaccharideCompIdMap.has(compId)) {
return 'branched'
} else if (WaterNames.has(compId)) {
return 'water'
} else {
return 'non-polymer'
}
}
export function isPolymer(moleculeType: MoleculeType) {
return moleculeType === MoleculeType.protein || moleculeType === MoleculeType.DNA || moleculeType === MoleculeType.RNA || moleculeType === MoleculeType.PNA
return isNucleic(moleculeType) || isProtein(moleculeType)
}
export function isNucleic(moleculeType: MoleculeType) {

View File

@@ -8,6 +8,7 @@ import { Structure, StructureElement, Unit } from '../structure';
import { now } from '../../../mol-util/now';
import { ElementIndex } from '../model';
import { Link } from '../structure/unit/links';
import { LinkType } from '../model/types';
export interface QueryContextView {
readonly element: StructureElement;
@@ -16,7 +17,7 @@ export interface QueryContextView {
export class QueryContext implements QueryContextView {
private currentElementStack: StructureElement[] = [];
private currentAtomicLinkStack: Link.Location<Unit.Atomic>[] = [];
private currentAtomicLinkStack: QueryContextLinkInfo<Unit.Atomic>[] = [];
private currentStructureStack: Structure[] = [];
private inputStructureStack: Structure[] = [];
@@ -30,7 +31,7 @@ export class QueryContext implements QueryContextView {
currentStructure: Structure = void 0 as any;
/** Current link between atoms */
readonly atomicLink: Link.Location<Unit.Atomic> = Link.Location() as Link.Location<Unit.Atomic>;
readonly atomicLink = QueryContextLinkInfo.empty<Unit.Atomic>();
setElement(unit: Unit, e: ElementIndex) {
this.element.unit = unit;
@@ -49,13 +50,13 @@ export class QueryContext implements QueryContextView {
pushCurrentLink() {
if (this.atomicLink) this.currentAtomicLinkStack.push(this.atomicLink);
(this.atomicLink as Link.Location<Unit.Atomic>) = Link.Location() as Link.Location<Unit.Atomic>;
(this.atomicLink as QueryContextLinkInfo<Unit.Atomic>) = QueryContextLinkInfo.empty();
return this.atomicLink;
}
popCurrentLink() {
if (this.currentAtomicLinkStack.length > 0) {
(this.atomicLink as Link.Location<Unit.Atomic>) = this.currentAtomicLinkStack.pop()!;
(this.atomicLink as QueryContextLinkInfo<Unit.Atomic>) = this.currentAtomicLinkStack.pop()!;
} else {
(this.atomicLink as any) = void 0;
}
@@ -94,4 +95,16 @@ export class QueryContext implements QueryContextView {
}
export interface QueryPredicate { (ctx: QueryContext): boolean }
export interface QueryFn<T = any> { (ctx: QueryContext): T }
export interface QueryFn<T = any> { (ctx: QueryContext): T }
export interface QueryContextLinkInfo<U extends Unit = Unit> {
link: Link.Location<U>,
type: LinkType,
order: number
}
export namespace QueryContextLinkInfo {
export function empty<U extends Unit = Unit>(): QueryContextLinkInfo<U> {
return { link: Link.Location() as Link.Location<U>, type: LinkType.Flag.None, order: 0 };
}
}

View File

@@ -15,6 +15,7 @@ import { checkStructureMaxRadiusDistance, checkStructureMinMaxDistance } from '.
import Structure from '../../structure/structure';
import StructureElement from '../../structure/element';
import { SortedArray } from '../../../../mol-data/int';
import { defaultLinkTest } from './internal';
export function pick(query: StructureQuery, pred: QueryPredicate): StructureQuery {
return ctx => {
@@ -38,7 +39,7 @@ export function first(query: StructureQuery): StructureQuery {
if (sel.kind === 'singletons') {
if (sel.structure.elementCount > 0) {
const u = sel.structure.units[0];
const s = Structure.create([u.getChild(SortedArray.ofSingleton(u.elements[0]))], ctx.inputStructure);
const s = Structure.create([u.getChild(SortedArray.ofSingleton(u.elements[0]))], { parent: ctx.inputStructure });
ret.add(s);
}
} else {
@@ -231,12 +232,13 @@ interface IsConnectedToCtx {
queryCtx: QueryContext,
input: Structure,
target: Structure,
bondTest: QueryFn<boolean>,
linkTest: QueryFn<boolean>,
tElement: StructureElement
}
function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
const { queryCtx, input, target, bondTest, tElement } = ctx;
const { queryCtx, input, target, linkTest, tElement } = ctx;
const atomicLink = queryCtx.atomicLink;
const interLinks = input.links;
for (const unit of structure.units) {
@@ -244,11 +246,11 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
const inputUnit = input.unitMap.get(unit.id) as Unit.Atomic;
const { offset, b } = inputUnit.links;
const { offset, b, edgeProps: { flags, order } } = inputUnit.links;
const linkedUnits = interLinks.getLinkedUnits(unit);
const luCount = linkedUnits.length;
queryCtx.atomicLink.aUnit = inputUnit;
atomicLink.link.aUnit = inputUnit;
const srcElements = unit.elements;
const inputElements = inputUnit.elements;
@@ -256,29 +258,33 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
for (let i = 0 as StructureElement.UnitIndex, _i = srcElements.length; i < _i; i++) {
const inputIndex = SortedArray.indexOf(inputElements, srcElements[i]) as StructureElement.UnitIndex;
queryCtx.atomicLink.aIndex = inputIndex;
queryCtx.atomicLink.bUnit = inputUnit;
atomicLink.link.aIndex = inputIndex;
atomicLink.link.bUnit = inputUnit;
tElement.unit = unit;
for (let l = offset[inputIndex], _l = offset[inputIndex + 1]; l < _l; l++) {
tElement.element = inputElements[b[l]];
if (!target.hasElement(tElement)) continue;
queryCtx.atomicLink.bIndex = b[l] as StructureElement.UnitIndex;
if (bondTest(queryCtx)) return true;
atomicLink.link.bIndex = b[l] as StructureElement.UnitIndex;
atomicLink.type = flags[l];
atomicLink.order = order[l];
if (linkTest(queryCtx)) return true;
}
for (let li = 0; li < luCount; li++) {
const lu = linkedUnits[li];
tElement.unit = lu.unitB;
queryCtx.atomicLink.bUnit = lu.unitB;
atomicLink.link.bUnit = lu.unitB;
const bElements = lu.unitB.elements;
const bonds = lu.getBonds(inputIndex);
for (let bi = 0, _bi = bonds.length; bi < _bi; bi++) {
const bond = bonds[bi];
tElement.element = bElements[bond.indexB];
if (!target.hasElement(tElement)) continue;
queryCtx.atomicLink.bIndex = bond.indexB;
if (bondTest(queryCtx)) return true;
atomicLink.link.bIndex = bond.indexB;
atomicLink.type = bond.flag;
atomicLink.order = bond.order;
if (linkTest(queryCtx)) return true;
}
}
}
@@ -290,16 +296,12 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
export interface IsConnectedToParams {
query: StructureQuery,
target: StructureQuery,
bondTest?: QueryFn<boolean>,
linkTest?: QueryFn<boolean>,
disjunct: boolean,
invert: boolean
}
function defaultBondTest(ctx: QueryContext) {
return true;
}
export function isConnectedTo({ query, target, disjunct, invert, bondTest }: IsConnectedToParams): StructureQuery {
export function isConnectedTo({ query, target, disjunct, invert, linkTest }: IsConnectedToParams): StructureQuery {
return ctx => {
const targetSel = target(ctx);
if (StructureSelection.isEmpty(targetSel)) return targetSel;
@@ -310,7 +312,7 @@ export function isConnectedTo({ query, target, disjunct, invert, bondTest }: IsC
queryCtx: ctx,
input: ctx.inputStructure,
target: StructureSelection.unionStructure(targetSel),
bondTest: bondTest || defaultBondTest,
linkTest: linkTest || defaultLinkTest,
tElement: StructureElement.create()
}

View File

@@ -1,7 +1,8 @@
/**
* 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>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { StructureQuery } from '../query'
@@ -20,11 +21,13 @@ export const none: StructureQuery = ctx => StructureSelection.Sequence(ctx.input
export const all: StructureQuery = ctx => StructureSelection.Singletons(ctx.inputStructure, ctx.inputStructure);
export interface AtomsQueryParams {
/** Query to be executed for each unit once */
unitTest: QueryPredicate,
/** Query to be executed for each entity once */
entityTest: QueryPredicate,
/** Query to be executed for each chain once */
chainTest: QueryPredicate,
/** Query to be executed for each residue once */
/** Query to be executed for each residue (or coarse element) once */
residueTest: QueryPredicate,
/** Query to be executed for each atom */
atomTest: QueryPredicate,
@@ -38,10 +41,11 @@ function _true(ctx: QueryContextView) { return true; }
function _zero(ctx: QueryContextView) { return 0; }
export function atoms(params?: Partial<AtomsQueryParams>): StructureQuery {
if (!params || (!params.atomTest && !params.residueTest && !params.chainTest && !params.entityTest && !params.groupBy)) return all;
if (!!params.atomTest && !params.residueTest && !params.chainTest && !params.entityTest && !params.groupBy) return atomGroupsLinear(params.atomTest);
if (!params || (!params.atomTest && !params.residueTest && !params.chainTest && !params.entityTest && !params.unitTest && !params.groupBy)) return all;
if (!!params.atomTest && !params.residueTest && !params.chainTest && !params.entityTest && !params.unitTest && !params.groupBy) return atomGroupsLinear(params.atomTest);
const normalized: AtomsQueryParams = {
unitTest: params.unitTest || _true,
entityTest: params.entityTest || _true,
chainTest: params.chainTest || _true,
residueTest: params.residueTest || _true,
@@ -78,7 +82,7 @@ function atomGroupsLinear(atomTest: QueryPredicate): StructureQuery {
};
}
function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: AtomsQueryParams): StructureQuery {
function atomGroupsSegmented({ unitTest, entityTest, chainTest, residueTest, atomTest }: AtomsQueryParams): StructureQuery {
return ctx => {
const { inputStructure } = ctx;
const { units } = inputStructure;
@@ -86,31 +90,53 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A
const builder = inputStructure.subsetBuilder(true);
for (const unit of units) {
if (unit.kind !== Unit.Kind.Atomic) continue;
l.unit = unit;
const elements = unit.elements;
if (!unitTest(ctx)) continue;
const { elements, model } = unit;
builder.beginUnit(unit.id);
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
l.element = elements[chainSegment.start];
// test entity and chain
if (!entityTest(ctx) || !chainTest(ctx)) continue;
residuesIt.setSegment(chainSegment);
while (residuesIt.hasNext) {
const residueSegment = residuesIt.move();
l.element = elements[residueSegment.start];
if (unit.kind === Unit.Kind.Atomic) {
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
// test residue
if (!residueTest(ctx)) continue;
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
l.element = elements[chainSegment.start];
// test entity and chain
if (!entityTest(ctx) || !chainTest(ctx)) continue;
for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
residuesIt.setSegment(chainSegment);
while (residuesIt.hasNext) {
const residueSegment = residuesIt.move();
l.element = elements[residueSegment.start];
// test residue
if (!residueTest(ctx)) continue;
for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
l.element = elements[j];
// test atom
if (atomTest(ctx)) {
builder.addElement(l.element);
}
}
}
}
} else {
const { chainElementSegments } = Unit.Kind.Spheres ? model.coarseHierarchy.spheres : model.coarseHierarchy.gaussians
const chainsIt = Segmentation.transientSegments(chainElementSegments, elements);
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
l.element = elements[chainSegment.start];
// test entity and chain
if (!entityTest(ctx) || !chainTest(ctx)) continue;
for (let j = chainSegment.start, _j = chainSegment.end; j < _j; j++) {
l.element = elements[j];
if (atomTest(ctx)) {
// test residue/coarse element
if (residueTest(ctx)) {
builder.addElement(l.element);
}
}
@@ -125,7 +151,7 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A
};
}
function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, groupBy }: AtomsQueryParams): StructureQuery {
function atomGroupsGrouped({ unitTest, entityTest, chainTest, residueTest, atomTest, groupBy }: AtomsQueryParams): StructureQuery {
return ctx => {
const { inputStructure } = ctx;
const { units } = inputStructure;
@@ -133,30 +159,53 @@ function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, group
const builder = new LinearGroupingBuilder(inputStructure);
for (const unit of units) {
if (unit.kind !== Unit.Kind.Atomic) continue;
l.unit = unit;
const elements = unit.elements;
if (!unitTest(ctx)) continue;
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
l.element = elements[chainSegment.start];
// test entity and chain
if (!entityTest(ctx) || !chainTest(ctx)) continue;
const { elements, model } = unit;
residuesIt.setSegment(chainSegment);
while (residuesIt.hasNext) {
const residueSegment = residuesIt.move();
l.element = elements[residueSegment.start];
if (unit.kind === Unit.Kind.Atomic) {
const chainsIt = Segmentation.transientSegments(model.atomicHierarchy.chainAtomSegments, elements);
const residuesIt = Segmentation.transientSegments(model.atomicHierarchy.residueAtomSegments, elements);
// test residue
if (!residueTest(ctx)) continue;
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
l.element = elements[chainSegment.start];
// test entity and chain
if (!entityTest(ctx) || !chainTest(ctx)) continue;
for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
residuesIt.setSegment(chainSegment);
while (residuesIt.hasNext) {
const residueSegment = residuesIt.move();
l.element = elements[residueSegment.start];
// test residue
if (!residueTest(ctx)) continue;
for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
l.element = elements[j];
// test atom
if (atomTest(ctx)) {
builder.add(groupBy(ctx), unit.id, l.element);
}
}
}
}
} else {
const { chainElementSegments } = Unit.Kind.Spheres ? model.coarseHierarchy.spheres : model.coarseHierarchy.gaussians
const chainsIt = Segmentation.transientSegments(chainElementSegments, elements);
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
l.element = elements[chainSegment.start];
// test entity and chain
if (!entityTest(ctx) || !chainTest(ctx)) continue;
for (let j = chainSegment.start, _j = chainSegment.end; j < _j; j++) {
l.element = elements[j];
if (atomTest(ctx)) builder.add(groupBy(ctx), unit.id, l.element);
// test residue/coarse element
if (residueTest(ctx)) {
builder.add(groupBy(ctx), unit.id, l.element);
}
}
}
}
@@ -171,7 +220,7 @@ function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, group
function getRingStructure(unit: Unit.Atomic, ring: UnitRing, inputStructure: Structure) {
const elements = new Int32Array(ring.length) as any as ElementIndex[];
for (let i = 0, _i = ring.length; i < _i; i++) elements[i] = unit.elements[ring[i]];
return Structure.create([unit.getChild(SortedArray.ofSortedArray(elements))], inputStructure);
return Structure.create([unit.getChild(SortedArray.ofSortedArray(elements))], { parent: inputStructure });
}
export function rings(fingerprints?: ArrayLike<UnitRing.Fingerprint>): StructureQuery {

View File

@@ -10,6 +10,12 @@ import { StructureProperties as P, Unit } from '../../structure';
import Structure from '../../structure/structure';
import { StructureQuery } from '../query';
import { StructureSelection } from '../selection';
import { QueryContext } from '../context';
import { LinkType } from '../../model/types';
export function defaultLinkTest(ctx: QueryContext) {
return LinkType.isCovalent(ctx.atomicLink.type);
}
export function atomicSequence(): StructureQuery {
return ctx => {
@@ -35,7 +41,7 @@ export function atomicSequence(): StructureQuery {
units.push(unit);
}
return StructureSelection.Singletons(inputStructure, new Structure(units, inputStructure, ));
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
};
}
@@ -54,7 +60,7 @@ export function water(): StructureQuery {
if (P.entity.type(l) !== 'water') continue;
units.push(unit);
}
return StructureSelection.Singletons(inputStructure, new Structure(units, inputStructure));
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
};
}
@@ -84,7 +90,7 @@ export function atomicHet(): StructureQuery {
units.push(unit);
}
return StructureSelection.Singletons(inputStructure, new Structure(units, inputStructure));
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
};
}
@@ -97,6 +103,6 @@ export function spheres(): StructureQuery {
if (unit.kind !== Unit.Kind.Spheres) continue;
units.push(unit);
}
return StructureSelection.Singletons(inputStructure, new Structure(units, inputStructure));
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
};
}

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Segmentation } from '../../../../mol-data/int';
import { Segmentation, SortedArray } from '../../../../mol-data/int';
import { Structure, Unit } from '../../structure';
import { StructureQuery } from '../query';
import { StructureSelection } from '../selection';
@@ -15,6 +15,7 @@ import { structureIntersect, structureSubtract } from '../utils/structure-set';
import { UniqueArray } from '../../../../mol-data/generic';
import { StructureSubsetBuilder } from '../../structure/util/subset-builder';
import StructureElement from '../../structure/element';
import { defaultLinkTest } from './internal';
function getWholeResidues(ctx: QueryContext, source: Structure, structure: Structure) {
const builder = source.subsetBuilder(true);
@@ -222,7 +223,7 @@ export function exceptBy(query: StructureQuery, by: StructureQuery): StructureQu
if (StructureSelection.structureCount(selection) === 0) return selection;
const bySel = by(ctx);
if (StructureSelection.structureCount(bySel) === 0) return StructureSelection.Empty(ctx.inputStructure);
if (StructureSelection.structureCount(bySel) === 0) return selection;
const subtractBy = StructureSelection.unionStructure(bySel);
const ret = StructureSelection.UniqueBuilder(ctx.inputStructure);
@@ -298,84 +299,107 @@ export function expandProperty(query: StructureQuery, property: QueryFn): Struct
export interface IncludeConnectedParams {
query: StructureQuery,
bondTest?: QueryFn<boolean>,
linkTest?: QueryFn<boolean>,
layerCount: number,
wholeResidues: boolean
}
export function includeConnected({ query, layerCount, wholeResidues, bondTest }: IncludeConnectedParams): StructureQuery {
export function includeConnected({ query, layerCount, wholeResidues, linkTest }: IncludeConnectedParams): StructureQuery {
const bt = linkTest || defaultLinkTest;
const lc = Math.max(layerCount, 0);
return ctx => {
return 0 as any;
const builder = StructureSelection.UniqueBuilder(ctx.inputStructure);
const src = query(ctx);
ctx.pushCurrentLink();
StructureSelection.forEach(src, (s, sI) => {
let incl = s;
for (let i = 0; i < lc; i++) {
incl = includeConnectedStep(ctx, bt, wholeResidues, incl);
}
builder.add(incl);
if (sI % 10 === 0) ctx.throwIfTimedOut();
});
ctx.popCurrentLink();
return builder.getSelection();
}
}
// function defaultBondTest(ctx: QueryContext) {
// return true;
// }
function includeConnectedStep(ctx: QueryContext, linkTest: QueryFn<boolean>, wholeResidues: boolean, structure: Structure) {
const expanded = expandConnected(ctx, structure, linkTest);
if (wholeResidues) return getWholeResidues(ctx, ctx.inputStructure, expanded);
return expanded;
}
// interface IncludeConnectedCtx {
// queryCtx: QueryContext,
// input: Structure,
// bondTest: QueryFn<boolean>,
// wholeResidues: boolean
// }
function expandConnected(ctx: QueryContext, structure: Structure, linkTest: QueryFn<boolean>) {
const inputStructure = ctx.inputStructure;
const interLinks = inputStructure.links;
const builder = new StructureUniqueSubsetBuilder(inputStructure);
// type FrontierSet = UniqueArray<StructureElement.UnitIndex, StructureElement.UnitIndex>
// type Frontier = { unitIds: UniqueArray<number>, elements: Map<number /* unit id */, FrontierSet> }
const processedUnits = new Set<number>();
// namespace Frontier {
// export function has({ elements }: Frontier, unitId: number, element: StructureElement.UnitIndex) {
// if (!elements.has(unitId)) return false;
// const xs = elements.get(unitId)!;
// return xs.keys.has(element);
// }
const atomicLink = ctx.atomicLink;
// export function create(pivot: Structure, input: Structure) {
// const unitIds = UniqueArray.create<number>();
// const elements: Frontier['elements'] = new Map();
// for (const unit of pivot.units) {
// if (!Unit.isAtomic(unit)) continue;
// Process intra unit links
for (const unit of structure.units) {
processedUnits.add(unit.id);
// UniqueArray.add(unitIds, unit.id, unit.id);
// const xs: FrontierSet = UniqueArray.create();
// elements.set(unit.id, xs);
if (unit.kind !== Unit.Kind.Atomic) {
// add the whole unit
builder.beginUnit(unit.id);
for (let i = 0, _i = unit.elements.length; i < _i; i++) {
builder.addElement(unit.elements[i]);
}
builder.commitUnit();
continue;
}
// const pivotElements = unit.elements;
// const inputElements = input.unitMap.get(unit.id).elements;
// for (let i = 0, _i = pivotElements.length; i < _i; i++) {
// const idx = SortedArray.indexOf(inputElements, pivotElements[i]) as StructureElement.UnitIndex;
// UniqueArray.add(xs, idx, idx);
// }
// }
const inputUnit = inputStructure.unitMap.get(unit.id) as Unit.Atomic;
const { offset: intraLinkOffset, b: intraLinkB, edgeProps: { flags, order } } = inputUnit.links;
// return { unitIds, elements };
// }
// Process intra unit links
atomicLink.link.aUnit = inputUnit;
atomicLink.link.bUnit = inputUnit;
for (let i = 0, _i = unit.elements.length; i < _i; i++) {
// add the current element
builder.addToUnit(unit.id, unit.elements[i]);
// export function addFrontier(target: Frontier, from: Frontier) {
// for (const unitId of from.unitIds.array) {
// let xs: FrontierSet;
// if (target.elements.has(unitId)) {
// xs = target.elements.get(unitId)!;
// } else {
// xs = UniqueArray.create();
// target.elements.set(unitId, xs);
// UniqueArray.add(target.unitIds, unitId, unitId);
// }
const srcIndex = SortedArray.indexOf(inputUnit.elements, unit.elements[i]);
atomicLink.link.aIndex = srcIndex as StructureElement.UnitIndex;
// for (const e of from.elements.get(unitId)!.array) {
// UniqueArray.add(xs, e, e);
// }
// }
// return target;
// }
// check intra unit links
for (let lI = intraLinkOffset[srcIndex], _lI = intraLinkOffset[srcIndex + 1]; lI < _lI; lI++) {
atomicLink.link.bIndex = intraLinkB[lI] as StructureElement.UnitIndex;
atomicLink.type = flags[lI];
atomicLink.order = order[lI];
if (linkTest(ctx)) {
builder.addToUnit(unit.id, inputUnit.elements[intraLinkB[lI]]);
}
}
}
// export function includeWholeResidues(structure: Structure, frontier: Frontier) {
// // ...
// }
// }
// Process inter unit links
for (const linkedUnit of interLinks.getLinkedUnits(inputUnit)) {
if (processedUnits.has(linkedUnit.unitB.id)) continue;
// function expandFrontier(ctx: IncludeConnectedCtx, currentFrontier: Frontier, result: Frontier): Frontier {
// return 0 as any;
// }
atomicLink.link.bUnit = linkedUnit.unitB;
for (const aI of linkedUnit.linkedElementIndices) {
// check if the element is in the expanded structure
if (!SortedArray.has(unit.elements, inputUnit.elements[aI])) continue;
atomicLink.link.aIndex = aI;
for (const bond of linkedUnit.getBonds(aI)) {
atomicLink.link.bIndex = bond.indexB;
atomicLink.type = bond.flag;
atomicLink.order = bond.order;
if (linkTest(ctx)) {
builder.addToUnit(linkedUnit.unitB.id, linkedUnit.unitB.elements[bond.indexB]);
}
}
}
}
}
return builder.getStructure();
}
// TODO: unionBy (skip this one?), cluster

View File

@@ -135,7 +135,7 @@ namespace StructureSelection {
const { elements } = unit;
for (let i = 0, _i = elements.length; i < _i; i++) {
// TODO: optimize this somehow???
const s = Structure.create([unit.getChild(SortedArray.ofSingleton(elements[i]))], sel.source);
const s = Structure.create([unit.getChild(SortedArray.ofSingleton(elements[i]))], { parent: sel.source });
fn(s, idx++);
}
}

View File

@@ -80,7 +80,7 @@ export function structureIntersect(sA: Structure, sB: Structure): Structure {
}
}
return Structure.create(units, sA.parent || sB.parent);
return Structure.create(units, { parent: sA.parent || sB.parent });
}
export function structureSubtract(a: Structure, b: Structure): Structure {
@@ -92,7 +92,10 @@ export function structureSubtract(a: Structure, b: Structure): Structure {
for (let i = 0, _i = aU.length; i < _i; i++) {
const u = aU[i];
if (!bU.has(u.id)) continue;
if (!bU.has(u.id)) {
units[units.length] = u;
continue;
}
const v = bU.get(u.id);
const sub = SortedArray.subtract(u.elements, v.elements);
if (sub.length > 0) {
@@ -100,5 +103,5 @@ export function structureSubtract(a: Structure, b: Structure): Structure {
}
}
return Structure.create(units, a.parent || b.parent);
return Structure.create(units, { parent: a.parent || b.parent });
}

View File

@@ -1,11 +1,12 @@
/**
* Copyright (c) 2017-2018 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>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { UniqueArray } from '../../../mol-data/generic';
import { OrderedSet, SortedArray } from '../../../mol-data/int';
import { OrderedSet, SortedArray, Interval } from '../../../mol-data/int';
import { BoundaryHelper } from '../../../mol-math/geometry/boundary-helper';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
@@ -15,8 +16,9 @@ import Structure from './structure';
import Unit from './unit';
import { Boundary } from './util/boundary';
import { StructureProperties } from '../structure';
import { sortArray } from '../../../mol-data/util';
import { sortArray, hashFnv32a, hash2 } from '../../../mol-data/util';
import Expression from '../../../mol-script/language/expression';
import SortedRanges from '../../../mol-data/int/sorted-ranges';
interface StructureElement<U = Unit> {
readonly kind: 'element-location',
@@ -30,6 +32,12 @@ namespace StructureElement {
return { kind: 'element-location', unit: unit!, element: element || (0 as ElementIndex) };
}
export function set(a: StructureElement, unit?: Unit, element?: ElementIndex): StructureElement {
if (unit) a.unit = unit
if (element !== undefined) a.element = element
return a;
}
// TODO: when nominal types are available, make this indexed by UnitIndex
export type Set = SortedArray<ElementIndex>
@@ -107,14 +115,7 @@ namespace StructureElement {
}
export function entityIndex(l: StructureElement) {
switch (l.unit.kind) {
case Unit.Kind.Atomic:
return l.unit.model.atomicHierarchy.index.getEntityFromChain(l.unit.chainIndex[l.element])
case Unit.Kind.Spheres:
return l.unit.model.coarseHierarchy.spheres.entityKey[l.element]
case Unit.Kind.Gaussians:
return l.unit.model.coarseHierarchy.gaussians.entityKey[l.element]
}
return StructureProperties.entity.key(l)
}
export namespace Loci {
@@ -127,17 +128,53 @@ namespace StructureElement {
export function all(structure: Structure): Loci {
return Loci(structure, structure.units.map(unit => ({
unit,
indices: OrderedSet.ofRange<UnitIndex>(0 as UnitIndex, unit.elements.length as UnitIndex)
indices: OrderedSet.ofBounds<UnitIndex>(0 as UnitIndex, unit.elements.length as UnitIndex)
})));
}
export function none(structure: Structure): Loci {
return Loci(structure, []);
}
export function remap(loci: Loci, structure: Structure): Loci {
return Loci(structure, loci.elements.map(e => ({
unit: structure.unitMap.get(e.unit.id)!,
indices: e.indices
})));
if (structure === loci.structure) return loci
const elements: Loci['elements'][0][] = [];
loci.elements.forEach(e => {
if (!structure.unitMap.has(e.unit.id)) return
const unit = structure.unitMap.get(e.unit.id)
if (SortedArray.areEqual(e.unit.elements, unit.elements)) {
elements.push({ unit, indices: e.indices })
} else {
const _indices: UnitIndex[] = []
const end = unit.elements.length
let start = 0
for (let i = 0; i < OrderedSet.size(e.indices); ++i) {
const v = OrderedSet.getAt(e.indices, i)
const eI = e.unit.elements[v]
const uI = SortedArray.indexOfInRange(unit.elements, eI, start, end) as UnitIndex | -1
if (uI !== -1) {
_indices.push(uI)
start = uI
}
}
let indices: OrderedSet<UnitIndex>
if (_indices.length > 12 && _indices[_indices.length - 1] - _indices[0] === _indices.length - 1) {
indices = Interval.ofRange(_indices[0], _indices[_indices.length - 1])
} else {
indices = SortedArray.ofSortedArray(_indices)
}
elements.push({ unit, indices })
}
});
return Loci(structure, elements);
}
/** Create union of `xs` and `ys` */
export function union(xs: Loci, ys: Loci): Loci {
if (xs.elements.length > ys.elements.length) return union(ys, xs);
if (xs.elements.length === 0) return ys;
@@ -150,14 +187,20 @@ namespace StructureElement {
for (const e of ys.elements) {
if (map.has(e.unit.id)) {
elements[elements.length] = { unit: e.unit, indices: OrderedSet.union(map.get(e.unit.id)!, e.indices) };
map.delete(e.unit.id)
} else {
elements[elements.length] = e;
}
}
map.forEach((indices, id) => {
elements[elements.length] = { unit: xs.structure.unitMap.get(id)!, indices };
});
return Loci(xs.structure, elements);
}
/** Subtract `ys` from `xs` */
export function subtract(xs: Loci, ys: Loci): Loci {
const map = new Map<number, OrderedSet<UnitIndex>>();
for (const e of ys.elements) map.set(e.unit.id, e.indices);
@@ -195,30 +238,78 @@ namespace StructureElement {
const elements: Loci['elements'][0][] = [];
for (const lociElement of loci.elements) {
if (lociElement.unit.kind !== Unit.Kind.Atomic) elements[elements.length] = lociElement;
if (lociElement.unit.kind === Unit.Kind.Atomic) {
const unitElements = lociElement.unit.elements;
const h = lociElement.unit.model.atomicHierarchy;
const { index: residueIndex, offsets: residueOffsets } = h.residueAtomSegments;
const newIndices: UnitIndex[] = [];
const indices = lociElement.indices, len = OrderedSet.size(indices);
let i = 0;
while (i < len) {
const rI = residueIndex[unitElements[OrderedSet.getAt(indices, i)]];
i++;
while (i < len && residueIndex[unitElements[OrderedSet.getAt(indices, i)]] === rI) {
i++;
}
for (let j = residueOffsets[rI], _j = residueOffsets[rI + 1]; j < _j; j++) {
const idx = OrderedSet.indexOf(unitElements, j);
if (idx >= 0) newIndices[newIndices.length] = idx as UnitIndex;
}
}
elements[elements.length] = { unit: lociElement.unit, indices: SortedArray.ofSortedArray(newIndices) };
} else {
// coarse elements are already by-residue
elements[elements.length] = lociElement;
}
}
return Loci(loci.structure, elements);
}
function getChainSegments(unit: Unit) {
switch (unit.kind) {
case Unit.Kind.Atomic: return unit.model.atomicHierarchy.chainAtomSegments
case Unit.Kind.Spheres: return unit.model.coarseHierarchy.spheres.chainElementSegments
case Unit.Kind.Gaussians: return unit.model.coarseHierarchy.gaussians.chainElementSegments
}
}
export function extendToWholeChains(loci: Loci): Loci {
const elements: Loci['elements'][0][] = [];
for (const lociElement of loci.elements) {
const _newIndices: UnitIndex[] = [];
const unitElements = lociElement.unit.elements;
const h = lociElement.unit.model.atomicHierarchy;
const { index: residueIndex, offsets: residueOffsets } = h.residueAtomSegments;
const { index: chainIndex, offsets: chainOffsets } = getChainSegments(lociElement.unit)
const newIndices: UnitIndex[] = [];
const indices = lociElement.indices, len = OrderedSet.size(indices);
let i = 0;
while (i < len) {
const rI = residueIndex[unitElements[OrderedSet.getAt(indices, i)]];
const cI = chainIndex[unitElements[OrderedSet.getAt(indices, i)]];
i++;
while (i < len && residueIndex[unitElements[OrderedSet.getAt(indices, i)]] === rI) {
while (i < len && chainIndex[unitElements[OrderedSet.getAt(indices, i)]] === cI) {
i++;
}
for (let j = residueOffsets[rI], _j = residueOffsets[rI + 1]; j < _j; j++) {
for (let j = chainOffsets[cI], _j = chainOffsets[cI + 1]; j < _j; j++) {
const idx = OrderedSet.indexOf(unitElements, j);
if (idx >= 0) newIndices[newIndices.length] = idx as UnitIndex;
if (idx >= 0) _newIndices[_newIndices.length] = idx as UnitIndex;
}
}
elements[elements.length] = { unit: lociElement.unit, indices: SortedArray.ofSortedArray(newIndices) };
let newIndices: OrderedSet<UnitIndex>
if (_newIndices.length > 12 && _newIndices[_newIndices.length - 1] - _newIndices[0] === _newIndices.length - 1) {
newIndices = Interval.ofRange(_newIndices[0], _newIndices[_newIndices.length - 1])
} else {
newIndices = SortedArray.ofSortedArray(_newIndices)
}
elements[elements.length] = { unit: lociElement.unit, indices: newIndices };
}
return Loci(loci.structure, elements);
@@ -254,24 +345,24 @@ namespace StructureElement {
}
export function toScriptExpression(loci: Loci) {
if (loci.structure.models.length > 1) {
console.warn('toScriptExpression is only supported for Structure with single model, returning empty expression.');
return MS.struct.generator.empty();
}
if (loci.elements.length === 0) return MS.struct.generator.empty();
const sourceIndexMap = new Map<string, UniqueArray<number, number>>();
const models = loci.structure.models;
const sourceIndexMap = new Map<string, { modelLabel: string, modelIndex: number, xs: UniqueArray<number, number> }>();
const el = StructureElement.create(), p = StructureProperties.atom.sourceIndex;
for (const e of loci.elements) {
const { indices } = e;
const { elements } = e.unit;
const opName = e.unit.conformation.operator.name;
const key = models.length === 1
? e.unit.conformation.operator.name
: `${e.unit.conformation.operator.name} ${e.unit.model.label} ${e.unit.model.modelNum}`;
let sourceIndices: UniqueArray<number, number>;
if (sourceIndexMap.has(opName)) sourceIndices = sourceIndexMap.get(opName)!;
if (sourceIndexMap.has(key)) sourceIndices = sourceIndexMap.get(key)!.xs;
else {
sourceIndices = UniqueArray.create<number, number>();
sourceIndexMap.set(opName, sourceIndices);
sourceIndexMap.set(key, { modelLabel: e.unit.model.label, modelIndex: e.unit.model.modelNum, xs: sourceIndices });
}
el.unit = e.unit;
@@ -282,20 +373,81 @@ namespace StructureElement {
}
}
const byOpName: Expression[] = [];
const opData: OpData[] = [];
const keys = sourceIndexMap.keys();
while (true) {
const k = keys.next();
if (k.done) break;
byOpName.push(getOpNameQuery(k.value, sourceIndexMap.get(k.value)!.array));
const e = sourceIndexMap.get(k.value)!;
opData.push(getOpData(k.value, e.xs.array, models.length > 1, e.modelLabel, e.modelIndex));
}
const opGroups = new Map<string, OpData>();
for (let i = 0, il = opData.length; i < il; ++i) {
const d = opData[i]
const hash = hash2(hashFnv32a(d.atom.ranges), hashFnv32a(d.atom.set))
const key = `${hash}|${d.entity ? (d.entity.modelLabel + d.entity.modelIndex) : ''}`
if (opGroups.has(key)) {
opGroups.get(key)!.chain.opName.push(...d.chain.opName)
} else {
opGroups.set(key, d)
}
}
const opQueries: Expression[] = [];
opGroups.forEach(d => {
const { ranges, set } = d.atom
const { opName } = d.chain
const opProp = MS.struct.atomProperty.core.operatorName()
const siProp = MS.struct.atomProperty.core.sourceIndex();
const tests: Expression[] = [];
// TODO: add set.ofRanges constructor to MolQL???
if (set.length > 0) {
tests[tests.length] = MS.core.set.has([MS.core.type.set(set), siProp]);
}
for (let rI = 0, _rI = ranges.length / 2; rI < _rI; rI++) {
tests[tests.length] = MS.core.rel.inRange([siProp, ranges[2 * rI], ranges[2 * rI + 1]]);
}
if (d.entity) {
const { modelLabel, modelIndex } = d.entity
opQueries.push(MS.struct.generator.atomGroups({
'atom-test': tests.length > 1 ? MS.core.logic.or(tests) : tests[0],
'chain-test': opName.length > 1
? MS.core.set.has([MS.core.type.set(opName), opProp])
: MS.core.rel.eq([opProp, opName[0]]),
'entity-test': MS.core.logic.and([
MS.core.rel.eq([MS.struct.atomProperty.core.modelLabel(), modelLabel]),
MS.core.rel.eq([MS.struct.atomProperty.core.modelIndex(), modelIndex]),
])
}))
} else {
opQueries.push(MS.struct.generator.atomGroups({
'atom-test': tests.length > 1 ? MS.core.logic.or(tests) : tests[0],
'chain-test': opName.length > 1
? MS.core.set.has([MS.core.type.set(opName), opProp])
: MS.core.rel.eq([opProp, opName[0]])
}))
}
})
return MS.struct.modifier.union([
byOpName.length === 1 ? byOpName[0] : MS.struct.combinator.merge.apply(null, byOpName)
opQueries.length === 1
? opQueries[0]
// Need to union before merge for fast performance
: MS.struct.combinator.merge(opQueries.map(q => MS.struct.modifier.union([ q ])))
]);
}
function getOpNameQuery(opName: string, xs: number[]) {
type OpData = {
atom: { set: number[], ranges: number[] },
chain: { opName: string[] },
entity?: { modelLabel: string, modelIndex: number }
}
function getOpData(opName: string, xs: number[], multimodel: boolean, modelLabel: string, modelIndex: number): OpData {
sortArray(xs);
const ranges: number[] = [];
@@ -318,21 +470,209 @@ namespace StructureElement {
}
}
const siProp = MS.struct.atomProperty.core.sourceIndex();
const tests: Expression[] = [];
return multimodel
? {
atom: { set, ranges },
chain: { opName: [ opName ] },
entity: { modelLabel, modelIndex }
}
: {
atom: { set, ranges },
chain: { opName: [ opName ] },
}
}
}
// TODO: add set.ofRanges constructor to MolQL???
if (set.length > 0) {
tests[tests.length] = MS.core.set.has([MS.set.apply(null, set), siProp]);
}
for (let rI = 0, _rI = ranges.length / 2; rI < _rI; rI++) {
tests[tests.length] = MS.core.rel.inRange([siProp, ranges[2 * rI], ranges[2 * rI + 1]]);
interface QueryElement {
/**
* Array (sorted by first element in sub-array) of
* arrays of `Unit.id`s that share the same `Unit.invariantId`
*/
groupedUnits: SortedArray<number>[],
set: SortedArray<UnitIndex>
ranges: SortedRanges<UnitIndex>
}
export interface Query {
/** Hash of the structure to which the query can be applied */
readonly hash: number
/** Query elements */
readonly elements: ReadonlyArray<Readonly<QueryElement>>
}
export namespace Query {
export const Empty: Query = { hash: -1, elements: [] }
export function fromLoci(loci: StructureElement.Loci): Query {
const _elements: {
unit: Unit
set: SortedArray<UnitIndex>
ranges: SortedRanges<UnitIndex>
}[] = []
for (const e of loci.elements) {
const { unit, indices } = e
if (OrderedSet.size(indices) === 0) continue
const ranges: UnitIndex[] = [];
const set: UnitIndex[] = [];
if (OrderedSet.isInterval(indices)) {
if (OrderedSet.size(indices) === 1) {
set.push(Interval.min(indices))
} else {
ranges.push(Interval.min(indices), Interval.max(indices))
}
} else {
let i = 0, len = indices.length;
while (i < len) {
const start = i;
i++;
while (i < len && indices[i - 1] + 1 === indices[i]) i++;
const end = i;
if (end - start > 2) {
ranges.push(indices[start], indices[end - 1])
} else {
for (let j = start; j < end; j++) {
set[set.length] = indices[j]
}
}
}
}
_elements.push({
unit,
set: SortedArray.ofSortedArray(set),
ranges: SortedRanges.ofSortedRanges(ranges)
})
}
return MS.struct.generator.atomGroups({
'atom-test': tests.length > 1 ? MS.core.logic.or.apply(null, tests) : tests[0],
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.core.operatorName(), opName])
});
const elementGroups = new Map<number, {
groupedUnits: Map<number, number[]>
set: SortedArray<UnitIndex>
ranges: SortedRanges<UnitIndex>
}>();
for (let i = 0, il = _elements.length; i < il; ++i) {
const e = _elements[i]
const key = hash2(hashFnv32a(e.ranges), hashFnv32a(e.set))
if (elementGroups.has(key)) {
const { groupedUnits } = elementGroups.get(key)!
if (groupedUnits.has(e.unit.invariantId)) {
groupedUnits.get(e.unit.invariantId)!.push(e.unit.id)
} else {
groupedUnits.set(e.unit.invariantId, [e.unit.id])
}
} else {
const groupedUnits = new Map<number, number[]>()
groupedUnits.set(e.unit.invariantId, [e.unit.id])
elementGroups.set(key, { groupedUnits, set: e.set, ranges: e.ranges })
}
}
const elements: QueryElement[] = []
elementGroups.forEach(e => {
const groupedUnits: SortedArray<number>[] = []
e.groupedUnits.forEach(g => groupedUnits.push(SortedArray.ofUnsortedArray(g)))
groupedUnits.sort((a, b) => a[0] - b[0]) // sort by first unit id of each group
elements.push({ groupedUnits, set: e.set, ranges: e.ranges })
})
return { hash: loci.structure.root.hashCode, elements }
}
function getUnitsFromIds(unitIds: ArrayLike<number>, structure: Structure) {
const units: Unit[] = []
for (let i = 0, il = unitIds.length; i < il; ++i) {
const unitId = unitIds[i]
if (structure.unitMap.has(unitId)) units.push(structure.unitMap.get(unitId))
}
return units
}
export function toLoci(query: Query, parent: Structure): Loci {
if (query.hash !== -1 && query.hash !== parent.root.hashCode) {
new Error('Query not compatible with given structure')
}
const elements: Loci['elements'][0][] = []
for (const e of query.elements) {
for (const g of e.groupedUnits) {
const units = getUnitsFromIds(g, parent)
if (units.length === 0) continue
let indices: OrderedSet<UnitIndex>
if (e.ranges.length === 0) {
indices = e.set
} else if (e.set.length === 0) {
if (e.ranges.length === 2) {
indices = Interval.ofRange(e.ranges[0], e.ranges[1])
} else {
const _indices = new Int32Array(SortedRanges.size(e.ranges))
SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = v)
indices = SortedArray.ofSortedArray(_indices)
}
} else {
const rangesSize = SortedRanges.size(e.ranges)
const _indices = new Int32Array(e.set.length + rangesSize)
SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = v)
_indices.set(e.set, rangesSize)
indices = SortedArray.ofUnsortedArray(_indices) // requires sort
}
for (const unit of units) {
elements.push({ unit, indices })
}
}
}
return Loci(parent, elements)
}
export function toStructure(query: Query, parent: Structure): Structure {
if (query.hash !== -1 && query.hash !== parent.root.hashCode) {
new Error('Query not compatible with given structure')
}
const units: Unit[] = []
for (const e of query.elements) {
for (const g of e.groupedUnits) {
const _units = getUnitsFromIds(g, parent)
if (_units.length === 0) continue
const unit = _units[0] // the elements are grouped by unit.invariantId
const rangesSize = SortedRanges.size(e.ranges)
const _indices = new Int32Array(e.set.length + rangesSize)
let indices: SortedArray<ElementIndex>
if (e.ranges.length === 0) {
for (let i = 0, il = e.set.length; i < il; ++i) {
_indices[i] = unit.elements[e.set[i]]
}
indices = SortedArray.ofSortedArray(_indices)
} else if (e.set.length === 0) {
SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = unit.elements[v])
indices = SortedArray.ofSortedArray(_indices)
} else {
const rangesSize = SortedRanges.size(e.ranges)
SortedRanges.forEach(e.ranges, (v, i) => _indices[i] = unit.elements[v])
SortedRanges.forEach(e.set, (v, i) => _indices[i + rangesSize] = unit.elements[v])
indices = SortedArray.ofUnsortedArray(_indices) // requires sort
}
for (const unit of _units) {
units.push(unit.getChild(indices))
}
}
}
return Structure.create(units, { parent })
}
export function areEqual(a: Query, b: Query) {
if (a.elements.length !== b.elements.length) return false
for (let i = 0, il = a.elements.length; i < il; ++i) {
const elementA = a.elements[i], elementB = b.elements[i]
if (elementA.groupedUnits.length !== elementB.groupedUnits.length) return false
for (let j = 0, jl = elementB.groupedUnits.length; j < jl; ++j) {
if (!SortedArray.areEqual(elementA.groupedUnits[j], elementB.groupedUnits[j])) return false
}
if (!SortedArray.areEqual(elementA.set, elementB.set)) return false
if (!SortedRanges.areEqual(elementA.ranges, elementB.ranges)) return false
}
return true
}
}
}

View File

@@ -8,10 +8,12 @@ import StructureElement from './element'
import Unit from './unit'
import { VdwRadius } from '../model/properties/atomic';
function p<T>(p: StructureElement.Property<T>) { return p; }
const constant = {
true: StructureElement.property(l => true),
false: StructureElement.property(l => false),
zero: StructureElement.property(l => 0)
true: p(l => true),
false: p(l => false),
zero: p(l => 0)
}
function notAtomic(): never {
@@ -26,93 +28,123 @@ function notCoarse(kind?: string): never {
// TODO: remove the type checks?
const atom = {
key: StructureElement.property(l => l.element),
key: p(l => l.element),
// Conformation
x: StructureElement.property(l => l.unit.conformation.x(l.element)),
y: StructureElement.property(l => l.unit.conformation.y(l.element)),
z: StructureElement.property(l => l.unit.conformation.z(l.element)),
id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.atomId.value(l.element)),
occupancy: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.occupancy.value(l.element)),
B_iso_or_equiv: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.B_iso_or_equiv.value(l.element)),
sourceIndex: StructureElement.property(l => Unit.isAtomic(l.unit)
x: p(l => l.unit.conformation.x(l.element)),
y: p(l => l.unit.conformation.y(l.element)),
z: p(l => l.unit.conformation.z(l.element)),
id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.atomId.value(l.element)),
occupancy: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.occupancy.value(l.element)),
B_iso_or_equiv: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.B_iso_or_equiv.value(l.element)),
sourceIndex: p(l => Unit.isAtomic(l.unit)
? l.unit.model.atomicHierarchy.atoms.sourceIndex.value(l.element)
// TODO: when implemented, this should map to the source index.
: l.element),
// Hierarchy
type_symbol: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element)),
label_atom_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_atom_id.value(l.element)),
auth_atom_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_atom_id.value(l.element)),
label_alt_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_alt_id.value(l.element)),
pdbx_formal_charge: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.value(l.element)),
type_symbol: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element)),
label_atom_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_atom_id.value(l.element)),
auth_atom_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_atom_id.value(l.element)),
label_alt_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_alt_id.value(l.element)),
pdbx_formal_charge: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.value(l.element)),
// Derived
vdw_radius: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element))),
vdw_radius: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element))),
}
function compId(l: StructureElement) {
return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])
}
const residue = {
key: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.residueIndex[l.element]),
key: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.residueIndex[l.element]),
group_PDB: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
label_comp_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])),
auth_comp_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])),
label_seq_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])),
auth_seq_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])),
pdbx_PDB_ins_code: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element])),
group_PDB: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
label_comp_id: p(compId),
auth_comp_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])),
label_seq_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])),
auth_seq_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])),
pdbx_PDB_ins_code: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element])),
// Properties
secondary_structure_type: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.type[l.unit.residueIndex[l.element]]),
secondary_structure_key: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.key[l.unit.residueIndex[l.element]]),
isModified: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.modifiedResidues.parentId.has(compId(l))),
modifiedParentName: p(l => {
if (!Unit.isAtomic(l.unit)) notAtomic()
const id = compId(l)
return l.unit.model.properties.modifiedResidues.parentId.get(id) || id
}),
secondary_structure_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.type[l.unit.residueIndex[l.element]]),
secondary_structure_key: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.key[l.unit.residueIndex[l.element]]),
chem_comp_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.type),
}
const chain = {
key: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.chainIndex[l.element]),
key: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.chainIndex[l.element]),
label_asym_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
auth_asym_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
label_entity_id: StructureElement.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
label_asym_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
auth_asym_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
label_entity_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
}
const coarse = {
key: atom.key,
entityKey: StructureElement.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.entityKey[l.element]),
entityKey: p(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.entityKey[l.element]),
x: atom.x,
y: atom.y,
z: atom.z,
asym_id: StructureElement.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.asym_id.value(l.element)),
seq_id_begin: StructureElement.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_begin.value(l.element)),
seq_id_end: StructureElement.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_end.value(l.element)),
asym_id: p(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.asym_id.value(l.element)),
seq_id_begin: p(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_begin.value(l.element)),
seq_id_end: p(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_end.value(l.element)),
sphere_radius: StructureElement.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.radius[l.element]),
sphere_rmsf: StructureElement.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.rmsf[l.element]),
sphere_radius: p(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.radius[l.element]),
sphere_rmsf: p(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.rmsf[l.element]),
gaussian_weight: StructureElement.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.weight[l.element]),
gaussian_covariance_matrix: StructureElement.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.covariance_matrix[l.element])
gaussian_weight: p(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.weight[l.element]),
gaussian_covariance_matrix: p(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.covariance_matrix[l.element])
}
const eK = StructureElement.entityIndex
function eK(l: StructureElement) {
switch (l.unit.kind) {
case Unit.Kind.Atomic:
return l.unit.model.atomicHierarchy.index.getEntityFromChain(l.unit.chainIndex[l.element])
case Unit.Kind.Spheres:
return l.unit.model.coarseHierarchy.spheres.entityKey[l.element]
case Unit.Kind.Gaussians:
return l.unit.model.coarseHierarchy.gaussians.entityKey[l.element]
}
}
const entity = {
key: eK,
key: p(eK),
id: StructureElement.property(l => l.unit.model.entities.data.id.value(eK(l))),
type: StructureElement.property(l => l.unit.model.entities.data.type.value(eK(l))),
src_method: StructureElement.property(l => l.unit.model.entities.data.src_method.value(eK(l))),
pdbx_description: StructureElement.property(l => l.unit.model.entities.data.pdbx_description.value(eK(l))),
formula_weight: StructureElement.property(l => l.unit.model.entities.data.formula_weight.value(eK(l))),
pdbx_number_of_molecules: StructureElement.property(l => l.unit.model.entities.data.pdbx_number_of_molecules.value(eK(l))),
details: StructureElement.property(l => l.unit.model.entities.data.details.value(eK(l))),
pdbx_mutation: StructureElement.property(l => l.unit.model.entities.data.pdbx_mutation.value(eK(l))),
pdbx_fragment: StructureElement.property(l => l.unit.model.entities.data.pdbx_fragment.value(eK(l))),
pdbx_ec: StructureElement.property(l => l.unit.model.entities.data.pdbx_ec.value(eK(l)))
id: p(l => l.unit.model.entities.data.id.value(eK(l))),
type: p(l => l.unit.model.entities.data.type.value(eK(l))),
src_method: p(l => l.unit.model.entities.data.src_method.value(eK(l))),
pdbx_description: p(l => l.unit.model.entities.data.pdbx_description.value(eK(l))),
formula_weight: p(l => l.unit.model.entities.data.formula_weight.value(eK(l))),
pdbx_number_of_molecules: p(l => l.unit.model.entities.data.pdbx_number_of_molecules.value(eK(l))),
details: p(l => l.unit.model.entities.data.details.value(eK(l))),
pdbx_mutation: p(l => l.unit.model.entities.data.pdbx_mutation.value(eK(l))),
pdbx_fragment: p(l => l.unit.model.entities.data.pdbx_fragment.value(eK(l))),
pdbx_ec: p(l => l.unit.model.entities.data.pdbx_ec.value(eK(l)))
}
const unit = {
operator_name: StructureElement.property(l => l.unit.conformation.operator.name),
model_num: StructureElement.property(l => l.unit.model.modelNum)
id: p(l => l.unit.id),
object_primitive: p(l => l.unit.objectPrimitive),
operator_name: p(l => l.unit.conformation.operator.name),
model_index: p(l => l.unit.model.modelNum),
model_label: p(l => l.unit.model.label),
hkl: p(l => l.unit.conformation.operator.hkl),
spgrOp: p(l => l.unit.conformation.operator.spgrOp),
model_num: p(l => l.unit.model.modelNum),
pdbx_struct_assembly_id: p(l => l.unit.conformation.operator.assembly.id),
pdbx_struct_oper_list_ids: p(l => l.unit.conformation.operator.assembly.operList),
struct_ncs_oper_id: p(l => l.unit.conformation.operator.ncsId),
}
const StructureProperties = {

View File

@@ -1,11 +1,11 @@
/**
* Copyright (c) 2017-2018 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>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { IntMap, SortedArray, Iterator, Segmentation } from '../../../mol-data/int'
import { IntMap, SortedArray, Iterator, Segmentation, Interval } from '../../../mol-data/int'
import { UniqueArray } from '../../../mol-data/generic'
import { SymmetryOperator } from '../../../mol-math/geometry/symmetry-operator'
import { Model, ElementIndex } from '../model'
@@ -27,6 +27,7 @@ import { idFactory } from '../../../mol-util/id-factory';
import { GridLookup3D } from '../../../mol-math/geometry';
import { UUID } from '../../../mol-util';
import { CustomProperties } from '../common/custom-property';
import { AtomicHierarchy } from '../model/properties/atomic';
class Structure {
/** Maps unit.id to unit */
@@ -43,6 +44,8 @@ class Structure {
carbohydrates?: Carbohydrates,
models?: ReadonlyArray<Model>,
model?: Model,
masterModel?: Model,
representativeModel?: Model,
uniqueResidueNames?: Set<string>,
entityIndices?: ReadonlyArray<EntityIndex>,
uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
@@ -52,6 +55,7 @@ class Structure {
elementCount: number,
polymerResidueCount: number,
coordinateSystem: SymmetryOperator,
label: string,
propertyData?: any,
customProps?: CustomProperties
} = {
@@ -59,7 +63,8 @@ class Structure {
transformHash: -1,
elementCount: 0,
polymerResidueCount: 0,
coordinateSystem: SymmetryOperator.Default
coordinateSystem: SymmetryOperator.Default,
label: ''
};
subsetBuilder(isSorted: boolean) {
@@ -141,6 +146,12 @@ class Structure {
return new Structure.ElementLocationIterator(this);
}
/** The parent or itself in case this is the root */
get root() {
return this._props.parent || this;
}
/** The root/top-most parent or `undefined` in case this is the root */
get parent() {
return this._props.parent;
}
@@ -149,6 +160,10 @@ class Structure {
return this._props.coordinateSystem;
}
get label() {
return this._props.label;
}
get boundary() {
return this.lookup3d.boundary;
}
@@ -203,20 +218,39 @@ class Structure {
|| (this._props.uniqueAtomicResidueIndices = getUniqueAtomicResidueIndices(this));
}
/** If the structure is based on a single model, return it. Otherwise throw an exception. */
/**
* If the structure is based on a single model or has a master-/representative-model, return it.
* Otherwise throw an exception.
*/
get model(): Model {
if (this._props.model) return this._props.model;
if (this._props.representativeModel) return this._props.representativeModel;
if (this._props.masterModel) return this._props.masterModel;
const models = this.models;
if (models.length > 1) throw new Error('The structre is based on multiple models.');
if (models.length > 1) {
throw new Error('The structure is based on multiple models and has neither a master- nor a representative-model.');
}
this._props.model = models[0];
return this._props.model;
}
get masterModel(): Model | undefined {
return this._props.masterModel
}
get representativeModel(): Model | undefined {
return this._props.representativeModel
}
hasElement(e: StructureElement) {
if (!this.unitMap.has(e.unit.id)) return false;
return SortedArray.has(this.unitMap.get(e.unit.id).elements, e.element);
}
getModelIndex(m: Model) {
return this.model
}
private initUnits(units: ArrayLike<Unit>) {
const map = IntMap.Mutable<Unit>();
let elementCount = 0;
@@ -237,12 +271,22 @@ class Structure {
return map;
}
constructor(units: ArrayLike<Unit>, parent: Structure | undefined, coordinateSystem?: SymmetryOperator) {
constructor(units: ArrayLike<Unit>, props: Structure.Props = {}) {
this.unitMap = this.initUnits(units);
this.units = units as ReadonlyArray<Unit>;
if (parent) this._props.parent = parent;
if (coordinateSystem) this._props.coordinateSystem = coordinateSystem;
else if (parent) this._props.coordinateSystem = parent.coordinateSystem;
if (props.parent) this._props.parent = props.parent.parent || props.parent;
if (props.coordinateSystem) this._props.coordinateSystem = props.coordinateSystem;
else if (props.parent) this._props.coordinateSystem = props.parent.coordinateSystem;
if (props.label) this._props.label = props.label;
else if (props.parent) this._props.label = props.parent.label;
if (props.masterModel) this._props.masterModel = props.masterModel;
else if (props.parent) this._props.masterModel = props.parent.masterModel;
if (props.representativeModel) this._props.representativeModel = props.representativeModel;
else if (props.parent) this._props.representativeModel = props.parent.representativeModel;
}
}
@@ -333,7 +377,17 @@ function getUniqueAtomicResidueIndices(structure: Structure): ReadonlyMap<UUID,
}
namespace Structure {
export const Empty = new Structure([], void 0, void 0);
export const Empty = new Structure([]);
export interface Props {
parent?: Structure
coordinateSystem?: SymmetryOperator
label?: string
/** Master model for structures of a protein model and multiple ligand models */
masterModel?: Model
/** Representative model for structures of a model trajectory */
representativeModel?: Model
}
/** Represents a single structure */
export interface Loci {
@@ -344,6 +398,14 @@ namespace Structure {
return { kind: 'structure-loci', structure };
}
export function toStructureElementLoci(loci: Loci): StructureElement.Loci {
const elements: StructureElement.Loci['elements'][0][] = []
for (const unit of loci.structure.units) {
elements.push({ unit, indices: Interval.ofBounds(0, unit.elements.length) })
}
return StructureElement.Loci(loci.structure, elements);
}
export function isLoci(x: any): x is Loci {
return !!x && x.kind === 'structure-loci';
}
@@ -352,34 +414,66 @@ namespace Structure {
return a.structure === b.structure
}
export function create(units: ReadonlyArray<Unit>, parent: Structure | undefined, coordinateSystem?: SymmetryOperator): Structure {
return new Structure(units, parent, coordinateSystem);
export function create(units: ReadonlyArray<Unit>, props?: Props): Structure {
return new Structure(units, props);
}
export function ofTrajectory(trajectory: ReadonlyArray<Model>): Structure {
if (trajectory.length === 0) return Empty
const units: Unit[] = [];
let count = 0
for (let i = 0, il = trajectory.length; i < il; ++i) {
const structure = ofModel(trajectory[i])
for (let j = 0, jl = structure.units.length; j < jl; ++j) {
const u = structure.units[j]
const invariantId = u.invariantId + count
const newUnit = Unit.create(units.length, invariantId, u.kind, u.model, u.conformation.operator, u.elements)
units.push(newUnit)
}
count = units.length
}
return create(units, { representativeModel: trajectory[0], label: trajectory[0].label });
}
/**
* Construct a Structure from a model.
*
* Generally, a single unit corresponds to a single chain, with the exception
* of consecutive "single atom chains".
* of consecutive "single atom chains" with same entity id.
*/
export function ofModel(model: Model): Structure {
const chains = model.atomicHierarchy.chainAtomSegments;
const builder = new StructureBuilder(void 0, void 0);
const builder = new StructureBuilder({ label: model.label });
for (let c = 0; c < chains.count; c++) {
for (let c = 0 as ChainIndex; c < chains.count; c++) {
const start = chains.offsets[c];
// merge all consecutive "single atom chains"
// set to true for chains that consist of "single atom residues",
// note that it assumes there are no "zero atom residues"
let singleAtomResidues = AtomicHierarchy.chainResidueCount(model.atomicHierarchy, c) === chains.offsets[c + 1] - chains.offsets[c]
// merge all consecutive "single atom chains" with same entity id
while (c + 1 < chains.count
&& chains.offsets[c + 1] - chains.offsets[c] === 1
&& chains.offsets[c + 2] - chains.offsets[c + 1] === 1) {
&& chains.offsets[c + 2] - chains.offsets[c + 1] === 1
) {
c++;
singleAtomResidues = true
const e1 = model.atomicHierarchy.index.getEntityFromChain(c);
const e2 = model.atomicHierarchy.index.getEntityFromChain(c + 1 as ChainIndex);
if (e1 !== e2) break
}
const elements = SortedArray.ofBounds(start as ElementIndex, chains.offsets[c + 1] as ElementIndex);
if (isWaterChain(model, c as ChainIndex, elements)) {
partitionAtomicUnit(model, elements, builder);
if (singleAtomResidues) {
partitionAtomicUnitByAtom(model, elements, builder);
} else if (elements.length > 200000 || isWaterChain(model, c)) {
// split up very large chains e.g. lipid bilayers, micelles or water with explicit H
partitionAtomicUnitByResidue(model, elements, builder);
} else {
builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements);
}
@@ -398,14 +492,14 @@ namespace Structure {
return builder.getStructure();
}
function isWaterChain(model: Model, chainIndex: ChainIndex, indices: SortedArray) {
function isWaterChain(model: Model, chainIndex: ChainIndex) {
const e = model.atomicHierarchy.index.getEntityFromChain(chainIndex);
return model.entities.data.type.value(e) === 'water';
}
function partitionAtomicUnit(model: Model, indices: SortedArray, builder: StructureBuilder) {
function partitionAtomicUnitByAtom(model: Model, indices: SortedArray, builder: StructureBuilder) {
const { x, y, z } = model.atomicConformation;
const lookup = GridLookup3D({ x, y, z, indices }, Vec3.create(64, 64, 64));
const lookup = GridLookup3D({ x, y, z, indices }, 8192);
const { offset, count, array } = lookup.buckets;
for (let i = 0, _i = offset.length; i < _i; i++) {
@@ -418,6 +512,37 @@ namespace Structure {
}
}
// keeps atoms of residues together
function partitionAtomicUnitByResidue(model: Model, indices: SortedArray, builder: StructureBuilder) {
model.atomicHierarchy.residueAtomSegments.offsets
const startIndices: number[] = []
const endIndices: number[] = []
const residueIt = Segmentation.transientSegments(model.atomicHierarchy.residueAtomSegments, indices)
while (residueIt.hasNext) {
const residueSegment = residueIt.move();
startIndices[startIndices.length] = indices[residueSegment.start]
endIndices[endIndices.length] = indices[residueSegment.end]
}
const { x, y, z } = model.atomicConformation;
const lookup = GridLookup3D({ x, y, z, indices: SortedArray.ofSortedArray(startIndices) }, 8192);
const { offset, count, array } = lookup.buckets;
for (let i = 0, _i = offset.length; i < _i; i++) {
const start = offset[i];
const set: number[] = [];
for (let j = 0, _j = count[i]; j < _j; j++) {
const k = array[start + j]
for (let l = startIndices[k], _l = endIndices[k]; l < _l; l++) {
set[set.length] = l;
}
}
builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(new Int32Array(set)));
}
}
function addCoarseUnits(builder: StructureBuilder, model: Model, elements: CoarseElements, kind: Unit.Kind) {
const { chainElementSegments } = elements;
for (let cI = 0; cI < chainElementSegments.count; cI++) {
@@ -439,15 +564,16 @@ namespace Structure {
const cs = s.coordinateSystem;
const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs.assembly, cs.ncsId, cs.hkl), cs);
return new Structure(units, s, newCS);
return new Structure(units, { parent: s, coordinateSystem: newCS });
}
export class StructureBuilder {
private units: Unit[] = [];
private invariantId = idFactory()
addUnit(kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set): Unit {
const unit = Unit.create(this.units.length, this.invariantId(), kind, model, operator, elements);
addUnit(kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set, invariantId?: number): Unit {
if (invariantId === undefined) invariantId = this.invariantId()
const unit = Unit.create(this.units.length, invariantId, kind, model, operator, elements);
this.units.push(unit);
return unit;
}
@@ -459,20 +585,20 @@ namespace Structure {
}
getStructure(): Structure {
return create(this.units, this.parent, this.coordinateSystem);
return create(this.units, this.props);
}
get isEmpty() {
return this.units.length === 0;
}
constructor(private parent: Structure | undefined, private coordinateSystem: SymmetryOperator | undefined) {
constructor(private props: Props = {}) {
}
}
export function Builder(parent: Structure | undefined, coordinateSystem: SymmetryOperator | undefined) {
return new StructureBuilder(parent, coordinateSystem);
export function Builder(props: Props = {}) {
return new StructureBuilder(props);
}
export function hashCode(s: Structure) {
@@ -508,8 +634,13 @@ namespace Structure {
}
/** Check if the structures or their parents are equivalent */
export function areParentsEquivalent(a: Structure, b: Structure) {
return areEquivalent(a.parent || a, b.parent || b)
export function areRootsEquivalent(a: Structure, b: Structure) {
return areEquivalent(a.root, b.root)
}
/** Check if the structures or their parents are equal */
export function areRootsEqual(a: Structure, b: Structure) {
return a.root === b.root
}
export class ElementLocationIterator implements Iterator<StructureElement> {

View File

@@ -24,7 +24,8 @@ namespace StructureSymmetry {
const assembly = ModelSymmetry.findAssembly(models[0], asmName);
if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`);
const assembler = Structure.Builder(void 0, SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] }));
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] })
const assembler = Structure.Builder({ coordinateSystem, label: structure.label });
const queryCtx = new QueryContext(structure);
@@ -95,29 +96,29 @@ namespace StructureSymmetry {
}
function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3) {
const { spacegroup, ncsOperators } = symmetry;
const ncsCount = (ncsOperators && ncsOperators.length) || 0
const operators: SymmetryOperator[] = [];
const { spacegroup } = symmetry;
if (ijkMin[0] <= 0 && ijkMax[0] >= 0 &&
if (!ncsCount &&
ijkMin[0] <= 0 && ijkMax[0] >= 0 &&
ijkMin[1] <= 0 && ijkMax[1] >= 0 &&
ijkMin[2] <= 0 && ijkMax[2] >= 0) {
operators[0] = Spacegroup.getSymmetryOperator(spacegroup, 0, 0, 0, 0)
}
const { ncsOperators } = symmetry
const ncsCount = (ncsOperators && ncsOperators.length) || 0
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++) {
// we have added identity as the 1st operator.
if (op === 0 && i === 0 && j === 0 && k === 0) continue;
// 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);
if (ncsCount) {
for (let u = 0; u < ncsCount; ++u) {
const ncsOp = ncsOperators![u]
const matrix = Mat4.mul(Mat4.zero(), symOp.matrix, ncsOp.matrix)
const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, symOp.assembly, ncsOp.ncsId, symOp.hkl);
const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, symOp.assembly, ncsOp.ncsId, symOp.hkl, symOp.spgrOp);
operators[operators.length] = operator;
}
} else {
@@ -137,7 +138,7 @@ function getOperatorsCached333(symmetry: ModelSymmetry) {
}
function assembleOperators(structure: Structure, operators: ReadonlyArray<SymmetryOperator>) {
const assembler = Structure.Builder(void 0, void 0);
const assembler = Structure.Builder({ label: structure.label });
const { units } = structure;
for (const oper of operators) {
for (const unit of units) {
@@ -179,7 +180,7 @@ async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius
const operators = getOperatorsCached333(symmetry);
const lookup = structure.lookup3d;
const assembler = Structure.Builder(void 0, void 0);
const assembler = Structure.Builder();
const { units } = structure;
const center = Vec3.zero();

View File

@@ -17,6 +17,7 @@ import { ChainIndex, ResidueIndex, ElementIndex } from '../model/indexing';
import { IntMap, SortedArray } 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';
/**
* A building block of a structure that corresponds to an atomic or
@@ -95,7 +96,7 @@ namespace Unit {
export interface Base {
readonly id: number,
// invariant ID stays the same even if the Operator/conformation changes.
/** invariant ID stays the same even if the Operator/conformation changes. */
readonly invariantId: number,
readonly elements: StructureElement.Set,
readonly model: Model,
@@ -107,6 +108,10 @@ namespace Unit {
readonly lookup3d: Lookup3D
readonly polymerElements: SortedArray<ElementIndex>
readonly gapElements: SortedArray<ElementIndex>
/**
* From mmCIF/IHM schema: `_ihm_model_representation_details.model_object_primitive`.
*/
readonly objectPrimitive: mmCIF_Schema['ihm_model_representation_details']['model_object_primitive']['T']
}
function getSphereRadiusFunc(model: Model) {
@@ -130,6 +135,7 @@ namespace Unit {
*/
export class Atomic implements Base {
readonly kind = Kind.Atomic;
readonly objectPrimitive = 'atomistic';
readonly id: number;
readonly invariantId: number;
@@ -237,6 +243,7 @@ namespace Unit {
class Coarse<K extends Kind.Gaussians | Kind.Spheres, C extends CoarseSphereConformation | CoarseGaussianConformation> implements Base {
readonly kind: K;
readonly objectPrimitive: 'sphere' | 'gaussian';
readonly id: number;
readonly invariantId: number;
@@ -287,6 +294,7 @@ namespace Unit {
constructor(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: CoarseProperties) {
this.kind = kind;
this.objectPrimitive = kind === Kind.Spheres ? 'sphere' : 'gaussian'
this.id = id;
this.invariantId = invariantId;
this.model = model;

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