mirror of
https://github.com/molstar/molstar.git
synced 2026-06-06 22:54:22 +08:00
Compare commits
248 Commits
v0.5.0-dev
...
v0.6.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
964d56752e | ||
|
|
cb6a66eba5 | ||
|
|
94adf7259d | ||
|
|
3c831b549a | ||
|
|
7c50a5a456 | ||
|
|
f3b6703fd4 | ||
|
|
60f2716b5a | ||
|
|
d7007ef99d | ||
|
|
4aca41cdbb | ||
|
|
7e31fac99a | ||
|
|
3bcf1cb6b5 | ||
|
|
a29cc74304 | ||
|
|
95c43d0864 | ||
|
|
369b958335 | ||
|
|
9a17da8f65 | ||
|
|
0c4ba20d6e | ||
|
|
22936ff6c9 | ||
|
|
3369ad0bdc | ||
|
|
20853ef60b | ||
|
|
da0c66bd8e | ||
|
|
44664917ff | ||
|
|
ce26e002c7 | ||
|
|
345b9f546c | ||
|
|
f4ed5e301d | ||
|
|
4818f851b3 | ||
|
|
6f2c47c0ca | ||
|
|
5e6bc0f0df | ||
|
|
083daa0b76 | ||
|
|
139a10ba8c | ||
|
|
0b512487f5 | ||
|
|
a85adfdf1f | ||
|
|
0171b785af | ||
|
|
4c1c484af9 | ||
|
|
db7585b6b6 | ||
|
|
d263f6d929 | ||
|
|
034c28e487 | ||
|
|
9a88f57ce6 | ||
|
|
28415646a2 | ||
|
|
b03295ef81 | ||
|
|
7d3e849ff3 | ||
|
|
926f20a6a4 | ||
|
|
c12d577ce6 | ||
|
|
aa8d3a9841 | ||
|
|
f11e03eb85 | ||
|
|
ab996b5189 | ||
|
|
4c91e730a2 | ||
|
|
5ca5f44c61 | ||
|
|
f8a89b1c0d | ||
|
|
99dacef747 | ||
|
|
24fc37105e | ||
|
|
cbd493d834 | ||
|
|
53dbd1aedf | ||
|
|
6ab1af54aa | ||
|
|
615d9758bb | ||
|
|
341f9f8c91 | ||
|
|
ba7d4a5215 | ||
|
|
428187ad81 | ||
|
|
fe96172fcd | ||
|
|
8b13be698c | ||
|
|
5edc924e4f | ||
|
|
78c50a4339 | ||
|
|
607284585c | ||
|
|
f7af352f03 | ||
|
|
008ec2c88c | ||
|
|
c91074ad91 | ||
|
|
51fbb619e5 | ||
|
|
062c4b335b | ||
|
|
d4107e273d | ||
|
|
4dad67fc2e | ||
|
|
64b0713742 | ||
|
|
22c45e788e | ||
|
|
75de544669 | ||
|
|
768ec10809 | ||
|
|
4d5d9be399 | ||
|
|
7f4afc111e | ||
|
|
0365f883dd | ||
|
|
6f98549f31 | ||
|
|
77920818d9 | ||
|
|
0682d54ee2 | ||
|
|
fbcc3b2fa2 | ||
|
|
c3f47b2ecb | ||
|
|
8b9f59ac5a | ||
|
|
362dcabe5c | ||
|
|
6e205661bd | ||
|
|
c1a8627702 | ||
|
|
2c5253943c | ||
|
|
03668216fa | ||
|
|
37ef234803 | ||
|
|
d25aa97fca | ||
|
|
17dc973305 | ||
|
|
eed4e4a134 | ||
|
|
5fd28c6cad | ||
|
|
0f69ea197d | ||
|
|
e1a214e1a8 | ||
|
|
e7e14750cb | ||
|
|
cb83013967 | ||
|
|
0a84e1bb7b | ||
|
|
aee7f4988a | ||
|
|
e762c402fa | ||
|
|
4375cae70d | ||
|
|
90268a9041 | ||
|
|
a3ebc4df45 | ||
|
|
b2743c76e0 | ||
|
|
969a70d571 | ||
|
|
e6a538dbd7 | ||
|
|
9cffce55c9 | ||
|
|
7bc91d7e99 | ||
|
|
943f5feb59 | ||
|
|
dc72dbbc97 | ||
|
|
1513a1e2d2 | ||
|
|
82989cae57 | ||
|
|
74f8a5053e | ||
|
|
79f7ea209a | ||
|
|
ba65e35c51 | ||
|
|
5bc4e3ce3b | ||
|
|
1c7ac60c11 | ||
|
|
00cd54b69c | ||
|
|
90e3a3f0c7 | ||
|
|
5eef3fc42a | ||
|
|
83a8474731 | ||
|
|
a5e13c5152 | ||
|
|
b19f53d380 | ||
|
|
abadef1d2e | ||
|
|
a3d976c5b8 | ||
|
|
414b20f06d | ||
|
|
d03a442b6c | ||
|
|
e5acce03e5 | ||
|
|
fe714d4515 | ||
|
|
7959344bca | ||
|
|
b9582f171d | ||
|
|
410123d933 | ||
|
|
ae484443d8 | ||
|
|
9d97e56f03 | ||
|
|
01269ec1ef | ||
|
|
a204264bcc | ||
|
|
eb0a048926 | ||
|
|
ba1509b37a | ||
|
|
ad379e7a32 | ||
|
|
9d45beea3b | ||
|
|
b2d134aeb4 | ||
|
|
d500b8ea19 | ||
|
|
8b3df4b373 | ||
|
|
f01fc7c8ba | ||
|
|
0e6da0bffa | ||
|
|
eab8b1c2bf | ||
|
|
8b42a0fede | ||
|
|
4bbe078230 | ||
|
|
aabb931d27 | ||
|
|
b1c1b21975 | ||
|
|
7b5dc3ef96 | ||
|
|
1f831f90e0 | ||
|
|
ef2c1b51e9 | ||
|
|
ace73c041b | ||
|
|
3c70fe5303 | ||
|
|
137e23c025 | ||
|
|
969a19d515 | ||
|
|
7536abe96c | ||
|
|
8e20e163f9 | ||
|
|
adbe8e1f67 | ||
|
|
5db134f34f | ||
|
|
80cf7c1dd2 | ||
|
|
9e5cc184ed | ||
|
|
a5185b456c | ||
|
|
0615198ad2 | ||
|
|
6cd422f5b3 | ||
|
|
4416178d6f | ||
|
|
b8cef99dc1 | ||
|
|
4dcb68af33 | ||
|
|
b4c70ab14a | ||
|
|
8436e17af9 | ||
|
|
bda04cbdc7 | ||
|
|
7287947a55 | ||
|
|
fb5eb1e19c | ||
|
|
d735dcdc3e | ||
|
|
83d8a61400 | ||
|
|
b72f57c040 | ||
|
|
8a7ef1c704 | ||
|
|
8731bc13d1 | ||
|
|
d103cda0c8 | ||
|
|
ca4e864e46 | ||
|
|
0e25950d51 | ||
|
|
2c29a90b5e | ||
|
|
2900836208 | ||
|
|
928a1df525 | ||
|
|
16e0dff551 | ||
|
|
7e443d5c9b | ||
|
|
172ae17966 | ||
|
|
8cbf7b5d0a | ||
|
|
2c40b85880 | ||
|
|
ad388f23ae | ||
|
|
27b559a2d8 | ||
|
|
c6d19e14c5 | ||
|
|
b807dca2d8 | ||
|
|
2cae6e3f59 | ||
|
|
69c73f3dcd | ||
|
|
4456ab2cd5 | ||
|
|
bdda18de23 | ||
|
|
1b2c2f3d41 | ||
|
|
fd19d29ef6 | ||
|
|
1f0c0fd756 | ||
|
|
83698cc52d | ||
|
|
70b94deb20 | ||
|
|
2da3df6e4d | ||
|
|
509e633a69 | ||
|
|
17fac2b82a | ||
|
|
c543c4e10a | ||
|
|
10073800dc | ||
|
|
04c38250b4 | ||
|
|
5ccb329af1 | ||
|
|
3cecb53bc5 | ||
|
|
0daf431d68 | ||
|
|
3e0c4242ad | ||
|
|
17b775c377 | ||
|
|
4525b98288 | ||
|
|
755699d479 | ||
|
|
a84309b800 | ||
|
|
16863535b8 | ||
|
|
dd9773d72e | ||
|
|
ec45f6c0ee | ||
|
|
59235630bb | ||
|
|
11ed0ca89b | ||
|
|
188ea6e8e2 | ||
|
|
293b464d9f | ||
|
|
0e91aa521e | ||
|
|
5272593cc3 | ||
|
|
c5f336b0e4 | ||
|
|
b1a6fa3ffc | ||
|
|
fe47134934 | ||
|
|
131cc606f0 | ||
|
|
3681f01fad | ||
|
|
e966c112ab | ||
|
|
d7b232b00b | ||
|
|
7986509ad3 | ||
|
|
73dcf970f3 | ||
|
|
9377aa2d05 | ||
|
|
686fa5a5ed | ||
|
|
c946ae6eab | ||
|
|
9b2f1d9415 | ||
|
|
f6c28aa8e2 | ||
|
|
64c72aa6f5 | ||
|
|
0a22917773 | ||
|
|
3c4888e52b | ||
|
|
2c327cfdf6 | ||
|
|
307f2efc97 | ||
|
|
64c51f0d94 | ||
|
|
7a0b4c4d23 | ||
|
|
f45edbc4f0 | ||
|
|
65d3355b18 |
@@ -35,7 +35,7 @@
|
||||
"@typescript-eslint/quotes": [
|
||||
"warn",
|
||||
"single",
|
||||
{
|
||||
{
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
|
||||
14
.vscode/tasks.json
vendored
14
.vscode/tasks.json
vendored
@@ -9,6 +9,20 @@
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "build-tsc",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -31,6 +31,7 @@ The core of Mol* currently consists of these modules (see under `src/`):
|
||||
- `mol-state` State representation tree with state saving and automatic updates.
|
||||
- `mol-app` Components for builduing 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-util` Useful things that do not fit elsewhere.
|
||||
|
||||
@@ -61,6 +62,8 @@ This project builds on experience from previous solutions:
|
||||
### Build automatically on file save:
|
||||
npm run watch
|
||||
|
||||
If working on just the viewer, ``npm run watch-viewer`` will provide shorter compile times.
|
||||
|
||||
### Build with debug mode enabled:
|
||||
DEBUG=molstar npm run watch
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
schema: https://data-beta.rcsb.org/graphql
|
||||
documents: './src/mol-model-props/rcsb/graphql/symmetry.gql.ts'
|
||||
generates:
|
||||
'./src/mol-model-props/rcsb/graphql/types.d.ts':
|
||||
'./src/mol-model-props/rcsb/graphql/types.ts':
|
||||
plugins:
|
||||
- add: '/* eslint-disable */'
|
||||
- time
|
||||
|
||||
@@ -6,133 +6,64 @@ Model Server is a tool for preprocessing and querying macromolecular structure d
|
||||
Installing and Running
|
||||
=====================
|
||||
|
||||
Getting the code (use node 8+):
|
||||
Requires nodejs 8+.
|
||||
|
||||
## From GitHub
|
||||
|
||||
```
|
||||
git clone https://github.com/molstar/molstar
|
||||
npm install
|
||||
```
|
||||
|
||||
Customize configuration at ``src/server/model/config.ts`` to point to your data and which custom properties to include (see the [Custom Properties](#custom-properties) section). Alternatively, the config can be edited in the compiled version in ``build/node_modules/servers/model/config.js``.
|
||||
|
||||
Afterwards, build the project:
|
||||
Afterwards, build the project source:
|
||||
|
||||
```
|
||||
npm run build
|
||||
npm run build-tsc
|
||||
```
|
||||
|
||||
(or run watch mode for automatic rebuilds: ``npm run watch``)
|
||||
and run the server by
|
||||
|
||||
Running the server locally for testing:
|
||||
```
|
||||
npm run model-server
|
||||
```
|
||||
or
|
||||
```
|
||||
node build/node_modules/servers/model/server
|
||||
node lib/servers/model/server/server
|
||||
```
|
||||
|
||||
In production it is a good idea to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
## From NPM
|
||||
|
||||
```
|
||||
npm install molstar
|
||||
./model-server
|
||||
```
|
||||
|
||||
(or ``node node_modules\.bin\model-server`` in Windows).
|
||||
|
||||
The NPM package contains all the tools mentioned here as "binaries":
|
||||
|
||||
- ``model-server``
|
||||
- ``model-server-query``
|
||||
- ``model-server-preprocess``
|
||||
|
||||
|
||||
## Memory issues
|
||||
### Production use
|
||||
|
||||
In production it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
|
||||
|
||||
### Memory issues
|
||||
|
||||
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
|
||||
|
||||
Preprocessor
|
||||
============
|
||||
## Preprocessor
|
||||
|
||||
The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. See the [Custom Properties](#custom-properties) section for providing custom properties.
|
||||
The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. ``node lib/servers/model/preprocess`` or ``model-server-preprocess`` binary from the NPM package.
|
||||
|
||||
## Usage
|
||||
|
||||
The app works in two modes: single files and folders.
|
||||
## Local Mode
|
||||
|
||||
Single files:
|
||||
|
||||
```
|
||||
node build\node_modules\servers\model\preprocess -i input.cif [-oc output.cif] [-ob output.bcif] [--cfg config.json]
|
||||
```
|
||||
|
||||
Folder:
|
||||
```
|
||||
node build\node_modules\servers\model\preprocess -fin input_folder [-foc output_cif_folder] [-fob output_bcif_folder] [--cfg config.json]
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
The config speficies the maximum number of processes to use (in case of folder processing) and defines sources and parameters for custom properties.
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"numProcesses": 4,
|
||||
"customProperties": {
|
||||
"sources": [
|
||||
"./properties/pdbe"
|
||||
],
|
||||
"params": {
|
||||
"PDBe": {
|
||||
"UseFileSource": false,
|
||||
"API": {
|
||||
"residuewise_outlier_summary": "https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry",
|
||||
"preferred_assembly": "https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary",
|
||||
"struct_ref_domain": "https://www.ebi.ac.uk/pdbe/api/mappings/sequence_domains"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
The server can be run in local/file based mode using ``node lib/servers/model/query`` (``model-server-query`` binary from the NPM package).
|
||||
|
||||
Custom Properties
|
||||
=================
|
||||
|
||||
It is possible to provide property descriptors that transform data to internal representation and define how it should be exported into one or mode CIF categories. Examples of this are located in the ``mol-model-props`` module and are linked to the server in the config and ``servers/model/properties``.
|
||||
This feature is still in development.
|
||||
|
||||
Local Mode
|
||||
==========
|
||||
|
||||
The server can be run in local/file based mode:
|
||||
|
||||
```
|
||||
node build/node_modules/servers/model/server jobs.json
|
||||
```
|
||||
|
||||
where ``jobs.json`` is an array of
|
||||
|
||||
```ts
|
||||
type LocalInput = {
|
||||
input: string,
|
||||
output: string,
|
||||
query: QueryName,
|
||||
modelNums?: number[],
|
||||
params?: any,
|
||||
binary?: boolean
|
||||
}[]
|
||||
```
|
||||
|
||||
For example
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"input": "c:/test/quick/1tqn.cif",
|
||||
"output": "c:/test/quick/localapi/1tqn_full.cif",
|
||||
"query": "full"
|
||||
},
|
||||
{
|
||||
"input": "c:/test/quick/1tqn.cif",
|
||||
"output": "c:/test/quick/localapi/1tqn_full.bcif",
|
||||
"query": "full",
|
||||
"params": {}
|
||||
},
|
||||
{
|
||||
"input": "c:/test/quick/1cbs_updated.cif",
|
||||
"output": "c:/test/quick/localapi/1cbs_ligint.cif",
|
||||
"query": "residueInteraction",
|
||||
"params": {
|
||||
"atom_site": { "label_comp_id": "REA" }
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
It is possible to provide property descriptors that transform data to internal representation and define how it should be exported into one or mode CIF categories. Examples of this are located in the ``mol-model-props`` module and are linked to the server in the config and ``servers/model/properties``.
|
||||
@@ -7,107 +7,64 @@ It uses the text based CIF and BinaryCIF formats to deliver the data to the clie
|
||||
|
||||
For quick info about the benefits of using the server, check out the [examples](examples.md).
|
||||
|
||||
Installing the Server
|
||||
Installing and Running
|
||||
=====================
|
||||
|
||||
- Install [Node.js](https://nodejs.org/en/) (tested on Node 6.* and 7.*; x64 version is strongly preferred).
|
||||
- Get the code.
|
||||
- Prepare the data.
|
||||
- Run the server.
|
||||
Requires nodejs 8+.
|
||||
|
||||
Preparing the Data
|
||||
------------------
|
||||
## From GitHub
|
||||
|
||||
```
|
||||
git clone https://github.com/molstar/molstar
|
||||
npm install
|
||||
```
|
||||
|
||||
Afterwards, build the project source:
|
||||
|
||||
```
|
||||
npm run build-tsc
|
||||
```
|
||||
|
||||
and run the server by
|
||||
|
||||
```
|
||||
node lib/servers/volume/server
|
||||
```
|
||||
|
||||
## From NPM
|
||||
|
||||
```
|
||||
npm install molstar
|
||||
./volume-server
|
||||
```
|
||||
|
||||
(or ``node node_modules\.bin\volume-server`` in Windows).
|
||||
|
||||
The NPM package contains all the tools mentioned here as "binaries":
|
||||
|
||||
- ``volume-server``
|
||||
- ``volume-server-pack``
|
||||
- ``volume-server-query``
|
||||
|
||||
|
||||
### Production use
|
||||
|
||||
In production it is required to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
|
||||
|
||||
### Memory issues
|
||||
|
||||
Sometimes nodejs might run into problems with memory. This is usually resolved by adding the ``--max-old-space-size=8192`` parameter.
|
||||
|
||||
|
||||
## Preparing the Data
|
||||
|
||||
For the server to work, CCP4/MAP (models 0, 1, 2 are supported) input data need to be converted into a custom block format.
|
||||
To achieve this, use the ``pack`` application.
|
||||
To achieve this, use the ``pack`` application (``node lib/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
|
||||
|
||||
- To prepare data from x-ray based methods, use:
|
||||
## Local Mode
|
||||
|
||||
```
|
||||
node pack -xray main.ccp4 diff.ccp4 out.mdb
|
||||
```
|
||||
|
||||
- For EM data, use:
|
||||
|
||||
```
|
||||
node pack -em em.map out.mdb
|
||||
```
|
||||
|
||||
Running the Server
|
||||
------------------
|
||||
|
||||
- Install production dependencies:
|
||||
|
||||
```
|
||||
npm install --only=production
|
||||
```
|
||||
|
||||
- Update ``server-config.js`` to link to your data and optionally tweak the other parameters.
|
||||
|
||||
- Run it:
|
||||
|
||||
```
|
||||
node server
|
||||
```
|
||||
|
||||
In production it is a good idea to use a service that will keep the server running, such as [forever.js](https://github.com/foreverjs/forever).
|
||||
|
||||
### Local Mode
|
||||
|
||||
The program ``local`` in the build folder can be used to query the data without running a http server.
|
||||
|
||||
- ``node local`` prints the program usage.
|
||||
- ``node local jobs.json`` takes a list of jobs to execute in JSON format. A job entry is defined by this interface:
|
||||
|
||||
```TypeScript
|
||||
interface JobEntry {
|
||||
source: {
|
||||
filename: string,
|
||||
name: string,
|
||||
id: string
|
||||
},
|
||||
query: {
|
||||
kind: 'box' | 'cell',
|
||||
space?: 'fractional' | 'cartesian',
|
||||
bottomLeft?: number[],
|
||||
topRight?: number[],
|
||||
}
|
||||
params: {
|
||||
/** Determines the detail level as specified in server-config */
|
||||
detail?: number,
|
||||
/**
|
||||
* Determines the sampling level:
|
||||
* 1: Original data
|
||||
* 2: Downsampled by factor 1/2
|
||||
* ...
|
||||
* N: downsampled 1/2^(N-1)
|
||||
*/
|
||||
forcedSamplingLevel?: number,
|
||||
asBinary: boolean,
|
||||
},
|
||||
outputFolder: string
|
||||
}
|
||||
```
|
||||
|
||||
Example ``jobs.json`` file content:
|
||||
|
||||
```TypeScript
|
||||
[{
|
||||
source: {
|
||||
filename: `g:/test/mdb/emd-8116.mdb`,
|
||||
name: 'em',
|
||||
id: '8116',
|
||||
},
|
||||
query: {
|
||||
kind: 'cell'
|
||||
},
|
||||
params: {
|
||||
detail: 4,
|
||||
asBinary: true
|
||||
},
|
||||
outputFolder: 'g:/test/local-test'
|
||||
}]
|
||||
```
|
||||
The program ``lib/servers/volume/pack`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
|
||||
|
||||
## Navigating the Source Code
|
||||
|
||||
@@ -122,8 +79,8 @@ The source code is split into 2 mains parts: ``pack`` and ``server``:
|
||||
Consuming the Data
|
||||
==================
|
||||
|
||||
The data can be consumed in any (modern) browser using the [CIFTools.js library](https://github.com/dsehnal/CIFTools.js) (or any other piece of code that can read text or binary CIF).
|
||||
The data can be consumed in any (modern) browser using the [ciftools library](https://github.com/molstar/ciftools) (or any other piece of code that can read text or binary CIF).
|
||||
|
||||
The [Data Format](DataFormat.md) document gives a detailed description of the server response format.
|
||||
|
||||
As a reference/example of the server usage, please see the implementation in [LiteMol](https://github.com/dsehnal/LiteMol) ([CIF.ts + Data.ts](https://github.com/dsehnal/LiteMol/tree/master/src/lib/Core/Formats/Density), [UI](https://github.com/dsehnal/LiteMol/tree/master/src/Viewer/Extensions/DensityStreaming)) or in Mol*.
|
||||
As a reference/example of the server usage is available in Mol* ``mol-plugin`` module.
|
||||
2077
package-lock.json
generated
2077
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
69
package.json
69
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.5.0-dev.2",
|
||||
"version": "0.6.0-dev.3",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -14,17 +14,19 @@
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"test": "npm run lint && jest",
|
||||
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
|
||||
"build-tsc": "tsc",
|
||||
"build-tsc": "tsc --incremental",
|
||||
"build-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/",
|
||||
"build-webpack": "webpack --mode production",
|
||||
"watch": "concurrently --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack\"",
|
||||
"watch-tsc": "tsc -watch",
|
||||
"watch": "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-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 minimal",
|
||||
"watch-webpack": "webpack -w --mode development --display errors-only --info-verbosity verbose",
|
||||
"watch-webpack-viewer": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.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",
|
||||
"volume-server": "node lib/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
|
||||
"volume-server-test": "node lib/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
|
||||
"plugin-state": "node lib/servers/plugin-state/index.js",
|
||||
"preversion": "npm run test",
|
||||
"postversion": "git push && git push --tags",
|
||||
@@ -33,6 +35,14 @@
|
||||
"files": [
|
||||
"lib/"
|
||||
],
|
||||
"bin": {
|
||||
"model-server": "lib/servers/model/server.js",
|
||||
"model-server-query": "lib/servers/model/local.js",
|
||||
"model-server-preprocess": "lib/servers/model/preprocess.js",
|
||||
"volume-server": "lib/servers/volume/server.js",
|
||||
"volume-server-query": "lib/servers/volume/query.js",
|
||||
"volume-server-pack": "lib/servers/volume/pack.js"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ignoreRoot": [
|
||||
"./node_modules",
|
||||
@@ -64,17 +74,16 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^1.12.2",
|
||||
"@graphql-codegen/cli": "^1.12.2",
|
||||
"@graphql-codegen/time": "^1.12.2",
|
||||
"@graphql-codegen/typescript": "^1.12.2",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.12.2",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.12.2",
|
||||
"@graphql-codegen/typescript-operations": "^1.12.2",
|
||||
"@graphql-codegen/add": "^1.13.0",
|
||||
"@graphql-codegen/cli": "^1.13.0",
|
||||
"@graphql-codegen/time": "^1.13.0",
|
||||
"@graphql-codegen/typescript": "^1.13.0",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.13.0",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.13.0",
|
||||
"@graphql-codegen/typescript-operations": "^1.13.0",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@typescript-eslint/eslint-plugin": "^2.19.2",
|
||||
"@typescript-eslint/eslint-plugin-tslint": "^2.19.2",
|
||||
"@typescript-eslint/parser": "^2.19.2",
|
||||
"@typescript-eslint/eslint-plugin": "^2.23.0",
|
||||
"@typescript-eslint/parser": "^2.23.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"circular-dependency-plugin": "^5.2.0",
|
||||
"concurrently": "^5.1.0",
|
||||
@@ -82,7 +91,7 @@
|
||||
"css-loader": "^3.4.2",
|
||||
"eslint": "^6.8.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^5.0.2",
|
||||
"file-loader": "^5.1.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"http-server": "^0.12.1",
|
||||
"jest": "^25.1.0",
|
||||
@@ -93,22 +102,22 @@
|
||||
"raw-loader": "^4.0.0",
|
||||
"resolve-url-loader": "^3.1.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"simple-git": "^1.131.0",
|
||||
"simple-git": "^1.132.0",
|
||||
"style-loader": "^1.1.3",
|
||||
"ts-jest": "^25.2.0",
|
||||
"typescript": "^3.7.5",
|
||||
"webpack": "^4.41.5",
|
||||
"webpack-cli": "^3.3.10"
|
||||
"ts-jest": "^25.2.1",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.42.0",
|
||||
"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.2",
|
||||
"@types/jest": "^25.1.2",
|
||||
"@types/node": "^13.7.0",
|
||||
"@types/node-fetch": "^2.5.4",
|
||||
"@types/react": "^16.9.19",
|
||||
"@types/express": "^4.17.3",
|
||||
"@types/jest": "^25.1.4",
|
||||
"@types/node": "^13.9.0",
|
||||
"@types/node-fetch": "^2.5.5",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/swagger-ui-dist": "3.0.5",
|
||||
"argparse": "^1.0.10",
|
||||
@@ -117,12 +126,14 @@
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"graphql": "^14.6.0",
|
||||
"immer": "^6.0.1",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react": "^16.13.0",
|
||||
"react-dom": "^16.13.0",
|
||||
"rxjs": "^6.5.4",
|
||||
"swagger-ui-dist": "^3.25.0",
|
||||
"tslib": "^1.11.1",
|
||||
"util.promisify": "^1.0.1",
|
||||
"xhr2": "^0.2.0"
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin/state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin/state/transforms';
|
||||
import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transforms/representation';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { StateBuilder } from '../../mol-state';
|
||||
import Expression from '../../mol-script/language/expression';
|
||||
import { BuiltInColorThemeName } from '../../mol-theme/color';
|
||||
import { ColorTheme } from '../../mol-theme/color';
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
type SupportedFormats = 'cif' | 'pdb'
|
||||
|
||||
export namespace StateHelper {
|
||||
@@ -67,27 +67,30 @@ export namespace StateHelper {
|
||||
}
|
||||
|
||||
export function assemble(b: StateBuilder.To<PSO.Molecule.Model>, id?: string) {
|
||||
return b.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: id || 'deposited' }, { tags: 'asm' })
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: id || 'deposited' }
|
||||
}
|
||||
}
|
||||
return b.apply(StateTransforms.Model.StructureFromModel, props, { tags: 'asm' })
|
||||
}
|
||||
|
||||
export function visual(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'cartoon'), { tags: 'seq-visual' });
|
||||
createStructureRepresentationParams(ctx, void 0, { type: 'cartoon' }), { tags: 'seq-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick'), { tags: 'het-visual' });
|
||||
// visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
|
||||
// .apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
// StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick', { alpha: 0.51 }), { tags: 'water-visual' });
|
||||
createStructureRepresentationParams(ctx, void 0, { type: 'ball-and-stick' }), { tags: 'het-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
export function ballsAndSticks(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression, coloring?: BuiltInColorThemeName) {
|
||||
export function ballsAndSticks(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression, color?: ColorTheme.BuiltIn) {
|
||||
visualRoot
|
||||
.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick', void 0, coloring), { tags: 'het-visual' });
|
||||
createStructureRepresentationParams(ctx, void 0, { type: 'ball-and-stick', color }), { tags: 'het-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,11 @@
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/command';
|
||||
import { StateTransforms } from '../../mol-plugin/state/transforms';
|
||||
import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transforms/representation';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin/state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin/state/animation/built-in';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in';
|
||||
import { StateBuilder, StateTransform } from '../../mol-state';
|
||||
import { StripedResidues } from './coloring';
|
||||
import { StaticSuperpositionTestData, buildStaticSuperposition, dynamicSuperpositionTest } from './superposition';
|
||||
@@ -21,6 +20,7 @@ import { CustomToastMessage } from './controls';
|
||||
import { EmptyLoci } from '../../mol-model/loci';
|
||||
import { StructureSelection } from '../../mol-model/structure';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
|
||||
type SupportedFormats = 'cif' | 'pdb'
|
||||
@@ -47,8 +47,8 @@ class BasicWrapper {
|
||||
}
|
||||
});
|
||||
|
||||
this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(StripedResidues.propertyProvider.descriptor.name, StripedResidues.colorThemeProvider!);
|
||||
this.plugin.lociLabels.addProvider(StripedResidues.labelProvider!);
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!);
|
||||
this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
|
||||
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
|
||||
}
|
||||
|
||||
@@ -61,25 +61,31 @@ class BasicWrapper {
|
||||
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
return parsed
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
|
||||
.apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [StripedResidues.propertyProvider.descriptor.name], properties: {} }, { ref: 'props', state: { isGhost: false } })
|
||||
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' });
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { ref: 'asm' });
|
||||
}
|
||||
|
||||
private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: 'seq' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'cartoon'), { ref: 'seq-visual' });
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'cartoon' }), { ref: 'seq-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick'), { ref: 'het-visual' });
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'ball-and-stick' }), { ref: 'het-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick', { alpha: 0.51 }), { ref: 'water-visual' });
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'ball-and-stick', typeParams: { alpha: 0.51 } }), { ref: 'water-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'spheres' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'spacefill'), { ref: 'ihm-visual' });
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'spacefill' }), { ref: 'ihm-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
@@ -87,7 +93,7 @@ class BasicWrapper {
|
||||
async load({ url, format = 'cif', assemblyId = '' }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.dataState;
|
||||
const state = this.plugin.state.data;
|
||||
|
||||
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
|
||||
loadType = 'full';
|
||||
@@ -97,22 +103,29 @@ class BasicWrapper {
|
||||
|
||||
let tree: StateBuilder.Root;
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref });
|
||||
tree = state.build();
|
||||
this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId));
|
||||
} else {
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
|
||||
tree = state.build();
|
||||
tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
|
||||
tree.to('asm').update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
this.loadedParams = { url, format, assemblyId };
|
||||
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
PluginCommands.Camera.Reset(this.plugin, { });
|
||||
}
|
||||
|
||||
setBackground(color: number) {
|
||||
const renderer = this.plugin.canvas3d!.props.renderer;
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
}
|
||||
|
||||
toggleSpin() {
|
||||
@@ -120,8 +133,8 @@ class BasicWrapper {
|
||||
|
||||
const trackball = this.plugin.canvas3d.props.trackball;
|
||||
const spinning = trackball.spin;
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
if (!spinning) PluginCommands.Camera.Reset(this.plugin, { });
|
||||
}
|
||||
|
||||
animate = {
|
||||
@@ -137,68 +150,68 @@ class BasicWrapper {
|
||||
|
||||
coloring = {
|
||||
applyStripes: async () => {
|
||||
const state = this.plugin.state.dataState;
|
||||
const state = this.plugin.state.data;
|
||||
|
||||
const visuals = state.selectQ(q => q.ofTransformer(StateTransforms.Representation.StructureRepresentation3D));
|
||||
const tree = state.build();
|
||||
const colorTheme = { name: StripedResidues.propertyProvider.descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(StripedResidues.propertyProvider.descriptor.name).defaultValues };
|
||||
const colorTheme = { name: StripedResidues.propertyProvider.descriptor.name, params: this.plugin.representation.structure.themes.colorThemeRegistry.get(StripedResidues.propertyProvider.descriptor.name).defaultValues };
|
||||
|
||||
for (const v of visuals) {
|
||||
tree.to(v).update(old => ({ ...old, colorTheme }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
}
|
||||
}
|
||||
|
||||
interactivity = {
|
||||
highlightOn: () => {
|
||||
const seq_id = 7;
|
||||
const data = (this.plugin.state.dataState.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
const data = (this.plugin.state.data.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
const sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
|
||||
'residue-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), seq_id]),
|
||||
'group-by': Q.struct.atomProperty.macromolecular.residueKey()
|
||||
}), data);
|
||||
const loci = StructureSelection.toLociWithSourceUnits(sel);
|
||||
this.plugin.interactivity.lociHighlights.highlightOnly({ loci });
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci });
|
||||
},
|
||||
clearHighlight: () => {
|
||||
this.plugin.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
|
||||
}
|
||||
}
|
||||
|
||||
tests = {
|
||||
staticSuperposition: async () => {
|
||||
const state = this.plugin.state.dataState;
|
||||
const state = this.plugin.state.data;
|
||||
const tree = buildStaticSuperposition(this.plugin, StaticSuperpositionTestData);
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: StateTransform.RootRef });
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: StateTransform.RootRef });
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
},
|
||||
dynamicSuperposition: async () => {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.plugin.state.dataState, ref: StateTransform.RootRef });
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state: this.plugin.state.data, ref: StateTransform.RootRef });
|
||||
await dynamicSuperpositionTest(this.plugin, ['1tqn', '2hhb', '4hhb'], 'HEM');
|
||||
},
|
||||
toggleValidationTooltip: async () => {
|
||||
const state = this.plugin.state.behaviorState;
|
||||
const state = this.plugin.state.behaviors;
|
||||
const tree = state.build().to(PDBeStructureQualityReport.id).update(PDBeStructureQualityReport, p => ({ ...p, showTooltip: !p.showTooltip }));
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
},
|
||||
showToasts: () => {
|
||||
PluginCommands.Toast.Show.dispatch(this.plugin, {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Toast 1',
|
||||
message: 'This is an example text, timeout 3s',
|
||||
key: 'toast-1',
|
||||
timeoutMs: 3000
|
||||
});
|
||||
PluginCommands.Toast.Show.dispatch(this.plugin, {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Toast 2',
|
||||
message: CustomToastMessage,
|
||||
key: 'toast-2'
|
||||
});
|
||||
},
|
||||
hideToasts: () => {
|
||||
PluginCommands.Toast.Hide.dispatch(this.plugin, { key: 'toast-1' });
|
||||
PluginCommands.Toast.Hide.dispatch(this.plugin, { key: 'toast-2' });
|
||||
PluginCommands.Toast.Hide(this.plugin, { key: 'toast-1' });
|
||||
PluginCommands.Toast.Hide(this.plugin, { key: 'toast-2' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { StateHelper } from './helpers';
|
||||
import { PluginCommands } from '../../mol-plugin/command';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { StateSelection, StateBuilder } from '../../mol-state';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin/state/objects';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
import { StructureSelection, QueryContext } from '../../mol-model/structure';
|
||||
@@ -32,7 +32,7 @@ export type SuperpositionTestInput = {
|
||||
// }
|
||||
|
||||
export function buildStaticSuperposition(ctx: PluginContext, src: SuperpositionTestInput) {
|
||||
const b = ctx.state.dataState.build().toRoot();
|
||||
const b = ctx.state.data.build().toRoot();
|
||||
for (const s of src) {
|
||||
StateHelper.visual(ctx,
|
||||
StateHelper.transform(
|
||||
@@ -63,7 +63,7 @@ export const StaticSuperpositionTestData: SuperpositionTestInput = [
|
||||
];
|
||||
|
||||
export async function dynamicSuperpositionTest(ctx: PluginContext, src: string[], comp_id: string) {
|
||||
const state = ctx.state.dataState;
|
||||
const state = ctx.state.data;
|
||||
|
||||
const structures = state.build().toRoot();
|
||||
for (const s of src) {
|
||||
@@ -71,7 +71,7 @@ export async function dynamicSuperpositionTest(ctx: PluginContext, src: string[]
|
||||
StateHelper.getModel(StateHelper.download(structures, `https://www.ebi.ac.uk/pdbe/static/entry/${s}_updated.cif`), 'cif'));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(ctx, { state, tree: structures });
|
||||
await PluginCommands.State.Update(ctx, { state, tree: structures });
|
||||
|
||||
const pivot = MS.struct.filter.first([
|
||||
MS.struct.generator.atomGroups({
|
||||
@@ -99,7 +99,7 @@ export async function dynamicSuperpositionTest(ctx: PluginContext, src: string[]
|
||||
pivot, rest);
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(ctx, { state, tree: visuals });
|
||||
await PluginCommands.State.Update(ctx, { state, tree: visuals });
|
||||
}
|
||||
|
||||
function siteVisual(ctx: PluginContext, b: StateBuilder.To<PSO.Molecule.Structure>, pivot: Expression, rest: Expression) {
|
||||
|
||||
@@ -144,9 +144,9 @@ async function createBonds() {
|
||||
const comp_id: string[] = []
|
||||
const atom_id_1: string[] = []
|
||||
const atom_id_2: string[] = []
|
||||
const value_order: string[] = []
|
||||
const pdbx_aromatic_flag: string[] = []
|
||||
const pdbx_stereo_config: string[] = []
|
||||
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[] = []
|
||||
|
||||
function addBonds(compId: string, ccb: CCB, protonationVariant: boolean) {
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
import { createPlugin, DefaultPluginSpec } from '../../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../../mol-plugin/command';
|
||||
import { StateTransforms } from '../../../mol-plugin/state/transforms';
|
||||
import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation';
|
||||
import { PluginStateObject as PSO } from '../../../mol-plugin/state/objects';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { StateTransforms } from '../../../mol-plugin-state/transforms';
|
||||
import { PluginStateObject as PSO } from '../../../mol-plugin-state/objects';
|
||||
import { StateBuilder } from '../../../mol-state';
|
||||
import { Canvas3DProps } from '../../../mol-canvas3d/canvas3d';
|
||||
import { createStructureRepresentationParams } from '../../../mol-plugin-state/helpers/structure-representation-params';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
|
||||
type SupportedFormats = 'cif' | 'pdb'
|
||||
@@ -94,7 +94,7 @@ class LightingDemo {
|
||||
|
||||
setPreset(preset: Canvas3DPreset) {
|
||||
const props = getPreset(preset)
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
|
||||
...props,
|
||||
multiSample: {
|
||||
...this.plugin.canvas3d!.props.multiSample,
|
||||
@@ -120,18 +120,24 @@ class LightingDemo {
|
||||
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
return parsed
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
|
||||
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' });
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { ref: 'asm' });
|
||||
}
|
||||
|
||||
private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'spacefill', {}, 'illustrative'), { ref: 'seq-visual' });
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'spacefill', color: 'illustrative' }), { ref: 'seq-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick'), { ref: 'het-visual' });
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'ball-and-stick' }), { ref: 'het-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
@@ -139,7 +145,7 @@ class LightingDemo {
|
||||
async load({ url, format = 'cif', assemblyId = '' }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.dataState;
|
||||
const state = this.plugin.state.data;
|
||||
|
||||
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
|
||||
loadType = 'full';
|
||||
@@ -149,17 +155,23 @@ class LightingDemo {
|
||||
|
||||
let tree: StateBuilder.Root;
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref });
|
||||
tree = state.build();
|
||||
this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId));
|
||||
} else {
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
tree = state.build();
|
||||
tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
|
||||
tree.to('asm').update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
this.loadedParams = { url, format, assemblyId };
|
||||
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
PluginCommands.Camera.Reset(this.plugin, { });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as _ from '../../mol-plugin/state/transforms'
|
||||
import * as _ from '../../mol-plugin-state/transforms'
|
||||
import { StateTransformer, StateObject } from '../../mol-state';
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
import * as fs from 'fs';
|
||||
|
||||
@@ -39,7 +39,7 @@ function paramInfo(param: PD.Any, offset: number): string {
|
||||
}
|
||||
}
|
||||
|
||||
function oToS(options: readonly (readonly [string, string])[]) {
|
||||
function oToS(options: readonly (readonly [string, string] | readonly [string, string, string])[]) {
|
||||
return options.map(o => `'${o[0]}'`).join(', ');
|
||||
}
|
||||
|
||||
|
||||
@@ -123,18 +123,6 @@ export function printSequence(model: Model) {
|
||||
console.log();
|
||||
}
|
||||
|
||||
export function printModRes(model: Model) {
|
||||
console.log('\nModified Residues\n=============');
|
||||
const map = model.properties.modifiedResidues.parentId;
|
||||
const { label_comp_id, _rowCount } = model.atomicHierarchy.residues;
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
const comp_id = label_comp_id.value(i);
|
||||
if (!map.has(comp_id)) continue;
|
||||
console.log(`[${i}] ${map.get(comp_id)} -> ${comp_id}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
export function printRings(structure: Structure) {
|
||||
console.log('\nRings\n=============');
|
||||
for (const unit of structure.units) {
|
||||
@@ -221,7 +209,6 @@ async function run(frame: CifFrame, args: Args) {
|
||||
if (args.rings) printRings(structure);
|
||||
if (args.intraBonds) printBonds(structure, true, false);
|
||||
if (args.interBonds) printBonds(structure, false, true);
|
||||
if (args.mod) printModRes(models[0]);
|
||||
if (args.sec) printSecStructure(models[0]);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { StateAction } from '../../../../mol-state';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../../../mol-plugin/state/objects';
|
||||
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 { getFromPdb, getFromCellPackDB } from './util';
|
||||
@@ -16,8 +16,7 @@ import { trajectoryFromPDB } from '../../../../mol-model-formats/structure/pdb';
|
||||
import { Mat4, Vec3, Quat } from '../../../../mol-math/linear-algebra';
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { StructureRepresentation3DHelpers } from '../../../../mol-plugin/state/transforms/representation';
|
||||
import { StateTransforms } from '../../../../mol-plugin/state/transforms';
|
||||
import { 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';
|
||||
@@ -26,12 +25,11 @@ 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 { ThemeRegistryContext } from '../../../../mol-theme/theme';
|
||||
import { ColorTheme } from '../../../../mol-theme/color';
|
||||
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';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/results/${fileName}`
|
||||
@@ -398,9 +396,9 @@ export const LoadCellPackModel = StateAction.build({
|
||||
}
|
||||
cellpackTree
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.createParams(ctx, Structure.Empty, {
|
||||
repr: getReprParams(ctx, params.preset),
|
||||
color: getColorParams(hue)
|
||||
createStructureRepresentationParams(ctx, Structure.Empty, {
|
||||
...getReprParams(ctx, params.preset),
|
||||
...getColorParams(hue)
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -414,8 +412,8 @@ export const LoadCellPackModel = StateAction.build({
|
||||
.apply(StateTransforms.Model.StructureFromModel, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Misc.CreateGroup, { label: 'HIV1_envelope_Membrane' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.createParams(ctx, Structure.Empty, {
|
||||
repr: getReprParams(ctx, params.preset),
|
||||
createStructureRepresentationParams(ctx, Structure.Empty, {
|
||||
...getReprParams(ctx, params.preset),
|
||||
color: UniformColorThemeProvider
|
||||
})
|
||||
)
|
||||
@@ -431,50 +429,41 @@ function getReprParams(ctx: PluginContext, params: { representation: Representat
|
||||
switch (representation) {
|
||||
case 'spacefill':
|
||||
return traceOnly
|
||||
? [
|
||||
ctx.structureRepresentation.registry.get('spacefill'),
|
||||
() => ({ sizeFactor: 2, ignoreHydrogens: true })
|
||||
] as [any, any]
|
||||
: [
|
||||
ctx.structureRepresentation.registry.get('spacefill'),
|
||||
() => ({ ignoreHydrogens: true })
|
||||
] as [any, any]
|
||||
? {
|
||||
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 [
|
||||
ctx.structureRepresentation.registry.get('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
|
||||
})
|
||||
] as [any, any]
|
||||
}
|
||||
}
|
||||
case 'point':
|
||||
return [
|
||||
ctx.structureRepresentation.registry.get('point'),
|
||||
() => ({ ignoreHydrogens: true })
|
||||
] as [any, any]
|
||||
return { type: ctx.representation.structure.registry.get('point') }
|
||||
case 'ellipsoid':
|
||||
return [
|
||||
ctx.structureRepresentation.registry.get('orientation'),
|
||||
() => ({})
|
||||
] as [any, any]
|
||||
return { type: ctx.representation.structure.registry.get('orientation') }
|
||||
}
|
||||
}
|
||||
|
||||
function getColorParams(hue: [number, number]) {
|
||||
return [
|
||||
ModelIndexColorThemeProvider,
|
||||
(c: ColorTheme.Provider<any>, ctx: ThemeRegistryContext) => {
|
||||
return {
|
||||
palette: {
|
||||
name: 'generate',
|
||||
params: {
|
||||
hue, chroma: [30, 80], luminance: [15, 85],
|
||||
clusteringStepCount: 50, minSampleCount: 800,
|
||||
maxCount: 75
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
] as [any, any]
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin/state/objects';
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { CellPack as _CellPack, Cell, CellPacking } from './data';
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// import { UUID } from '../../../mol-util';
|
||||
// import { ColorNames } from '../../../mol-util/color/names';
|
||||
// import { Camera } from '../../../mol-canvas3d/camera';
|
||||
// import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation';
|
||||
// import { createStructureRepresentation3dParamss } from '../../../mol-plugin/state/transforms/representation';
|
||||
// import { createDefaultStructureComplex } from '../../../mol-plugin/util/structure-complex-helper';
|
||||
|
||||
// export const CreateJoleculeState = StateAction.build({
|
||||
@@ -108,13 +108,13 @@
|
||||
// group
|
||||
// .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: MS.struct.modifier.wholeResidues([ expression ]), label: 'Residue' })
|
||||
// .apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
// StructureRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'ball-and-stick', { }));
|
||||
// createStructureRepresentation3dParamss.getDefaultParamsStatic(plugin, 'ball-and-stick', { }));
|
||||
// }
|
||||
// if (params.e.selected && params.e.selected.length > 0) {
|
||||
// b.to(template.structure)
|
||||
// .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: createExpression(params.e.selected), label: `Selected` })
|
||||
// .apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
// StructureRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'ball-and-stick'));
|
||||
// createStructureRepresentation3dParamss.getDefaultParamsStatic(plugin, 'ball-and-stick'));
|
||||
// }
|
||||
// // TODO
|
||||
// // for (const l of params.e.distances) {
|
||||
|
||||
@@ -10,10 +10,11 @@ import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import './favicon.ico'
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/command';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
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';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
|
||||
function getParam(name: string, regex: string): string {
|
||||
@@ -47,6 +48,7 @@ function init() {
|
||||
};
|
||||
const plugin = createPlugin(document.getElementById('app')!, spec);
|
||||
trySetSnapshot(plugin);
|
||||
tryLoadFromUrl(plugin);
|
||||
}
|
||||
|
||||
async function trySetSnapshot(ctx: PluginContext) {
|
||||
@@ -58,11 +60,42 @@ async function trySetSnapshot(ctx: PluginContext) {
|
||||
const url = snapshotId
|
||||
? `https://webchem.ncbr.muni.cz/molstar-state/get/${snapshotId}`
|
||||
: snapshotUrl;
|
||||
await PluginCommands.State.Snapshots.Fetch.dispatch(ctx, { url })
|
||||
await PluginCommands.State.Snapshots.Fetch(ctx, { url })
|
||||
} catch (e) {
|
||||
ctx.log.error('Failed to load snapshot.');
|
||||
console.warn('Failed to load snapshot', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function tryLoadFromUrl(ctx: PluginContext) {
|
||||
const url = getParam('loadFromURL', '[^&]+').trim();
|
||||
try {
|
||||
if (!url) return;
|
||||
|
||||
let format = 'cif', isBinary = false;
|
||||
switch (getParam('loadFromURLFormat', '[a-z]+').toLocaleLowerCase().trim()) {
|
||||
case 'pdb': format = 'pdb'; break;
|
||||
case 'mmbcif': isBinary = true; break;
|
||||
}
|
||||
|
||||
const params = DownloadStructure.createDefaultParams(void 0 as any, ctx);
|
||||
|
||||
return ctx.runTask(ctx.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'url',
|
||||
params: {
|
||||
url,
|
||||
format: format as any,
|
||||
isBinary,
|
||||
options: params.source.params.options,
|
||||
structure: params.source.params.structure,
|
||||
}
|
||||
}
|
||||
}));
|
||||
} catch (e) {
|
||||
ctx.log.error(`Failed to load from URL (${url})`);
|
||||
console.warn(`Failed to load from URL (${url})`, e);
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -94,13 +94,13 @@ export function createProteopediaCustomTheme(colors: number[]) {
|
||||
}
|
||||
}
|
||||
|
||||
const ProteopediaCustomColorThemeProvider: ColorTheme.Provider<ProteopediaCustomColorThemeParams> = {
|
||||
return {
|
||||
name: 'proteopedia-custom',
|
||||
label: 'Proteopedia Custom',
|
||||
category: 'Custom',
|
||||
factory: ProteopediaCustomColorTheme,
|
||||
getParams: getChainIdColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(ProteopediaCustomColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
|
||||
}
|
||||
|
||||
return ProteopediaCustomColorThemeProvider;
|
||||
}
|
||||
@@ -5,9 +5,9 @@
|
||||
*/
|
||||
|
||||
import { ResidueIndex, Model } from '../../mol-model/structure';
|
||||
import { BuiltInStructureRepresentationsName } from '../../mol-repr/structure/registry';
|
||||
import { BuiltInColorThemeName } from '../../mol-theme/color';
|
||||
import { AminoAcidNames } from '../../mol-model/structure/model/types';
|
||||
import { StructureRepresentationRegistry } from '../../mol-repr/structure/registry';
|
||||
import { ColorTheme } from '../../mol-theme/color';
|
||||
import { PolymerType } from '../../mol-model/structure/model/types';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
|
||||
@@ -54,15 +54,14 @@ export namespace ModelInfo {
|
||||
const hetMap = new Map<string, ModelInfo['hetResidues'][0]>();
|
||||
|
||||
for (let rI = 0 as ResidueIndex; rI < residueCount; rI++) {
|
||||
const comp_id = model.atomicHierarchy.residues.label_comp_id.value(rI);
|
||||
if (AminoAcidNames.has(comp_id)) continue;
|
||||
const mod_parent = model.properties.modifiedResidues.parentId.get(comp_id);
|
||||
if (mod_parent && AminoAcidNames.has(mod_parent)) continue;
|
||||
if (model.atomicHierarchy.derived.residue.polymerType[rI] !== PolymerType.NA) continue;
|
||||
|
||||
const cI = chainIndex[residueOffsets[rI]];
|
||||
const eI = model.atomicHierarchy.index.getEntityFromChain(cI);
|
||||
if (model.entities.data.type.value(eI) === 'water') continue;
|
||||
|
||||
const comp_id = model.atomicHierarchy.residues.label_comp_id.value(rI);
|
||||
|
||||
let lig = hetMap.get(comp_id);
|
||||
if (!lig) {
|
||||
lig = { name: comp_id, indices: [] };
|
||||
@@ -99,7 +98,7 @@ export interface RepresentationStyle {
|
||||
}
|
||||
|
||||
export namespace RepresentationStyle {
|
||||
export type Entry = { hide?: boolean, kind?: BuiltInStructureRepresentationsName, coloring?: BuiltInColorThemeName }
|
||||
export type Entry = { hide?: boolean, kind?: StructureRepresentationRegistry.BuiltIn, coloring?: ColorTheme.BuiltIn }
|
||||
}
|
||||
|
||||
export enum StateElements {
|
||||
|
||||
@@ -8,35 +8,32 @@ import * as ReactDOM from 'react-dom';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/command';
|
||||
import { StateTransforms } from '../../mol-plugin/state/transforms';
|
||||
import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transforms/representation';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin/state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin/state/animation/built-in';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in';
|
||||
import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
|
||||
import { EvolutionaryConservation } from './annotation';
|
||||
import { LoadParams, SupportedFormats, RepresentationStyle, ModelInfo, StateElements } from './helpers';
|
||||
import { RxEventHelper } from '../../mol-util/rx-event-helper';
|
||||
import { ControlsWrapper, volumeStreamingControls } from './ui/controls';
|
||||
import { volumeStreamingControls } from './ui/controls';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { Scheduler } from '../../mol-task';
|
||||
import { createProteopediaCustomTheme } from './coloring';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { BuiltInStructureRepresentations } from '../../mol-repr/structure/registry';
|
||||
import { BuiltInColorThemes } from '../../mol-theme/color';
|
||||
import { BuiltInSizeThemes } from '../../mol-theme/size';
|
||||
import { ColorNames } from '../../mol-util/color/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';
|
||||
require('../../mol-plugin-ui/skin/light.scss')
|
||||
|
||||
class MolStarProteopediaWrapper {
|
||||
static VERSION_MAJOR = 3;
|
||||
static VERSION_MINOR = 4;
|
||||
static VERSION_MAJOR = 4;
|
||||
static VERSION_MINOR = 0;
|
||||
|
||||
private _ev = RxEventHelper.create();
|
||||
|
||||
@@ -58,23 +55,23 @@ class MolStarProteopediaWrapper {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: {
|
||||
right: ControlsWrapper
|
||||
}
|
||||
},
|
||||
components: {
|
||||
remoteState: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
const customColoring = createProteopediaCustomTheme((options && options.customColorList) || []);
|
||||
|
||||
this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add('proteopedia-custom', customColoring);
|
||||
this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(EvolutionaryConservation.propertyProvider.descriptor.name, EvolutionaryConservation.colorThemeProvider!);
|
||||
this.plugin.lociLabels.addProvider(EvolutionaryConservation.labelProvider!);
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(customColoring);
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(EvolutionaryConservation.colorThemeProvider!);
|
||||
this.plugin.managers.lociLabels.addProvider(EvolutionaryConservation.labelProvider!);
|
||||
this.plugin.customModelProperties.register(EvolutionaryConservation.propertyProvider, true);
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this.plugin.state.dataState;
|
||||
return this.plugin.state.data;
|
||||
}
|
||||
|
||||
private download(b: StateBuilder.To<PSO.Root>, url: string) {
|
||||
@@ -92,10 +89,16 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
private structure(assemblyId: string) {
|
||||
const model = this.state.build().to(StateElements.Model);
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
|
||||
const s = model
|
||||
.apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [EvolutionaryConservation.propertyProvider.descriptor.name], properties: {} }, { ref: StateElements.ModelProps, state: { isGhost: false } })
|
||||
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: StateElements.Assembly });
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { ref: StateElements.Assembly });
|
||||
|
||||
s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: StateElements.Sequence });
|
||||
s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }, { ref: StateElements.Het });
|
||||
@@ -118,9 +121,10 @@ class MolStarProteopediaWrapper {
|
||||
root.delete(StateElements.SequenceVisual);
|
||||
} else {
|
||||
root.applyOrUpdate(StateElements.SequenceVisual, StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
|
||||
(style.sequence && style.sequence.kind) || 'cartoon',
|
||||
(style.sequence && style.sequence.coloring) || 'unit-index', structure));
|
||||
createStructureRepresentationParams(this.plugin, structure, {
|
||||
type: (style.sequence && style.sequence.kind) || 'cartoon',
|
||||
color: (style.sequence && style.sequence.coloring) || 'unit-index'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,9 +137,10 @@ class MolStarProteopediaWrapper {
|
||||
root.delete(StateElements.HetVisual);
|
||||
} else {
|
||||
root.applyOrUpdate(StateElements.HetVisual, StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
|
||||
(style.hetGroups && style.hetGroups.kind) || 'ball-and-stick',
|
||||
(style.hetGroups && style.hetGroups.coloring), structure));
|
||||
createStructureRepresentationParams(this.plugin, structure, {
|
||||
type: (style.hetGroups && style.hetGroups.kind) || 'ball-and-stick',
|
||||
color: style.hetGroups && style.hetGroups.coloring
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,7 +154,7 @@ class MolStarProteopediaWrapper {
|
||||
root.delete(StateElements.Het3DSNFG);
|
||||
} else {
|
||||
root.applyOrUpdate(StateElements.Het3DSNFG, StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin, 'carbohydrate', void 0, structure));
|
||||
createStructureRepresentationParams(this.plugin, structure, { type: 'carbohydrate' }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,9 +165,11 @@ class MolStarProteopediaWrapper {
|
||||
root.delete(StateElements.WaterVisual);
|
||||
} else {
|
||||
root.applyOrUpdate(StateElements.WaterVisual, StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsWithTheme(this.plugin,
|
||||
(style.water && style.water.kind) || 'ball-and-stick',
|
||||
(style.water && style.water.coloring), structure, { alpha: 0.51 }));
|
||||
createStructureRepresentationParams(this.plugin, structure, {
|
||||
type: (style.water && style.water.kind) || 'ball-and-stick',
|
||||
typeParams: { alpha: 0.51 },
|
||||
color: style.water && style.water.coloring
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,14 +193,14 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
private applyState(tree: StateBuilder) {
|
||||
return PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
|
||||
return PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
}
|
||||
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = 'deposited', representationStyle }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.dataState;
|
||||
const state = this.plugin.state.data;
|
||||
|
||||
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
|
||||
loadType = 'full';
|
||||
@@ -202,7 +209,7 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref });
|
||||
const modelTree = this.model(this.download(state.build().toRoot(), url), format);
|
||||
await this.applyState(modelTree);
|
||||
const info = await this.doInfo(true);
|
||||
@@ -213,39 +220,45 @@ class MolStarProteopediaWrapper {
|
||||
const tree = state.build();
|
||||
const info = await this.doInfo(true);
|
||||
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: asmId }));
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: asmId || 'deposited' }
|
||||
}
|
||||
}
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
|
||||
await this.applyState(tree);
|
||||
}
|
||||
|
||||
await this.updateStyle(representationStyle);
|
||||
|
||||
this.loadedParams = { url, format, assemblyId };
|
||||
Scheduler.setImmediate(() => PluginCommands.Camera.Reset.dispatch(this.plugin, { }));
|
||||
Scheduler.setImmediate(() => PluginCommands.Camera.Reset(this.plugin, { }));
|
||||
}
|
||||
|
||||
async updateStyle(style?: RepresentationStyle, partial?: boolean) {
|
||||
const tree = this.visual(style, partial);
|
||||
if (!tree) return;
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
}
|
||||
|
||||
setBackground(color: number) {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const renderer = this.plugin.canvas3d.props.renderer;
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
}
|
||||
|
||||
toggleSpin() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const trackball = this.plugin.canvas3d.props.trackball;
|
||||
const spinning = trackball.spin;
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
if (!spinning) PluginCommands.Camera.Reset(this.plugin, { });
|
||||
}
|
||||
|
||||
viewport = {
|
||||
setSettings: (settings?: Canvas3DProps) => {
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
settings: settings || DefaultCanvas3DParams
|
||||
});
|
||||
}
|
||||
@@ -253,10 +266,10 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
camera = {
|
||||
toggleSpin: () => this.toggleSpin(),
|
||||
resetPosition: () => PluginCommands.Camera.Reset.dispatch(this.plugin, { }),
|
||||
resetPosition: () => PluginCommands.Camera.Reset(this.plugin, { }),
|
||||
// setClip: (options?: { distance?: number, near?: number, far?: number }) => {
|
||||
// if (!options) {
|
||||
// PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
|
||||
// PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
// settings: {
|
||||
// cameraClipDistance: DefaultCanvas3DParams.cameraClipDistance,
|
||||
// clip: DefaultCanvas3DParams.clip
|
||||
@@ -269,7 +282,7 @@ class MolStarProteopediaWrapper {
|
||||
// const props = this.plugin.canvas3d.props;
|
||||
// const clipNear = typeof options.near === 'undefined' ? props.clip[0] : options.near;
|
||||
// const clipFar = typeof options.far === 'undefined' ? props.clip[1] : options.far;
|
||||
// PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
|
||||
// PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
// settings: { cameraClipDistance: options.distance, clip: [clipNear, clipFar] }
|
||||
// });
|
||||
// }
|
||||
@@ -299,7 +312,7 @@ class MolStarProteopediaWrapper {
|
||||
// }
|
||||
|
||||
const tree = state.build();
|
||||
const colorTheme = { name: EvolutionaryConservation.propertyProvider.descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.propertyProvider.descriptor.name).defaultValues };
|
||||
const colorTheme = { name: EvolutionaryConservation.propertyProvider.descriptor.name, params: this.plugin.representation.structure.themes.colorThemeRegistry.get(EvolutionaryConservation.propertyProvider.descriptor.name).defaultValues };
|
||||
|
||||
if (!params || !!params.sequence) {
|
||||
tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
|
||||
@@ -308,7 +321,7 @@ class MolStarProteopediaWrapper {
|
||||
tree.to(StateElements.HetVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +341,7 @@ class MolStarProteopediaWrapper {
|
||||
remove: () => {
|
||||
const r = this.state.select(StateSelection.Generators.ofTransformer(CreateVolumeStreamingInfo))[0];
|
||||
if (!r) return;
|
||||
PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.state, ref: r.transform.ref });
|
||||
PluginCommands.State.RemoveObject(this.plugin, { state: this.state, ref: r.transform.ref });
|
||||
if (this.experimentalDataElement) {
|
||||
ReactDOM.unmountComponentAtNode(this.experimentalDataElement);
|
||||
this.experimentalDataElement = void 0;
|
||||
@@ -339,12 +352,12 @@ class MolStarProteopediaWrapper {
|
||||
hetGroups = {
|
||||
reset: () => {
|
||||
const update = this.state.build().delete(StateElements.HetGroupFocusGroup);
|
||||
PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update });
|
||||
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
PluginCommands.State.Update(this.plugin, { state: this.state, tree: update });
|
||||
PluginCommands.Camera.Reset(this.plugin, { });
|
||||
},
|
||||
focusFirst: async (compId: string) => {
|
||||
if (!this.state.transforms.has(StateElements.Assembly)) return;
|
||||
await PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
await PluginCommands.Camera.Reset(this.plugin, { });
|
||||
|
||||
// const asm = (this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
|
||||
@@ -377,7 +390,7 @@ class MolStarProteopediaWrapper {
|
||||
// }
|
||||
// });
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update });
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.state, tree: update });
|
||||
|
||||
const focus = (this.state.select(StateElements.HetGroupFocus)[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
const sphere = focus.boundary.sphere;
|
||||
@@ -386,26 +399,24 @@ class MolStarProteopediaWrapper {
|
||||
// Vec3.normalize(position, position);
|
||||
// Vec3.scaleAndAdd(position, sphere.center, position, sphere.radius);
|
||||
const radius = Math.max(sphere.radius, 5)
|
||||
const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, radius, radius);
|
||||
PluginCommands.Camera.SetSnapshot.dispatch(this.plugin, { snapshot, durationMs: 250 });
|
||||
const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, radius);
|
||||
PluginCommands.Camera.SetSnapshot(this.plugin, { snapshot, durationMs: 250 });
|
||||
}
|
||||
}
|
||||
|
||||
private createSurVisualParams() {
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
|
||||
return StructureRepresentation3DHelpers.createParams(this.plugin, asm.data, {
|
||||
repr: BuiltInStructureRepresentations['ball-and-stick'],
|
||||
color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
|
||||
size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
|
||||
return createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'ball-and-stick',
|
||||
color: 'uniform', colorParams: { value: ColorNames.gray },
|
||||
size: 'uniform', sizeParams: { value: 0.33 }
|
||||
});
|
||||
}
|
||||
|
||||
private createCoreVisualParams() {
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
|
||||
return StructureRepresentation3DHelpers.createParams(this.plugin, asm.data, {
|
||||
repr: BuiltInStructureRepresentations['ball-and-stick'],
|
||||
// color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
|
||||
// size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
|
||||
return createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'ball-and-stick'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,24 +6,11 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { CurrentObject, PluginContextContainer } from '../../../mol-plugin-ui/plugin';
|
||||
import { AnimationControls } from '../../../mol-plugin-ui/state/animation';
|
||||
import { CameraSnapshots } from '../../../mol-plugin-ui/camera';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { PluginContextContainer } from '../../../mol-plugin-ui/plugin';
|
||||
import { TransformUpdaterControl } from '../../../mol-plugin-ui/state/update-transform';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateElements } from '../helpers';
|
||||
|
||||
export class ControlsWrapper extends PluginUIComponent {
|
||||
render() {
|
||||
return <div className='msp-scrollable-container msp-right-controls'>
|
||||
<CurrentObject />
|
||||
<AnimationControls />
|
||||
<CameraSnapshots />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export function volumeStreamingControls(plugin: PluginContext, parent: Element) {
|
||||
ReactDOM.render(<PluginContextContainer plugin={plugin}>
|
||||
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra'
|
||||
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
|
||||
import { CameraTransitionManager } from './camera/transition';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
export { Camera }
|
||||
|
||||
@@ -33,6 +34,7 @@ class Camera {
|
||||
zoom = 1
|
||||
|
||||
readonly transition: CameraTransitionManager = new CameraTransitionManager(this);
|
||||
readonly stateChanged = new BehaviorSubject<Partial<Camera.Snapshot>>(this.state);
|
||||
|
||||
get position() { return this.state.position; }
|
||||
set position(v: Vec3) { Vec3.copy(this.state.position, v); }
|
||||
@@ -76,18 +78,20 @@ class Camera {
|
||||
|
||||
setState(snapshot: Partial<Camera.Snapshot>, durationMs?: number) {
|
||||
this.transition.apply(snapshot, durationMs);
|
||||
this.stateChanged.next(snapshot);
|
||||
}
|
||||
|
||||
getSnapshot() {
|
||||
return Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
|
||||
}
|
||||
|
||||
getFocus(target: Vec3, radiusNear: number, radiusFar: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
|
||||
const fov = this.state.fov
|
||||
getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
|
||||
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((radiusNear / aspectFactor) / Math.sin(fov / 2))
|
||||
const targetDistance = Math.abs((r / aspectFactor) / Math.sin(fov / 2))
|
||||
|
||||
Vec3.sub(this.deltaDirection, this.target, this.position)
|
||||
if (dir) Vec3.matchDirection(this.deltaDirection, dir, this.deltaDirection)
|
||||
@@ -96,17 +100,16 @@ class Camera {
|
||||
|
||||
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state)
|
||||
state.target = Vec3.clone(target)
|
||||
state.radiusNear = radiusNear
|
||||
state.radiusFar = radiusFar
|
||||
state.radius = r
|
||||
state.position = Vec3.clone(this.newPosition)
|
||||
if (up) Vec3.matchDirection(state.up, up, state.up)
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
focus(target: Vec3, radiusNear: number, radiusFar: number, durationMs?: number, up?: Vec3, dir?: Vec3) {
|
||||
if (radiusNear > 0 && radiusFar > 0) {
|
||||
this.setState(this.getFocus(target, radiusNear, radiusFar, up, dir), durationMs);
|
||||
focus(target: Vec3, radius: number, durationMs?: number, up?: Vec3, dir?: Vec3) {
|
||||
if (radius > 0) {
|
||||
this.setState(this.getFocus(target, radius, up, dir), durationMs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,8 +164,8 @@ namespace Camera {
|
||||
up: Vec3.create(0, 1, 0),
|
||||
target: Vec3.create(0, 0, 0),
|
||||
|
||||
radiusNear: 10,
|
||||
radiusFar: 10,
|
||||
radius: 10,
|
||||
radiusMax: 10,
|
||||
fog: 50,
|
||||
clipFar: true
|
||||
};
|
||||
@@ -176,8 +179,8 @@ namespace Camera {
|
||||
up: Vec3
|
||||
target: Vec3
|
||||
|
||||
radiusNear: number
|
||||
radiusFar: number
|
||||
radius: number
|
||||
radiusMax: number
|
||||
fog: number
|
||||
clipFar: boolean
|
||||
}
|
||||
@@ -192,8 +195,8 @@ namespace Camera {
|
||||
if (typeof source.up !== 'undefined') Vec3.copy(out.up, source.up);
|
||||
if (typeof source.target !== 'undefined') Vec3.copy(out.target, source.target);
|
||||
|
||||
if (typeof source.radiusNear !== 'undefined') out.radiusNear = source.radiusNear;
|
||||
if (typeof source.radiusFar !== 'undefined') out.radiusFar = source.radiusFar;
|
||||
if (typeof source.radius !== 'undefined') out.radius = source.radius;
|
||||
if (typeof source.radiusMax !== 'undefined') out.radiusMax = source.radiusMax;
|
||||
if (typeof source.fog !== 'undefined') out.fog = source.fog;
|
||||
if (typeof source.clipFar !== 'undefined') out.clipFar = source.clipFar;
|
||||
|
||||
@@ -262,14 +265,16 @@ function updatePers(camera: Camera) {
|
||||
}
|
||||
|
||||
function updateClip(camera: Camera) {
|
||||
const { radiusNear, radiusFar, mode, fog, clipFar } = camera.state
|
||||
let { radius, radiusMax, mode, fog, clipFar } = camera.state
|
||||
if (radius < 0.01) radius = 0.01
|
||||
|
||||
const cDist = Vec3.distance(camera.position, camera.target)
|
||||
let near = cDist - radiusNear
|
||||
let far = cDist + (clipFar ? radiusNear : radiusFar)
|
||||
const normalizedFar = clipFar ? radius : radiusMax
|
||||
const cameraDistance = Vec3.distance(camera.position, camera.target)
|
||||
let near = cameraDistance - radius
|
||||
let far = cameraDistance + normalizedFar
|
||||
|
||||
const fogNearFactor = -(50 - fog) / 50
|
||||
let fogNear = cDist - (radiusNear * fogNearFactor)
|
||||
let fogNear = cameraDistance - (normalizedFar * fogNearFactor)
|
||||
let fogFar = far
|
||||
|
||||
if (mode === 'perspective') {
|
||||
|
||||
@@ -17,19 +17,26 @@ class CameraTransitionManager {
|
||||
private start = 0;
|
||||
inTransition = false;
|
||||
private durationMs = 0;
|
||||
private source: Camera.Snapshot = Camera.createDefaultSnapshot();
|
||||
private target: Camera.Snapshot = Camera.createDefaultSnapshot();
|
||||
private current = Camera.createDefaultSnapshot();
|
||||
private _source: Camera.Snapshot = Camera.createDefaultSnapshot();
|
||||
private _target: Camera.Snapshot = Camera.createDefaultSnapshot();
|
||||
private _current = Camera.createDefaultSnapshot();
|
||||
|
||||
get source(): Readonly<Camera.Snapshot> { return this._source }
|
||||
get target(): Readonly<Camera.Snapshot> { return this._target }
|
||||
|
||||
apply(to: Partial<Camera.Snapshot>, durationMs: number = 0, transition?: CameraTransitionManager.TransitionFunc) {
|
||||
if (durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
|
||||
this.finish(to);
|
||||
return;
|
||||
Camera.copySnapshot(this._source, this.camera.state);
|
||||
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
|
||||
}
|
||||
|
||||
Camera.copySnapshot(this.source, this.camera.state);
|
||||
Camera.copySnapshot(this.target, this.camera.state);
|
||||
Camera.copySnapshot(this.target, to);
|
||||
if (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;
|
||||
@@ -52,12 +59,12 @@ class CameraTransitionManager {
|
||||
|
||||
const normalized = Math.min((this.t - this.start) / this.durationMs, 1);
|
||||
if (normalized === 1) {
|
||||
this.finish(this.target!);
|
||||
this.finish(this._target!);
|
||||
return;
|
||||
}
|
||||
|
||||
this.func(this.current, normalized, this.source, this.target);
|
||||
Camera.copySnapshot(this.camera.state, this.current);
|
||||
this.func(this._current, normalized, this._source, this._target);
|
||||
Camera.copySnapshot(this.camera.state, this._current);
|
||||
}
|
||||
|
||||
constructor(private camera: Camera) {
|
||||
@@ -79,9 +86,9 @@ namespace CameraTransitionManager {
|
||||
// Lerp target, position & radius
|
||||
Vec3.lerp(out.target, source.target, target.target, t);
|
||||
Vec3.lerp(out.position, source.position, target.position, t);
|
||||
out.radiusNear = lerp(source.radiusNear, target.radiusNear, t);
|
||||
out.radius = lerp(source.radius, target.radius, t);
|
||||
// TODO take change of `clipFar` into account
|
||||
out.radiusFar = lerp(source.radiusFar, target.radiusFar, t);
|
||||
out.radiusMax = lerp(source.radiusMax, target.radiusMax, t);
|
||||
|
||||
// Lerp fov & fog
|
||||
out.fov = lerp(source.fov, target.fov, t);
|
||||
|
||||
@@ -37,9 +37,18 @@ import { Sphere3D } from '../mol-math/geometry';
|
||||
import { isDebugMode } from '../mol-util/debug';
|
||||
|
||||
export const Canvas3DParams = {
|
||||
cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]),
|
||||
cameraFog: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
|
||||
cameraClipFar: PD.Boolean(true),
|
||||
cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']] as const),
|
||||
cameraFog: PD.MappedStatic('on', {
|
||||
on: PD.Group({
|
||||
intensity: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Show fog in the distance' }),
|
||||
cameraClipping: PD.Group({
|
||||
radius: PD.Numeric(100, { min: 0, max: 99, step: 1 }, { label: 'Clipping', description: 'How much of the scene to show.' }),
|
||||
far: PD.Boolean(true, { description: 'Hide scene in the distance' }),
|
||||
}, { pivot: 'radius' }),
|
||||
|
||||
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
|
||||
transparentBackground: PD.Boolean(false),
|
||||
|
||||
@@ -62,7 +71,7 @@ interface Canvas3D {
|
||||
/**
|
||||
* This function must be called if animate() is not set up so that add/remove actions take place.
|
||||
*/
|
||||
commit(): void
|
||||
commit(isSynchronous?: boolean): void
|
||||
update(repr?: Representation.Any, keepBoundingSphere?: boolean): void
|
||||
clear(): void
|
||||
|
||||
@@ -77,7 +86,7 @@ interface Canvas3D {
|
||||
|
||||
handleResize(): void
|
||||
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
|
||||
requestCameraReset(): void
|
||||
requestCameraReset(options?: { durationMs?: number, snapshot?: Partial<Camera.Snapshot> }): void
|
||||
readonly camera: Camera
|
||||
readonly boundingSphere: Readonly<Sphere3D>
|
||||
downloadScreenshot(): void
|
||||
@@ -169,8 +178,8 @@ namespace Canvas3D {
|
||||
const camera = new Camera({
|
||||
position: Vec3.create(0, 0, 100),
|
||||
mode: p.cameraMode,
|
||||
fog: p.cameraFog,
|
||||
clipFar: p.cameraClipFar
|
||||
fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0,
|
||||
clipFar: p.cameraClipping.far
|
||||
})
|
||||
|
||||
const controls = TrackballControls.create(input, camera, p.trackball)
|
||||
@@ -190,6 +199,8 @@ namespace Canvas3D {
|
||||
|
||||
let drawPending = false
|
||||
let cameraResetRequested = false
|
||||
let nextCameraResetDuration: number | undefined = void 0
|
||||
let nextCameraResetSnapshot: Partial<Camera.Snapshot> | undefined = void 0
|
||||
|
||||
function getLoci(pickingId: PickingId) {
|
||||
let loci: Loci = EmptyLoci
|
||||
@@ -266,9 +277,7 @@ namespace Canvas3D {
|
||||
|
||||
function animate() {
|
||||
currentTime = now();
|
||||
|
||||
commit();
|
||||
|
||||
camera.transition.tick(currentTime);
|
||||
|
||||
draw(false);
|
||||
@@ -282,25 +291,38 @@ namespace Canvas3D {
|
||||
return webgl.isContextLost ? undefined : pickPass.identify(x, y)
|
||||
}
|
||||
|
||||
function commit() {
|
||||
commitScene();
|
||||
resolveCameraReset();
|
||||
function commit(isSynchronous: boolean = false) {
|
||||
const allCommited = commitScene(isSynchronous);
|
||||
// Only reset the camera after the full scene has been commited.
|
||||
if (allCommited) resolveCameraReset();
|
||||
}
|
||||
|
||||
function resolveCameraReset() {
|
||||
if (!cameraResetRequested) return;
|
||||
const { center, radius } = scene.boundingSphere;
|
||||
camera.focus(center, radius, radius, p.cameraResetDurationMs);
|
||||
if (radius > 0) {
|
||||
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration
|
||||
const focus = camera.getFocus(center, radius);
|
||||
const snapshot = nextCameraResetSnapshot ? { ...focus, ...nextCameraResetSnapshot } : focus;
|
||||
camera.setState(snapshot, duration);
|
||||
}
|
||||
|
||||
nextCameraResetDuration = void 0;
|
||||
nextCameraResetSnapshot = void 0;
|
||||
cameraResetRequested = false;
|
||||
}
|
||||
|
||||
const sceneCommitTimeoutMs = 250;
|
||||
function commitScene() {
|
||||
if (!scene.needsCommit) return;
|
||||
function commitScene(isSynchronous: boolean) {
|
||||
if (!scene.needsCommit) return true;
|
||||
|
||||
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
|
||||
|
||||
const allCommited = scene.commit(sceneCommitTimeoutMs);
|
||||
if (debugHelper.isEnabled) debugHelper.update();
|
||||
if (allCommited) reprCount.next(reprRenderObjects.size);
|
||||
if (reprCount.value === 0 || camera.state.radiusMax === 0) cameraResetRequested = true;
|
||||
camera.setState({ radiusMax: scene.boundingSphere.radius })
|
||||
reprCount.next(reprRenderObjects.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
function add(repr: Representation.Any) {
|
||||
@@ -384,7 +406,9 @@ namespace Canvas3D {
|
||||
getLoci,
|
||||
|
||||
handleResize,
|
||||
requestCameraReset: () => {
|
||||
requestCameraReset: options => {
|
||||
nextCameraResetDuration = options?.durationMs;
|
||||
nextCameraResetSnapshot = options?.snapshot;
|
||||
cameraResetRequested = true;
|
||||
},
|
||||
camera,
|
||||
@@ -404,15 +428,28 @@ namespace Canvas3D {
|
||||
didDraw,
|
||||
reprCount,
|
||||
setProps: (props: Partial<Canvas3DProps>) => {
|
||||
const cameraState: Partial<Camera.Snapshot> = Object.create(null)
|
||||
if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
|
||||
camera.setState({ mode: props.cameraMode })
|
||||
cameraState.mode = props.cameraMode
|
||||
}
|
||||
if (props.cameraFog !== undefined && props.cameraFog !== camera.state.fog) {
|
||||
camera.setState({ fog: props.cameraFog })
|
||||
if (props.cameraFog !== undefined) {
|
||||
const newFog = props.cameraFog.name === 'on' ? props.cameraFog.params.intensity : 0
|
||||
if (newFog !== camera.state.fog) cameraState.fog = newFog
|
||||
}
|
||||
if (props.cameraClipFar !== undefined && props.cameraClipFar !== camera.state.clipFar) {
|
||||
camera.setState({ clipFar: props.cameraClipFar })
|
||||
if (props.cameraClipping !== undefined) {
|
||||
if (props.cameraClipping.far !== undefined && props.cameraClipping.far !== camera.state.clipFar) {
|
||||
cameraState.clipFar = props.cameraClipping.far
|
||||
}
|
||||
if (props.cameraClipping.radius !== undefined) {
|
||||
const radius = (scene.boundingSphere.radius / 100) * (100 - props.cameraClipping.radius)
|
||||
if (radius > 0 && radius !== cameraState.radius) {
|
||||
// if radius = 0, NaNs happen
|
||||
cameraState.radius = Math.max(radius, 0.01)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(cameraState).length > 0) camera.setState(cameraState)
|
||||
|
||||
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs
|
||||
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground
|
||||
|
||||
@@ -421,6 +458,7 @@ namespace Canvas3D {
|
||||
if (props.renderer) renderer.setProps(props.renderer)
|
||||
if (props.trackball) controls.setProps(props.trackball)
|
||||
if (props.debug) debugHelper.setProps(props.debug)
|
||||
|
||||
requestDraw(true)
|
||||
},
|
||||
getImagePass: (props: Partial<ImageProps> = {}) => {
|
||||
@@ -428,10 +466,16 @@ namespace Canvas3D {
|
||||
},
|
||||
|
||||
get props() {
|
||||
const radius = scene.boundingSphere.radius > 0
|
||||
? 100 - Math.round((camera.transition.target.radius / scene.boundingSphere.radius) * 100)
|
||||
: 0
|
||||
|
||||
return {
|
||||
cameraMode: camera.state.mode,
|
||||
cameraFog: camera.state.fog,
|
||||
cameraClipFar: camera.state.clipFar,
|
||||
cameraFog: camera.state.fog > 0
|
||||
? { name: 'on' as const, params: { intensity: camera.state.fog } }
|
||||
: { name: 'off' as const, params: {} },
|
||||
cameraClipping: { far: camera.state.clipFar, radius },
|
||||
cameraResetDurationMs: p.cameraResetDurationMs,
|
||||
transparentBackground: p.transparentBackground,
|
||||
|
||||
|
||||
@@ -21,15 +21,15 @@ const M = ModifiersKeys
|
||||
const Trigger = Binding.Trigger
|
||||
|
||||
export const DefaultTrackballBindings = {
|
||||
dragRotate: Binding([Trigger(B.Flag.Primary, M.create())], 'Rotate the 3D scene by dragging using ${triggers}'),
|
||||
dragRotateZ: Binding([Trigger(B.Flag.Primary, M.create({ shift: true }))], 'Rotate the 3D scene around the z-axis by dragging using ${triggers}'),
|
||||
dragPan: Binding([Trigger(B.Flag.Secondary, M.create()), Trigger(B.Flag.Primary, M.create({ control: true }))], 'Pan the 3D scene by dragging using ${triggers}'),
|
||||
dragRotate: Binding([Trigger(B.Flag.Primary, M.create())], 'Rotate', 'Drag using ${triggers}'),
|
||||
dragRotateZ: Binding([Trigger(B.Flag.Primary, M.create({ shift: true }))], 'Rotate around z-axis', 'Drag using ${triggers}'),
|
||||
dragPan: Binding([Trigger(B.Flag.Secondary, M.create()), Trigger(B.Flag.Primary, M.create({ control: true }))], 'Pan', 'Drag using ${triggers}'),
|
||||
dragZoom: Binding.Empty,
|
||||
dragFocus: Binding([Trigger(B.Flag.Forth, M.create())], 'Focus the 3D scene by dragging using ${triggers}'),
|
||||
dragFocusZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Focus and zoom the 3D scene by dragging using ${triggers}'),
|
||||
dragFocus: Binding([Trigger(B.Flag.Forth, M.create())], 'Focus', 'Drag using ${triggers}'),
|
||||
dragFocusZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Focus and zoom', 'Drag using ${triggers}'),
|
||||
|
||||
scrollZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Zoom the 3D scene by scrolling using ${triggers}'),
|
||||
scrollFocus: Binding([Trigger(B.Flag.Auxilary, M.create({ shift: true }))], 'Focus the 3D scene by scrolling using ${triggers}'),
|
||||
scrollZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Zoom', 'Scroll using ${triggers}'),
|
||||
scrollFocus: Binding([Trigger(B.Flag.Auxilary, M.create({ shift: true }))], 'Clip', 'Scroll using ${triggers}'),
|
||||
scrollFocusZoom: Binding.Empty,
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export const TrackballControlsParams = {
|
||||
panSpeed: PD.Numeric(0.8, { min: 0.1, max: 5, step: 0.1 }),
|
||||
|
||||
spin: PD.Boolean(false, { description: 'Spin the 3D scene around the x-axis in view space' }),
|
||||
spinSpeed: PD.Numeric(1, { min: -100, max: 100, step: 1 }),
|
||||
spinSpeed: PD.Numeric(1, { min: -20, max: 20, step: 1 }),
|
||||
|
||||
staticMoving: PD.Boolean(true, { isHidden: true }),
|
||||
dynamicDampingFactor: PD.Numeric(0.2, {}, { isHidden: true }),
|
||||
@@ -208,8 +208,8 @@ namespace TrackballControls {
|
||||
function focusCamera() {
|
||||
const factor = (_focusEnd[1] - _focusStart[1]) * p.zoomSpeed
|
||||
if (factor !== 0.0) {
|
||||
const radiusNear = Math.max(1, camera.state.radiusNear + 10 * factor)
|
||||
camera.setState({ radiusNear })
|
||||
const radius = Math.max(1, camera.state.radius + 10 * factor)
|
||||
camera.setState({ radius })
|
||||
}
|
||||
|
||||
if (p.staticMoving) {
|
||||
@@ -343,7 +343,7 @@ namespace TrackballControls {
|
||||
if (dragFocus) Vec2.copy(_focusEnd, mouseOnScreenVec2)
|
||||
if (dragFocusZoom) {
|
||||
const dist = Vec3.distance(camera.state.position, camera.state.target);
|
||||
camera.setState({ radiusNear: dist / 5 })
|
||||
camera.setState({ radius: dist / 5 })
|
||||
}
|
||||
if (dragPan) Vec2.copy(_panEnd, mouseOnScreenVec2)
|
||||
}
|
||||
|
||||
@@ -53,19 +53,17 @@ export class BoundingSphereHelper {
|
||||
const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato)
|
||||
if (newObjectData) this.objectsData.set(ro, newObjectData)
|
||||
|
||||
if (ro.type === 'mesh' || ro.type === 'lines' || ro.type === 'points') {
|
||||
const instanceData = this.instancesData.get(ro)
|
||||
const newInstanceData = updateBoundingSphereData(this.scene, r.values.invariantBoundingSphere.ref.value, instanceData, ColorNames.skyblue, {
|
||||
aTransform: ro.values.aTransform,
|
||||
matrix: ro.values.matrix,
|
||||
transform: ro.values.transform,
|
||||
extraTransform: ro.values.extraTransform,
|
||||
uInstanceCount: ro.values.uInstanceCount,
|
||||
instanceCount: ro.values.instanceCount,
|
||||
aInstance: ro.values.aInstance,
|
||||
})
|
||||
if (newInstanceData) this.instancesData.set(ro, newInstanceData)
|
||||
}
|
||||
const instanceData = this.instancesData.get(ro)
|
||||
const newInstanceData = updateBoundingSphereData(this.scene, r.values.invariantBoundingSphere.ref.value, instanceData, ColorNames.skyblue, {
|
||||
aTransform: ro.values.aTransform,
|
||||
matrix: ro.values.matrix,
|
||||
transform: ro.values.transform,
|
||||
extraTransform: ro.values.extraTransform,
|
||||
uInstanceCount: ro.values.uInstanceCount,
|
||||
instanceCount: ro.values.instanceCount,
|
||||
aInstance: ro.values.aInstance,
|
||||
})
|
||||
if (newInstanceData) this.instancesData.set(ro, newInstanceData)
|
||||
})
|
||||
|
||||
this.objectsData.forEach((objectData, ro) => {
|
||||
@@ -133,7 +131,11 @@ function createBoundingSphereMesh(boundingSphere: Sphere3D, mesh?: Mesh) {
|
||||
const detail = 2
|
||||
const vertexCount = sphereVertexCount(detail)
|
||||
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh)
|
||||
if (boundingSphere.radius) addSphere(builderState, boundingSphere.center, boundingSphere.radius, detail)
|
||||
if (boundingSphere.radius) {
|
||||
for (const b of Sphere3D.getList(boundingSphere)) {
|
||||
addSphere(builderState, b.center, b.radius, detail)
|
||||
}
|
||||
}
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
@@ -17,6 +17,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { RenderTarget } from '../../mol-gl/webgl/render-target';
|
||||
import { DrawPass } from './draw';
|
||||
import { Camera } from '../../mol-canvas3d/camera';
|
||||
import { produce } from 'immer';
|
||||
|
||||
import quad_vert from '../../mol-gl/shader/quad.vert'
|
||||
import postprocessing_frag from '../../mol-gl/shader/postprocessing.frag'
|
||||
@@ -47,14 +48,21 @@ const PostprocessingSchema = {
|
||||
}
|
||||
|
||||
export const PostprocessingParams = {
|
||||
occlusionEnable: PD.Boolean(false),
|
||||
occlusionKernelSize: PD.Numeric(4, { min: 1, max: 32, step: 1 }),
|
||||
occlusionBias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
|
||||
occlusionRadius: PD.Numeric(32, { min: 0, max: 256, step: 1 }),
|
||||
|
||||
outlineEnable: PD.Boolean(false),
|
||||
outlineScale: PD.Numeric(1, { min: 0, max: 10, step: 1 }),
|
||||
outlineThreshold: PD.Numeric(0.8, { min: 0, max: 1, step: 0.01 }),
|
||||
occlusion: PD.MappedStatic('off', {
|
||||
on: PD.Group({
|
||||
kernelSize: PD.Numeric(4, { min: 1, max: 32, step: 1 }),
|
||||
bias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
|
||||
radius: PD.Numeric(64, { min: 0, max: 256, step: 1 }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
|
||||
outline: PD.MappedStatic('off', {
|
||||
on: PD.Group({
|
||||
scale: PD.Numeric(1, { min: 0, max: 10, step: 1 }),
|
||||
threshold: PD.Numeric(0.8, { min: 0, max: 1, step: 0.01 }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Draw outline around 3D objects' })
|
||||
}
|
||||
export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
|
||||
|
||||
@@ -75,14 +83,14 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
|
||||
uFogFar: ValueCell.create(10000),
|
||||
uFogColor: ValueCell.create(Vec3.create(1, 1, 1)),
|
||||
|
||||
dOcclusionEnable: ValueCell.create(p.occlusionEnable),
|
||||
dOcclusionKernelSize: ValueCell.create(p.occlusionKernelSize),
|
||||
uOcclusionBias: ValueCell.create(p.occlusionBias),
|
||||
uOcclusionRadius: ValueCell.create(p.occlusionRadius),
|
||||
dOcclusionEnable: ValueCell.create(p.occlusion.name === 'on'),
|
||||
dOcclusionKernelSize: ValueCell.create(p.occlusion.name === 'on' ? p.occlusion.params.kernelSize : 4),
|
||||
uOcclusionBias: ValueCell.create(p.occlusion.name === 'on' ? p.occlusion.params.bias : 0.5),
|
||||
uOcclusionRadius: ValueCell.create(p.occlusion.name === 'on' ? p.occlusion.params.radius : 64),
|
||||
|
||||
dOutlineEnable: ValueCell.create(p.outlineEnable),
|
||||
uOutlineScale: ValueCell.create(p.outlineScale * ctx.pixelRatio),
|
||||
uOutlineThreshold: ValueCell.create(p.outlineThreshold),
|
||||
dOutlineEnable: ValueCell.create(p.outline.name === 'on'),
|
||||
uOutlineScale: ValueCell.create((p.outline.name === 'on' ? p.outline.params.scale : 1) * ctx.pixelRatio),
|
||||
uOutlineThreshold: ValueCell.create(p.outline.name === 'on' ? p.outline.params.threshold : 0.8),
|
||||
|
||||
dPackedDepth: ValueCell.create(packedDepth),
|
||||
}
|
||||
@@ -108,7 +116,7 @@ export class PostprocessingPass {
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
return this.props.occlusionEnable || this.props.outlineEnable
|
||||
return this.props.occlusion.name === 'on' || this.props.outline.name === 'on'
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
@@ -117,35 +125,28 @@ export class PostprocessingPass {
|
||||
}
|
||||
|
||||
setProps(props: Partial<PostprocessingProps>) {
|
||||
if (props.occlusionEnable !== undefined) {
|
||||
this.props.occlusionEnable = props.occlusionEnable
|
||||
ValueCell.update(this.renderable.values.dOcclusionEnable, props.occlusionEnable)
|
||||
}
|
||||
if (props.occlusionKernelSize !== undefined) {
|
||||
this.props.occlusionKernelSize = props.occlusionKernelSize
|
||||
ValueCell.update(this.renderable.values.dOcclusionKernelSize, props.occlusionKernelSize)
|
||||
}
|
||||
if (props.occlusionBias !== undefined) {
|
||||
this.props.occlusionBias = props.occlusionBias
|
||||
ValueCell.update(this.renderable.values.uOcclusionBias, props.occlusionBias)
|
||||
}
|
||||
if (props.occlusionRadius !== undefined) {
|
||||
this.props.occlusionRadius = props.occlusionRadius
|
||||
ValueCell.update(this.renderable.values.uOcclusionRadius, props.occlusionRadius)
|
||||
}
|
||||
this.props = produce(this.props, p => {
|
||||
if (props.occlusion !== undefined) {
|
||||
p.occlusion.name = props.occlusion.name
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, props.occlusion.name === 'on')
|
||||
if (props.occlusion.name === 'on') {
|
||||
p.occlusion.params = { ...props.occlusion.params }
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOcclusionKernelSize, props.occlusion.params.kernelSize)
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOcclusionBias, props.occlusion.params.bias)
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOcclusionRadius, props.occlusion.params.radius)
|
||||
}
|
||||
}
|
||||
|
||||
if (props.outlineEnable !== undefined) {
|
||||
this.props.outlineEnable = props.outlineEnable
|
||||
ValueCell.update(this.renderable.values.dOutlineEnable, props.outlineEnable)
|
||||
}
|
||||
if (props.outlineScale !== undefined) {
|
||||
this.props.outlineScale = props.outlineScale
|
||||
ValueCell.update(this.renderable.values.uOutlineScale, props.outlineScale * this.webgl.pixelRatio)
|
||||
}
|
||||
if (props.outlineThreshold !== undefined) {
|
||||
this.props.outlineThreshold = props.outlineThreshold
|
||||
ValueCell.update(this.renderable.values.uOutlineThreshold, props.outlineThreshold)
|
||||
}
|
||||
if (props.outline !== undefined) {
|
||||
p.outline.name = props.outline.name
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOutlineEnable, props.outline.name === 'on')
|
||||
if (props.outline.name === 'on') {
|
||||
p.outline.params = { ...props.outline.params }
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOutlineScale, props.outline.params.scale)
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOutlineThreshold, props.outline.params.threshold)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.renderable.update()
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { sortArray } from '../util/sort'
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
|
||||
/** A collection of columns */
|
||||
type Table<Schema extends Table.Schema> = {
|
||||
type Table<Schema extends Table.Schema = any> = {
|
||||
readonly _rowCount: number,
|
||||
readonly _columns: ReadonlyArray<string>,
|
||||
readonly _schema: Schema
|
||||
@@ -73,7 +73,7 @@ namespace Table {
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, rows: ArrayLike<Partial<Row<S>>>): R {
|
||||
export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: S, rows: ArrayLike<Partial<Row<S>>>): R {
|
||||
const ret = Object.create(null);
|
||||
const rowCount = rows.length;
|
||||
const columns = Object.keys(schema);
|
||||
@@ -91,14 +91,19 @@ namespace Table {
|
||||
return ret as R;
|
||||
}
|
||||
|
||||
export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, arrays: Arrays<S>): R {
|
||||
export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: S, arrays: Partial<Arrays<S>>): R {
|
||||
const ret = Object.create(null);
|
||||
const columns = Object.keys(schema);
|
||||
ret._rowCount = arrays[columns[0]].length;
|
||||
ret._rowCount = 0;
|
||||
ret._columns = columns;
|
||||
ret._schema = schema;
|
||||
for (const k of columns) {
|
||||
(ret as any)[k] = typeof arrays[k] !== 'undefined' ? Column.ofArray({ array: arrays[k], schema: schema[k] }) : Column.Undefined(ret._rowCount, schema[k]);
|
||||
if (typeof arrays[k] !== 'undefined') {
|
||||
(ret as any)[k] = Column.ofArray({ array: arrays[k]!, schema: schema[k] });
|
||||
ret._rowCount = arrays[k]?.length;
|
||||
} else {
|
||||
(ret as any)[k] = Column.Undefined(ret._rowCount, schema[k]);
|
||||
}
|
||||
}
|
||||
return ret as R;
|
||||
}
|
||||
@@ -167,7 +172,7 @@ namespace Table {
|
||||
}
|
||||
|
||||
/** Sort and return a new table */
|
||||
export function sort<T extends Table<S>, S extends Schema>(table: T, cmp: (i: number, j: number) => number) {
|
||||
export function sort<T extends Table>(table: T, cmp: (i: number, j: number) => number) {
|
||||
const indices = new Int32Array(table._rowCount);
|
||||
for (let i = 0, _i = indices.length; i < _i; i++) indices[i] = i;
|
||||
sortArray(indices, (_, i, j) => cmp(i, j));
|
||||
@@ -191,7 +196,7 @@ namespace Table {
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function areEqual<T extends Table<Schema>>(a: T, b: T) {
|
||||
export function areEqual<T extends Table<any>>(a: T, b: T) {
|
||||
if (a._rowCount !== b._rowCount) return false;
|
||||
if (a._columns.length !== b._columns.length) return false;
|
||||
for (const c of a._columns) {
|
||||
|
||||
@@ -28,18 +28,24 @@ export const VisualQualityInfo = {
|
||||
'lowest': {},
|
||||
}
|
||||
export type VisualQuality = keyof typeof VisualQualityInfo
|
||||
export const VisualQualityNames = Object.keys(VisualQualityInfo)
|
||||
export const VisualQualityOptions = VisualQualityNames.map(n => [n, n] as [VisualQuality, string])
|
||||
export const VisualQualityNames = Object.keys(VisualQualityInfo) as VisualQuality[]
|
||||
export const VisualQualityOptions = PD.arrayToOptions(VisualQualityNames)
|
||||
|
||||
//
|
||||
|
||||
export namespace BaseGeometry {
|
||||
export const Params = {
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity' }),
|
||||
quality: PD.Select<VisualQuality>('auto', VisualQualityOptions),
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }),
|
||||
quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }),
|
||||
}
|
||||
export type Params = typeof Params
|
||||
|
||||
export const ShadingCategory: PD.Info = { category: 'Shading' };
|
||||
export const CustomQualityParamInfo: PD.Info = {
|
||||
category: 'Custom Quality',
|
||||
hideIf: (params: PD.Values<Params>) => typeof params.quality !== 'undefined' && params.quality !== 'custom'
|
||||
};
|
||||
|
||||
export type Counts = { drawCount: number, groupCount: number, instanceCount: number }
|
||||
|
||||
export function createSimple(colorValue = ColorNames.grey, sizeValue = 1, transform?: TransformData) {
|
||||
|
||||
@@ -321,10 +321,10 @@ export namespace Mesh {
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
doubleSided: PD.Boolean(false),
|
||||
flipSided: PD.Boolean(false),
|
||||
flatShaded: PD.Boolean(false),
|
||||
ignoreLight: PD.Boolean(false),
|
||||
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
|
||||
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
}
|
||||
export type Params = typeof Params
|
||||
|
||||
|
||||
@@ -114,8 +114,8 @@ export namespace Spheres {
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
|
||||
doubleSided: PD.Boolean(false),
|
||||
ignoreLight: PD.Boolean(false),
|
||||
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
}
|
||||
export type Params = typeof Params
|
||||
|
||||
|
||||
@@ -70,9 +70,9 @@ export namespace TextureMesh {
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
doubleSided: PD.Boolean(false),
|
||||
flipSided: PD.Boolean(false),
|
||||
flatShaded: PD.Boolean(false),
|
||||
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
|
||||
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
}
|
||||
export type Params = typeof Params
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -166,6 +166,19 @@ class MarchingCubesState {
|
||||
const v1 = sfg(sf, hi, hj, hk);
|
||||
const t = (this.isoLevel - v0) / (v0 - v1);
|
||||
|
||||
if (this.idField) {
|
||||
const u = this.idFieldGet!(this.idField, li, lj, lk);
|
||||
const v = this.idFieldGet!(this.idField, hi, hj, hk)
|
||||
let a = t < 0.5 ? u : v;
|
||||
// -1 means 'no id', check if the other cell has an id
|
||||
if (a === -1) a = t < 0.5 ? v : u;
|
||||
// -2 means 'ignore this cell'
|
||||
if (a === -2) return -1
|
||||
this.builder.addGroup(a);
|
||||
} else {
|
||||
this.builder.addGroup(0);
|
||||
}
|
||||
|
||||
const id = this.builder.addVertex(
|
||||
li + t * (li - hi),
|
||||
lj + t * (lj - hj),
|
||||
@@ -189,16 +202,6 @@ class MarchingCubesState {
|
||||
n0z + t * (n0z - n1z)
|
||||
)
|
||||
|
||||
if (this.idField) {
|
||||
const u = this.idFieldGet!(this.idField, li, lj, lk);
|
||||
const v = this.idFieldGet!(this.idField, hi, hj, hk)
|
||||
let a = t < 0.5 ? u : v;
|
||||
if (a < 0) a = t < 0.5 ? v : u;
|
||||
this.builder.addGroup(a);
|
||||
} else {
|
||||
this.builder.addGroup(0);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,13 @@ export function MarchinCubesMeshBuilder(vertexChunkSize: number, mesh?: Mesh): M
|
||||
ChunkedArray.add(groups, group);
|
||||
},
|
||||
addTriangle: (vertList: number[], a: number, b: number, c: number) => {
|
||||
++triangleCount
|
||||
ChunkedArray.add3(indices, vertList[a], vertList[b], vertList[c]);
|
||||
const i = vertList[a], j = vertList[b], k = vertList[c]
|
||||
// vertex indices <0 mean that the vertex was ignored and is not available
|
||||
// and hence we don't add a triangle when this occurs
|
||||
if (i >= 0 && j >= 0 && k >= 0) {
|
||||
++triangleCount
|
||||
ChunkedArray.add3(indices, i, j, k)
|
||||
}
|
||||
},
|
||||
get: () => {
|
||||
const vb = ChunkedArray.compact(vertices, true) as Float32Array;
|
||||
@@ -73,17 +78,22 @@ export function MarchinCubesLinesBuilder(vertexChunkSize: number, lines?: Lines)
|
||||
ChunkedArray.add(groups, group);
|
||||
},
|
||||
addTriangle: (vertList: number[], a: number, b: number, c: number, edgeFilter: number) => {
|
||||
if (AllowedContours[a][b] & edgeFilter) {
|
||||
++linesCount
|
||||
ChunkedArray.add2(indices, vertList[a], vertList[b])
|
||||
}
|
||||
if (AllowedContours[b][c] & edgeFilter) {
|
||||
++linesCount
|
||||
ChunkedArray.add2(indices, vertList[b], vertList[c])
|
||||
}
|
||||
if (AllowedContours[a][c] & edgeFilter) {
|
||||
++linesCount
|
||||
ChunkedArray.add2(indices, vertList[a], vertList[c])
|
||||
const i = vertList[a], j = vertList[b], k = vertList[c]
|
||||
// vertex indices <0 mean that the vertex was ignored and is not available
|
||||
// and hence we don't add a triangle when this occurs
|
||||
if (i >= 0 && j >= 0 && k >= 0) {
|
||||
if (AllowedContours[a][b] & edgeFilter) {
|
||||
++linesCount
|
||||
ChunkedArray.add2(indices, vertList[a], vertList[b])
|
||||
}
|
||||
if (AllowedContours[b][c] & edgeFilter) {
|
||||
++linesCount
|
||||
ChunkedArray.add2(indices, vertList[b], vertList[c])
|
||||
}
|
||||
if (AllowedContours[a][c] & edgeFilter) {
|
||||
++linesCount
|
||||
ChunkedArray.add2(indices, vertList[a], vertList[c])
|
||||
}
|
||||
}
|
||||
},
|
||||
get: () => {
|
||||
|
||||
@@ -4,37 +4,39 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { calculateBoundingSphere } from '../renderable/util';
|
||||
// import { calculateBoundingSphere } from '../renderable/util';
|
||||
// import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
|
||||
describe('renderable', () => {
|
||||
it('calculateBoundingSphere', () => {
|
||||
const position = new Float32Array([
|
||||
0, 0, 0,
|
||||
1, 0, 0
|
||||
])
|
||||
const transform = new Float32Array([
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 0,
|
||||
// const position = new Float32Array([
|
||||
// 0, 0, 0,
|
||||
// 1, 0, 0
|
||||
// ])
|
||||
// const transform = new Float32Array([
|
||||
// 1, 0, 0, 0,
|
||||
// 0, 1, 0, 0,
|
||||
// 0, 0, 1, 0,
|
||||
// 0, 0, 0, 0,
|
||||
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
1, 0, 0, 0,
|
||||
// 1, 0, 0, 0,
|
||||
// 0, 1, 0, 0,
|
||||
// 0, 0, 1, 0,
|
||||
// 1, 0, 0, 0,
|
||||
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
2, 0, 0, 0
|
||||
])
|
||||
// 1, 0, 0, 0,
|
||||
// 0, 1, 0, 0,
|
||||
// 0, 0, 1, 0,
|
||||
// 2, 0, 0, 0
|
||||
// ])
|
||||
|
||||
const { boundingSphere } = calculateBoundingSphere(
|
||||
position, position.length / 3,
|
||||
transform, transform.length / 16
|
||||
)
|
||||
// const { boundingSphere } = calculateBoundingSphere(
|
||||
// position, position.length / 3,
|
||||
// transform, transform.length / 16
|
||||
// )
|
||||
|
||||
expect(boundingSphere.radius).toBe(1.5)
|
||||
expect(boundingSphere.center).toEqual([1.5, 0.0, 0.0])
|
||||
// TODO:
|
||||
// expect(boundingSphere.radius).toBeCloseTo(1.58, 2)
|
||||
// expect(Vec3.equals(boundingSphere.center, Vec3.create(1.418367, 0, 0))).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* 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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Sphere3D } from '../../mol-math/geometry'
|
||||
import { Vec3 } from '../../mol-math/linear-algebra'
|
||||
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
|
||||
import { BoundaryHelper, HierarchyHelper } from '../../mol-math/geometry/boundary-helper';
|
||||
|
||||
export function calculateTextureInfo (n: number, itemSize: number) {
|
||||
const sqN = Math.sqrt(n)
|
||||
@@ -66,7 +66,10 @@ export function printImageData(imageData: ImageData, scale = 1, pixelated = fals
|
||||
img.src = objectURL
|
||||
img.style.width = imageData.width * scale + 'px'
|
||||
img.style.height = imageData.height * scale + 'px';
|
||||
if (pixelated) (img.style as any).imageRendering = 'pixelated' // supported only in Chrome
|
||||
if (pixelated) {
|
||||
// not supported in Firefox and IE
|
||||
img.style.imageRendering = 'pixelated'
|
||||
}
|
||||
img.style.position = 'absolute'
|
||||
img.style.top = '0px'
|
||||
img.style.left = '0px'
|
||||
@@ -78,36 +81,79 @@ export function printImageData(imageData: ImageData, scale = 1, pixelated = fals
|
||||
//
|
||||
|
||||
const v = Vec3.zero()
|
||||
const boundaryHelper = new BoundaryHelper()
|
||||
const boundaryHelperCoarse = new BoundaryHelper('14')
|
||||
const boundaryHelperFine = new BoundaryHelper('98')
|
||||
const hierarchyHelperCoarse = new HierarchyHelper('14')
|
||||
const hierarchyHelperFine = new HierarchyHelper('98')
|
||||
|
||||
function getHelper(count: number) {
|
||||
return count > 500_000 ? {
|
||||
boundaryHelper: boundaryHelperCoarse,
|
||||
hierarchyHelper: hierarchyHelperCoarse
|
||||
} : {
|
||||
boundaryHelper: boundaryHelperFine,
|
||||
hierarchyHelper: hierarchyHelperFine
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateInvariantBoundingSphere(position: Float32Array, positionCount: number, stepFactor: number): Sphere3D {
|
||||
const step = stepFactor * 3
|
||||
boundaryHelper.reset(0)
|
||||
const { boundaryHelper, hierarchyHelper } = getHelper(positionCount)
|
||||
|
||||
boundaryHelper.reset()
|
||||
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
|
||||
Vec3.fromArray(v, position, i)
|
||||
boundaryHelper.boundaryStep(v, 0)
|
||||
boundaryHelper.includeStep(v)
|
||||
}
|
||||
boundaryHelper.finishBoundaryStep()
|
||||
boundaryHelper.finishedIncludeStep()
|
||||
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
|
||||
Vec3.fromArray(v, position, i)
|
||||
boundaryHelper.extendStep(v, 0)
|
||||
boundaryHelper.radiusStep(v)
|
||||
}
|
||||
|
||||
const hierarchyInput = boundaryHelper.getHierarchyInput()
|
||||
if (hierarchyInput) {
|
||||
hierarchyHelper.reset(hierarchyInput.sphere, hierarchyInput.normal)
|
||||
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
|
||||
Vec3.fromArray(v, position, i)
|
||||
hierarchyHelper.includeStep(v)
|
||||
}
|
||||
hierarchyHelper.finishedIncludeStep()
|
||||
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
|
||||
Vec3.fromArray(v, position, i)
|
||||
hierarchyHelper.radiusStep(v)
|
||||
}
|
||||
return hierarchyHelper.getSphere()
|
||||
} else {
|
||||
return boundaryHelper.getSphere()
|
||||
}
|
||||
return boundaryHelper.getSphere()
|
||||
}
|
||||
|
||||
export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere3D, transform: Float32Array, transformCount: number): Sphere3D {
|
||||
const { center, radius } = invariantBoundingSphere
|
||||
boundaryHelper.reset(0)
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
|
||||
boundaryHelper.boundaryStep(v, radius)
|
||||
const { boundaryHelper } = getHelper(transformCount)
|
||||
boundaryHelper.reset()
|
||||
|
||||
const transformedSpheres: Sphere3D[] = []
|
||||
for (const b of Sphere3D.getList(invariantBoundingSphere)) {
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
const c = Vec3.transformMat4Offset(Vec3(), b.center, transform, 0, 0, i * 16)
|
||||
transformedSpheres.push(Sphere3D.create(c as Vec3, b.radius))
|
||||
}
|
||||
}
|
||||
boundaryHelper.finishBoundaryStep()
|
||||
for (let i = 0, _i = transformCount; i < _i; ++i) {
|
||||
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
|
||||
boundaryHelper.extendStep(v, radius)
|
||||
|
||||
for (const b of transformedSpheres) {
|
||||
boundaryHelper.includeSphereStep(b.center, b.radius)
|
||||
}
|
||||
return boundaryHelper.getSphere()
|
||||
boundaryHelper.finishedIncludeStep()
|
||||
for (const b of transformedSpheres) {
|
||||
boundaryHelper.radiusSphereStep(b.center, b.radius)
|
||||
}
|
||||
|
||||
const sphere = boundaryHelper.getSphere()
|
||||
if (transformedSpheres.length > 1) {
|
||||
(sphere as Sphere3D.Hierarchy).hierarchy = transformedSpheres
|
||||
}
|
||||
return sphere
|
||||
}
|
||||
|
||||
export function calculateBoundingSphere(position: Float32Array, positionCount: number, transform: Float32Array, transformCount: number, padding = 0, stepFactor = 1): { boundingSphere: Sphere3D, invariantBoundingSphere: Sphere3D } {
|
||||
|
||||
@@ -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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -55,22 +55,63 @@ export const RendererParams = {
|
||||
interiorColorFlag: PD.Boolean(true, { label: 'Use Interior Color' }),
|
||||
interiorColor: PD.Color(Color.fromNormalizedRgb(0.3, 0.3, 0.3)),
|
||||
|
||||
lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
|
||||
metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
|
||||
highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
|
||||
selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
|
||||
|
||||
style: PD.MappedStatic('matte', {
|
||||
custom: PD.Group({
|
||||
lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
}, { isExpanded: true }),
|
||||
flat: PD.Group({}),
|
||||
matte: PD.Group({}),
|
||||
glossy: PD.Group({}),
|
||||
metallic: PD.Group({}),
|
||||
plastic: PD.Group({}),
|
||||
}, { label: 'Lighting', description: 'Style in which the 3D scene is rendered/lighted' }),
|
||||
}
|
||||
export type RendererProps = PD.Values<typeof RendererParams>
|
||||
|
||||
function getStyle(props: RendererProps['style']) {
|
||||
switch (props.name) {
|
||||
case 'custom':
|
||||
return props.params
|
||||
case 'flat':
|
||||
return {
|
||||
lightIntensity: 0, ambientIntensity: 1,
|
||||
metalness: 0, roughness: 0.4, reflectivity: 0.5
|
||||
}
|
||||
case 'matte':
|
||||
return {
|
||||
lightIntensity: 0.6, ambientIntensity: 0.4,
|
||||
metalness: 0, roughness: 1, reflectivity: 0.5
|
||||
}
|
||||
case 'glossy':
|
||||
return {
|
||||
lightIntensity: 0.6, ambientIntensity: 0.4,
|
||||
metalness: 0, roughness: 0.4, reflectivity: 0.5
|
||||
}
|
||||
case 'metallic':
|
||||
return {
|
||||
lightIntensity: 0.6, ambientIntensity: 0.4,
|
||||
metalness: 0.4, roughness: 0.6, reflectivity: 0.5
|
||||
}
|
||||
case 'plastic':
|
||||
return {
|
||||
lightIntensity: 0.6, ambientIntensity: 0.4,
|
||||
metalness: 0, roughness: 0.2, reflectivity: 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Renderer {
|
||||
export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
|
||||
const { gl, state, stats } = ctx
|
||||
const p = deepClone({ ...PD.getDefaultValues(RendererParams), ...props })
|
||||
const style = getStyle(p.style)
|
||||
|
||||
const viewport = Viewport()
|
||||
const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor)
|
||||
@@ -112,12 +153,12 @@ namespace Renderer {
|
||||
uTransparentBackground: ValueCell.create(0),
|
||||
|
||||
// the following are general 'material' uniforms
|
||||
uLightIntensity: ValueCell.create(p.lightIntensity),
|
||||
uAmbientIntensity: ValueCell.create(p.ambientIntensity),
|
||||
uLightIntensity: ValueCell.create(style.lightIntensity),
|
||||
uAmbientIntensity: ValueCell.create(style.ambientIntensity),
|
||||
|
||||
uMetalness: ValueCell.create(p.metalness),
|
||||
uRoughness: ValueCell.create(p.roughness),
|
||||
uReflectivity: ValueCell.create(p.reflectivity),
|
||||
uMetalness: ValueCell.create(style.metalness),
|
||||
uRoughness: ValueCell.create(style.roughness),
|
||||
uReflectivity: ValueCell.create(style.reflectivity),
|
||||
|
||||
uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
|
||||
|
||||
@@ -276,28 +317,6 @@ namespace Renderer {
|
||||
ValueCell.update(globalUniforms.uInteriorColor, Color.toVec3Normalized(globalUniforms.uInteriorColor.ref.value, p.interiorColor))
|
||||
}
|
||||
|
||||
if (props.lightIntensity !== undefined && props.lightIntensity !== p.lightIntensity) {
|
||||
p.lightIntensity = props.lightIntensity
|
||||
ValueCell.update(globalUniforms.uLightIntensity, p.lightIntensity)
|
||||
}
|
||||
if (props.ambientIntensity !== undefined && props.ambientIntensity !== p.ambientIntensity) {
|
||||
p.ambientIntensity = props.ambientIntensity
|
||||
ValueCell.update(globalUniforms.uAmbientIntensity, p.ambientIntensity)
|
||||
}
|
||||
|
||||
if (props.metalness !== undefined && props.metalness !== p.metalness) {
|
||||
p.metalness = props.metalness
|
||||
ValueCell.update(globalUniforms.uMetalness, p.metalness)
|
||||
}
|
||||
if (props.roughness !== undefined && props.roughness !== p.roughness) {
|
||||
p.roughness = props.roughness
|
||||
ValueCell.update(globalUniforms.uRoughness, p.roughness)
|
||||
}
|
||||
if (props.reflectivity !== undefined && props.reflectivity !== p.reflectivity) {
|
||||
p.reflectivity = props.reflectivity
|
||||
ValueCell.update(globalUniforms.uReflectivity, p.reflectivity)
|
||||
}
|
||||
|
||||
if (props.highlightColor !== undefined && props.highlightColor !== p.highlightColor) {
|
||||
p.highlightColor = props.highlightColor
|
||||
ValueCell.update(globalUniforms.uHighlightColor, Color.toVec3Normalized(globalUniforms.uHighlightColor.ref.value, p.highlightColor))
|
||||
@@ -306,6 +325,16 @@ namespace Renderer {
|
||||
p.selectColor = props.selectColor
|
||||
ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor))
|
||||
}
|
||||
|
||||
if (props.style !== undefined) {
|
||||
p.style = props.style
|
||||
Object.assign(style, getStyle(props.style))
|
||||
ValueCell.updateIfChanged(globalUniforms.uLightIntensity, style.lightIntensity)
|
||||
ValueCell.updateIfChanged(globalUniforms.uAmbientIntensity, style.ambientIntensity)
|
||||
ValueCell.updateIfChanged(globalUniforms.uMetalness, style.metalness)
|
||||
ValueCell.updateIfChanged(globalUniforms.uRoughness, style.roughness)
|
||||
ValueCell.updateIfChanged(globalUniforms.uReflectivity, style.reflectivity)
|
||||
}
|
||||
},
|
||||
setViewport: (x: number, y: number, width: number, height: number) => {
|
||||
gl.viewport(x, y, width, height)
|
||||
|
||||
@@ -11,32 +11,35 @@ import { RenderableValues, BaseValues } from './renderable/schema';
|
||||
import { GraphicsRenderObject, createRenderable } from './render-object';
|
||||
import { Object3D } from './object3d';
|
||||
import { Sphere3D } from '../mol-math/geometry';
|
||||
import { Vec3 } from '../mol-math/linear-algebra';
|
||||
import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
|
||||
import { CommitQueue } from './commit-queue';
|
||||
import { now } from '../mol-util/now';
|
||||
import { arraySetRemove } from '../mol-util/array';
|
||||
import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
|
||||
|
||||
const boundaryHelper = new BoundaryHelper('98')
|
||||
|
||||
const boundaryHelper = new BoundaryHelper();
|
||||
function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
|
||||
boundaryHelper.reset(0.1);
|
||||
boundaryHelper.reset();
|
||||
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const boundingSphere = renderables[i].values.boundingSphere.ref.value
|
||||
if (!boundingSphere.radius) continue;
|
||||
boundaryHelper.boundaryStep(boundingSphere.center, boundingSphere.radius);
|
||||
|
||||
for (const b of Sphere3D.getList(boundingSphere)) {
|
||||
boundaryHelper.includeSphereStep(b.center, b.radius);
|
||||
}
|
||||
}
|
||||
boundaryHelper.finishBoundaryStep();
|
||||
boundaryHelper.finishedIncludeStep();
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const boundingSphere = renderables[i].values.boundingSphere.ref.value
|
||||
if (!boundingSphere.radius) continue;
|
||||
boundaryHelper.extendStep(boundingSphere.center, boundingSphere.radius);
|
||||
|
||||
for (const b of Sphere3D.getList(boundingSphere)) {
|
||||
boundaryHelper.radiusSphereStep(b.center, b.radius);
|
||||
}
|
||||
}
|
||||
|
||||
Vec3.copy(boundingSphere.center, boundaryHelper.center);
|
||||
boundingSphere.radius = boundaryHelper.radius;
|
||||
|
||||
return boundingSphere;
|
||||
return boundaryHelper.getSphere(boundingSphere);
|
||||
}
|
||||
|
||||
function renderableSort(a: Renderable<RenderableValues & BaseValues>, b: Renderable<RenderableValues & BaseValues>) {
|
||||
@@ -126,8 +129,6 @@ namespace Scene {
|
||||
return true;
|
||||
}
|
||||
|
||||
// const toAdd: GraphicsRenderObject[] = []
|
||||
// const toRemove: GraphicsRenderObject[] = []
|
||||
const commitQueue = new CommitQueue();
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
export default `
|
||||
// inputs
|
||||
// - vViewPosition (if dFlatShaded)
|
||||
// - vNormal (if NOT dFlatShaded)
|
||||
|
||||
// outputs
|
||||
// - normal
|
||||
|
||||
// surface normal
|
||||
#if defined(dFlatShaded) && defined(enabledStandardDerivatives)
|
||||
vec3 fdx = dFdx(vViewPosition);
|
||||
vec3 fdy = dFdy(vViewPosition);
|
||||
@@ -14,7 +6,7 @@ export default `
|
||||
#else
|
||||
vec3 normal = -normalize(vNormal);
|
||||
#ifdef dDoubleSided
|
||||
normal = normal * (float(gl_FrontFacing) * 2.0 - 1.0);
|
||||
normal = normal * (float(frontFacing) * 2.0 - 1.0);
|
||||
#endif
|
||||
#endif
|
||||
`
|
||||
@@ -1,5 +1,3 @@
|
||||
export default `
|
||||
#if !defined(dFlatShaded) || !defined(enabledStandardDerivatives)
|
||||
varying vec3 vNormal;
|
||||
#endif
|
||||
varying vec3 vNormal;
|
||||
`
|
||||
@@ -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 Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -15,7 +15,18 @@ precision highp int;
|
||||
#include normal_frag_params
|
||||
|
||||
void main() {
|
||||
interior = !gl_FrontFacing; // TODO take dFlipSided into account
|
||||
// Workaround for buggy gl_FrontFacing (e.g. on some integrated Intel GPUs)
|
||||
#if defined(enabledStandardDerivatives)
|
||||
vec3 fdx = dFdx(vViewPosition);
|
||||
vec3 fdy = dFdy(vViewPosition);
|
||||
vec3 faceNormal = normalize(cross(fdx,fdy));
|
||||
bool frontFacing = dot(vNormal, faceNormal) > 0.0;
|
||||
#else
|
||||
bool frontFacing = dot(vNormal, vViewPosition) < 0.0;
|
||||
#endif
|
||||
|
||||
interior = !frontFacing; // TODO take dFlipSided into account
|
||||
|
||||
#include assign_material_color
|
||||
|
||||
#if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
|
||||
|
||||
@@ -23,14 +23,12 @@ attribute mat4 aTransform;
|
||||
attribute float aInstance;
|
||||
attribute float aGroup;
|
||||
|
||||
#if !defined(dFlatShaded) || !defined(enabledStandardDerivatives) || defined(dIgnoreLight)
|
||||
#ifdef dGeoTexture
|
||||
uniform sampler2D tNormal;
|
||||
#else
|
||||
attribute vec3 aNormal;
|
||||
#endif
|
||||
varying vec3 vNormal;
|
||||
#ifdef dGeoTexture
|
||||
uniform sampler2D tNormal;
|
||||
#else
|
||||
attribute vec3 aNormal;
|
||||
#endif
|
||||
varying vec3 vNormal;
|
||||
|
||||
void main(){
|
||||
#include assign_group
|
||||
@@ -38,18 +36,16 @@ void main(){
|
||||
#include assign_marker_varying
|
||||
#include assign_position
|
||||
|
||||
#if !defined(dFlatShaded) || !defined(enabledStandardDerivatives) || defined(dIgnoreLight)
|
||||
#ifdef dGeoTexture
|
||||
vec3 normal = readFromTexture(tNormal, aGroup, uGeoTexDim).xyz;
|
||||
#else
|
||||
vec3 normal = aNormal;
|
||||
#endif
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(modelView)));
|
||||
vec3 transformedNormal = normalize(normalMatrix * normalize(normal));
|
||||
#if defined(dFlipSided) && !defined(dDoubleSided) // TODO checking dDoubleSided should not be required, ASR
|
||||
transformedNormal = -transformedNormal;
|
||||
#endif
|
||||
vNormal = transformedNormal;
|
||||
#ifdef dGeoTexture
|
||||
vec3 normal = readFromTexture(tNormal, aGroup, uGeoTexDim).xyz;
|
||||
#else
|
||||
vec3 normal = aNormal;
|
||||
#endif
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(modelView)));
|
||||
vec3 transformedNormal = normalize(normalMatrix * normalize(normal));
|
||||
#if defined(dFlipSided) && !defined(dDoubleSided) // TODO checking dDoubleSided should not be required, ASR
|
||||
transformedNormal = -transformedNormal;
|
||||
#endif
|
||||
vNormal = transformedNormal;
|
||||
}
|
||||
`
|
||||
@@ -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.07, CARB draft.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
|
||||
*
|
||||
* @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 'CCD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.07, CARB draft.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
|
||||
*
|
||||
* @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.07, CARB draft.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -199,7 +199,7 @@ export namespace Category {
|
||||
getFormat(cat, field) { return void 0; }
|
||||
}
|
||||
|
||||
export function ofTable(table: Table<Table.Schema>, indices?: ArrayLike<number>): Category.Instance {
|
||||
export function ofTable(table: Table, indices?: ArrayLike<number>): Category.Instance {
|
||||
if (indices) {
|
||||
return {
|
||||
fields: cifFieldsFromTableSchema(table._schema),
|
||||
|
||||
36
src/mol-math/geometry/_spec/spacegroup.spec.ts
Normal file
36
src/mol-math/geometry/_spec/spacegroup.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Spacegroup, SpacegroupCell } from '../spacegroup/construction';
|
||||
import { Vec3 } from '../../linear-algebra';
|
||||
|
||||
function getSpacegroup(name: string) {
|
||||
const size = Vec3.create(1, 1, 1)
|
||||
const anglesInRadians = Vec3.create(Math.PI / 2, Math.PI / 2, Math.PI / 2)
|
||||
const cell = SpacegroupCell.create(name, size, anglesInRadians)
|
||||
return Spacegroup.create(cell)
|
||||
}
|
||||
|
||||
function checkOperatorsXyz(name: string, expected: string[]) {
|
||||
const spacegroup = getSpacegroup(name)
|
||||
for (let i = 0, il = spacegroup.operators.length; i < il; ++i) {
|
||||
const op = spacegroup.operators[i]
|
||||
const actual = Spacegroup.getOperatorXyz(op)
|
||||
expect(actual).toBe(expected[i])
|
||||
}
|
||||
}
|
||||
|
||||
describe('Spacegroup', () => {
|
||||
it('operators xyz', () => {
|
||||
checkOperatorsXyz('P 1', ['X,Y,Z'])
|
||||
checkOperatorsXyz('P -1', ['X,Y,Z', '-X,-Y,-Z'])
|
||||
checkOperatorsXyz('P 1 21 1', ['X,Y,Z', '-X,1/2+Y,-Z'])
|
||||
checkOperatorsXyz('P 1 21/m 1', ['X,Y,Z', '-X,1/2+Y,-Z', '-X,-Y,-Z', 'X,1/2-Y,Z'])
|
||||
checkOperatorsXyz('P 41', ['X,Y,Z', '-X,-Y,1/2+Z', '-Y,X,1/4+Z', 'Y,-X,3/4+Z'])
|
||||
checkOperatorsXyz('P 41 21 2', ['X,Y,Z', '-X,-Y,1/2+Z', '1/2-Y,1/2+X,1/4+Z', '1/2+Y,1/2-X,3/4+Z', '1/2-X,1/2+Y,1/4-Z', '1/2+X,1/2-Y,3/4-Z', 'Y,X,-Z', '-Y,-X,1/2-Z'])
|
||||
checkOperatorsXyz('P 3', ['X,Y,Z', '-Y,X-Y,Z', 'Y-X,-X,Z'])
|
||||
});
|
||||
})
|
||||
@@ -1,124 +1,223 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 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 { Vec3 } from '../../mol-math/linear-algebra/3d';
|
||||
import { Vec3 } from '../linear-algebra/3d';
|
||||
import { CentroidHelper } from './centroid-helper';
|
||||
import { Sphere3D } from '../geometry';
|
||||
import { Box3D } from './primitives/box3d';
|
||||
import { Sphere3D } from './primitives/sphere3d';
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
*
|
||||
* 1. .reset(tolerance); tolerance plays part in the "extend" step
|
||||
* 2. for each point/sphere call boundaryStep()
|
||||
* 3. .finishBoundaryStep
|
||||
* 4. for each point/sphere call extendStep
|
||||
* 5. use .center/.radius or call getSphere/getBox
|
||||
*/
|
||||
// implementing http://www.ep.liu.se/ecp/034/009/ecp083409.pdf
|
||||
|
||||
const MinThresholdDist = 0.1
|
||||
|
||||
export class BoundaryHelper {
|
||||
private count = 0;
|
||||
private extremes = [Vec3.zero(), Vec3.zero(), Vec3.zero(), Vec3.zero(), Vec3.zero(), Vec3.zero()];
|
||||
private u = Vec3.zero();
|
||||
private v = Vec3.zero();
|
||||
private dir: Vec3[]
|
||||
|
||||
tolerance = 0;
|
||||
center: Vec3 = Vec3.zero();
|
||||
radius = 0;
|
||||
private minDist: number[] = []
|
||||
private maxDist: number[] = []
|
||||
private extrema: Vec3[] = []
|
||||
centroidHelper = new CentroidHelper()
|
||||
|
||||
reset(tolerance: number) {
|
||||
Vec3.set(this.center, 0, 0, 0);
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const e = i % 2 === 0 ? Number.MAX_VALUE : -Number.MAX_VALUE;
|
||||
Vec3.set(this.extremes[i], e, e, e);
|
||||
private computeExtrema(i: number, p: Vec3) {
|
||||
const d = Vec3.dot(this.dir[i], p)
|
||||
|
||||
if (d < this.minDist[i]) {
|
||||
this.minDist[i] = d
|
||||
Vec3.copy(this.extrema[i * 2], p)
|
||||
}
|
||||
if (d > this.maxDist[i]) {
|
||||
this.maxDist[i] = d
|
||||
Vec3.copy(this.extrema[i * 2 + 1], p)
|
||||
}
|
||||
this.radius = 0;
|
||||
this.count = 0;
|
||||
this.tolerance = tolerance;
|
||||
}
|
||||
|
||||
boundaryStep(p: Vec3, r: number) {
|
||||
updateExtremeMin(0, this.extremes[0], p, r);
|
||||
updateExtremeMax(0, this.extremes[1], p, r);
|
||||
private computeSphereExtrema(i: number, center: Vec3, radius: number) {
|
||||
const d = Vec3.dot(this.dir[i], center)
|
||||
|
||||
updateExtremeMin(1, this.extremes[2], p, r);
|
||||
updateExtremeMax(1, this.extremes[3], p, r);
|
||||
|
||||
updateExtremeMin(2, this.extremes[4], p, r);
|
||||
updateExtremeMax(2, this.extremes[5], p, r);
|
||||
this.count++;
|
||||
if (d - radius < this.minDist[i]) {
|
||||
this.minDist[i] = d - radius
|
||||
Vec3.scaleAndSub(this.extrema[i * 2], center, this.dir[i], radius)
|
||||
}
|
||||
if (d + radius > this.maxDist[i]) {
|
||||
this.maxDist[i] = d + radius
|
||||
Vec3.scaleAndAdd(this.extrema[i * 2 + 1], center, this.dir[i], radius)
|
||||
}
|
||||
}
|
||||
|
||||
finishBoundaryStep() {
|
||||
if (this.count === 0) return;
|
||||
includeStep(p: Vec3) {
|
||||
for (let i = 0, il = this.dir.length; i < il; ++i) {
|
||||
this.computeExtrema(i, p)
|
||||
}
|
||||
}
|
||||
|
||||
let maxSpan = 0, mI = 0, mJ = 0;
|
||||
includeSphereStep(center: Vec3, radius: number) {
|
||||
for (let i = 0, il = this.dir.length; i < il; ++i) {
|
||||
this.computeSphereExtrema(i, center, radius)
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
for (let j = i + 1; j < 6; j++) {
|
||||
const d = Vec3.squaredDistance(this.extremes[i], this.extremes[j]);
|
||||
if (d > maxSpan) {
|
||||
maxSpan = d;
|
||||
mI = i;
|
||||
mJ = j;
|
||||
}
|
||||
finishedIncludeStep() {
|
||||
for (let i = 0; i < this.extrema.length; i++) {
|
||||
this.centroidHelper.includeStep(this.extrema[i]);
|
||||
}
|
||||
this.centroidHelper.finishedIncludeStep();
|
||||
}
|
||||
|
||||
radiusStep(p: Vec3) {
|
||||
this.centroidHelper.radiusStep(p);
|
||||
}
|
||||
|
||||
radiusSphereStep(center: Vec3, radius: number) {
|
||||
this.centroidHelper.radiusSphereStep(center, radius);
|
||||
}
|
||||
|
||||
getHierarchyInput() {
|
||||
if (this.centroidHelper.getCount() < 2) return false
|
||||
|
||||
const sphere = this.centroidHelper.getSphere();
|
||||
const normal = Vec3()
|
||||
const t = sphere.radius * this.hierarchyThresholdFactor
|
||||
|
||||
let maxDist = -Infinity
|
||||
let belowThreshold = false
|
||||
|
||||
for (let i = 0; i < this.extrema.length; i += 2) {
|
||||
const halfDist = Vec3.distance(this.extrema[i], this.extrema[i + 1]) / 2
|
||||
if (halfDist > maxDist) {
|
||||
maxDist = halfDist
|
||||
Vec3.normalize(normal, Vec3.sub(normal, this.extrema[i], this.extrema[i + 1]))
|
||||
}
|
||||
if (halfDist < t) belowThreshold = true
|
||||
}
|
||||
|
||||
Vec3.add(this.center, this.extremes[mI], this.extremes[mJ]);
|
||||
Vec3.scale(this.center, this.center, 0.5);
|
||||
this.radius = Vec3.distance(this.center, this.extremes[mI]);
|
||||
return (belowThreshold && maxDist > MinThresholdDist) ? { sphere, normal } : false
|
||||
}
|
||||
|
||||
extendStep(p: Vec3, r: number) {
|
||||
const d = Vec3.distance(p, this.center);
|
||||
if ((1 + this.tolerance) * this.radius >= r + d) return;
|
||||
|
||||
Vec3.sub(this.u, p, this.center);
|
||||
Vec3.normalize(this.u, this.u);
|
||||
|
||||
Vec3.scale(this.v, this.u, -this.radius);
|
||||
Vec3.add(this.v, this.v, this.center);
|
||||
Vec3.scale(this.u, this.u, r + d);
|
||||
Vec3.add(this.u, this.u, this.center);
|
||||
|
||||
Vec3.add(this.center, this.u, this.v);
|
||||
Vec3.scale(this.center, this.center, 0.5);
|
||||
this.radius = 0.5 * (r + d + this.radius);
|
||||
getSphere(sphere?: Sphere3D) {
|
||||
return this.centroidHelper.getSphere(sphere)
|
||||
}
|
||||
|
||||
getBox(): Box3D {
|
||||
Vec3.copy(this.u, this.extremes[0]);
|
||||
Vec3.copy(this.v, this.extremes[0]);
|
||||
getBox(box?: Box3D) {
|
||||
// TODO can we get a tighter box from the extrema???
|
||||
if (!box) box = Box3D()
|
||||
return Box3D.fromSphere3D(box, this.centroidHelper.getSphere())
|
||||
}
|
||||
|
||||
for (let i = 1; i < 6; i++) {
|
||||
Vec3.min(this.u, this.u, this.extremes[i]);
|
||||
Vec3.max(this.v, this.v, this.extremes[i]);
|
||||
reset() {
|
||||
for (let i = 0, il = this.dir.length; i < il; ++i) {
|
||||
this.minDist[i] = Infinity
|
||||
this.maxDist[i] = -Infinity
|
||||
this.extrema[i * 2] = Vec3()
|
||||
this.extrema[i * 2 + 1] = Vec3()
|
||||
}
|
||||
this.centroidHelper.reset()
|
||||
}
|
||||
|
||||
return { min: Vec3.clone(this.u), max: Vec3.clone(this.v) };
|
||||
constructor(quality: EposQuality, private hierarchyThresholdFactor = 0.66) {
|
||||
this.dir = getEposDir(quality)
|
||||
this.reset()
|
||||
}
|
||||
}
|
||||
|
||||
export class HierarchyHelper {
|
||||
private tmpV = Vec3()
|
||||
private tmpS = Sphere3D()
|
||||
|
||||
private sphere = Sphere3D()
|
||||
private normal = Vec3()
|
||||
private helperA = new BoundaryHelper(this.quality)
|
||||
private helperB = new BoundaryHelper(this.quality)
|
||||
|
||||
private checkSide(p: Vec3) {
|
||||
return Vec3.dot(this.normal, Vec3.sub(this.tmpV, this.sphere.center, p)) > 0
|
||||
}
|
||||
|
||||
includeStep(p: Vec3) {
|
||||
if (this.checkSide(p)) {
|
||||
this.helperA.includeStep(p)
|
||||
} else {
|
||||
this.helperB.includeStep(p)
|
||||
}
|
||||
}
|
||||
|
||||
finishedIncludeStep() {
|
||||
this.helperA.finishedIncludeStep();
|
||||
this.helperB.finishedIncludeStep();
|
||||
}
|
||||
|
||||
radiusStep(p: Vec3) {
|
||||
if (this.checkSide(p)) {
|
||||
this.helperA.radiusStep(p)
|
||||
} else {
|
||||
this.helperB.radiusStep(p)
|
||||
}
|
||||
}
|
||||
|
||||
getSphere(): Sphere3D {
|
||||
return { center: Vec3.clone(this.center), radius: this.radius };
|
||||
const sphereA = this.helperA.getSphere()
|
||||
const sphereB = this.helperB.getSphere()
|
||||
Sphere3D.expandBySphere(this.tmpS, this.sphere, sphereA)
|
||||
Sphere3D.expandBySphere(this.tmpS, this.tmpS, sphereB)
|
||||
// check if the split spheres actually result in a smaller radius
|
||||
return this.tmpS.radius < this.sphere.radius ? {
|
||||
center: Vec3.clone(this.sphere.center),
|
||||
radius: this.sphere.radius,
|
||||
hierarchy: [this.helperA.getSphere(), this.helperB.getSphere()]
|
||||
} : Sphere3D.clone(this.sphere)
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.reset(0);
|
||||
reset(sphere: Sphere3D, normal: Vec3) {
|
||||
Sphere3D.copy(this.sphere, sphere)
|
||||
Vec3.copy(this.normal, normal)
|
||||
this.helperA.reset()
|
||||
this.helperB.reset()
|
||||
}
|
||||
|
||||
constructor(private quality: EposQuality) { }
|
||||
}
|
||||
|
||||
function updateExtremeMin(d: number, e: Vec3, center: Vec3, r: number) {
|
||||
if (center[d] - r < e[d]) {
|
||||
Vec3.copy(e, center);
|
||||
e[d] -= r;
|
||||
type EposQuality = '6' | '14' | '26' | '98'
|
||||
|
||||
function getEposDir(quality: EposQuality) {
|
||||
let dir: number[][]
|
||||
switch (quality) {
|
||||
case '6': dir = [ ...Type001 ]; break
|
||||
case '14': dir = [ ...Type001, ...Type111 ]; break
|
||||
case '26': dir = [ ...Type001, ...Type111, ...Type011 ]; break
|
||||
case '98': dir = [ ...Type001, ...Type111, ...Type011, ...Type012, ...Type112, ...Type122 ]; break
|
||||
}
|
||||
return dir.map(a => {
|
||||
const v = Vec3.create(a[0], a[1], a[2])
|
||||
return Vec3.normalize(v, v)
|
||||
})
|
||||
}
|
||||
|
||||
function updateExtremeMax(d: number, e: Vec3, center: Vec3, r: number) {
|
||||
if (center[d] + r > e[d]) {
|
||||
Vec3.copy(e, center);
|
||||
e[d] += r;
|
||||
}
|
||||
}
|
||||
const Type001 = [
|
||||
[1, 0, 0], [0, 1, 0], [0, 0, 1]
|
||||
]
|
||||
|
||||
const Type111 = [
|
||||
[1, 1, 1], [-1, 1, 1], [-1, -1, 1], [1, -1, 1]
|
||||
]
|
||||
|
||||
const Type011 = [
|
||||
[1, 1, 0], [1, -1, 0], [1, 0, 1], [1, 0, -1], [0, 1, 1], [0, 1, -1]
|
||||
]
|
||||
|
||||
const Type012 = [
|
||||
[0, 1, 2], [0, 2, 1], [1, 0, 2], [2, 0, 1], [1, 2, 0], [2, 1, 0],
|
||||
[0, 1, -2], [0, 2, -1], [1, 0, -2], [2, 0, -1], [1, -2, 0], [2, -1, 0]
|
||||
]
|
||||
|
||||
const Type112 = [
|
||||
[1, 1, 2], [2, 1, 1], [1, 2, 1], [1, -1, 2], [1, 1, -2], [1, -1, -2],
|
||||
[2, -1, 1], [2, 1, -1], [2, -1, -1], [1, -2, 1], [1, 2, -1], [1, -2, -1]
|
||||
]
|
||||
|
||||
const Type122 = [
|
||||
[2, 2, 1], [1, 2, 2], [2, 1, 2], [2, -2, 1], [2, 2, -1], [2, -2, -1],
|
||||
[1, -2, 2], [1, 2, -2], [1, -2, -2], [2, -1, 2], [2, 1, -2], [2, -1, -2]
|
||||
]
|
||||
@@ -37,13 +37,24 @@ class CentroidHelper {
|
||||
if (d > this.radiusSq) this.radiusSq = d;
|
||||
}
|
||||
|
||||
getSphere(): Sphere3D {
|
||||
return { center: Vec3.clone(this.center), radius: Math.sqrt(this.radiusSq) };
|
||||
radiusSphereStep(center: Vec3, radius: number) {
|
||||
const _d = Vec3.distance(center, this.center) + radius;
|
||||
const d = _d * _d;
|
||||
if (d > this.radiusSq) this.radiusSq = d;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
|
||||
getSphere(sphere?: Sphere3D): Sphere3D {
|
||||
if (!sphere) sphere = Sphere3D()
|
||||
Vec3.copy(sphere.center, this.center)
|
||||
sphere.radius = Math.sqrt(this.radiusSq)
|
||||
return sphere
|
||||
}
|
||||
|
||||
getCount() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
constructor() { }
|
||||
}
|
||||
|
||||
namespace CentroidHelper {
|
||||
|
||||
@@ -166,21 +166,27 @@ function _build(state: BuildState): Grid3D {
|
||||
}
|
||||
}
|
||||
|
||||
const boundaryHelper = new BoundaryHelper();
|
||||
const boundaryHelperCoarse = new BoundaryHelper('14');
|
||||
const boundaryHelperFine = new BoundaryHelper('98');
|
||||
function getBoundaryHelper(count: number) {
|
||||
return count > 500_000 ? boundaryHelperCoarse : boundaryHelperFine
|
||||
}
|
||||
|
||||
function getBoundary(data: PositionData) {
|
||||
const { x, y, z, radius, indices } = data;
|
||||
const p = Vec3();
|
||||
boundaryHelper.reset(0);
|
||||
const boundaryHelper = getBoundaryHelper(OrderedSet.size(indices));
|
||||
boundaryHelper.reset();
|
||||
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.boundaryStep(p, (radius && radius[i]) || 0);
|
||||
boundaryHelper.includeSphereStep(p, (radius && radius[i]) || 0);
|
||||
}
|
||||
boundaryHelper.finishBoundaryStep();
|
||||
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.extendStep(p, (radius && radius[i]) || 0);
|
||||
boundaryHelper.radiusSphereStep(p, (radius && radius[i]) || 0);
|
||||
}
|
||||
|
||||
return { boundingBox: boundaryHelper.getBox(), boundingSphere: boundaryHelper.getSphere() };
|
||||
|
||||
@@ -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 Fred Ludlow <fred.ludlow@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -14,6 +14,7 @@ import { OrderedSet } from '../../mol-data/int';
|
||||
import { PositionData } from './common';
|
||||
import { Mat4 } from '../../mol-math/linear-algebra/3d';
|
||||
import { Box3D, GridLookup3D, fillGridDim } from '../../mol-math/geometry';
|
||||
import { BaseGeometry } from '../../mol-geo/geometry/base';
|
||||
|
||||
function normalToLine (out: Vec3, p: Vec3) {
|
||||
out[0] = out[1] = out[2] = 1.0
|
||||
@@ -45,15 +46,15 @@ function getAngleTables (probePositions: number): AnglesTables {
|
||||
//
|
||||
|
||||
export const MolecularSurfaceCalculationParams = {
|
||||
resolution: PD.Numeric(0.5, { min: 0.01, max: 20, step: 0.01 }),
|
||||
probeRadius: PD.Numeric(1.4, { min: 0, max: 10, step: 0.1 }),
|
||||
probePositions: PD.Numeric(30, { min: 12, max: 90, step: 1 }),
|
||||
probeRadius: PD.Numeric(1.4, { min: 0, max: 10, step: 0.1 }, { description: 'Radius of the probe tracing the molecular surface.' }),
|
||||
resolution: PD.Numeric(0.5, { min: 0.01, max: 20, step: 0.01 }, { description: 'Grid resolution/cell spacing.', ...BaseGeometry.CustomQualityParamInfo }),
|
||||
probePositions: PD.Numeric(30, { min: 12, max: 90, step: 1 }, { description: 'Number of positions tested for probe target intersection.', ...BaseGeometry.CustomQualityParamInfo }),
|
||||
}
|
||||
export const DefaultMolecularSurfaceCalculationProps = PD.getDefaultValues(MolecularSurfaceCalculationParams)
|
||||
export type MolecularSurfaceCalculationProps = typeof DefaultMolecularSurfaceCalculationProps
|
||||
|
||||
|
||||
export async function calcMolecularSurface(ctx: RuntimeContext, position: Required<PositionData>, maxRadius: number, props: MolecularSurfaceCalculationProps) {
|
||||
export async function calcMolecularSurface(ctx: RuntimeContext, position: Required<PositionData>, maxRadius: number, box: Box3D | null, props: MolecularSurfaceCalculationProps) {
|
||||
// Field generation method adapted from AstexViewer (Mike Hartshorn) by Fred Ludlow.
|
||||
// Other parts based heavily on NGL (Alexander Rose) EDT Surface class
|
||||
|
||||
@@ -78,7 +79,7 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
}
|
||||
|
||||
for (let j = 0, jl = neighbours.count; j < jl; ++j) {
|
||||
const ai = OrderedSet.getAt(position.indices, neighbours.indices[j])
|
||||
const ai = OrderedSet.getAt(indices, neighbours.indices[j])
|
||||
if (ai !== a && ai !== b && singleAtomObscures(ai, x, y, z)) {
|
||||
lastClip = ai
|
||||
return ai
|
||||
@@ -281,7 +282,7 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
// Is this grid point closer to a or b?
|
||||
// Take dot product of atob and gridpoint->p (dx, dy, dz)
|
||||
const dp = dx * atob[0] + dy * atob[1] + dz * atob[2]
|
||||
idData[idx] = id[OrderedSet.indexOf(position.indices, dp < 0.0 ? b : a)]
|
||||
idData[idx] = id[OrderedSet.indexOf(indices, dp < 0.0 ? b : a)]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,15 +318,16 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
const scaleFactor = 1 / resolution
|
||||
const ngTorus = Math.max(5, 2 + Math.floor(probeRadius * scaleFactor))
|
||||
|
||||
const cellSize = Vec3.create(maxRadius, maxRadius, maxRadius)
|
||||
const cellSize = Vec3.create(maxRadius, maxRadius, maxRadius)
|
||||
Vec3.scale(cellSize, cellSize, 2)
|
||||
const lookup3d = GridLookup3D(position, cellSize)
|
||||
const neighbours = lookup3d.result
|
||||
const box = lookup3d.boundary.box
|
||||
if (box === null) box = lookup3d.boundary.box
|
||||
|
||||
const { indices, x: px, y: py, z: pz, id, radius } = position
|
||||
const n = OrderedSet.size(indices)
|
||||
|
||||
const pad = maxRadius * 2 + resolution
|
||||
const pad = maxRadius + resolution
|
||||
const expandedBox = Box3D.expand(Box3D(), box, Vec3.create(pad, pad, pad));
|
||||
const [ minX, minY, minZ ] = expandedBox.min
|
||||
const scaledBox = Box3D.scale(Box3D(), expandedBox, scaleFactor)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { Vec3, Mat4 } from '../../linear-algebra'
|
||||
import { PositionData } from '../common'
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { Sphere3D } from './sphere3d';
|
||||
|
||||
interface Box3D { min: Vec3, max: Vec3 }
|
||||
|
||||
@@ -29,6 +30,13 @@ namespace Box3D {
|
||||
return copy(empty(), a);
|
||||
}
|
||||
|
||||
export function fromSphere3D(out: Box3D, sphere: Sphere3D): Box3D {
|
||||
const r = Vec3.create(sphere.radius, sphere.radius, sphere.radius)
|
||||
Vec3.sub(out.min, sphere.center, r)
|
||||
Vec3.add(out.max, sphere.center, r)
|
||||
return out
|
||||
}
|
||||
|
||||
export function computeBounding(data: PositionData): Box3D {
|
||||
const min = Vec3.create(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
|
||||
const max = Vec3.create(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
|
||||
|
||||
@@ -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>
|
||||
@@ -12,26 +12,42 @@ import { NumberArray } from '../../../mol-util/type-helpers';
|
||||
import { Box3D } from './box3d';
|
||||
import { Axes3D } from './axes3d';
|
||||
|
||||
interface Sphere3D { center: Vec3, radius: number }
|
||||
type Sphere3D = Sphere3D.Data | Sphere3D.Hierarchy
|
||||
|
||||
function Sphere3D() {
|
||||
return Sphere3D.zero();
|
||||
}
|
||||
|
||||
namespace Sphere3D {
|
||||
export interface Data { center: Vec3, radius: number }
|
||||
export interface Hierarchy extends Data { hierarchy: Sphere3D[] }
|
||||
export function isHierarchy(x: Sphere3D | Hierarchy): x is Hierarchy {
|
||||
return 'hierarchy' in x
|
||||
}
|
||||
export function getList(sphere: Sphere3D) {
|
||||
return Sphere3D.isHierarchy(sphere) ? sphere.hierarchy : [sphere]
|
||||
}
|
||||
|
||||
export function create(center: Vec3, radius: number): Sphere3D { return { center, radius }; }
|
||||
export function zero(): Sphere3D { return { center: Vec3.zero(), radius: 0 }; }
|
||||
|
||||
export function clone(a: Sphere3D): Sphere3D {
|
||||
const out = zero();
|
||||
Vec3.copy(out.center, a.center);
|
||||
out.radius = a.radius
|
||||
const out = create(Vec3.clone(a.center), a.radius)
|
||||
if (isHierarchy(a)) (out as Hierarchy).hierarchy = a.hierarchy
|
||||
return out;
|
||||
}
|
||||
|
||||
export function copy(out: Sphere3D, a: Sphere3D) {
|
||||
Vec3.copy(out.center, a.center)
|
||||
out.radius = a.radius
|
||||
if (isHierarchy(a)) {
|
||||
if (isHierarchy(out)) {
|
||||
out.hierarchy.length = 0
|
||||
out.hierarchy.push(...a.hierarchy)
|
||||
} else {
|
||||
(out as Hierarchy).hierarchy = a.hierarchy
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -117,6 +133,15 @@ namespace Sphere3D {
|
||||
export function expand(out: Sphere3D, sphere: Sphere3D, delta: number): Sphere3D {
|
||||
Vec3.copy(out.center, sphere.center)
|
||||
out.radius = sphere.radius + delta
|
||||
if (isHierarchy(sphere)) {
|
||||
const hierarchy = sphere.hierarchy.map(s => expand(Sphere3D(), s, delta))
|
||||
if (isHierarchy(out)) {
|
||||
out.hierarchy.length = 0
|
||||
out.hierarchy.push(...hierarchy)
|
||||
} else {
|
||||
(out as Hierarchy).hierarchy = hierarchy
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -145,6 +145,54 @@ namespace Spacegroup {
|
||||
const r3 = TransformData[ids[2]];
|
||||
return Mat4.ofRows([r1, r2, r3, [0, 0, 0, 1]]);
|
||||
}
|
||||
|
||||
export function getOperatorXyz(op: Mat4) {
|
||||
return [
|
||||
formatElement(getRotation(op[0], op[4], op[8]), getShift(op[12])),
|
||||
formatElement(getRotation(op[1], op[5], op[9]), getShift(op[13])),
|
||||
formatElement(getRotation(op[2], op[6], op[10]), getShift(op[14]))
|
||||
].join(',')
|
||||
}
|
||||
|
||||
function getRotation(x: number, y: number, z: number) {
|
||||
let r: string[] = []
|
||||
if (x > 0) r.push('+X')
|
||||
else if (x < 0) r.push('-X')
|
||||
if (y > 0) r.push('+Y')
|
||||
else if (y < 0) r.push('-Y')
|
||||
if (z > 0) r.push('+Z')
|
||||
else if (z < 0) r.push('-Z')
|
||||
|
||||
if (r.length === 1) {
|
||||
return r[0].charAt(0) === '+' ? r[0].substr(1) : r[0]
|
||||
}
|
||||
if (r.length === 2) {
|
||||
const s0 = r[0].charAt(0)
|
||||
const s1 = r[1].charAt(0)
|
||||
if (s0 === '+') return `${r[0].substr(1)}${r[1]}`
|
||||
if (s1 === '+') return `${r[1].substr(1)}${r[0]}`
|
||||
}
|
||||
throw new Error(`unknown rotation '${r}', ${x} ${y} ${z}`)
|
||||
}
|
||||
|
||||
function getShift(s: number) {
|
||||
switch (s) {
|
||||
case 1/2: return '1/2'
|
||||
case 1/4: return '1/4'
|
||||
case 3/4: return '3/4'
|
||||
case 1/3: return '1/3'
|
||||
case 2/3: return '2/3'
|
||||
case 1/6: return '1/6'
|
||||
case 5/6: return '5/6'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function formatElement(rotation: string, shift: string) {
|
||||
if (shift === '') return rotation
|
||||
if (rotation.length > 2) return `${rotation}+${shift}`
|
||||
return rotation.charAt(0) === '-' ? `${shift}${rotation}` : `${shift}+${rotation}`
|
||||
}
|
||||
}
|
||||
|
||||
export { Spacegroup, SpacegroupCell }
|
||||
@@ -436,7 +436,7 @@ namespace Vec3 {
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Computes the angle between 2 vectors, reports in rad. */
|
||||
/** Computes the angle between 2 vectors, reports in radians. */
|
||||
export function angle(a: Vec3, b: Vec3) {
|
||||
const theta = dot(a, b) / Math.sqrt(squaredMagnitude(a) * squaredMagnitude(b));
|
||||
return Math.acos(clamp(theta, -1, 1)); // clamp to avoid numerical problems
|
||||
@@ -450,7 +450,7 @@ namespace Vec3 {
|
||||
const tmp_dh_bcd = zero();
|
||||
const tmp_dh_cross = zero();
|
||||
/**
|
||||
* Computes the dihedral angles of 4 points.
|
||||
* Computes the dihedral angles of 4 points, reports in radians.
|
||||
*/
|
||||
export function dihedralAngle(a: Vec3, b: Vec3, c: Vec3, d: Vec3): number {
|
||||
sub(tmp_dh_ab, a, b);
|
||||
@@ -461,7 +461,7 @@ namespace Vec3 {
|
||||
cross(tmp_dh_abc, tmp_dh_ab, tmp_dh_cb);
|
||||
cross(tmp_dh_bcd, tmp_dh_bc, tmp_dh_dc);
|
||||
|
||||
const _angle = angle(tmp_dh_abc, tmp_dh_bcd) * 360.0 / (2 * Math.PI);
|
||||
const _angle = angle(tmp_dh_abc, tmp_dh_bcd);
|
||||
cross(tmp_dh_cross, tmp_dh_abc, tmp_dh_bcd);
|
||||
return dot(tmp_dh_cb, tmp_dh_cross) > 0 ? _angle : -_angle;
|
||||
}
|
||||
|
||||
@@ -90,9 +90,9 @@ function getConformation(atom_site: AtomSite): AtomicConformation {
|
||||
|
||||
function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
|
||||
// TODO need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300
|
||||
return Table.areEqual(a.chains as Table<ChainsSchema>, b.chains as Table<ChainsSchema>)
|
||||
&& Table.areEqual(a.residues as Table<ResiduesSchema>, b.residues as Table<ResiduesSchema>)
|
||||
&& Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>)
|
||||
return Table.areEqual(a.chains, b.chains)
|
||||
&& Table.areEqual(a.residues, b.residues)
|
||||
&& Table.areEqual(a.atoms, b.atoms)
|
||||
}
|
||||
|
||||
function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {
|
||||
|
||||
@@ -24,9 +24,16 @@ import { getModelGroupName } from './util';
|
||||
|
||||
export async function createModels(data: BasicData, format: ModelFormat, ctx: RuntimeContext) {
|
||||
const properties = getProperties(data)
|
||||
return data.ihm_model_list._rowCount > 0
|
||||
const models = data.ihm_model_list._rowCount > 0
|
||||
? await readIntegrative(ctx, data, properties, format)
|
||||
: await readStandard(ctx, data, properties, format);
|
||||
|
||||
for (let i = 0; i < models.length; i++) {
|
||||
models[i].trajectoryInfo.index = i;
|
||||
models[i].trajectoryInfo.size = models.length;
|
||||
}
|
||||
|
||||
return models;
|
||||
}
|
||||
|
||||
/** Standard atomic model */
|
||||
@@ -45,7 +52,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
}
|
||||
|
||||
const coarse = EmptyCoarse;
|
||||
const sequence = getSequence(data, entities, atomic.hierarchy, coarse.hierarchy, properties.modifiedResidues.parentId)
|
||||
const sequence = getSequence(data, entities, atomic.hierarchy, coarse.hierarchy)
|
||||
const atomicRanges = getAtomicRanges(atomic.hierarchy, entities, atomic.conformation, sequence)
|
||||
|
||||
const entry = data.entry.id.valueKind(0) === Column.ValueKind.Present
|
||||
@@ -62,6 +69,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
entry,
|
||||
sourceData: format,
|
||||
modelNum,
|
||||
trajectoryInfo: { index: 0, size: 1 },
|
||||
entities,
|
||||
sequence,
|
||||
atomicHierarchy: atomic.hierarchy,
|
||||
@@ -80,7 +88,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
|
||||
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 coarse = getCoarse(ihm, properties);
|
||||
const sequence = getSequence(data, ihm.entities, atomic.hierarchy, coarse.hierarchy, properties.modifiedResidues.parentId)
|
||||
const sequence = getSequence(data, ihm.entities, atomic.hierarchy, coarse.hierarchy)
|
||||
const atomicRanges = getAtomicRanges(atomic.hierarchy, ihm.entities, atomic.conformation, sequence)
|
||||
|
||||
const entry = data.entry.id.valueKind(0) === Column.ValueKind.Present
|
||||
@@ -99,6 +107,7 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
|
||||
entry,
|
||||
sourceData: format,
|
||||
modelNum: ihm.model_id,
|
||||
trajectoryInfo: { index: 0, size: 1 },
|
||||
entities: ihm.entities,
|
||||
sequence,
|
||||
atomicHierarchy: atomic.hierarchy,
|
||||
|
||||
@@ -6,30 +6,13 @@
|
||||
*/
|
||||
|
||||
import { Model } from '../../../mol-model/structure/model/model';
|
||||
import { ChemicalComponent, MissingResidue } from '../../../mol-model/structure/model/properties/common';
|
||||
import { ChemicalComponent, MissingResidue, StructAsym } from '../../../mol-model/structure/model/properties/common';
|
||||
import { getMoleculeType, MoleculeType, getDefaultChemicalComponent } from '../../../mol-model/structure/model/types';
|
||||
import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants';
|
||||
import { memoize1 } from '../../../mol-util/memoize';
|
||||
import { BasicData } from './schema';
|
||||
import { Table } from '../../../mol-data/db';
|
||||
|
||||
function getModifiedResidueNameMap(data: BasicData): Model['properties']['modifiedResidues'] {
|
||||
const parentId = new Map<string, string>();
|
||||
const details = new Map<string, string>();
|
||||
|
||||
const c = data.pdbx_struct_mod_residue;
|
||||
const comp_id = c.label_comp_id.isDefined ? c.label_comp_id : c.auth_comp_id;
|
||||
const parent_id = c.parent_comp_id, details_data = c.details;
|
||||
|
||||
for (let i = 0; i < c._rowCount; i++) {
|
||||
const id = comp_id.value(i);
|
||||
parentId.set(id, parent_id.value(i));
|
||||
details.set(id, details_data.value(i));
|
||||
}
|
||||
|
||||
return { parentId, details };
|
||||
}
|
||||
|
||||
function getMissingResidues(data: BasicData): Model['properties']['missingResidues'] {
|
||||
const map = new Map<string, MissingResidue>();
|
||||
const getKey = (model_num: number, asym_id: string, seq_id: number) => {
|
||||
@@ -124,11 +107,43 @@ const getUniqueComponentNames = memoize1((data: BasicData) => {
|
||||
return uniqueNames
|
||||
})
|
||||
|
||||
|
||||
function getStructAsymMap(data: BasicData): Model['properties']['structAsymMap'] {
|
||||
const map = new Map<string, StructAsym>();
|
||||
|
||||
const { label_asym_id, auth_asym_id, label_entity_id } = data.atom_site
|
||||
for (let i = 0, il = label_asym_id.rowCount; i < il; ++i) {
|
||||
const id = label_asym_id.value(i)
|
||||
if (!map.has(id)) {
|
||||
map.set(id, {
|
||||
id,
|
||||
auth_id: auth_asym_id.value(i),
|
||||
entity_id: label_entity_id.value(i)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (data.struct_asym._rowCount > 0) {
|
||||
const { id, entity_id } = data.struct_asym
|
||||
for (let i = 0, il = id.rowCount; i < il; ++i) {
|
||||
const _id = id.value(i)
|
||||
if (!map.has(_id)) {
|
||||
map.set(_id, {
|
||||
id: _id,
|
||||
auth_id: '',
|
||||
entity_id: entity_id.value(i)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
export function getProperties(data: BasicData): Model['properties'] {
|
||||
return {
|
||||
modifiedResidues: getModifiedResidueNameMap(data),
|
||||
missingResidues: getMissingResidues(data),
|
||||
chemicalComponentMap: getChemicalComponentMap(data),
|
||||
saccharideComponentMap: getSaccharideComponentMap(data)
|
||||
saccharideComponentMap: getSaccharideComponentMap(data),
|
||||
structAsymMap: getStructAsymMap(data)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Table } from '../../../mol-data/db';
|
||||
|
||||
// TODO split into conformation and hierarchy parts
|
||||
// TODO extract `pdbx_struct_mod_residue` as property?
|
||||
|
||||
export type Entry = Table<mmCIF_Schema['entry']>
|
||||
export type Struct = Table<mmCIF_Schema['struct']>
|
||||
@@ -22,7 +21,6 @@ export type EntityPolySeq = Table<mmCIF_Schema['entity_poly_seq']>
|
||||
export type EntityBranch = Table<mmCIF_Schema['pdbx_entity_branch']>
|
||||
export type ChemComp = Table<mmCIF_Schema['chem_comp']>
|
||||
export type ChemCompIdentifier = Table<mmCIF_Schema['pdbx_chem_comp_identifier']>
|
||||
export type StructModResidue = Table<mmCIF_Schema['pdbx_struct_mod_residue']>
|
||||
export type AtomSite = Table<mmCIF_Schema['atom_site']>
|
||||
export type IhmSphereObjSite = Table<mmCIF_Schema['ihm_sphere_obj_site']>
|
||||
export type IhmGaussianObjSite =Table<mmCIF_Schema['ihm_gaussian_obj_site']>
|
||||
@@ -41,7 +39,6 @@ export const BasicSchema = {
|
||||
pdbx_entity_branch: mmCIF_Schema.pdbx_entity_branch,
|
||||
chem_comp: mmCIF_Schema.chem_comp,
|
||||
pdbx_chem_comp_identifier: mmCIF_Schema.pdbx_chem_comp_identifier,
|
||||
pdbx_struct_mod_residue: mmCIF_Schema.pdbx_struct_mod_residue,
|
||||
atom_site: mmCIF_Schema.atom_site,
|
||||
ihm_sphere_obj_site: mmCIF_Schema.ihm_sphere_obj_site,
|
||||
ihm_gaussian_obj_site: mmCIF_Schema.ihm_gaussian_obj_site,
|
||||
@@ -61,7 +58,6 @@ export interface BasicData {
|
||||
pdbx_entity_branch: EntityBranch
|
||||
chem_comp: ChemComp
|
||||
pdbx_chem_comp_identifier: ChemCompIdentifier
|
||||
pdbx_struct_mod_residue: StructModResidue
|
||||
atom_site: AtomSite
|
||||
ihm_sphere_obj_site: IhmSphereObjSite
|
||||
ihm_gaussian_obj_site: IhmGaussianObjSite
|
||||
|
||||
@@ -13,9 +13,9 @@ import { Sequence } from '../../../mol-model/sequence';
|
||||
import { CoarseHierarchy } from '../../../mol-model/structure/model/properties/coarse';
|
||||
import { BasicData } from './schema';
|
||||
|
||||
export function getSequence(data: BasicData, entities: Entities, atomicHierarchy: AtomicHierarchy, coarseHierarchy: CoarseHierarchy, modResMap: ReadonlyMap<string, string>): StructureSequence {
|
||||
export function getSequence(data: BasicData, entities: Entities, atomicHierarchy: AtomicHierarchy, coarseHierarchy: CoarseHierarchy): StructureSequence {
|
||||
if (!data.entity_poly_seq || !data.entity_poly_seq._rowCount) {
|
||||
return StructureSequence.fromHierarchy(entities, atomicHierarchy, coarseHierarchy, modResMap);
|
||||
return StructureSequence.fromHierarchy(entities, atomicHierarchy, coarseHierarchy);
|
||||
}
|
||||
|
||||
const { entity_id, num, mon_id } = data.entity_poly_seq;
|
||||
@@ -37,7 +37,7 @@ export function getSequence(data: BasicData, entities: Entities, atomicHierarchy
|
||||
|
||||
byEntityKey[entityKey] = {
|
||||
entityId: id,
|
||||
sequence: Sequence.ofResidueNames(compId, seqId, modResMap)
|
||||
sequence: Sequence.ofResidueNames(compId, seqId)
|
||||
};
|
||||
|
||||
sequences.push(byEntityKey[entityKey]);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Table, Column } from '../../../mol-data/db';
|
||||
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { WaterNames } from '../../../mol-model/structure/model/types';
|
||||
import { WaterNames, PolymerNames } from '../../../mol-model/structure/model/types';
|
||||
import { SetUtils } from '../../../mol-util/set';
|
||||
import { BasicSchema } from '../basic/schema';
|
||||
|
||||
@@ -77,12 +77,14 @@ export class ComponentBuilder {
|
||||
private ids: string[] = []
|
||||
private names: string[] = []
|
||||
private types: mmCIF_Schema['chem_comp']['type']['T'][] = []
|
||||
private mon_nstd_flags: mmCIF_Schema['chem_comp']['mon_nstd_flag']['T'][] = []
|
||||
|
||||
private set(c: Component) {
|
||||
this.comps.set(c.id, c)
|
||||
this.ids.push(c.id)
|
||||
this.names.push(c.name)
|
||||
this.types.push(c.type)
|
||||
this.mon_nstd_flags.push(PolymerNames.has(c.id) ? 'y' : 'n')
|
||||
}
|
||||
|
||||
private getAtomIds(index: number) {
|
||||
@@ -141,6 +143,7 @@ export class ComponentBuilder {
|
||||
id: Column.ofStringArray(this.ids),
|
||||
name: Column.ofStringArray(this.names),
|
||||
type: Column.ofStringAliasArray(this.types),
|
||||
mon_nstd_flag: Column.ofStringAliasArray(this.mon_nstd_flags),
|
||||
}, this.ids.length)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ class FormatRegistry<T> {
|
||||
this.map.set(kind, obtain)
|
||||
}
|
||||
|
||||
remove(kind: ModelFormat['kind']) {
|
||||
this.map.delete(kind)
|
||||
}
|
||||
|
||||
get(kind: ModelFormat['kind']) {
|
||||
return this.map.get(kind)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import { Table } from '../../mol-data/db';
|
||||
import { AtomSiteAnisotrop } from './property/anisotropic';
|
||||
import { ComponentBond } from './property/bonds/comp';
|
||||
import { StructConn } from './property/bonds/struct_conn';
|
||||
import { ModelCrossLinkRestraint } from './property/pair-restraints/cross-links';
|
||||
|
||||
function modelSymmetryFromMmcif(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return;
|
||||
@@ -65,14 +64,6 @@ function structConnFromMmcif(model: Model) {
|
||||
}
|
||||
StructConn.Provider.formatRegistry.add('mmCIF', structConnFromMmcif)
|
||||
|
||||
function crossLinkRestraintFromMmcif(model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) return;
|
||||
const { ihm_cross_link_restraint } = model.sourceData.data.db;
|
||||
if (ihm_cross_link_restraint._rowCount === 0) return;
|
||||
return ModelCrossLinkRestraint.fromTable(ihm_cross_link_restraint, model)
|
||||
}
|
||||
ModelCrossLinkRestraint.Provider.formatRegistry.add('mmCIF', crossLinkRestraintFromMmcif)
|
||||
|
||||
//
|
||||
|
||||
export { MmcifFormat }
|
||||
|
||||
@@ -162,8 +162,8 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
}
|
||||
|
||||
const categories = {
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
chem_comp: componentBuilder.getChemCompTable(),
|
||||
entity: CifCategory.ofTable('entity', entityBuilder.getEntityTable()),
|
||||
chem_comp: CifCategory.ofTable('chem_comp', componentBuilder.getChemCompTable()),
|
||||
atom_site: CifCategory.ofFields('atom_site', getAtomSite(atomSite)),
|
||||
atom_site_anisotrop: CifCategory.ofFields('atom_site_anisotrop', getAnisotropic(anisotropic))
|
||||
} as any;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
// TODO
|
||||
// ihm_predicted_contact_restraint: {
|
||||
// id: int,
|
||||
// entity_id_1: str,
|
||||
// entity_id_2: str,
|
||||
// asym_id_1: str,
|
||||
// asym_id_2: str,
|
||||
// comp_id_1: str,
|
||||
// comp_id_2: str,
|
||||
// seq_id_1: int,
|
||||
// seq_id_2: int,
|
||||
// atom_id_1: str,
|
||||
// atom_id_2: str,
|
||||
// distance_upper_limit: float,
|
||||
// probability: float,
|
||||
// restraint_type: Aliased<'lower bound' | 'upper bound' | 'lower and upper bound'>(str),
|
||||
// model_granularity: Aliased<'by-residue' | 'by-feature' | 'by-atom'>(str),
|
||||
// dataset_list_id: int,
|
||||
// software_id: int,
|
||||
// },
|
||||
@@ -15,7 +15,7 @@ import { Loci } from '../../mol-model/loci';
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { CustomModelProperty } from './custom-model-property';
|
||||
import { CustomProperty } from './custom-property';
|
||||
import { LociLabelProvider } from '../../mol-plugin/util/loci-label-manager';
|
||||
import { LociLabelProvider } from '../../mol-plugin-state/manager/loci-label';
|
||||
|
||||
export { CustomElementProperty };
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace CustomElementProperty {
|
||||
})
|
||||
}
|
||||
|
||||
function createColorThemeProvider<T>(modelProperty: CustomModelProperty.Provider<{}, Value<T>>, getColor: (p: T) => Color, defaultColor: Color) {
|
||||
function createColorThemeProvider<T>(modelProperty: CustomModelProperty.Provider<{}, Value<T>>, getColor: (p: T) => Color, defaultColor: Color): ColorTheme.Provider<{}> {
|
||||
|
||||
function Coloring(ctx: ThemeDataContext, props: {}): ColorTheme<{}> {
|
||||
let color: LocationColor;
|
||||
@@ -98,11 +98,17 @@ namespace CustomElementProperty {
|
||||
}
|
||||
|
||||
return {
|
||||
name: modelProperty.descriptor.name,
|
||||
label: modelProperty.label,
|
||||
category: 'Custom',
|
||||
factory: Coloring,
|
||||
getParams: () => ({}),
|
||||
defaultValues: {},
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !!modelProperty.get(ctx.structure.models[0]).value
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,8 +49,10 @@ namespace CustomModelProperty {
|
||||
label: builder.label,
|
||||
descriptor: builder.descriptor,
|
||||
getParams: builder.getParams,
|
||||
defaultParams: builder.defaultParams,
|
||||
isApplicable: builder.isApplicable,
|
||||
attach: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<Params>> = {}) => {
|
||||
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 }
|
||||
if (property.data.value && PD.areEqual(builder.defaultParams, property.props, p)) return
|
||||
@@ -58,6 +60,7 @@ namespace CustomModelProperty {
|
||||
data.customProperties.add(builder.descriptor);
|
||||
set(data, p, value);
|
||||
},
|
||||
ref: (data: Model, add: boolean) => data.customProperties.reference(builder.descriptor, add),
|
||||
get: (data: Model) => get(data)?.data,
|
||||
set: (data: Model, props: Partial<PD.Values<Params>> = {}) => {
|
||||
const property = get(data)
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { CustomPropertyDescriptor, Model, Structure } from '../../mol-model/structure';
|
||||
import { OrderedMap } from 'immutable';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Task } from '../../mol-task';
|
||||
|
||||
export { CustomPropertyRegistry }
|
||||
|
||||
class CustomPropertyRegistry<T = never> {
|
||||
private providers = OrderedMap<string, CustomPropertyRegistry.Provider<T>>().asMutable();
|
||||
|
||||
getSelect(object: T) {
|
||||
const values = this.providers.values();
|
||||
const options: [string, string][] = [], selected: string[] = [];
|
||||
while (true) {
|
||||
const v = values.next();
|
||||
if (v.done) break;
|
||||
if (!v.value.attachableTo(object)) continue;
|
||||
options.push(v.value.option);
|
||||
if (v.value.defaultSelected) selected.push(v.value.option[0]);
|
||||
}
|
||||
return PD.MultiSelect(selected, options);
|
||||
}
|
||||
|
||||
getDefault(object: T) {
|
||||
const values = this.providers.values();
|
||||
const selected: string[] = [];
|
||||
while (true) {
|
||||
const v = values.next();
|
||||
if (v.done) break;
|
||||
if (!v.value.attachableTo(object)) continue;
|
||||
if (v.value.defaultSelected) selected.push(v.value.option[0]);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
get(name: string) {
|
||||
const prop = this.providers.get(name);
|
||||
if (!prop) throw new Error(`Custom prop '${name}' is not registered.`);
|
||||
return this.providers.get(name);
|
||||
}
|
||||
|
||||
register(provider: CustomPropertyRegistry.Provider<T>) {
|
||||
this.providers.set(provider.descriptor.name, provider);
|
||||
}
|
||||
|
||||
unregister(name: string) {
|
||||
this.providers.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
namespace CustomPropertyRegistry {
|
||||
export interface Provider<T> {
|
||||
option: [string, string],
|
||||
defaultSelected: boolean,
|
||||
descriptor: CustomPropertyDescriptor<any, any>,
|
||||
attachableTo: (object: T) => boolean,
|
||||
attach: (object: T) => Task<boolean>
|
||||
}
|
||||
|
||||
export type ModelProvider = Provider<Model>
|
||||
export type StructureProvider = Provider<Structure>
|
||||
}
|
||||
@@ -29,8 +29,10 @@ namespace CustomProperty {
|
||||
readonly label: string
|
||||
readonly descriptor: CustomPropertyDescriptor
|
||||
readonly getParams: (data: Data) => Params
|
||||
readonly defaultParams: Params
|
||||
readonly isApplicable: (data: Data) => boolean
|
||||
readonly attach: (ctx: Context, data: Data, props?: Partial<PD.Values<Params>>) => Promise<void>
|
||||
readonly attach: (ctx: Context, data: Data, props?: Partial<PD.Values<Params>>, addRef?: boolean) => Promise<void>
|
||||
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
|
||||
}
|
||||
@@ -75,7 +77,9 @@ namespace CustomProperty {
|
||||
|
||||
get(name: string) {
|
||||
const prop = this.providers.get(name);
|
||||
if (!prop) throw new Error(`Custom property '${name}' is not registered.`)
|
||||
if (!prop) {
|
||||
throw new Error(`Custom property '${name}' is not registered.`)
|
||||
}
|
||||
return this.providers.get(name)
|
||||
}
|
||||
|
||||
|
||||
@@ -49,8 +49,10 @@ namespace CustomStructureProperty {
|
||||
label: builder.label,
|
||||
descriptor: builder.descriptor,
|
||||
getParams: builder.getParams,
|
||||
defaultParams: builder.defaultParams,
|
||||
isApplicable: builder.isApplicable,
|
||||
attach: async (ctx: CustomProperty.Context, data: Structure, props: Partial<PD.Values<Params>> = {}) => {
|
||||
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 property = get(data)
|
||||
const p = { ...property.props, ...props }
|
||||
@@ -59,6 +61,7 @@ namespace CustomStructureProperty {
|
||||
data.customPropertyDescriptors.add(builder.descriptor);
|
||||
set(data, p, value);
|
||||
},
|
||||
ref: (data: Structure, add: boolean) => data.customPropertyDescriptors.reference(builder.descriptor, add),
|
||||
get: (data: Structure) => get(data).data,
|
||||
set: (data: Structure, props: Partial<PD.Values<Params>> = {}, value?: Value) => {
|
||||
if (builder.type === 'root') data = data.root
|
||||
|
||||
@@ -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 Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -7,9 +7,12 @@
|
||||
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition'
|
||||
import { ShrakeRupleyComputationParams, AccessibleSurfaceArea } from './accessible-surface-area/shrake-rupley';
|
||||
import { Structure, CustomPropertyDescriptor } from '../../mol-model/structure';
|
||||
import { Structure, CustomPropertyDescriptor, Unit } from '../../mol-model/structure';
|
||||
import { CustomStructureProperty } from '../common/custom-structure-property';
|
||||
import { CustomProperty } from '../common/custom-property';
|
||||
import { QuerySymbolRuntime } from '../../mol-script/runtime/query/compiler';
|
||||
import { CustomPropSymbol } from '../../mol-script/language/symbol';
|
||||
import Type from '../../mol-script/language/type';
|
||||
|
||||
export const AccessibleSurfaceAreaParams = {
|
||||
...ShrakeRupleyComputationParams
|
||||
@@ -17,13 +20,33 @@ export const AccessibleSurfaceAreaParams = {
|
||||
export type AccessibleSurfaceAreaParams = typeof AccessibleSurfaceAreaParams
|
||||
export type AccessibleSurfaceAreaProps = PD.Values<AccessibleSurfaceAreaParams>
|
||||
|
||||
export const AccessibleSurfaceAreaSymbols = {
|
||||
isBuried: QuerySymbolRuntime.Dynamic(CustomPropSymbol('computed', 'accessible-surface-area.is-buried', Type.Bool),
|
||||
ctx => {
|
||||
if (!Unit.isAtomic(ctx.element.unit)) return false
|
||||
const accessibleSurfaceArea = AccessibleSurfaceAreaProvider.get(ctx.element.structure).value
|
||||
if (!accessibleSurfaceArea) return false
|
||||
return AccessibleSurfaceArea.getFlag(ctx.element, accessibleSurfaceArea) === AccessibleSurfaceArea.Flag.Buried
|
||||
}
|
||||
),
|
||||
isAccessible: QuerySymbolRuntime.Dynamic(CustomPropSymbol('computed', 'accessible-surface-area.is-accessible', Type.Bool),
|
||||
ctx => {
|
||||
if (!Unit.isAtomic(ctx.element.unit)) return false
|
||||
const accessibleSurfaceArea = AccessibleSurfaceAreaProvider.get(ctx.element.structure).value
|
||||
if (!accessibleSurfaceArea) return false
|
||||
return AccessibleSurfaceArea.getFlag(ctx.element, accessibleSurfaceArea) === AccessibleSurfaceArea.Flag.Accessible
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
export type AccessibleSurfaceAreaValue = AccessibleSurfaceArea
|
||||
|
||||
export const AccessibleSurfaceAreaProvider: CustomStructureProperty.Provider<AccessibleSurfaceAreaParams, AccessibleSurfaceAreaValue> = CustomStructureProperty.createProvider({
|
||||
label: 'Accessible Surface Area',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'molstar_accessible_surface_area',
|
||||
// TODO `cifExport` and `symbol`
|
||||
symbols: AccessibleSurfaceAreaSymbols,
|
||||
// TODO `cifExport`
|
||||
}),
|
||||
type: 'root',
|
||||
defaultParams: AccessibleSurfaceAreaParams,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Task, RuntimeContext } from '../../../mol-task';
|
||||
// import { BitFlags } from '../../../mol-util';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { Structure } from '../../../mol-model/structure';
|
||||
import { Structure, StructureElement, StructureProperties } from '../../../mol-model/structure';
|
||||
import { assignRadiusForHeavyAtoms } from './shrake-rupley/radii';
|
||||
import { ShrakeRupleyContext, VdWLookup, MaxAsa, DefaultMaxAsa } from './shrake-rupley/common';
|
||||
import { computeArea } from './shrake-rupley/area';
|
||||
@@ -86,19 +86,35 @@ namespace AccessibleSurfaceArea {
|
||||
return points;
|
||||
}
|
||||
|
||||
// export namespace SolventAccessibility {
|
||||
// export const is: (t: number, f: Flag) => boolean = BitFlags.has
|
||||
// export const create: (f: Flag) => number = BitFlags.create
|
||||
// export const enum Flag {
|
||||
// _ = 0x0,
|
||||
// BURIED = 0x1,
|
||||
// ACCESSIBLE = 0x2
|
||||
// }
|
||||
// }
|
||||
export const enum Flag {
|
||||
NA = 0x0,
|
||||
Buried = 0x1,
|
||||
Accessible = 0x2
|
||||
}
|
||||
|
||||
/** Get relative area for a given component id */
|
||||
export function normalize(compId: string, asa: number) {
|
||||
const maxAsa = MaxAsa[compId] || DefaultMaxAsa;
|
||||
return asa / maxAsa
|
||||
}
|
||||
|
||||
export function getValue(location: StructureElement.Location, accessibleSurfaceArea: AccessibleSurfaceArea) {
|
||||
const { getSerialIndex } = location.structure.root.serialMapping
|
||||
const { area, serialResidueIndex } = accessibleSurfaceArea
|
||||
const rSI = serialResidueIndex[getSerialIndex(location.unit, location.element)]
|
||||
if (rSI === -1) return -1
|
||||
return area[rSI]
|
||||
}
|
||||
|
||||
export function getNormalizedValue(location: StructureElement.Location, accessibleSurfaceArea: AccessibleSurfaceArea) {
|
||||
const value = getValue(location, accessibleSurfaceArea)
|
||||
return value === -1 ? -1 : normalize(StructureProperties.residue.label_comp_id(location), value)
|
||||
}
|
||||
|
||||
export function getFlag(location: StructureElement.Location, accessibleSurfaceArea: AccessibleSurfaceArea) {
|
||||
const value = getNormalizedValue(location, accessibleSurfaceArea)
|
||||
return value === -1 ? Flag.NA :
|
||||
value < 0.16 ? Flag.Buried :
|
||||
Flag.Accessible
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import { ValenceModelProvider } from '../valence-model';
|
||||
import { InteractionsIntraContacts, InteractionsInterContacts, FeatureType, interactionTypeLabel } from './common';
|
||||
import { IntraContactsBuilder, InterContactsBuilder } from './contacts-builder';
|
||||
import { IntMap } from '../../../mol-data/int';
|
||||
import { addUnitContacts, ContactTester, addStructureContacts, ContactProvider, ContactsParams, ContactsProps } from './contacts';
|
||||
import { addUnitContacts, ContactTester, addStructureContacts, ContactsParams, ContactsProps } from './contacts';
|
||||
import { HalogenDonorProvider, HalogenAcceptorProvider, HalogenBondsProvider } from './halogen-bonds';
|
||||
import { HydrogenDonorProvider, WeakHydrogenDonorProvider, HydrogenAcceptorProvider, HydrogenBondsProvider, WeakHydrogenBondsProvider } from './hydrogen-bonds';
|
||||
import { NegativChargeProvider, PositiveChargeProvider, AromaticRingProvider, IonicProvider, PiStackingProvider, CationPiProvider } from './charged';
|
||||
@@ -25,6 +25,7 @@ import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { DataLoci } from '../../../mol-model/loci';
|
||||
import { bondLabel, LabelGranularity } from '../../../mol-theme/label';
|
||||
import { ObjectKeys } from '../../../mol-util/type-helpers';
|
||||
|
||||
export { Interactions }
|
||||
|
||||
@@ -38,8 +39,9 @@ interface Interactions {
|
||||
}
|
||||
|
||||
namespace Interactions {
|
||||
type StructureInteractions = { readonly structure: Structure, readonly interactions: Interactions }
|
||||
|
||||
export interface Element {
|
||||
structure: Structure,
|
||||
unitA: Unit
|
||||
/** Index into features of unitA */
|
||||
indexA: Features.FeatureIndex
|
||||
@@ -47,11 +49,12 @@ namespace Interactions {
|
||||
/** Index into features of unitB */
|
||||
indexB: Features.FeatureIndex
|
||||
}
|
||||
export interface Location extends DataLocation<Interactions, Element> {}
|
||||
|
||||
export interface Location extends DataLocation<StructureInteractions, Element> {}
|
||||
|
||||
export function Location(interactions: Interactions, structure: Structure, unitA?: Unit, indexA?: Features.FeatureIndex, unitB?: Unit, indexB?: Features.FeatureIndex): Location {
|
||||
return DataLocation('interactions', interactions,
|
||||
{ structure: structure as any, unitA: unitA as any, indexA: indexA as any, unitB: unitB as any, indexB: indexB as any });
|
||||
return DataLocation('interactions', { structure, interactions },
|
||||
{ unitA: unitA as any, indexA: indexA as any, unitB: unitB as any, indexB: indexB as any });
|
||||
}
|
||||
|
||||
export function isLocation(x: any): x is Location {
|
||||
@@ -60,7 +63,8 @@ namespace Interactions {
|
||||
|
||||
export function areLocationsEqual(locA: Location, locB: Location) {
|
||||
return (
|
||||
locA.data === locB.data &&
|
||||
locA.data.structure === locB.data.structure &&
|
||||
locA.data.interactions === locB.data.interactions &&
|
||||
locA.element.indexA === locB.element.indexA &&
|
||||
locA.element.indexB === locB.element.indexB &&
|
||||
locA.element.unitA === locB.element.unitA &&
|
||||
@@ -82,10 +86,9 @@ namespace Interactions {
|
||||
}
|
||||
|
||||
export function locationLabel(location: Location): string {
|
||||
return _label(location.data, location.element)
|
||||
return _label(location.data.interactions, location.element)
|
||||
}
|
||||
|
||||
type StructureInteractions = { readonly structure: Structure, readonly interactions: Interactions }
|
||||
export interface Loci extends DataLoci<StructureInteractions, Element> { }
|
||||
|
||||
export function Loci(structure: Structure, interactions: Interactions, elements: ReadonlyArray<Element>): Loci {
|
||||
@@ -145,26 +148,34 @@ const ContactProviders = {
|
||||
}
|
||||
type ContactProviders = typeof ContactProviders
|
||||
|
||||
function getProvidersParams() {
|
||||
const params: { [k in keyof ContactProviders]: PD.Group<ContactProviders[k]['params']> } = Object.create(null)
|
||||
function getProvidersParams(defaultOn: string[] = []) {
|
||||
const params: { [k in keyof ContactProviders]: PD.Mapped<PD.NamedParamUnion<{
|
||||
on: PD.Group<ContactProviders[k]['params']>
|
||||
off: PD.Group<{}>
|
||||
}>> } = Object.create(null)
|
||||
|
||||
Object.keys(ContactProviders).forEach(k => {
|
||||
(params as any)[k] = PD.Group(ContactProviders[k as keyof ContactProviders].params)
|
||||
(params as any)[k] = PD.MappedStatic(defaultOn.includes(k) ? 'on' : 'off', {
|
||||
on: PD.Group(ContactProviders[k as keyof ContactProviders].params),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true })
|
||||
})
|
||||
return params
|
||||
}
|
||||
export const ContactProviderParams = getProvidersParams([
|
||||
// 'ionic',
|
||||
'cation-pi',
|
||||
'pi-stacking',
|
||||
'hydrogen-bonds',
|
||||
'halogen-bonds',
|
||||
// 'hydrophobic',
|
||||
'metal-coordination',
|
||||
// 'weak-hydrogen-bonds',
|
||||
])
|
||||
|
||||
export const InteractionsParams = {
|
||||
types: PD.MultiSelect([
|
||||
// 'ionic',
|
||||
'cation-pi',
|
||||
'pi-stacking',
|
||||
'hydrogen-bonds',
|
||||
'halogen-bonds',
|
||||
// 'hydrophobic',
|
||||
'metal-coordination',
|
||||
// 'weak-hydrogen-bonds',
|
||||
], PD.objectToOptions(ContactProviders)),
|
||||
contacts: PD.Group(ContactsParams, { isFlat: true }),
|
||||
...getProvidersParams()
|
||||
providers: PD.Group(ContactProviderParams, { isFlat: true }),
|
||||
contacts: PD.Group(ContactsParams, { label: 'Advanced Options' }),
|
||||
}
|
||||
export type InteractionsParams = typeof InteractionsParams
|
||||
export type InteractionsProps = PD.Values<InteractionsParams>
|
||||
@@ -173,11 +184,13 @@ export async function computeInteractions(ctx: CustomProperty.Context, structure
|
||||
const p = { ...PD.getDefaultValues(InteractionsParams), ...props }
|
||||
await ValenceModelProvider.attach(ctx, structure)
|
||||
|
||||
const contactProviders: ContactProvider<any>[] = []
|
||||
Object.keys(ContactProviders).forEach(k => {
|
||||
if (p.types.includes(k)) contactProviders.push(ContactProviders[k as keyof typeof ContactProviders])
|
||||
const contactTesters: ContactTester[] = [];
|
||||
ObjectKeys(ContactProviders).forEach(k => {
|
||||
const { name, params } = p.providers[k]
|
||||
if (name === 'on') {
|
||||
contactTesters.push(ContactProviders[k].createTester(params as any))
|
||||
}
|
||||
})
|
||||
const contactTesters = contactProviders.map(l => l.createTester(p[l.name as keyof InteractionsProps]))
|
||||
|
||||
const requiredFeatures = new Set<FeatureType>()
|
||||
contactTesters.forEach(l => SetUtils.add(requiredFeatures, l.requiredFeatures))
|
||||
|
||||
@@ -101,8 +101,8 @@ function getInteractionLoci(pickingId: PickingId, structure: Structure, id: numb
|
||||
const interactions = InteractionsProvider.get(structure).value!
|
||||
const c = interactions.contacts.edges[groupId]
|
||||
return Interactions.Loci(structure, interactions, [
|
||||
{ structure, unitA: c.unitA, indexA: c.indexA, unitB: c.unitB, indexB: c.indexB },
|
||||
{ structure, unitA: c.unitB, indexA: c.indexB, unitB: c.unitA, indexB: c.indexA },
|
||||
{ unitA: c.unitA, indexA: c.indexA, unitB: c.unitB, indexB: c.indexB },
|
||||
{ unitA: c.unitB, indexA: c.indexB, unitB: c.unitA, indexB: c.indexA },
|
||||
])
|
||||
}
|
||||
return EmptyLoci
|
||||
|
||||
@@ -97,8 +97,8 @@ function getInteractionLoci(pickingId: PickingId, structureGroup: StructureGroup
|
||||
const interactions = InteractionsProvider.get(structure).value!
|
||||
const { a, b } = interactions.unitsContacts.get(unit.id)
|
||||
return Interactions.Loci(structure, interactions, [
|
||||
{ structure, unitA: unit, indexA: a[groupId], unitB: unit, indexB: b[groupId] },
|
||||
{ structure, unitA: unit, indexA: b[groupId], unitB: unit, indexB: a[groupId] },
|
||||
{ unitA: unit, indexA: a[groupId], unitB: unit, indexB: b[groupId] },
|
||||
{ unitA: unit, indexA: b[groupId], unitB: unit, indexB: a[groupId] },
|
||||
])
|
||||
}
|
||||
return EmptyLoci
|
||||
|
||||
@@ -8,9 +8,8 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Representation, RepresentationParamsGetter, RepresentationContext } from '../../../mol-repr/representation';
|
||||
import { ThemeRegistryContext } from '../../../mol-theme/theme';
|
||||
import { Structure } from '../../../mol-model/structure';
|
||||
import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
|
||||
import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation, getUnitKindsParam } from '../../../mol-repr/structure/representation';
|
||||
import { InteractionsIntraUnitParams, InteractionsIntraUnitVisual } from './interactions-intra-unit-cylinder';
|
||||
import { UnitKindOptions, UnitKind } from '../../../mol-repr/structure/visual/util/common';
|
||||
import { InteractionsProvider } from '../interactions';
|
||||
import { InteractionsInterUnitParams, InteractionsInterUnitVisual } from './interactions-inter-unit-cylinder';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
@@ -23,8 +22,8 @@ const InteractionsVisuals = {
|
||||
export const InteractionsParams = {
|
||||
...InteractionsIntraUnitParams,
|
||||
...InteractionsInterUnitParams,
|
||||
unitKinds: PD.MultiSelect<UnitKind>(['atomic'], UnitKindOptions),
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0.01, max: 10, step: 0.01 }),
|
||||
unitKinds: getUnitKindsParam(['atomic']),
|
||||
sizeFactor: PD.Numeric(0.15, { min: 0.01, max: 1, step: 0.01 }),
|
||||
visuals: PD.MultiSelect(['intra-unit', 'inter-unit'], PD.objectToOptions(InteractionsVisuals)),
|
||||
}
|
||||
export type InteractionsParams = typeof InteractionsParams
|
||||
@@ -37,7 +36,8 @@ export function InteractionRepresentation(ctx: RepresentationContext, getParams:
|
||||
return Representation.createMulti('Interactions', ctx, getParams, StructureRepresentationStateBuilder, InteractionsVisuals as unknown as Representation.Def<Structure, InteractionsParams>)
|
||||
}
|
||||
|
||||
export const InteractionsRepresentationProvider: StructureRepresentationProvider<InteractionsParams> = {
|
||||
export const InteractionsRepresentationProvider = StructureRepresentationProvider({
|
||||
name: 'interactions',
|
||||
label: 'Non-covalent Interactions',
|
||||
description: 'Displays non-covalent interactions as dashed cylinders.',
|
||||
factory: InteractionRepresentation,
|
||||
@@ -46,7 +46,8 @@ export const InteractionsRepresentationProvider: StructureRepresentationProvider
|
||||
defaultColorTheme: { name: 'interaction-type' },
|
||||
defaultSizeTheme: { name: 'uniform' },
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0,
|
||||
ensureCustomProperties: (ctx: CustomProperty.Context, structure: Structure) => {
|
||||
return InteractionsProvider.attach(ctx, structure)
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => InteractionsProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (_, data) => InteractionsProvider.ref(data, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
|
||||
import { CustomPropertyDescriptor, Structure } from '../../mol-model/structure';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { DSSPComputationParams, DSSPComputationProps, computeUnitDSSP } from './secondary-structure/dssp';
|
||||
import { SecondaryStructure } from '../../mol-model/structure/model/properties/seconday-structure';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
@@ -13,9 +13,10 @@ import { CustomStructureProperty } from '../common/custom-structure-property';
|
||||
import { CustomProperty } from '../common/custom-property';
|
||||
import { ModelSecondaryStructure } from '../../mol-model-formats/structure/property/secondary-structure';
|
||||
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/structure/common/custom-property';
|
||||
|
||||
function getSecondaryStructureParams(data?: Structure) {
|
||||
let defaultType = 'mmcif' as 'mmcif' | 'dssp'
|
||||
let defaultType = 'model' as 'model' | 'dssp'
|
||||
if (data) {
|
||||
defaultType = 'dssp'
|
||||
for (let i = 0, il = data.models.length; i < il; ++i) {
|
||||
@@ -27,7 +28,7 @@ function getSecondaryStructureParams(data?: Structure) {
|
||||
) {
|
||||
// if there is any secondary structure definition given or if there is
|
||||
// an archival model, don't calculate dssp by default
|
||||
defaultType = 'mmcif'
|
||||
defaultType = 'model'
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -35,9 +36,9 @@ function getSecondaryStructureParams(data?: Structure) {
|
||||
}
|
||||
return {
|
||||
type: PD.MappedStatic(defaultType, {
|
||||
'mmcif': PD.EmptyGroup({ label: 'mmCIF' }),
|
||||
'model': PD.EmptyGroup({ label: 'Model' }),
|
||||
'dssp': PD.Group(DSSPComputationParams, { label: 'DSSP', isFlat: true })
|
||||
}, { options: [['mmcif', 'mmCIF'], ['dssp', 'DSSP']] })
|
||||
}, { options: [['model', 'Model'], ['dssp', 'DSSP']] })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +46,7 @@ export const SecondaryStructureParams = getSecondaryStructureParams()
|
||||
export type SecondaryStructureParams = typeof SecondaryStructureParams
|
||||
export type SecondaryStructureProps = PD.Values<SecondaryStructureParams>
|
||||
|
||||
/** Maps `unit.id` to `SecondaryStructure` */
|
||||
export type SecondaryStructureValue = Map<number, SecondaryStructure>
|
||||
|
||||
export const SecondaryStructureProvider: CustomStructureProperty.Provider<SecondaryStructureParams, SecondaryStructureValue> = CustomStructureProperty.createProvider({
|
||||
@@ -61,7 +63,7 @@ export const SecondaryStructureProvider: CustomStructureProperty.Provider<Second
|
||||
const p = { ...PD.getDefaultValues(SecondaryStructureParams), ...props }
|
||||
switch (p.type.name) {
|
||||
case 'dssp': return await computeDssp(data, p.type.params)
|
||||
case 'mmcif': return await computeMmcif(data)
|
||||
case 'model': return await computeModel(data)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -80,7 +82,7 @@ async function computeDssp(structure: Structure, props: DSSPComputationProps): P
|
||||
return map
|
||||
}
|
||||
|
||||
async function computeMmcif(structure: Structure): Promise<SecondaryStructureValue> {
|
||||
async function computeModel(structure: Structure): Promise<SecondaryStructureValue> {
|
||||
const map = new Map<number, SecondaryStructure>()
|
||||
for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
|
||||
const u = structure.unitSymmetryGroups[i].units[0]
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Unit } from '../../../../mol-model/structure';
|
||||
import { Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { ProteinInfo } from './protein-info';
|
||||
import { ElementIndex } from '../../../../mol-model/structure/model';
|
||||
import { radToDeg } from '../../../../mol-math/misc';
|
||||
|
||||
export interface DihedralAngles {
|
||||
phi: Float32Array
|
||||
@@ -50,8 +51,8 @@ export function calculateUnitDihedralAngles(unit: Unit.Atomic, proteinInfo: Prot
|
||||
if (index.findAtomOnResidue(residueIndices[i], 'OXT') !== -1) continue
|
||||
|
||||
// returns NaN for missing atoms
|
||||
phi[i] = Vec3.dihedralAngle(cPosPrev, nPos, caPos, cPos)
|
||||
psi[i] = Vec3.dihedralAngle(nPos, caPos, cPos, nPosNext)
|
||||
phi[i] = radToDeg(Vec3.dihedralAngle(cPosPrev, nPos, caPos, cPos))
|
||||
psi[i] = radToDeg(Vec3.dihedralAngle(nPos, caPos, cPos, nPosNext))
|
||||
|
||||
cPosPrev = cPos, caPosPrev = caPos, nPosPrev = nPos
|
||||
cPos = cPosNext, caPos = caPosNext, nPos = nPosNext
|
||||
|
||||
@@ -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 Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -10,7 +10,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition'
|
||||
import { Color, ColorScale } from '../../../mol-util/color'
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme'
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color'
|
||||
import { StructureProperties, StructureElement, Unit } from '../../../mol-model/structure'
|
||||
import { StructureElement, Unit } from '../../../mol-model/structure'
|
||||
import { AccessibleSurfaceAreaProvider } from '../accessible-surface-area'
|
||||
import { AccessibleSurfaceArea } from '../accessible-surface-area/shrake-rupley'
|
||||
import { CustomProperty } from '../../common/custom-property'
|
||||
@@ -36,18 +36,16 @@ export function AccessibleSurfaceAreaColorTheme(ctx: ThemeDataContext, props: PD
|
||||
domain: [0.0, 1.0]
|
||||
})
|
||||
|
||||
const { label_comp_id } = StructureProperties.residue
|
||||
const accessibleSurfaceArea = ctx.structure && AccessibleSurfaceAreaProvider.get(ctx.structure)
|
||||
const contextHash = accessibleSurfaceArea?.version
|
||||
|
||||
if (accessibleSurfaceArea?.value && ctx.structure) {
|
||||
const { getSerialIndex } = ctx.structure.root.serialMapping
|
||||
const { area, serialResidueIndex } = accessibleSurfaceArea.value
|
||||
const asa = accessibleSurfaceArea.value
|
||||
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location) && Unit.isAtomic(location.unit)) {
|
||||
const rSI = serialResidueIndex[getSerialIndex(location.unit, location.element)]
|
||||
return rSI === -1 ? DefaultColor : scale.color(AccessibleSurfaceArea.normalize(label_comp_id(location), area[rSI]))
|
||||
const value = AccessibleSurfaceArea.getNormalizedValue(location, asa)
|
||||
return value === -1 ? DefaultColor : scale.color(value)
|
||||
}
|
||||
return DefaultColor
|
||||
}
|
||||
@@ -66,13 +64,16 @@ export function AccessibleSurfaceAreaColorTheme(ctx: ThemeDataContext, props: PD
|
||||
}
|
||||
}
|
||||
|
||||
export const AccessibleSurfaceAreaColorThemeProvider: ColorTheme.Provider<AccessibleSurfaceAreaColorThemeParams> = {
|
||||
export const AccessibleSurfaceAreaColorThemeProvider: ColorTheme.Provider<AccessibleSurfaceAreaColorThemeParams, 'accessible-surface-area'> = {
|
||||
name: 'accessible-surface-area',
|
||||
label: 'Accessible Surface Area',
|
||||
category: ColorTheme.Category.Residue,
|
||||
factory: AccessibleSurfaceAreaColorTheme,
|
||||
getParams: getAccessibleSurfaceAreaColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(AccessibleSurfaceAreaColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
|
||||
ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
return data.structure ? AccessibleSurfaceAreaProvider.attach(ctx, data.structure) : Promise.resolve()
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ export function InteractionTypeColorTheme(ctx: ThemeDataContext, props: PD.Value
|
||||
if (interactions && interactions.value) {
|
||||
color = (location: Location) => {
|
||||
if (Interactions.isLocation(location)) {
|
||||
const { unitsContacts, contacts } = location.data
|
||||
const { unitsContacts, contacts } = location.data.interactions
|
||||
const { unitA, unitB, indexA, indexB } = location.element
|
||||
if (unitA === unitB) {
|
||||
const links = unitsContacts.get(unitA.id)
|
||||
@@ -106,13 +106,16 @@ export function InteractionTypeColorTheme(ctx: ThemeDataContext, props: PD.Value
|
||||
}
|
||||
}
|
||||
|
||||
export const InteractionTypeColorThemeProvider: ColorTheme.Provider<InteractionTypeColorThemeParams> = {
|
||||
export const InteractionTypeColorThemeProvider: ColorTheme.Provider<InteractionTypeColorThemeParams, 'interaction-type'> = {
|
||||
name: 'interaction-type',
|
||||
label: 'Interaction Type',
|
||||
category: ColorTheme.Category.Misc,
|
||||
factory: InteractionTypeColorTheme,
|
||||
getParams: getInteractionTypeColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(InteractionTypeColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
|
||||
ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
return data.structure ? InteractionsProvider.attach(ctx, data.structure) : Promise.resolve()
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
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';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC)
|
||||
const Description = 'Colors cross-links by the deviation of the observed distance versus the modeled distance (e.g. modeled / `ihm_cross_link_restraint.distance_threshold`).'
|
||||
|
||||
export const CrossLinkColorThemeParams = {
|
||||
domain: PD.Interval([0.5, 1.5], { step: 0.01 }),
|
||||
list: PD.ColorList<ColorListName>('red-grey', ColorListOptionsScale),
|
||||
}
|
||||
export type CrossLinkColorThemeParams = typeof CrossLinkColorThemeParams
|
||||
export function getCrossLinkColorThemeParams(ctx: ThemeDataContext) {
|
||||
return CrossLinkColorThemeParams // TODO return copy
|
||||
}
|
||||
|
||||
export function CrossLinkColorTheme(ctx: ThemeDataContext, props: PD.Values<CrossLinkColorThemeParams>): ColorTheme<CrossLinkColorThemeParams> {
|
||||
let color: LocationColor
|
||||
let scale: ColorScale | undefined = undefined
|
||||
|
||||
const crossLinkRestraints = ctx.structure && CrossLinkRestraintProvider.get(ctx.structure).value
|
||||
|
||||
if (crossLinkRestraints) {
|
||||
scale = ColorScale.create({
|
||||
domain: props.domain,
|
||||
listOrName: props.list
|
||||
})
|
||||
const scaleColor = scale.color
|
||||
|
||||
color = (location: Location): Color => {
|
||||
if (CrossLinkRestraint.isLocation(location)) {
|
||||
const pair = crossLinkRestraints.pairs[location.element]
|
||||
if (pair) {
|
||||
return scaleColor(CrossLinkRestraint.distance(pair) / pair.distanceThreshold)
|
||||
}
|
||||
}
|
||||
return DefaultColor
|
||||
}
|
||||
} else {
|
||||
color = () => DefaultColor
|
||||
}
|
||||
|
||||
return {
|
||||
factory: CrossLinkColorTheme,
|
||||
granularity: 'group',
|
||||
color,
|
||||
props,
|
||||
description: Description,
|
||||
legend: scale ? scale.legend : undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const CrossLinkColorThemeProvider: ColorTheme.Provider<CrossLinkColorThemeParams, 'cross-link'> = {
|
||||
name: 'cross-link',
|
||||
label: 'Cross Link',
|
||||
category: ColorTheme.Category.Misc,
|
||||
factory: CrossLinkColorTheme,
|
||||
getParams: getCrossLinkColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(CrossLinkColorThemeParams),
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Model } from '../../../../mol-model/structure/model/model'
|
||||
import { Table } from '../../../../mol-data/db'
|
||||
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Unit, CustomPropertyDescriptor } from '../../../../mol-model/structure';
|
||||
import { ElementIndex } from '../../../../mol-model/structure/model/indexing';
|
||||
import { FormatPropertyProvider } from '../../common/property';
|
||||
import { Model } from '../../../mol-model/structure/model/model'
|
||||
import { Table } from '../../../mol-data/db'
|
||||
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Unit, CustomPropertyDescriptor } from '../../../mol-model/structure';
|
||||
import { ElementIndex } from '../../../mol-model/structure/model/indexing';
|
||||
import { FormatPropertyProvider } from '../../../mol-model-formats/structure/common/property';
|
||||
|
||||
export { ModelCrossLinkRestraint }
|
||||
|
||||
221
src/mol-model-props/integrative/cross-link-restraint/property.ts
Normal file
221
src/mol-model-props/integrative/cross-link-restraint/property.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ModelCrossLinkRestraint } from './format';
|
||||
import { Unit, StructureElement, Structure, CustomPropertyDescriptor, Bond} from '../../../mol-model/structure';
|
||||
import { PairRestraints, PairRestraint } from '../pair-restraints';
|
||||
import { CustomStructureProperty } from '../../common/custom-structure-property';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { DataLocation } from '../../../mol-model/location';
|
||||
import { DataLoci } from '../../../mol-model/loci';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
|
||||
import { bondLabel } from '../../../mol-theme/label';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
|
||||
export type CrossLinkRestraintValue = PairRestraints<CrossLinkRestraint>
|
||||
|
||||
export const CrossLinkRestraintProvider: CustomStructureProperty.Provider<{}, CrossLinkRestraintValue> = CustomStructureProperty.createProvider({
|
||||
label: 'Cross Link Restraint',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'integrative-cross-link-restraint',
|
||||
// TODO `cifExport` and `symbol`
|
||||
}),
|
||||
type: 'local',
|
||||
defaultParams: {},
|
||||
getParams: (data: Structure) => ({}),
|
||||
isApplicable: (data: Structure) => data.models.some(m => !!ModelCrossLinkRestraint.Provider.get(m)),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<{}>) => {
|
||||
return extractCrossLinkRestraints(data)
|
||||
}
|
||||
})
|
||||
|
||||
export { CrossLinkRestraint }
|
||||
|
||||
interface CrossLinkRestraint extends PairRestraint {
|
||||
readonly restraintType: 'harmonic' | 'upper bound' | 'lower bound'
|
||||
readonly distanceThreshold: number
|
||||
readonly psi: number
|
||||
readonly sigma1: number
|
||||
readonly sigma2: number
|
||||
}
|
||||
|
||||
namespace CrossLinkRestraint {
|
||||
export enum Tag {
|
||||
CrossLinkRestraint = 'cross-link-restraint'
|
||||
}
|
||||
|
||||
export function isApplicable(structure: Structure) {
|
||||
return structure.models.some(m => !!ModelCrossLinkRestraint.Provider.get(m))
|
||||
}
|
||||
|
||||
const distVecA = Vec3(), distVecB = Vec3()
|
||||
export function distance(pair: CrossLinkRestraint) {
|
||||
pair.unitA.conformation.position(pair.unitA.elements[pair.indexA], distVecA)
|
||||
pair.unitB.conformation.position(pair.unitB.elements[pair.indexB], distVecB)
|
||||
return Vec3.distance(distVecA, distVecB)
|
||||
}
|
||||
|
||||
type StructureCrossLinkRestraints = { readonly structure: Structure, readonly crossLinkRestraints: CrossLinkRestraintValue }
|
||||
|
||||
export type Element = number
|
||||
export interface Location extends DataLocation<StructureCrossLinkRestraints, Element> {}
|
||||
|
||||
export function Location(crossLinkRestraints: CrossLinkRestraintValue, structure: Structure, index?: number): Location {
|
||||
return DataLocation('cross-link-restraints', { structure, crossLinkRestraints }, index as any);
|
||||
}
|
||||
|
||||
export function isLocation(x: any): x is Location {
|
||||
return !!x && x.kind === 'data-location' && x.tag === 'cross-link-restraints';
|
||||
}
|
||||
|
||||
export function areLocationsEqual(locA: Location, locB: Location) {
|
||||
return (
|
||||
locA.data.structure === locB.data.structure &&
|
||||
locA.data.crossLinkRestraints === locB.data.crossLinkRestraints &&
|
||||
locA.element === locB.element
|
||||
)
|
||||
}
|
||||
|
||||
function _label(crossLinkRestraints: CrossLinkRestraintValue, element: Element): string {
|
||||
const p = crossLinkRestraints.pairs[element]
|
||||
return `Cross Link Restraint | Type: ${p.restraintType} | Threshold: ${p.distanceThreshold} \u212B | Psi: ${p.psi} | Sigma 1: ${p.sigma1} | Sigma 2: ${p.sigma2} | Distance: ${distance(p).toFixed(2)} \u212B`
|
||||
}
|
||||
|
||||
export function locationLabel(location: Location): string {
|
||||
return _label(location.data.crossLinkRestraints, location.element)
|
||||
}
|
||||
|
||||
export interface Loci extends DataLoci<StructureCrossLinkRestraints, Element> { }
|
||||
|
||||
export function Loci(structure: Structure, crossLinkRestraints: CrossLinkRestraintValue, elements: ReadonlyArray<Element>): Loci {
|
||||
return DataLoci('cross-link-restraints', { structure, crossLinkRestraints }, elements,
|
||||
(boundingSphere) => getBoundingSphere(crossLinkRestraints, elements, boundingSphere),
|
||||
() => getLabel(structure, crossLinkRestraints, elements));
|
||||
}
|
||||
|
||||
export function isLoci(x: any): x is Loci {
|
||||
return !!x && x.kind === 'data-loci' && x.tag === 'interactions';
|
||||
}
|
||||
|
||||
export function getBoundingSphere(crossLinkRestraints: CrossLinkRestraintValue, elements: ReadonlyArray<Element>, boundingSphere: Sphere3D) {
|
||||
return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
|
||||
const p = crossLinkRestraints.pairs[elements[i]]
|
||||
p.unitA.conformation.position(p.unitA.elements[p.indexA], pA)
|
||||
p.unitB.conformation.position(p.unitB.elements[p.indexB], pB)
|
||||
}, boundingSphere)
|
||||
}
|
||||
|
||||
export function getLabel(structure: Structure, crossLinkRestraints: CrossLinkRestraintValue, elements: ReadonlyArray<Element>) {
|
||||
const element = elements[0]
|
||||
if (element === undefined) return ''
|
||||
const p = crossLinkRestraints.pairs[element]
|
||||
return [
|
||||
_label(crossLinkRestraints, element),
|
||||
bondLabel(Bond.Location(structure, p.unitA, p.indexA, structure, p.unitB, p.indexB))
|
||||
].join('</br>')
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
function _addRestraints(map: Map<number, number>, unit: Unit, restraints: ModelCrossLinkRestraint) {
|
||||
const { elements } = unit;
|
||||
const elementCount = elements.length;
|
||||
const kind = unit.kind
|
||||
|
||||
for (let i = 0; i < elementCount; i++) {
|
||||
const e = elements[i];
|
||||
restraints.getIndicesByElement(e, kind).forEach(ri => map.set(ri, i))
|
||||
}
|
||||
}
|
||||
|
||||
function extractInter(pairs: CrossLinkRestraint[], unitA: Unit, unitB: Unit) {
|
||||
if (unitA.model !== unitB.model) return
|
||||
if (unitA.model.sourceData.kind !== 'mmCIF') return
|
||||
|
||||
const restraints = ModelCrossLinkRestraint.Provider.get(unitA.model)
|
||||
if (!restraints) return
|
||||
|
||||
const rA = new Map<number, StructureElement.UnitIndex>();
|
||||
const rB = new Map<number, StructureElement.UnitIndex>();
|
||||
_addRestraints(rA, unitA, restraints)
|
||||
_addRestraints(rB, unitB, restraints)
|
||||
|
||||
rA.forEach((indexA, ri) => {
|
||||
const indexB = rB.get(ri)
|
||||
if (indexB !== undefined) {
|
||||
pairs.push(
|
||||
createCrossLinkRestraint(unitA, indexA, unitB, indexB, restraints, ri),
|
||||
createCrossLinkRestraint(unitB, indexB, unitA, indexA, restraints, ri)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function extractIntra(pairs: CrossLinkRestraint[], unit: Unit) {
|
||||
if (unit.model.sourceData.kind !== 'mmCIF') return
|
||||
|
||||
const restraints = ModelCrossLinkRestraint.Provider.get(unit.model)
|
||||
if (!restraints) return
|
||||
|
||||
const { elements } = unit;
|
||||
const elementCount = elements.length;
|
||||
const kind = unit.kind
|
||||
|
||||
const r = new Map<number, StructureElement.UnitIndex[]>();
|
||||
|
||||
for (let i = 0; i < elementCount; i++) {
|
||||
const e = elements[i];
|
||||
restraints.getIndicesByElement(e, kind).forEach(ri => {
|
||||
const il = r.get(ri)
|
||||
if (il) il.push(i as StructureElement.UnitIndex)
|
||||
else r.set(ri, [i as StructureElement.UnitIndex])
|
||||
})
|
||||
}
|
||||
|
||||
r.forEach((il, ri) => {
|
||||
if (il.length < 2) return
|
||||
const [ indexA, indexB ] = il
|
||||
pairs.push(
|
||||
createCrossLinkRestraint(unit, indexA, unit, indexB, restraints, ri),
|
||||
createCrossLinkRestraint(unit, indexB, unit, indexA, restraints, ri)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function createCrossLinkRestraint(unitA: Unit, indexA: StructureElement.UnitIndex, unitB: Unit, indexB: StructureElement.UnitIndex, restraints: ModelCrossLinkRestraint, row: number): CrossLinkRestraint {
|
||||
return {
|
||||
unitA, indexA, unitB, indexB,
|
||||
|
||||
restraintType: restraints.data.restraint_type.value(row),
|
||||
distanceThreshold: restraints.data.distance_threshold.value(row),
|
||||
psi: restraints.data.psi.value(row),
|
||||
sigma1: restraints.data.sigma_1.value(row),
|
||||
sigma2: restraints.data.sigma_2.value(row),
|
||||
}
|
||||
}
|
||||
|
||||
function extractCrossLinkRestraints(structure: Structure): PairRestraints<CrossLinkRestraint> {
|
||||
const pairs: CrossLinkRestraint[] = []
|
||||
if (!structure.models.some(m => ModelCrossLinkRestraint.Provider.get(m))) {
|
||||
return new PairRestraints(pairs)
|
||||
}
|
||||
|
||||
const n = structure.units.length
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const unitA = structure.units[i]
|
||||
extractIntra(pairs, unitA)
|
||||
for (let j = i + 1; j < n; ++j) {
|
||||
const unitB = structure.units[j]
|
||||
if (unitA.model === unitB.model) {
|
||||
extractInter(pairs, unitA, unitB)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new PairRestraints(pairs)
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
|
||||
import { ThemeRegistryContext } from '../../../mol-theme/theme';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { PickingId } from '../../../mol-geo/geometry/picking';
|
||||
import { EmptyLoci, Loci } from '../../../mol-model/loci';
|
||||
import { Interval } from '../../../mol-data/int';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Structure, StructureElement } from '../../../mol-model/structure';
|
||||
import { VisualContext } from '../../../mol-repr/visual';
|
||||
import { createLinkCylinderMesh, LinkCylinderParams } from '../../../mol-repr/structure/visual/util/link';
|
||||
import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../../../mol-repr/structure/complex-visual';
|
||||
import { VisualUpdateState } from '../../../mol-repr/util';
|
||||
import { ComplexRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider } from '../../../mol-repr/structure/representation';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { CrossLinkRestraintProvider, CrossLinkRestraint } from './property';
|
||||
|
||||
function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<CrossLinkRestraintCylinderParams>, mesh?: Mesh) {
|
||||
|
||||
const crossLinks = CrossLinkRestraintProvider.get(structure).value!
|
||||
if (!crossLinks.count) return Mesh.createEmpty(mesh)
|
||||
const { sizeFactor } = props
|
||||
|
||||
const location = StructureElement.Location.create(structure)
|
||||
|
||||
const builderProps = {
|
||||
linkCount: crossLinks.count,
|
||||
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
|
||||
const b = crossLinks.pairs[edgeIndex]
|
||||
const uA = b.unitA, uB = b.unitB
|
||||
uA.conformation.position(uA.elements[b.indexA], posA)
|
||||
uB.conformation.position(uB.elements[b.indexB], posB)
|
||||
},
|
||||
radius: (edgeIndex: number) => {
|
||||
const b = crossLinks.pairs[edgeIndex]
|
||||
location.unit = b.unitA
|
||||
location.element = b.unitA.elements[b.indexA]
|
||||
return theme.size.size(location) * sizeFactor
|
||||
},
|
||||
}
|
||||
|
||||
return createLinkCylinderMesh(ctx, builderProps, props, mesh)
|
||||
}
|
||||
|
||||
export const CrossLinkRestraintCylinderParams = {
|
||||
...ComplexMeshParams,
|
||||
...LinkCylinderParams,
|
||||
sizeFactor: PD.Numeric(0.5, { min: 0, max: 10, step: 0.1 }),
|
||||
}
|
||||
export type CrossLinkRestraintCylinderParams = typeof CrossLinkRestraintCylinderParams
|
||||
|
||||
export function CrossLinkRestraintVisual(materialId: number): ComplexVisual<CrossLinkRestraintCylinderParams> {
|
||||
return ComplexMeshVisual<CrossLinkRestraintCylinderParams>({
|
||||
defaultProps: PD.getDefaultValues(CrossLinkRestraintCylinderParams),
|
||||
createGeometry: createCrossLinkRestraintCylinderMesh,
|
||||
createLocationIterator: createCrossLinkRestraintIterator,
|
||||
getLoci: getLinkLoci,
|
||||
eachLocation: eachCrossLink,
|
||||
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<CrossLinkRestraintCylinderParams>, currentProps: PD.Values<CrossLinkRestraintCylinderParams>) => {
|
||||
state.createGeometry = (
|
||||
newProps.sizeFactor !== currentProps.sizeFactor ||
|
||||
newProps.radialSegments !== currentProps.radialSegments ||
|
||||
newProps.linkCap !== currentProps.linkCap
|
||||
)
|
||||
}
|
||||
}, materialId)
|
||||
}
|
||||
|
||||
function createCrossLinkRestraintIterator(structure: Structure): LocationIterator {
|
||||
const crossLinkRestraints = CrossLinkRestraintProvider.get(structure).value!
|
||||
const { pairs } = crossLinkRestraints
|
||||
const groupCount = pairs.length
|
||||
const instanceCount = 1
|
||||
const location = CrossLinkRestraint.Location(crossLinkRestraints, structure)
|
||||
const getLocation = (groupIndex: number) => {
|
||||
location.element = groupIndex
|
||||
return location
|
||||
}
|
||||
return LocationIterator(groupCount, instanceCount, getLocation, true)
|
||||
}
|
||||
|
||||
function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
|
||||
const { objectId, groupId } = pickingId
|
||||
if (id === objectId) {
|
||||
const crossLinkRestraints = CrossLinkRestraintProvider.get(structure).value!
|
||||
const pair = crossLinkRestraints.pairs[groupId]
|
||||
if (pair) {
|
||||
return CrossLinkRestraint.Loci(structure, crossLinkRestraints, [groupId])
|
||||
}
|
||||
}
|
||||
return EmptyLoci
|
||||
}
|
||||
|
||||
function eachCrossLink(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
|
||||
let changed = false
|
||||
if (CrossLinkRestraint.isLoci(loci)) {
|
||||
if (!Structure.areEquivalent(loci.data.structure, structure)) return false
|
||||
const crossLinkRestraints = CrossLinkRestraintProvider.get(structure).value!
|
||||
if (loci.data.crossLinkRestraints !== crossLinkRestraints) return false
|
||||
|
||||
for (const e of loci.elements) {
|
||||
if (apply(Interval.ofSingleton(e))) changed = true
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const CrossLinkRestraintVisuals = {
|
||||
'cross-link-restraint': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, CrossLinkRestraintCylinderParams>) => ComplexRepresentation('Cross-link restraint', ctx, getParams, CrossLinkRestraintVisual),
|
||||
}
|
||||
|
||||
export const CrossLinkRestraintParams = {
|
||||
...CrossLinkRestraintCylinderParams,
|
||||
}
|
||||
export type CrossLinkRestraintParams = typeof CrossLinkRestraintParams
|
||||
export function getCrossLinkRestraintParams(ctx: ThemeRegistryContext, structure: Structure) {
|
||||
return PD.clone(CrossLinkRestraintParams)
|
||||
}
|
||||
|
||||
export type CrossLinkRestraintRepresentation = StructureRepresentation<CrossLinkRestraintParams>
|
||||
export function CrossLinkRestraintRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, CrossLinkRestraintParams>): CrossLinkRestraintRepresentation {
|
||||
return Representation.createMulti('CrossLinkRestraint', ctx, getParams, StructureRepresentationStateBuilder, CrossLinkRestraintVisuals as unknown as Representation.Def<Structure, CrossLinkRestraintParams>)
|
||||
}
|
||||
|
||||
export const CrossLinkRestraintRepresentationProvider = StructureRepresentationProvider({
|
||||
name: CrossLinkRestraint.Tag.CrossLinkRestraint,
|
||||
label: 'Cross Link Restraint',
|
||||
description: 'Displays cross-link restraints.',
|
||||
factory: CrossLinkRestraintRepresentation,
|
||||
getParams: getCrossLinkRestraintParams,
|
||||
defaultValues: PD.getDefaultValues(CrossLinkRestraintParams),
|
||||
defaultColorTheme: { name: CrossLinkRestraint.Tag.CrossLinkRestraint },
|
||||
defaultSizeTheme: { name: 'uniform' },
|
||||
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)
|
||||
}
|
||||
})
|
||||
49
src/mol-model-props/integrative/pair-restraints.ts
Normal file
49
src/mol-model-props/integrative/pair-restraints.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StructureElement, Unit } from '../../mol-model/structure';
|
||||
|
||||
const emptyArray: number[] = []
|
||||
|
||||
export interface PairRestraint {
|
||||
readonly unitA: Unit,
|
||||
readonly unitB: Unit,
|
||||
readonly indexA: StructureElement.UnitIndex,
|
||||
readonly indexB: StructureElement.UnitIndex,
|
||||
}
|
||||
|
||||
function getPairKey(indexA: StructureElement.UnitIndex, unitA: Unit, indexB: StructureElement.UnitIndex, unitB: Unit) {
|
||||
return `${indexA}|${unitA.id}|${indexB}|${unitB.id}`
|
||||
}
|
||||
|
||||
export class PairRestraints<T extends PairRestraint> {
|
||||
readonly count: number
|
||||
private readonly pairKeyIndices: Map<string, number[]>
|
||||
|
||||
/** Indices into this.pairs */
|
||||
getPairIndices(indexA: StructureElement.UnitIndex, unitA: Unit, indexB: StructureElement.UnitIndex, unitB: Unit): ReadonlyArray<number> {
|
||||
const key = getPairKey(indexA, unitA, indexB, unitB)
|
||||
return this.pairKeyIndices.get(key) || emptyArray
|
||||
}
|
||||
|
||||
getPairs(indexA: StructureElement.UnitIndex, unitA: Unit, indexB: StructureElement.UnitIndex, unitB: Unit): T[] {
|
||||
const indices = this.getPairIndices(indexA, unitA, indexB, unitB)
|
||||
return indices.map(idx => this.pairs[idx])
|
||||
}
|
||||
|
||||
constructor(public pairs: ReadonlyArray<T>) {
|
||||
const pairKeyIndices = new Map<string, number[]>()
|
||||
this.pairs.forEach((p, i) => {
|
||||
const key = getPairKey(p.indexA, p.unitA, p.indexB, p.unitB)
|
||||
const indices = pairKeyIndices.get(key)
|
||||
if (indices) indices.push(i)
|
||||
else pairKeyIndices.set(key, [i])
|
||||
})
|
||||
|
||||
this.count = pairs.length
|
||||
this.pairKeyIndices = pairKeyIndices
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ export type StructureQualityReportParams = typeof StructureQualityReportParams
|
||||
export type StructureQualityReportProps = PD.Values<StructureQualityReportParams>
|
||||
|
||||
export const StructureQualityReportProvider: CustomModelProperty.Provider<StructureQualityReportParams, StructureQualityReport> = CustomModelProperty.createProvider({
|
||||
label: 'PDBe Structure Quality Report',
|
||||
label: 'Structure Quality Report',
|
||||
descriptor: CustomPropertyDescriptor<ReportExportContext, any>({
|
||||
name: 'pdbe_structure_quality_report',
|
||||
cifExport: {
|
||||
|
||||
@@ -72,13 +72,15 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
|
||||
granularity: 'group',
|
||||
color: color,
|
||||
props: props,
|
||||
description: 'Assigns residue colors according to the number of issues or a specific issue in the PDBe Validation Report.',
|
||||
description: 'Assigns residue colors according to the number of quality issues or a specific quality issue. Data from wwPDB Validation Report, obtained via PDBe.',
|
||||
legend: TableLegend(ValidationColorTable)
|
||||
}
|
||||
}
|
||||
|
||||
export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Params> = {
|
||||
label: 'PDBe Structure Quality Report',
|
||||
export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Params, 'pdbe-structure-quality-report'> = {
|
||||
name: 'pdbe-structure-quality-report',
|
||||
label: 'Structure Quality Report',
|
||||
category: ColorTheme.Category.Validation,
|
||||
factory: StructureQualityReportColorTheme,
|
||||
getParams: ctx => {
|
||||
const issueTypes = StructureQualityReport.getIssueTypes(ctx.structure);
|
||||
@@ -101,7 +103,8 @@ export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Param
|
||||
},
|
||||
defaultValues: PD.getDefaultValues(StructureQualityReportColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => StructureQualityReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
return data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { CustomProperty } from '../common/custom-property';
|
||||
import { NonNullableArray } from '../../mol-util/type-helpers';
|
||||
import { CustomStructureProperty } from '../common/custom-structure-property';
|
||||
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { ReadonlyVec3 } from '../../mol-math/linear-algebra/3d/vec3';
|
||||
|
||||
const BiologicalAssemblyNames = new Set([
|
||||
'author_and_software_defined_assembly',
|
||||
@@ -26,6 +27,11 @@ const BiologicalAssemblyNames = new Set([
|
||||
])
|
||||
|
||||
export namespace AssemblySymmetry {
|
||||
export enum Tag {
|
||||
Cluster = 'rcsb-assembly-symmetry-cluster',
|
||||
Representation = 'rcsb-assembly-symmetry-3d'
|
||||
}
|
||||
|
||||
export const DefaultServerUrl = 'https://data-beta.rcsb.org/graphql'
|
||||
|
||||
export function isApplicable(structure?: Structure): boolean {
|
||||
@@ -37,6 +43,7 @@ export namespace AssemblySymmetry {
|
||||
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])
|
||||
@@ -48,27 +55,35 @@ export namespace AssemblySymmetry {
|
||||
|
||||
const client = new GraphQLClient(props.serverUrl, ctx.fetch)
|
||||
const variables: AssemblySymmetryQueryVariables = {
|
||||
assembly_id: structure.units[0].conformation.operator.assembly.id,
|
||||
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)
|
||||
|
||||
if (!result.assembly?.rcsb_struct_symmetry) {
|
||||
throw new Error('missing fields')
|
||||
console.error('expected `rcsb_struct_symmetry` field')
|
||||
return []
|
||||
}
|
||||
return result.assembly.rcsb_struct_symmetry as AssemblySymmetryValue
|
||||
}
|
||||
|
||||
export type RotationAxes = ReadonlyArray<{ order: number, start: ReadonlyVec3, end: ReadonlyVec3 }>
|
||||
export function isRotationAxes(x: AssemblySymmetryValue[0]['rotation_axes']): x is RotationAxes {
|
||||
return !!x && x.length > 0
|
||||
}
|
||||
}
|
||||
|
||||
export function getSymmetrySelectParam(structure?: Structure) {
|
||||
const param = PD.Select<number>(0, [[0, 'No Symmetries']])
|
||||
const param = PD.Select<number>(-1, [[-1, 'No Symmetries']])
|
||||
if (structure) {
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(structure).value
|
||||
if (assemblySymmetry) {
|
||||
const options: [number, string][] = []
|
||||
for (let i = 0, il = assemblySymmetry.length; i < il; ++i) {
|
||||
const { symbol, kind } = assemblySymmetry[i]
|
||||
options.push([ i, `${i + 1}: ${symbol} ${kind}` ])
|
||||
if (symbol !== 'C1') {
|
||||
options.push([ i, `${i + 1}: ${symbol} ${kind}` ])
|
||||
}
|
||||
}
|
||||
if (options.length) {
|
||||
param.options = options
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable */
|
||||
export type Maybe<T> = T | null;
|
||||
|
||||
// Generated in 2020-02-07T10:59:45-08:00
|
||||
// Generated in 2020-02-21T15:58:06-08:00
|
||||
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
@@ -14,7 +14,6 @@ export type Scalars = {
|
||||
UNREPRESENTABLE: any,
|
||||
};
|
||||
|
||||
|
||||
export type AuditAuthor = {
|
||||
readonly identifier_ORCID?: Maybe<Scalars['String']>,
|
||||
readonly name?: Maybe<Scalars['String']>,
|
||||
@@ -1750,6 +1749,7 @@ export type RcsbEntryContainerIdentifiers = {
|
||||
readonly emdb_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>,
|
||||
readonly entity_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>,
|
||||
readonly entry_id: Scalars['String'],
|
||||
readonly model_ids?: Maybe<ReadonlyArray<Maybe<Scalars['Int']>>>,
|
||||
readonly non_polymer_entity_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>,
|
||||
readonly polymer_entity_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>,
|
||||
readonly pubmed_id?: Maybe<Scalars['Int']>,
|
||||
@@ -1921,7 +1921,6 @@ export type RcsbNonpolymerInstanceAnnotationAnnotationLineage = {
|
||||
|
||||
export type RcsbNonpolymerInstanceFeature = {
|
||||
readonly assignment_version?: Maybe<Scalars['String']>,
|
||||
readonly auth_seq_id?: Maybe<Scalars['String']>,
|
||||
readonly comp_id?: Maybe<Scalars['String']>,
|
||||
readonly description?: Maybe<Scalars['String']>,
|
||||
readonly feature_id?: Maybe<Scalars['String']>,
|
||||
@@ -1942,6 +1941,7 @@ export type RcsbNonpolymerInstanceFeatureFeatureValue = {
|
||||
};
|
||||
|
||||
export type RcsbNonpolymerInstanceFeatureSummary = {
|
||||
readonly comp_id?: Maybe<Scalars['String']>,
|
||||
readonly count?: Maybe<Scalars['Int']>,
|
||||
readonly maximum_length?: Maybe<Scalars['Int']>,
|
||||
readonly maximum_value?: Maybe<Scalars['Float']>,
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { AssemblySymmetryValue, getSymmetrySelectParam, AssemblySymmetryProvider } from '../assembly-symmetry';
|
||||
import { AssemblySymmetryValue, getSymmetrySelectParam, 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';
|
||||
@@ -29,7 +29,6 @@ import { TetrahedronCage } from '../../../mol-geo/primitive/tetrahedron';
|
||||
import { IcosahedronCage } from '../../../mol-geo/primitive/icosahedron';
|
||||
import { degToRad, radToDeg } from '../../../mol-math/misc';
|
||||
import { Mutable } from '../../../mol-util/type-helpers';
|
||||
import { ReadonlyVec3 } from '../../../mol-math/linear-algebra/3d/vec3';
|
||||
import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
|
||||
import { Structure } from '../../../mol-model/structure';
|
||||
import { isInteger } from '../../../mol-util/number';
|
||||
@@ -87,11 +86,6 @@ export type AssemblySymmetryProps = PD.Values<AssemblySymmetryParams>
|
||||
|
||||
//
|
||||
|
||||
type RotationAxes = ReadonlyArray<{ order: number, start: ReadonlyVec3, end: ReadonlyVec3 }>
|
||||
function isRotationAxes(x: AssemblySymmetryValue[0]['rotation_axes']): x is RotationAxes {
|
||||
return !!x && x.length > 0
|
||||
}
|
||||
|
||||
function getAssemblyName(s: Structure) {
|
||||
const { id } = s.units[0].conformation.operator.assembly
|
||||
return isInteger(id) ? `Assembly ${id}` : id
|
||||
@@ -122,7 +116,7 @@ function getAxesMesh(data: AssemblySymmetryValue, props: PD.Values<AxesParams>,
|
||||
const { symmetryIndex, scale } = props
|
||||
|
||||
const { rotation_axes } = data[symmetryIndex]
|
||||
if (!isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
|
||||
|
||||
const { start, end } = rotation_axes[0]
|
||||
const radius = (Vec3.distance(start, end) / 500) * scale
|
||||
@@ -227,11 +221,11 @@ function getSymbolScale(symbol: string) {
|
||||
return 1
|
||||
}
|
||||
|
||||
function setSymbolTransform(t: Mat4, symbol: string, axes: RotationAxes, size: number, structure: Structure) {
|
||||
function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.RotationAxes, size: number, structure: Structure) {
|
||||
const eye = Vec3()
|
||||
const target = Vec3()
|
||||
const up = Vec3()
|
||||
let pair: Mutable<RotationAxes> | undefined = undefined
|
||||
let pair: Mutable<AssemblySymmetry.RotationAxes> | undefined = undefined
|
||||
|
||||
if (symbol.startsWith('C')) {
|
||||
pair = [axes[0]]
|
||||
@@ -288,7 +282,7 @@ function getCageMesh(data: Structure, props: PD.Values<CageParams>, mesh?: Mesh)
|
||||
const { symmetryIndex, scale } = props
|
||||
|
||||
const { rotation_axes, symbol } = assemblySymmetry[symmetryIndex]
|
||||
if (!isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
|
||||
|
||||
const cage = getSymbolCage(symbol)
|
||||
if (!cage) return Mesh.createEmpty(mesh)
|
||||
@@ -329,5 +323,5 @@ function getCageShape(ctx: RuntimeContext, data: Structure, props: AssemblySymme
|
||||
|
||||
export type AssemblySymmetryRepresentation = Representation<Structure, AssemblySymmetryParams>
|
||||
export function AssemblySymmetryRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, AssemblySymmetryParams>): AssemblySymmetryRepresentation {
|
||||
return Representation.createMulti('Symmetry', ctx, getParams, Representation.StateBuilder, AssemblySymmetryVisuals as unknown as Representation.Def<Structure, AssemblySymmetryParams>)
|
||||
return Representation.createMulti('Assembly Symmetry', ctx, getParams, Representation.StateBuilder, AssemblySymmetryVisuals as unknown as Representation.Def<Structure, AssemblySymmetryParams>)
|
||||
}
|
||||
@@ -13,14 +13,13 @@ import { PickingId } from '../../../mol-geo/geometry/picking';
|
||||
import { EmptyLoci, Loci, DataLoci } from '../../../mol-model/loci';
|
||||
import { Interval } from '../../../mol-data/int';
|
||||
import { RepresentationContext, RepresentationParamsGetter, Representation } from '../../../mol-repr/representation';
|
||||
import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
|
||||
import { UnitKind, UnitKindOptions } from '../../../mol-repr/structure/visual/util/common';
|
||||
import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation, getUnitKindsParam } from '../../../mol-repr/structure/representation';
|
||||
import { VisualContext } from '../../../mol-repr/visual';
|
||||
import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from '../../../mol-repr/structure/visual/util/link';
|
||||
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../../../mol-repr/structure/units-visual';
|
||||
import { VisualUpdateState } from '../../../mol-repr/util';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { ClashesProvider, IntraUnitClashes, InterUnitClashes } from '../validation-report';
|
||||
import { ClashesProvider, IntraUnitClashes, InterUnitClashes, ValidationReport } from '../validation-report';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../../../mol-repr/structure/complex-visual';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
@@ -263,7 +262,7 @@ const ClashesVisuals = {
|
||||
export const ClashesParams = {
|
||||
...IntraUnitClashParams,
|
||||
...InterUnitClashParams,
|
||||
unitKinds: PD.MultiSelect<UnitKind>(['atomic'], UnitKindOptions),
|
||||
unitKinds: getUnitKindsParam(['atomic']),
|
||||
visuals: PD.MultiSelect(['intra-clash', 'inter-clash'], PD.objectToOptions(ClashesVisuals))
|
||||
}
|
||||
export type ClashesParams = typeof ClashesParams
|
||||
@@ -278,16 +277,18 @@ export function ClashesRepresentation(ctx: RepresentationContext, getParams: Rep
|
||||
return repr
|
||||
}
|
||||
|
||||
export const ClashesRepresentationProvider: StructureRepresentationProvider<ClashesParams> = {
|
||||
label: 'RCSB Clashes',
|
||||
description: 'Displays clashes between atoms as disks.',
|
||||
export const ClashesRepresentationProvider = StructureRepresentationProvider({
|
||||
name: ValidationReport.Tag.Clashes,
|
||||
label: 'Validation Clashes',
|
||||
description: 'Displays clashes between atoms as disks. Data from wwPDB Validation Report, obtained via RCSB PDB.',
|
||||
factory: ClashesRepresentation,
|
||||
getParams: getClashesParams,
|
||||
defaultValues: PD.getDefaultValues(ClashesParams),
|
||||
defaultColorTheme: { name: 'uniform', props: { value: Color(0xFA28FF) } },
|
||||
defaultSizeTheme: { name: 'physical' },
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0,
|
||||
ensureCustomProperties: (ctx: CustomProperty.Context, structure: Structure) => {
|
||||
return ClashesProvider.attach(ctx, structure)
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => ClashesProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (_, data) => ClashesProvider.ref(data, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -59,11 +59,13 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
|
||||
for (let j = 0, jl = members.length; j < jl; ++j) {
|
||||
const asymId = members[j]!.asym_id
|
||||
const operList = [...members[j]!.pdbx_struct_oper_list_ids || []] as string[]
|
||||
if (operList.length === 0) operList.push('1') // TODO hack assuming '1' is the id of the identity operator
|
||||
clusterByMember.set(clusterMemberKey(asymId, operList), i)
|
||||
if (operList.length === 0) {
|
||||
operList.push('1') // TODO hack assuming '1' is the id of the identity operator
|
||||
clusterByMember.set(clusterMemberKey(asymId, operList), i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const palette = getPalette(clusters.length, props)
|
||||
legend = palette.legend
|
||||
|
||||
@@ -84,18 +86,21 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
|
||||
color,
|
||||
props,
|
||||
contextHash,
|
||||
description: 'Assigns chain colors according to assembly symmetry cluster membership.',
|
||||
description: 'Assigns chain colors according to assembly symmetry cluster membership calculated with BioJava and obtained via RCSB PDB.',
|
||||
legend
|
||||
}
|
||||
}
|
||||
|
||||
export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<AssemblySymmetryClusterColorThemeParams> = {
|
||||
label: 'RCSB Assembly Symmetry Cluster',
|
||||
export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<AssemblySymmetryClusterColorThemeParams, AssemblySymmetry.Tag.Cluster> = {
|
||||
name: AssemblySymmetry.Tag.Cluster,
|
||||
label: 'Assembly Symmetry Cluster',
|
||||
category: ColorTheme.Category.Symmetry,
|
||||
factory: AssemblySymmetryClusterColorTheme,
|
||||
getParams: getAssemblySymmetryClusterColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(AssemblySymmetryClusterColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => AssemblySymmetry.isApplicable(ctx.structure),
|
||||
ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
return data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure) : Promise.resolve()
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -55,18 +55,21 @@ export function DensityFitColorTheme(ctx: ThemeDataContext, props: {}): ColorThe
|
||||
color,
|
||||
props,
|
||||
contextHash,
|
||||
description: 'Assigns residue colors according to the density fit using normalized Real Space R (RSRZ) for polymer residues and real space correlation coefficient (RSCC) for ligands. Colors range from poor (RSRZ = 2 or RSCC = 0.678) - to better (RSRZ = 0 or RSCC = 1.0).',
|
||||
description: 'Assigns residue colors according to the density fit using normalized Real Space R (RSRZ) for polymer residues and real space correlation coefficient (RSCC) for ligands. Colors range from poor (RSRZ = 2 or RSCC = 0.678) - to better (RSRZ = 0 or RSCC = 1.0). Data from wwPDB Validation Report, obtained via RCSB PDB.',
|
||||
legend: scaleRsrz.legend
|
||||
}
|
||||
}
|
||||
|
||||
export const DensityFitColorThemeProvider: ColorTheme.Provider<{}> = {
|
||||
label: 'RCSB Density Fit',
|
||||
export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationReport.Tag.DensityFit> = {
|
||||
name: ValidationReport.Tag.DensityFit,
|
||||
label: 'Density Fit',
|
||||
category: ColorTheme.Category.Validation,
|
||||
factory: DensityFitColorTheme,
|
||||
getParams: () => ({}),
|
||||
defaultValues: PD.getDefaultValues({}),
|
||||
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
return data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -95,18 +95,21 @@ export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Value
|
||||
color,
|
||||
props,
|
||||
contextHash,
|
||||
description: 'Assigns residue colors according to the number of (filtered) geometry issues.',
|
||||
description: 'Assigns residue colors according to the number of (filtered) geometry issues. Data from wwPDB Validation Report, obtained via RCSB PDB.',
|
||||
legend: ColorLegend
|
||||
}
|
||||
}
|
||||
|
||||
export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQualityColorThemeParams> = {
|
||||
label: 'RCSB Geometry Quality',
|
||||
export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQualityColorThemeParams, ValidationReport.Tag.GeometryQuality> = {
|
||||
name: ValidationReport.Tag.GeometryQuality,
|
||||
label: 'Geometry Quality',
|
||||
category: ColorTheme.Category.Validation,
|
||||
factory: GeometryQualityColorTheme,
|
||||
getParams: getGeometricQualityColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(getGeometricQualityColorThemeParams({})),
|
||||
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
return data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -46,18 +46,21 @@ export function RandomCoilIndexColorTheme(ctx: ThemeDataContext, props: {}): Col
|
||||
color,
|
||||
props,
|
||||
contextHash,
|
||||
description: 'Assigns residue colors according to the Random Coil Index value.',
|
||||
description: 'Assigns residue colors according to the Random Coil Index value. Data from wwPDB Validation Report, obtained via RCSB PDB.',
|
||||
legend: scale.legend
|
||||
}
|
||||
}
|
||||
|
||||
export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}> = {
|
||||
label: 'RCSB Random Coil Index',
|
||||
export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}, ValidationReport.Tag.RandomCoilIndex> = {
|
||||
name: ValidationReport.Tag.RandomCoilIndex,
|
||||
label: 'Random Coil Index',
|
||||
category: ColorTheme.Category.Validation,
|
||||
factory: RandomCoilIndexColorTheme,
|
||||
getParams: () => ({}),
|
||||
defaultValues: PD.getDefaultValues({}),
|
||||
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
|
||||
ensureCustomProperties: (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
return data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0]) : Promise.resolve()
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,9 @@ import { arrayMax } from '../../mol-util/array';
|
||||
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { QuerySymbolRuntime } from '../../mol-script/runtime/query/compiler';
|
||||
import { CustomPropSymbol } from '../../mol-script/language/symbol';
|
||||
import Type from '../../mol-script/language/type';
|
||||
|
||||
export { ValidationReport }
|
||||
|
||||
@@ -118,6 +121,25 @@ namespace ValidationReport {
|
||||
case 'server': return fetch(ctx, model, props.source.params)
|
||||
}
|
||||
}
|
||||
|
||||
export const symbols = {
|
||||
hasClash: QuerySymbolRuntime.Dynamic(CustomPropSymbol('rcsb', 'validation-report.has-clash', Type.Bool),
|
||||
ctx => {
|
||||
const { unit, element } = ctx.element
|
||||
if (!Unit.isAtomic(unit)) return 0
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value
|
||||
return validationReport && validationReport.clashes.getVertexEdgeCount(element) > 0
|
||||
}
|
||||
),
|
||||
issueCount: QuerySymbolRuntime.Dynamic(CustomPropSymbol('rcsb', 'validation-report.issue-count', Type.Num),
|
||||
ctx => {
|
||||
const { unit, element } = ctx.element
|
||||
if (!Unit.isAtomic(unit)) return 0
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value
|
||||
return validationReport?.geometryIssues.get(unit.residueIndex[element])?.size || 0
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const FileSourceParams = {
|
||||
@@ -140,10 +162,10 @@ export type ValidationReportParams = typeof ValidationReportParams
|
||||
export type ValidationReportProps = PD.Values<ValidationReportParams>
|
||||
|
||||
export const ValidationReportProvider: CustomModelProperty.Provider<ValidationReportParams, ValidationReport> = CustomModelProperty.createProvider({
|
||||
label: 'RCSB Validation Report',
|
||||
label: 'Validation Report',
|
||||
descriptor: CustomPropertyDescriptor({
|
||||
name: 'rcsb_validation_report',
|
||||
// TODO `cifExport` and `symbol`
|
||||
symbols: ValidationReport.symbols
|
||||
}),
|
||||
type: 'dynamic',
|
||||
defaultParams: ValidationReportParams,
|
||||
|
||||
@@ -13,6 +13,9 @@ import { Structure } from './structure/structure';
|
||||
import { PrincipalAxes } from '../mol-math/linear-algebra/matrix/principal-axes';
|
||||
import { ParamDefinition } from '../mol-util/param-definition';
|
||||
import { shallowEqual } from '../mol-util';
|
||||
import { FiniteArray } from '../mol-util/type-helpers';
|
||||
import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
|
||||
import { stringToWords } from '../mol-util/string';
|
||||
|
||||
/** A Loci that includes every loci */
|
||||
export const EveryLoci = { kind: 'every-loci' as 'every-loci' }
|
||||
@@ -42,7 +45,8 @@ export function isDataLoci(x?: Loci): x is DataLoci {
|
||||
return !!x && x.kind === 'data-loci';
|
||||
}
|
||||
export function areDataLociEqual(a: DataLoci, b: DataLoci) {
|
||||
if (a.data !== b.data || a.tag !== b.tag) return false
|
||||
// use shallowEqual to allow simple data objects that are contructed on-the-fly
|
||||
if (!shallowEqual(a.data, b.data) || a.tag !== b.tag) return false
|
||||
if (a.elements.length !== b.elements.length) return false
|
||||
for (let i = 0, il = a.elements.length; i < il; ++i) {
|
||||
if (!shallowEqual(a.elements[i], b.elements[i])) return false
|
||||
@@ -61,9 +65,18 @@ export { Loci }
|
||||
type Loci = StructureElement.Loci | Structure.Loci | Bond.Loci | EveryLoci | EmptyLoci | DataLoci | Shape.Loci | ShapeGroup.Loci
|
||||
|
||||
namespace Loci {
|
||||
interface FiniteArray<T, L extends number = number> extends ReadonlyArray<T> { length: L };
|
||||
export interface Bundle<L extends number> { loci: FiniteArray<Loci, L> }
|
||||
|
||||
const boundaryHelper = new BoundaryHelper('98');
|
||||
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);
|
||||
boundaryHelper.finishedIncludeStep();
|
||||
for (const s of spheres) boundaryHelper.radiusSphereStep(s.center, s.radius);
|
||||
return boundaryHelper.getSphere();
|
||||
}
|
||||
|
||||
export function areEqual(lociA: Loci, lociB: Loci) {
|
||||
if (isEveryLoci(lociA) && isEveryLoci(lociB)) return true
|
||||
if (isEmptyLoci(lociA) && isEmptyLoci(lociB)) return true
|
||||
@@ -207,16 +220,19 @@ namespace Loci {
|
||||
'structure': (loci: Loci) => {
|
||||
return StructureElement.Loci.is(loci)
|
||||
? Structure.toStructureElementLoci(loci.structure)
|
||||
: loci
|
||||
},
|
||||
'shape': (loci: Loci) => {
|
||||
return ShapeGroup.isLoci(loci)
|
||||
? Shape.Loci(loci.shape)
|
||||
: loci
|
||||
: ShapeGroup.isLoci(loci)
|
||||
? Shape.Loci(loci.shape)
|
||||
: loci
|
||||
},
|
||||
}
|
||||
export type Granularity = keyof typeof Granularity
|
||||
export const GranularityOptions = ParamDefinition.objectToOptions(Granularity);
|
||||
export const GranularityOptions = ParamDefinition.objectToOptions(Granularity, k => {
|
||||
switch (k) {
|
||||
case 'element': return'Atom/Coarse Element'
|
||||
case 'structure': return'Structure/Shape'
|
||||
default: return stringToWords(k)
|
||||
}
|
||||
});
|
||||
|
||||
export function applyGranularity(loci: Loci, granularity: Granularity) {
|
||||
return Granularity[granularity](loci)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user