mirror of
https://github.com/molstar/molstar.git
synced 2026-06-07 15:14:22 +08:00
Compare commits
217 Commits
v0.6.0-dev
...
v0.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a7413a8d9 | ||
|
|
593b34bfe3 | ||
|
|
691b1721b2 | ||
|
|
b8133b4300 | ||
|
|
0c459b98db | ||
|
|
060725642f | ||
|
|
eaf28bf016 | ||
|
|
ed6084c40b | ||
|
|
e56063b065 | ||
|
|
f8ddfb1638 | ||
|
|
900f2e1f76 | ||
|
|
00c9e05a65 | ||
|
|
ecaab51315 | ||
|
|
2a4d45714c | ||
|
|
6e13ef5cbc | ||
|
|
f48e2ab238 | ||
|
|
0e34d976c0 | ||
|
|
2d6cd4c6da | ||
|
|
4a4b3ef5b4 | ||
|
|
67e6167b55 | ||
|
|
79b33bfbd8 | ||
|
|
f5fc96ee3b | ||
|
|
691571ec39 | ||
|
|
f2b20a646e | ||
|
|
bef9ff86d2 | ||
|
|
ff3da5f2db | ||
|
|
129727d5d1 | ||
|
|
6e26b4d5f9 | ||
|
|
371bb11ce8 | ||
|
|
556196ae3f | ||
|
|
f5667411d7 | ||
|
|
774f419a53 | ||
|
|
9fa5d40306 | ||
|
|
ba89d5ec1e | ||
|
|
b7fa577d9b | ||
|
|
949425d14d | ||
|
|
2cb0d63750 | ||
|
|
038aa47578 | ||
|
|
c833598c26 | ||
|
|
489c52361a | ||
|
|
9edf41a2f2 | ||
|
|
5bcca99f60 | ||
|
|
1b0a310fc7 | ||
|
|
e3e7fa3040 | ||
|
|
5297dd6f11 | ||
|
|
c557e93255 | ||
|
|
ad5f0c987a | ||
|
|
30aa6d035d | ||
|
|
b198289fc4 | ||
|
|
51e45085f1 | ||
|
|
97ebb2dccc | ||
|
|
29d28bd3f4 | ||
|
|
09c5c040f2 | ||
|
|
6e60a2f9dc | ||
|
|
5d36108113 | ||
|
|
f4a423ebe5 | ||
|
|
9942fbe549 | ||
|
|
dbde7521e4 | ||
|
|
6eee3e8368 | ||
|
|
cc0ccd7830 | ||
|
|
7ad25249a9 | ||
|
|
687c7d54ff | ||
|
|
d6fff1ffdf | ||
|
|
20a8b8892e | ||
|
|
5c09ecc98d | ||
|
|
19539a2c9d | ||
|
|
5c093c7f22 | ||
|
|
c43ed90607 | ||
|
|
a85242d9c5 | ||
|
|
722d2a9c6c | ||
|
|
f7d65ff52c | ||
|
|
7bee2be12d | ||
|
|
326649d4f5 | ||
|
|
6aba5df55d | ||
|
|
57bbcf9425 | ||
|
|
08ba34d607 | ||
|
|
4c122227a7 | ||
|
|
e14afb4dad | ||
|
|
472def49f6 | ||
|
|
90549893e3 | ||
|
|
73a8e45202 | ||
|
|
35df55cb4f | ||
|
|
c2028d20a8 | ||
|
|
eaffdc6a98 | ||
|
|
0b1e6100a9 | ||
|
|
2168905c11 | ||
|
|
f955e6a299 | ||
|
|
98f3981e12 | ||
|
|
82f94d20ea | ||
|
|
fbc6d47117 | ||
|
|
6a2e4cf813 | ||
|
|
4aabef7683 | ||
|
|
74ae91ee1b | ||
|
|
8b62766474 | ||
|
|
2995504916 | ||
|
|
63f6848d26 | ||
|
|
988e429693 | ||
|
|
ba1c6ef046 | ||
|
|
7ceff92a4e | ||
|
|
1f968b2836 | ||
|
|
d97d7e3b14 | ||
|
|
5c4c4811e4 | ||
|
|
f2a6e63a20 | ||
|
|
1aace4a26f | ||
|
|
b1c140d23e | ||
|
|
ee16212c31 | ||
|
|
f6d232b1c5 | ||
|
|
c8a002933e | ||
|
|
4757ca9913 | ||
|
|
5264c75e37 | ||
|
|
032bf44863 | ||
|
|
2f4f5e43f3 | ||
|
|
7b1edcadf6 | ||
|
|
f0f74d182d | ||
|
|
59142adbbc | ||
|
|
2fda8c5db1 | ||
|
|
7b5efa3e42 | ||
|
|
d784d202bd | ||
|
|
8833474a43 | ||
|
|
57cbb2f8b6 | ||
|
|
b6112a914f | ||
|
|
2008f8538c | ||
|
|
09fba43a1c | ||
|
|
b76c3613f9 | ||
|
|
cf4ddcb587 | ||
|
|
696106f48b | ||
|
|
fb286cd9cf | ||
|
|
5121bd700e | ||
|
|
6173520ad0 | ||
|
|
a7189232dd | ||
|
|
4a96b45b04 | ||
|
|
20ac549dd6 | ||
|
|
38be00c0b7 | ||
|
|
0a9bdc8cf6 | ||
|
|
0b4318280a | ||
|
|
b1da60e1c0 | ||
|
|
2cc5987f9e | ||
|
|
745415a1d8 | ||
|
|
c80658f368 | ||
|
|
033675a417 | ||
|
|
3dd57e9dc8 | ||
|
|
1e3daa6c98 | ||
|
|
8855f51cfc | ||
|
|
50d8debb2b | ||
|
|
e682eb78b0 | ||
|
|
bd2bbf3e2d | ||
|
|
f38f040aea | ||
|
|
f912b2d802 | ||
|
|
0ad03e6a0b | ||
|
|
160d8c529e | ||
|
|
28edfd810c | ||
|
|
7b931cfb66 | ||
|
|
5575c61577 | ||
|
|
8f93dce105 | ||
|
|
0eb3d7226a | ||
|
|
a9533b666c | ||
|
|
6d67b4db56 | ||
|
|
1b5eff6454 | ||
|
|
83fb28cc9d | ||
|
|
c39ffc0b0e | ||
|
|
23892cfbdd | ||
|
|
3bdabc444d | ||
|
|
c233be4467 | ||
|
|
8468f33803 | ||
|
|
0e77369fdb | ||
|
|
9fcc8e7977 | ||
|
|
538371ced8 | ||
|
|
380887bd22 | ||
|
|
17b4b1cb86 | ||
|
|
3e7c358c07 | ||
|
|
05b592a173 | ||
|
|
e1d0515fae | ||
|
|
d4d3b9645e | ||
|
|
d12d99dcfa | ||
|
|
174324d21c | ||
|
|
26156a5982 | ||
|
|
af5ddf6950 | ||
|
|
2ef35e5fb9 | ||
|
|
513bfeaae7 | ||
|
|
7311e6f484 | ||
|
|
90ecedcae8 | ||
|
|
352a20ac48 | ||
|
|
b1d8c5f6ea | ||
|
|
6497be9e52 | ||
|
|
40a8cee8e5 | ||
|
|
112cfcac90 | ||
|
|
8016fcbd54 | ||
|
|
6de97f1d03 | ||
|
|
204075bbe0 | ||
|
|
eb448bce37 | ||
|
|
3d09b5cb67 | ||
|
|
35d06040f7 | ||
|
|
0868e81944 | ||
|
|
7efbeb7d0f | ||
|
|
815f61b550 | ||
|
|
c3a90ab499 | ||
|
|
321126afa2 | ||
|
|
9ad4a9c3c9 | ||
|
|
b12f7c7ce4 | ||
|
|
6d7d3c0794 | ||
|
|
92b988a8d5 | ||
|
|
18952ee2bd | ||
|
|
bff07888f9 | ||
|
|
8e6b0b220a | ||
|
|
74acb0d078 | ||
|
|
d4727eea02 | ||
|
|
76d14eba0c | ||
|
|
90d05d9260 | ||
|
|
23b24bbb6c | ||
|
|
91bc6f07c5 | ||
|
|
9b2181667d | ||
|
|
40347e3e46 | ||
|
|
ed277f6e16 | ||
|
|
af1fb7041e | ||
|
|
e2eb1bf223 | ||
|
|
5a64a6f1a3 | ||
|
|
88aa9303d7 |
24
README.md
24
README.md
@@ -5,7 +5,7 @@
|
||||
|
||||
# Mol*
|
||||
|
||||
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that will serve as basis for the next-generation data delivery and analysis tools for macromolecular structure data. This is a collaboration between PDBe and RCSB PDB teams and the development will be open source and available to anyone who wants to use it for developing visualisation tools for macromolecular structure data available from [PDB](https://www.wwpdb.org/) and other institutions.
|
||||
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that will serve as a basis for the next-generation data delivery and analysis tools for macromolecular structure data. This is a collaboration between PDBe and RCSB PDB teams and the development will be open-source and available to anyone who wants to use it for developing visualization tools for macromolecular structure data available from [PDB](https://www.wwpdb.org/) and other institutions.
|
||||
|
||||
This particular project is the implementation of this technology (still under development).
|
||||
|
||||
@@ -16,26 +16,26 @@ This particular project is the implementation of this technology (still under de
|
||||
The core of Mol* currently consists of these modules (see under `src/`):
|
||||
|
||||
- `mol-task` Computation abstraction with progress tracking and cancellation support.
|
||||
- `mol-data` Collections (integer based sets, interface to columns/tables, etc.)
|
||||
- `mol-data` Collections (integer-based sets, interface to columns/tables, etc.)
|
||||
- `mol-math` Math related (loosely) algorithms and data structures.
|
||||
- `mol-io` Parsing library. Each format is parsed into an interface that corresponds to the data stored by it. Support for common coordinate, experimental/map, and annotation data formats.
|
||||
- `mol-model` Data structures and algorithms (such as querying) for representing molecular data (including coordinate, experimental/map, and annotation data).
|
||||
- `mol-model-formats` Data format parsers for `mol-model`.
|
||||
- `mol-model-props` Common "custom properties".
|
||||
- `mol-script` A scriting language for creating representations/scenes and querying (includes the [MolQL query language](https://molql.github.io)).
|
||||
- `mol-script` A scripting language for creating representations/scenes and querying (includes the [MolQL query language](https://molql.github.io)).
|
||||
- `mol-geo` Creating (molecular) geometries.
|
||||
- `mol-theme` Theming for structure, volume and shape representations.
|
||||
- `mol-repr` Molecular representations for structures, volumes and shapes.
|
||||
- `mol-gl` A wrapper around WebGL.
|
||||
- `mol-canvas3d` A low level 3d view component. Uses `mol-geo` to generate geometries.
|
||||
- `mol-canvas3d` A low-level 3d view component. Uses `mol-geo` to generate geometries.
|
||||
- `mol-state` State representation tree with state saving and automatic updates.
|
||||
- `mol-app` Components for builduing UIs.
|
||||
- `mol-app` Components for building UIs.
|
||||
- `mol-plugin` Allow to define modular Mol* plugin instances utilizing `mol-state` and `mol-canvas3d`.
|
||||
- `mol-plugin-state` State transformations, builders, and managers.
|
||||
- `mol-plugin-ui` React based user interface for the Mol* plugin. Some components of the UI are usable outside the main plugin and can be integrated to 3rd party solutions.
|
||||
- `mol-plugin-ui` React-based user interface for the Mol* plugin. Some components of the UI are usable outside the main plugin and can be integrated into 3rd party solutions.
|
||||
- `mol-util` Useful things that do not fit elsewhere.
|
||||
|
||||
Moreover, the project contains the imlementation of `servers`, including
|
||||
Moreover, the project contains the implementation of `servers`, including
|
||||
|
||||
- `servers/model` A tool for accessing coordinate and annotation data of molecular structures.
|
||||
- `servers/volume` A tool for accessing volumetric experimental data related to molecular structures.
|
||||
@@ -121,16 +121,16 @@ To see all available commands, use ``node build/model-server/preprocess -h``.
|
||||
|
||||
## Development
|
||||
|
||||
### Intallation
|
||||
### Installation
|
||||
|
||||
If node complains about a missine acorn peer dependency, run the following commands
|
||||
If node complains about a missing acorn peer dependency, run the following commands
|
||||
|
||||
npm update acorn --depth 20
|
||||
npm dedupe
|
||||
|
||||
### Editor
|
||||
|
||||
To get syntax highlighting for shader and graphql files add the following to Visual Code's settings files and make sure relevant extanesions are installed in the editor.
|
||||
To get syntax highlighting for shader and graphql files add the following to Visual Code's settings files and make sure relevant extensions are installed in the editor.
|
||||
|
||||
"files.associations": {
|
||||
"*.glsl.ts": "glsl",
|
||||
@@ -142,7 +142,7 @@ To get syntax highlighting for shader and graphql files add the following to Vis
|
||||
## Publish
|
||||
|
||||
### Prerelease
|
||||
npm version prerelease # asumes the current version ends with '-dev.X'
|
||||
npm version prerelease # assumes the current version ends with '-dev.X'
|
||||
npm publish --tag next
|
||||
|
||||
### Release
|
||||
@@ -164,4 +164,4 @@ Continually develop this prototype project. As individual modules become stable,
|
||||
Funding sources include but are not limited to:
|
||||
* [RCSB PDB](https://www.rcsb.org) funding by a grant [DBI-1338415; PI: SK Burley] from the NSF, the NIH, and the US DoE
|
||||
* [PDBe, EMBL-EBI](https://pdbe.org)
|
||||
* [CEITEC](https://www.ceitec.eu/)
|
||||
* [CEITEC](https://www.ceitec.eu/)
|
||||
|
||||
4303
package-lock.json
generated
4303
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.6.0-dev.4",
|
||||
"version": "0.6.2",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -19,10 +19,12 @@
|
||||
"build-webpack": "webpack --mode production",
|
||||
"watch": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack\"",
|
||||
"watch-viewer": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer\"",
|
||||
"watch-viewer-debug": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer-debug\"",
|
||||
"watch-tsc": "tsc --watch --incremental",
|
||||
"watch-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/ --watch",
|
||||
"watch-webpack": "webpack -w --mode development --display errors-only --info-verbosity verbose",
|
||||
"watch-webpack": "webpack -w --mode development --display minimal",
|
||||
"watch-webpack-viewer": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.js",
|
||||
"watch-webpack-viewer-debug": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.debug.js",
|
||||
"serve": "http-server -p 1338",
|
||||
"model-server": "node lib/servers/model/server.js",
|
||||
"model-server-watch": "nodemon --watch lib lib/servers/model/server.js",
|
||||
@@ -82,8 +84,8 @@
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.13.1",
|
||||
"@graphql-codegen/typescript-operations": "^1.13.1",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@typescript-eslint/eslint-plugin": "^2.24.0",
|
||||
"@typescript-eslint/parser": "^2.24.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^2.26.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"circular-dependency-plugin": "^5.2.0",
|
||||
"concurrently": "^5.1.0",
|
||||
@@ -92,9 +94,9 @@
|
||||
"eslint": "^6.8.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.0.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"http-server": "^0.12.1",
|
||||
"jest": "^25.1.0",
|
||||
"jest": "^25.2.7",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.13.1",
|
||||
@@ -104,21 +106,21 @@
|
||||
"sass-loader": "^8.0.2",
|
||||
"simple-git": "^1.132.0",
|
||||
"style-loader": "^1.1.3",
|
||||
"ts-jest": "^25.2.1",
|
||||
"ts-jest": "^25.3.1",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
"@types/benchmark": "^1.0.31",
|
||||
"@types/compression": "1.7.0",
|
||||
"@types/express": "^4.17.3",
|
||||
"@types/jest": "^25.1.4",
|
||||
"@types/node": "^13.9.2",
|
||||
"@types/express": "^4.17.4",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/node": "^13.11.0",
|
||||
"@types/node-fetch": "^2.5.5",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/react": "^16.9.32",
|
||||
"@types/react-dom": "^16.9.6",
|
||||
"@types/swagger-ui-dist": "3.0.5",
|
||||
"argparse": "^1.0.10",
|
||||
"body-parser": "^1.19.0",
|
||||
@@ -126,12 +128,12 @@
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"graphql": "^14.6.0",
|
||||
"immer": "^6.0.2",
|
||||
"immer": "^6.0.3",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"react": "^16.13.0",
|
||||
"react-dom": "^16.13.0",
|
||||
"rxjs": "^6.5.4",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"rxjs": "^6.5.5",
|
||||
"swagger-ui-dist": "^3.25.0",
|
||||
"tslib": "^1.11.1",
|
||||
"util.promisify": "^1.0.1",
|
||||
|
||||
@@ -58,12 +58,14 @@ export namespace StateHelper {
|
||||
|
||||
export function identityTransform(b: StateBuilder.To<PSO.Molecule.Structure>, m: Mat4) {
|
||||
return b.apply(StateTransforms.Model.TransformStructureConformation,
|
||||
{ axis: Vec3.create(1, 0, 0), angle: 0, translation: Vec3.zero() },
|
||||
{ transform: { name: 'components', params: { axis: Vec3.create(1, 0, 0), angle: 0, translation: Vec3.zero() } } },
|
||||
{ tags: 'transform' });
|
||||
}
|
||||
|
||||
export function transform(b: StateBuilder.To<PSO.Molecule.Structure>, matrix: Mat4) {
|
||||
return b.apply(StateTransforms.Model.TransformStructureConformationByMatrix, { matrix }, { tags: 'transform' });
|
||||
return b.apply(StateTransforms.Model.TransformStructureConformation, {
|
||||
transform: { name: 'matrix', params: matrix }
|
||||
}, { tags: 'transform' });
|
||||
}
|
||||
|
||||
export function assemble(b: StateBuilder.To<PSO.Molecule.Model>, id?: string) {
|
||||
|
||||
@@ -144,7 +144,7 @@ async function createBonds() {
|
||||
const comp_id: string[] = []
|
||||
const atom_id_1: string[] = []
|
||||
const atom_id_2: string[] = []
|
||||
const value_order: typeof mmCIF_chemCompBond_schema['value_order']['T'][] = []
|
||||
const value_order: typeof mmCIF_chemCompBond_schema['value_order']['T'][] = []
|
||||
const pdbx_aromatic_flag: typeof mmCIF_chemCompBond_schema['pdbx_aromatic_flag']['T'][] = []
|
||||
const pdbx_stereo_config: typeof mmCIF_chemCompBond_schema['pdbx_stereo_config']['T'][] = []
|
||||
const molstar_protonation_variant: string[] = []
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<!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* ModelServer Query Builder</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,134 +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 * as ReactDOM from 'react-dom'
|
||||
import * as Rx from 'rxjs'
|
||||
|
||||
import { QueryDefinition, QueryList } from '../../servers/model/server/api'
|
||||
|
||||
import './index.html'
|
||||
|
||||
interface State {
|
||||
query: Rx.BehaviorSubject<QueryDefinition>,
|
||||
id: Rx.BehaviorSubject<string>,
|
||||
params: Rx.BehaviorSubject<any>,
|
||||
isBinary: Rx.BehaviorSubject<boolean>,
|
||||
models: Rx.BehaviorSubject<number[]>,
|
||||
url: Rx.Subject<string>
|
||||
}
|
||||
|
||||
class Root extends React.Component<{ state: State }, { }> {
|
||||
render() {
|
||||
return <div>
|
||||
<div>
|
||||
Query: <QuerySelect state={this.props.state} />
|
||||
</div>
|
||||
<div>
|
||||
ID: <input type='text' onChange={t => this.props.state.id.next(t.currentTarget.value)} />
|
||||
</div>
|
||||
<div>
|
||||
Params:<br/>
|
||||
<QueryParams state={this.props.state} />
|
||||
</div>
|
||||
<div>
|
||||
Model numbers (empty for all): <ModelNums state={this.props.state} />
|
||||
</div>
|
||||
<div>
|
||||
<input type='checkbox' onChange={t => this.props.state.isBinary.next(!!t.currentTarget.checked)} /> Binary
|
||||
</div>
|
||||
<div>
|
||||
Query string:
|
||||
<QueryUrl state={this.props.state} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
class QuerySelect extends React.Component<{ state: State }> {
|
||||
render() {
|
||||
return <select onChange={s => this.props.state.query.next(QueryList[+s.currentTarget.value].definition)}>
|
||||
{ QueryList.map((q, i) => <option value={i} key={i} selected={i === 1}>{q.definition.niceName}</option>) }
|
||||
</select>
|
||||
}
|
||||
}
|
||||
|
||||
class QueryParams extends React.Component<{ state: State }, { prms: string }> {
|
||||
state = { prms: '' };
|
||||
|
||||
parseParams(str: string) {
|
||||
this.setState({ prms: str });
|
||||
try {
|
||||
const params = JSON.parse(str);
|
||||
this.props.state.params.next(params);
|
||||
} catch {
|
||||
this.props.state.params.next({});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.state.query.subscribe(q => this.setState({ prms: formatParams(q) }))
|
||||
}
|
||||
|
||||
render() {
|
||||
return <textarea style={{height: '300px'}} value={this.state.prms} cols={80} onChange={t => this.parseParams(t.currentTarget.value)} />;
|
||||
}
|
||||
}
|
||||
|
||||
class QueryUrl extends React.Component<{ state: State }, { queryString: string }> {
|
||||
state = { queryString: '' };
|
||||
|
||||
componentDidMount() {
|
||||
this.props.state.url.subscribe(url => this.setState({ queryString: url }))
|
||||
}
|
||||
|
||||
render() {
|
||||
return <input type='text' value={this.state.queryString} style={{ width: '800px' }} />
|
||||
}
|
||||
}
|
||||
|
||||
class ModelNums extends React.Component<{ state: State }> {
|
||||
render() {
|
||||
return <input type='text' defaultValue='1' style={{ width: '300px' }} onChange={t =>
|
||||
this.props.state.models.next(t.currentTarget.value.split(',')
|
||||
.map(v => v.trim())
|
||||
.filter(v => !!v)
|
||||
.map(v => +v)
|
||||
)} />
|
||||
}
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
query: new Rx.BehaviorSubject(QueryList[1].definition),
|
||||
id: new Rx.BehaviorSubject('1cbs'),
|
||||
params: new Rx.BehaviorSubject({ }),
|
||||
isBinary: new Rx.BehaviorSubject<boolean>(false),
|
||||
models: new Rx.BehaviorSubject<number[]>([]),
|
||||
url: new Rx.Subject()
|
||||
}
|
||||
|
||||
function formatParams(def: QueryDefinition) {
|
||||
const prms = Object.create(null);
|
||||
for (const p of def.jsonParams) {
|
||||
prms[p.name] = p.exampleValues ? p.exampleValues[0] : void 0;
|
||||
}
|
||||
return JSON.stringify(prms, void 0, 2);
|
||||
}
|
||||
|
||||
function formatUrl() {
|
||||
const json = JSON.stringify({
|
||||
name: state.query.value.name,
|
||||
id: state.id.value,
|
||||
modelNums: state.models.value.length ? state.models.value : void 0,
|
||||
binary: state.isBinary.value,
|
||||
params: state.params.value
|
||||
});
|
||||
state.url.next(encodeURIComponent(json));
|
||||
}
|
||||
|
||||
Rx.merge(state.query, state.id, state.params, state.isBinary, state.models).subscribe(s => formatUrl());
|
||||
|
||||
ReactDOM.render(<Root state={state} />, document.getElementById('app'));
|
||||
@@ -19,8 +19,9 @@ 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-list': return `One of ${oToS(param.options)}`;
|
||||
case 'color-list': return `A list of colors as 0xrrggbb`;
|
||||
case 'vec3': return `3D vector [x, y, z]`;
|
||||
case 'mat4': return `4x4 transformation matrix`;
|
||||
case 'file': return `JavaScript File Handle`;
|
||||
case 'file-list': return `JavaScript FileList Handle`;
|
||||
case 'select': return `One of ${oToS(param.options)}`;
|
||||
@@ -39,7 +40,7 @@ function paramInfo(param: PD.Any, offset: number): string {
|
||||
}
|
||||
}
|
||||
|
||||
function oToS(options: readonly (readonly [string, string] | readonly [string, string, string])[]) {
|
||||
function oToS(options: readonly (readonly [string, string] | readonly [string, string, string | undefined])[]) {
|
||||
return options.map(o => `'${o[0]}'`).join(', ');
|
||||
}
|
||||
|
||||
|
||||
96
src/apps/viewer/extensions/cellpack/color.ts
Normal file
96
src/apps/viewer/extensions/cellpack/color.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ThemeDataContext } from '../../../../mol-theme/theme'
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition'
|
||||
import { Color } from '../../../../mol-util/color'
|
||||
import { getPalette } from '../../../../mol-util/color/palette'
|
||||
import { ColorTheme, LocationColor } from '../../../../mol-theme/color'
|
||||
import { ScaleLegend, TableLegend } from '../../../../mol-util/legend'
|
||||
import { StructureElement, Bond } from '../../../../mol-model/structure'
|
||||
import { Location } from '../../../../mol-model/location';
|
||||
import { CellPackInfoProvider } from './property'
|
||||
import { distinctColors } from '../../../../mol-util/color/distinct'
|
||||
import { Hcl } from '../../../../mol-util/color/spaces/hcl'
|
||||
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC)
|
||||
const Description = 'Gives every model in a CellPack packing a unique color similar to other models in the packing.'
|
||||
|
||||
export const CellPackColorThemeParams = {}
|
||||
export type CellPackColorThemeParams = typeof CellPackColorThemeParams
|
||||
export function getCellPackColorThemeParams(ctx: ThemeDataContext) {
|
||||
return CellPackColorThemeParams // TODO return copy
|
||||
}
|
||||
|
||||
export function CellPackColorTheme(ctx: ThemeDataContext, props: PD.Values<CellPackColorThemeParams>): ColorTheme<CellPackColorThemeParams> {
|
||||
let color: LocationColor
|
||||
let legend: ScaleLegend | TableLegend | undefined
|
||||
|
||||
const info = ctx.structure && CellPackInfoProvider.get(ctx.structure).value
|
||||
|
||||
if (ctx.structure && info) {
|
||||
const colors = distinctColors(info.packingsCount)
|
||||
const hcl = Hcl.fromColor(Hcl(), colors[info.packingIndex])
|
||||
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number]
|
||||
|
||||
const { models } = ctx.structure.root
|
||||
|
||||
let size = 0;
|
||||
for (const m of models) size = Math.max(size, m.trajectoryInfo.size);
|
||||
|
||||
const palette = getPalette(size, { palette: {
|
||||
name: 'generate',
|
||||
params: {
|
||||
hue, chroma: [30, 80], luminance: [15, 85],
|
||||
clusteringStepCount: 50, minSampleCount: 800, maxCount: 75,
|
||||
minLabel: 'Min', maxLabel: 'Max', valueLabel: (i: number) => `${i + 1}`,
|
||||
}
|
||||
}})
|
||||
legend = palette.legend
|
||||
const modelColor = new Map<number, Color>()
|
||||
for (let i = 0, il = models.length; i <il; ++i) {
|
||||
const idx = models[i].trajectoryInfo.index;
|
||||
modelColor.set(models[i].trajectoryInfo.index, palette.color(idx))
|
||||
}
|
||||
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
return modelColor.get(location.unit.model.trajectoryInfo.index)!
|
||||
} else if (Bond.isLocation(location)) {
|
||||
return modelColor.get(location.aUnit.model.trajectoryInfo.index)!
|
||||
}
|
||||
return DefaultColor
|
||||
}
|
||||
} else {
|
||||
color = () => DefaultColor
|
||||
}
|
||||
|
||||
return {
|
||||
factory: CellPackColorTheme,
|
||||
granularity: 'instance',
|
||||
color,
|
||||
props,
|
||||
description: Description,
|
||||
legend
|
||||
}
|
||||
}
|
||||
|
||||
export const CellPackColorThemeProvider: ColorTheme.Provider<CellPackColorThemeParams, 'cellpack'> = {
|
||||
name: 'cellpack',
|
||||
label: 'CellPack',
|
||||
category: ColorTheme.Category.Chain,
|
||||
factory: CellPackColorTheme,
|
||||
getParams: getCellPackColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(CellPackColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => {
|
||||
return (
|
||||
!!ctx.structure && ctx.structure.elementCount > 0 &&
|
||||
ctx.structure.models[0].trajectoryInfo.size > 1 &&
|
||||
!!CellPackInfoProvider.get(ctx.structure).value
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -4,32 +4,31 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateAction } from '../../../../mol-state';
|
||||
import { StateAction, StateBuilder, StateTransformer, State } from '../../../../mol-state';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Ingredient, CellPacking, Cell } from './data';
|
||||
import { Ingredient, CellPacking } from './data';
|
||||
import { getFromPdb, getFromCellPackDB } from './util';
|
||||
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../../../mol-model/structure';
|
||||
import { trajectoryFromMmCIF, MmcifFormat } 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 { Task, RuntimeContext } from '../../../../mol-task';
|
||||
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, DefaultCellPackBaseUrl } from './state';
|
||||
import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
|
||||
import { getMatFromResamplePoints } from './curve';
|
||||
import { compile } from '../../../../mol-script/runtime/query/compiler';
|
||||
import { UniformColorThemeProvider } from '../../../../mol-theme/color/uniform';
|
||||
import { CifCategory, CifField } from '../../../../mol-io/reader/cif';
|
||||
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Column } from '../../../../mol-data/db';
|
||||
import { createModels } from '../../../../mol-model-formats/structure/basic/parser';
|
||||
import { createStructureRepresentationParams } from '../../../../mol-plugin-state/helpers/structure-representation-params';
|
||||
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
|
||||
import { AjaxTask } from '../../../../mol-util/data-source';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
import { CellPackColorThemeProvider } from './color';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/results/${fileName}`
|
||||
@@ -111,7 +110,7 @@ function getAssembly(transforms: Mat4[], structure: 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 ] })
|
||||
const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [ id ] } })
|
||||
for (const unit of units) {
|
||||
builder.addWithOperator(unit, op)
|
||||
}
|
||||
@@ -270,200 +269,134 @@ export function createStructureFromCellPack(packing: CellPacking, baseUrl: strin
|
||||
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId
|
||||
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId)
|
||||
}
|
||||
offsetInvariantId += maxInvariantId
|
||||
offsetInvariantId += maxInvariantId + 1
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`)
|
||||
const s = builder.getStructure()
|
||||
for( let i = 0, il = s.models.length; i < il; ++i) {
|
||||
const { trajectoryInfo } = s.models[i]
|
||||
trajectoryInfo.size = il
|
||||
trajectoryInfo.index = i
|
||||
}
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
const RepresentationOptions = PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'ellipsoid'] as const)
|
||||
type RepresentationName = (typeof RepresentationOptions)[0][0]
|
||||
|
||||
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'],
|
||||
['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'],
|
||||
['curveTest', 'Curve Test'],
|
||||
] as const),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
preset: PD.Group({
|
||||
traceOnly: PD.Boolean(false),
|
||||
representation: PD.Select('spacefill', RepresentationOptions)
|
||||
}, { 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();
|
||||
|
||||
let cellPackBuilder: any
|
||||
|
||||
if (params.id === 'curveTest') {
|
||||
const url = `${params.baseUrl}/extras/rna_allpoints.json`
|
||||
const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
|
||||
const { points } = await (new Response(data)).json() as { points: number[] }
|
||||
const curve0: Vec3[] = []
|
||||
for (let j = 0, jl = Math.min(points.length, 3 * 100); j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j))
|
||||
}
|
||||
const cell: Cell = {
|
||||
recipe: { setupfile: '', paths: [], version: '', name: 'Curve Test' },
|
||||
compartments: {
|
||||
'CurveCompartment': {
|
||||
interior: {
|
||||
ingredients: {
|
||||
'CurveIngredient': {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cellPackBuilder = root
|
||||
.apply(StateTransforms.Data.ImportJson, { data: cell }, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack)
|
||||
} else {
|
||||
cellPackBuilder = root
|
||||
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.id }, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack)
|
||||
|
||||
|
||||
}
|
||||
|
||||
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(taskCtx)
|
||||
const { packings } = cellPackObject.data
|
||||
const tree = state.build().to(cellPackBuilder.ref);
|
||||
|
||||
const isHiv = (
|
||||
params.id === 'BloodHIV1.0_mixed_fixed_nc1.cpr' ||
|
||||
params.id === 'HIV-1_0.1.6-8_mixed_radii_pdb.cpr'
|
||||
)
|
||||
|
||||
if (isHiv) {
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
|
||||
const url = `${params.baseUrl}/extras/rna_allpoints.json`
|
||||
const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
|
||||
const { points } = await (new Response(data)).json() as { points: number[] }
|
||||
|
||||
const curve0: Vec3[] = []
|
||||
for (let j = 0, jl = points.length; j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j))
|
||||
}
|
||||
packings[i].ingredients['RNA'] = {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const colors = distinctColors(packings.length)
|
||||
|
||||
async function handleHivRna(ctx: { runtime: RuntimeContext, fetch: AjaxTask }, packings: CellPacking[], baseUrl: string) {
|
||||
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 }
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
|
||||
const url = `${baseUrl}/extras/rna_allpoints.json`
|
||||
const data = await ctx.fetch({ url, type: 'string' }).runInContext(ctx.runtime);
|
||||
const { points } = await (new Response(data)).json() as { points: number[] }
|
||||
|
||||
let cellpackTree = tree.apply(StructureFromCellpack, p)
|
||||
if (params.preset.traceOnly) {
|
||||
const expression = 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'])
|
||||
])
|
||||
})
|
||||
cellpackTree = cellpackTree.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression }, { state: { isGhost: true } }) as any
|
||||
}
|
||||
cellpackTree
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
createStructureRepresentationParams(ctx, Structure.Empty, {
|
||||
...getReprParams(ctx, params.preset),
|
||||
...getColorParams(hue)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (isHiv) {
|
||||
const url = `${params.baseUrl}/membranes/hiv_lipids.bcif`
|
||||
tree.apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.StructureFromModel, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Misc.CreateGroup, { label: 'HIV1_envelope_Membrane' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
createStructureRepresentationParams(ctx, Structure.Empty, {
|
||||
...getReprParams(ctx, params.preset),
|
||||
color: UniformColorThemeProvider
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
console.time('cellpack')
|
||||
await state.updateTree(tree).runInContext(taskCtx);
|
||||
console.timeEnd('cellpack')
|
||||
}));
|
||||
|
||||
function getReprParams(ctx: PluginContext, params: { representation: RepresentationName, traceOnly: boolean }) {
|
||||
const { representation, traceOnly } = params
|
||||
switch (representation) {
|
||||
case 'spacefill':
|
||||
return traceOnly
|
||||
? {
|
||||
type: ctx.representation.structure.registry.get('spacefill'),
|
||||
typeParams: { sizeFactor: 2, ignoreHydrogens: true }
|
||||
} : {
|
||||
type: ctx.representation.structure.registry.get('spacefill'),
|
||||
typeParams: { ignoreHydrogens: true }
|
||||
}
|
||||
case 'gaussian-surface':
|
||||
return {
|
||||
type: ctx.representation.structure.registry.get('gaussian-surface'),
|
||||
typeParams: {
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2,
|
||||
alpha: 1.0, flatShaded: false, doubleSided: false,
|
||||
ignoreHydrogens: true
|
||||
}
|
||||
const curve0: Vec3[] = []
|
||||
for (let j = 0, jl = points.length; j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j))
|
||||
}
|
||||
case 'point':
|
||||
return { type: ctx.representation.structure.registry.get('point') }
|
||||
case 'ellipsoid':
|
||||
return { type: ctx.representation.structure.registry.get('orientation') }
|
||||
packings[i].ingredients['RNA'] = {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getColorParams(hue: [number, number]): any {
|
||||
return {
|
||||
color: ModelIndexColorThemeProvider,
|
||||
colorParams: {
|
||||
palette: {
|
||||
name: 'generate',
|
||||
params: {
|
||||
hue, chroma: [30, 80], luminance: [15, 85],
|
||||
clusteringStepCount: 50, minSampleCount: 800,
|
||||
maxCount: 75
|
||||
}
|
||||
}
|
||||
}
|
||||
async function loadHivMembrane(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
|
||||
const url = `${params.baseUrl}/membranes/hiv_lipids.bcif`
|
||||
const membraneBuilder = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.StructureFromModel)
|
||||
await state.updateTree(membraneBuilder).runInContext(runtime)
|
||||
|
||||
const membraneParams = {
|
||||
representation: params.preset.representation,
|
||||
}
|
||||
}
|
||||
const membrane = state.build().to(membraneBuilder.ref)
|
||||
await plugin.updateDataState(membrane, { revertOnError: true });
|
||||
await CellpackMembranePreset.apply(membrane.selector, membraneParams, plugin)
|
||||
}
|
||||
|
||||
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
|
||||
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>
|
||||
if (params.source.name === 'id') {
|
||||
const url = getCellPackModelUrl(params.source.params, params.baseUrl)
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } })
|
||||
} else {
|
||||
const file = params.source.params
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.ReadFile, { file, isBinary: false, label: file.name }, { state: { isGhost: true } })
|
||||
}
|
||||
|
||||
const cellPackBuilder = cellPackJson
|
||||
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack)
|
||||
|
||||
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime)
|
||||
const { packings } = cellPackObject.data
|
||||
|
||||
await handleHivRna({ runtime, fetch: plugin.fetch }, packings, params.baseUrl)
|
||||
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
const p = { packing: i, baseUrl: params.baseUrl }
|
||||
|
||||
const packing = state.build().to(cellPackBuilder.ref).apply(StructureFromCellpack, p)
|
||||
await plugin.updateDataState(packing, { revertOnError: true });
|
||||
|
||||
const structure = packing.selector.obj?.data
|
||||
if (structure) {
|
||||
await CellPackInfoProvider.attach({ fetch: plugin.fetch, runtime }, structure, {
|
||||
info: { packingsCount: packings.length, packingIndex: i }
|
||||
})
|
||||
}
|
||||
|
||||
const packingParams = {
|
||||
traceOnly: params.preset.traceOnly,
|
||||
representation: params.preset.representation,
|
||||
}
|
||||
await CellpackPackingPreset.apply(packing.selector, packingParams, plugin)
|
||||
}
|
||||
}
|
||||
|
||||
const LoadCellPackModelParams = {
|
||||
source: PD.MappedStatic('id', {
|
||||
'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'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
||||
['hiv_lipids.bcif', 'hiv_lipids'],
|
||||
['influenza_model1.json', 'influenza_model1'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
] as const),
|
||||
'file': PD.File({ accept: 'id' }),
|
||||
}, { options: [['id', 'Id'], ['file', 'File']] }),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
preset: PD.Group({
|
||||
traceOnly: PD.Boolean(false),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'ellipsoid']))
|
||||
}, { isExpanded: true })
|
||||
}
|
||||
type LoadCellPackModelParams = PD.Values<typeof LoadCellPackModelParams>
|
||||
|
||||
export const LoadCellPackModel = StateAction.build({
|
||||
display: { name: 'Load CellPack', description: 'Open or download a model' },
|
||||
params: LoadCellPackModelParams,
|
||||
from: PSO.Root
|
||||
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
|
||||
if (!ctx.representation.structure.themes.colorThemeRegistry.has(CellPackColorThemeProvider)) {
|
||||
ctx.representation.structure.themes.colorThemeRegistry.add(CellPackColorThemeProvider)
|
||||
}
|
||||
|
||||
if (params.source.name === 'id' && params.source.params === 'hiv_lipids.bcif') {
|
||||
await loadHivMembrane(ctx, taskCtx, state, params)
|
||||
} else {
|
||||
await loadPackings(ctx, taskCtx, state, params)
|
||||
}
|
||||
}));
|
||||
90
src/apps/viewer/extensions/cellpack/preset.ts
Normal file
90
src/apps/viewer/extensions/cellpack/preset.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateObjectRef } from '../../../../mol-state';
|
||||
import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../../../mol-util/color/names';
|
||||
import { CellPackColorThemeProvider } from './color';
|
||||
|
||||
export const CellpackPackingPresetParams = {
|
||||
traceOnly: PD.Boolean(true),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'])),
|
||||
}
|
||||
export type CellpackPackingPresetParams = PD.ValuesFor<typeof CellpackPackingPresetParams>
|
||||
|
||||
export const CellpackPackingPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-cellpack-packing',
|
||||
display: { name: 'CellPack Packing' },
|
||||
params: () => CellpackPackingPresetParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
|
||||
const reprProps = {
|
||||
ignoreHydrogens: true,
|
||||
traceOnly: params.traceOnly
|
||||
};
|
||||
const components = {
|
||||
polymer: await presetStaticComponent(plugin, structureCell, 'polymer')
|
||||
};
|
||||
|
||||
if (params.representation === 'gaussian-surface') {
|
||||
Object.assign(reprProps, {
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2, doubleSided: false
|
||||
})
|
||||
} else if (params.representation === 'spacefill' && params.traceOnly) {
|
||||
Object.assign(reprProps, { sizeFactor: 2 })
|
||||
}
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
|
||||
const color = CellPackColorThemeProvider.name
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation<any>(update, components.polymer, { type: params.representation, typeParams: { ...typeParams, ...reprProps }, color }, { tag: 'polymer' })
|
||||
};
|
||||
|
||||
await plugin.updateDataState(update, { revertOnError: true });
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
export const CellpackMembranePresetParams = {
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'])),
|
||||
}
|
||||
export type CellpackMembranePresetParams = PD.ValuesFor<typeof CellpackMembranePresetParams>
|
||||
|
||||
export const CellpackMembranePreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-cellpack-membrane',
|
||||
display: { name: 'CellPack Membrane' },
|
||||
params: () => CellpackMembranePresetParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
|
||||
const reprProps = {
|
||||
ignoreHydrogens: true,
|
||||
};
|
||||
const components = {
|
||||
membrane: await presetStaticComponent(plugin, structureCell, 'all', { label: 'Membrane' })
|
||||
};
|
||||
|
||||
if (params.representation === 'gaussian-surface') {
|
||||
Object.assign(reprProps, {
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2, doubleSided: false
|
||||
})
|
||||
}
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
|
||||
const representations = {
|
||||
membrane: builder.buildRepresentation(update, components.membrane, { type: 'gaussian-surface', typeParams: { ...typeParams, ...reprProps }, color: 'uniform', colorParams: { value: ColorNames.lightgrey } }, { tag: 'all' })
|
||||
};
|
||||
|
||||
await plugin.updateDataState(update, { revertOnError: true });
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
32
src/apps/viewer/extensions/cellpack/property.ts
Normal file
32
src/apps/viewer/extensions/cellpack/property.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CustomStructureProperty } from '../../../../mol-model-props/common/custom-structure-property'
|
||||
import { Structure, CustomPropertyDescriptor } from '../../../../mol-model/structure'
|
||||
import { CustomProperty } from '../../../../mol-model-props/common/custom-property'
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition'
|
||||
|
||||
export type CellPackInfoValue = {
|
||||
packingsCount: number
|
||||
packingIndex: number
|
||||
}
|
||||
|
||||
const CellPackInfoParams = {
|
||||
info: PD.Value<CellPackInfoValue>({ packingsCount: 1, packingIndex: 0 }, { isHidden: true })
|
||||
}
|
||||
type CellPackInfoParams = PD.Values<typeof CellPackInfoParams>
|
||||
|
||||
export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellPackInfoParams, CellPackInfoValue> = CustomStructureProperty.createProvider({
|
||||
label: 'CellPack Info',
|
||||
descriptor: CustomPropertyDescriptor({ name: 'cellpack-info' }),
|
||||
type: 'root',
|
||||
defaultParams: CellPackInfoParams,
|
||||
getParams: (data: Structure) => CellPackInfoParams,
|
||||
isApplicable: (data: Structure) => true,
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: CellPackInfoParams) => {
|
||||
return { ...CellPackInfoParams.info.defaultValue, ...props.info }
|
||||
}
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -10,8 +10,7 @@ import { Task } from '../../../../mol-task';
|
||||
import { CellPack as _CellPack, Cell, CellPacking } from './data';
|
||||
import { createStructureFromCellPack } from './model';
|
||||
|
||||
// export const DefaultCellPackBaseUrl = 'https://cdn.jsdelivr.net/gh/mesoscope/cellPACK_data@master/cellPACK_database_1.1.0/'
|
||||
export const DefaultCellPackBaseUrl = 'https://mgldev.scripps.edu/projects/autoPACK/web/cellpackproject/'
|
||||
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/'
|
||||
|
||||
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { LoadCellPackModel } from './extensions/cellpack/model';
|
||||
import { StructureFromCellpack } from './extensions/cellpack/state';
|
||||
import { DownloadStructure } from '../../mol-plugin-state/actions/structure';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
|
||||
function getParam(name: string, regex: string): string {
|
||||
@@ -46,6 +47,7 @@ function init() {
|
||||
},
|
||||
config: DefaultPluginSpec.config
|
||||
};
|
||||
spec.config?.set(PluginConfig.Viewport.ShowExpand, false);
|
||||
const plugin = createPlugin(document.getElementById('app')!, spec);
|
||||
trySetSnapshot(plugin);
|
||||
tryLoadFromUrl(plugin);
|
||||
@@ -88,7 +90,6 @@ async function tryLoadFromUrl(ctx: PluginContext) {
|
||||
format: format as any,
|
||||
isBinary,
|
||||
options: params.source.params.options,
|
||||
structure: params.source.params.structure,
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -172,16 +172,17 @@
|
||||
addHeader('State');
|
||||
|
||||
var snapshot;
|
||||
addControl('Create Snapshot', () => {
|
||||
addControl('Set Snapshot', () => {
|
||||
snapshot = PluginWrapper.snapshot.get();
|
||||
// could use JSON.stringify(snapshot) and upload the data
|
||||
// console.log(JSON.stringify(snapshot, null, 2));
|
||||
});
|
||||
addControl('Apply Snapshot', () => {
|
||||
addControl('Restore Snapshot', () => {
|
||||
if (!snapshot) return;
|
||||
PluginWrapper.snapshot.set(snapshot);
|
||||
|
||||
// or download snapshot using fetch or ajax or whatever
|
||||
// or PluginWrapper.snapshot.download(url);
|
||||
});
|
||||
addControl('Download Snapshot', () => {
|
||||
snapshot = PluginWrapper.snapshot.download();
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
@@ -26,13 +26,12 @@ import { ColorNames } from '../../mol-util/color/names';
|
||||
import { InitVolumeStreaming, CreateVolumeStreamingInfo } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { DefaultCanvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
// import { Vec3 } from 'mol-math/linear-algebra';
|
||||
// import { ParamDefinition } from 'mol-util/param-definition';
|
||||
// import { Text } from 'mol-geo/geometry/text/text';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { getFormattedTime } from '../../mol-util/date';
|
||||
require('../../mol-plugin-ui/skin/light.scss')
|
||||
|
||||
class MolStarProteopediaWrapper {
|
||||
static VERSION_MAJOR = 4;
|
||||
static VERSION_MAJOR = 5;
|
||||
static VERSION_MINOR = 0;
|
||||
|
||||
private _ev = RxEventHelper.create();
|
||||
@@ -97,7 +96,6 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
const s = model
|
||||
.apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [EvolutionaryConservation.propertyProvider.descriptor.name], properties: {} }, { ref: StateElements.ModelProps, state: { isGhost: false } })
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { ref: StateElements.Assembly });
|
||||
|
||||
s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: StateElements.Sequence });
|
||||
@@ -427,10 +425,16 @@ class MolStarProteopediaWrapper {
|
||||
set: (snapshot: PluginState.Snapshot) => {
|
||||
return this.plugin.state.setSnapshot(snapshot);
|
||||
},
|
||||
download: async (url: string) => {
|
||||
download: () => {
|
||||
const json = JSON.stringify(this.plugin.state.getSnapshot(), null, 2);
|
||||
const blob = new Blob([json], {type : 'application/json;charset=utf-8'});
|
||||
download(blob, `mol-star_state_${(name || getFormattedTime())}.json`)
|
||||
},
|
||||
fetch: async (url: string) => {
|
||||
try {
|
||||
const snapshot = await this.plugin.runTask(this.plugin.fetch({ url, type: 'json' }));
|
||||
await this.plugin.state.setSnapshot(snapshot);
|
||||
// TODO: is this OK to test for snapshots from server?
|
||||
await this.plugin.state.setSnapshot(snapshot?.data?.entries?.[0]?.snapshot || snapshot);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 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>
|
||||
@@ -47,8 +47,8 @@ class Camera {
|
||||
|
||||
private prevProjection = Mat4.identity();
|
||||
private prevView = Mat4.identity();
|
||||
private deltaDirection = Vec3.zero();
|
||||
private newPosition = Vec3.zero();
|
||||
private deltaDirection = Vec3();
|
||||
private newPosition = Vec3();
|
||||
|
||||
update() {
|
||||
const snapshot = this.state as Camera.Snapshot;
|
||||
@@ -85,13 +85,18 @@ class Camera {
|
||||
return Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
|
||||
}
|
||||
|
||||
getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
|
||||
getTargetDistance(radius: number) {
|
||||
const r = Math.max(radius, 0.01)
|
||||
const { fov } = this.state
|
||||
const { width, height } = this.viewport
|
||||
const aspect = width / height
|
||||
const aspectFactor = (height < width ? 1 : aspect)
|
||||
const targetDistance = Math.abs((r / aspectFactor) / Math.sin(fov / 2))
|
||||
return Math.abs((r / aspectFactor) / Math.sin(fov / 2))
|
||||
}
|
||||
|
||||
getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
|
||||
const r = Math.max(radius, 0.01)
|
||||
const targetDistance = this.getTargetDistance(r)
|
||||
|
||||
Vec3.sub(this.deltaDirection, this.target, this.position)
|
||||
if (dir) Vec3.matchDirection(this.deltaDirection, dir, this.deltaDirection)
|
||||
@@ -125,7 +130,6 @@ class Camera {
|
||||
this.viewport = viewport;
|
||||
Camera.copySnapshot(this.state, state);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Camera {
|
||||
@@ -286,6 +290,11 @@ function updateClip(camera: Camera) {
|
||||
far = Math.max(0, far)
|
||||
}
|
||||
|
||||
if (near === far) {
|
||||
// make sure near and far are not identical to avoid Infinity in the projection matrix
|
||||
far = near + 0.01
|
||||
}
|
||||
|
||||
camera.near = near;
|
||||
camera.far = far;
|
||||
camera.fogNear = fogNear;
|
||||
|
||||
@@ -25,23 +25,32 @@ class CameraTransitionManager {
|
||||
get target(): Readonly<Camera.Snapshot> { return this._target }
|
||||
|
||||
apply(to: Partial<Camera.Snapshot>, durationMs: number = 0, transition?: CameraTransitionManager.TransitionFunc) {
|
||||
Camera.copySnapshot(this._source, this.camera.state);
|
||||
Camera.copySnapshot(this._target, this.camera.state);
|
||||
if (!this.inTransition || durationMs > 0) {
|
||||
Camera.copySnapshot(this._source, this.camera.state);
|
||||
}
|
||||
|
||||
if (!this.inTransition) {
|
||||
Camera.copySnapshot(this._target, this.camera.state);
|
||||
}
|
||||
|
||||
Camera.copySnapshot(this._target, to);
|
||||
|
||||
if (this._target.radius > this._target.radiusMax) {
|
||||
this._target.radius = this._target.radiusMax
|
||||
}
|
||||
|
||||
if (durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
|
||||
if (!this.inTransition && durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
|
||||
this.finish(this._target);
|
||||
return;
|
||||
}
|
||||
|
||||
this.inTransition = true;
|
||||
this.func = transition || CameraTransitionManager.defaultTransition;
|
||||
this.start = this.t;
|
||||
this.durationMs = durationMs;
|
||||
|
||||
if (!this.inTransition || durationMs > 0) {
|
||||
this.start = this.t;
|
||||
this.durationMs = durationMs;
|
||||
}
|
||||
}
|
||||
|
||||
tick(t: number) {
|
||||
|
||||
@@ -31,13 +31,16 @@ 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';
|
||||
import { ImagePass, ImageProps } from './passes/image';
|
||||
import { Sphere3D } from '../mol-math/geometry';
|
||||
import { isDebugMode } from '../mol-util/debug';
|
||||
import { CameraHelperParams } from './helper/camera-helper';
|
||||
|
||||
export const Canvas3DParams = {
|
||||
cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']] as const),
|
||||
camera: PD.Group({
|
||||
mode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']] as const, { label: 'Camera' }),
|
||||
helper: PD.Group(CameraHelperParams, { isFlat: true })
|
||||
}, { pivot: 'mode' }),
|
||||
cameraFog: PD.MappedStatic('on', {
|
||||
on: PD.Group({
|
||||
intensity: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
|
||||
@@ -93,7 +96,7 @@ interface Canvas3D {
|
||||
downloadScreenshot(): void
|
||||
getPixelData(variant: GraphicsRenderVariant): PixelData
|
||||
setProps(props: Partial<Canvas3DProps>): void
|
||||
getImagePass(): ImagePass
|
||||
getImagePass(props: Partial<ImageProps>): ImagePass
|
||||
|
||||
/** Returns a copy of the current Canvas3D instance props */
|
||||
readonly props: Readonly<Canvas3DProps>
|
||||
@@ -105,13 +108,12 @@ 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 HoverEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
|
||||
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
|
||||
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask) {
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}) {
|
||||
const gl = getGLContext(canvas, {
|
||||
alpha: true,
|
||||
antialias: true,
|
||||
@@ -156,10 +158,10 @@ namespace Canvas3D {
|
||||
if (isDebugMode) console.log('context restored')
|
||||
}, false)
|
||||
|
||||
return Canvas3D.create(webgl, input, props, runTask)
|
||||
return Canvas3D.create(webgl, input, props)
|
||||
}
|
||||
|
||||
export function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask): Canvas3D {
|
||||
export function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}): Canvas3D {
|
||||
const p = { ...DefaultCanvas3DParams, ...props }
|
||||
|
||||
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>()
|
||||
@@ -178,7 +180,7 @@ namespace Canvas3D {
|
||||
|
||||
const camera = new Camera({
|
||||
position: Vec3.create(0, 0, 100),
|
||||
mode: p.cameraMode,
|
||||
mode: p.camera.mode,
|
||||
fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0,
|
||||
clipFar: p.cameraClipping.far
|
||||
})
|
||||
@@ -188,7 +190,9 @@ namespace Canvas3D {
|
||||
const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
|
||||
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
|
||||
|
||||
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper)
|
||||
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, {
|
||||
cameraHelper: p.camera.helper
|
||||
})
|
||||
const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5)
|
||||
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing)
|
||||
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample)
|
||||
@@ -300,7 +304,8 @@ namespace Canvas3D {
|
||||
|
||||
function resolveCameraReset() {
|
||||
if (!cameraResetRequested) return;
|
||||
const { center, radius } = scene.boundingSphere;
|
||||
|
||||
const { center, radius } = scene.boundingSphereVisible;
|
||||
if (radius > 0) {
|
||||
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration
|
||||
const focus = camera.getFocus(center, radius);
|
||||
@@ -313,16 +318,50 @@ namespace Canvas3D {
|
||||
cameraResetRequested = false;
|
||||
}
|
||||
|
||||
const oldBoundingSphereVisible = Sphere3D();
|
||||
const cameraSphere = Sphere3D();
|
||||
|
||||
function shouldResetCamera() {
|
||||
if (camera.state.radiusMax === 0) return true;
|
||||
|
||||
if (camera.transition.inTransition || nextCameraResetSnapshot) return false;
|
||||
|
||||
let cameraSphereOverlapsNone = true
|
||||
Sphere3D.set(cameraSphere, camera.state.target, camera.state.radius)
|
||||
|
||||
// check if any renderable has moved outside of the old bounding sphere
|
||||
// and if no renderable is overlapping with the camera sphere
|
||||
for (const r of scene.renderables) {
|
||||
if (!r.state.visible) continue;
|
||||
|
||||
const b = r.values.boundingSphere.ref.value;
|
||||
if (!b.radius) continue;
|
||||
|
||||
if (!Sphere3D.includes(oldBoundingSphereVisible, b)) return true;
|
||||
if (Sphere3D.overlaps(cameraSphere, b)) cameraSphereOverlapsNone = false;
|
||||
}
|
||||
|
||||
return cameraSphereOverlapsNone;
|
||||
}
|
||||
|
||||
const sceneCommitTimeoutMs = 250;
|
||||
function commitScene(isSynchronous: boolean) {
|
||||
if (!scene.needsCommit) return true;
|
||||
|
||||
// snapshot the current bounding sphere of visible objects
|
||||
Sphere3D.copy(oldBoundingSphereVisible, scene.boundingSphereVisible);
|
||||
|
||||
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
|
||||
|
||||
if (debugHelper.isEnabled) debugHelper.update();
|
||||
if (reprCount.value === 0 || camera.state.radiusMax === 0) cameraResetRequested = true;
|
||||
camera.setState({ radiusMax: scene.boundingSphere.radius })
|
||||
if (reprCount.value === 0 || shouldResetCamera()) {
|
||||
cameraResetRequested = true;
|
||||
}
|
||||
if (oldBoundingSphereVisible.radius === 0) nextCameraResetDuration = 0;
|
||||
|
||||
camera.setState({ radiusMax: scene.boundingSphere.radius }, 0)
|
||||
reprCount.next(reprRenderObjects.size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -399,8 +438,13 @@ namespace Canvas3D {
|
||||
reprCount.next(reprRenderObjects.size)
|
||||
},
|
||||
syncVisibility: () => {
|
||||
if (camera.state.radiusMax === 0) {
|
||||
cameraResetRequested = true
|
||||
nextCameraResetDuration = 0
|
||||
}
|
||||
|
||||
if (scene.syncVisibility()) {
|
||||
camera.setState({ radiusMax: scene.boundingSphere.radius })
|
||||
if (debugHelper.isEnabled) debugHelper.update()
|
||||
}
|
||||
},
|
||||
|
||||
@@ -435,8 +479,8 @@ namespace Canvas3D {
|
||||
reprCount,
|
||||
setProps: (props: Partial<Canvas3DProps>) => {
|
||||
const cameraState: Partial<Camera.Snapshot> = Object.create(null)
|
||||
if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
|
||||
cameraState.mode = props.cameraMode
|
||||
if (props.camera && props.camera.mode !== undefined && props.camera.mode !== camera.state.mode) {
|
||||
cameraState.mode = props.camera.mode
|
||||
}
|
||||
if (props.cameraFog !== undefined) {
|
||||
const newFog = props.cameraFog.name === 'on' ? props.cameraFog.params.intensity : 0
|
||||
@@ -456,6 +500,7 @@ namespace Canvas3D {
|
||||
}
|
||||
if (Object.keys(cameraState).length > 0) camera.setState(cameraState)
|
||||
|
||||
if (props.camera?.helper) drawPass.setProps({ cameraHelper: props.camera.helper })
|
||||
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs
|
||||
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground
|
||||
|
||||
@@ -477,7 +522,10 @@ namespace Canvas3D {
|
||||
: 0
|
||||
|
||||
return {
|
||||
cameraMode: camera.state.mode,
|
||||
camera: {
|
||||
mode: camera.state.mode,
|
||||
helper: { ...drawPass.props.cameraHelper }
|
||||
},
|
||||
cameraFog: camera.state.fog > 0
|
||||
? { name: 'on' as const, params: { intensity: camera.state.fog } }
|
||||
: { name: 'off' as const, params: {} },
|
||||
|
||||
@@ -208,7 +208,7 @@ namespace TrackballControls {
|
||||
function focusCamera() {
|
||||
const factor = (_focusEnd[1] - _focusStart[1]) * p.zoomSpeed
|
||||
if (factor !== 0.0) {
|
||||
const radius = Math.max(1, camera.state.radius + 10 * factor)
|
||||
const radius = Math.max(1, camera.state.radius + camera.state.radius * factor)
|
||||
camera.setState({ radius })
|
||||
}
|
||||
|
||||
@@ -248,10 +248,14 @@ namespace TrackballControls {
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensure the distance between object and target is within the min/max distance */
|
||||
/**
|
||||
* Ensure the distance between object and target is within the min/max distance
|
||||
* and not too large compared to `camera.state.radiusMax`
|
||||
*/
|
||||
function checkDistances() {
|
||||
if (Vec3.squaredMagnitude(_eye) > p.maxDistance * p.maxDistance) {
|
||||
Vec3.setMagnitude(_eye, _eye, p.maxDistance)
|
||||
const maxDistance = Math.min(Math.max(camera.state.radiusMax * 1000, 0.01), p.maxDistance)
|
||||
if (Vec3.squaredMagnitude(_eye) > maxDistance * maxDistance) {
|
||||
Vec3.setMagnitude(_eye, _eye, maxDistance)
|
||||
Vec3.add(camera.position, camera.target, _eye)
|
||||
Vec2.copy(_zoomStart, _zoomEnd)
|
||||
Vec2.copy(_focusStart, _focusEnd)
|
||||
|
||||
@@ -20,9 +20,10 @@ import { ValueCell } from '../../mol-util';
|
||||
import { Geometry } from '../../mol-geo/geometry/geometry';
|
||||
|
||||
export const DebugHelperParams = {
|
||||
sceneBoundingSpheres: PD.Boolean(false, { description: 'Show scene bounding spheres.' }),
|
||||
objectBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of render objects.' }),
|
||||
instanceBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of instances.' }),
|
||||
sceneBoundingSpheres: PD.Boolean(false, { description: 'Show full scene bounding spheres.' }),
|
||||
visibleSceneBoundingSpheres: PD.Boolean(false, { description: 'Show visible scene bounding spheres.' }),
|
||||
objectBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of visible render objects.' }),
|
||||
instanceBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of visible instances.' }),
|
||||
}
|
||||
export type DebugHelperParams = typeof DebugHelperParams
|
||||
export type DebugHelperProps = PD.Values<DebugHelperParams>
|
||||
@@ -37,6 +38,7 @@ export class BoundingSphereHelper {
|
||||
private objectsData = new Map<GraphicsRenderObject, BoundingSphereData>()
|
||||
private instancesData = new Map<GraphicsRenderObject, BoundingSphereData>()
|
||||
private sceneData: BoundingSphereData | undefined
|
||||
private visibleSceneData: BoundingSphereData | undefined
|
||||
|
||||
constructor(ctx: WebGLContext, parent: Scene, props: Partial<DebugHelperProps>) {
|
||||
this.scene = Scene.create(ctx)
|
||||
@@ -45,9 +47,12 @@ export class BoundingSphereHelper {
|
||||
}
|
||||
|
||||
update() {
|
||||
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.grey, sceneMaterialId)
|
||||
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.lightgrey, sceneMaterialId)
|
||||
if (newSceneData) this.sceneData = newSceneData
|
||||
|
||||
const newVisibleSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphereVisible, this.visibleSceneData, ColorNames.black, visibleSceneMaterialId)
|
||||
if (newVisibleSceneData) this.visibleSceneData = newVisibleSceneData
|
||||
|
||||
this.parent.forEach((r, ro) => {
|
||||
const objectData = this.objectsData.get(ro)
|
||||
const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato, objectMaterialId)
|
||||
@@ -88,6 +93,10 @@ export class BoundingSphereHelper {
|
||||
this.sceneData.renderObject.state.visible = this._props.sceneBoundingSpheres
|
||||
}
|
||||
|
||||
if (this.visibleSceneData) {
|
||||
this.visibleSceneData.renderObject.state.visible = this._props.visibleSceneBoundingSpheres
|
||||
}
|
||||
|
||||
this.parent.forEach((_, ro) => {
|
||||
const objectData = this.objectsData.get(ro)
|
||||
if (objectData) objectData.renderObject.state.visible = ro.state.visible && this._props.objectBoundingSpheres
|
||||
@@ -104,7 +113,10 @@ export class BoundingSphereHelper {
|
||||
}
|
||||
|
||||
get isEnabled() {
|
||||
return this._props.sceneBoundingSpheres || this._props.objectBoundingSpheres || this._props.instanceBoundingSpheres
|
||||
return (
|
||||
this._props.sceneBoundingSpheres || this._props.visibleSceneBoundingSpheres ||
|
||||
this._props.objectBoundingSpheres || this._props.instanceBoundingSpheres
|
||||
)
|
||||
}
|
||||
get props() { return this._props as Readonly<DebugHelperProps> }
|
||||
|
||||
@@ -141,6 +153,7 @@ function createBoundingSphereMesh(boundingSphere: Sphere3D, mesh?: Mesh) {
|
||||
}
|
||||
|
||||
const sceneMaterialId = getNextMaterialId()
|
||||
const visibleSceneMaterialId = getNextMaterialId()
|
||||
const objectMaterialId = getNextMaterialId()
|
||||
const instanceMaterialId = getNextMaterialId()
|
||||
|
||||
|
||||
175
src/mol-canvas3d/helper/camera-helper.ts
Normal file
175
src/mol-canvas3d/helper/camera-helper.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import Scene from '../../mol-gl/scene';
|
||||
import { Camera } from '../camera';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { Viewport } from '../camera/util';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import produce from 'immer';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
|
||||
// TODO add scale line/grid
|
||||
|
||||
const AxesParams = {
|
||||
...Mesh.Params,
|
||||
alpha: { ...Mesh.Params.alpha, defaultValue: 0.33 },
|
||||
ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
|
||||
colorX: PD.Color(ColorNames.red, { isEssential: true }),
|
||||
colorY: PD.Color(ColorNames.green, { isEssential: true }),
|
||||
colorZ: PD.Color(ColorNames.blue, { isEssential: true }),
|
||||
scale: PD.Numeric(0.33, { min: 0.1, max: 2, step: 0.1 }, { isEssential: true }),
|
||||
}
|
||||
type AxesParams = typeof AxesParams
|
||||
type AxesProps = PD.Values<AxesParams>
|
||||
|
||||
export const CameraHelperParams = {
|
||||
axes: PD.MappedStatic('on', {
|
||||
on: PD.Group(AxesParams),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Show camera orientation axes' }),
|
||||
}
|
||||
export type CameraHelperParams = typeof CameraHelperParams
|
||||
export type CameraHelperProps = PD.Values<CameraHelperParams>
|
||||
|
||||
export class CameraHelper {
|
||||
scene: Scene
|
||||
camera: Camera
|
||||
props: CameraHelperProps = {
|
||||
axes: { name: 'off', params: {} }
|
||||
}
|
||||
|
||||
private renderObject: GraphicsRenderObject | undefined
|
||||
|
||||
constructor(private webgl: WebGLContext, props: Partial<CameraHelperProps> = {}) {
|
||||
this.scene = Scene.create(webgl)
|
||||
|
||||
this.camera = new Camera()
|
||||
Vec3.set(this.camera.up, 0, 1, 0)
|
||||
Vec3.set(this.camera.target, 0, 0, 0)
|
||||
|
||||
this.setProps(props)
|
||||
}
|
||||
|
||||
setProps(props: Partial<CameraHelperProps>) {
|
||||
this.props = produce(this.props, p => {
|
||||
if (props.axes !== undefined) {
|
||||
p.axes.name = props.axes.name
|
||||
if (props.axes.name === 'on') {
|
||||
this.scene.clear()
|
||||
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio }
|
||||
this.renderObject = createAxesRenderObject(params)
|
||||
this.scene.add(this.renderObject)
|
||||
this.scene.commit()
|
||||
|
||||
Vec3.set(this.camera.position, 0, 0, params.scale * 200)
|
||||
Mat4.lookAt(this.camera.view, this.camera.position, this.camera.target, this.camera.up)
|
||||
|
||||
p.axes.params = { ...props.axes.params }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
get isEnabled() {
|
||||
return this.props.axes.name === 'on'
|
||||
}
|
||||
|
||||
update(camera: Camera) {
|
||||
if (!this.renderObject) return
|
||||
|
||||
updateCamera(this.camera, camera.viewport)
|
||||
|
||||
const m = this.renderObject.values.aTransform.ref.value as unknown as Mat4
|
||||
Mat4.extractRotation(m, camera.view)
|
||||
|
||||
const r = this.renderObject.values.boundingSphere.ref.value.radius
|
||||
Mat4.setTranslation(m, Vec3.create(
|
||||
-camera.viewport.width / 2 + r,
|
||||
-camera.viewport.height / 2 + r,
|
||||
0
|
||||
))
|
||||
|
||||
ValueCell.update(this.renderObject.values.aTransform, this.renderObject.values.aTransform.ref.value)
|
||||
this.scene.update([this.renderObject], true)
|
||||
}
|
||||
}
|
||||
|
||||
function updateCamera(camera: Camera, viewport: Viewport) {
|
||||
const { near, far } = camera
|
||||
|
||||
const fullLeft = -(viewport.width - viewport.x) / 2
|
||||
const fullRight = (viewport.width - viewport.x) / 2
|
||||
const fullTop = (viewport.height - viewport.y) / 2
|
||||
const fullBottom = -(viewport.height - viewport.y) / 2
|
||||
|
||||
const dx = (fullRight - fullLeft) / 2
|
||||
const dy = (fullTop - fullBottom) / 2
|
||||
const cx = (fullRight + fullLeft) / 2
|
||||
const cy = (fullTop + fullBottom) / 2
|
||||
|
||||
const left = cx - dx
|
||||
const right = cx + dx
|
||||
const top = cy + dy
|
||||
const bottom = cy - dy
|
||||
|
||||
Mat4.ortho(camera.projection, left, right, top, bottom, near, far)
|
||||
}
|
||||
|
||||
function createAxesMesh(scale: number, mesh?: Mesh) {
|
||||
const state = MeshBuilder.createState(512, 256, mesh)
|
||||
const radius = 0.05 * scale
|
||||
const x = Vec3.scale(Vec3(), Vec3.unitX, scale)
|
||||
const y = Vec3.scale(Vec3(), Vec3.unitY, scale)
|
||||
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale)
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 }
|
||||
|
||||
state.currentGroup = 0
|
||||
addSphere(state, Vec3.origin, radius, 2)
|
||||
|
||||
state.currentGroup = 1
|
||||
addSphere(state, x, radius, 2)
|
||||
addCylinder(state, Vec3.origin, x, 1, cylinderProps)
|
||||
|
||||
state.currentGroup = 2
|
||||
addSphere(state, y, radius, 2)
|
||||
addCylinder(state, Vec3.origin, y, 1, cylinderProps)
|
||||
|
||||
state.currentGroup = 3
|
||||
addSphere(state, z, radius, 2)
|
||||
addCylinder(state, Vec3.origin, z, 1, cylinderProps)
|
||||
|
||||
return MeshBuilder.getMesh(state)
|
||||
}
|
||||
|
||||
function getAxesShape(props: AxesProps, shape?: Shape<Mesh>) {
|
||||
const scale = 100 * props.scale
|
||||
const mesh = createAxesMesh(scale, shape?.geometry)
|
||||
mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4))
|
||||
const getColor = (groupId: number) => {
|
||||
switch (groupId) {
|
||||
case 1: return props.colorX
|
||||
case 2: return props.colorY
|
||||
case 3: return props.colorZ
|
||||
default: return ColorNames.grey
|
||||
}
|
||||
}
|
||||
return Shape.create('axes', {}, mesh, getColor, () => 1, () => '')
|
||||
}
|
||||
|
||||
function createAxesRenderObject(props: AxesProps) {
|
||||
const shape = getAxesShape(props)
|
||||
return Shape.createRenderObject(shape, props)
|
||||
}
|
||||
@@ -111,7 +111,7 @@ export class Canvas3dInteractionHelper {
|
||||
this.ev.dispose();
|
||||
}
|
||||
|
||||
constructor(private canvasIdentify: Canvas3D['identify'], private getLoci: Canvas3D['getLoci'], input: InputObserver, private maxFps: number = 15) {
|
||||
constructor(private canvasIdentify: Canvas3D['identify'], private getLoci: Canvas3D['getLoci'], input: InputObserver, private maxFps: number = 30) {
|
||||
input.move.subscribe(({x, y, inside, buttons, button, modifiers }) => {
|
||||
if (!inside) return;
|
||||
this.move(x, y, buttons, button, modifiers);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -11,15 +11,25 @@ import Scene from '../../mol-gl/scene';
|
||||
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
import { Camera } from '../camera';
|
||||
import { CameraHelper, CameraHelperParams } from '../helper/camera-helper';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
export const DrawPassParams = {
|
||||
cameraHelper: PD.Group(CameraHelperParams)
|
||||
}
|
||||
export const DefaultDrawPassProps = PD.getDefaultValues(DrawPassParams);
|
||||
export type DrawPassProps = PD.Values<typeof DrawPassParams>
|
||||
|
||||
export class DrawPass {
|
||||
colorTarget: RenderTarget
|
||||
depthTexture: Texture
|
||||
packedDepth: boolean
|
||||
|
||||
cameraHelper: CameraHelper
|
||||
|
||||
private depthTarget: RenderTarget | null
|
||||
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper) {
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, props: Partial<DrawPassProps> = {}) {
|
||||
const { gl, extensions, resources } = webgl
|
||||
const width = gl.drawingBufferWidth
|
||||
const height = gl.drawingBufferHeight
|
||||
@@ -31,6 +41,9 @@ export class DrawPass {
|
||||
this.depthTexture.define(width, height)
|
||||
this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth')
|
||||
}
|
||||
|
||||
const p = { ...DefaultDrawPassProps, ...props }
|
||||
this.cameraHelper = new CameraHelper(webgl, p.cameraHelper);
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
@@ -42,8 +55,18 @@ export class DrawPass {
|
||||
}
|
||||
}
|
||||
|
||||
setProps(props: Partial<DrawPassProps>) {
|
||||
if (props.cameraHelper) this.cameraHelper.setProps(props.cameraHelper)
|
||||
}
|
||||
|
||||
get props(): DrawPassProps {
|
||||
return {
|
||||
cameraHelper: { ...this.cameraHelper.props }
|
||||
}
|
||||
}
|
||||
|
||||
render(toDrawingBuffer: boolean, transparentBackground: boolean) {
|
||||
const { webgl, renderer, scene, camera, debugHelper, colorTarget, depthTarget } = this
|
||||
const { webgl, renderer, colorTarget, depthTarget } = this
|
||||
if (toDrawingBuffer) {
|
||||
webgl.unbindFramebuffer()
|
||||
} else {
|
||||
@@ -55,21 +78,26 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
renderer.setViewport(0, 0, colorTarget.getWidth(), colorTarget.getHeight())
|
||||
renderer.render(scene, camera, 'color', true, transparentBackground)
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility()
|
||||
renderer.render(debugHelper.scene, camera, 'color', false, transparentBackground)
|
||||
}
|
||||
this.renderInternal('color', transparentBackground)
|
||||
|
||||
// do a depth pass if not rendering to drawing buffer and
|
||||
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
|
||||
if (!toDrawingBuffer && depthTarget) {
|
||||
depthTarget.bind()
|
||||
renderer.render(scene, camera, 'depth', true, transparentBackground)
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility()
|
||||
renderer.render(debugHelper.scene, camera, 'depth', false, transparentBackground)
|
||||
}
|
||||
this.renderInternal('depth', transparentBackground)
|
||||
}
|
||||
}
|
||||
|
||||
private renderInternal(variant: 'color' | 'depth', transparentBackground: boolean) {
|
||||
const { renderer, scene, camera, debugHelper, cameraHelper } = this
|
||||
renderer.render(scene, camera, variant, true, transparentBackground)
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility()
|
||||
renderer.render(debugHelper.scene, camera, variant, false, transparentBackground)
|
||||
}
|
||||
if (cameraHelper.isEnabled) {
|
||||
cameraHelper.update(camera)
|
||||
renderer.render(cameraHelper.scene, cameraHelper.camera, variant, false, transparentBackground)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -10,7 +10,7 @@ import Renderer from '../../mol-gl/renderer';
|
||||
import Scene from '../../mol-gl/scene';
|
||||
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { DrawPass } from './draw'
|
||||
import { DrawPass, DrawPassParams } from './draw'
|
||||
import { PostprocessingPass, PostprocessingParams } from './postprocessing'
|
||||
import { MultiSamplePass, MultiSampleParams } from './multi-sample'
|
||||
import { Camera } from '../camera';
|
||||
@@ -20,6 +20,7 @@ export const ImageParams = {
|
||||
transparentBackground: PD.Boolean(false),
|
||||
multiSample: PD.Group(MultiSampleParams),
|
||||
postprocessing: PD.Group(PostprocessingParams),
|
||||
drawPass: PD.Group(DrawPassParams),
|
||||
}
|
||||
export type ImageProps = PD.Values<typeof ImageParams>
|
||||
|
||||
@@ -44,7 +45,7 @@ export class ImagePass {
|
||||
|
||||
this._transparentBackground = p.transparentBackground
|
||||
|
||||
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper)
|
||||
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, p.drawPass)
|
||||
this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing)
|
||||
this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample)
|
||||
|
||||
@@ -66,6 +67,16 @@ export class ImagePass {
|
||||
if (props.transparentBackground !== undefined) this._transparentBackground = props.transparentBackground
|
||||
if (props.postprocessing) this.postprocessing.setProps(props.postprocessing)
|
||||
if (props.multiSample) this.multiSample.setProps(props.multiSample)
|
||||
if (props.drawPass) this.drawPass.setProps(props.drawPass)
|
||||
}
|
||||
|
||||
get props(): ImageProps {
|
||||
return {
|
||||
transparentBackground: this._transparentBackground,
|
||||
postprocessing: this.postprocessing.props,
|
||||
multiSample: this.multiSample.props,
|
||||
drawPass: this.drawPass.props
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -40,7 +40,7 @@ function getComposeRenderable(ctx: WebGLContext, colorTexture: Texture): Compose
|
||||
}
|
||||
|
||||
const schema = { ...ComposeSchema }
|
||||
const shaderCode = ShaderCode(quad_vert, compose_frag)
|
||||
const shaderCode = ShaderCode('compose', quad_vert, compose_frag)
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
|
||||
return createComputeRenderable(renderItem, values)
|
||||
|
||||
@@ -96,7 +96,7 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
|
||||
}
|
||||
|
||||
const schema = { ...PostprocessingSchema }
|
||||
const shaderCode = ShaderCode(quad_vert, postprocessing_frag)
|
||||
const shaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag)
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
|
||||
return createComputeRenderable(renderItem, values)
|
||||
|
||||
@@ -4,29 +4,28 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ValueCell } from '../../../mol-util'
|
||||
import { Sphere3D, Box3D } from '../../../mol-math/geometry'
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { DirectVolumeValues } from '../../../mol-gl/renderable/direct-volume';
|
||||
import { Vec3, Mat4, Vec2 } from '../../../mol-math/linear-algebra';
|
||||
import { Box } from '../../primitive/box';
|
||||
import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { TransformData } from '../transform-data';
|
||||
import { createColors } from '../color-data';
|
||||
import { createMarkers } from '../marker-data';
|
||||
import { GeometryUtils } from '../geometry';
|
||||
import { transformPositionArray } from '../../../mol-geo/util';
|
||||
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { ColorListOptions, ColorListName } from '../../../mol-util/color/lists';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { BaseGeometry } from '../base';
|
||||
import { createEmptyOverpaint } from '../overpaint-data';
|
||||
import { createEmptyTransparency } from '../transparency-data';
|
||||
import { hashFnv32a } from '../../../mol-data/util';
|
||||
import { transformPositionArray } from '../../../mol-geo/util';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { DirectVolumeValues } from '../../../mol-gl/renderable/direct-volume';
|
||||
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { Box3D, Sphere3D } from '../../../mol-math/geometry';
|
||||
import { Mat4, Vec2, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Box } from '../../primitive/box';
|
||||
import { BaseGeometry } from '../base';
|
||||
import { createColors } from '../color-data';
|
||||
import { GeometryUtils } from '../geometry';
|
||||
import { createMarkers } from '../marker-data';
|
||||
import { createEmptyOverpaint } from '../overpaint-data';
|
||||
import { TransformData } from '../transform-data';
|
||||
import { createEmptyTransparency } from '../transparency-data';
|
||||
import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function';
|
||||
|
||||
const VolumeBox = Box()
|
||||
const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][]
|
||||
@@ -117,7 +116,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.ColorList<ColorListName>('red-yellow-blue', ColorListOptions),
|
||||
list: PD.ColorList('red-yellow-blue'),
|
||||
}
|
||||
export type Params = typeof Params
|
||||
|
||||
@@ -148,7 +147,7 @@ export namespace DirectVolume {
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount)
|
||||
|
||||
const controlPoints = getControlPointsFromVec2Array(props.controlPoints)
|
||||
const transferTex = createTransferFunctionTexture(controlPoints, props.list)
|
||||
const transferTex = createTransferFunctionTexture(controlPoints, props.list.colors)
|
||||
|
||||
const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value)) * 2 * 5
|
||||
|
||||
@@ -193,7 +192,7 @@ export namespace DirectVolume {
|
||||
ValueCell.updateIfChanged(values.dRenderMode, props.renderMode)
|
||||
|
||||
const controlPoints = getControlPointsFromVec2Array(props.controlPoints)
|
||||
createTransferFunctionTexture(controlPoints, props.list, values.tTransferTex)
|
||||
createTransferFunctionTexture(controlPoints, props.list.colors, values.tTransferTex)
|
||||
}
|
||||
|
||||
function updateBoundingSphere(values: DirectVolumeValues, directVolume: DirectVolume) {
|
||||
|
||||
@@ -41,7 +41,7 @@ function getHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: T
|
||||
}
|
||||
|
||||
const schema = { ...HistopyramidReductionSchema }
|
||||
const shaderCode = ShaderCode(quad_vert, reduction_frag)
|
||||
const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag)
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
|
||||
HistopyramidReductionRenderable = createComputeRenderable(renderItem, values);
|
||||
|
||||
@@ -34,7 +34,7 @@ function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
|
||||
}
|
||||
|
||||
const schema = { ...HistopyramidSumSchema }
|
||||
const shaderCode = ShaderCode(quad_vert, sum_frag)
|
||||
const shaderCode = ShaderCode('sum', quad_vert, sum_frag)
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
|
||||
HistopyramidSumRenderable = createComputeRenderable(renderItem, values)
|
||||
|
||||
@@ -46,7 +46,7 @@ function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridD
|
||||
}
|
||||
|
||||
const schema = { ...ActiveVoxelsSchema }
|
||||
const shaderCode = ShaderCode(quad_vert, active_voxels_frag)
|
||||
const shaderCode = ShaderCode('active-voxels', quad_vert, active_voxels_frag)
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
|
||||
@@ -62,7 +62,7 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
|
||||
}
|
||||
|
||||
const schema = { ...IsosurfaceSchema }
|
||||
const shaderCode = ShaderCode(quad_vert, isosurface_frag, { drawBuffers: true })
|
||||
const shaderCode = ShaderCode('isosurface', quad_vert, isosurface_frag, { drawBuffers: true })
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
|
||||
@@ -95,12 +95,12 @@ export function calculateInvariantBoundingSphere(position: Float32Array, positio
|
||||
boundaryHelper.reset()
|
||||
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
|
||||
Vec3.fromArray(v, position, i)
|
||||
boundaryHelper.includeStep(v)
|
||||
boundaryHelper.includePosition(v)
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep()
|
||||
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
|
||||
Vec3.fromArray(v, position, i)
|
||||
boundaryHelper.radiusStep(v)
|
||||
boundaryHelper.radiusPosition(v)
|
||||
}
|
||||
|
||||
const sphere = boundaryHelper.getSphere()
|
||||
@@ -122,29 +122,30 @@ export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere
|
||||
|
||||
const { center, radius, extrema } = invariantBoundingSphere
|
||||
|
||||
if (extrema) {
|
||||
// only use extrema if there are not too many transforms
|
||||
if (extrema && transformCount < 50) {
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
for (const e of extrema) {
|
||||
Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16)
|
||||
boundaryHelper.includeStep(v)
|
||||
boundaryHelper.includePosition(v)
|
||||
}
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep()
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
for (const e of extrema) {
|
||||
Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16)
|
||||
boundaryHelper.radiusStep(v)
|
||||
boundaryHelper.radiusPosition(v)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
|
||||
boundaryHelper.includeSphereStep(v, radius)
|
||||
boundaryHelper.includePositionRadius(v, radius)
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep()
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
|
||||
boundaryHelper.radiusSphereStep(v, radius)
|
||||
boundaryHelper.radiusPositionRadius(v, radius)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import { ValueCell } from '../mol-util';
|
||||
import { RenderableValues, GlobalUniformValues, BaseValues } from './renderable/schema';
|
||||
import { GraphicsRenderVariant } from './webgl/render-item';
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
import { deepClone } from '../mol-util/object';
|
||||
|
||||
export interface RendererStats {
|
||||
programCount: number
|
||||
@@ -110,7 +109,7 @@ function getStyle(props: RendererProps['style']) {
|
||||
namespace Renderer {
|
||||
export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
|
||||
const { gl, state, stats } = ctx
|
||||
const p = deepClone({ ...PD.getDefaultValues(RendererParams), ...props })
|
||||
const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props)
|
||||
const style = getStyle(p.style)
|
||||
|
||||
const viewport = Viewport()
|
||||
|
||||
@@ -19,37 +19,25 @@ import { hash1 } from '../mol-data/util';
|
||||
|
||||
const boundaryHelper = new BoundaryHelper('98')
|
||||
|
||||
function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
|
||||
function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D, onlyVisible: boolean): Sphere3D {
|
||||
boundaryHelper.reset();
|
||||
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
if (!renderables[i].state.visible) continue;
|
||||
if (onlyVisible && !renderables[i].state.visible) continue;
|
||||
|
||||
const boundingSphere = renderables[i].values.boundingSphere.ref.value
|
||||
if (!boundingSphere.radius) continue;
|
||||
|
||||
if (Sphere3D.hasExtrema(boundingSphere)) {
|
||||
for (const e of boundingSphere.extrema) {
|
||||
boundaryHelper.includeStep(e)
|
||||
}
|
||||
} else {
|
||||
boundaryHelper.includeSphereStep(boundingSphere.center, boundingSphere.radius);
|
||||
}
|
||||
boundaryHelper.includeSphere(boundingSphere);
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep();
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
if (!renderables[i].state.visible) continue;
|
||||
if (onlyVisible && !renderables[i].state.visible) continue;
|
||||
|
||||
const boundingSphere = renderables[i].values.boundingSphere.ref.value
|
||||
if (!boundingSphere.radius) continue;
|
||||
|
||||
if (Sphere3D.hasExtrema(boundingSphere)) {
|
||||
for (const e of boundingSphere.extrema) {
|
||||
boundaryHelper.radiusStep(e)
|
||||
}
|
||||
} else {
|
||||
boundaryHelper.radiusSphereStep(boundingSphere.center, boundingSphere.radius);
|
||||
}
|
||||
boundaryHelper.radiusSphere(boundingSphere);
|
||||
}
|
||||
|
||||
return boundaryHelper.getSphere(boundingSphere);
|
||||
@@ -74,6 +62,7 @@ interface Scene extends Object3D {
|
||||
readonly count: number
|
||||
readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
|
||||
readonly boundingSphere: Sphere3D
|
||||
readonly boundingSphereVisible: Sphere3D
|
||||
|
||||
/** Returns `true` if some visibility has changed, `false` otherwise. */
|
||||
syncVisibility: () => boolean
|
||||
@@ -92,8 +81,10 @@ namespace Scene {
|
||||
const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>()
|
||||
const renderables: Renderable<RenderableValues & BaseValues>[] = []
|
||||
const boundingSphere = Sphere3D()
|
||||
const boundingSphereVisible = Sphere3D()
|
||||
|
||||
let boundingSphereDirty = true
|
||||
let boundingSphereVisibleDirty = true
|
||||
|
||||
const object3d = Object3D.create()
|
||||
|
||||
@@ -103,6 +94,7 @@ namespace Scene {
|
||||
renderables.push(renderable)
|
||||
renderableMap.set(o, renderable)
|
||||
boundingSphereDirty = true
|
||||
boundingSphereVisibleDirty = true
|
||||
return renderable;
|
||||
} else {
|
||||
console.warn(`RenderObject with id '${o.id}' already present`)
|
||||
@@ -117,6 +109,7 @@ namespace Scene {
|
||||
arraySetRemove(renderables, renderable);
|
||||
renderableMap.delete(o)
|
||||
boundingSphereDirty = true
|
||||
boundingSphereVisibleDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +154,7 @@ namespace Scene {
|
||||
function syncVisibility() {
|
||||
const newVisibleHash = computeVisibleHash()
|
||||
if (newVisibleHash !== visibleHash) {
|
||||
boundingSphereDirty = true
|
||||
boundingSphereVisibleDirty = true
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@@ -190,6 +183,7 @@ namespace Scene {
|
||||
}
|
||||
if (!keepBoundingSphere) {
|
||||
boundingSphereDirty = true
|
||||
boundingSphereVisibleDirty = true
|
||||
} else {
|
||||
syncVisibility()
|
||||
}
|
||||
@@ -208,6 +202,7 @@ namespace Scene {
|
||||
renderables.length = 0
|
||||
renderableMap.clear()
|
||||
boundingSphereDirty = true
|
||||
boundingSphereVisibleDirty = true
|
||||
},
|
||||
forEach: (callbackFn: (value: Renderable<any>, key: GraphicsRenderObject) => void) => {
|
||||
renderableMap.forEach(callbackFn)
|
||||
@@ -218,11 +213,18 @@ namespace Scene {
|
||||
renderables,
|
||||
get boundingSphere() {
|
||||
if (boundingSphereDirty) {
|
||||
calculateBoundingSphere(renderables, boundingSphere)
|
||||
calculateBoundingSphere(renderables, boundingSphere, false)
|
||||
boundingSphereDirty = false
|
||||
visibleHash = computeVisibleHash()
|
||||
}
|
||||
return boundingSphere
|
||||
},
|
||||
get boundingSphereVisible() {
|
||||
if (boundingSphereVisibleDirty) {
|
||||
calculateBoundingSphere(renderables, boundingSphereVisible, true)
|
||||
boundingSphereVisibleDirty = false
|
||||
visibleHash = computeVisibleHash()
|
||||
}
|
||||
return boundingSphereVisible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface ShaderExtensions {
|
||||
|
||||
export interface ShaderCode {
|
||||
readonly id: number
|
||||
readonly name: string
|
||||
readonly vert: string
|
||||
readonly frag: string
|
||||
readonly extensions: ShaderExtensions
|
||||
@@ -97,33 +98,33 @@ function addIncludes(text: string) {
|
||||
.replace(reMultipleLinebreaks, '\n')
|
||||
}
|
||||
|
||||
export function ShaderCode(vert: string, frag: string, extensions: ShaderExtensions = {}): ShaderCode {
|
||||
return { id: shaderCodeId(), vert: addIncludes(vert), frag: addIncludes(frag), extensions }
|
||||
export function ShaderCode(name: string, vert: string, frag: string, extensions: ShaderExtensions = {}): ShaderCode {
|
||||
return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions }
|
||||
}
|
||||
|
||||
import points_vert from './shader/points.vert'
|
||||
import points_frag from './shader/points.frag'
|
||||
export const PointsShaderCode = ShaderCode(points_vert, points_frag)
|
||||
export const PointsShaderCode = ShaderCode('points', points_vert, points_frag)
|
||||
|
||||
import spheres_vert from './shader/spheres.vert'
|
||||
import spheres_frag from './shader/spheres.frag'
|
||||
export const SpheresShaderCode = ShaderCode(spheres_vert, spheres_frag, { fragDepth: true })
|
||||
export const SpheresShaderCode = ShaderCode('spheres', spheres_vert, spheres_frag, { fragDepth: true })
|
||||
|
||||
import text_vert from './shader/text.vert'
|
||||
import text_frag from './shader/text.frag'
|
||||
export const TextShaderCode = ShaderCode(text_vert, text_frag, { standardDerivatives: true })
|
||||
export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: true })
|
||||
|
||||
import lines_vert from './shader/lines.vert'
|
||||
import lines_frag from './shader/lines.frag'
|
||||
export const LinesShaderCode = ShaderCode(lines_vert, lines_frag)
|
||||
export const LinesShaderCode = ShaderCode('lines', lines_vert, lines_frag)
|
||||
|
||||
import mesh_vert from './shader/mesh.vert'
|
||||
import mesh_frag from './shader/mesh.frag'
|
||||
export const MeshShaderCode = ShaderCode(mesh_vert, mesh_frag, { standardDerivatives: true })
|
||||
export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { standardDerivatives: true })
|
||||
|
||||
import direct_volume_vert from './shader/direct-volume.vert'
|
||||
import direct_volume_frag from './shader/direct-volume.frag'
|
||||
export const DirectVolumeShaderCode = ShaderCode(direct_volume_vert, direct_volume_frag, { fragDepth: true })
|
||||
export const DirectVolumeShaderCode = ShaderCode('direct-volume', direct_volume_vert, direct_volume_frag, { fragDepth: true })
|
||||
|
||||
//
|
||||
|
||||
@@ -233,6 +234,7 @@ export function addShaderDefines(gl: GLRenderingContext, extensions: WebGLExtens
|
||||
const frag = isWebGL2(gl) ? transformGlsl300Frag(shaders.frag) : shaders.frag
|
||||
return {
|
||||
id: shaderCodeId(),
|
||||
name: shaders.name,
|
||||
vert: `${vertPrefix}${header}${shaders.vert}`,
|
||||
frag: `${fragPrefix}${header}${frag}`,
|
||||
extensions: shaders.extensions
|
||||
|
||||
@@ -38,10 +38,12 @@ function getLocations(gl: GLRenderingContext, program: WebGLProgram, schema: Ren
|
||||
const spec = schema[k]
|
||||
if (spec.type === 'attribute') {
|
||||
const loc = gl.getAttribLocation(program, k)
|
||||
// unused attributes will result in a `-1` location which is usually fine
|
||||
// if (loc === -1) console.info(`Could not get attribute location for '${k}'`)
|
||||
locations[k] = loc
|
||||
} else if (spec.type === 'uniform' || spec.type === 'texture') {
|
||||
const loc = gl.getUniformLocation(program, k)
|
||||
// unused uniforms will result in a `null` location which is usually fine
|
||||
// if (loc === null) console.info(`Could not get uniform location for '${k}'`)
|
||||
locations[k] = loc as number
|
||||
}
|
||||
@@ -146,8 +148,8 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
|
||||
const vertShader = getShader('vert', shaderCode.vert)
|
||||
const fragShader = getShader('frag', shaderCode.frag)
|
||||
|
||||
let locations: Locations // = getLocations(gl, program, schema)
|
||||
let uniformSetters: UniformSetters // = getUniformSetters(schema)
|
||||
let locations: Locations
|
||||
let uniformSetters: UniformSetters
|
||||
|
||||
function init() {
|
||||
vertShader.attach(program)
|
||||
@@ -186,9 +188,9 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
|
||||
}
|
||||
}
|
||||
},
|
||||
bindAttributes: (attribueBuffers: AttributeBuffers) => {
|
||||
for (let i = 0, il = attribueBuffers.length; i < il; ++i) {
|
||||
const [k, buffer] = attribueBuffers[i]
|
||||
bindAttributes: (attributeBuffers: AttributeBuffers) => {
|
||||
for (let i = 0, il = attributeBuffers.length; i < il; ++i) {
|
||||
const [k, buffer] = attributeBuffers[i]
|
||||
const l = locations[k]
|
||||
if (l !== -1) buffer.bind(l)
|
||||
}
|
||||
|
||||
@@ -198,9 +198,6 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
|
||||
try {
|
||||
checkError(ctx.gl)
|
||||
} catch (e) {
|
||||
// console.log('shaderCode', shaderCode)
|
||||
// console.log('schema', schema)
|
||||
// console.log('attributeBuffers', attributeBuffers)
|
||||
throw new Error(`Error rendering item id ${id}: '${e}'`)
|
||||
}
|
||||
}
|
||||
@@ -246,10 +243,10 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
|
||||
const value = attributeValues[k]
|
||||
if (value.ref.version !== versions[k]) {
|
||||
if (buffer.length >= value.ref.value.length) {
|
||||
// console.log('attribute array large enough to update', k, value.ref.id, value.ref.version)
|
||||
// console.log('attribute array large enough to update', buffer.id, k, value.ref.id, value.ref.version)
|
||||
buffer.updateData(value.ref.value)
|
||||
} else {
|
||||
// console.log('attribute array to small, need to create new attribute', k, value.ref.id, value.ref.version)
|
||||
// console.log('attribute array too small, need to create new attribute', buffer.id, k, value.ref.id, value.ref.version)
|
||||
buffer.destroy()
|
||||
const { itemSize, divisor } = schema[k] as AttributeSpec<AttributeKind>
|
||||
attributeBuffers[i][1] = resources.attribute(value.ref.value, itemSize, divisor)
|
||||
@@ -276,7 +273,8 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
|
||||
// console.log('program/defines or buffers changed, update vaos')
|
||||
Object.keys(renderVariantDefines).forEach(k => {
|
||||
const vertexArray = vertexArrays[k]
|
||||
if (vertexArray) vertexArray.update()
|
||||
if (vertexArray) vertexArray.destroy()
|
||||
vertexArrays[k] = vertexArrayObject ? resources.vertexArray(programs[k], attributeBuffers, elementsBuffer) : null
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -129,7 +129,7 @@ export const BIRD_Schema = {
|
||||
/**
|
||||
* Defines the polymer characteristic of the entity.
|
||||
*/
|
||||
type: str,
|
||||
type: Aliased<'polymer' | 'polymer-like' | 'non-polymer' | 'branched'>(str),
|
||||
/**
|
||||
* Additional details about this entity.
|
||||
*/
|
||||
@@ -423,7 +423,7 @@ export const BIRD_Schema = {
|
||||
/**
|
||||
* The monomer type for the sequence.
|
||||
*/
|
||||
type: str,
|
||||
type: Aliased<'peptide-like' | 'saccharide'>(str),
|
||||
/**
|
||||
* A flag to indicate a non-ribosomal entity.
|
||||
*/
|
||||
@@ -487,7 +487,7 @@ export const BIRD_Schema = {
|
||||
/**
|
||||
* An identifier for the wwPDB site creating or modifying the molecule.
|
||||
*/
|
||||
processing_site: Aliased<'RCSB' | 'PDBe' | 'PDBJ' | 'BMRB'>(str),
|
||||
processing_site: Aliased<'RCSB' | 'PDBe' | 'PDBJ' | 'BMRB' | 'PDBC'>(str),
|
||||
/**
|
||||
* The action associated with this audit record.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -175,7 +175,7 @@ export const CCD_Schema = {
|
||||
* This data item identifies the deposition site that processed
|
||||
* this chemical component defintion.
|
||||
*/
|
||||
pdbx_processing_site: Aliased<'PDBE' | 'EBI' | 'PDBJ' | 'RCSB'>(str),
|
||||
pdbx_processing_site: Aliased<'PDBE' | 'EBI' | 'PDBJ' | 'PDBC' | 'RCSB'>(str),
|
||||
},
|
||||
/**
|
||||
* Data items in the CHEM_COMP_ATOM category record details about
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'CifCore' schema file. Dictionary versions: CifCore 3.0.11.
|
||||
* Code-generated 'CifCore' schema file. Dictionary versions: CifCore 3.0.13.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -185,7 +185,7 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* The fraction of the atom type present at this site.
|
||||
* The sum of the occupancies of all the atom types at this site
|
||||
* may not significantly exceed 1.0 unless it is a dummy site.
|
||||
* may not exceed 1.0 unless it is a dummy site.
|
||||
*/
|
||||
occupancy: float,
|
||||
/**
|
||||
@@ -1774,11 +1774,11 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* The site where the file was deposited.
|
||||
*/
|
||||
deposit_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BMRB' | 'BNL'>(str),
|
||||
deposit_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BMRB' | 'BNL' | 'PDBC'>(str),
|
||||
/**
|
||||
* The site where the file was deposited.
|
||||
*/
|
||||
process_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BNL'>(str),
|
||||
process_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BNL' | 'PDBC'>(str),
|
||||
/**
|
||||
* Code for status of chemical shift data file.
|
||||
*/
|
||||
@@ -2192,7 +2192,7 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* Defines the polymer characteristic of the entity.
|
||||
*/
|
||||
type: str,
|
||||
type: Aliased<'polymer' | 'polymer-like' | 'non-polymer' | 'branched'>(str),
|
||||
/**
|
||||
* Additional details about this entity.
|
||||
*/
|
||||
@@ -2575,6 +2575,48 @@ export const mmCIF_Schema = {
|
||||
*/
|
||||
ordinal: int,
|
||||
},
|
||||
/**
|
||||
* Data items in the pdbx_entity_instance_feature category records
|
||||
* special features of selected entity instances.
|
||||
*/
|
||||
pdbx_entity_instance_feature: {
|
||||
/**
|
||||
* Special structural details about this entity instance.
|
||||
*/
|
||||
details: str,
|
||||
/**
|
||||
* A feature type associated with entity instance.
|
||||
*/
|
||||
feature_type: Aliased<'SUBJECT OF INVESTIGATION' | 'NO FUNCTIONAL ROLE' | 'OTHER'>(str),
|
||||
/**
|
||||
* Author instance identifier (formerly PDB Chain ID)
|
||||
*/
|
||||
auth_asym_id: str,
|
||||
/**
|
||||
* Instance identifier for this entity.
|
||||
*/
|
||||
asym_id: str,
|
||||
/**
|
||||
* Author provided residue number.
|
||||
*/
|
||||
auth_seq_num: str,
|
||||
/**
|
||||
* Position in the sequence.
|
||||
*/
|
||||
seq_num: int,
|
||||
/**
|
||||
* Chemical component identifier
|
||||
*/
|
||||
comp_id: str,
|
||||
/**
|
||||
* The author provided chemical component identifier
|
||||
*/
|
||||
auth_comp_id: str,
|
||||
/**
|
||||
* An ordinal index for this category
|
||||
*/
|
||||
ordinal: int,
|
||||
},
|
||||
/**
|
||||
* Data items in the IHM_STARTING_MODEL_DETAILS category records the
|
||||
* details about structural models used as starting inputs in
|
||||
@@ -4585,6 +4627,21 @@ export const mmCIF_Schema = {
|
||||
* PDBX_ENTITY_BRANCH_LIST category.
|
||||
*/
|
||||
num: int,
|
||||
/**
|
||||
* This data item is a pointer to _atom_site.auth_asym_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
pdb_asym_id: str,
|
||||
/**
|
||||
* This data item is a pointer to _atom_site.auth_seq_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
pdb_seq_num: str,
|
||||
/**
|
||||
* This data item is a pointer to _atom_site.auth_comp_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
pdb_mon_id: str,
|
||||
/**
|
||||
* This data item is a pointer to _atom_site.pdbx_auth_asym_id in the
|
||||
* ATOM_SITE category.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import TextEncoder from './cif/encoder/text'
|
||||
import BinaryEncoder, { EncodingProvider } from './cif/encoder/binary'
|
||||
import BinaryEncoder, { BinaryEncodingProvider } from './cif/encoder/binary'
|
||||
import * as _Encoder from './cif/encoder'
|
||||
import { ArrayEncoding, ArrayEncoder } from '../common/binary-cif';
|
||||
import { CifFrame } from '../reader/cif';
|
||||
@@ -20,7 +20,7 @@ export namespace CifWriter {
|
||||
export interface EncoderParams {
|
||||
binary?: boolean,
|
||||
encoderName?: string,
|
||||
binaryEncodingPovider?: EncodingProvider,
|
||||
binaryEncodingPovider?: BinaryEncodingProvider,
|
||||
binaryAutoClassifyEncoding?: boolean
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ export namespace CifWriter {
|
||||
return binary ? new BinaryEncoder(encoderName, params ? params.binaryEncodingPovider : void 0, params ? !!params.binaryAutoClassifyEncoding : false) : new TextEncoder();
|
||||
}
|
||||
|
||||
export function fields<K = number, D = any>() {
|
||||
return Field.build<K, D>();
|
||||
export function fields<K = number, D = any, N extends string = string>() {
|
||||
return Field.build<K, D, N>();
|
||||
}
|
||||
|
||||
import E = Encoding
|
||||
@@ -44,7 +44,7 @@ export namespace CifWriter {
|
||||
return { fields, source: [source] };
|
||||
}
|
||||
|
||||
export function createEncodingProviderFromCifFrame(frame: CifFrame): EncodingProvider {
|
||||
export function createEncodingProviderFromCifFrame(frame: CifFrame): BinaryEncodingProvider {
|
||||
return {
|
||||
get(c, f) {
|
||||
const cat = frame.categories[c];
|
||||
@@ -55,7 +55,7 @@ export namespace CifWriter {
|
||||
}
|
||||
};
|
||||
|
||||
export function createEncodingProviderFromJsonConfig(hints: EncodingStrategyHint[]): EncodingProvider {
|
||||
export function createEncodingProviderFromJsonConfig(hints: EncodingStrategyHint[]): BinaryEncodingProvider {
|
||||
return {
|
||||
get(c, f) {
|
||||
for (let i = 0; i < hints.length; i++) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Column, Table, Database, DatabaseCollection } from '../../../mol-data/d
|
||||
import { Tensor } from '../../../mol-math/linear-algebra'
|
||||
import EncoderBase from '../encoder'
|
||||
import { ArrayEncoder, ArrayEncoding } from '../../common/binary-cif';
|
||||
import { BinaryEncodingProvider } from './encoder/binary';
|
||||
|
||||
// TODO: support for "coordinate fields", make "coordinate precision" a parameter of the encoder
|
||||
// TODO: automatically detect "precision" of floating point arrays.
|
||||
@@ -72,25 +73,32 @@ export namespace Field {
|
||||
return int(name, (e, d, i) => i + 1, { typedArray: Int32Array, encoder: ArrayEncoding.by(ArrayEncoding.delta).and(ArrayEncoding.runLength).and(ArrayEncoding.integerPacking) })
|
||||
}
|
||||
|
||||
export class Builder<K = number, D = any> {
|
||||
export class Builder<K = number, D = any, N extends string = string> {
|
||||
private fields: Field<K, D>[] = [];
|
||||
|
||||
index(name: string) {
|
||||
index(name: N) {
|
||||
this.fields.push(Field.index(name));
|
||||
return this;
|
||||
}
|
||||
|
||||
str(name: string, value: (k: K, d: D, index: number) => string, params?: ParamsBase<K, D>) {
|
||||
str(name: N, value: (k: K, d: D, index: number) => string, params?: ParamsBase<K, D>) {
|
||||
this.fields.push(Field.str(name, value, params));
|
||||
return this;
|
||||
}
|
||||
|
||||
int(name: string, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }) {
|
||||
int(name: N, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }) {
|
||||
this.fields.push(Field.int(name, value, params));
|
||||
return this;
|
||||
}
|
||||
|
||||
float(name: string, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }) {
|
||||
vec(name: N, values: ((k: K, d: D, index: number) => number)[], params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
this.fields.push(Field.int(`${name}[${i + 1}]`, values[i], params));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
float(name: N, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }) {
|
||||
this.fields.push(Field.float(name, value, params));
|
||||
return this;
|
||||
}
|
||||
@@ -103,8 +111,8 @@ export namespace Field {
|
||||
getFields() { return this.fields; }
|
||||
}
|
||||
|
||||
export function build<K = number, D = any>() {
|
||||
return new Builder<K, D>();
|
||||
export function build<K = number, D = any, N extends string = string>() {
|
||||
return new Builder<K, D, N>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,14 +223,21 @@ export namespace Category {
|
||||
|
||||
export interface Encoder<T = string | Uint8Array> extends EncoderBase {
|
||||
setFilter(filter?: Category.Filter): void,
|
||||
isCategoryIncluded(name: string): boolean,
|
||||
setFormatter(formatter?: Category.Formatter): void,
|
||||
|
||||
startDataBlock(header: string): void,
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx): void,
|
||||
getData(): T
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx, options?: Encoder.WriteCategoryOptions): void,
|
||||
getData(): T,
|
||||
|
||||
binaryEncodingProvider: BinaryEncodingProvider | undefined;
|
||||
}
|
||||
|
||||
export namespace Encoder {
|
||||
export interface WriteCategoryOptions {
|
||||
ignoreFilter?: boolean
|
||||
}
|
||||
|
||||
export function writeDatabase(encoder: Encoder, name: string, database: Database<Database.Schema>) {
|
||||
encoder.startDataBlock(name);
|
||||
for (const table of database._tableNames) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { getIncludedFields, getCategoryInstanceData, CategoryInstanceData } from
|
||||
import { classifyIntArray, classifyFloatArray } from '../../../common/binary-cif/classifier';
|
||||
import { ArrayCtor } from '../../../../mol-util/type-helpers';
|
||||
|
||||
export interface EncodingProvider {
|
||||
export interface BinaryEncodingProvider {
|
||||
get(category: string, field: string): ArrayEncoder | undefined;
|
||||
}
|
||||
|
||||
@@ -28,10 +28,16 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
private filter: Category.Filter = Category.DefaultFilter;
|
||||
private formatter: Category.Formatter = Category.DefaultFormatter;
|
||||
|
||||
binaryEncodingProvider: BinaryEncodingProvider | undefined = void 0;
|
||||
|
||||
setFilter(filter?: Category.Filter) {
|
||||
this.filter = filter || Category.DefaultFilter;
|
||||
}
|
||||
|
||||
isCategoryIncluded(name: string) {
|
||||
return this.filter.includeCategory(name);
|
||||
}
|
||||
|
||||
setFormatter(formatter?: Category.Formatter) {
|
||||
this.formatter = formatter || Category.DefaultFormatter;
|
||||
}
|
||||
@@ -43,7 +49,7 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
});
|
||||
}
|
||||
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx) {
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx, options?: Encoder.WriteCategoryOptions) {
|
||||
if (!this.data) {
|
||||
throw new Error('The writer contents have already been encoded, no more writing.');
|
||||
}
|
||||
@@ -52,7 +58,7 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
throw new Error('No data block created.');
|
||||
}
|
||||
|
||||
if (!this.filter.includeCategory(category.name)) return;
|
||||
if (!options?.ignoreFilter && !this.filter.includeCategory(category.name)) return;
|
||||
|
||||
const { instance, rowCount, source } = getCategoryInstanceData(category, context);
|
||||
if (!rowCount) return;
|
||||
@@ -64,7 +70,7 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
if (!this.filter.includeField(category.name, f.name)) continue;
|
||||
|
||||
const format = this.formatter.getFormat(category.name, f.name);
|
||||
cat.columns.push(encodeField(category.name, f, source, rowCount, format, this.encodingProvider, this.autoClassify));
|
||||
cat.columns.push(encodeField(category.name, f, source, rowCount, format, this.binaryEncodingProvider, this.autoClassify));
|
||||
}
|
||||
// no columns included.
|
||||
if (!cat.columns.length) return;
|
||||
@@ -88,7 +94,12 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
return this.encodedData;
|
||||
}
|
||||
|
||||
constructor(encoder: string, private encodingProvider: EncodingProvider | undefined, private autoClassify: boolean) {
|
||||
getSize() {
|
||||
return this.encodedData.length;
|
||||
}
|
||||
|
||||
constructor(encoder: string, encodingProvider: BinaryEncodingProvider | undefined, private autoClassify: boolean) {
|
||||
this.binaryEncodingProvider = encodingProvider;
|
||||
this.data = {
|
||||
encoder,
|
||||
version: VERSION,
|
||||
@@ -110,7 +121,7 @@ function getDefaultEncoder(type: Field.Type): ArrayEncoder {
|
||||
return ArrayEncoder.by(E.byteArray);
|
||||
}
|
||||
|
||||
function tryGetEncoder(categoryName: string, field: Field, format: Field.Format | undefined, provider: EncodingProvider | undefined) {
|
||||
function tryGetEncoder(categoryName: string, field: Field, format: Field.Format | undefined, provider: BinaryEncodingProvider | undefined) {
|
||||
if (format && format.encoder) {
|
||||
return format.encoder;
|
||||
} else if (field.defaultFormat && field.defaultFormat.encoder) {
|
||||
@@ -129,7 +140,7 @@ function classify(type: Field.Type, data: ArrayLike<any>) {
|
||||
}
|
||||
|
||||
function encodeField(categoryName: string, field: Field, data: CategoryInstanceData['source'], totalCount: number,
|
||||
format: Field.Format | undefined, encoderProvider: EncodingProvider | undefined, autoClassify: boolean): EncodedColumn {
|
||||
format: Field.Format | undefined, encoderProvider: BinaryEncodingProvider | undefined, autoClassify: boolean): EncodedColumn {
|
||||
|
||||
const { array, allPresent, mask } = getFieldData(field, getArrayCtor(field, format), totalCount, data);
|
||||
|
||||
|
||||
@@ -19,10 +19,16 @@ export default class TextEncoder implements Encoder<string> {
|
||||
private filter: Category.Filter = Category.DefaultFilter;
|
||||
private formatter: Category.Formatter = Category.DefaultFormatter;
|
||||
|
||||
binaryEncodingProvider = void 0;
|
||||
|
||||
setFilter(filter?: Category.Filter) {
|
||||
this.filter = filter || Category.DefaultFilter;
|
||||
}
|
||||
|
||||
isCategoryIncluded(name: string) {
|
||||
return this.filter.includeCategory(name);
|
||||
}
|
||||
|
||||
setFormatter(formatter?: Category.Formatter) {
|
||||
this.formatter = formatter || Category.DefaultFormatter;
|
||||
}
|
||||
@@ -32,7 +38,7 @@ export default class TextEncoder implements Encoder<string> {
|
||||
StringBuilder.write(this.builder, `data_${(header || '').replace(/[ \n\t]/g, '').toUpperCase()}\n#\n`);
|
||||
}
|
||||
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx) {
|
||||
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx, options?: Encoder.WriteCategoryOptions) {
|
||||
if (this.encoded) {
|
||||
throw new Error('The writer contents have already been encoded, no more writing.');
|
||||
}
|
||||
@@ -41,7 +47,7 @@ export default class TextEncoder implements Encoder<string> {
|
||||
throw new Error('No data block created.');
|
||||
}
|
||||
|
||||
if (!this.filter.includeCategory(category.name)) return;
|
||||
if (!options?.ignoreFilter && !this.filter.includeCategory(category.name)) return;
|
||||
const { instance, rowCount, source } = getCategoryInstanceData(category, context);
|
||||
if (!rowCount) return;
|
||||
|
||||
@@ -63,6 +69,10 @@ export default class TextEncoder implements Encoder<string> {
|
||||
}
|
||||
}
|
||||
|
||||
getSize() {
|
||||
return StringBuilder.getSize(this.builder);
|
||||
}
|
||||
|
||||
getData() {
|
||||
return StringBuilder.getString(this.builder);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ import Writer from './writer'
|
||||
|
||||
interface Encoder {
|
||||
encode(): void,
|
||||
writeTo(writer: Writer): void
|
||||
writeTo(writer: Writer): void,
|
||||
getSize(): number
|
||||
}
|
||||
|
||||
export default Encoder
|
||||
@@ -45,13 +45,23 @@ export class BoundaryHelper {
|
||||
}
|
||||
}
|
||||
|
||||
includeStep(p: Vec3) {
|
||||
includeSphere(s: Sphere3D) {
|
||||
if (Sphere3D.hasExtrema(s)) {
|
||||
for (const e of s.extrema) {
|
||||
this.includePosition(e);
|
||||
}
|
||||
} else {
|
||||
this.includePositionRadius(s.center, s.radius);
|
||||
}
|
||||
}
|
||||
|
||||
includePosition(p: Vec3) {
|
||||
for (let i = 0, il = this.dir.length; i < il; ++i) {
|
||||
this.computeExtrema(i, p)
|
||||
}
|
||||
}
|
||||
|
||||
includeSphereStep(center: Vec3, radius: number) {
|
||||
includePositionRadius(center: Vec3, radius: number) {
|
||||
for (let i = 0, il = this.dir.length; i < il; ++i) {
|
||||
this.computeSphereExtrema(i, center, radius)
|
||||
}
|
||||
@@ -64,11 +74,21 @@ export class BoundaryHelper {
|
||||
this.centroidHelper.finishedIncludeStep();
|
||||
}
|
||||
|
||||
radiusStep(p: Vec3) {
|
||||
radiusSphere(s: Sphere3D) {
|
||||
if (Sphere3D.hasExtrema(s)) {
|
||||
for (const e of s.extrema) {
|
||||
this.radiusPosition(e)
|
||||
}
|
||||
} else {
|
||||
this.radiusPositionRadius(s.center, s.radius);
|
||||
}
|
||||
}
|
||||
|
||||
radiusPosition(p: Vec3) {
|
||||
this.centroidHelper.radiusStep(p);
|
||||
}
|
||||
|
||||
radiusSphereStep(center: Vec3, radius: number) {
|
||||
radiusPositionRadius(center: Vec3, radius: number) {
|
||||
this.centroidHelper.radiusSphereStep(center, radius);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ export function getBoundary(data: PositionData): Boundary {
|
||||
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
|
||||
const i = OrderedSet.getAt(indices, t);
|
||||
Vec3.set(p, x[i], y[i], z[i]);
|
||||
boundaryHelper.includeSphereStep(p, (radius && radius[i]) || 0);
|
||||
boundaryHelper.includePositionRadius(p, (radius && radius[i]) || 0);
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep();
|
||||
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
|
||||
const i = OrderedSet.getAt(indices, t);
|
||||
Vec3.set(p, x[i], y[i], z[i]);
|
||||
boundaryHelper.radiusSphereStep(p, (radius && radius[i]) || 0);
|
||||
boundaryHelper.radiusPositionRadius(p, (radius && radius[i]) || 0);
|
||||
}
|
||||
|
||||
const sphere = boundaryHelper.getSphere()
|
||||
|
||||
@@ -46,7 +46,7 @@ export const GaussianDensitySchema = {
|
||||
}
|
||||
|
||||
export const GaussianDensityShaderCode = ShaderCode(
|
||||
gaussian_density_vert, gaussian_density_frag,
|
||||
'gaussian-density', gaussian_density_vert, gaussian_density_frag,
|
||||
{ standardDerivatives: false, fragDepth: false }
|
||||
)
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Sphere3D {
|
||||
}
|
||||
|
||||
export function create(center: Vec3, radius: number): Sphere3D { return { center, radius }; }
|
||||
export function zero(): Sphere3D { return { center: Vec3.zero(), radius: 0 }; }
|
||||
export function zero(): Sphere3D { return { center: Vec3(), radius: 0 }; }
|
||||
|
||||
export function clone(a: Sphere3D): Sphere3D {
|
||||
const out = create(Vec3.clone(a.center), a.radius)
|
||||
@@ -36,6 +36,12 @@ namespace Sphere3D {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function set(out: Sphere3D, center: Vec3, radius: number) {
|
||||
Vec3.copy(out.center, center)
|
||||
out.radius = radius
|
||||
return out
|
||||
}
|
||||
|
||||
export function copy(out: Sphere3D, a: Sphere3D) {
|
||||
Vec3.copy(out.center, a.center)
|
||||
out.radius = a.radius
|
||||
@@ -128,6 +134,12 @@ namespace Sphere3D {
|
||||
export function expandBySphere(out: Sphere3D, sphere: Sphere3D, by: Sphere3D) {
|
||||
Vec3.copy(out.center, sphere.center)
|
||||
out.radius = Math.max(sphere.radius, Vec3.distance(sphere.center, by.center) + by.radius)
|
||||
if (hasExtrema(sphere) && hasExtrema(by)) {
|
||||
setExtrema(out, [
|
||||
...sphere.extrema.map(e => Vec3.clone(e)),
|
||||
...by.extrema.map(e => Vec3.clone(e))
|
||||
])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -163,6 +175,30 @@ namespace Sphere3D {
|
||||
return (Math.abs(ar - br) <= EPSILON * Math.max(1.0, Math.abs(ar), Math.abs(br)) &&
|
||||
Vec3.equals(a.center, b.center));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if `a` includes `b`, use `extrema` of `b` when available
|
||||
*/
|
||||
export function includes(a: Sphere3D, b: Sphere3D) {
|
||||
if (hasExtrema(b)) {
|
||||
for (const e of b.extrema) {
|
||||
if (Vec3.distance(a.center, e) > a.radius) return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return Vec3.distance(a.center, b.center) + b.radius <= a.radius
|
||||
}
|
||||
}
|
||||
|
||||
/** Check if `a` and `b` are overlapping */
|
||||
export function overlaps(a: Sphere3D, b: Sphere3D) {
|
||||
return Vec3.distance(a.center, b.center) <= a.radius + b.radius
|
||||
}
|
||||
|
||||
/** Get the signed distance of `a` and `b` */
|
||||
export function distance(a: Sphere3D, b: Sphere3D) {
|
||||
return Vec3.distance(a.center, b.center) - a.radius + b.radius
|
||||
}
|
||||
}
|
||||
|
||||
export { Sphere3D }
|
||||
@@ -35,7 +35,8 @@ namespace SpacegroupCell {
|
||||
export const Zero: SpacegroupCell = create('P 1', Vec3.create(1, 1, 1), Vec3.create(Math.PI / 2, Math.PI / 2, Math.PI / 2));
|
||||
|
||||
/** True if 'P 1' with cellsize [1, 1, 1] */
|
||||
export function isZero(cell: SpacegroupCell) {
|
||||
export function isZero(cell?: SpacegroupCell) {
|
||||
if (!cell) return true;
|
||||
return cell.index === 0 && cell.size[0] === 1 && cell.size[1] === 1 && cell.size[1] === 1;
|
||||
}
|
||||
|
||||
@@ -103,9 +104,9 @@ namespace Spacegroup {
|
||||
);
|
||||
}
|
||||
|
||||
export function getSymmetryOperator(spacegroup: Spacegroup, index: number, i: number, j: number, k: number): SymmetryOperator {
|
||||
const operator = setOperatorMatrix(spacegroup, index, i, j, k, Mat4.zero());
|
||||
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k), index);
|
||||
export function getSymmetryOperator(spacegroup: Spacegroup, spgrOp: number, i: number, j: number, k: number): SymmetryOperator {
|
||||
const operator = setOperatorMatrix(spacegroup, spgrOp, i, j, k, Mat4.zero());
|
||||
return SymmetryOperator.create(`${spgrOp + 1}_${5 + i}${5 + j}${5 + k}`, operator, { hkl: Vec3.create(i, j, k), spgrOp });
|
||||
}
|
||||
|
||||
const _translationRef = Vec3()
|
||||
@@ -134,9 +135,9 @@ namespace Spacegroup {
|
||||
* Get Symmetry operator for transformation around the given
|
||||
* reference point `ref` in fractional coordinates
|
||||
*/
|
||||
export function getSymmetryOperatorRef(spacegroup: Spacegroup, index: number, i: number, j: number, k: number, ref: Vec3) {
|
||||
const operator = setOperatorMatrixRef(spacegroup, index, i, j, k, ref, Mat4.zero());
|
||||
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k), index);
|
||||
export function getSymmetryOperatorRef(spacegroup: Spacegroup, spgrOp: number, i: number, j: number, k: number, ref: Vec3) {
|
||||
const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
|
||||
return SymmetryOperator.create(`${spgrOp + 1}_${5 + i}${5 + j}${5 + k}`, operator, { hkl: Vec3.create(i, j, k), spgrOp });
|
||||
}
|
||||
|
||||
function getOperatorMatrix(ids: number[]) {
|
||||
|
||||
@@ -11,12 +11,14 @@ import { defaults } from '../../mol-util';
|
||||
interface SymmetryOperator {
|
||||
readonly name: string,
|
||||
|
||||
readonly assembly: {
|
||||
readonly assembly?: {
|
||||
/** pointer to `pdbx_struct_assembly.id` or empty string */
|
||||
readonly id: string
|
||||
readonly id: string,
|
||||
/** pointers to `pdbx_struct_oper_list.id` or empty list */
|
||||
readonly operList: string[]
|
||||
}
|
||||
readonly operList: string[],
|
||||
/** (arbitrary) unique id of the operator to be used in suffix */
|
||||
readonly operId: number
|
||||
},
|
||||
|
||||
/** pointer to `struct_ncs_oper.id` or empty string */
|
||||
readonly ncsId: string,
|
||||
@@ -29,22 +31,52 @@ interface SymmetryOperator {
|
||||
// cache the inverse of the transform
|
||||
readonly inverse: Mat4,
|
||||
// optimize the identity case
|
||||
readonly isIdentity: boolean
|
||||
readonly isIdentity: boolean,
|
||||
|
||||
/**
|
||||
* Suffix based on operator type.
|
||||
* - Assembly: _assembly.operId
|
||||
* - Crytal: -op_ijk
|
||||
* - ncs: _ncsId
|
||||
*/
|
||||
readonly suffix: string
|
||||
}
|
||||
|
||||
namespace SymmetryOperator {
|
||||
export const DefaultName = '1_555'
|
||||
export const Default: SymmetryOperator = create(DefaultName, Mat4.identity(), { id: '', operList: [] });
|
||||
export const Default: SymmetryOperator = create(DefaultName, Mat4.identity());
|
||||
|
||||
export const RotationTranslationEpsilon = 0.005;
|
||||
|
||||
export function create(name: string, matrix: Mat4, assembly: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3, spgrOp?: number): SymmetryOperator {
|
||||
export type CreateInfo = { assembly?: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3, spgrOp?: number }
|
||||
export function create(name: string, matrix: Mat4, info?: CreateInfo): SymmetryOperator {
|
||||
let { assembly, ncsId, hkl, spgrOp } = info || { };
|
||||
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, spgrOp, ncsId };
|
||||
spgrOp = defaults(spgrOp, -1);
|
||||
ncsId = ncsId || '';
|
||||
const suffix = getSuffix(info);
|
||||
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId, suffix };
|
||||
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, spgrOp, ncsId };
|
||||
return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, spgrOp, ncsId, suffix };
|
||||
}
|
||||
|
||||
function getSuffix(info?: CreateInfo) {
|
||||
if (!info) return '';
|
||||
|
||||
if (info.assembly) {
|
||||
return `_${info.assembly.operId}`;
|
||||
}
|
||||
|
||||
if (typeof info.spgrOp !== 'undefined' && typeof info.hkl !== 'undefined' && info.spgrOp !== -1) {
|
||||
const [i, j, k] = info.hkl;
|
||||
return `-${info.spgrOp + 1}_${5 + i}${5 + j}${5 + k}`
|
||||
}
|
||||
|
||||
if (info.ncsId) {
|
||||
return `_${info.ncsId}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
export function checkIfRotationAndTranslation(rot: Mat3, offset: Vec3) {
|
||||
@@ -66,7 +98,7 @@ namespace SymmetryOperator {
|
||||
}
|
||||
}
|
||||
Mat4.setTranslation(t, offset);
|
||||
return create(name, t, { id: '', operList: [] }, ncsId);
|
||||
return create(name, t, { ncsId });
|
||||
}
|
||||
|
||||
const _q1 = Quat.identity(), _q2 = Quat.zero(), _q3 = Quat.zero(), _axis = Vec3.zero();
|
||||
@@ -114,7 +146,7 @@ namespace SymmetryOperator {
|
||||
*/
|
||||
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, second.spgrOp);
|
||||
return create(second.name, matrix, second);
|
||||
}
|
||||
|
||||
export interface CoordinateMapper<T extends number> { (index: T, slot: Vec3): Vec3 }
|
||||
|
||||
@@ -254,6 +254,31 @@ namespace Mat4 {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function extractRotation(out: Mat4, mat: Mat4) {
|
||||
const scaleX = 1 / Math.sqrt(mat[0] * mat[0] + mat[1] * mat[1] + mat[2] * mat[2]);
|
||||
const scaleY = 1 / Math.sqrt(mat[4] * mat[4] + mat[5] * mat[5] + mat[6] * mat[6]);
|
||||
const scaleZ = 1 / Math.sqrt(mat[8] * mat[8] + mat[9] * mat[9] + mat[10] * mat[10]);
|
||||
|
||||
out[0] = mat[0] * scaleX;
|
||||
out[1] = mat[1] * scaleX;
|
||||
out[2] = mat[2] * scaleX;
|
||||
out[3] = 0;
|
||||
out[4] = mat[4] * scaleY;
|
||||
out[5] = mat[5] * scaleY;
|
||||
out[6] = mat[6] * scaleY;
|
||||
out[7] = 0;
|
||||
out[8] = mat[8] * scaleZ;
|
||||
out[9] = mat[9] * scaleZ;
|
||||
out[10] = mat[10] * scaleZ;
|
||||
out[11] = 0;
|
||||
out[12] = 0;
|
||||
out[13] = 0;
|
||||
out[14] = 0;
|
||||
out[15] = 1;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export function transpose(out: Mat4, a: Mat4) {
|
||||
// If we are transposing ourselves we can skip a few steps but have to cache some values
|
||||
if (out === a) {
|
||||
|
||||
@@ -546,6 +546,8 @@ namespace Vec3 {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)}]`;
|
||||
}
|
||||
|
||||
export const origin: ReadonlyVec3 = Vec3.create(0, 0, 0)
|
||||
|
||||
export const unit: ReadonlyVec3 = Vec3.create(1, 1, 1)
|
||||
export const negUnit: ReadonlyVec3 = Vec3.create(-1, -1, -1)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace PrincipalAxes {
|
||||
|
||||
export function calculateMomentsAxes(positions: NumberArray): Axes3D {
|
||||
if (positions.length === 3) {
|
||||
return Axes3D.create(Vec3.fromArray(Vec3(), positions, 0), Vec3(), Vec3(), Vec3())
|
||||
return Axes3D.create(Vec3.fromArray(Vec3(), positions, 0), Vec3.create(1, 0, 0), Vec3.create(0, 1, 0), Vec3.create(0, 1, 0))
|
||||
}
|
||||
|
||||
const points = Matrix.fromArray(positions, 3, positions.length / 3)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { Column, Table } from '../../../mol-data/db';
|
||||
import { Interval, Segmentation } from '../../../mol-data/int';
|
||||
import UUID from '../../../mol-util/uuid';
|
||||
import { ElementIndex } from '../../../mol-model/structure';
|
||||
import { ElementIndex, ChainIndex } from '../../../mol-model/structure';
|
||||
import { Model } from '../../../mol-model/structure/model/model';
|
||||
import { AtomicConformation, AtomicData, AtomicHierarchy, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../../../mol-model/structure/model/properties/atomic';
|
||||
import { getAtomicIndex } from '../../../mol-model/structure/model/properties/utils/atomic-index';
|
||||
@@ -16,6 +16,12 @@ import { ElementSymbol } from '../../../mol-model/structure/model/types';
|
||||
import { Entities } from '../../../mol-model/structure/model/properties/common';
|
||||
import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived';
|
||||
import { AtomSite } from './schema';
|
||||
import { ModelFormat } from '../format';
|
||||
import { SymmetryOperator } from '../../../mol-math/geometry';
|
||||
import { MmcifFormat } from '../mmcif';
|
||||
import { AtomSiteOperatorMappingSchema } from '../../../mol-model/structure/export/categories/atom_site_operator_mapping';
|
||||
import { toDatabase } from '../../../mol-io/reader/cif/schema';
|
||||
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
|
||||
function findHierarchyOffsets(atom_site: AtomSite) {
|
||||
if (atom_site._rowCount === 0) return { residues: [], chains: [] };
|
||||
@@ -95,14 +101,70 @@ function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
|
||||
&& Table.areEqual(a.atoms, b.atoms)
|
||||
}
|
||||
|
||||
function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {
|
||||
function createChainOperatorMappingAndSubstituteNames(hierarchy: AtomicData, format: ModelFormat) {
|
||||
const mapping = new Map<ChainIndex, SymmetryOperator>();
|
||||
if (!MmcifFormat.is(format)) return mapping;
|
||||
|
||||
const { molstar_atom_site_operator_mapping: entries } = toDatabase(AtomSiteOperatorMappingSchema, format.data.frame);
|
||||
if (entries._rowCount === 0) return mapping;
|
||||
|
||||
const labelMap = new Map<string, { name: string, operator: SymmetryOperator }>();
|
||||
const authMap = new Map<string, string>();
|
||||
|
||||
for (let i = 0; i < entries._rowCount; i++) {
|
||||
const assembly: SymmetryOperator['assembly'] = entries.assembly_operator_id.valueKind(i) === Column.ValueKind.Present
|
||||
? { id: entries.assembly_id.value(i), operList: [], operId: entries.assembly_operator_id.value(i) }
|
||||
: void 0;
|
||||
|
||||
const operator = SymmetryOperator.create(entries.operator_name.value(i), Mat4.identity(), {
|
||||
assembly,
|
||||
spgrOp: entries.symmetry_operator_index.valueKind(i) === Column.ValueKind.Present ? entries.symmetry_operator_index.value(i) : void 0,
|
||||
hkl: Vec3.ofArray(entries.symmetry_hkl.value(i)),
|
||||
ncsId: entries.ncs_id.value(i)
|
||||
});
|
||||
|
||||
const suffix = entries.suffix.value(i);
|
||||
const label = entries.label_asym_id.value(i);
|
||||
labelMap.set(`${label}${suffix}`, { name: label, operator });
|
||||
const auth = entries.auth_asym_id.value(i);
|
||||
authMap.set(`${auth}${suffix}`, auth);
|
||||
}
|
||||
|
||||
const { label_asym_id, auth_asym_id } = hierarchy.chains;
|
||||
const mappedLabel: string[] = new Array(label_asym_id.rowCount);
|
||||
const mappedAuth: string[] = new Array(label_asym_id.rowCount);
|
||||
|
||||
for (let i = 0 as ChainIndex; i < label_asym_id.rowCount; i++) {
|
||||
const label = label_asym_id.value(i), auth = auth_asym_id.value(i);
|
||||
if (!labelMap.has(label)) {
|
||||
mappedLabel[i] = label;
|
||||
mappedAuth[i] = auth;
|
||||
continue;
|
||||
}
|
||||
|
||||
const { name, operator } = labelMap.get(label)!;
|
||||
mapping.set(i, operator);
|
||||
|
||||
mappedLabel[i] = name;
|
||||
mappedAuth[i] = authMap.get(auth) || auth;
|
||||
}
|
||||
|
||||
hierarchy.chains.label_asym_id = Column.ofArray({ array: mappedLabel, valueKind: hierarchy.chains.label_asym_id.valueKind, schema: hierarchy.chains.label_asym_id.schema });
|
||||
hierarchy.chains.auth_asym_id = Column.ofArray({ array: mappedAuth, valueKind: hierarchy.chains.auth_asym_id.valueKind, schema: hierarchy.chains.auth_asym_id.schema });
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], format: ModelFormat, previous?: Model) {
|
||||
const hierarchyOffsets = findHierarchyOffsets(atom_site);
|
||||
const hierarchyData = createHierarchyData(atom_site, sourceIndex, hierarchyOffsets);
|
||||
const chainOperatorMapping = createChainOperatorMappingAndSubstituteNames(hierarchyData, format);
|
||||
|
||||
if (previous && isHierarchyDataEqual(previous.atomicHierarchy, hierarchyData)) {
|
||||
return {
|
||||
sameAsPrevious: true,
|
||||
hierarchy: previous.atomicHierarchy,
|
||||
chainOperatorMapping
|
||||
};
|
||||
}
|
||||
|
||||
@@ -114,11 +176,11 @@ function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, en
|
||||
const index = getAtomicIndex(hierarchyData, entities, hierarchySegments);
|
||||
const derived = getAtomicDerivedData(hierarchyData, index, chemicalComponentMap);
|
||||
const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, index, derived };
|
||||
return { sameAsPrevious: false, hierarchy };
|
||||
return { sameAsPrevious: false, hierarchy, chainOperatorMapping };
|
||||
}
|
||||
|
||||
export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {
|
||||
const { sameAsPrevious, hierarchy } = getAtomicHierarchy(atom_site, sourceIndex, entities, chemicalComponentMap, previous)
|
||||
export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], format: ModelFormat, previous?: Model) {
|
||||
const { sameAsPrevious, hierarchy, chainOperatorMapping } = getAtomicHierarchy(atom_site, sourceIndex, entities, chemicalComponentMap, format, previous)
|
||||
const conformation = getConformation(atom_site)
|
||||
return { sameAsPrevious, hierarchy, conformation };
|
||||
return { sameAsPrevious, hierarchy, conformation, chainOperatorMapping };
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export async function createModels(data: BasicData, format: ModelFormat, ctx: Ru
|
||||
/** Standard atomic model */
|
||||
function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, properties: Model['properties'], format: ModelFormat, previous?: Model): Model {
|
||||
|
||||
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, properties.chemicalComponentMap, previous);
|
||||
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, properties.chemicalComponentMap, format, previous);
|
||||
const modelNum = atom_site.pdbx_PDB_model_num.value(0)
|
||||
if (previous && atomic.sameAsPrevious) {
|
||||
return {
|
||||
@@ -75,6 +75,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
atomicHierarchy: atomic.hierarchy,
|
||||
atomicConformation: atomic.conformation,
|
||||
atomicRanges,
|
||||
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
|
||||
coarseHierarchy: coarse.hierarchy,
|
||||
coarseConformation: coarse.conformation,
|
||||
properties,
|
||||
@@ -86,7 +87,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
|
||||
/** Integrative model with atomic/coarse parts */
|
||||
function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Model['properties'], format: ModelFormat): Model {
|
||||
const atomic = getAtomicHierarchyAndConformation(ihm.atom_site, ihm.atom_site_sourceIndex, ihm.entities, properties.chemicalComponentMap);
|
||||
const atomic = getAtomicHierarchyAndConformation(ihm.atom_site, ihm.atom_site_sourceIndex, ihm.entities, properties.chemicalComponentMap, format);
|
||||
const coarse = getCoarse(ihm, properties);
|
||||
const sequence = getSequence(data, ihm.entities, atomic.hierarchy, coarse.hierarchy)
|
||||
const atomicRanges = getAtomicRanges(atomic.hierarchy, ihm.entities, atomic.conformation, sequence)
|
||||
@@ -113,6 +114,7 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
|
||||
atomicHierarchy: atomic.hierarchy,
|
||||
atomicConformation: atomic.conformation,
|
||||
atomicRanges,
|
||||
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
|
||||
coarseHierarchy: coarse.hierarchy,
|
||||
coarseConformation: coarse.conformation,
|
||||
properties,
|
||||
|
||||
@@ -117,7 +117,7 @@ function getAssemblyOperators(matrices: Matrices, operatorNames: string[][], sta
|
||||
Mat4.mul(m, m, matrices.get(op[i])!);
|
||||
}
|
||||
index++
|
||||
operators[operators.length] = SymmetryOperator.create(`ASM_${index}`, m, { id: assemblyId, operList: op });
|
||||
operators[operators.length] = SymmetryOperator.create(`ASM_${index}`, m, { assembly: { id: assemblyId, operId: index, operList: op } });
|
||||
}
|
||||
|
||||
return operators;
|
||||
|
||||
@@ -107,23 +107,25 @@ namespace CustomElementProperty {
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !!modelProperty.get(ctx.structure.models[0]).value,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? modelProperty.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (_, data: ThemeDataContext) => data.structure && data.structure.models[0].customProperties.reference(modelProperty.descriptor, false)
|
||||
detach: (data: ThemeDataContext) => data.structure && data.structure.models[0].customProperties.reference(modelProperty.descriptor, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createLabelProvider<T>(modelProperty: CustomModelProperty.Provider<{}, Value<T>>, getLabel: (p: T) => string | undefined) {
|
||||
return function(loci: Loci): string | undefined {
|
||||
if (loci.kind === 'element-loci') {
|
||||
const e = loci.elements[0];
|
||||
if (!e) return
|
||||
const data = modelProperty.get(e.unit.model).value
|
||||
const element = e.unit.elements[OrderedSet.start(e.indices)]
|
||||
const value = data?.get(element)
|
||||
if (value === undefined) return
|
||||
return getLabel(value);
|
||||
function createLabelProvider<T>(modelProperty: CustomModelProperty.Provider<{}, Value<T>>, getLabel: (p: T) => string | undefined): LociLabelProvider {
|
||||
return {
|
||||
label: (loci: Loci) => {
|
||||
if (loci.kind === 'element-loci') {
|
||||
const e = loci.elements[0];
|
||||
if (!e || !e.unit.model.customProperties.hasReference(modelProperty.descriptor)) return
|
||||
const data = modelProperty.get(e.unit.model).value
|
||||
const element = e.unit.elements[OrderedSet.start(e.indices)]
|
||||
const value = data?.get(element)
|
||||
if (value === undefined) return
|
||||
return getLabel(value);
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ namespace CustomModelProperty {
|
||||
attach: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<Params>> = {}, addRef) => {
|
||||
if (addRef) data.customProperties.reference(builder.descriptor, true);
|
||||
const property = get(data)
|
||||
const p = { ...property.props, ...props }
|
||||
const p = PD.merge(builder.defaultParams, property.props, props)
|
||||
if (property.data.value && PD.areEqual(builder.defaultParams, property.props, p)) return
|
||||
const value = await builder.obtain(ctx, data, p)
|
||||
data.customProperties.add(builder.descriptor);
|
||||
@@ -64,12 +64,13 @@ namespace CustomModelProperty {
|
||||
get: (data: Model) => get(data)?.data,
|
||||
set: (data: Model, props: Partial<PD.Values<Params>> = {}) => {
|
||||
const property = get(data)
|
||||
const p = { ...property.props, ...props }
|
||||
const p = PD.merge(builder.defaultParams, property.props, props)
|
||||
if (!PD.areEqual(builder.defaultParams, property.props, p)) {
|
||||
// this invalidates property.value
|
||||
set(data, p, undefined)
|
||||
}
|
||||
}
|
||||
},
|
||||
props: (data: Model) => get(data).props,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ namespace CustomProperty {
|
||||
readonly ref: (data: Data, add: boolean) => void
|
||||
readonly get: (data: Data) => ValueBox<Value | undefined>
|
||||
readonly set: (data: Data, props: PD.Values<Params>, value?: Value) => void
|
||||
readonly props: (data: Data) => PD.Values<Params>
|
||||
}
|
||||
|
||||
export class Registry<Data> {
|
||||
|
||||
@@ -54,8 +54,9 @@ namespace CustomStructureProperty {
|
||||
attach: async (ctx: CustomProperty.Context, data: Structure, props: Partial<PD.Values<Params>> = {}, addRef) => {
|
||||
if (addRef) data.customPropertyDescriptors.reference(builder.descriptor, true);
|
||||
if (builder.type === 'root') data = data.root
|
||||
const rootProps = get(data.root).props
|
||||
const property = get(data)
|
||||
const p = { ...property.props, ...props }
|
||||
const p = PD.merge(builder.defaultParams, rootProps, props)
|
||||
if (property.data.value && PD.areEqual(builder.defaultParams, property.props, p)) return
|
||||
const value = await builder.obtain(ctx, data, p)
|
||||
data.customPropertyDescriptors.add(builder.descriptor);
|
||||
@@ -66,12 +67,13 @@ namespace CustomStructureProperty {
|
||||
set: (data: Structure, props: Partial<PD.Values<Params>> = {}, value?: Value) => {
|
||||
if (builder.type === 'root') data = data.root
|
||||
const property = get(data)
|
||||
const p = { ...property.props, ...props }
|
||||
const p = PD.merge(builder.defaultParams, property.props, props)
|
||||
if (!PD.areEqual(builder.defaultParams, property.props, p)) {
|
||||
// this invalidates property.value
|
||||
set(data, p, value)
|
||||
}
|
||||
}
|
||||
},
|
||||
props: (data: Structure) => get(data).props,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,6 @@ export const InteractionsRepresentationProvider = StructureRepresentationProvide
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => InteractionsProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (_, data) => InteractionsProvider.ref(data, false)
|
||||
detach: (data) => InteractionsProvider.ref(data, false)
|
||||
}
|
||||
})
|
||||
@@ -5,7 +5,6 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ColorListName, ColorListOptionsScale } from '../../../mol-util/color/lists'
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
|
||||
import { Color, ColorScale } from '../../../mol-util/color'
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme'
|
||||
@@ -20,7 +19,7 @@ const DefaultColor = Color(0xFAFAFA)
|
||||
const Description = 'Assigns a color based on the relative accessible surface area of a residue.'
|
||||
|
||||
export const AccessibleSurfaceAreaColorThemeParams = {
|
||||
list: PD.ColorList<ColorListName>('rainbow', ColorListOptionsScale)
|
||||
list: PD.ColorList('rainbow', { presetKind: 'scale' })
|
||||
}
|
||||
export type AccessibleSurfaceAreaColorThemeParams = typeof AccessibleSurfaceAreaColorThemeParams
|
||||
export function getAccessibleSurfaceAreaColorThemeParams(ctx: ThemeDataContext) {
|
||||
@@ -30,7 +29,7 @@ export function AccessibleSurfaceAreaColorTheme(ctx: ThemeDataContext, props: PD
|
||||
let color: LocationColor
|
||||
|
||||
const scale = ColorScale.create({
|
||||
listOrName: props.list,
|
||||
listOrName: props.list.colors,
|
||||
minLabel: 'buried',
|
||||
maxLabel: 'exposed',
|
||||
domain: [0.0, 1.0]
|
||||
@@ -74,6 +73,6 @@ export const AccessibleSurfaceAreaColorThemeProvider: ColorTheme.Provider<Access
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AccessibleSurfaceAreaProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(AccessibleSurfaceAreaProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(AccessibleSurfaceAreaProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -116,6 +116,6 @@ export const InteractionTypeColorThemeProvider: ColorTheme.Provider<InteractionT
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? InteractionsProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(InteractionsProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(InteractionsProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import { Color, ColorScale } from '../../../mol-util/color';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { ColorListName, ColorListOptionsScale } from '../../../mol-util/color/lists';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { CrossLinkRestraintProvider, CrossLinkRestraint } from './property';
|
||||
@@ -18,7 +17,7 @@ const Description = 'Colors cross-links by the deviation of the observed distanc
|
||||
|
||||
export const CrossLinkColorThemeParams = {
|
||||
domain: PD.Interval([0.5, 1.5], { step: 0.01 }),
|
||||
list: PD.ColorList<ColorListName>('red-grey', ColorListOptionsScale),
|
||||
list: PD.ColorList('red-grey', { presetKind: 'scale' }),
|
||||
}
|
||||
export type CrossLinkColorThemeParams = typeof CrossLinkColorThemeParams
|
||||
export function getCrossLinkColorThemeParams(ctx: ThemeDataContext) {
|
||||
@@ -34,7 +33,7 @@ export function CrossLinkColorTheme(ctx: ThemeDataContext, props: PD.Values<Cros
|
||||
if (crossLinkRestraints) {
|
||||
scale = ColorScale.create({
|
||||
domain: props.domain,
|
||||
listOrName: props.list
|
||||
listOrName: props.list.colors
|
||||
})
|
||||
const scaleColor = scale.color
|
||||
|
||||
@@ -71,6 +70,6 @@ export const CrossLinkColorThemeProvider: ColorTheme.Provider<CrossLinkColorThem
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && CrossLinkRestraint.isApplicable(ctx.structure),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? CrossLinkRestraintProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(CrossLinkRestraintProvider.descriptor, false)
|
||||
}
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(CrossLinkRestraintProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -144,6 +144,6 @@ export const CrossLinkRestraintRepresentationProvider = StructureRepresentationP
|
||||
isApplicable: (structure: Structure) => CrossLinkRestraint.isApplicable(structure),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => CrossLinkRestraintProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (_, data) => CrossLinkRestraintProvider.ref(data, false)
|
||||
detach: (data) => CrossLinkRestraintProvider.ref(data, false)
|
||||
}
|
||||
})
|
||||
@@ -105,6 +105,6 @@ export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Param
|
||||
isApplicable: (ctx: ThemeDataContext) => StructureQualityReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(StructureQualityReportProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(StructureQualityReportProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { AssemblySymmetryQuery, AssemblySymmetryQueryVariables } from './graphql
|
||||
import query from './graphql/symmetry.gql';
|
||||
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition'
|
||||
import { CustomPropertyDescriptor, Structure } from '../../mol-model/structure';
|
||||
import { CustomPropertyDescriptor, Structure, Model } from '../../mol-model/structure';
|
||||
import { Database as _Database, Column } from '../../mol-data/db'
|
||||
import { GraphQLClient } from '../../mol-util/graphql-client';
|
||||
import { CustomProperty } from '../common/custom-property';
|
||||
@@ -26,6 +26,18 @@ const BiologicalAssemblyNames = new Set([
|
||||
'software_defined_assembly'
|
||||
])
|
||||
|
||||
export function isBiologicalAssembly(structure: Structure): boolean {
|
||||
if (!MmcifFormat.is(structure.models[0].sourceData)) return false
|
||||
const mmcif = structure.models[0].sourceData.data.db
|
||||
if (!mmcif.pdbx_struct_assembly.details.isDefined) return false
|
||||
const id = structure.units[0].conformation.operator.assembly?.id || ''
|
||||
if (id === '' || id === 'deposited') return true
|
||||
const indices = Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id)
|
||||
if (indices.length !== 1) return false
|
||||
const details = mmcif.pdbx_struct_assembly.details.value(indices[0])
|
||||
return BiologicalAssemblyNames.has(details)
|
||||
}
|
||||
|
||||
export namespace AssemblySymmetry {
|
||||
export enum Tag {
|
||||
Cluster = 'rcsb-assembly-symmetry-cluster',
|
||||
@@ -35,27 +47,19 @@ export namespace AssemblySymmetry {
|
||||
export const DefaultServerUrl = 'https://data-beta.rcsb.org/graphql'
|
||||
|
||||
export function isApplicable(structure?: Structure): boolean {
|
||||
// check if structure is from pdb entry
|
||||
if (!structure || structure.models.length !== 1 || !MmcifFormat.is(structure.models[0].sourceData) || (!structure.models[0].sourceData.data.db.database_2.database_id.isDefined &&
|
||||
structure.models[0].entryId.length !== 4)) return false
|
||||
|
||||
// check if assembly is 'biological'
|
||||
const mmcif = structure.models[0].sourceData.data.db
|
||||
if (!mmcif.pdbx_struct_assembly.details.isDefined) return false
|
||||
const id = structure.units[0].conformation.operator.assembly.id
|
||||
if (id === '' || id === 'deposited') return true
|
||||
const indices = Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id)
|
||||
if (indices.length !== 1) return false
|
||||
const details = mmcif.pdbx_struct_assembly.details.value(indices[0])
|
||||
return BiologicalAssemblyNames.has(details)
|
||||
return (
|
||||
!!structure && structure.models.length === 1 &&
|
||||
Model.isFromPdbArchive(structure.models[0]) &&
|
||||
isBiologicalAssembly(structure)
|
||||
)
|
||||
}
|
||||
|
||||
export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryProps): Promise<AssemblySymmetryValue> {
|
||||
export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<AssemblySymmetryDataValue> {
|
||||
if (!isApplicable(structure)) return []
|
||||
|
||||
const client = new GraphQLClient(props.serverUrl, ctx.fetch)
|
||||
const variables: AssemblySymmetryQueryVariables = {
|
||||
assembly_id: structure.units[0].conformation.operator.assembly.id || 'deposited',
|
||||
assembly_id: structure.units[0].conformation.operator.assembly?.id || 'deposited',
|
||||
entry_id: structure.units[0].model.entryId
|
||||
}
|
||||
const result = await client.request<AssemblySymmetryQuery>(ctx.runtime, query, variables)
|
||||
@@ -64,23 +68,23 @@ export namespace AssemblySymmetry {
|
||||
console.error('expected `rcsb_struct_symmetry` field')
|
||||
return []
|
||||
}
|
||||
return result.assembly.rcsb_struct_symmetry as AssemblySymmetryValue
|
||||
return result.assembly.rcsb_struct_symmetry as AssemblySymmetryDataValue
|
||||
}
|
||||
|
||||
export type RotationAxes = ReadonlyArray<{ order: number, start: ReadonlyVec3, end: ReadonlyVec3 }>
|
||||
export function isRotationAxes(x: AssemblySymmetryValue[0]['rotation_axes']): x is RotationAxes {
|
||||
export function isRotationAxes(x: AssemblySymmetryValue['rotation_axes']): x is RotationAxes {
|
||||
return !!x && x.length > 0
|
||||
}
|
||||
}
|
||||
|
||||
export function getSymmetrySelectParam(structure?: Structure) {
|
||||
const param = PD.Select<number>(-1, [[-1, 'No Symmetries']])
|
||||
const param = PD.Select<number>(0, [[0, 'First Symmetry']])
|
||||
if (structure) {
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(structure).value
|
||||
if (assemblySymmetry) {
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value
|
||||
if (assemblySymmetryData) {
|
||||
const options: [number, string][] = []
|
||||
for (let i = 0, il = assemblySymmetry.length; i < il; ++i) {
|
||||
const { symbol, kind } = assemblySymmetry[i]
|
||||
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
|
||||
const { symbol, kind } = assemblySymmetryData[i]
|
||||
if (symbol !== 'C1') {
|
||||
options.push([ i, `${i + 1}: ${symbol} ${kind}` ])
|
||||
}
|
||||
@@ -94,13 +98,46 @@ export function getSymmetrySelectParam(structure?: Structure) {
|
||||
return param
|
||||
}
|
||||
|
||||
export const AssemblySymmetryParams = {
|
||||
//
|
||||
|
||||
export const AssemblySymmetryDataParams = {
|
||||
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl, { description: 'GraphQL endpoint URL' })
|
||||
}
|
||||
export type AssemblySymmetryDataParams = typeof AssemblySymmetryDataParams
|
||||
export type AssemblySymmetryDataProps = PD.Values<AssemblySymmetryDataParams>
|
||||
|
||||
export type AssemblySymmetryDataValue = NonNullableArray<NonNullable<NonNullable<AssemblySymmetryQuery['assembly']>['rcsb_struct_symmetry']>>
|
||||
|
||||
export const AssemblySymmetryDataProvider: CustomStructureProperty.Provider<AssemblySymmetryDataParams, AssemblySymmetryDataValue> = CustomStructureProperty.createProvider({
|
||||
label: 'Assembly Symmetry Data',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'rcsb_struct_symmetry_data',
|
||||
// TODO `cifExport` and `symbol`
|
||||
}),
|
||||
type: 'root',
|
||||
defaultParams: AssemblySymmetryDataParams,
|
||||
getParams: (data: Structure) => AssemblySymmetryDataParams,
|
||||
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryDataProps>) => {
|
||||
const p = { ...PD.getDefaultValues(AssemblySymmetryDataParams), ...props }
|
||||
return await AssemblySymmetry.fetch(ctx, data, p)
|
||||
}
|
||||
})
|
||||
|
||||
//
|
||||
|
||||
function getAssemblySymmetryParams(data?: Structure) {
|
||||
return {
|
||||
... AssemblySymmetryDataParams,
|
||||
symmetryIndex: getSymmetrySelectParam(data)
|
||||
}
|
||||
}
|
||||
|
||||
export const AssemblySymmetryParams = getAssemblySymmetryParams()
|
||||
export type AssemblySymmetryParams = typeof AssemblySymmetryParams
|
||||
export type AssemblySymmetryProps = PD.Values<AssemblySymmetryParams>
|
||||
|
||||
export type AssemblySymmetryValue = NonNullableArray<NonNullable<NonNullable<AssemblySymmetryQuery['assembly']>['rcsb_struct_symmetry']>>
|
||||
export type AssemblySymmetryValue = AssemblySymmetryDataValue[0]
|
||||
|
||||
export const AssemblySymmetryProvider: CustomStructureProperty.Provider<AssemblySymmetryParams, AssemblySymmetryValue> = CustomStructureProperty.createProvider({
|
||||
label: 'Assembly Symmetry',
|
||||
@@ -110,10 +147,14 @@ export const AssemblySymmetryProvider: CustomStructureProperty.Provider<Assembly
|
||||
}),
|
||||
type: 'root',
|
||||
defaultParams: AssemblySymmetryParams,
|
||||
getParams: (data: Structure) => AssemblySymmetryParams,
|
||||
getParams: getAssemblySymmetryParams,
|
||||
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryProps>) => {
|
||||
const p = { ...PD.getDefaultValues(AssemblySymmetryParams), ...props }
|
||||
return await AssemblySymmetry.fetch(ctx, data, p)
|
||||
const p = { ...PD.getDefaultValues(getAssemblySymmetryParams(data)), ...props }
|
||||
await AssemblySymmetryDataProvider.attach(ctx, data, p)
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(data).value
|
||||
const assemblySymmetry = assemblySymmetryData?.[p.symmetryIndex]
|
||||
if (!assemblySymmetry) new Error(`No assembly symmetry found for index ${p.symmetryIndex}`)
|
||||
return assemblySymmetry
|
||||
}
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { AssemblySymmetryValue, getSymmetrySelectParam, AssemblySymmetryProvider, AssemblySymmetry } from '../assembly-symmetry';
|
||||
import { AssemblySymmetryValue, AssemblySymmetryProvider, AssemblySymmetry } from '../assembly-symmetry';
|
||||
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
|
||||
import { addCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
@@ -51,7 +51,6 @@ function axesColorHelp(value: { name: string, params: {} }) {
|
||||
const SharedParams = {
|
||||
...Mesh.Params,
|
||||
scale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
|
||||
symmetryIndex: getSymmetrySelectParam(),
|
||||
}
|
||||
|
||||
const AxesParams = {
|
||||
@@ -87,7 +86,7 @@ export type AssemblySymmetryProps = PD.Values<AssemblySymmetryParams>
|
||||
//
|
||||
|
||||
function getAssemblyName(s: Structure) {
|
||||
const { id } = s.units[0].conformation.operator.assembly
|
||||
const id = s.units[0].conformation.operator.assembly?.id || ''
|
||||
return isInteger(id) ? `Assembly ${id}` : id
|
||||
}
|
||||
|
||||
@@ -113,9 +112,9 @@ const getOrderPrimitive = memoize1((order: number): Primitive | undefined => {
|
||||
})
|
||||
|
||||
function getAxesMesh(data: AssemblySymmetryValue, props: PD.Values<AxesParams>, mesh?: Mesh) {
|
||||
const { symmetryIndex, scale } = props
|
||||
const { scale } = props
|
||||
|
||||
const { rotation_axes } = data[symmetryIndex]
|
||||
const { rotation_axes } = data
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
|
||||
|
||||
const { start, end } = rotation_axes[0]
|
||||
@@ -158,7 +157,7 @@ function getAxesShape(ctx: RuntimeContext, data: Structure, props: AssemblySymme
|
||||
const geo = getAxesMesh(assemblySymmetry, props, shape && shape.geometry);
|
||||
const getColor = (groupId: number) => {
|
||||
if (props.axesColor.name === 'byOrder') {
|
||||
const { rotation_axes } = assemblySymmetry[props.symmetryIndex]
|
||||
const { rotation_axes } = assemblySymmetry
|
||||
const order = rotation_axes![groupId]?.order
|
||||
if (order === 2) return OrderColors[2]
|
||||
else if (order === 3) return OrderColors[3]
|
||||
@@ -168,7 +167,7 @@ function getAxesShape(ctx: RuntimeContext, data: Structure, props: AssemblySymme
|
||||
}
|
||||
}
|
||||
const getLabel = (groupId: number) => {
|
||||
const { type, symbol, kind, rotation_axes } = assemblySymmetry[props.symmetryIndex]
|
||||
const { type, symbol, kind, rotation_axes } = assemblySymmetry
|
||||
const order = rotation_axes![groupId]?.order
|
||||
return [
|
||||
`<small>${data.model.entryId}</small>`,
|
||||
@@ -279,9 +278,9 @@ function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.Rota
|
||||
|
||||
function getCageMesh(data: Structure, props: PD.Values<CageParams>, mesh?: Mesh) {
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!
|
||||
const { symmetryIndex, scale } = props
|
||||
const { scale } = props
|
||||
|
||||
const { rotation_axes, symbol } = assemblySymmetry[symmetryIndex]
|
||||
const { rotation_axes, symbol } = assemblySymmetry
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
|
||||
|
||||
const cage = getSymbolCage(symbol)
|
||||
@@ -308,7 +307,7 @@ function getCageShape(ctx: RuntimeContext, data: Structure, props: AssemblySymme
|
||||
return props.cageColor
|
||||
}
|
||||
const getLabel = (groupId: number) => {
|
||||
const { type, symbol, kind } = assemblySymmetry[props.symmetryIndex]
|
||||
const { type, symbol, kind } = assemblySymmetry
|
||||
data.model.entryId
|
||||
return [
|
||||
`<small>${data.model.entryId}</small>`,
|
||||
|
||||
@@ -289,6 +289,6 @@ export const ClashesRepresentationProvider = StructureRepresentationProvider({
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0,
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => ClashesProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (_, data) => ClashesProvider.ref(data, false)
|
||||
detach: (data) => ClashesProvider.ref(data, false)
|
||||
}
|
||||
})
|
||||
@@ -7,7 +7,7 @@
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry, getSymmetrySelectParam } from '../assembly-symmetry';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry } from '../assembly-symmetry';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { Unit, StructureElement, StructureProperties } from '../../../mol-model/structure';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
@@ -32,13 +32,11 @@ function clusterMemberKey(asymId: string, operList: string[]) {
|
||||
}
|
||||
|
||||
export const AssemblySymmetryClusterColorThemeParams = {
|
||||
...getPaletteParams({ scaleList: 'red-yellow-blue' }),
|
||||
symmetryIndex: getSymmetrySelectParam(),
|
||||
...getPaletteParams({ colorList: 'red-yellow-blue' }),
|
||||
}
|
||||
export type AssemblySymmetryClusterColorThemeParams = typeof AssemblySymmetryClusterColorThemeParams
|
||||
export function getAssemblySymmetryClusterColorThemeParams(ctx: ThemeDataContext) {
|
||||
const params = PD.clone(AssemblySymmetryClusterColorThemeParams)
|
||||
params.symmetryIndex = getSymmetrySelectParam(ctx.structure)
|
||||
return params
|
||||
}
|
||||
|
||||
@@ -46,11 +44,10 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
|
||||
let color: LocationColor = () => DefaultColor
|
||||
let legend: ScaleLegend | TableLegend | undefined
|
||||
|
||||
const { symmetryIndex } = props
|
||||
const assemblySymmetry = ctx.structure && AssemblySymmetryProvider.get(ctx.structure)
|
||||
const contextHash = assemblySymmetry?.version
|
||||
|
||||
const clusters = assemblySymmetry?.value?.[symmetryIndex]?.clusters
|
||||
const clusters = assemblySymmetry?.value?.clusters
|
||||
|
||||
if (clusters?.length && ctx.structure) {
|
||||
const clusterByMember = new Map<string, number>()
|
||||
@@ -69,11 +66,12 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
|
||||
const palette = getPalette(clusters.length, props)
|
||||
legend = palette.legend
|
||||
|
||||
const _emptyList: any[] = [];
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
const { assembly } = location.unit.conformation.operator
|
||||
const asymId = getAsymId(location.unit)(location)
|
||||
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly.operList))
|
||||
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly?.operList || _emptyList))
|
||||
return cluster !== undefined ? palette.color(cluster) : DefaultColor
|
||||
}
|
||||
return DefaultColor
|
||||
@@ -101,6 +99,6 @@ export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<Asse
|
||||
isApplicable: (ctx: ThemeDataContext) => AssemblySymmetry.isApplicable(ctx.structure),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(AssemblySymmetryProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(AssemblySymmetryProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,6 @@ export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationRep
|
||||
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,6 @@ export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQua
|
||||
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,6 @@ export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}, Validati
|
||||
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
@@ -202,8 +202,7 @@ function createInterUnitClashes(structure: Structure, clashes: ValidationReport[
|
||||
const builder = new InterUnitGraph.Builder<Unit.Atomic, UnitIndex, InterUnitClashesProps>()
|
||||
const { a, b, edgeProps: { id, magnitude, distance } } = clashes
|
||||
|
||||
const pA = Vec3()
|
||||
const pB = Vec3()
|
||||
const pA = Vec3(), pB = Vec3()
|
||||
|
||||
Structure.eachUnitPair(structure, (unitA: Unit, unitB: Unit) => {
|
||||
const elementsA = unitA.elements
|
||||
@@ -248,6 +247,8 @@ function createIntraUnitClashes(unit: Unit.Atomic, clashes: ValidationReport['cl
|
||||
const magnitudes: number[] = []
|
||||
const distances: number[] = []
|
||||
|
||||
const pA = Vec3(), pB = Vec3()
|
||||
|
||||
const { elements } = unit
|
||||
const { a, b, edgeCount, edgeProps } = clashes
|
||||
|
||||
@@ -257,11 +258,17 @@ function createIntraUnitClashes(unit: Unit.Atomic, clashes: ValidationReport['cl
|
||||
let indexB = SortedArray.indexOf(elements, b[i])
|
||||
|
||||
if (indexA !== -1 && indexB !== -1) {
|
||||
aIndices.push(indexA as UnitIndex)
|
||||
bIndices.push(indexB as UnitIndex)
|
||||
ids.push(edgeProps.id[i])
|
||||
magnitudes.push(edgeProps.magnitude[i])
|
||||
distances.push(edgeProps.distance[i])
|
||||
unit.conformation.position(a[i], pA)
|
||||
unit.conformation.position(b[i], pB)
|
||||
|
||||
// check actual distance to avoid clashes between unrelated chain instances
|
||||
if (equalEps(edgeProps.distance[i], Vec3.distance(pA, pB), 0.1)) {
|
||||
aIndices.push(indexA as UnitIndex)
|
||||
bIndices.push(indexB as UnitIndex)
|
||||
ids.push(edgeProps.id[i])
|
||||
magnitudes.push(edgeProps.magnitude[i])
|
||||
distances.push(edgeProps.distance[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +286,6 @@ function createIntraUnitClashes(unit: Unit.Atomic, clashes: ValidationReport['cl
|
||||
}
|
||||
|
||||
function createClashes(structure: Structure, clashes: ValidationReport['clashes']): Clashes {
|
||||
|
||||
const intraUnit = IntMap.Mutable<IntraUnitClashes>()
|
||||
|
||||
for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
|
||||
|
||||
@@ -71,9 +71,9 @@ namespace Loci {
|
||||
export function getBundleBoundingSphere(bundle: Bundle<any>): Sphere3D {
|
||||
const spheres = bundle.loci.map(l => getBoundingSphere(l)).filter(s => !!s) as Sphere3D[]
|
||||
boundaryHelper.reset();
|
||||
for (const s of spheres) boundaryHelper.includeSphereStep(s.center, s.radius);
|
||||
for (const s of spheres) boundaryHelper.includePositionRadius(s.center, s.radius);
|
||||
boundaryHelper.finishedIncludeStep();
|
||||
for (const s of spheres) boundaryHelper.radiusSphereStep(s.center, s.radius);
|
||||
for (const s of spheres) boundaryHelper.radiusPositionRadius(s.center, s.radius);
|
||||
return boundaryHelper.getSphere();
|
||||
}
|
||||
|
||||
@@ -192,21 +192,6 @@ namespace Loci {
|
||||
? StructureElement.Loci.extendToWholeChains(loci)
|
||||
: loci
|
||||
},
|
||||
'elementInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToAllInstances(loci)
|
||||
: loci
|
||||
},
|
||||
'residueInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeResidues(loci, true))
|
||||
: loci
|
||||
},
|
||||
'chainInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeChains(loci))
|
||||
: loci
|
||||
},
|
||||
'entity': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToWholeEntities(loci)
|
||||
@@ -224,9 +209,25 @@ namespace Loci {
|
||||
? Shape.Loci(loci.shape)
|
||||
: loci
|
||||
},
|
||||
'elementInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToAllInstances(loci)
|
||||
: loci
|
||||
},
|
||||
'residueInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeResidues(loci, true))
|
||||
: loci
|
||||
},
|
||||
'chainInstances': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeChains(loci))
|
||||
: loci
|
||||
},
|
||||
}
|
||||
export type Granularity = keyof typeof Granularity
|
||||
export const GranularityOptions = ParamDefinition.objectToOptions(Granularity, k => {
|
||||
if (k.indexOf('Instances') > 0) return [stringToWords(k), 'With Symmetry'];
|
||||
switch (k) {
|
||||
case 'element': return'Atom/Coarse Element'
|
||||
case 'structure': return'Structure/Shape'
|
||||
|
||||
@@ -12,6 +12,13 @@ import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { CentroidHelper } from '../../mol-math/geometry/centroid-helper';
|
||||
import { GroupMapping } from '../../mol-geo/util';
|
||||
import { ShapeGroupSizeTheme } from '../../mol-theme/size/shape-group';
|
||||
import { ShapeGroupColorTheme } from '../../mol-theme/color/shape-group';
|
||||
import { Theme } from '../../mol-theme/theme';
|
||||
import { TransformData, createTransform as _createTransform } from '../../mol-geo/geometry/transform-data';
|
||||
import { createRenderObject as _createRenderObject, getNextMaterialId } from '../../mol-gl/render-object';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition'
|
||||
import { LocationIterator } from '../../mol-geo/util/location-iterator';
|
||||
|
||||
export interface Shape<G extends Geometry = Geometry> {
|
||||
/** A uuid to identify a shape object */
|
||||
@@ -49,6 +56,46 @@ export namespace Shape {
|
||||
}
|
||||
}
|
||||
|
||||
export function getTheme(shape: Shape): Theme {
|
||||
return {
|
||||
color: ShapeGroupColorTheme({ shape }, {}),
|
||||
size: ShapeGroupSizeTheme({ shape }, {})
|
||||
}
|
||||
}
|
||||
|
||||
export function groupIterator(shape: Shape): LocationIterator {
|
||||
const instanceCount = shape.transforms.length
|
||||
const location = ShapeGroup.Location(shape)
|
||||
const getLocation = (groupIndex: number, instanceIndex: number) => {
|
||||
location.group = groupIndex
|
||||
location.instance = instanceIndex
|
||||
return location
|
||||
}
|
||||
return LocationIterator(shape.groupCount, instanceCount, getLocation)
|
||||
}
|
||||
|
||||
export function createTransform(transforms: Mat4[], transformData?: TransformData) {
|
||||
const transformArray = transformData && transformData.aTransform.ref.value.length >= transforms.length * 16 ? transformData.aTransform.ref.value : new Float32Array(transforms.length * 16)
|
||||
for (let i = 0, il = transforms.length; i < il; ++i) {
|
||||
Mat4.toArray(transforms[i], transformArray, i * 16)
|
||||
}
|
||||
return _createTransform(transformArray, transforms.length, transformData)
|
||||
}
|
||||
|
||||
export function createRenderObject<G extends Geometry>(shape: Shape<G>, props: PD.Values<Geometry.Params<G>>) {
|
||||
props
|
||||
const theme = Shape.getTheme(shape)
|
||||
const utils = Geometry.getUtils(shape.geometry)
|
||||
|
||||
const materialId = getNextMaterialId()
|
||||
const locationIt = groupIterator(shape)
|
||||
const transform = Shape.createTransform(shape.transforms)
|
||||
const values = utils.createValues(shape.geometry, transform, locationIt, theme, props)
|
||||
const state = utils.createRenderableState(props)
|
||||
|
||||
return _createRenderObject(shape.geometry.kind, values, state, materialId)
|
||||
}
|
||||
|
||||
export interface Loci { readonly kind: 'shape-loci', readonly shape: Shape }
|
||||
export function Loci(shape: Shape): Loci { return { kind: 'shape-loci', shape } }
|
||||
export function isLoci(x: any): x is Loci { return !!x && x.kind === 'shape-loci' }
|
||||
|
||||
@@ -69,6 +69,6 @@ class CustomProperties {
|
||||
}
|
||||
|
||||
has(desc: CustomPropertyDescriptor<any>): boolean {
|
||||
return this._refs.has(desc);
|
||||
return this._set.has(desc);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,22 @@ import CifField = CifWriter.Field
|
||||
import CifCategory = CifWriter.Category
|
||||
import E = CifWriter.Encodings
|
||||
|
||||
const _label_asym_id = P.chain.label_asym_id;
|
||||
function atom_site_label_asym_id(e: StructureElement.Location) {
|
||||
const l = _label_asym_id(e);
|
||||
const suffix = e.unit.conformation.operator.suffix;
|
||||
if (!suffix) return l;
|
||||
return l + suffix;
|
||||
}
|
||||
|
||||
const _auth_asym_id = P.chain.auth_asym_id;
|
||||
function atom_site_auth_asym_id(e: StructureElement.Location) {
|
||||
const l = _auth_asym_id(e);
|
||||
const suffix = e.unit.conformation.operator.suffix;
|
||||
if (!suffix) return l;
|
||||
return l + suffix;
|
||||
}
|
||||
|
||||
const atom_site_fields = CifWriter.fields<StructureElement.Location, Structure>()
|
||||
.str('group_PDB', P.residue.group_PDB)
|
||||
.index('id')
|
||||
@@ -29,14 +45,14 @@ const atom_site_fields = CifWriter.fields<StructureElement.Location, Structure>(
|
||||
.str('label_alt_id', P.atom.label_alt_id)
|
||||
.str('pdbx_PDB_ins_code', P.residue.pdbx_PDB_ins_code)
|
||||
|
||||
.str('label_asym_id', P.chain.label_asym_id)
|
||||
.str('label_asym_id', atom_site_label_asym_id)
|
||||
.str('label_entity_id', P.chain.label_entity_id)
|
||||
|
||||
.float('Cartn_x', P.atom.x, { digitCount: 3, encoder: E.fixedPoint3 })
|
||||
.float('Cartn_y', P.atom.y, { digitCount: 3, encoder: E.fixedPoint3 })
|
||||
.float('Cartn_z', P.atom.z, { digitCount: 3, encoder: E.fixedPoint3 })
|
||||
.float('occupancy', P.atom.occupancy, { digitCount: 2, encoder: E.fixedPoint2 })
|
||||
.int('pdbx_formal_charge', P.atom.pdbx_formal_charge, {
|
||||
.int('pdbx_formal_charge', P.atom.pdbx_formal_charge, {
|
||||
encoder: E.deltaRLE,
|
||||
valueKind: (k, d) => k.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.valueKind(k.element)
|
||||
})
|
||||
@@ -44,12 +60,12 @@ const atom_site_fields = CifWriter.fields<StructureElement.Location, Structure>(
|
||||
.str('auth_atom_id', P.atom.auth_atom_id)
|
||||
.str('auth_comp_id', P.residue.auth_comp_id)
|
||||
.int('auth_seq_id', P.residue.auth_seq_id, { encoder: E.deltaRLE })
|
||||
.str('auth_asym_id', P.chain.auth_asym_id)
|
||||
.str('auth_asym_id', atom_site_auth_asym_id)
|
||||
|
||||
.int('pdbx_PDB_model_num', P.unit.model_num, { encoder: E.deltaRLE })
|
||||
.str('operator_name', P.unit.operator_name, {
|
||||
shouldInclude: structure => structure.units.some(u => !u.conformation.operator.isIdentity)
|
||||
})
|
||||
// .str('operator_name', P.unit.operator_name, {
|
||||
// shouldInclude: structure => structure.units.some(u => !u.conformation.operator.isIdentity)
|
||||
// })
|
||||
.getFields();
|
||||
|
||||
export const _atom_site: CifCategory<CifExportContext> = {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { CifExportContext } from '../mmcif';
|
||||
import { StructureElement, StructureProperties as P } from '../../structure';
|
||||
import Unit from '../../structure/unit';
|
||||
import { Segmentation } from '../../../../mol-data/int';
|
||||
import { CifWriter } from '../../../../mol-io/writer/cif';
|
||||
import { Column } from '../../../../mol-data/db';
|
||||
|
||||
export function atom_site_operator_mapping(encoder: CifWriter.Encoder, ctx: CifExportContext) {
|
||||
const entries = getEntries(ctx);
|
||||
if (entries.length === 0) return;
|
||||
encoder.writeCategory(Category, entries, { ignoreFilter: true });
|
||||
}
|
||||
|
||||
export const AtomSiteOperatorMappingSchema = {
|
||||
molstar_atom_site_operator_mapping: {
|
||||
label_asym_id: Column.Schema.Str(),
|
||||
auth_asym_id: Column.Schema.Str(),
|
||||
operator_name: Column.Schema.Str(),
|
||||
suffix: Column.Schema.Str(),
|
||||
|
||||
// assembly
|
||||
assembly_id: Column.Schema.Str(),
|
||||
assembly_operator_id: Column.Schema.Int(),
|
||||
|
||||
// symmetry
|
||||
symmetry_operator_index: Column.Schema.Int(),
|
||||
symmetry_hkl: Column.Schema.Vector(3),
|
||||
|
||||
// NCS
|
||||
ncs_id: Column.Schema.Str(),
|
||||
}
|
||||
}
|
||||
|
||||
const asmValueKind = (i: number, xs: Entry[]) => typeof xs[i].operator.assembly === 'undefined' ? Column.ValueKind.NotPresent : Column.ValueKind.Present;
|
||||
const symmetryValueKind = (i: number, xs: Entry[]) => xs[i].operator.spgrOp === -1 ? Column.ValueKind.NotPresent : Column.ValueKind.Present;
|
||||
|
||||
const Fields = CifWriter.fields<number, Entry[], keyof (typeof AtomSiteOperatorMappingSchema)['molstar_atom_site_operator_mapping']>()
|
||||
.str('label_asym_id', (i, xs) => xs[i].label_asym_id)
|
||||
.str('auth_asym_id', (i, xs) => xs[i].auth_asym_id)
|
||||
.str('operator_name', (i, xs) => xs[i].operator.name)
|
||||
.str('suffix', (i, xs) => xs[i].operator.suffix)
|
||||
// assembly
|
||||
// TODO: include oper list as well?
|
||||
.str('assembly_id', (i, xs) => xs[i].operator.assembly?.id || '', { valueKind: asmValueKind })
|
||||
.int('assembly_operator_id', (i, xs) => xs[i].operator.assembly?.operId || 0, { valueKind: asmValueKind })
|
||||
// symmetry
|
||||
.int('symmetry_operator_index', (i, xs) => xs[i].operator.spgrOp, { valueKind: symmetryValueKind })
|
||||
.vec('symmetry_hkl', [(i, xs) => xs[i].operator.hkl[0], (i, xs) => xs[i].operator.hkl[1], (i, xs) => xs[i].operator.hkl[2]], { valueKind: symmetryValueKind })
|
||||
// NCS
|
||||
.str('ncs_id', (i, xs) => xs[i].operator.ncsId || '', { valueKind: (i, xs) => !xs[i].operator.ncsId ? Column.ValueKind.NotPresent : Column.ValueKind.Present })
|
||||
.getFields()
|
||||
|
||||
const Category: CifWriter.Category<Entry[]> = {
|
||||
name: 'molstar_atom_site_operator_mapping',
|
||||
instance(entries: Entry[]) {
|
||||
return { fields: Fields, source: [{ data: entries, rowCount: entries.length }] };
|
||||
}
|
||||
}
|
||||
|
||||
interface Entry {
|
||||
label_asym_id: string,
|
||||
auth_asym_id: string,
|
||||
operator: SymmetryOperator
|
||||
}
|
||||
|
||||
function getEntries(ctx: CifExportContext) {
|
||||
const existing = new Set<string>();
|
||||
const entries: Entry[] = [];
|
||||
|
||||
for (const s of ctx.structures) {
|
||||
const l = StructureElement.Location.create(s);
|
||||
for (const unit of s.units) {
|
||||
const operator = unit.conformation.operator;
|
||||
if (!operator.suffix || unit.kind !== Unit.Kind.Atomic) continue;
|
||||
|
||||
l.unit = unit;
|
||||
|
||||
const { elements } = unit;
|
||||
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
|
||||
while (chainsIt.hasNext) {
|
||||
const chainSegment = chainsIt.move();
|
||||
l.element = elements[chainSegment.start];
|
||||
|
||||
const label_asym_id = P.chain.label_asym_id(l);
|
||||
const key = `${label_asym_id}${operator.suffix}`;
|
||||
|
||||
if (existing.has(key)) continue;
|
||||
existing.add(key);
|
||||
|
||||
const auth_asym_id = P.chain.label_asym_id(l);
|
||||
entries.push({ label_asym_id, auth_asym_id, operator });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
@@ -51,7 +51,7 @@ const struct_conf_fields: CifField[] = [
|
||||
CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('details', (i, data) => data[i].element.details || '', {
|
||||
valueKind: (i, d) => !!d[i].element.details ? Column.ValueKind.Present : Column.ValueKind.Unknown
|
||||
}),
|
||||
CifField.int<number, SSElement<SecondaryStructure.Helix>[]>('pdbx_PDB_helix_class', (i, data) => data[i].length)
|
||||
CifField.int<number, SSElement<SecondaryStructure.Helix>[]>('pdbx_PDB_helix_length', (i, data) => data[i].length)
|
||||
];
|
||||
|
||||
const struct_sheet_range_fields: CifField[] = [
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Model } from '../model';
|
||||
import { getUniqueEntityIndicesFromStructures, copy_mmCif_category } from './categories/utils';
|
||||
import { _struct_asym, _entity_poly, _entity_poly_seq } from './categories/sequence';
|
||||
import { CustomPropertyDescriptor } from '../common/custom-property';
|
||||
import { atom_site_operator_mapping } from './categories/atom_site_operator_mapping';
|
||||
|
||||
export interface CifExportContext {
|
||||
structures: Structure[],
|
||||
@@ -136,6 +137,10 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures:
|
||||
encoder.writeCategory(cat, ctx);
|
||||
}
|
||||
|
||||
if ((!_params.skipCategoryNames || !_params.skipCategoryNames.has('atom_site')) && encoder.isCategoryIncluded('atom_site')) {
|
||||
atom_site_operator_mapping(encoder, ctx);
|
||||
}
|
||||
|
||||
for (const customProp of models[0].customProperties.all) {
|
||||
encodeCustomProp(customProp, ctx, encoder, _params);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import { Task } from '../../../mol-task';
|
||||
import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair';
|
||||
import { createModels } from '../../../mol-model-formats/structure/basic/parser';
|
||||
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
import { ChainIndex } from './indexing';
|
||||
import { SymmetryOperator } from '../../../mol-math/geometry';
|
||||
|
||||
/**
|
||||
* Interface to the "source data" of the molecule.
|
||||
@@ -60,6 +62,7 @@ export interface Model extends Readonly<{
|
||||
atomicHierarchy: AtomicHierarchy,
|
||||
atomicConformation: AtomicConformation,
|
||||
atomicRanges: AtomicRanges,
|
||||
atomicChainOperatorMappinng: Map<ChainIndex, SymmetryOperator>,
|
||||
|
||||
properties: {
|
||||
/** map that holds details about unobserved or zero occurrence residues */
|
||||
@@ -182,6 +185,16 @@ export namespace Model {
|
||||
return false
|
||||
}
|
||||
|
||||
export function isFromEm(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false
|
||||
const { db } = model.sourceData.data
|
||||
for (let i = 0; i < db.exptl.method.rowCount; i++) {
|
||||
const v = db.exptl.method.value(i).toUpperCase()
|
||||
if (v.indexOf('MICROSCOPY') >= 0) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function isFromNmr(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false
|
||||
const { db } = model.sourceData.data
|
||||
@@ -194,21 +207,29 @@ export namespace Model {
|
||||
|
||||
export function hasXrayMap(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false
|
||||
// Check exprimental method to exclude models solved with
|
||||
// 'ELECTRON CRYSTALLOGRAPHY' which also have structure factors
|
||||
if (!isFromXray(model)) return false
|
||||
const { db } = model.sourceData.data
|
||||
return db.pdbx_database_status.status_code_sf.value(0) === 'REL'
|
||||
const { status_code_sf } = db.pdbx_database_status
|
||||
return status_code_sf.isDefined && status_code_sf.value(0) === 'REL'
|
||||
}
|
||||
|
||||
/**
|
||||
* Also checks for `content_type` of 'associated EM volume' to exclude cases
|
||||
* like 6TEK which are solved with 'X-RAY DIFFRACTION' but have an related
|
||||
* EMDB entry of type 'other EM volume'.
|
||||
*/
|
||||
export function hasEmMap(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false
|
||||
const { db } = model.sourceData.data
|
||||
let hasEmMap = false
|
||||
const { db_name, content_type } = db.pdbx_database_related
|
||||
for (let i = 0, il = db.pdbx_database_related._rowCount; i < il; ++i) {
|
||||
if (db.pdbx_database_related.db_name.value(i).toUpperCase() === 'EMDB') {
|
||||
hasEmMap = true
|
||||
break
|
||||
if (db_name.value(i).toUpperCase() === 'EMDB' && content_type.value(i) === 'associated EM volume') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return hasEmMap
|
||||
return false
|
||||
}
|
||||
|
||||
export function hasDensityMap(model: Model) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Model } from '../../model'
|
||||
import { Spacegroup } from '../../../../mol-math/geometry';
|
||||
import { Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry';
|
||||
import { radToDeg } from '../../../../mol-math/misc';
|
||||
|
||||
/** Determine an atom set and a list of operators that should be applied to that set */
|
||||
export interface OperatorGroup {
|
||||
@@ -67,6 +68,25 @@ namespace Symmetry {
|
||||
const symmetry = ModelSymmetry.Provider.get(model)
|
||||
return symmetry ? arrayFind(symmetry.assemblies, a => a.id.toLowerCase() === _id) : undefined;
|
||||
}
|
||||
|
||||
export function getUnitcellLabel(symmetry: Symmetry) {
|
||||
const { cell, name, num } = symmetry.spacegroup
|
||||
const { size, anglesInRadians } = cell
|
||||
const a = size[0].toFixed(2)
|
||||
const b = size[1].toFixed(2)
|
||||
const c = size[2].toFixed(2)
|
||||
const alpha = radToDeg(anglesInRadians[0]).toFixed(2)
|
||||
const beta = radToDeg(anglesInRadians[1]).toFixed(2)
|
||||
const gamma = radToDeg(anglesInRadians[2]).toFixed(2)
|
||||
const label: string[] = []
|
||||
// name
|
||||
label.push(`Unitcell <b>${name}</b> #${num}`)
|
||||
// sizes
|
||||
label.push(`${a}\u00D7${b}\u00D7${c} \u212B`)
|
||||
// angles
|
||||
label.push(`\u03b1=${alpha}\u00B0 \u03b2=${beta}\u00B0 \u03b3=${gamma}\u00B0`)
|
||||
return label.join(' | ')
|
||||
}
|
||||
}
|
||||
|
||||
export { Symmetry }
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2020 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>
|
||||
@@ -21,9 +21,9 @@ interface Location<U = Unit> {
|
||||
}
|
||||
|
||||
namespace Location {
|
||||
export function create<U extends Unit>(structure: Structure | undefined, unit?: U, element?: ElementIndex): Location<U> {
|
||||
export function create<U extends Unit>(structure?: Structure, unit?: U, element?: ElementIndex): Location<U> {
|
||||
return {
|
||||
kind: 'element-location',
|
||||
kind: 'element-location',
|
||||
structure: structure as any,
|
||||
unit: unit as any,
|
||||
element: element || (0 as ElementIndex)
|
||||
|
||||
@@ -485,7 +485,7 @@ export namespace Loci {
|
||||
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
|
||||
const eI = elements[OrderedSet.getAt(indices, i)];
|
||||
pos(eI, tempPosBoundary);
|
||||
boundaryHelper.includeSphereStep(tempPosBoundary, r(eI));
|
||||
boundaryHelper.includePositionRadius(tempPosBoundary, r(eI));
|
||||
}
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep();
|
||||
@@ -496,7 +496,7 @@ export namespace Loci {
|
||||
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
|
||||
const eI = elements[OrderedSet.getAt(indices, i)];
|
||||
pos(eI, tempPosBoundary);
|
||||
boundaryHelper.radiusSphereStep(tempPosBoundary, r(eI));
|
||||
boundaryHelper.radiusPositionRadius(tempPosBoundary, r(eI));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,9 +71,13 @@ export namespace Stats {
|
||||
Location.set(stats.firstUnitLoc, structure, unit, elements[OrderedSet.start(indices)])
|
||||
}
|
||||
} else if (size === 1) {
|
||||
stats.elementCount += 1
|
||||
if (stats.elementCount === 1) {
|
||||
Location.set(stats.firstElementLoc, structure, unit, elements[OrderedSet.start(indices)])
|
||||
if (Unit.Traits.is(unit.traits, Unit.Trait.MultiChain)) {
|
||||
return
|
||||
} else {
|
||||
stats.elementCount += 1
|
||||
if (stats.elementCount === 1) {
|
||||
Location.set(stats.firstElementLoc, structure, unit, elements[OrderedSet.start(indices)])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (Unit.isAtomic(unit)) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import Unit from './unit'
|
||||
import { VdwRadius } from '../model/properties/atomic';
|
||||
import { SecondaryStructureType } from '../model/types';
|
||||
import { SecondaryStructureProvider } from '../../../mol-model-props/computed/secondary-structure';
|
||||
import { SymmetryOperator } from '../../../mol-math/geometry';
|
||||
|
||||
function p<T>(p: StructureElement.Property<T>) { return p; }
|
||||
|
||||
@@ -167,6 +168,7 @@ const entity = {
|
||||
pdbx_ec: p(l => l.unit.model.entities.data.pdbx_ec.value(eK(l)))
|
||||
}
|
||||
|
||||
const _emptyList: any[] = [];
|
||||
const unit = {
|
||||
id: p(l => l.unit.id),
|
||||
chainGroupId: p(l => l.unit.chainGroupId),
|
||||
@@ -180,8 +182,8 @@ const unit = {
|
||||
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),
|
||||
pdbx_struct_assembly_id: p(l => l.unit.conformation.operator.assembly?.id || SymmetryOperator.DefaultName),
|
||||
pdbx_struct_oper_list_ids: p(l => l.unit.conformation.operator.assembly?.operList || _emptyList),
|
||||
struct_ncs_oper_id: p(l => l.unit.conformation.operator.ncsId),
|
||||
}
|
||||
|
||||
|
||||
@@ -205,7 +205,13 @@ class Structure {
|
||||
}
|
||||
|
||||
get coordinateSystem() {
|
||||
return this._props.coordinateSystem;
|
||||
// TODO: do not use SymmetryOperator for this?
|
||||
// TODO: figure out a good way to compose this
|
||||
return this.parent?.coordinateSystem || this._props.coordinateSystem;
|
||||
}
|
||||
|
||||
set coordinateSystem(op: SymmetryOperator) {
|
||||
this._props.coordinateSystem = op;
|
||||
}
|
||||
|
||||
get label() {
|
||||
@@ -635,11 +641,13 @@ namespace Structure {
|
||||
*/
|
||||
export function ofModel(model: Model): Structure {
|
||||
const chains = model.atomicHierarchy.chainAtomSegments;
|
||||
const { index } = model.atomicHierarchy
|
||||
const { auth_asym_id } = model.atomicHierarchy.chains
|
||||
const { index } = model.atomicHierarchy;
|
||||
const { auth_asym_id } = model.atomicHierarchy.chains;
|
||||
const { atomicChainOperatorMappinng } = model;
|
||||
const builder = new StructureBuilder({ label: model.label });
|
||||
|
||||
for (let c = 0 as ChainIndex; c < chains.count; c++) {
|
||||
const operator = atomicChainOperatorMappinng.get(c) || SymmetryOperator.Default;
|
||||
const start = chains.offsets[c];
|
||||
|
||||
// set to true for chains that consist of "single atom residues",
|
||||
@@ -655,11 +663,15 @@ namespace Structure {
|
||||
singleAtomResidues = true
|
||||
const e1 = index.getEntityFromChain(c);
|
||||
const e2 = index.getEntityFromChain(c + 1 as ChainIndex);
|
||||
if (e1 !== e2) break
|
||||
if (e1 !== e2) break;
|
||||
|
||||
const a1 = auth_asym_id.value(c);
|
||||
const a2 = auth_asym_id.value(c + 1);
|
||||
if (a1 !== a2) break
|
||||
if (a1 !== a2) break;
|
||||
|
||||
const op1 = atomicChainOperatorMappinng.get(c);
|
||||
const op2 = atomicChainOperatorMappinng.get(c + 1 as ChainIndex);
|
||||
if (op1 !== op2) break;
|
||||
|
||||
multiChain = true
|
||||
c++;
|
||||
@@ -668,12 +680,12 @@ namespace Structure {
|
||||
const elements = SortedArray.ofBounds(start as ElementIndex, chains.offsets[c + 1] as ElementIndex);
|
||||
|
||||
if (singleAtomResidues) {
|
||||
partitionAtomicUnitByAtom(model, elements, builder, multiChain);
|
||||
partitionAtomicUnitByAtom(model, elements, builder, multiChain, operator);
|
||||
} 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, multiChain);
|
||||
partitionAtomicUnitByResidue(model, elements, builder, multiChain, operator);
|
||||
} else {
|
||||
builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements, multiChain ? Unit.Trait.MultiChain : Unit.Trait.None);
|
||||
builder.addUnit(Unit.Kind.Atomic, model, operator, elements, multiChain ? Unit.Trait.MultiChain : Unit.Trait.None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,7 +707,7 @@ namespace Structure {
|
||||
return model.entities.data.type.value(e) === 'water';
|
||||
}
|
||||
|
||||
function partitionAtomicUnitByAtom(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean) {
|
||||
function partitionAtomicUnitByAtom(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean, operator: SymmetryOperator) {
|
||||
const { x, y, z } = model.atomicConformation;
|
||||
const position = { x, y, z, indices }
|
||||
const lookup = GridLookup3D(position, getBoundary(position), 8192);
|
||||
@@ -710,13 +722,13 @@ namespace Structure {
|
||||
for (let j = 0, _j = count[i]; j < _j; j++) {
|
||||
set[j] = indices[array[start + j]];
|
||||
}
|
||||
builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(set), traits);
|
||||
builder.addUnit(Unit.Kind.Atomic, model, operator, SortedArray.ofSortedArray(set), traits);
|
||||
}
|
||||
builder.endChainGroup();
|
||||
}
|
||||
|
||||
// keeps atoms of residues together
|
||||
function partitionAtomicUnitByResidue(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean) {
|
||||
function partitionAtomicUnitByResidue(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean, operator: SymmetryOperator) {
|
||||
const { residueAtomSegments } = model.atomicHierarchy
|
||||
|
||||
const startIndices: number[] = []
|
||||
@@ -749,7 +761,7 @@ namespace Structure {
|
||||
set[set.length] = l;
|
||||
}
|
||||
}
|
||||
builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(new Int32Array(set)), traits);
|
||||
builder.addUnit(Unit.Kind.Atomic, model, operator, SortedArray.ofSortedArray(new Int32Array(set)), traits);
|
||||
}
|
||||
builder.endChainGroup();
|
||||
}
|
||||
@@ -769,12 +781,12 @@ namespace Structure {
|
||||
const units: Unit[] = [];
|
||||
for (const u of s.units) {
|
||||
const old = u.conformation.operator;
|
||||
const op = SymmetryOperator.create(old.name, transform, old.assembly, old.ncsId, old.hkl);
|
||||
const op = SymmetryOperator.create(old.name, transform, old);
|
||||
units.push(u.applyOperator(u.id, op));
|
||||
}
|
||||
|
||||
const cs = s.coordinateSystem;
|
||||
const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs.assembly, cs.ncsId, cs.hkl), cs);
|
||||
const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs), cs);
|
||||
return new Structure(units, { parent: s, coordinateSystem: newCS });
|
||||
}
|
||||
|
||||
@@ -1002,14 +1014,17 @@ namespace Structure {
|
||||
|
||||
//
|
||||
|
||||
const DefaultSizeThresholds = {
|
||||
export const DefaultSizeThresholds = {
|
||||
smallResidueCount: 10,
|
||||
mediumResidueCount: 1500,
|
||||
largeResidueCount: 12000,
|
||||
mediumResidueCount: 3000,
|
||||
/** large ribosomes like 4UG0 should still be `large` */
|
||||
largeResidueCount: 20000,
|
||||
highSymmetryUnitCount: 10,
|
||||
fiberResidueCount: 15
|
||||
fiberResidueCount: 15,
|
||||
|
||||
residueCountFactor: 1
|
||||
}
|
||||
type SizeThresholds = typeof DefaultSizeThresholds
|
||||
export type SizeThresholds = typeof DefaultSizeThresholds
|
||||
|
||||
function getPolymerSymmetryGroups(structure: Structure) {
|
||||
return structure.unitSymmetryGroups.filter(ug => ug.units[0].polymerElements.length > 0)
|
||||
@@ -1030,7 +1045,7 @@ namespace Structure {
|
||||
function hasHighSymmetry(structure: Structure, thresholds: SizeThresholds) {
|
||||
const polymerSymmetryGroups = getPolymerSymmetryGroups(structure)
|
||||
return (
|
||||
polymerSymmetryGroups.length > 1 &&
|
||||
polymerSymmetryGroups.length >= 1 &&
|
||||
polymerSymmetryGroups[0].units.length > thresholds.highSymmetryUnitCount
|
||||
)
|
||||
}
|
||||
@@ -1038,8 +1053,8 @@ namespace Structure {
|
||||
export enum Size { Small, Medium, Large, Huge, Gigantic }
|
||||
|
||||
export function getSize(structure: Structure, thresholds: Partial<SizeThresholds> = {}): Size {
|
||||
const t = { ...DefaultSizeThresholds, thresholds }
|
||||
if (structure.polymerResidueCount >= t.largeResidueCount) {
|
||||
const t = { ...DefaultSizeThresholds, ...thresholds }
|
||||
if (structure.polymerResidueCount >= t.largeResidueCount * t.residueCountFactor) {
|
||||
if (hasHighSymmetry(structure, t)) {
|
||||
return Size.Huge
|
||||
} else {
|
||||
@@ -1047,9 +1062,9 @@ namespace Structure {
|
||||
}
|
||||
} else if (isFiberLike(structure, t)) {
|
||||
return Size.Small
|
||||
} else if (structure.polymerResidueCount < t.smallResidueCount) {
|
||||
} else if (structure.polymerResidueCount < t.smallResidueCount * t.residueCountFactor) {
|
||||
return Size.Small
|
||||
} else if (structure.polymerResidueCount < t.mediumResidueCount) {
|
||||
} else if (structure.polymerResidueCount < t.mediumResidueCount * t.residueCountFactor) {
|
||||
return Size.Medium
|
||||
} else {
|
||||
return Size.Large
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace StructureSymmetry {
|
||||
const assembly = Symmetry.findAssembly(models[0], asmName);
|
||||
if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`);
|
||||
|
||||
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] })
|
||||
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } })
|
||||
const assembler = Structure.Builder({ coordinateSystem, label: structure.label });
|
||||
|
||||
const queryCtx = new QueryContext(structure);
|
||||
@@ -57,7 +57,7 @@ namespace StructureSymmetry {
|
||||
if (models.length !== 1) throw new Error('Can only build symmetry assemblies from structures based on 1 model.');
|
||||
|
||||
const modelCenter = Vec3()
|
||||
const assembler = Structure.Builder({ label: structure.label });
|
||||
const assembler = Structure.Builder({ label: structure.label, representativeModel: models[0] });
|
||||
|
||||
const queryCtx = new QueryContext(structure);
|
||||
|
||||
@@ -150,7 +150,12 @@ function getOperatorsForIndex(symmetry: Symmetry, index: number, i: number, j: n
|
||||
for (let u = 0, ul = ncsOperators.length; u < ul; ++u) {
|
||||
const ncsOp = ncsOperators![u]
|
||||
const matrix = Mat4.mul(Mat4(), symOp.matrix, ncsOp.matrix)
|
||||
const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, symOp.assembly, ncsOp.ncsId, symOp.hkl, symOp.spgrOp);
|
||||
const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, {
|
||||
assembly: symOp.assembly,
|
||||
ncsId: ncsOp.ncsId,
|
||||
hkl: symOp.hkl,
|
||||
spgrOp: symOp.spgrOp
|
||||
});
|
||||
operators.push(operator)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -19,7 +19,7 @@ export * from './bonds/inter-compute'
|
||||
namespace Bond {
|
||||
export interface Location<U extends Unit = Unit> {
|
||||
readonly kind: 'bond-location',
|
||||
|
||||
|
||||
aStructure: Structure,
|
||||
aUnit: U,
|
||||
/** Index into aUnit.elements */
|
||||
|
||||
@@ -1,41 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2020 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 { ElementSymbol } from '../../../model/types';
|
||||
|
||||
export interface BondComputationProps {
|
||||
/**
|
||||
* Experimental covalent hydrogen bond lengths
|
||||
*
|
||||
* C-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rCH)
|
||||
* - Average 1.091 (+/- 0.017)
|
||||
* - Min 0.931
|
||||
* - Max 1.140
|
||||
*
|
||||
* N-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rNH)
|
||||
* - Average 1.009 (+/- 0.043)
|
||||
* - Min 0.836
|
||||
* - Max 1.090
|
||||
*
|
||||
* O-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rOH)
|
||||
* - Average 0.967 (+/- 0.022)
|
||||
* - Min 0.912
|
||||
* - Max 1.033
|
||||
*
|
||||
* S-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rSH)
|
||||
* - Average 1.345 (+/- 0.020)
|
||||
* - Min 1.322
|
||||
* - Max 1.400
|
||||
*/
|
||||
maxCovalentHydrogenBondingLength: number,
|
||||
forceCompute: boolean
|
||||
noCompute: boolean
|
||||
}
|
||||
export const DefaultBondComputationProps: BondComputationProps = {
|
||||
maxCovalentHydrogenBondingLength: 1.45,
|
||||
forceCompute: false,
|
||||
noCompute: false
|
||||
}
|
||||
@@ -43,11 +19,48 @@ export const DefaultBondComputationProps: BondComputationProps = {
|
||||
// H,D,T are all mapped to H
|
||||
const __ElementIndex: { [e: string]: number | undefined } = { 'H': 0, 'h': 0, 'D': 0, 'd': 0, 'T': 0, 't': 0, 'He': 2, 'HE': 2, 'he': 2, 'Li': 3, 'LI': 3, 'li': 3, 'Be': 4, 'BE': 4, 'be': 4, 'B': 5, 'b': 5, 'C': 6, 'c': 6, 'N': 7, 'n': 7, 'O': 8, 'o': 8, 'F': 9, 'f': 9, 'Ne': 10, 'NE': 10, 'ne': 10, 'Na': 11, 'NA': 11, 'na': 11, 'Mg': 12, 'MG': 12, 'mg': 12, 'Al': 13, 'AL': 13, 'al': 13, 'Si': 14, 'SI': 14, 'si': 14, 'P': 15, 'p': 15, 'S': 16, 's': 16, 'Cl': 17, 'CL': 17, 'cl': 17, 'Ar': 18, 'AR': 18, 'ar': 18, 'K': 19, 'k': 19, 'Ca': 20, 'CA': 20, 'ca': 20, 'Sc': 21, 'SC': 21, 'sc': 21, 'Ti': 22, 'TI': 22, 'ti': 22, 'V': 23, 'v': 23, 'Cr': 24, 'CR': 24, 'cr': 24, 'Mn': 25, 'MN': 25, 'mn': 25, 'Fe': 26, 'FE': 26, 'fe': 26, 'Co': 27, 'CO': 27, 'co': 27, 'Ni': 28, 'NI': 28, 'ni': 28, 'Cu': 29, 'CU': 29, 'cu': 29, 'Zn': 30, 'ZN': 30, 'zn': 30, 'Ga': 31, 'GA': 31, 'ga': 31, 'Ge': 32, 'GE': 32, 'ge': 32, 'As': 33, 'AS': 33, 'as': 33, 'Se': 34, 'SE': 34, 'se': 34, 'Br': 35, 'BR': 35, 'br': 35, 'Kr': 36, 'KR': 36, 'kr': 36, 'Rb': 37, 'RB': 37, 'rb': 37, 'Sr': 38, 'SR': 38, 'sr': 38, 'Y': 39, 'y': 39, 'Zr': 40, 'ZR': 40, 'zr': 40, 'Nb': 41, 'NB': 41, 'nb': 41, 'Mo': 42, 'MO': 42, 'mo': 42, 'Tc': 43, 'TC': 43, 'tc': 43, 'Ru': 44, 'RU': 44, 'ru': 44, 'Rh': 45, 'RH': 45, 'rh': 45, 'Pd': 46, 'PD': 46, 'pd': 46, 'Ag': 47, 'AG': 47, 'ag': 47, 'Cd': 48, 'CD': 48, 'cd': 48, 'In': 49, 'IN': 49, 'in': 49, 'Sn': 50, 'SN': 50, 'sn': 50, 'Sb': 51, 'SB': 51, 'sb': 51, 'Te': 52, 'TE': 52, 'te': 52, 'I': 53, 'i': 53, 'Xe': 54, 'XE': 54, 'xe': 54, 'Cs': 55, 'CS': 55, 'cs': 55, 'Ba': 56, 'BA': 56, 'ba': 56, 'La': 57, 'LA': 57, 'la': 57, 'Ce': 58, 'CE': 58, 'ce': 58, 'Pr': 59, 'PR': 59, 'pr': 59, 'Nd': 60, 'ND': 60, 'nd': 60, 'Pm': 61, 'PM': 61, 'pm': 61, 'Sm': 62, 'SM': 62, 'sm': 62, 'Eu': 63, 'EU': 63, 'eu': 63, 'Gd': 64, 'GD': 64, 'gd': 64, 'Tb': 65, 'TB': 65, 'tb': 65, 'Dy': 66, 'DY': 66, 'dy': 66, 'Ho': 67, 'HO': 67, 'ho': 67, 'Er': 68, 'ER': 68, 'er': 68, 'Tm': 69, 'TM': 69, 'tm': 69, 'Yb': 70, 'YB': 70, 'yb': 70, 'Lu': 71, 'LU': 71, 'lu': 71, 'Hf': 72, 'HF': 72, 'hf': 72, 'Ta': 73, 'TA': 73, 'ta': 73, 'W': 74, 'w': 74, 'Re': 75, 'RE': 75, 're': 75, 'Os': 76, 'OS': 76, 'os': 76, 'Ir': 77, 'IR': 77, 'ir': 77, 'Pt': 78, 'PT': 78, 'pt': 78, 'Au': 79, 'AU': 79, 'au': 79, 'Hg': 80, 'HG': 80, 'hg': 80, 'Tl': 81, 'TL': 81, 'tl': 81, 'Pb': 82, 'PB': 82, 'pb': 82, 'Bi': 83, 'BI': 83, 'bi': 83, 'Po': 84, 'PO': 84, 'po': 84, 'At': 85, 'AT': 85, 'at': 85, 'Rn': 86, 'RN': 86, 'rn': 86, 'Fr': 87, 'FR': 87, 'fr': 87, 'Ra': 88, 'RA': 88, 'ra': 88, 'Ac': 89, 'AC': 89, 'ac': 89, 'Th': 90, 'TH': 90, 'th': 90, 'Pa': 91, 'PA': 91, 'pa': 91, 'U': 92, 'u': 92, 'Np': 93, 'NP': 93, 'np': 93, 'Pu': 94, 'PU': 94, 'pu': 94, 'Am': 95, 'AM': 95, 'am': 95, 'Cm': 96, 'CM': 96, 'cm': 96, 'Bk': 97, 'BK': 97, 'bk': 97, 'Cf': 98, 'CF': 98, 'cf': 98, 'Es': 99, 'ES': 99, 'es': 99, 'Fm': 100, 'FM': 100, 'fm': 100, 'Md': 101, 'MD': 101, 'md': 101, 'No': 102, 'NO': 102, 'no': 102, 'Lr': 103, 'LR': 103, 'lr': 103, 'Rf': 104, 'RF': 104, 'rf': 104, 'Db': 105, 'DB': 105, 'db': 105, 'Sg': 106, 'SG': 106, 'sg': 106, 'Bh': 107, 'BH': 107, 'bh': 107, 'Hs': 108, 'HS': 108, 'hs': 108, 'Mt': 109, 'MT': 109, 'mt': 109 };
|
||||
|
||||
// increased P (15) threshold from 1.9 to 2.0 (e.g. for G16 in 1O08)
|
||||
// Increased P (15) threshold from 1.9 to 2.0 (e.g. for G16 in 1O08)
|
||||
const __ElementBondThresholds: { [e: number]: number | undefined } = { 0: 1.42, 1: 1.42, 3: 2.7, 4: 2.7, 6: 1.75, 7: 1.6, 8: 1.52, 11: 2.7, 12: 2.7, 13: 2.7, 14: 1.9, 15: 2.0, 16: 1.9, 17: 1.8, 19: 2.7, 20: 2.7, 21: 2.7, 22: 2.7, 23: 2.7, 24: 2.7, 25: 2.7, 26: 2.7, 27: 2.7, 28: 2.7, 29: 2.7, 30: 2.7, 31: 2.7, 33: 2.68, 37: 2.7, 38: 2.7, 39: 2.7, 40: 2.7, 41: 2.7, 42: 2.7, 43: 2.7, 44: 2.7, 45: 2.7, 46: 2.7, 47: 2.7, 48: 2.7, 49: 2.7, 50: 2.7, 55: 2.7, 56: 2.7, 57: 2.7, 58: 2.7, 59: 2.7, 60: 2.7, 61: 2.7, 62: 2.7, 63: 2.7, 64: 2.7, 65: 2.7, 66: 2.7, 67: 2.7, 68: 2.7, 69: 2.7, 70: 2.7, 71: 2.7, 72: 2.7, 73: 2.7, 74: 2.7, 75: 2.7, 76: 2.7, 77: 2.7, 78: 2.7, 79: 2.7, 80: 2.7, 81: 2.7, 82: 2.7, 83: 2.7, 87: 2.7, 88: 2.7, 89: 2.7, 90: 2.7, 91: 2.7, 92: 2.7, 93: 2.7, 94: 2.7, 95: 2.7, 96: 2.7, 97: 2.7, 98: 2.7, 99: 2.7, 100: 2.7, 101: 2.7, 102: 2.7, 103: 2.7, 104: 2.7, 105: 2.7, 106: 2.7, 107: 2.7, 108: 2.7, 109: 2.88 };
|
||||
|
||||
// increased N-N (112) threshold from 1.55 to 1.6 (e.g. for 0QH in 1BMA)
|
||||
const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.3, 35: 1.3, 44: 1.05, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.6, 113: 1.59, 114: 1.36, 129: 1.45, 144: 1.6, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 };
|
||||
/**
|
||||
* Increased N-N (112) threshold from 1.55 to 1.6 (e.g. for 0QH in 1BMA)
|
||||
*
|
||||
* More experimentally observed bond length here (https://cccbdb.nist.gov/expbondlengths1x.asp)
|
||||
*
|
||||
* H-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rHH)
|
||||
* - 0.741
|
||||
*
|
||||
* Changed C-H (27) to 1.2
|
||||
* C-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rCH)
|
||||
* - Average 1.091 (+/- 0.017)
|
||||
* - Min 0.931
|
||||
* - Max 1.140
|
||||
*
|
||||
* Changed N-H (35) to 1.15
|
||||
* N-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rNH)
|
||||
* - Average 1.009 (+/- 0.043)
|
||||
* - Min 0.836
|
||||
* - Max 1.090
|
||||
*
|
||||
* Changed O-H (44) to 1.1
|
||||
* O-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rOH)
|
||||
* - Average 0.967 (+/- 0.022)
|
||||
* - Min 0.912
|
||||
* - Max 1.033
|
||||
*
|
||||
* Added P-H (135) as 1.47
|
||||
* P-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rPH)
|
||||
* - Average 1.423 ((+/- 0.007)
|
||||
* - Min 0.912
|
||||
* - Max 1.033
|
||||
*
|
||||
* Added S-H (152) as 1.45
|
||||
* S-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rSH)
|
||||
* - Average 1.345 (+/- 0.020)
|
||||
* - Min 1.322
|
||||
* - Max 1.400
|
||||
*/
|
||||
const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.2, 35: 1.15, 44: 1.1, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.6, 113: 1.59, 114: 1.36, 129: 1.45, 135: 1.47, 144: 1.6, 152: 1.45, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 };
|
||||
|
||||
const __DefaultBondingRadius = 2.001;
|
||||
|
||||
|
||||
@@ -125,25 +125,15 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
}
|
||||
|
||||
const beI = getElementIdx(type_symbolB.value(bI)!);
|
||||
const isMetal = metalA || MetalsSet.has(beI);
|
||||
|
||||
const isHb = isHydrogen(beI);
|
||||
if (isHa && isHb) continue;
|
||||
|
||||
const isMetal = (metalA || MetalsSet.has(beI)) && !(isHa || isHb);
|
||||
|
||||
const dist = Math.sqrt(squaredDistances[ni]);
|
||||
if (dist === 0) continue;
|
||||
|
||||
if (isHa || isHb) {
|
||||
if (dist < props.maxCovalentHydrogenBondingLength) {
|
||||
// covalent bonds involving a hydrogen are always of order 1
|
||||
builder.add(_aI, _bI, {
|
||||
order: 1,
|
||||
flag: BondType.Flag.Covalent | BondType.Flag.Computed
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const thresholdAB = getElementPairThreshold(aeI, beI);
|
||||
const pairingThreshold = thresholdAB > 0
|
||||
? thresholdAB
|
||||
|
||||
@@ -127,7 +127,11 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
|
||||
if (altA && altB && altA !== altB) continue;
|
||||
|
||||
const beI = getElementIdx(type_symbol.value(bI)!);
|
||||
const isMetal = metalA || MetalsSet.has(beI);
|
||||
|
||||
const isHb = isHydrogen(beI);
|
||||
if (isHa && isHb) continue;
|
||||
|
||||
const isMetal = (metalA || MetalsSet.has(beI)) && !(isHa || isHb);
|
||||
|
||||
const rbI = residueIndex[bI];
|
||||
// handle "component dictionary" bonds.
|
||||
@@ -147,22 +151,9 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
|
||||
continue;
|
||||
}
|
||||
|
||||
const isHb = isHydrogen(beI);
|
||||
if (isHa && isHb) continue;
|
||||
|
||||
const dist = Math.sqrt(squaredDistances[ni]);
|
||||
if (dist === 0) continue;
|
||||
|
||||
if (isHa || isHb) {
|
||||
if (dist < props.maxCovalentHydrogenBondingLength) {
|
||||
atomA[atomA.length] = _aI;
|
||||
atomB[atomB.length] = _bI;
|
||||
order[order.length] = 1; // covalent bonds involving hydrogen are always of order 1
|
||||
flags[flags.length] = BondType.Flag.Covalent | BondType.Flag.Computed;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const thresholdAB = getElementPairThreshold(aeI, beI);
|
||||
const pairingThreshold = thresholdAB > 0
|
||||
? thresholdAB
|
||||
|
||||
@@ -157,11 +157,11 @@ namespace UnitRings {
|
||||
/** Creates a mapping ResidueIndex -> list or rings that are on that residue and have one of the specified fingerprints. */
|
||||
export function byFingerprintAndResidue(rings: UnitRings, fingerprints: ReadonlyArray<UnitRing.Fingerprint>) {
|
||||
const map = new Map<ResidueIndex, Index[]>();
|
||||
|
||||
|
||||
for (let fI = 0, _fI = fingerprints.length; fI < _fI; fI++) {
|
||||
const fp = fingerprints[fI];
|
||||
addSingleResidueRings(rings, fp, map);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ function processResidue(state: State, start: number, end: number) {
|
||||
arraySetAdd(altLocs, altLoc);
|
||||
}
|
||||
arraySetRemove(altLocs, '');
|
||||
|
||||
|
||||
if (altLocs.length === 0) {
|
||||
resetState(state);
|
||||
for (let i = 0; i < state.count; i++) {
|
||||
|
||||
@@ -34,14 +34,14 @@ export function computeStructureBoundary(s: Structure): Boundary {
|
||||
Vec3.min(min, min, invariantBoundary.box.min);
|
||||
Vec3.max(max, max, invariantBoundary.box.max);
|
||||
|
||||
boundaryHelper.includeSphereStep(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
|
||||
boundaryHelper.includePositionRadius(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
|
||||
} else {
|
||||
Box3D.transform(tmpBox, invariantBoundary.box, o.matrix);
|
||||
Vec3.min(min, min, tmpBox.min);
|
||||
Vec3.max(max, max, tmpBox.max);
|
||||
|
||||
Sphere3D.transform(tmpSphere, invariantBoundary.sphere, o.matrix);
|
||||
boundaryHelper.includeSphereStep(tmpSphere.center, tmpSphere.radius);
|
||||
boundaryHelper.includePositionRadius(tmpSphere.center, tmpSphere.radius);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +53,10 @@ export function computeStructureBoundary(s: Structure): Boundary {
|
||||
const o = u.conformation.operator;
|
||||
|
||||
if (o.isIdentity) {
|
||||
boundaryHelper.radiusSphereStep(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
|
||||
boundaryHelper.radiusPositionRadius(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
|
||||
} else {
|
||||
Sphere3D.transform(tmpSphere, invariantBoundary.sphere, o.matrix);
|
||||
boundaryHelper.radiusSphereStep(tmpSphere.center, tmpSphere.radius);
|
||||
boundaryHelper.radiusPositionRadius(tmpSphere.center, tmpSphere.radius);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user