mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 22:31:26 +08:00
Compare commits
215 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddc1119a80 | ||
|
|
0fe6774f04 | ||
|
|
2a71e44ae8 | ||
|
|
1257f1ce85 | ||
|
|
07cd9f4b16 | ||
|
|
ed5ff1c9ce | ||
|
|
7ffc2db76e | ||
|
|
03067ca6d6 | ||
|
|
a69f1337d7 | ||
|
|
5e83c3350a | ||
|
|
f051d2d01e | ||
|
|
257340283b | ||
|
|
e769d77ec8 | ||
|
|
1923535918 | ||
|
|
dd3fc5620b | ||
|
|
7165258431 | ||
|
|
df9b367e0b | ||
|
|
ed1ae71f71 | ||
|
|
1c58bca454 | ||
|
|
c01be0644e | ||
|
|
66b9f6104c | ||
|
|
af4d2c4003 | ||
|
|
3b1a2f19a4 | ||
|
|
6b874786a8 | ||
|
|
f9d2560468 | ||
|
|
961034584a | ||
|
|
f6d11a59a3 | ||
|
|
53ee758378 | ||
|
|
c80c630810 | ||
|
|
13cd6e82ba | ||
|
|
f2966032d9 | ||
|
|
76503b52f5 | ||
|
|
aa24be8e9b | ||
|
|
5d7bb894d4 | ||
|
|
fcf559fa6b | ||
|
|
27963b5aed | ||
|
|
40d539c4aa | ||
|
|
d1433aaf7b | ||
|
|
8f4bf9a314 | ||
|
|
cca0f407f6 | ||
|
|
1e9e41754a | ||
|
|
38dc2d6e26 | ||
|
|
ea46b1c8c8 | ||
|
|
5846d7c4b4 | ||
|
|
214176ce2e | ||
|
|
ee4cb214e1 | ||
|
|
01cca9e8a6 | ||
|
|
7a23493e19 | ||
|
|
e109f069a8 | ||
|
|
82e667e402 | ||
|
|
c28feb2d1c | ||
|
|
8447a2d4f2 | ||
|
|
f9ebf1c399 | ||
|
|
286b27720a | ||
|
|
dc0e54c275 | ||
|
|
b710291d5e | ||
|
|
d84d5f38f5 | ||
|
|
9bea13438f | ||
|
|
2fc28f6005 | ||
|
|
bb07d6ec56 | ||
|
|
3cdfd04048 | ||
|
|
2120a258f9 | ||
|
|
2b5e49d215 | ||
|
|
b88bf9bdf2 | ||
|
|
df0f15d132 | ||
|
|
a6319bfb3d | ||
|
|
d6278cb3eb | ||
|
|
560da38687 | ||
|
|
83ba9d8776 | ||
|
|
ee776e6e3e | ||
|
|
3e52496b4e | ||
|
|
2659b96008 | ||
|
|
a8be84701b | ||
|
|
0c79aa1709 | ||
|
|
e57a19857f | ||
|
|
469dd05cd9 | ||
|
|
fb72db61bd | ||
|
|
c285e30ee0 | ||
|
|
789a327322 | ||
|
|
b8d2021599 | ||
|
|
36eae744af | ||
|
|
00df6ae52a | ||
|
|
023b65572e | ||
|
|
904e9b869c | ||
|
|
6f204b960d | ||
|
|
6217a51fa5 | ||
|
|
f1edb05c5c | ||
|
|
55bd27bb97 | ||
|
|
e2c9b601a6 | ||
|
|
238191660e | ||
|
|
0b175acc25 | ||
|
|
02865cbece | ||
|
|
a3e14bf579 | ||
|
|
1d502cbb54 | ||
|
|
4894b110b9 | ||
|
|
cefd0440a0 | ||
|
|
296bfb343e | ||
|
|
bc91f0d3ff | ||
|
|
aaa8215a6d | ||
|
|
bf0b37895d | ||
|
|
0810ed411d | ||
|
|
ff8fec542c | ||
|
|
8fb7308572 | ||
|
|
c9b7049532 | ||
|
|
da71332de1 | ||
|
|
a0de8dd9f9 | ||
|
|
e226f27041 | ||
|
|
804a04d9f8 | ||
|
|
3be06bb3b6 | ||
|
|
c7b618c246 | ||
|
|
55d990962f | ||
|
|
53e0a36539 | ||
|
|
ed7a5219bf | ||
|
|
8b49ccdc08 | ||
|
|
04fd3ade5f | ||
|
|
48985cd49d | ||
|
|
dd0707a8a5 | ||
|
|
b41ebcbbc8 | ||
|
|
991d2e3a57 | ||
|
|
7f4ac6782f | ||
|
|
7e7e30a82e | ||
|
|
08e92f12d3 | ||
|
|
d713ea6a76 | ||
|
|
0924020f24 | ||
|
|
9f10af3ba6 | ||
|
|
f754026cc5 | ||
|
|
321d98f4c1 | ||
|
|
58a49a8512 | ||
|
|
98bb9575b6 | ||
|
|
f10a135dea | ||
|
|
f300e524d1 | ||
|
|
87028c0a0b | ||
|
|
8ea23e6965 | ||
|
|
f9d8942814 | ||
|
|
b40df2f1e3 | ||
|
|
884cb0d9a4 | ||
|
|
ef1ccd4286 | ||
|
|
898abda373 | ||
|
|
e42c664a8c | ||
|
|
987bf47827 | ||
|
|
6201dd1d74 | ||
|
|
5ed17ce4e5 | ||
|
|
e301eca9c2 | ||
|
|
8a4ef015a2 | ||
|
|
67f3f3fdbb | ||
|
|
adc5b559cd | ||
|
|
bbaa637118 | ||
|
|
a3094b4d19 | ||
|
|
c3f937e113 | ||
|
|
04df327939 | ||
|
|
b1a0f46ade | ||
|
|
389e249862 | ||
|
|
cfcf9f6818 | ||
|
|
bcb8419f37 | ||
|
|
7d24bcf1dc | ||
|
|
8d0f7a2dc7 | ||
|
|
a7cb7beaa8 | ||
|
|
3c9b82dc04 | ||
|
|
9dba6d5371 | ||
|
|
a65bba0969 | ||
|
|
175e009152 | ||
|
|
897d17c8ed | ||
|
|
ca866cfa3a | ||
|
|
f6b2c0b2ba | ||
|
|
ea419c68ae | ||
|
|
40cf348d40 | ||
|
|
93ea759a71 | ||
|
|
3e50377eb8 | ||
|
|
3fbd1f8dc4 | ||
|
|
f03ce68513 | ||
|
|
50e2d542df | ||
|
|
e53e739d18 | ||
|
|
dfb7f7811f | ||
|
|
115824bbcf | ||
|
|
438de5760d | ||
|
|
3f765aedec | ||
|
|
96144fb10f | ||
|
|
cc34425712 | ||
|
|
9d68838893 | ||
|
|
6c68cebca0 | ||
|
|
b3e784262d | ||
|
|
2fdc22de71 | ||
|
|
77f0b0033f | ||
|
|
69da5abb88 | ||
|
|
a827e9a449 | ||
|
|
e74a29ae6a | ||
|
|
9b3d2f396e | ||
|
|
14cf7cc101 | ||
|
|
e5293c4d36 | ||
|
|
3afe21a4c3 | ||
|
|
bb50b69bb4 | ||
|
|
af7c030338 | ||
|
|
34b1eee876 | ||
|
|
c9cd1075d3 | ||
|
|
4fa04f0ff8 | ||
|
|
75f6466fd7 | ||
|
|
503ffd80fd | ||
|
|
c8ac64a571 | ||
|
|
bf81b902bd | ||
|
|
b636cdf9cc | ||
|
|
2d3b85825a | ||
|
|
a5a34f39e0 | ||
|
|
fe7e04f61b | ||
|
|
1a14720e35 | ||
|
|
18bf743ed2 | ||
|
|
f8d085a034 | ||
|
|
94bf3a136c | ||
|
|
9e8a8f3e71 | ||
|
|
45f9d93f3a | ||
|
|
44a566fdf3 | ||
|
|
d7f7770b7c | ||
|
|
df6b163505 | ||
|
|
51f88fff71 | ||
|
|
6b8db5abc6 | ||
|
|
37a0b07d56 |
16
.vscode/extensions.json
vendored
Normal file
16
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"slevesque.shader",
|
||||
"stpn.vscode-graphql",
|
||||
"wayou.vscode-todo-highlight"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": [
|
||||
|
||||
]
|
||||
}
|
||||
13
README.md
13
README.md
@@ -85,7 +85,7 @@ and navigate to `build/viewer`
|
||||
Install CIFTools `npm install ciftools -g`
|
||||
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF
|
||||
cifschema -mip ../../../../mol-data-o src/mol-io/reader/cif/schema/ccd.ts -p CCD
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD
|
||||
|
||||
**GraphQL schemas**
|
||||
@@ -95,7 +95,7 @@ Install CIFTools `npm install ciftools -g`
|
||||
### Other scripts
|
||||
**Create chem comp bond table**
|
||||
|
||||
export NODE_PATH="lib"; node --max-old-space-size=8192 build/src/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
|
||||
export NODE_PATH="lib"; node --max-old-space-size=4096 lib/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
|
||||
|
||||
**Test model server**
|
||||
|
||||
@@ -133,14 +133,19 @@ To get syntax highlighting for shader and graphql files add the following to Vis
|
||||
|
||||
## Publish
|
||||
|
||||
## Prerelease
|
||||
### Prerelease
|
||||
npm version prerelease # asumes the current version ends with '-dev.X'
|
||||
npm publish --tag next
|
||||
|
||||
## Release
|
||||
### Release
|
||||
npm version 0.X.0 # provide valid semver string
|
||||
npm publish
|
||||
|
||||
## Deploy
|
||||
npm run test
|
||||
NODE_ENV=production npm run build
|
||||
node ./scripts/deploy.js # currently updates the viewer on molstar.org/viewer
|
||||
|
||||
## Contributing
|
||||
Just open an issue or make a pull request. All contributions are welcome.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const path = require('path')
|
||||
const basePath = path.join(__dirname, '..', '..', 'src', 'mol-model-props', 'rcsb', 'graphql')
|
||||
|
||||
generate({
|
||||
schema: 'http://rest-dev.rcsb.org/graphql',
|
||||
schema: 'http://rest-staging.rcsb.org/graphql',
|
||||
documents: {
|
||||
[path.join(basePath, 'symmetry.gql.ts')]: {
|
||||
loader: path.join(__dirname, 'loader.js')
|
||||
|
||||
3820
package-lock.json
generated
3820
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
59
package.json
59
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.6",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -62,53 +62,54 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"benchmark": "^2.1.4",
|
||||
"circular-dependency-plugin": "^5.0.2",
|
||||
"concurrently": "^4.1.0",
|
||||
"circular-dependency-plugin": "^5.2.0",
|
||||
"concurrently": "^4.1.2",
|
||||
"cpx": "^1.5.0",
|
||||
"css-loader": "^2.1.1",
|
||||
"css-loader": "^3.2.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^3.0.1",
|
||||
"file-loader": "^4.2.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"graphql-code-generator": "^0.18.2",
|
||||
"graphql-codegen-time": "^0.18.2",
|
||||
"graphql-codegen-typescript-template": "^0.18.2",
|
||||
"jest": "^24.8.0",
|
||||
"jest": "^24.9.0",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"mini-css-extract-plugin": "^0.7.0",
|
||||
"mini-css-extract-plugin": "^0.8.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"raw-loader": "^2.0.0",
|
||||
"raw-loader": "^3.1.0",
|
||||
"resolve-url-loader": "^3.1.0",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"sass-loader": "^7.3.1",
|
||||
"simple-git": "^1.124.0",
|
||||
"style-loader": "^1.0.0",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.17.0",
|
||||
"typescript": "^3.5.1",
|
||||
"uglify-js": "^3.6.0",
|
||||
"util.promisify": "^1.0.0",
|
||||
"webpack": "^4.32.2",
|
||||
"webpack-cli": "^3.3.2"
|
||||
"tslint": "^5.19.0",
|
||||
"typescript": "^3.5.3",
|
||||
"webpack": "^4.39.2",
|
||||
"webpack-cli": "^3.3.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.36",
|
||||
"@types/benchmark": "^1.0.31",
|
||||
"@types/compression": "0.0.36",
|
||||
"@types/express": "^4.16.1",
|
||||
"@types/jest": "^24.0.13",
|
||||
"@types/node": "^12.0.4",
|
||||
"@types/node-fetch": "^2.3.4",
|
||||
"@types/react": "^16.8.19",
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/swagger-ui-dist": "3.0.0",
|
||||
"@types/webgl2": "0.0.4",
|
||||
"@types/compression": "1.0.1",
|
||||
"@types/express": "^4.17.1",
|
||||
"@types/jest": "^24.0.18",
|
||||
"@types/node": "^12.7.2",
|
||||
"@types/node-fetch": "^2.5.0",
|
||||
"@types/react": "^16.9.2",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
"@types/swagger-ui-dist": "3.0.3",
|
||||
"@types/webgl2": "0.0.5",
|
||||
"argparse": "^1.0.10",
|
||||
"compression": "^1.7.4",
|
||||
"express": "^4.17.1",
|
||||
"graphql": "^14.3.1",
|
||||
"graphql": "^14.4.2",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react": "^16.9.0",
|
||||
"react-dom": "^16.9.0",
|
||||
"rxjs": "^6.5.2",
|
||||
"swagger-ui-dist": "^3.22.2",
|
||||
"swagger-ui-dist": "^3.23.5",
|
||||
"util.promisify": "^1.0.0",
|
||||
"xhr2": "^0.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
59
scripts/deploy.js
Normal file
59
scripts/deploy.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
const git = require('simple-git')
|
||||
const path = require('path')
|
||||
const fs = require("fs")
|
||||
const fse = require("fs-extra")
|
||||
|
||||
const remoteUrl = "https://github.com/molstar/molstar.github.io.git"
|
||||
const buildDir = path.resolve(__dirname, '../build/')
|
||||
const deployDir = path.resolve(buildDir, 'deploy/')
|
||||
const localPath = path.resolve(deployDir, 'molstar.github.io/')
|
||||
|
||||
function log(command, stdout, stderr) {
|
||||
if (command) {
|
||||
console.log('\n###', command)
|
||||
stdout.pipe(process.stdout)
|
||||
stderr.pipe(process.stderr)
|
||||
}
|
||||
}
|
||||
|
||||
function copyViewer() {
|
||||
console.log('\n###', 'copy viewer files')
|
||||
const viewerBuildPath = path.resolve(buildDir, '../build/viewer/')
|
||||
const viewerDeployPath = path.resolve(localPath, 'viewer/')
|
||||
fse.copySync(viewerBuildPath, viewerDeployPath, { overwrite: true })
|
||||
}
|
||||
|
||||
if (!fs.existsSync(localPath)) {
|
||||
console.log('\n###', 'create localPath')
|
||||
fs.mkdirSync(localPath, { recursive: true })
|
||||
}
|
||||
|
||||
process.chdir(localPath);
|
||||
|
||||
if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
|
||||
console.log('\n###', 'clone repository')
|
||||
git()
|
||||
.outputHandler(log)
|
||||
.clone(remoteUrl, localPath)
|
||||
.fetch(['--all'])
|
||||
.exec(copyViewer)
|
||||
.add(['-A'])
|
||||
.commit('updated viewer')
|
||||
.push()
|
||||
} else {
|
||||
console.log('\n###', 'update repository')
|
||||
git()
|
||||
.outputHandler(log)
|
||||
.fetch(['--all'])
|
||||
.reset(['--hard', 'origin/master'])
|
||||
.exec(copyViewer)
|
||||
.add(['-A'])
|
||||
.commit('updated viewer')
|
||||
.push()
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -15,12 +15,13 @@ const readFile = util.promisify(fs.readFile)
|
||||
const writeFile = util.promisify(fs.writeFile)
|
||||
|
||||
import { Progress } from '../../mol-task'
|
||||
import { Database, Table, DatabaseCollection, Column } from '../../mol-data/db'
|
||||
import { Database, Table, DatabaseCollection } from '../../mol-data/db'
|
||||
import { CIF } from '../../mol-io/reader/cif'
|
||||
import { CifWriter } from '../../mol-io/writer/cif'
|
||||
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd'
|
||||
import { SetUtils } from '../../mol-util/set'
|
||||
import { DefaultMap } from '../../mol-util/map'
|
||||
import { mmCIF_chemCompBond_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
|
||||
export async function ensureAvailable(path: string, url: string) {
|
||||
if (FORCE_DOWNLOAD || !fs.existsSync(path)) {
|
||||
@@ -74,16 +75,6 @@ export function getEncodedCif(name: string, database: Database<Database.Schema>,
|
||||
type CCB = Table<CCD_Schema['chem_comp_bond']>
|
||||
type CCA = Table<CCD_Schema['chem_comp_atom']>
|
||||
|
||||
const ChemCompBond_Schema = {
|
||||
comp_id: CCD_Schema['chem_comp_bond'].comp_id,
|
||||
atom_id_1: CCD_Schema['chem_comp_bond'].atom_id_1,
|
||||
atom_id_2: CCD_Schema['chem_comp_bond'].atom_id_2,
|
||||
value_order: CCD_Schema['chem_comp_bond'].value_order,
|
||||
pdbx_aromatic_flag: CCD_Schema['chem_comp_bond'].pdbx_aromatic_flag,
|
||||
pdbx_stereo_config: CCD_Schema['chem_comp_bond'].pdbx_stereo_config,
|
||||
molstar_protonation_variant: Column.Schema.Str()
|
||||
}
|
||||
|
||||
function ccbKey(compId: string, atomId1: string, atomId2: string) {
|
||||
return atomId1 < atomId2 ? `${compId}:${atomId1}-${atomId2}` : `${compId}:${atomId2}-${atomId1}`
|
||||
}
|
||||
@@ -202,14 +193,14 @@ async function createBonds() {
|
||||
}
|
||||
}
|
||||
|
||||
const bondTable = Table.ofArrays(ChemCompBond_Schema, {
|
||||
const bondTable = Table.ofArrays(mmCIF_chemCompBond_schema, {
|
||||
comp_id, atom_id_1, atom_id_2, value_order,
|
||||
pdbx_aromatic_flag, pdbx_stereo_config, molstar_protonation_variant
|
||||
})
|
||||
|
||||
const bondDatabase = Database.ofTables(
|
||||
TABLE_NAME,
|
||||
{ chem_comp_bond: ChemCompBond_Schema },
|
||||
{ chem_comp_bond: mmCIF_chemCompBond_schema },
|
||||
{ chem_comp_bond: bondTable }
|
||||
)
|
||||
|
||||
@@ -220,12 +211,15 @@ async function run(out: string, binary = false) {
|
||||
const bonds = await createBonds()
|
||||
|
||||
const cif = getEncodedCif(TABLE_NAME, bonds, binary)
|
||||
if (!fs.existsSync(path.dirname(out))) {
|
||||
fs.mkdirSync(path.dirname(out));
|
||||
}
|
||||
writeFile(out, cif)
|
||||
}
|
||||
|
||||
const TABLE_NAME = 'CHEM_COMP_BONDS'
|
||||
|
||||
const DATA_DIR = path.join(__dirname, '..', '..', '..', 'data')
|
||||
const DATA_DIR = path.join(__dirname, '..', '..', '..', 'build/data')
|
||||
const CCD_PATH = path.join(DATA_DIR, 'components.cif')
|
||||
const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif')
|
||||
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif'
|
||||
|
||||
87
src/apps/demos/lighting/index.html
Normal file
87
src/apps/demos/lighting/index.html
Normal file
@@ -0,0 +1,87 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<title>Mol* Lighting Demo</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#app {
|
||||
position: absolute;
|
||||
left: 160px;
|
||||
top: 100px;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: absolute;
|
||||
width: 150px;
|
||||
top: 100px;
|
||||
left: 780px;
|
||||
}
|
||||
|
||||
#controls > button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
|
||||
#controls > input, #controls > select {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="app.css" />
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id='controls'></div>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
LightingDemo.init('app')
|
||||
LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' })
|
||||
|
||||
addHeader('Example PDB IDs');
|
||||
addControl('1M07', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' }));
|
||||
addControl('6HY0', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6HY0.cif', assemblyId: '1' }));
|
||||
addControl('6QVK', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6QVK.cif', assemblyId: '1' }));
|
||||
addControl('1RB8', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1RB8.cif', assemblyId: '1' }));
|
||||
|
||||
addSeparator()
|
||||
|
||||
addHeader('Lighting Presets');
|
||||
addControl('Illustrative', () => LightingDemo.setPreset('illustrative'));
|
||||
addControl('Standard', () => LightingDemo.setPreset('standard'));
|
||||
addControl('Ambient Occlusion', () => LightingDemo.setPreset('occlusion'));
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
function $(id) { return document.getElementById(id); }
|
||||
|
||||
function addControl(label, action) {
|
||||
var btn = document.createElement('button');
|
||||
btn.onclick = action;
|
||||
btn.innerText = label;
|
||||
$('controls').appendChild(btn);
|
||||
}
|
||||
|
||||
function addSeparator() {
|
||||
var hr = document.createElement('br');
|
||||
$('controls').appendChild(hr);
|
||||
}
|
||||
|
||||
function addHeader(header) {
|
||||
var h = document.createElement('h3');
|
||||
h.innerText = header;
|
||||
$('controls').appendChild(h);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
166
src/apps/demos/lighting/index.ts
Normal file
166
src/apps/demos/lighting/index.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createPlugin, DefaultPluginSpec } from '../../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../../mol-plugin/command';
|
||||
import { StateTransforms } from '../../../mol-plugin/state/transforms';
|
||||
import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation';
|
||||
import { PluginStateObject as PSO } from '../../../mol-plugin/state/objects';
|
||||
import { StateBuilder } from '../../../mol-state';
|
||||
import { Canvas3DProps } from '../../../mol-canvas3d/canvas3d';
|
||||
require('mol-plugin/skin/light.scss')
|
||||
|
||||
type SupportedFormats = 'cif' | 'pdb'
|
||||
type LoadParams = { url: string, format?: SupportedFormats, assemblyId?: string }
|
||||
|
||||
const Canvas3DPresets = {
|
||||
illustrative: {
|
||||
multiSample: {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusionEnable: true,
|
||||
occlusionBias: 0.8,
|
||||
occlusionKernelSize: 6,
|
||||
outlineEnable: true,
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 1,
|
||||
lightIntensity: 0,
|
||||
}
|
||||
},
|
||||
occlusion: {
|
||||
multiSample: {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusionEnable: true,
|
||||
occlusionBias: 0.8,
|
||||
occlusionKernelSize: 6,
|
||||
outlineEnable: false,
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
lightIntensity: 0.6,
|
||||
}
|
||||
},
|
||||
standard: {
|
||||
multiSample: {
|
||||
mode: 'off' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusionEnable: false,
|
||||
outlineEnable: false,
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
lightIntensity: 0.6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Canvas3DPreset = keyof typeof Canvas3DPresets
|
||||
|
||||
function getPreset(preset: Canvas3DPreset) {
|
||||
switch (preset) {
|
||||
case 'illustrative': return Canvas3DPresets['illustrative']
|
||||
case 'standard': return Canvas3DPresets['standard']
|
||||
case 'occlusion': return Canvas3DPresets['occlusion']
|
||||
}
|
||||
}
|
||||
|
||||
class LightingDemo {
|
||||
plugin: PluginContext;
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
|
||||
}
|
||||
});
|
||||
|
||||
this.setPreset('illustrative');
|
||||
}
|
||||
|
||||
setPreset(preset: Canvas3DPreset) {
|
||||
const props = getPreset(preset)
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
|
||||
...props,
|
||||
multiSample: {
|
||||
...this.plugin.canvas3d.props.multiSample,
|
||||
...props.multiSample
|
||||
},
|
||||
renderer: {
|
||||
...this.plugin.canvas3d.props.renderer,
|
||||
...props.renderer
|
||||
},
|
||||
postprocessing: {
|
||||
...this.plugin.canvas3d.props.postprocessing,
|
||||
...props.postprocessing
|
||||
},
|
||||
}});
|
||||
}
|
||||
|
||||
private download(b: StateBuilder.To<PSO.Root>, url: string) {
|
||||
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
|
||||
}
|
||||
|
||||
private parse(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) {
|
||||
const parsed = format === 'cif'
|
||||
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
|
||||
return parsed
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
|
||||
.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' });
|
||||
}
|
||||
|
||||
private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'spacefill', {}, 'illustrative'), { ref: 'seq-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'ball-and-stick'), { ref: 'het-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = '' }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.dataState;
|
||||
|
||||
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
|
||||
loadType = 'full';
|
||||
} else if (this.loadedParams.url === url) {
|
||||
if (state.select('asm').length > 0) loadType = 'update';
|
||||
}
|
||||
|
||||
let tree: StateBuilder.Root;
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
|
||||
tree = state.build();
|
||||
this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId));
|
||||
} else {
|
||||
tree = state.build();
|
||||
tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree });
|
||||
this.loadedParams = { url, format, assemblyId };
|
||||
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).LightingDemo = new LightingDemo();
|
||||
@@ -112,7 +112,7 @@ const state: State = {
|
||||
|
||||
function formatParams(def: QueryDefinition) {
|
||||
const prms = Object.create(null);
|
||||
for (const p of def.params) {
|
||||
for (const p of def.jsonParams) {
|
||||
prms[p.name] = p.exampleValues ? p.exampleValues[0] : void 0;
|
||||
}
|
||||
return JSON.stringify(prms, void 0, 2);
|
||||
|
||||
@@ -19,7 +19,7 @@ function paramInfo(param: PD.Any, offset: number): string {
|
||||
case 'conditioned': return getParams(param.conditionParams, offset);
|
||||
case 'multi-select': return `Array of ${oToS(param.options)}`;
|
||||
case 'color': return 'Color as 0xrrggbb';
|
||||
case 'color-scale': return `One of ${oToS(param.options)}`;
|
||||
case 'color-list': return `One of ${oToS(param.options)}`;
|
||||
case 'vec3': return `3D vector [x, y, z]`;
|
||||
case 'file': return `JavaScript File Handle`;
|
||||
case 'select': return `One of ${oToS(param.options)}`;
|
||||
|
||||
57
src/apps/viewer/extensions/cellpack/data.ts
Normal file
57
src/apps/viewer/extensions/cellpack/data.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec3, Quat } from '../../../../mol-math/linear-algebra';
|
||||
|
||||
export interface CellPack {
|
||||
cell: Cell
|
||||
packings: CellPacking[]
|
||||
}
|
||||
|
||||
export interface CellPacking {
|
||||
name: string,
|
||||
location: 'surface' | 'interior' | 'cytoplasme',
|
||||
ingredients: Packing['ingredients']
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export interface Cell {
|
||||
recipe: Recipe
|
||||
cytoplasme?: Packing
|
||||
compartments?: { [key: string]: Compartment }
|
||||
}
|
||||
|
||||
export interface Recipe {
|
||||
setupfile: string
|
||||
paths: [string, string][] // [name: string, path: string][]
|
||||
version: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface Compartment {
|
||||
surface?: Packing
|
||||
interior?: Packing
|
||||
}
|
||||
|
||||
export interface Packing {
|
||||
ingredients: { [key: string]: Ingredient }
|
||||
}
|
||||
|
||||
export interface Ingredient {
|
||||
source: IngredientSource
|
||||
results: [Vec3, Quat][]
|
||||
name: string
|
||||
positions?: [Vec3[]] // why wrapped in an extra array?
|
||||
radii?: [number[]] // why wrapped in an extra array?
|
||||
nbCurve?: number
|
||||
}
|
||||
|
||||
export interface IngredientSource {
|
||||
pdb: string
|
||||
transform: { center: boolean, translate?: Vec3 }
|
||||
biomt?: boolean
|
||||
}
|
||||
238
src/apps/viewer/extensions/cellpack/model.ts
Normal file
238
src/apps/viewer/extensions/cellpack/model.ts
Normal file
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateAction } from '../../../../mol-state';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../../../mol-plugin/state/objects';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Ingredient, CellPacking } from './data';
|
||||
import { getFromPdb, getFromCellPackDB } from './util';
|
||||
import { Model, Structure, StructureSymmetry, StructureSelection, Queries, QueryContext } from '../../../../mol-model/structure';
|
||||
import { trajectoryFromMmCIF } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { trajectoryFromPDB } from '../../../../mol-model-formats/structure/pdb';
|
||||
import { Mat4, Vec3, Quat } from '../../../../mol-math/linear-algebra';
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { StructureRepresentation3DHelpers } from '../../../../mol-plugin/state/transforms/representation';
|
||||
import { StateTransforms } from '../../../../mol-plugin/state/transforms';
|
||||
import { distinctColors } from '../../../../mol-util/color/distinct';
|
||||
import { ModelIndexColorThemeProvider } from '../../../../mol-theme/color/model-index';
|
||||
import { Hcl } from '../../../../mol-util/color/spaces/hcl';
|
||||
import { ParseCellPack, StructureFromCellpack } from './state';
|
||||
import { formatMolScript } from '../../../../mol-script/language/expression-formatter';
|
||||
import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/cellPACK_database_1.1.0/results/${fileName}`
|
||||
}
|
||||
|
||||
async function getModel(id: string, baseUrl: string) {
|
||||
let model: Model;
|
||||
if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
|
||||
// return
|
||||
const cif = await getFromPdb(id)
|
||||
model = (await trajectoryFromMmCIF(cif).run())[0]
|
||||
} else {
|
||||
const pdb = await getFromCellPackDB(id, baseUrl)
|
||||
model = (await trajectoryFromPDB(pdb).run())[0]
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
async function getStructure(model: Model, props: { assembly?: string }) {
|
||||
let structure = Structure.ofModel(model)
|
||||
const { assembly } = props
|
||||
|
||||
if (assembly) {
|
||||
structure = await StructureSymmetry.buildAssembly(structure, assembly).run()
|
||||
}
|
||||
|
||||
const query = Queries.internal.atomicSequence()
|
||||
const result = query(new QueryContext(structure))
|
||||
structure = StructureSelection.unionStructure(result)
|
||||
|
||||
return structure
|
||||
}
|
||||
|
||||
function getTransform(trans: Vec3, rot: Quat) {
|
||||
const q: Quat = Quat.create(-rot[3], rot[0], rot[1], rot[2])
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q)
|
||||
Mat4.transpose(m, m)
|
||||
Mat4.scale(m, m, Vec3.create(-1.0, 1.0, -1.0))
|
||||
Mat4.setTranslation(m, trans)
|
||||
return m
|
||||
}
|
||||
|
||||
function getTransforms(results: Ingredient['results']) {
|
||||
return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1]))
|
||||
}
|
||||
|
||||
function getAssembly(transforms: Mat4[], structure: Structure) {
|
||||
const builder = Structure.Builder()
|
||||
const { units } = structure;
|
||||
|
||||
for (let i = 0, il = transforms.length; i < il; ++i) {
|
||||
const id = `${i + 1}`
|
||||
const op = SymmetryOperator.create(id, transforms[i], { id, operList: [ id ] })
|
||||
for (const unit of units) {
|
||||
builder.addWithOperator(unit, op)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.getStructure();
|
||||
}
|
||||
|
||||
async function getIngredientStructure(ingredient: Ingredient, baseUrl: string) {
|
||||
const { name, source, results, nbCurve } = ingredient
|
||||
|
||||
// TODO can these be added to the library?
|
||||
if (name === 'HIV1_CAhex_0_1_0') return
|
||||
if (name === 'HIV1_CAhexCyclophilA_0_1_0') return
|
||||
if (name === 'iLDL') return
|
||||
if (source.pdb === 'None') return
|
||||
|
||||
// TODO handle fibers
|
||||
if (nbCurve) return
|
||||
|
||||
const model = await getModel(source.pdb || name, baseUrl)
|
||||
if (!model) return
|
||||
|
||||
const structure = await getStructure(model, { assembly: source.biomt ? '1' : undefined })
|
||||
const transforms = getTransforms(results)
|
||||
const assembly = getAssembly(transforms, structure)
|
||||
return assembly
|
||||
}
|
||||
|
||||
export function createStructureFromCellPack(packing: CellPacking, baseUrl: string) {
|
||||
return Task.create('Create Packing Structure', async ctx => {
|
||||
const { ingredients, name } = packing
|
||||
const structures: Structure[] = []
|
||||
for (const iName in ingredients) {
|
||||
if (ctx.shouldUpdate) ctx.update(iName)
|
||||
const s = await getIngredientStructure(ingredients[iName], baseUrl)
|
||||
if (s) structures.push(s)
|
||||
}
|
||||
|
||||
const builder = Structure.Builder({ label: name })
|
||||
let offsetInvariantId = 0
|
||||
for (const s of structures) {
|
||||
let maxInvariantId = 0
|
||||
for (const u of s.units) {
|
||||
const invariantId = u.invariantId + offsetInvariantId
|
||||
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId
|
||||
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, invariantId)
|
||||
}
|
||||
offsetInvariantId += maxInvariantId
|
||||
}
|
||||
|
||||
const s = builder.getStructure()
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
export const LoadCellPackModel = StateAction.build({
|
||||
display: { name: 'Load CellPack Model' },
|
||||
params: {
|
||||
id: PD.Select('influenza_model1.json', [
|
||||
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
|
||||
['BloodPlasma1.2.apr.json', 'BloodPlasma1.2'],
|
||||
['BloodSerumfillResult.apr', 'BloodSerumfillResult'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
||||
['influenza_model1.json', 'influenza_model1'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
['NM_Analysis_FigureC1.4.cpr.json', 'NM_Analysis_FigureC1.4']
|
||||
]),
|
||||
baseUrl: PD.Text('https://cdn.jsdelivr.net/gh/mesoscope/cellPACK_data@master/'),
|
||||
preset: PD.Group({
|
||||
traceOnly: PD.Boolean(false),
|
||||
representation: PD.Select('spacefill', [
|
||||
['spacefill', 'Spacefill'],
|
||||
['gaussian-surface', 'Gaussian Surface'],
|
||||
['point', 'Point'],
|
||||
])
|
||||
}, { isExpanded: true })
|
||||
},
|
||||
from: PSO.Root
|
||||
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
|
||||
const url = getCellPackModelUrl(params.id, params.baseUrl)
|
||||
|
||||
const root = state.build().toRoot();
|
||||
|
||||
const cellPackBuilder = root
|
||||
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.id })
|
||||
.apply(StateTransforms.Data.ParseJson)
|
||||
.apply(ParseCellPack)
|
||||
|
||||
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(taskCtx)
|
||||
const { packings } = cellPackObject.data
|
||||
let tree = state.build().to(cellPackBuilder.ref);
|
||||
|
||||
const colors = distinctColors(packings.length)
|
||||
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
const hcl = Hcl.fromColor(Hcl(), colors[i])
|
||||
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number]
|
||||
const p = { packing: i, baseUrl: params.baseUrl }
|
||||
|
||||
const expression = params.preset.traceOnly
|
||||
? MS.struct.generator.atomGroups({
|
||||
'atom-test': MS.core.logic.or([
|
||||
MS.core.rel.eq([MS.ammp('label_atom_id'), 'CA']),
|
||||
MS.core.rel.eq([MS.ammp('label_atom_id'), 'P'])
|
||||
])
|
||||
})
|
||||
: MS.struct.generator.all()
|
||||
const query = { language: 'mol-script' as const, expression: formatMolScript(expression) }
|
||||
|
||||
tree.apply(StructureFromCellpack, p)
|
||||
.apply(StateTransforms.Model.UserStructureSelection, { query })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
StructureRepresentation3DHelpers.createParams(ctx, Structure.Empty, {
|
||||
repr: getReprParams(ctx, params.preset),
|
||||
color: [
|
||||
ModelIndexColorThemeProvider,
|
||||
(c, ctx) => {
|
||||
return {
|
||||
palette: {
|
||||
name: 'generate',
|
||||
params: {
|
||||
hue, chroma: [30, 80], luminance: [15, 85],
|
||||
clusteringStepCount: 50, minSampleCount: 800,
|
||||
maxCount: 75
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}))
|
||||
}
|
||||
|
||||
await state.updateTree(tree).runInContext(taskCtx);
|
||||
}));
|
||||
|
||||
function getReprParams(ctx: PluginContext, params: { representation: 'spacefill' | 'gaussian-surface' | 'point', traceOnly: boolean }) {
|
||||
const { representation, traceOnly } = params
|
||||
switch (representation) {
|
||||
case 'spacefill':
|
||||
return traceOnly
|
||||
? [
|
||||
ctx.structureRepresentation.registry.get('spacefill'),
|
||||
() => ({ sizeFactor: 2 })
|
||||
] as [any, any]
|
||||
: ctx.structureRepresentation.registry.get('spacefill')
|
||||
case 'gaussian-surface':
|
||||
return [
|
||||
ctx.structureRepresentation.registry.get('gaussian-surface'),
|
||||
() => ({
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2,
|
||||
alpha: 1.0, flatShaded: false, doubleSided: false,
|
||||
})
|
||||
] as [any, any]
|
||||
case 'point':
|
||||
return ctx.structureRepresentation.registry.get('point')
|
||||
}
|
||||
}
|
||||
93
src/apps/viewer/extensions/cellpack/state.ts
Normal file
93
src/apps/viewer/extensions/cellpack/state.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin/state/objects';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Structure } from '../../../../mol-model/structure';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { CellPack as _CellPack, Cell, CellPacking } from './data';
|
||||
import { createStructureFromCellPack } from './model';
|
||||
|
||||
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
|
||||
|
||||
export { ParseCellPack }
|
||||
type ParseCellPack = typeof ParseCellPack
|
||||
const ParseCellPack = PluginStateTransform.BuiltIn({
|
||||
name: 'parse-cellpack',
|
||||
display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
|
||||
from: PSO.Format.Json,
|
||||
to: CellPack
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse CellPack', async ctx => {
|
||||
const cell = a.data as Cell
|
||||
|
||||
const packings: CellPacking[] = []
|
||||
const { compartments, cytoplasme } = cell
|
||||
if (compartments) {
|
||||
for (const name in compartments) {
|
||||
const { surface, interior } = compartments[name]
|
||||
if (surface) packings.push({ name, location: 'surface', ingredients: surface.ingredients })
|
||||
if (interior) packings.push({ name, location: 'interior', ingredients: interior.ingredients })
|
||||
}
|
||||
}
|
||||
if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients })
|
||||
|
||||
return new CellPack({ cell, packings });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export { StructureFromCellpack }
|
||||
type StructureFromCellpack = typeof ParseCellPack
|
||||
const StructureFromCellpack = PluginStateTransform.BuiltIn({
|
||||
name: 'structure-from-cellpack',
|
||||
display: { name: 'Structure from CellPack', description: 'Create Structure from CellPack Packing' },
|
||||
from: CellPack,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: a => {
|
||||
if (!a) {
|
||||
return {
|
||||
packing: PD.Numeric(0, {}, { description: 'Packing Index' }),
|
||||
baseUrl: PD.Text('https://cdn.jsdelivr.net/gh/mesoscope/cellPACK_data@master/')
|
||||
};
|
||||
}
|
||||
const options = a.data.packings.map((d, i) => [i, d.name] as [number, string])
|
||||
return {
|
||||
packing: PD.Select(0, options),
|
||||
baseUrl: PD.Text('https://cdn.jsdelivr.net/gh/mesoscope/cellPACK_data@master/')
|
||||
}
|
||||
}
|
||||
})({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Structure from CellPack', async ctx => {
|
||||
const packing = a.data.packings[params.packing]
|
||||
const structure = await createStructureFromCellPack(packing, params.baseUrl).runInContext(ctx)
|
||||
return new PSO.Molecule.Structure(structure, { label: packing.name })
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export { AddStructure }
|
||||
type AddStructure = typeof AddStructure
|
||||
const AddStructure = PluginStateTransform.BuiltIn({
|
||||
name: 'add-structure',
|
||||
display: { name: 'Add Structure', description: 'Add existing molecular structure.' },
|
||||
from: PSO.Root,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: {
|
||||
structure: PD.Value<Structure>(Structure.Empty),
|
||||
label: PD.Text('Structure')
|
||||
}
|
||||
})({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
const s = params.structure
|
||||
const props = { label: params.label, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` };
|
||||
return new PSO.Molecule.Structure(s, props);
|
||||
})
|
||||
}
|
||||
});
|
||||
47
src/apps/viewer/extensions/cellpack/util.ts
Normal file
47
src/apps/viewer/extensions/cellpack/util.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CIF } from '../../../../mol-io/reader/cif'
|
||||
import { parsePDB } from '../../../../mol-io/reader/pdb/parser';
|
||||
|
||||
async function parseCif(data: string|Uint8Array) {
|
||||
const comp = CIF.parse(data);
|
||||
const parsed = await comp.run();
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
async function parsePDBfile(data: string) {
|
||||
const comp = parsePDB(data);
|
||||
const parsed = await comp.run();
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
async function downloadCif(url: string, isBinary: boolean) {
|
||||
const data = await fetch(url);
|
||||
return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text());
|
||||
}
|
||||
|
||||
async function downloadPDB(url: string) {
|
||||
const data = await fetch(url);
|
||||
return parsePDBfile(await data.text());
|
||||
}
|
||||
|
||||
export async function getFromPdb(id: string) {
|
||||
const parsed = await downloadCif(`https://files.rcsb.org/download/${id}.cif`, false);
|
||||
return parsed.blocks[0];
|
||||
}
|
||||
|
||||
function getCellPackDataUrl(id: string, baseUrl: string) {
|
||||
const url = `${baseUrl}/cellPACK_database_1.1.0/other/${id}`
|
||||
return url.endsWith('.pdb') ? url : `${url}.pdb`
|
||||
}
|
||||
|
||||
export async function getFromCellPackDB(id: string, baseUrl: string) {
|
||||
const parsed = await downloadPDB(getCellPackDataUrl(id, baseUrl));
|
||||
return parsed;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import { PluginStateSnapshotManager } from '../../../mol-plugin/state/snapshots'
|
||||
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
|
||||
import { Text } from '../../../mol-geo/geometry/text/text';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { ColorNames } from '../../../mol-util/color/tables';
|
||||
import { ColorNames } from '../../../mol-util/color/names';
|
||||
import { Camera } from '../../../mol-canvas3d/camera';
|
||||
import { StructureRepresentation3DHelpers } from '../../../mol-plugin/state/transforms/representation';
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
@@ -10,6 +11,8 @@ import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/command';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { CreateJoleculeState } from './extensions/jolecule';
|
||||
import { LoadCellPackModel } from './extensions/cellpack/model';
|
||||
import { StructureFromCellpack } from './extensions/cellpack/state';
|
||||
require('mol-plugin/skin/light.scss')
|
||||
|
||||
function getParam(name: string, regex: string): string {
|
||||
@@ -21,7 +24,12 @@ const hideControls = getParam('hide-controls', `[^&]+`) === '1';
|
||||
|
||||
function init() {
|
||||
const spec: PluginSpec = {
|
||||
actions: [...DefaultPluginSpec.actions, PluginSpec.Action(CreateJoleculeState)],
|
||||
actions: [
|
||||
...DefaultPluginSpec.actions,
|
||||
PluginSpec.Action(CreateJoleculeState),
|
||||
PluginSpec.Action(LoadCellPackModel),
|
||||
PluginSpec.Action(StructureFromCellpack),
|
||||
],
|
||||
behaviors: [...DefaultPluginSpec.behaviors],
|
||||
animations: [...DefaultPluginSpec.animations || []],
|
||||
customParamEditors: DefaultPluginSpec.customParamEditors,
|
||||
@@ -29,6 +37,9 @@ function init() {
|
||||
initial: {
|
||||
isExpanded: true,
|
||||
showControls: !hideControls
|
||||
},
|
||||
controls: {
|
||||
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
== v3.3 ==
|
||||
|
||||
* Camera Clipping.
|
||||
|
||||
== v3.2 ==
|
||||
|
||||
* Fixed assembly loading.
|
||||
* Better HET group focus.
|
||||
|
||||
== v3.0 ==
|
||||
|
||||
* Fixed initial camera zoom.
|
||||
|
||||
@@ -105,6 +105,8 @@ export enum StateElements {
|
||||
ModelProps = 'model-props',
|
||||
Assembly = 'assembly',
|
||||
|
||||
VolumeStreaming = 'volume-streaming',
|
||||
|
||||
Sequence = 'sequence',
|
||||
SequenceVisual = 'sequence-visual',
|
||||
Het = 'het',
|
||||
@@ -113,5 +115,6 @@ export enum StateElements {
|
||||
Water = 'water',
|
||||
WaterVisual = 'water-visual',
|
||||
|
||||
HetGroupFocus = 'het-group-focus'
|
||||
HetGroupFocus = 'het-group-focus',
|
||||
HetGroupFocusGroup = 'het-group-focus-group'
|
||||
}
|
||||
@@ -40,6 +40,13 @@
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#volume-streaming-wrapper {
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 780px;
|
||||
width: 300px;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="app.css" />
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
@@ -55,6 +62,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
<div id="volume-streaming-wrapper"></div>
|
||||
<script>
|
||||
// it might be a good idea to define these colors in a separate script file
|
||||
var CustomColors = [0x00ff00, 0x0000ff];
|
||||
@@ -66,7 +74,7 @@
|
||||
|
||||
function $(id) { return document.getElementById(id); }
|
||||
|
||||
var pdbId = '1eve', assemblyId= 'preferred';
|
||||
var pdbId = '1cbs', assemblyId= 'preferred';
|
||||
var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif';
|
||||
var format = 'cif';
|
||||
|
||||
@@ -122,7 +130,11 @@
|
||||
addSeparator();
|
||||
|
||||
addHeader('Camera');
|
||||
addControl('Toggle Spin', () => PluginWrapper.toggleSpin());
|
||||
addControl('Reset Position', () => PluginWrapper.camera.resetPosition());
|
||||
addControl('Toggle Spin', () => PluginWrapper.camera.toggleSpin());
|
||||
// Same as "wheel icon" and Viewport options
|
||||
addControl('Clip', () => PluginWrapper.viewport.setSettings({ clip: [33, 66] }));
|
||||
addControl('Reset Clip', () => PluginWrapper.viewport.setSettings({ clip: [1, 100] }));
|
||||
|
||||
addSeparator();
|
||||
|
||||
@@ -141,7 +153,8 @@
|
||||
addSeparator();
|
||||
addHeader('Misc');
|
||||
|
||||
addControl('Apply Evo Cons', () => PluginWrapper.coloring.evolutionaryConservation());
|
||||
addControl('Apply Evo Cons Style', () => PluginWrapper.coloring.evolutionaryConservation());
|
||||
addControl('Apply Evo Cons Colors', () => PluginWrapper.coloring.evolutionaryConservation({ sequence: true, het: false, keepStyle: true }));
|
||||
addControl('Default Visuals', () => PluginWrapper.updateStyle());
|
||||
|
||||
addSeparator();
|
||||
@@ -151,6 +164,11 @@
|
||||
addHetGroupsContainer();
|
||||
|
||||
addSeparator();
|
||||
addHeader('Exp. Data');
|
||||
addControl('Init', () => PluginWrapper.experimentalData.init($('volume-streaming-wrapper')));
|
||||
addControl('Remove', () => PluginWrapper.experimentalData.remove());
|
||||
|
||||
addSeparator();
|
||||
addHeader('State');
|
||||
|
||||
var snapshot;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
@@ -13,11 +14,11 @@ import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transfo
|
||||
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 { StateBuilder, StateObject } from '../../mol-state';
|
||||
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 } from './ui/controls';
|
||||
import { ControlsWrapper, volumeStreamingControls } from './ui/controls';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { Scheduler } from '../../mol-task';
|
||||
import { createProteopediaCustomTheme } from './coloring';
|
||||
@@ -25,7 +26,10 @@ import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { BuiltInStructureRepresentations } from '../../mol-repr/structure/registry';
|
||||
import { BuiltInColorThemes } from '../../mol-theme/color';
|
||||
import { BuiltInSizeThemes } from '../../mol-theme/size';
|
||||
import { ColorNames } from '../../mol-util/color/tables';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { InitVolumeStreaming, CreateVolumeStreamingInfo } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
import { DefaultCanvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
|
||||
// import { Vec3 } from 'mol-math/linear-algebra';
|
||||
// import { ParamDefinition } from 'mol-util/param-definition';
|
||||
// import { Text } from 'mol-geo/geometry/text/text';
|
||||
@@ -33,7 +37,7 @@ require('../../mol-plugin/skin/light.scss')
|
||||
|
||||
class MolStarProteopediaWrapper {
|
||||
static VERSION_MAJOR = 3;
|
||||
static VERSION_MINOR = 1;
|
||||
static VERSION_MINOR = 3;
|
||||
|
||||
private _ev = RxEventHelper.create();
|
||||
|
||||
@@ -78,7 +82,7 @@ class MolStarProteopediaWrapper {
|
||||
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
|
||||
}
|
||||
|
||||
private model(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) {
|
||||
private model(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats) {
|
||||
const parsed = format === 'cif'
|
||||
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
@@ -187,7 +191,7 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = '', representationStyle }: LoadParams) {
|
||||
async load({ url, format = 'cif', assemblyId = 'deposited', representationStyle }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.dataState;
|
||||
@@ -200,14 +204,17 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref });
|
||||
const modelTree = this.model(this.download(state.build().toRoot(), url), format, assemblyId);
|
||||
const modelTree = this.model(this.download(state.build().toRoot(), url), format);
|
||||
await this.applyState(modelTree);
|
||||
const info = await this.doInfo(true);
|
||||
const structureTree = this.structure((assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId);
|
||||
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
|
||||
const structureTree = this.structure(asmId);
|
||||
await this.applyState(structureTree);
|
||||
} else {
|
||||
const tree = state.build();
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' }));
|
||||
const info = await this.doInfo(true);
|
||||
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: asmId }));
|
||||
await this.applyState(tree);
|
||||
}
|
||||
|
||||
@@ -235,6 +242,38 @@ class MolStarProteopediaWrapper {
|
||||
if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
}
|
||||
|
||||
viewport = {
|
||||
setSettings: (settings?: Canvas3DProps) => {
|
||||
PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
|
||||
settings: settings || DefaultCanvas3DParams
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
camera = {
|
||||
toggleSpin: () => this.toggleSpin(),
|
||||
resetPosition: () => PluginCommands.Camera.Reset.dispatch(this.plugin, { }),
|
||||
// setClip: (options?: { distance?: number, near?: number, far?: number }) => {
|
||||
// if (!options) {
|
||||
// PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
|
||||
// settings: {
|
||||
// cameraClipDistance: DefaultCanvas3DParams.cameraClipDistance,
|
||||
// clip: DefaultCanvas3DParams.clip
|
||||
// }
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// options = options || { };
|
||||
// const props = this.plugin.canvas3d.props;
|
||||
// const clipNear = typeof options.near === 'undefined' ? props.clip[0] : options.near;
|
||||
// const clipFar = typeof options.far === 'undefined' ? props.clip[1] : options.far;
|
||||
// PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, {
|
||||
// settings: { cameraClipDistance: options.distance, clip: [clipNear, clipFar] }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
animate = {
|
||||
modelIndex: {
|
||||
maxFPS: 8,
|
||||
@@ -247,53 +286,83 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
coloring = {
|
||||
evolutionaryConservation: async () => {
|
||||
await this.updateStyle({ sequence: { kind: 'spacefill' } }, true);
|
||||
evolutionaryConservation: async (params?: { sequence?: boolean, het?: boolean, keepStyle?: boolean }) => {
|
||||
if (!params || !params.keepStyle) {
|
||||
await this.updateStyle({ sequence: { kind: 'spacefill' } }, true);
|
||||
}
|
||||
|
||||
const state = this.state;
|
||||
|
||||
// const visuals = state.selectQ(q => q.ofType(PluginStateObject.Molecule.Structure.Representation3D).filter(c => c.transform.transformer === StateTransforms.Representation.StructureRepresentation3D));
|
||||
const tree = state.build();
|
||||
const colorTheme = { name: EvolutionaryConservation.Descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.Descriptor.name).defaultValues };
|
||||
|
||||
tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
|
||||
// for (const v of visuals) {
|
||||
// }
|
||||
|
||||
const tree = state.build();
|
||||
const colorTheme = { name: EvolutionaryConservation.Descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.Descriptor.name).defaultValues };
|
||||
|
||||
if (!params || !!params.sequence) {
|
||||
tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
|
||||
}
|
||||
if (params && !!params.het) {
|
||||
tree.to(StateElements.HetVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
|
||||
}
|
||||
}
|
||||
|
||||
private experimentalDataElement?: Element = void 0;
|
||||
experimentalData = {
|
||||
init: async (parent: Element) => {
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj!;
|
||||
const params = ParamDefinition.getDefaultValues(InitVolumeStreaming.definition.params!(asm, this.plugin));
|
||||
params.behaviorRef = StateElements.VolumeStreaming;
|
||||
params.defaultView = 'box';
|
||||
await this.plugin.runTask(this.state.applyAction(InitVolumeStreaming, params, StateElements.Assembly));
|
||||
this.experimentalDataElement = parent;
|
||||
volumeStreamingControls(this.plugin, parent);
|
||||
},
|
||||
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 });
|
||||
if (this.experimentalDataElement) {
|
||||
ReactDOM.unmountComponentAtNode(this.experimentalDataElement);
|
||||
this.experimentalDataElement = void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hetGroups = {
|
||||
reset: () => {
|
||||
const update = this.state.build().delete(StateElements.HetGroupFocus);
|
||||
PluginCommands.State.Update.dispatch(this.plugin, { state: this.state, tree: update });
|
||||
PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
},
|
||||
focusFirst: async (resn: string) => {
|
||||
focusFirst: async (compId: string) => {
|
||||
if (!this.state.transforms.has(StateElements.Assembly)) return;
|
||||
await PluginCommands.Camera.Reset.dispatch(this.plugin, { });
|
||||
|
||||
// const asm = (this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
|
||||
const update = this.state.build();
|
||||
|
||||
update.delete(StateElements.HetGroupFocus);
|
||||
update.delete(StateElements.HetGroupFocusGroup);
|
||||
|
||||
const surroundings = MS.struct.modifier.includeSurroundings({
|
||||
0: MS.struct.filter.first([
|
||||
MS.struct.generator.atomGroups({
|
||||
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), resn]),
|
||||
'group-by': MS.struct.atomProperty.macromolecular.residueKey()
|
||||
})
|
||||
]),
|
||||
radius: 5,
|
||||
'as-whole-residues': true
|
||||
});
|
||||
const core = MS.struct.filter.first([
|
||||
MS.struct.generator.atomGroups({
|
||||
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), compId]),
|
||||
'group-by': MS.core.str.concat([MS.struct.atomProperty.core.operatorName(), MS.struct.atomProperty.macromolecular.residueKey()])
|
||||
})
|
||||
]);
|
||||
const surroundings = MS.struct.modifier.includeSurroundings({ 0: core, radius: 5, 'as-whole-residues': true });
|
||||
|
||||
const sel = update.to(StateElements.Assembly)
|
||||
.apply(StateTransforms.Model.StructureSelection, { label: resn, query: surroundings }, { ref: StateElements.HetGroupFocus });
|
||||
const group = update.to(StateElements.Assembly).group(StateTransforms.Misc.CreateGroup, { label: compId }, { ref: StateElements.HetGroupFocusGroup });
|
||||
|
||||
sel.apply(StateTransforms.Representation.StructureRepresentation3D, this.createSurVisualParams());
|
||||
group.apply(StateTransforms.Model.StructureSelection, { label: 'Core', query: core }, { ref: StateElements.HetGroupFocus })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D, this.createCoreVisualParams());
|
||||
group.apply(StateTransforms.Model.StructureSelection, { label: 'Surroundings', query: surroundings })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D, this.createSurVisualParams());
|
||||
// sel.apply(StateTransforms.Representation.StructureLabels3D, {
|
||||
// target: { name: 'residues', params: { } },
|
||||
// options: {
|
||||
@@ -313,7 +382,7 @@ class MolStarProteopediaWrapper {
|
||||
// const position = Vec3.sub(Vec3.zero(), sphere.center, asmCenter);
|
||||
// Vec3.normalize(position, position);
|
||||
// Vec3.scaleAndAdd(position, sphere.center, position, sphere.radius);
|
||||
const snapshot = this.plugin.canvas3d.camera.getFocus(sphere.center, 0.75 * sphere.radius);
|
||||
const snapshot = this.plugin.canvas3d.camera.getFocus(sphere.center, Math.max(sphere.radius, 5));
|
||||
PluginCommands.Camera.SetSnapshot.dispatch(this.plugin, { snapshot, durationMs: 250 });
|
||||
}
|
||||
}
|
||||
@@ -327,6 +396,15 @@ class MolStarProteopediaWrapper {
|
||||
});
|
||||
}
|
||||
|
||||
private createCoreVisualParams() {
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
|
||||
return StructureRepresentation3DHelpers.createParams(this.plugin, asm.data, {
|
||||
repr: BuiltInStructureRepresentations['ball-and-stick'],
|
||||
// color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
|
||||
// size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
|
||||
});
|
||||
}
|
||||
|
||||
snapshot = {
|
||||
get: () => {
|
||||
return this.plugin.state.getSnapshot();
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { PluginUIComponent } from '../../../mol-plugin/ui/base';
|
||||
import { CurrentObject } from '../../../mol-plugin/ui/plugin';
|
||||
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 { TransformUpdaterControl } from '../../../mol-plugin/ui/state/update-transform';
|
||||
import { StateElements } from '../helpers';
|
||||
|
||||
export class ControlsWrapper extends PluginUIComponent {
|
||||
render() {
|
||||
@@ -18,4 +22,11 @@ export class ControlsWrapper extends PluginUIComponent {
|
||||
<CameraSnapshots />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export function volumeStreamingControls(plugin: PluginContext, parent: Element) {
|
||||
ReactDOM.render(<PluginContextContainer plugin={plugin}>
|
||||
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
|
||||
</PluginContextContainer>,
|
||||
parent);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export interface BooleanParamComponentProps {
|
||||
label: string
|
||||
param: PD.Boolean
|
||||
value: boolean
|
||||
onChange(v: boolean): void
|
||||
}
|
||||
|
||||
export interface BooleanParamComponentState {
|
||||
value: boolean
|
||||
}
|
||||
|
||||
export class BooleanParamComponent extends React.Component<BooleanParamComponentProps, BooleanParamComponentState> {
|
||||
state = {
|
||||
value: this.props.value
|
||||
}
|
||||
|
||||
onChange(value: boolean) {
|
||||
this.setState({ value })
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<span>{this.props.label} </span>
|
||||
<button onClick={e => this.onChange(!this.state.value) }>
|
||||
{this.state.value ? 'Off' : 'On'}
|
||||
</button>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../../mol-util/color/tables';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
|
||||
export interface ColorParamComponentProps {
|
||||
label: string
|
||||
param: PD.Color
|
||||
value: Color
|
||||
onChange(v: Color): void
|
||||
}
|
||||
|
||||
export interface ColorParamComponentState {
|
||||
value: Color
|
||||
}
|
||||
|
||||
export class ColorParamComponent extends React.Component<ColorParamComponentProps, ColorParamComponentState> {
|
||||
state = {
|
||||
value: this.props.value
|
||||
}
|
||||
|
||||
onChange(value: Color) {
|
||||
this.setState({ value })
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<span>{this.props.label} </span>
|
||||
<select value={this.state.value} onChange={e => this.onChange(Color(parseInt(e.target.value))) }>
|
||||
{Object.keys(ColorNames).map(name => {
|
||||
return <option key={name} value={(ColorNames as { [k: string]: Color})[name]}>{name}</option>
|
||||
})}
|
||||
</select>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export interface MultiSelectParamComponentProps<T extends string> {
|
||||
label: string
|
||||
param: PD.MultiSelect<T>
|
||||
value: T[]
|
||||
onChange(v: T[]): void
|
||||
}
|
||||
|
||||
export interface MultiSelectParamComponentState<T extends string> {
|
||||
value: T[]
|
||||
}
|
||||
|
||||
export class MultiSelectParamComponent<T extends string> extends React.Component<MultiSelectParamComponentProps<T>, MultiSelectParamComponentState<T>> {
|
||||
state = {
|
||||
value: this.props.value
|
||||
}
|
||||
|
||||
onChange(value: T[]) {
|
||||
this.setState({ value })
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<span>{this.props.label} </span>
|
||||
<select multiple value={this.state.value} onChange={e => {
|
||||
const value = Array.from(e.target.options).filter(option => option.selected).map(option => option.value)
|
||||
this.onChange(value as T[])
|
||||
}}>
|
||||
{this.props.param.options.map(v => {
|
||||
const [value, label] = v
|
||||
return <option key={label} value={value}>{label}</option>
|
||||
})}
|
||||
</select>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export interface NumberParamComponentProps {
|
||||
label: string
|
||||
param: PD.Numeric
|
||||
value: number
|
||||
onChange(v: number): void
|
||||
}
|
||||
|
||||
export interface NumberParamComponentState {
|
||||
value: number
|
||||
}
|
||||
|
||||
export class NumberParamComponent extends React.Component<NumberParamComponentProps, NumberParamComponentState> {
|
||||
state = {
|
||||
value: this.props.value
|
||||
}
|
||||
|
||||
onChange(valueStr: string) {
|
||||
const value = this.props.param.step && Number.isInteger(this.props.param.step) ? parseInt(valueStr) : parseFloat(valueStr)
|
||||
this.setState({ value })
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<span>{this.props.label} </span>
|
||||
<input type='range'
|
||||
value={this.state.value}
|
||||
min={this.props.param.min}
|
||||
max={this.props.param.max}
|
||||
step={this.props.param.step}
|
||||
onChange={e => this.onChange(e.currentTarget.value)}
|
||||
>
|
||||
</input>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export interface SelectParamComponentProps<T extends string> {
|
||||
label: string
|
||||
param: PD.Select<T>
|
||||
value: T
|
||||
onChange(v: T): void
|
||||
}
|
||||
|
||||
export interface SelectParamComponentState<T extends string> {
|
||||
value: T
|
||||
}
|
||||
|
||||
export class SelectParamComponent<T extends string> extends React.Component<SelectParamComponentProps<T>, SelectParamComponentState<T>> {
|
||||
state = {
|
||||
value: this.props.value
|
||||
}
|
||||
|
||||
onChange(value: T) {
|
||||
this.setState({ value })
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<span>{this.props.label} </span>
|
||||
<select value={this.state.value} onChange={e => this.onChange(e.target.value as T) }>
|
||||
{this.props.param.options.map(v => {
|
||||
const [value, label] = v
|
||||
return <option key={label} value={value}>{label}</option>
|
||||
})}
|
||||
</select>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export interface TextParamComponentProps {
|
||||
label: string
|
||||
param: PD.Text
|
||||
value: string
|
||||
onChange(v: string): void
|
||||
}
|
||||
|
||||
export interface TextParamComponentState {
|
||||
value: string
|
||||
}
|
||||
|
||||
export class TextParamComponent extends React.Component<TextParamComponentProps, TextParamComponentState> {
|
||||
state = {
|
||||
value: this.props.value
|
||||
}
|
||||
|
||||
onChange(value: string) {
|
||||
this.setState({ value })
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<span>{this.props.label} </span>
|
||||
<input type='text'
|
||||
value={this.state.value}
|
||||
onChange={e => this.onChange(e.currentTarget.value)}
|
||||
>
|
||||
</input>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react'
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { BooleanParamComponent } from './parameter/boolean';
|
||||
import { NumberParamComponent } from './parameter/number';
|
||||
import { SelectParamComponent } from './parameter/select';
|
||||
import { MultiSelectParamComponent } from './parameter/multi-select';
|
||||
import { TextParamComponent } from './parameter/text';
|
||||
import { ColorParamComponent } from './parameter/color';
|
||||
import { camelCaseToWords } from '../../mol-util/string';
|
||||
|
||||
interface ParametersProps<P extends PD.Params> {
|
||||
params: P
|
||||
values: { [k in keyof P]: P[k]['defaultValue'] }
|
||||
onChange<K extends keyof P>(k: K, v: P[K]['defaultValue']): void
|
||||
}
|
||||
|
||||
type ParametersState = {}
|
||||
|
||||
function getParamComponent<P extends PD.Any>(label: string, param: PD.Any, value: P['defaultValue'], onChange: (v: P['defaultValue']) => void) {
|
||||
switch (param.type) {
|
||||
case 'boolean':
|
||||
return <BooleanParamComponent label={label} param={param} value={value} onChange={onChange} />
|
||||
case 'number':
|
||||
return <NumberParamComponent label={label} param={param} value={value} onChange={onChange} />
|
||||
case 'select':
|
||||
return <SelectParamComponent label={label} param={param} value={value} onChange={onChange} />
|
||||
case 'multi-select':
|
||||
return <MultiSelectParamComponent label={label} param={param} value={value} onChange={onChange} />
|
||||
case 'text':
|
||||
return <TextParamComponent label={label} param={param} value={value} onChange={onChange} />
|
||||
case 'color':
|
||||
return <ColorParamComponent label={label} param={param} value={value} onChange={onChange} />
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function getLabel(name: string, param: PD.Base<any>) {
|
||||
return param.label === undefined ? camelCaseToWords(name) : param.label
|
||||
}
|
||||
|
||||
export class ParametersComponent<P extends PD.Params> extends React.Component<ParametersProps<P>, ParametersState> {
|
||||
onChange(k: string, value: any) {
|
||||
this.props.onChange(k, value)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div style={{ width: '100%' }}>
|
||||
{ Object.keys(this.props.params).map(k => {
|
||||
const param = this.props.params[k]
|
||||
const value = this.props.values[k]
|
||||
const label = getLabel(k, param)
|
||||
return <div key={k}>
|
||||
{getParamComponent(label, param, value, v => this.onChange(k, v))}
|
||||
</div>
|
||||
})}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
// /**
|
||||
// * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
// *
|
||||
// * @author David Sehnal <david.sehnal@gmail.com>
|
||||
// */
|
||||
|
||||
// import * as React from 'react'
|
||||
// import { Structure, StructureSequence, Queries, StructureSelection, StructureProperties, StructureQuery } from 'mol-model/structure';
|
||||
// import { EmptyLoci } from 'mol-model/loci';
|
||||
|
||||
// export class SequenceView extends View<SequenceViewController, {}, {}> {
|
||||
// render() {
|
||||
// const s = this.controller.latestState.structure;
|
||||
// if (!s) return <div className='molstar-sequence-view-wrap'>No structure available.</div>;
|
||||
|
||||
// const seqs = s.models[0].sequence.sequences;
|
||||
// return <div className='molstar-sequence-view-wrap'>
|
||||
// {seqs.map((seq, i) => <EntitySequence key={i} ctx={this.controller.context} seq={seq} structure={s} /> )}
|
||||
// </div>;
|
||||
// }
|
||||
// }
|
||||
|
||||
// function createQuery(entityId: string, label_seq_id: number) {
|
||||
// return Queries.generators.atoms({
|
||||
// entityTest: ctx => StructureProperties.entity.id(ctx.element) === entityId,
|
||||
// residueTest: ctx => StructureProperties.residue.label_seq_id(ctx.element) === label_seq_id
|
||||
// });
|
||||
// }
|
||||
|
||||
// // TODO: this is really ineffective and should be done using a canvas.
|
||||
// class EntitySequence extends React.Component<{ ctx: Context, seq: StructureSequence.Entity, structure: Structure }> {
|
||||
|
||||
// raiseInteractityEvent(seqId?: number) {
|
||||
// if (typeof seqId === 'undefined') {
|
||||
// InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const query = createQuery(this.props.seq.entityId, seqId);
|
||||
// const loci = StructureSelection.toLoci(StructureQuery.run(query, this.props.structure));
|
||||
// if (loci.elements.length === 0) InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, EmptyLoci);
|
||||
// else InteractivityEvents.HighlightLoci.dispatch(this.props.ctx, loci);
|
||||
// }
|
||||
|
||||
|
||||
// render() {
|
||||
// const { ctx, seq } = this.props;
|
||||
// const { offset, sequence } = seq.sequence;
|
||||
|
||||
// const elems: JSX.Element[] = [];
|
||||
// for (let i = 0, _i = sequence.length; i < _i; i++) {
|
||||
// elems[elems.length] = <ResidueView ctx={ctx} seqId={offset + i} letter={sequence[i]} parent={this} key={i} />;
|
||||
// }
|
||||
|
||||
// return <div style={{ wordWrap: 'break-word' }}>
|
||||
// <span style={{ fontWeight: 'bold' }}>{this.props.seq.entityId}:{offset} </span>
|
||||
// {elems}
|
||||
// </div>;
|
||||
// }
|
||||
// }
|
||||
|
||||
// class ResidueView extends React.Component<{ ctx: Context, seqId: number, letter: string, parent: EntitySequence }, { isHighlighted: boolean }> {
|
||||
// state = { isHighlighted: false }
|
||||
|
||||
// mouseEnter = () => {
|
||||
// this.setState({ isHighlighted: true });
|
||||
// this.props.parent.raiseInteractityEvent(this.props.seqId);
|
||||
// }
|
||||
|
||||
// mouseLeave = () => {
|
||||
// this.setState({ isHighlighted: false });
|
||||
// this.props.parent.raiseInteractityEvent();
|
||||
// }
|
||||
|
||||
// render() {
|
||||
// return <span onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave}
|
||||
// style={{ cursor: 'pointer', backgroundColor: this.state.isHighlighted ? 'yellow' : void 0 }}>
|
||||
// {this.props.letter}
|
||||
// </span>;
|
||||
// }
|
||||
// }
|
||||
@@ -90,25 +90,30 @@ class Camera implements Object3D {
|
||||
return ret;
|
||||
}
|
||||
|
||||
getFocus(target: Vec3, radius: number): Partial<Camera.Snapshot> {
|
||||
getFocus(target: Vec3, radius: number, dir?: Vec3): Partial<Camera.Snapshot> {
|
||||
const fov = this.state.fov
|
||||
const { width, height } = this.viewport
|
||||
const aspect = width / height
|
||||
const aspectFactor = (height < width ? 1 : aspect)
|
||||
const currentDistance = Vec3.distance(this.state.position, target)
|
||||
const targetDistance = Math.abs((radius / aspectFactor) / Math.sin(fov / 2))
|
||||
|
||||
const deltaDistance = Math.abs(currentDistance - targetDistance)
|
||||
|
||||
Vec3.sub(this.deltaDirection, this.state.position, target)
|
||||
Vec3.setMagnitude(this.deltaDirection, this.state.direction, deltaDistance)
|
||||
if (currentDistance < targetDistance) Vec3.negate(this.deltaDirection, this.deltaDirection)
|
||||
Vec3.add(this.newPosition, this.state.position, this.deltaDirection)
|
||||
if (dir) {
|
||||
Vec3.setMagnitude(this.deltaDirection, dir, targetDistance)
|
||||
Vec3.add(this.newPosition, target, this.deltaDirection)
|
||||
} else {
|
||||
Vec3.setMagnitude(this.deltaDirection, this.state.direction, deltaDistance)
|
||||
if (currentDistance < targetDistance) Vec3.negate(this.deltaDirection, this.deltaDirection)
|
||||
Vec3.add(this.newPosition, this.state.position, this.deltaDirection)
|
||||
}
|
||||
|
||||
return { target, position: Vec3.clone(this.newPosition) };
|
||||
}
|
||||
|
||||
focus(target: Vec3, radius: number) {
|
||||
this.setState(this.getFocus(target, radius));
|
||||
focus(target: Vec3, radius: number, dir?: Vec3) {
|
||||
if (radius > 0) this.setState(this.getFocus(target, radius, dir));
|
||||
}
|
||||
|
||||
// lookAt(target: Vec3) {
|
||||
@@ -177,8 +182,8 @@ namespace Camera {
|
||||
return {
|
||||
mode: 'perspective',
|
||||
|
||||
position: Vec3.zero(),
|
||||
direction: Vec3.create(0, 0, -1),
|
||||
position: Vec3.create(0, 0, 100),
|
||||
direction: Vec3.create(0, 0, 1),
|
||||
up: Vec3.create(0, 1, 0),
|
||||
|
||||
target: Vec3.create(0, 0, 0),
|
||||
@@ -250,7 +255,7 @@ function updateOrtho(camera: Camera) {
|
||||
let top = cy + dy
|
||||
let bottom = cy - dy
|
||||
|
||||
if (viewOffset && viewOffset.enabled) {
|
||||
if (viewOffset.enabled) {
|
||||
const zoomW = zoom / (viewOffset.width / viewOffset.fullWidth)
|
||||
const zoomH = zoom / (viewOffset.height / viewOffset.fullHeight)
|
||||
const scaleW = (fullRight - fullLeft) / viewOffset.width
|
||||
@@ -279,7 +284,7 @@ function updatePers(camera: Camera) {
|
||||
let width = aspect * height
|
||||
let left = -0.5 * width
|
||||
|
||||
if (viewOffset && viewOffset.enabled) {
|
||||
if (viewOffset.enabled) {
|
||||
left += viewOffset.offsetX * width / viewOffset.fullWidth
|
||||
top -= viewOffset.offsetY * height / viewOffset.fullHeight
|
||||
width *= viewOffset.width / viewOffset.fullWidth
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Representation } from '../mol-repr/representation';
|
||||
import Scene from '../mol-gl/scene';
|
||||
import { GraphicsRenderVariant } from '../mol-gl/webgl/render-item';
|
||||
import { PickingId } from '../mol-geo/geometry/picking';
|
||||
import { MarkerAction } from '../mol-geo/geometry/marker-data';
|
||||
import { MarkerAction } from '../mol-util/marker-action';
|
||||
import { Loci, EmptyLoci, isEmptyLoci } from '../mol-model/loci';
|
||||
import { Camera } from './camera';
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
@@ -31,6 +31,7 @@ import { PixelData } from '../mol-util/image';
|
||||
import { readTexture } from '../mol-gl/compute/util';
|
||||
import { DrawPass } from './passes/draw';
|
||||
import { PickPass } from './passes/pick';
|
||||
import { Task } from '../mol-task';
|
||||
|
||||
export const Canvas3DParams = {
|
||||
// TODO: FPS cap?
|
||||
@@ -46,6 +47,7 @@ export const Canvas3DParams = {
|
||||
trackball: PD.Group(TrackballControlsParams),
|
||||
debug: PD.Group(DebugHelperParams)
|
||||
}
|
||||
export const DefaultCanvas3DParams = PD.getDefaultValues(Canvas3DParams);
|
||||
export type Canvas3DProps = PD.Values<typeof Canvas3DParams>
|
||||
|
||||
export { Canvas3D }
|
||||
@@ -66,6 +68,7 @@ interface Canvas3D {
|
||||
getLoci: (pickingId: PickingId) => Representation.Loci
|
||||
|
||||
readonly didDraw: BehaviorSubject<now.Timestamp>
|
||||
readonly reprCount: BehaviorSubject<number>
|
||||
|
||||
handleResize: () => void
|
||||
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
|
||||
@@ -85,12 +88,13 @@ interface Canvas3D {
|
||||
}
|
||||
|
||||
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()))
|
||||
const DefaultRunTask = (task: Task<unknown>) => task.run()
|
||||
|
||||
namespace Canvas3D {
|
||||
export interface HighlightEvent { current: Representation.Loci, prev: Representation.Loci, modifiers?: ModifiersKeys }
|
||||
export interface HighlightEvent { current: Representation.Loci, modifiers?: ModifiersKeys }
|
||||
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, modifiers: ModifiersKeys }
|
||||
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}) {
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask) {
|
||||
const gl = getGLContext(canvas, {
|
||||
alpha: false,
|
||||
antialias: true,
|
||||
@@ -99,11 +103,11 @@ namespace Canvas3D {
|
||||
})
|
||||
if (gl === null) throw new Error('Could not create a WebGL rendering context')
|
||||
const input = InputObserver.fromElement(canvas)
|
||||
return Canvas3D.create(gl, input, props)
|
||||
return Canvas3D.create(gl, input, props, runTask)
|
||||
}
|
||||
|
||||
export function create(gl: GLRenderingContext, input: InputObserver, props: Partial<Canvas3DProps> = {}): Canvas3D {
|
||||
const p = { ...PD.getDefaultValues(Canvas3DParams), ...props }
|
||||
export function create(gl: GLRenderingContext, input: InputObserver, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask): Canvas3D {
|
||||
const p = { ...DefaultCanvas3DParams, ...props }
|
||||
|
||||
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>()
|
||||
const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>()
|
||||
@@ -115,7 +119,7 @@ namespace Canvas3D {
|
||||
const camera = new Camera({
|
||||
near: 0.1,
|
||||
far: 10000,
|
||||
position: Vec3.create(0, 0, 10),
|
||||
position: Vec3.create(0, 0, 100),
|
||||
mode: p.cameraMode
|
||||
})
|
||||
|
||||
@@ -135,8 +139,8 @@ namespace Canvas3D {
|
||||
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing)
|
||||
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample)
|
||||
|
||||
let isUpdating = false
|
||||
let drawPending = false
|
||||
let cameraResetRequested: boolean | Vec3 = false
|
||||
|
||||
function getLoci(pickingId: PickingId) {
|
||||
let loci: Loci = EmptyLoci
|
||||
@@ -184,10 +188,11 @@ namespace Canvas3D {
|
||||
let fogFar = cDist + (bRadius * fogFarFactor)
|
||||
|
||||
if (camera.state.mode === 'perspective') {
|
||||
near = Math.max(1, p.cameraClipDistance, near)
|
||||
far = Math.max(1, far)
|
||||
fogNear = Math.max(1, fogNear)
|
||||
fogFar = Math.max(1, fogFar)
|
||||
// set at least to 5 to avoid slow sphere impostor rendering
|
||||
near = Math.max(5, p.cameraClipDistance, near)
|
||||
far = Math.max(5, far)
|
||||
fogNear = Math.max(5, fogNear)
|
||||
fogFar = Math.max(5, fogFar)
|
||||
} else if (camera.state.mode === 'orthographic') {
|
||||
if (p.cameraClipDistance > 0) {
|
||||
near = Math.max(p.cameraClipDistance, near)
|
||||
@@ -201,7 +206,7 @@ namespace Canvas3D {
|
||||
}
|
||||
|
||||
function render(variant: 'pick' | 'draw', force: boolean) {
|
||||
if (isUpdating) return false
|
||||
if (scene.isCommiting) return false
|
||||
|
||||
let didRender = false
|
||||
controls.update(currentTime);
|
||||
@@ -261,8 +266,22 @@ namespace Canvas3D {
|
||||
return pickPass.identify(x, y)
|
||||
}
|
||||
|
||||
function commit(renderObjects?: readonly GraphicsRenderObject[]) {
|
||||
scene.update(renderObjects, false)
|
||||
|
||||
runTask(scene.commit()).then(() => {
|
||||
if (cameraResetRequested && !scene.isCommiting) {
|
||||
const dir = typeof cameraResetRequested === 'boolean' ? undefined : cameraResetRequested
|
||||
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius, dir)
|
||||
cameraResetRequested = false
|
||||
}
|
||||
if (debugHelper.isEnabled) debugHelper.update()
|
||||
requestDraw(true)
|
||||
reprCount.next(reprRenderObjects.size)
|
||||
})
|
||||
}
|
||||
|
||||
function add(repr: Representation.Any) {
|
||||
isUpdating = true
|
||||
const oldRO = reprRenderObjects.get(repr)
|
||||
const newRO = new Set<GraphicsRenderObject>()
|
||||
repr.renderObjects.forEach(o => newRO.add(o))
|
||||
@@ -276,11 +295,7 @@ namespace Canvas3D {
|
||||
repr.renderObjects.forEach(o => scene.add(o))
|
||||
}
|
||||
reprRenderObjects.set(repr, newRO)
|
||||
scene.update(repr.renderObjects, false)
|
||||
if (debugHelper.isEnabled) debugHelper.update()
|
||||
isUpdating = false
|
||||
requestDraw(true)
|
||||
reprCount.next(reprRenderObjects.size)
|
||||
commit(repr.renderObjects)
|
||||
}
|
||||
|
||||
handleResize()
|
||||
@@ -301,14 +316,9 @@ namespace Canvas3D {
|
||||
}
|
||||
const renderObjects = reprRenderObjects.get(repr)
|
||||
if (renderObjects) {
|
||||
isUpdating = true
|
||||
renderObjects.forEach(o => scene.remove(o))
|
||||
reprRenderObjects.delete(repr)
|
||||
scene.update(void 0, false)
|
||||
if (debugHelper.isEnabled) debugHelper.update()
|
||||
isUpdating = false
|
||||
requestDraw(true)
|
||||
reprCount.next(reprRenderObjects.size)
|
||||
commit()
|
||||
}
|
||||
},
|
||||
update: (repr, keepSphere) => {
|
||||
@@ -333,9 +343,13 @@ namespace Canvas3D {
|
||||
getLoci,
|
||||
|
||||
handleResize,
|
||||
resetCamera: () => {
|
||||
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius)
|
||||
requestDraw(true);
|
||||
resetCamera: (dir?: Vec3) => {
|
||||
if (scene.isCommiting) {
|
||||
cameraResetRequested = dir || true
|
||||
} else {
|
||||
camera.focus(scene.boundingSphere.center, scene.boundingSphere.radius, dir)
|
||||
requestDraw(true);
|
||||
}
|
||||
},
|
||||
camera,
|
||||
downloadScreenshot: () => {
|
||||
@@ -351,6 +365,7 @@ namespace Canvas3D {
|
||||
}
|
||||
},
|
||||
didDraw,
|
||||
reprCount,
|
||||
setProps: (props: Partial<Canvas3DProps>) => {
|
||||
if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
|
||||
camera.setState({ mode: props.cameraMode })
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
export const TrackballControlsParams = {
|
||||
noScroll: PD.Boolean(true, { isHidden: true }),
|
||||
|
||||
rotateSpeed: PD.Numeric(5.0, { min: 0.1, max: 10, step: 0.1 }),
|
||||
rotateSpeed: PD.Numeric(3.0, { min: 0.1, max: 10, step: 0.1 }),
|
||||
zoomSpeed: PD.Numeric(6.0, { min: 0.1, max: 10, step: 0.1 }),
|
||||
panSpeed: PD.Numeric(0.8, { min: 0.1, max: 5, step: 0.1 }),
|
||||
|
||||
@@ -217,9 +217,7 @@ namespace TrackballControls {
|
||||
panCamera()
|
||||
|
||||
Vec3.add(object.position, target, _eye)
|
||||
|
||||
checkDistances()
|
||||
|
||||
cameraLookAt(object.position, object.up, object.direction, target)
|
||||
|
||||
if (Vec3.squaredDistance(lastPosition, object.position) > EPSILON.Value) {
|
||||
@@ -259,7 +257,6 @@ namespace TrackballControls {
|
||||
}
|
||||
|
||||
if (buttons === ButtonsType.Flag.Primary) {
|
||||
Vec2.copy(_movePrev, _moveCurr)
|
||||
Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY))
|
||||
} else if (buttons === ButtonsType.Flag.Auxilary) {
|
||||
Vec2.copy(_zoomEnd, getMouseOnScreen(pageX, pageY))
|
||||
@@ -273,12 +270,12 @@ namespace TrackballControls {
|
||||
}
|
||||
|
||||
function onWheel({ dy }: WheelInput) {
|
||||
_zoomStart[1] -= dy * 0.0001
|
||||
_zoomEnd[1] += dy * 0.0001
|
||||
}
|
||||
|
||||
function onPinch({ fraction }: PinchInput) {
|
||||
_isInteracting = true;
|
||||
_zoomStart[1] -= (fraction - 1) * 0.1
|
||||
_zoomEnd[1] += (fraction - 1) * 0.1
|
||||
}
|
||||
|
||||
function dispose() {
|
||||
|
||||
@@ -13,7 +13,7 @@ import Scene from '../../mol-gl/scene';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorNames } from '../../mol-util/color/tables';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { TransformData } from '../../mol-geo/geometry/transform-data';
|
||||
import { sphereVertexCount } from '../../mol-geo/primitive/sphere';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
@@ -81,7 +81,8 @@ export class BoundingSphereHelper {
|
||||
}
|
||||
})
|
||||
|
||||
this.scene.update(void 0, false);
|
||||
this.scene.update(void 0, false)
|
||||
this.scene.syncCommit()
|
||||
}
|
||||
|
||||
syncVisibility() {
|
||||
|
||||
@@ -59,7 +59,7 @@ export class Canvas3dInteractionHelper {
|
||||
|
||||
const loci = this.getLoci(this.id);
|
||||
if (!Representation.Loci.areEqual(this.prevLoci, loci)) {
|
||||
this.events.highlight.next({ current: loci, prev: this.prevLoci, modifiers: this.modifiers });
|
||||
this.events.highlight.next({ current: loci, modifiers: this.modifiers });
|
||||
this.prevLoci = loci;
|
||||
}
|
||||
}
|
||||
@@ -75,9 +75,8 @@ export class Canvas3dInteractionHelper {
|
||||
leave() {
|
||||
this.inside = false;
|
||||
if (this.prevLoci.loci !== EmptyLoci) {
|
||||
const prev = this.prevLoci;
|
||||
this.prevLoci = Representation.Loci.Empty;
|
||||
this.events.highlight.next({ current: this.prevLoci, prev });
|
||||
this.events.highlight.next({ current: this.prevLoci });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +98,7 @@ export class Canvas3dInteractionHelper {
|
||||
modify(modifiers: ModifiersKeys) {
|
||||
if (this.prevLoci.loci === EmptyLoci || ModifiersKeys.areEqual(modifiers, this.modifiers)) return;
|
||||
this.modifiers = modifiers;
|
||||
this.events.highlight.next({ current: this.prevLoci, prev: this.prevLoci, modifiers: this.modifiers });
|
||||
this.events.highlight.next({ current: this.prevLoci, modifiers: this.modifiers });
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
@@ -11,14 +11,17 @@ import Scene from '../../mol-gl/scene';
|
||||
import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { decodeFloatRGB } from '../../mol-util/float-packing';
|
||||
|
||||
const readBuffer = new Uint8Array(4)
|
||||
|
||||
export class PickPass {
|
||||
pickDirty = true
|
||||
|
||||
objectPickTarget: RenderTarget
|
||||
instancePickTarget: RenderTarget
|
||||
groupPickTarget: RenderTarget
|
||||
|
||||
private objectBuffer: Uint8Array
|
||||
private instanceBuffer: Uint8Array
|
||||
private groupBuffer: Uint8Array
|
||||
|
||||
private pickScale: number
|
||||
private pickWidth: number
|
||||
private pickHeight: number
|
||||
@@ -31,18 +34,33 @@ export class PickPass {
|
||||
this.pickScale = pickBaseScale / webgl.pixelRatio
|
||||
this.pickWidth = Math.round(width * this.pickScale)
|
||||
this.pickHeight = Math.round(height * this.pickScale)
|
||||
|
||||
this.objectPickTarget = createRenderTarget(webgl, this.pickWidth, this.pickHeight)
|
||||
this.instancePickTarget = createRenderTarget(webgl, this.pickWidth, this.pickHeight)
|
||||
this.groupPickTarget = createRenderTarget(webgl, this.pickWidth, this.pickHeight)
|
||||
|
||||
this.setupBuffers()
|
||||
}
|
||||
|
||||
private setupBuffers() {
|
||||
const bufferSize = this.pickWidth * this.pickHeight * 4
|
||||
if (!this.objectBuffer || this.objectBuffer.length !== bufferSize) {
|
||||
this.objectBuffer = new Uint8Array(bufferSize)
|
||||
this.instanceBuffer = new Uint8Array(bufferSize)
|
||||
this.groupBuffer = new Uint8Array(bufferSize)
|
||||
}
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
this.pickScale = this.pickBaseScale / this.webgl.pixelRatio
|
||||
this.pickWidth = Math.round(width * this.pickScale)
|
||||
this.pickHeight = Math.round(height * this.pickScale)
|
||||
|
||||
this.objectPickTarget.setSize(this.pickWidth, this.pickHeight)
|
||||
this.instancePickTarget.setSize(this.pickWidth, this.pickHeight)
|
||||
this.groupPickTarget.setSize(this.pickWidth, this.pickHeight)
|
||||
|
||||
this.setupBuffers()
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -54,12 +72,35 @@ export class PickPass {
|
||||
renderer.render(scene, 'pickInstance', true);
|
||||
this.groupPickTarget.bind();
|
||||
renderer.render(scene, 'pickGroup', true);
|
||||
|
||||
this.pickDirty = false
|
||||
}
|
||||
|
||||
private syncBuffers() {
|
||||
const { webgl } = this
|
||||
|
||||
this.objectPickTarget.bind()
|
||||
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.objectBuffer)
|
||||
|
||||
this.instancePickTarget.bind()
|
||||
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.instanceBuffer)
|
||||
|
||||
this.groupPickTarget.bind()
|
||||
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.groupBuffer)
|
||||
}
|
||||
|
||||
private getId(x: number, y: number, buffer: Uint8Array) {
|
||||
const idx = (y * this.pickWidth + x) * 4
|
||||
return decodeFloatRGB(buffer[idx], buffer[idx + 1], buffer[idx + 2])
|
||||
}
|
||||
|
||||
identify(x: number, y: number): PickingId | undefined {
|
||||
const { webgl, pickScale } = this
|
||||
const { gl } = webgl
|
||||
if (this.pickDirty) this.render()
|
||||
if (this.pickDirty) {
|
||||
this.render()
|
||||
this.syncBuffers()
|
||||
}
|
||||
|
||||
x *= webgl.pixelRatio
|
||||
y *= webgl.pixelRatio
|
||||
@@ -68,19 +109,13 @@ export class PickPass {
|
||||
const xp = Math.round(x * pickScale)
|
||||
const yp = Math.round(y * pickScale)
|
||||
|
||||
this.objectPickTarget.bind()
|
||||
webgl.readPixels(xp, yp, 1, 1, readBuffer)
|
||||
const objectId = decodeFloatRGB(readBuffer[0], readBuffer[1], readBuffer[2])
|
||||
const objectId = this.getId(xp, yp, this.objectBuffer)
|
||||
if (objectId === -1) return
|
||||
|
||||
this.instancePickTarget.bind()
|
||||
webgl.readPixels(xp, yp, 1, 1, readBuffer)
|
||||
const instanceId = decodeFloatRGB(readBuffer[0], readBuffer[1], readBuffer[2])
|
||||
const instanceId = this.getId(xp, yp, this.instanceBuffer)
|
||||
if (instanceId === -1) return
|
||||
|
||||
this.groupPickTarget.bind()
|
||||
webgl.readPixels(xp, yp, 1, 1, readBuffer)
|
||||
const groupId = decodeFloatRGB(readBuffer[0], readBuffer[1], readBuffer[2])
|
||||
const groupId = this.getId(xp, yp, this.groupBuffer)
|
||||
if (groupId === -1) return
|
||||
|
||||
return { objectId, instanceId, groupId }
|
||||
|
||||
@@ -51,7 +51,7 @@ export const PostprocessingParams = {
|
||||
occlusionEnable: PD.Boolean(false),
|
||||
occlusionKernelSize: PD.Numeric(4, { min: 1, max: 32, step: 1 }),
|
||||
occlusionBias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
|
||||
occlusionRadius: PD.Numeric(64, { min: 0, max: 256, step: 1 }),
|
||||
occlusionRadius: PD.Numeric(32, { min: 0, max: 256, step: 1 }),
|
||||
|
||||
outlineEnable: PD.Boolean(false),
|
||||
outlineScale: PD.Numeric(1, { min: 0, max: 10, step: 1 }),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
import * as ColumnHelpers from './column-helpers'
|
||||
import { Tensor as Tensors } from '../../mol-math/linear-algebra'
|
||||
import { Tokens } from '../../mol-io/reader/common/text/tokenizer';
|
||||
import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../../mol-io/reader/common/text/number-parser';
|
||||
|
||||
interface Column<T> {
|
||||
readonly schema: Column.Schema,
|
||||
@@ -128,6 +130,37 @@ namespace Column {
|
||||
return arrayColumn({ array, schema: Schema.str });
|
||||
}
|
||||
|
||||
export function ofIntTokens(tokens: Tokens) {
|
||||
const { count, data, indices } = tokens
|
||||
return lambdaColumn({
|
||||
value: (row: number) => fastParseInt(data, indices[2 * row], indices[2 * row + 1]) || 0,
|
||||
rowCount: count,
|
||||
schema: Schema.int,
|
||||
});
|
||||
}
|
||||
|
||||
export function ofFloatTokens(tokens: Tokens) {
|
||||
const { count, data, indices } = tokens
|
||||
return lambdaColumn({
|
||||
value: (row: number) => fastParseFloat(data, indices[2 * row], indices[2 * row + 1]) || 0,
|
||||
rowCount: count,
|
||||
schema: Schema.float,
|
||||
});
|
||||
}
|
||||
|
||||
export function ofStringTokens(tokens: Tokens) {
|
||||
const { count, data, indices } = tokens
|
||||
return lambdaColumn({
|
||||
value: (row: number) => {
|
||||
const ret = data.substring(indices[2 * row], indices[2 * row + 1]);
|
||||
if (ret === '.' || ret === '?') return '';
|
||||
return ret;
|
||||
},
|
||||
rowCount: count,
|
||||
schema: Schema.str,
|
||||
});
|
||||
}
|
||||
|
||||
export function window<T>(column: Column<T>, start: number, end: number) {
|
||||
return windowColumn(column, start, end);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('rangesArray', () => {
|
||||
it(`iterator, ${name}`, () => {
|
||||
const rangesIt = SortedRanges.transientSegments(ranges, set)
|
||||
const { index, start, end } = expectedValues
|
||||
|
||||
|
||||
let i = 0
|
||||
while (rangesIt.hasNext) {
|
||||
const segment = rangesIt.move()
|
||||
@@ -41,7 +41,7 @@ describe('rangesArray', () => {
|
||||
|
||||
testIterator('two ranges',
|
||||
SortedRanges.ofSortedRanges([1, 2, 3, 4]),
|
||||
OrderedSet.ofBounds(1, 4),
|
||||
OrderedSet.ofBounds(1, 5),
|
||||
{ index: [0, 1], start: [0, 2], end: [2, 4] }
|
||||
)
|
||||
testIterator('first range',
|
||||
@@ -62,7 +62,7 @@ describe('rangesArray', () => {
|
||||
testIterator('set in second range and beyond',
|
||||
SortedRanges.ofSortedRanges([1, 2, 3, 4]),
|
||||
SortedArray.ofSortedArray([3, 10]),
|
||||
{ index: [1], start: [0], end: [2] }
|
||||
{ index: [1], start: [0], end: [1] }
|
||||
)
|
||||
testIterator('length 1 range',
|
||||
SortedRanges.ofSortedRanges([1, 1, 3, 4]),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -17,6 +17,7 @@ export const min = Tuple.fst;
|
||||
export function max(i: Tuple) { return Tuple.snd(i) - 1; }
|
||||
export function size(i: Tuple) { return Tuple.snd(i) - Tuple.fst(i); }
|
||||
export const hashCode = Tuple.hashCode;
|
||||
export const toString = Tuple.toString;
|
||||
|
||||
export function has(int: Tuple, v: number) { return Tuple.fst(int) <= v && v < Tuple.snd(int); }
|
||||
/** Returns the index of `x` in `set` or -1 if not found. */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -36,6 +36,8 @@ export function end(set: OrderedSetImpl) { return I.is(set) ? I.end(set) : S.end
|
||||
export function hashCode(set: OrderedSetImpl) { return I.is(set) ? I.hashCode(set) : S.hashCode(set); }
|
||||
// TODO: possibly add more hash functions to allow for multilevel hashing.
|
||||
|
||||
export function toString(set: OrderedSetImpl) { return I.is(set) ? I.toString(set) : S.toString(set); }
|
||||
|
||||
export function areEqual(a: OrderedSetImpl, b: OrderedSetImpl) {
|
||||
if (I.is(a)) {
|
||||
if (I.is(b)) return I.areEqual(a, b);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -35,6 +35,11 @@ export function hashCode(xs: Nums) {
|
||||
if (s > 2) return hash4(s, xs[0], xs[s - 1], xs[s >> 1]);
|
||||
return hash3(s, xs[0], xs[s - 1]);
|
||||
}
|
||||
export function toString(xs: Nums) {
|
||||
const s = xs.length;
|
||||
if (s > 5) return `[${xs[0]}, ${xs[1]}, ..., ${xs[s - 1]}], length ${s}`;
|
||||
return `[${(xs as number[]).join(', ')}]`;
|
||||
}
|
||||
|
||||
/** Returns the index of `x` in `set` or -1 if not found. */
|
||||
export function indexOf(xs: Nums, v: number) {
|
||||
@@ -42,8 +47,10 @@ export function indexOf(xs: Nums, v: number) {
|
||||
return l === 0 ? -1 : xs[0] <= v && v <= xs[l - 1] ? binarySearchRange(xs, v, 0, l) : -1;
|
||||
}
|
||||
export function indexOfInInterval(xs: Nums, v: number, bounds: Interval) {
|
||||
return indexOfInRange(xs, v, Interval.start(bounds), Interval.end(bounds))
|
||||
}
|
||||
export function indexOfInRange(xs: Nums, v: number, s: number, e: number) {
|
||||
const l = xs.length;
|
||||
const s = Interval.start(bounds), e = Interval.end(bounds);
|
||||
return l === 0 || e <= s ? -1 : xs[s] <= v && v <= xs[e - 1] ? binarySearchRange(xs, v, s, e) : -1;
|
||||
}
|
||||
export function has(xs: Nums, v: number) { return indexOf(xs, v) >= 0; }
|
||||
@@ -60,6 +67,11 @@ export function areEqual(a: Nums, b: Nums) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 0 if `v` is smaller or equal the first element of `xs`
|
||||
* Returns length of `xs` if `v` is bigger than the last element of `xs`
|
||||
* Otherwise returns the first index where the value of `xs` is equal or bigger than `v`
|
||||
*/
|
||||
export function findPredecessorIndex(xs: Nums, v: number) {
|
||||
const len = xs.length;
|
||||
if (v <= xs[0]) return 0;
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace Interval {
|
||||
export const size: <T extends number = number>(interval: Interval<T>) => number = Impl.size as any;
|
||||
/** Hash code describing the interval */
|
||||
export const hashCode: <T extends number = number>(interval: Interval<T>) => number = Impl.hashCode as any;
|
||||
/** String representation of the interval */
|
||||
export const toString: <T extends number = number>(interval: Interval<T>) => string = Impl.toString as any;
|
||||
|
||||
/** Test if two intervals are identical */
|
||||
export const areEqual: <T extends number = number>(a: Interval<T>, b: Interval<T>) => boolean = Impl.areEqual as any;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -8,7 +8,6 @@ import * as Base from './impl/ordered-set'
|
||||
import Interval from './interval'
|
||||
import SortedArray from './sorted-array';
|
||||
|
||||
/** test */
|
||||
namespace OrderedSet {
|
||||
export const Empty: OrderedSet = Base.Empty as any;
|
||||
export const ofSingleton: <T extends number = number>(value: T) => OrderedSet<T> = Base.ofSingleton as any;
|
||||
@@ -22,12 +21,14 @@ namespace OrderedSet {
|
||||
export const has: <T extends number = number>(set: OrderedSet<T>, x: T) => boolean = Base.has as any;
|
||||
/** Returns the index of `x` in `set` or -1 if not found. */
|
||||
export const indexOf: <T extends number = number>(set: OrderedSet<T>, x: T) => number = Base.indexOf as any;
|
||||
/** Returns the value in `set` at index `i`. */
|
||||
export const getAt: <T extends number = number>(set: OrderedSet<T>, i: number) => T = Base.getAt as any;
|
||||
|
||||
export const min: <T extends number = number>(set: OrderedSet<T>) => T = Base.min as any;
|
||||
export const max: <T extends number = number>(set: OrderedSet<T>) => T = Base.max as any;
|
||||
export const start: <T extends number = number>(set: OrderedSet<T>) => T = Base.start as any;
|
||||
export const end: <T extends number = number>(set: OrderedSet<T>) => T = Base.end as any;
|
||||
/** Number of elements in the OrderedSet */
|
||||
export const size: <T extends number = number>(set: OrderedSet<T>) => number = Base.size as any;
|
||||
export const hashCode: <T extends number = number>(set: OrderedSet<T>) => number = Base.hashCode as any;
|
||||
|
||||
@@ -38,8 +39,14 @@ namespace OrderedSet {
|
||||
|
||||
export const union: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.union as any;
|
||||
export const intersect: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.intersect as any;
|
||||
/** Returns elements of `a` that are not in `b`, i.e `a` - `b` */
|
||||
export const subtract: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.subtract as any;
|
||||
|
||||
/**
|
||||
* Returns 0 if `x` is smaller or equal the first element of `set`
|
||||
* Returns length of `set` if `x` is bigger than the last element of `set`
|
||||
* Otherwise returns the first index where the value of `set` is equal or bigger than `x`
|
||||
*/
|
||||
export const findPredecessorIndex: <T extends number = number>(set: OrderedSet<T>, x: number) => number = Base.findPredecessorIndex as any;
|
||||
export const findPredecessorIndexInInterval: <T extends number = number>(set: OrderedSet<T>, x: T, range: Interval) => number = Base.findPredecessorIndexInInterval as any;
|
||||
export const findRange: <T extends number = number>(set: OrderedSet<T>, min: T, max: T) => Interval = Base.findRange as any;
|
||||
@@ -62,10 +69,12 @@ namespace OrderedSet {
|
||||
OrderedSet.forEach(set, v => array.push(v))
|
||||
return array
|
||||
}
|
||||
|
||||
export function toString<T extends number = number>(set: OrderedSet<T>): string {
|
||||
return Base.toString(set)
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents bla */
|
||||
type OrderedSet<T extends number = number> = SortedArray<T> | Interval<T>
|
||||
|
||||
|
||||
export default OrderedSet
|
||||
@@ -18,7 +18,7 @@ namespace Segmentation {
|
||||
export const getSegment: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, value: T) => number = Impl.getSegment as any;
|
||||
export const projectValue: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, set: OrderedSet<T>, value: T) => Interval = Impl.projectValue as any;
|
||||
|
||||
// Segment iterator that mutates a single segment object to mark all the segments.
|
||||
/** Segment iterator that mutates a single segment object to mark all the segments. */
|
||||
export const transientSegments: <T extends number = number, I extends number = number>(segs: Segmentation<T, I>, set: OrderedSet<T>, segment?: Segment) => Impl.SegmentIterator<I> = Impl.segments as any;
|
||||
|
||||
export type SegmentIterator<I extends number = number> = Impl.SegmentIterator<I>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -12,9 +12,9 @@ namespace SortedArray {
|
||||
export const ofUnsortedArray: <T extends number = number>(xs: ArrayLike<number>) => SortedArray<T> = Impl.ofUnsortedArray as any;
|
||||
export const ofSingleton: <T extends number = number>(v: number) => SortedArray<T> = Impl.ofSingleton as any;
|
||||
export const ofSortedArray: <T extends number = number>(xs: ArrayLike<number>) => SortedArray<T> = Impl.ofSortedArray as any;
|
||||
// create sorted array [min, max] (it DOES contain the max value)
|
||||
/** create sorted array [min, max] (it DOES contain the max value) */
|
||||
export const ofRange: <T extends number = number>(min: T, max: T) => SortedArray<T> = Impl.ofRange as any;
|
||||
// create sorted array [min, max) (it does NOT contain the max value)
|
||||
/** create sorted array [min, max) (it does NOT contain the max value) */
|
||||
export const ofBounds: <T extends number = number>(min: T, max: T) => SortedArray<T> = (min, max) => Impl.ofRange(min, max - 1) as any;
|
||||
export const is: <T extends number = number>(v: any) => v is SortedArray<T> = Impl.is as any;
|
||||
|
||||
@@ -22,15 +22,17 @@ namespace SortedArray {
|
||||
/** Returns the index of `x` in `set` or -1 if not found. */
|
||||
export const indexOf: <T extends number = number>(array: SortedArray<T>, x: T) => number = Impl.indexOf as any;
|
||||
export const indexOfInInterval: <T extends number = number>(array: SortedArray<T>, x: number, bounds: Interval) => number = Impl.indexOfInInterval as any;
|
||||
export const indexOfInRange: <T extends number = number>(array: SortedArray<T>, x: number, start: number, end: number) => number = Impl.indexOfInRange as any;
|
||||
|
||||
// array[0]
|
||||
/** Returns `array[0]` */
|
||||
export const start: <T extends number = number>(array: SortedArray<T>) => T = Impl.start as any;
|
||||
// array[array.length - 1] + 1
|
||||
/** Returns `array[array.length - 1] + 1` */
|
||||
export const end: <T extends number = number>(array: SortedArray<T>) => T = Impl.end as any;
|
||||
export const min: <T extends number = number>(array: SortedArray<T>) => T = Impl.min as any;
|
||||
export const max: <T extends number = number>(array: SortedArray<T>) => T = Impl.max as any;
|
||||
export const size: <T extends number = number>(array: SortedArray<T>) => number = Impl.size as any;
|
||||
export const hashCode: <T extends number = number>(array: SortedArray<T>) => number = Impl.hashCode as any;
|
||||
export const toString: <T extends number = number>(array: SortedArray<T>) => string = Impl.toString as any;
|
||||
|
||||
export const areEqual: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => boolean = Impl.areEqual as any;
|
||||
export const areIntersecting: <T extends number = number>(a: SortedArray<T>, b: SortedArray<T>) => boolean = Impl.areIntersecting as any;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -23,6 +23,64 @@ namespace SortedRanges {
|
||||
}
|
||||
return size
|
||||
}
|
||||
export function count<T extends number = number>(ranges: SortedRanges<T>) { return ranges.length / 2 }
|
||||
|
||||
export function startAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
|
||||
return ranges[index * 2]
|
||||
}
|
||||
export function endAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
|
||||
return ranges[index * 2 + 1] + 1
|
||||
}
|
||||
|
||||
export function minAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
|
||||
return ranges[index * 2]
|
||||
}
|
||||
export function maxAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
|
||||
return ranges[index * 2 + 1]
|
||||
}
|
||||
|
||||
export function areEqual<T extends number = number>(a: SortedRanges<T>, b: SortedRanges<T>) {
|
||||
if (a.length !== b.length) return false
|
||||
for (let i = 0, il = a.length; i < il; ++i) {
|
||||
if (a[i] !== b[i]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export function forEach<T extends number = number>(ranges: SortedRanges<T>, f: (value: T, i: number) => void) {
|
||||
let k = 0
|
||||
for (let i = 0, il = ranges.length; i < il; i += 2) {
|
||||
for (let j = ranges[i], jl = ranges[i + 1]; j <= jl; ++j) {
|
||||
f(j, k);
|
||||
++k
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns if a value of `set` is included in `ranges` */
|
||||
export function has<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>) {
|
||||
return firstIntersectionIndex(ranges, set) !== -1
|
||||
}
|
||||
|
||||
/** Returns if a value of `set` is included in `ranges` from given index */
|
||||
export function hasFrom<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>, from: number) {
|
||||
return firstIntersectionIndexFrom(ranges, set, from) !== -1
|
||||
}
|
||||
|
||||
export function firstIntersectionIndex<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>): number {
|
||||
return firstIntersectionIndexFrom(ranges, set, 0)
|
||||
}
|
||||
|
||||
export function firstIntersectionIndexFrom<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>, from: number): number {
|
||||
if (minAt(ranges, from) > OrderedSet.max(set) || max(ranges) < OrderedSet.min(set)) return -1
|
||||
|
||||
for (let i = from, il = count(ranges); i < il; ++i) {
|
||||
const interval = Interval.ofRange(minAt(ranges, i), maxAt(ranges, i))
|
||||
if (OrderedSet.areIntersecting(interval, set)) return i
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
export function transientSegments<T extends number = number, I extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>) {
|
||||
return new Iterator<T, I>(ranges, set)
|
||||
@@ -32,53 +90,27 @@ namespace SortedRanges {
|
||||
private value: Segmentation.Segment<I> = { index: 0 as I, start: 0 as T, end: 0 as T }
|
||||
|
||||
private curIndex = 0
|
||||
private maxIndex = 0
|
||||
private interval: Interval<T>
|
||||
private curMin: T = 0 as T
|
||||
|
||||
hasNext: boolean = false;
|
||||
|
||||
updateInterval() {
|
||||
this.interval = Interval.ofRange(this.ranges[this.curIndex], this.ranges[this.curIndex + 1])
|
||||
}
|
||||
|
||||
updateValue() {
|
||||
this.value.index = this.curIndex / 2 as I
|
||||
this.value.start = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex])
|
||||
this.value.end = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex + 1]) + 1
|
||||
private updateValue() {
|
||||
this.value.index = this.curIndex as I
|
||||
this.value.start = OrderedSet.findPredecessorIndex(this.set, startAt(this.ranges, this.curIndex))
|
||||
this.value.end = OrderedSet.findPredecessorIndex(this.set, endAt(this.ranges, this.curIndex))
|
||||
}
|
||||
|
||||
move() {
|
||||
if (this.hasNext) {
|
||||
this.updateValue()
|
||||
while (this.curIndex <= this.maxIndex) {
|
||||
this.curIndex += 2
|
||||
this.curMin = Interval.end(this.interval)
|
||||
this.updateInterval()
|
||||
if (Interval.min(this.interval) >= this.curMin && OrderedSet.areIntersecting(this.interval, this.set)) break
|
||||
}
|
||||
this.hasNext = this.curIndex <= this.maxIndex
|
||||
this.curIndex = firstIntersectionIndexFrom(this.ranges, this.set, this.curIndex + 1)
|
||||
this.hasNext = this.curIndex !== -1
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
|
||||
getRangeIndex(value: number) {
|
||||
const index = SortedArray.findPredecessorIndex(this.ranges, value)
|
||||
return (index % 2 === 1) ? index - 1 : index
|
||||
}
|
||||
|
||||
constructor(private ranges: SortedRanges<T>, private set: OrderedSet<T>) {
|
||||
// TODO cleanup, refactor to make it clearer
|
||||
const min = SortedArray.findPredecessorIndex(this.ranges, OrderedSet.min(set))
|
||||
const max = SortedArray.findPredecessorIndex(this.ranges, OrderedSet.max(set) + 1)
|
||||
if (ranges.length && min !== max) {
|
||||
this.curIndex = this.getRangeIndex(OrderedSet.min(set))
|
||||
this.maxIndex = Math.min(ranges.length - 2, this.getRangeIndex(OrderedSet.max(set)))
|
||||
this.curMin = this.ranges[this.curIndex]
|
||||
this.updateInterval()
|
||||
}
|
||||
|
||||
this.hasNext = ranges.length > 0 && min !== max && this.curIndex <= this.maxIndex
|
||||
this.curIndex = firstIntersectionIndex(ranges, set)
|
||||
this.hasNext = this.curIndex !== -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -73,6 +73,11 @@ namespace IntTuple {
|
||||
_float64[0] = t as any;
|
||||
return hash2(_int32[0], _int32[1]);
|
||||
}
|
||||
|
||||
export function toString(t: IntTuple) {
|
||||
_float64[0] = t as any;
|
||||
return `(${_int32[0]}, ${_int32[1]})`;
|
||||
}
|
||||
}
|
||||
|
||||
export default IntTuple
|
||||
@@ -73,7 +73,7 @@ export function sortedCantorPairing(a: number, b: number) {
|
||||
/**
|
||||
* 32 bit FNV-1a hash, see http://isthe.com/chongo/tech/comp/fnv/
|
||||
*/
|
||||
export function hashFnv32a(array: number[]) {
|
||||
export function hashFnv32a(array: ArrayLike<number>) {
|
||||
let hval = 0x811c9dc5;
|
||||
for (let i = 0, il = array.length; i < il; ++i) {
|
||||
hval ^= array[i];
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Color } from '../../mol-util/color';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { TransformData, createIdentityTransform } from './transform-data';
|
||||
import { Theme } from '../../mol-theme/theme';
|
||||
import { ColorNames } from '../../mol-util/color/tables';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { NullLocation } from '../../mol-model/location';
|
||||
import { UniformColorTheme } from '../../mol-theme/color/uniform';
|
||||
import { UniformSizeTheme } from '../../mol-theme/size/uniform';
|
||||
|
||||
@@ -21,7 +21,7 @@ import { transformPositionArray } from '../../../mol-geo/util';
|
||||
import { calculateBoundingSphere } from '../../../mol-gl/renderable/util';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { ColorListOptions, ColorListName } from '../../../mol-util/color/scale';
|
||||
import { ColorListOptions, ColorListName } from '../../../mol-util/color/lists';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { BaseGeometry } from '../base';
|
||||
import { createEmptyOverpaint } from '../overpaint-data';
|
||||
@@ -81,7 +81,7 @@ export namespace DirectVolume {
|
||||
Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
|
||||
Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
|
||||
]),
|
||||
list: PD.ColorScale<ColorListName>('RedYellowBlue', ColorListOptions),
|
||||
list: PD.ColorList<ColorListName>('red-yellow-blue', ColorListOptions),
|
||||
}
|
||||
export type Params = typeof Params
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { spline } from '../../../mol-math/interpolate';
|
||||
import { ColorScale, Color } from '../../../mol-util/color';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { Vec2 } from '../../../mol-math/linear-algebra';
|
||||
import { ColorListName } from '../../../mol-util/color/scale';
|
||||
import { ColorListName } from '../../../mol-util/color/lists';
|
||||
|
||||
export interface ControlPoint { x: number, alpha: number }
|
||||
|
||||
|
||||
@@ -13,57 +13,6 @@ export type MarkerData = {
|
||||
uMarkerTexDim: ValueCell<Vec2>
|
||||
}
|
||||
|
||||
export enum MarkerAction {
|
||||
Highlight,
|
||||
RemoveHighlight,
|
||||
Select,
|
||||
Deselect,
|
||||
Toggle,
|
||||
Clear
|
||||
}
|
||||
|
||||
export function applyMarkerAction(array: Uint8Array, start: number, end: number, action: MarkerAction) {
|
||||
let changed = false
|
||||
for (let i = start; i < end; ++i) {
|
||||
let v = array[i]
|
||||
switch (action) {
|
||||
case MarkerAction.Highlight:
|
||||
if (v % 2 === 0) {
|
||||
v += 1
|
||||
}
|
||||
break
|
||||
case MarkerAction.RemoveHighlight:
|
||||
if (v % 2 !== 0) {
|
||||
v -= 1
|
||||
}
|
||||
break
|
||||
case MarkerAction.Select:
|
||||
if (v < 2) v += 2
|
||||
// v += 2
|
||||
break
|
||||
case MarkerAction.Deselect:
|
||||
// if (v >= 2) {
|
||||
// v -= 2
|
||||
// }
|
||||
v = v % 2
|
||||
break
|
||||
case MarkerAction.Toggle:
|
||||
if (v >= 2) {
|
||||
v -= 2
|
||||
} else {
|
||||
v += 2
|
||||
}
|
||||
break
|
||||
case MarkerAction.Clear:
|
||||
v = 0
|
||||
break
|
||||
}
|
||||
changed = array[i] !== v || changed
|
||||
array[i] = v
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
export function createMarkers(count: number, markerData?: MarkerData): MarkerData {
|
||||
const markers = createTextureImage(Math.max(1, count), 1, Uint8Array, markerData && markerData.tMarker.ref.value.array)
|
||||
if (markerData) {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec3, Mat4 } from '../../../../mol-math/linear-algebra';
|
||||
import { MeshBuilder } from '../mesh-builder';
|
||||
import { Primitive } from '../../../primitive/primitive';
|
||||
import { Primitive, transformPrimitive } from '../../../primitive/primitive';
|
||||
import { Cylinder, CylinderProps } from '../../../primitive/cylinder';
|
||||
import { Prism } from '../../../primitive/prism';
|
||||
import { polygon } from '../../../primitive/polygon';
|
||||
|
||||
const cylinderMap = new Map<string, Primitive>()
|
||||
const up = Vec3.create(0, 1, 0)
|
||||
@@ -34,7 +36,12 @@ function getCylinder(props: CylinderProps) {
|
||||
const key = JSON.stringify(props)
|
||||
let cylinder = cylinderMap.get(key)
|
||||
if (cylinder === undefined) {
|
||||
cylinder = Cylinder(props)
|
||||
if (props.radialSegments && props.radialSegments <= 4) {
|
||||
const box = Prism(polygon(4, true, props.radiusTop), props)
|
||||
cylinder = transformPrimitive(box, Mat4.rotX90)
|
||||
} else {
|
||||
cylinder = Cylinder(props)
|
||||
}
|
||||
cylinderMap.set(key, cylinder)
|
||||
}
|
||||
return cylinder
|
||||
|
||||
108
src/mol-geo/geometry/mesh/builder/ribbon.ts
Normal file
108
src/mol-geo/geometry/mesh/builder/ribbon.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Vec3 } from '../../../../mol-math/linear-algebra';
|
||||
import { ChunkedArray } from '../../../../mol-data/util';
|
||||
import { MeshBuilder } from '../mesh-builder';
|
||||
|
||||
const tA = Vec3.zero()
|
||||
const tB = Vec3.zero()
|
||||
const tV = Vec3.zero()
|
||||
|
||||
const horizontalVector = Vec3.zero()
|
||||
const verticalVector = Vec3.zero()
|
||||
const normalOffset = Vec3.zero()
|
||||
const positionVector = Vec3.zero()
|
||||
const normalVector = Vec3.zero()
|
||||
const torsionVector = Vec3.zero()
|
||||
|
||||
/** set arrowHeight = 0 for no arrow */
|
||||
export function addRibbon(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, arrowHeight: number) {
|
||||
const { currentGroup, vertices, normals, indices, groups } = state
|
||||
|
||||
let vertexCount = vertices.elementCount
|
||||
let offsetLength = 0
|
||||
|
||||
if (arrowHeight > 0) {
|
||||
Vec3.fromArray(tA, controlPoints, 0)
|
||||
Vec3.fromArray(tB, controlPoints, linearSegments * 3)
|
||||
offsetLength = arrowHeight / Vec3.magnitude(Vec3.sub(tV, tB, tA))
|
||||
}
|
||||
|
||||
for (let i = 0; i <= linearSegments; ++i) {
|
||||
const width = widthValues[i]
|
||||
const height = heightValues[i]
|
||||
|
||||
const actualHeight = arrowHeight === 0 ? height : arrowHeight * (1 - i / linearSegments);
|
||||
const i3 = i * 3
|
||||
|
||||
Vec3.fromArray(verticalVector, normalVectors, i3)
|
||||
Vec3.scale(verticalVector, verticalVector, actualHeight);
|
||||
|
||||
Vec3.fromArray(horizontalVector, binormalVectors, i3)
|
||||
Vec3.scale(horizontalVector, horizontalVector, width);
|
||||
|
||||
if (arrowHeight > 0) {
|
||||
Vec3.fromArray(tA, normalVectors, i3)
|
||||
Vec3.fromArray(tB, binormalVectors, i3)
|
||||
Vec3.scale(normalOffset, Vec3.cross(normalOffset, tA, tB), offsetLength)
|
||||
}
|
||||
|
||||
Vec3.fromArray(positionVector, controlPoints, i3)
|
||||
Vec3.fromArray(normalVector, normalVectors, i3)
|
||||
Vec3.fromArray(torsionVector, binormalVectors, i3)
|
||||
|
||||
Vec3.add(tA, positionVector, verticalVector)
|
||||
Vec3.negate(tB, torsionVector)
|
||||
ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
|
||||
ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
|
||||
|
||||
Vec3.sub(tA, positionVector, verticalVector)
|
||||
ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
|
||||
ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
|
||||
|
||||
Vec3.add(tA, positionVector, verticalVector)
|
||||
Vec3.copy(tB, torsionVector)
|
||||
ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
|
||||
ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
|
||||
|
||||
Vec3.sub(tA, positionVector, verticalVector)
|
||||
ChunkedArray.add3(vertices, tA[0], tA[1], tA[2])
|
||||
ChunkedArray.add3(normals, tB[0], tB[1], tB[2])
|
||||
}
|
||||
|
||||
for (let i = 0; i < linearSegments; ++i) {
|
||||
ChunkedArray.add3(
|
||||
indices,
|
||||
vertexCount + i * 4,
|
||||
vertexCount + (i + 1) * 4 + 1,
|
||||
vertexCount + i * 4 + 1
|
||||
);
|
||||
ChunkedArray.add3(
|
||||
indices,
|
||||
vertexCount + i * 4,
|
||||
vertexCount + (i + 1) * 4,
|
||||
vertexCount + (i + 1) * 4 + 1
|
||||
);
|
||||
|
||||
ChunkedArray.add3(
|
||||
indices,
|
||||
vertexCount + i * 4 + 2 + 1,
|
||||
vertexCount + (i + 1) * 4 + 2 + 1,
|
||||
vertexCount + i * 4 + 2
|
||||
);
|
||||
ChunkedArray.add3(
|
||||
indices,
|
||||
vertexCount + i * 4 + 2,
|
||||
vertexCount + (i + 1) * 4 + 2 + 1,
|
||||
vertexCount + (i + 1) * 4 + 2
|
||||
);
|
||||
}
|
||||
|
||||
const addedVertexCount = (linearSegments + 1) * 4
|
||||
for (let i = 0, il = addedVertexCount; i < il; ++i) ChunkedArray.add(groups, currentGroup)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -71,7 +71,7 @@ function addCap(offset: number, state: MeshBuilder.State, controlPoints: ArrayLi
|
||||
}
|
||||
|
||||
/** set arrowHeight = 0 for no arrow */
|
||||
export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, width: number, height: number, arrowHeight: number, startCap: boolean, endCap: boolean) {
|
||||
export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<number>, normalVectors: ArrayLike<number>, binormalVectors: ArrayLike<number>, linearSegments: number, widthValues: ArrayLike<number>, heightValues: ArrayLike<number>, arrowHeight: number, startCap: boolean, endCap: boolean) {
|
||||
const { currentGroup, vertices, normals, indices, groups } = state
|
||||
|
||||
let vertexCount = vertices.elementCount
|
||||
@@ -84,6 +84,9 @@ export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<numb
|
||||
}
|
||||
|
||||
for (let i = 0; i <= linearSegments; ++i) {
|
||||
const width = widthValues[i]
|
||||
const height = heightValues[i]
|
||||
|
||||
const actualHeight = arrowHeight === 0 ? height : arrowHeight * (1 - i / linearSegments);
|
||||
const i3 = i * 3
|
||||
|
||||
@@ -141,32 +144,55 @@ export function addSheet(state: MeshBuilder.State, controlPoints: ArrayLike<numb
|
||||
}
|
||||
|
||||
for (let i = 0; i < linearSegments; ++i) {
|
||||
for (let j = 0; j < 4; j++) {
|
||||
// the triangles are arranged such that opposing triangles of the sheet align
|
||||
// which prevents triangle intersection within tight curves
|
||||
for (let j = 0; j < 2; j++) {
|
||||
ChunkedArray.add3(
|
||||
indices,
|
||||
vertexCount + i * 8 + 2 * j,
|
||||
vertexCount + (i + 1) * 8 + 2 * j + 1,
|
||||
vertexCount + i * 8 + 2 * j + 1
|
||||
vertexCount + i * 8 + 2 * j, // a
|
||||
vertexCount + (i + 1) * 8 + 2 * j + 1, // c
|
||||
vertexCount + i * 8 + 2 * j + 1 // b
|
||||
);
|
||||
ChunkedArray.add3(
|
||||
indices,
|
||||
vertexCount + i * 8 + 2 * j,
|
||||
vertexCount + (i + 1) * 8 + 2 * j,
|
||||
vertexCount + (i + 1) * 8 + 2 * j + 1
|
||||
vertexCount + i * 8 + 2 * j, // a
|
||||
vertexCount + (i + 1) * 8 + 2 * j, // d
|
||||
vertexCount + (i + 1) * 8 + 2 * j + 1 // c
|
||||
);
|
||||
}
|
||||
for (let j = 2; j < 4; j++) {
|
||||
ChunkedArray.add3(
|
||||
indices,
|
||||
vertexCount + i * 8 + 2 * j, // a
|
||||
vertexCount + (i + 1) * 8 + 2 * j, // d
|
||||
vertexCount + i * 8 + 2 * j + 1, // b
|
||||
);
|
||||
ChunkedArray.add3(
|
||||
indices,
|
||||
vertexCount + (i + 1) * 8 + 2 * j, // d
|
||||
vertexCount + (i + 1) * 8 + 2 * j + 1, // c
|
||||
vertexCount + i * 8 + 2 * j + 1, // b
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (startCap) {
|
||||
const width = widthValues[0]
|
||||
const height = heightValues[0]
|
||||
const h = arrowHeight === 0 ? height : arrowHeight
|
||||
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, h, h)
|
||||
} else if (arrowHeight > 0) {
|
||||
const width = widthValues[0]
|
||||
const height = heightValues[0]
|
||||
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, arrowHeight, -height)
|
||||
addCap(0, state, controlPoints, normalVectors, binormalVectors, width, -arrowHeight, height)
|
||||
}
|
||||
|
||||
if (endCap && arrowHeight === 0) {
|
||||
addCap(linearSegments * 3, state, controlPoints, normalVectors, binormalVectors, width, height, height)
|
||||
const width = widthValues[linearSegments]
|
||||
const height = heightValues[linearSegments]
|
||||
// use negative height to flip the direction the cap's triangles are facing
|
||||
addCap(linearSegments * 3, state, controlPoints, normalVectors, binormalVectors, width, -height, -height)
|
||||
}
|
||||
|
||||
const addedVertexCount = (linearSegments + 1) * 8 +
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -50,7 +50,15 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
|
||||
const t = 2 * Math.PI * j / radialSegments;
|
||||
|
||||
add3AndScale2(surfacePoint, u, v, controlPoint, h * Math.cos(t), w * Math.sin(t))
|
||||
add2AndScale2(normalVector, u, v, w * Math.cos(t), h * Math.sin(t))
|
||||
if (radialSegments === 2) {
|
||||
// add2AndScale2(normalVector, u, v, w * Math.cos(t), h * Math.sin(t))
|
||||
Vec3.copy(normalVector, v)
|
||||
console.log(i, t)
|
||||
Vec3.normalize(normalVector, normalVector)
|
||||
if (t !== 0 || i % 2 === 0) Vec3.negate(normalVector, normalVector)
|
||||
} else {
|
||||
add2AndScale2(normalVector, u, v, w * Math.cos(t), h * Math.sin(t))
|
||||
}
|
||||
Vec3.normalize(normalVector, normalVector)
|
||||
|
||||
ChunkedArray.add3(vertices, surfacePoint[0], surfacePoint[1], surfacePoint[2]);
|
||||
|
||||
@@ -24,7 +24,8 @@ export function applyOverpaintColor(array: Uint8Array, start: number, end: numbe
|
||||
}
|
||||
|
||||
export function clearOverpaint(array: Uint8Array, start: number, end: number) {
|
||||
array.fill(0, start, end)
|
||||
array.fill(0, start * 4, end * 4)
|
||||
return true
|
||||
}
|
||||
|
||||
export function createOverpaint(count: number, overpaintData?: OverpaintData): OverpaintData {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Theme } from '../../../mol-theme/theme';
|
||||
import { createColors } from '../color-data';
|
||||
import { createSizes, getMaxSize } from '../size-data';
|
||||
import { createMarkers } from '../marker-data';
|
||||
import { ColorNames } from '../../../mol-util/color/tables';
|
||||
import { ColorNames } from '../../../mol-util/color/names';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { calculateBoundingSphere, TextureImage, createTextureImage } from '../../../mol-gl/renderable/util';
|
||||
import { TextValues } from '../../../mol-gl/renderable/text';
|
||||
|
||||
@@ -8,16 +8,18 @@
|
||||
* Create 3d points for a polygon:
|
||||
* 3 for a triangle, 4 for a rectangle, 5 for a pentagon, 6 for a hexagon...
|
||||
*/
|
||||
export function polygon(sideCount: number, shift: boolean) {
|
||||
export function polygon(sideCount: number, shift: boolean, radius = -1) {
|
||||
const points = new Float32Array(sideCount * 3)
|
||||
const radius = sideCount <= 4 ? Math.sqrt(2) / 2 : 0.6
|
||||
const r = radius === -1
|
||||
? (sideCount <= 4 ? Math.sqrt(2) / 2 : 0.6)
|
||||
: radius
|
||||
|
||||
const offset = shift ? 1 : 0
|
||||
|
||||
for (let i = 0, il = sideCount; i < il; ++i) {
|
||||
const c = (i * 2 + offset) / sideCount * Math.PI
|
||||
points[i * 3] = Math.cos(c) * radius
|
||||
points[i * 3 + 1] = Math.sin(c) * radius
|
||||
points[i * 3] = Math.cos(c) * r
|
||||
points[i * 3 + 1] = Math.sin(c) * r
|
||||
points[i * 3 + 2] = 0
|
||||
}
|
||||
return points
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Vec3, Mat4, Mat3 } from '../../mol-math/linear-algebra';
|
||||
import { getNormalMatrix } from '../util';
|
||||
import { NumberArray } from '../../mol-util/type-helpers';
|
||||
|
||||
export interface Primitive {
|
||||
vertices: ArrayLike<number>
|
||||
@@ -56,4 +58,22 @@ export function PrimitiveBuilder(triangleCount: number): PrimitiveBuilder {
|
||||
},
|
||||
getPrimitive: () => ({ vertices, normals, indices })
|
||||
}
|
||||
}
|
||||
|
||||
const tmpV = Vec3.zero()
|
||||
const tmpMat3 = Mat3.zero()
|
||||
|
||||
/** Transform primitive in-place */
|
||||
export function transformPrimitive(primitive: Primitive, t: Mat4) {
|
||||
const { vertices, normals } = primitive
|
||||
const n = getNormalMatrix(tmpMat3, t)
|
||||
for (let i = 0, il = vertices.length; i < il; i += 3) {
|
||||
// position
|
||||
Vec3.transformMat4(tmpV, Vec3.fromArray(tmpV, vertices, i), t)
|
||||
Vec3.toArray(tmpV, vertices as NumberArray, i)
|
||||
// normal
|
||||
Vec3.transformMat3(tmpV, Vec3.fromArray(tmpV, normals, i), n)
|
||||
Vec3.toArray(tmpV, normals as NumberArray, i)
|
||||
}
|
||||
return primitive
|
||||
}
|
||||
@@ -9,26 +9,39 @@ import { Primitive, PrimitiveBuilder } from './primitive';
|
||||
import { polygon } from './polygon'
|
||||
import { Cage } from './cage';
|
||||
|
||||
const on = Vec3.create(0, 0, -0.5), op = Vec3.create(0, 0, 0.5)
|
||||
const a = Vec3.zero(), b = Vec3.zero(), c = Vec3.zero(), d = Vec3.zero()
|
||||
const on = Vec3(), op = Vec3()
|
||||
const a = Vec3(), b = Vec3(), c = Vec3(), d = Vec3()
|
||||
|
||||
export const DefaultPrismProps = {
|
||||
height: 1,
|
||||
topCap: true,
|
||||
bottomCap: true,
|
||||
}
|
||||
export type PrismProps = Partial<typeof DefaultPrismProps>
|
||||
|
||||
/**
|
||||
* Create a prism with a base of 4 or more points
|
||||
*/
|
||||
export function Prism(points: ArrayLike<number>): Primitive {
|
||||
export function Prism(points: ArrayLike<number>, props?: PrismProps): Primitive {
|
||||
const sideCount = points.length / 3
|
||||
if (sideCount < 4) throw new Error('need at least 4 points to build a prism')
|
||||
|
||||
const { height, topCap, bottomCap } = { ...DefaultPrismProps, ...props };
|
||||
|
||||
const count = 4 * sideCount
|
||||
const builder = PrimitiveBuilder(count)
|
||||
const halfHeight = height * 0.5
|
||||
|
||||
Vec3.set(on, 0, 0, -halfHeight)
|
||||
Vec3.set(op, 0, 0, halfHeight)
|
||||
|
||||
// create sides
|
||||
for (let i = 0; i < sideCount; ++i) {
|
||||
const ni = (i + 1) % sideCount
|
||||
Vec3.set(a, points[i * 3], points[i * 3 + 1], -0.5)
|
||||
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -0.5)
|
||||
Vec3.set(c, points[ni * 3], points[ni * 3 + 1], 0.5)
|
||||
Vec3.set(d, points[i * 3], points[i * 3 + 1], 0.5)
|
||||
Vec3.set(a, points[i * 3], points[i * 3 + 1], -halfHeight)
|
||||
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -halfHeight)
|
||||
Vec3.set(c, points[ni * 3], points[ni * 3 + 1], halfHeight)
|
||||
Vec3.set(d, points[i * 3], points[i * 3 + 1], halfHeight)
|
||||
builder.add(a, b, c)
|
||||
builder.add(c, d, a)
|
||||
}
|
||||
@@ -36,12 +49,16 @@ export function Prism(points: ArrayLike<number>): Primitive {
|
||||
// create bases
|
||||
for (let i = 0; i < sideCount; ++i) {
|
||||
const ni = (i + 1) % sideCount
|
||||
Vec3.set(a, points[i * 3], points[i * 3 + 1], -0.5)
|
||||
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -0.5)
|
||||
builder.add(on, b, a)
|
||||
Vec3.set(a, points[i * 3], points[i * 3 + 1], 0.5)
|
||||
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], 0.5)
|
||||
builder.add(a, b, op)
|
||||
if (topCap) {
|
||||
Vec3.set(a, points[i * 3], points[i * 3 + 1], -halfHeight)
|
||||
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], -halfHeight)
|
||||
builder.add(on, b, a)
|
||||
}
|
||||
if (bottomCap) {
|
||||
Vec3.set(a, points[i * 3], points[i * 3 + 1], halfHeight)
|
||||
Vec3.set(b, points[ni * 3], points[ni * 3 + 1], halfHeight)
|
||||
builder.add(a, b, op)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.getPrimitive()
|
||||
@@ -70,20 +87,21 @@ export function HexagonalPrism() {
|
||||
/**
|
||||
* Create a prism cage
|
||||
*/
|
||||
export function PrismCage(points: ArrayLike<number>): Cage {
|
||||
export function PrismCage(points: ArrayLike<number>, height = 1): Cage {
|
||||
const sideCount = points.length / 3
|
||||
|
||||
// const count = 4 * sideCount
|
||||
const vertices: number[] = []
|
||||
const edges: number[] = []
|
||||
|
||||
const halfHeight = height * 0.5
|
||||
|
||||
let offset = 0
|
||||
|
||||
// vertices and side edges
|
||||
for (let i = 0; i < sideCount; ++i) {
|
||||
vertices.push(
|
||||
points[i * 3], points[i * 3 + 1], -0.5,
|
||||
points[i * 3], points[i * 3 + 1], 0.5
|
||||
points[i * 3], points[i * 3 + 1], -halfHeight,
|
||||
points[i * 3], points[i * 3 + 1], halfHeight
|
||||
)
|
||||
edges.push(offset, offset + 1)
|
||||
offset += 2
|
||||
|
||||
@@ -117,7 +117,7 @@ describe('renderer', () => {
|
||||
expect(ctx.gl.getParameter(ctx.gl.VIEWPORT)[3]).toBe(48)
|
||||
})
|
||||
|
||||
it('points', () => {
|
||||
it('points', async () => {
|
||||
const [ width, height ] = [ 32, 32 ]
|
||||
const gl = createGl(width, height, { preserveDrawingBuffer: true })
|
||||
const { ctx } = createRenderer(gl)
|
||||
@@ -126,6 +126,7 @@ describe('renderer', () => {
|
||||
const points = createPoints()
|
||||
|
||||
scene.add(points)
|
||||
await scene.commit().run()
|
||||
expect(ctx.stats.bufferCount).toBe(4);
|
||||
expect(ctx.stats.textureCount).toBe(5);
|
||||
expect(ctx.stats.vaoCount).toBe(5);
|
||||
@@ -133,6 +134,7 @@ describe('renderer', () => {
|
||||
expect(ctx.shaderCache.count).toBe(10);
|
||||
|
||||
scene.remove(points)
|
||||
await scene.commit().run()
|
||||
expect(ctx.stats.bufferCount).toBe(0);
|
||||
expect(ctx.stats.textureCount).toBe(0);
|
||||
expect(ctx.stats.vaoCount).toBe(0);
|
||||
|
||||
@@ -159,6 +159,7 @@ export const GlobalUniformSchema = {
|
||||
uPixelRatio: UniformSpec('f'),
|
||||
uViewportHeight: UniformSpec('f'),
|
||||
uViewport: UniformSpec('v4'),
|
||||
uViewOffset: UniformSpec('v2'),
|
||||
|
||||
uCameraPosition: UniformSpec('v3'),
|
||||
uNear: UniformSpec('f'),
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Camera } from '../mol-canvas3d/camera';
|
||||
|
||||
import Scene from './scene';
|
||||
import { WebGLContext } from './webgl/context';
|
||||
import { Mat4, Vec3, Vec4 } from '../mol-math/linear-algebra';
|
||||
import { Mat4, Vec3, Vec4, Vec2 } from '../mol-math/linear-algebra';
|
||||
import { Renderable } from './renderable';
|
||||
import { Color } from '../mol-util/color';
|
||||
import { ValueCell } from '../mol-util';
|
||||
@@ -52,7 +52,7 @@ export const RendererParams = {
|
||||
ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
|
||||
metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
roughness: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
|
||||
}
|
||||
export type RendererProps = PD.Values<typeof RendererParams>
|
||||
@@ -73,6 +73,8 @@ namespace Renderer {
|
||||
const modelViewProjection = Mat4.mul(Mat4.identity(), modelView, camera.projection)
|
||||
const invModelViewProjection = Mat4.invert(Mat4.identity(), modelViewProjection)
|
||||
|
||||
const viewOffset = camera.viewOffset.enabled ? Vec2.create(camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2()
|
||||
|
||||
const globalUniforms: GlobalUniformValues = {
|
||||
uModel: ValueCell.create(Mat4.identity()),
|
||||
uView: ValueCell.create(camera.view),
|
||||
@@ -88,6 +90,7 @@ namespace Renderer {
|
||||
uPixelRatio: ValueCell.create(ctx.pixelRatio),
|
||||
uViewportHeight: ValueCell.create(viewport.height),
|
||||
uViewport: ValueCell.create(Viewport.toVec4(Vec4(), viewport)),
|
||||
uViewOffset: ValueCell.create(viewOffset),
|
||||
|
||||
uLightIntensity: ValueCell.create(p.lightIntensity),
|
||||
uAmbientIntensity: ValueCell.create(p.ambientIntensity),
|
||||
@@ -165,6 +168,7 @@ namespace Renderer {
|
||||
ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection))
|
||||
|
||||
ValueCell.update(globalUniforms.uIsOrtho, camera.state.mode === 'orthographic' ? 1 : 0)
|
||||
ValueCell.update(globalUniforms.uViewOffset, camera.viewOffset.enabled ? Vec2.set(viewOffset, camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2.set(viewOffset, 0, 0))
|
||||
|
||||
ValueCell.update(globalUniforms.uCameraPosition, camera.state.position)
|
||||
ValueCell.update(globalUniforms.uFar, camera.state.far)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -12,6 +12,8 @@ import { Object3D } from './object3d';
|
||||
import { Sphere3D } from '../mol-math/geometry';
|
||||
import { Vec3 } from '../mol-math/linear-algebra';
|
||||
import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
|
||||
import { RuntimeContext, Task } from '../mol-task';
|
||||
import { AsyncQueue } from '../mol-util/async-queue';
|
||||
|
||||
const boundaryHelper = new BoundaryHelper();
|
||||
function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
|
||||
@@ -40,17 +42,11 @@ function renderableSort(a: Renderable<RenderableValues & BaseValues>, b: Rendera
|
||||
const drawProgramIdB = b.getProgram('color').id
|
||||
const materialIdA = a.materialId
|
||||
const materialIdB = b.materialId
|
||||
const zA = a.values.boundingSphere.ref.value.center[2]
|
||||
const zB = b.values.boundingSphere.ref.value.center[2]
|
||||
|
||||
if (drawProgramIdA !== drawProgramIdB) {
|
||||
return drawProgramIdA - drawProgramIdB // sort by program id to minimize gl state changes
|
||||
} else if (materialIdA !== materialIdB) {
|
||||
return materialIdA - materialIdB // sort by material id to minimize gl state changes
|
||||
} else if (zA !== zB) {
|
||||
return a.state.opaque
|
||||
? zA - zB // when opaque, draw closer elements first to minimize overdraw
|
||||
: zB - zA // when transparent, draw elements last to maximize partial visibility
|
||||
} else {
|
||||
return a.id - b.id;
|
||||
}
|
||||
@@ -60,10 +56,13 @@ interface Scene extends Object3D {
|
||||
readonly count: number
|
||||
readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
|
||||
readonly boundingSphere: Sphere3D
|
||||
readonly isCommiting: boolean
|
||||
|
||||
update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean) => void
|
||||
add: (o: GraphicsRenderObject) => Renderable<any>
|
||||
add: (o: GraphicsRenderObject) => void // Renderable<any>
|
||||
remove: (o: GraphicsRenderObject) => void
|
||||
syncCommit: () => void
|
||||
commit: () => Task<void>
|
||||
has: (o: GraphicsRenderObject) => boolean
|
||||
clear: () => void
|
||||
forEach: (callbackFn: (value: Renderable<RenderableValues & BaseValues>, key: GraphicsRenderObject) => void) => void
|
||||
@@ -74,15 +73,63 @@ namespace Scene {
|
||||
const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>()
|
||||
const renderables: Renderable<RenderableValues & BaseValues>[] = []
|
||||
const boundingSphere = Sphere3D.zero()
|
||||
|
||||
let boundingSphereDirty = true
|
||||
|
||||
const object3d = Object3D.create()
|
||||
|
||||
const add = (o: GraphicsRenderObject) => {
|
||||
if (!renderableMap.has(o)) {
|
||||
const renderable = createRenderable(ctx, o)
|
||||
renderables.push(renderable)
|
||||
renderableMap.set(o, renderable)
|
||||
boundingSphereDirty = true
|
||||
return renderable;
|
||||
} else {
|
||||
console.warn(`RenderObject with id '${o.id}' already present`)
|
||||
return renderableMap.get(o)!
|
||||
}
|
||||
}
|
||||
|
||||
const remove = (o: GraphicsRenderObject) => {
|
||||
const renderable = renderableMap.get(o)
|
||||
if (renderable) {
|
||||
renderable.dispose()
|
||||
renderables.splice(renderables.indexOf(renderable), 1)
|
||||
renderableMap.delete(o)
|
||||
boundingSphereDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
const commitQueue = new AsyncQueue<any>();
|
||||
const toAdd: GraphicsRenderObject[] = []
|
||||
const toRemove: GraphicsRenderObject[] = []
|
||||
|
||||
type CommitParams = { toAdd: GraphicsRenderObject[], toRemove: GraphicsRenderObject[] }
|
||||
|
||||
const step = 100
|
||||
const handle = async (ctx: RuntimeContext, arr: GraphicsRenderObject[], fn: (o: GraphicsRenderObject) => void, message: string) => {
|
||||
for (let i = 0, il = arr.length; i < il; i += step) {
|
||||
if (ctx.shouldUpdate) await ctx.update({ message, current: i, max: il })
|
||||
for (let j = i, jl = Math.min(i + step, il); j < jl; ++j) {
|
||||
fn(arr[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const commit = async (ctx: RuntimeContext, p: CommitParams) => {
|
||||
await handle(ctx, p.toRemove, remove, 'Removing GraphicsRenderObjects')
|
||||
await handle(ctx, p.toAdd, add, 'Adding GraphicsRenderObjects')
|
||||
if (ctx.shouldUpdate) await ctx.update({ message: 'Sorting GraphicsRenderObjects' })
|
||||
renderables.sort(renderableSort)
|
||||
}
|
||||
|
||||
return {
|
||||
get view () { return object3d.view },
|
||||
get position () { return object3d.position },
|
||||
get direction () { return object3d.direction },
|
||||
get up () { return object3d.up },
|
||||
get isCommiting () { return commitQueue.length > 0 },
|
||||
|
||||
update(objects, keepBoundingSphere) {
|
||||
Object3D.update(object3d)
|
||||
@@ -100,27 +147,35 @@ namespace Scene {
|
||||
if (!keepBoundingSphere) boundingSphereDirty = true
|
||||
},
|
||||
add: (o: GraphicsRenderObject) => {
|
||||
if (!renderableMap.has(o)) {
|
||||
const renderable = createRenderable(ctx, o)
|
||||
renderables.push(renderable)
|
||||
renderables.sort(renderableSort)
|
||||
renderableMap.set(o, renderable)
|
||||
boundingSphereDirty = true
|
||||
return renderable;
|
||||
} else {
|
||||
console.warn(`RenderObject with id '${o.id}' already present`)
|
||||
return renderableMap.get(o)!
|
||||
}
|
||||
toAdd.push(o)
|
||||
},
|
||||
remove: (o: GraphicsRenderObject) => {
|
||||
const renderable = renderableMap.get(o)
|
||||
if (renderable) {
|
||||
renderable.dispose()
|
||||
renderables.splice(renderables.indexOf(renderable), 1)
|
||||
renderables.sort(renderableSort)
|
||||
renderableMap.delete(o)
|
||||
boundingSphereDirty = true
|
||||
}
|
||||
toRemove.push(o)
|
||||
},
|
||||
syncCommit: () => {
|
||||
for (let i = 0, il = toRemove.length; i < il; ++i) remove(toRemove[i])
|
||||
toRemove.length = 0
|
||||
for (let i = 0, il = toAdd.length; i < il; ++i) add(toAdd[i])
|
||||
toAdd.length = 0
|
||||
renderables.sort(renderableSort)
|
||||
},
|
||||
commit: () => {
|
||||
const params = { toAdd: [ ...toAdd ], toRemove: [ ...toRemove ] }
|
||||
toAdd.length = 0
|
||||
toRemove.length = 0
|
||||
|
||||
return Task.create('Commiting GraphicsRenderObjects', async ctx => {
|
||||
const removed = await commitQueue.enqueue(params);
|
||||
if (!removed) return;
|
||||
|
||||
try {
|
||||
await commit(ctx, params);
|
||||
} finally {
|
||||
commitQueue.handled(params);
|
||||
}
|
||||
}, () => {
|
||||
commitQueue.remove(params);
|
||||
})
|
||||
},
|
||||
has: (o: GraphicsRenderObject) => {
|
||||
return renderableMap.has(o)
|
||||
|
||||
@@ -23,6 +23,9 @@ export default `
|
||||
float ta = 1.0 - vTransparency;
|
||||
float at = 0.0;
|
||||
|
||||
// shift by view-offset during multi-sample rendering to allow for blending
|
||||
vec2 coord = gl_FragCoord.xy + uViewOffset * 0.25;
|
||||
|
||||
#if defined(dTransparencyVariant_single)
|
||||
const mat4 thresholdMatrix = mat4(
|
||||
1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0,
|
||||
@@ -30,9 +33,9 @@ export default `
|
||||
4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0,
|
||||
16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0
|
||||
);
|
||||
at = thresholdMatrix[int(intMod(gl_FragCoord.x, 4.0))][int(intMod(gl_FragCoord.y, 4.0))];
|
||||
at = thresholdMatrix[int(intMod(coord.x, 4.0))][int(intMod(coord.y, 4.0))];
|
||||
#elif defined(dTransparencyVariant_multi)
|
||||
at = fract(dot(vec3(gl_FragCoord.xy, vGroup + 0.5), vec3(2.0, 7.0, 23.0) / 17.0f));
|
||||
at = fract(dot(vec3(coord, vGroup + 0.5), vec3(2.0, 7.0, 23.0) / 17.0f));
|
||||
#endif
|
||||
|
||||
if (ta < 0.99 && (ta < 0.01 || ta < at)) discard;
|
||||
|
||||
@@ -9,6 +9,8 @@ varying float vMarker;
|
||||
|
||||
varying vec3 vViewPosition;
|
||||
|
||||
uniform vec2 uViewOffset;
|
||||
|
||||
uniform float uFogNear;
|
||||
uniform float uFogFar;
|
||||
uniform vec3 uFogColor;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.311, IHM 1.0, CARB draft.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.313, IHM 1.01, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.311, IHM 1.0, CARB draft.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.313, IHM 1.01, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { mmCIF_Schema } from './mmcif';
|
||||
import { mmCIF_Schema } from './mmcif';
|
||||
import { Column } from '../../../../mol-data/db';
|
||||
|
||||
export const mmCIF_residueId_schema = {
|
||||
label_comp_id: mmCIF_Schema.atom_site.label_comp_id,
|
||||
@@ -15,4 +17,10 @@ export const mmCIF_residueId_schema = {
|
||||
auth_comp_id: mmCIF_Schema.atom_site.auth_atom_id,
|
||||
auth_seq_id: mmCIF_Schema.atom_site.auth_seq_id,
|
||||
auth_asym_id: mmCIF_Schema.atom_site.auth_asym_id
|
||||
}
|
||||
|
||||
export const mmCIF_chemCompBond_schema = {
|
||||
...mmCIF_Schema.chem_comp_bond,
|
||||
/** Indicates if the bond entry was taken from the protonation variant dictionary */
|
||||
molstar_protonation_variant: Column.Schema.Str()
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.311, IHM 1.0, CARB draft.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.313, IHM 1.01, CARB draft.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -1471,6 +1471,62 @@ export const mmCIF_Schema = {
|
||||
*/
|
||||
'space_group_name_H-M': str,
|
||||
},
|
||||
/**
|
||||
* These are internal RCSB records to keep track of data processing
|
||||
* and status of the entry.
|
||||
*/
|
||||
pdbx_database_status: {
|
||||
/**
|
||||
* Code for status of file.
|
||||
*/
|
||||
status_code: Aliased<'PROC' | 'WAIT' | 'REL' | 'HOLD' | 'HPUB' | 'REFI' | 'OBS' | 'WDRN' | 'AUTH' | 'POLC' | 'REPL' | 'AUCO' | 'TRSF' | 'RMVD' | 'DEL' | 'REV' | 'UPD' | 'BIB'>(str),
|
||||
/**
|
||||
* Code for status of structure factor file.
|
||||
*/
|
||||
status_code_sf: Aliased<'PROC' | 'WAIT' | 'REL' | 'HOLD' | 'HPUB' | 'OBS' | 'WDRN' | 'AUTH' | 'POLC' | 'REPL' | 'RMVD'>(str),
|
||||
/**
|
||||
* Code for status of NMR constraints file.
|
||||
*/
|
||||
status_code_mr: Aliased<'PROC' | 'WAIT' | 'REL' | 'HOLD' | 'HPUB' | 'OBS' | 'WDRN' | 'AUTH' | 'POLC' | 'REPL' | 'RMVD'>(str),
|
||||
/**
|
||||
* The value of _pdbx_database_status.entry_id identifies the data block.
|
||||
*/
|
||||
entry_id: str,
|
||||
/**
|
||||
* The date of initial deposition. (The first message for
|
||||
* deposition has been received.)
|
||||
*/
|
||||
recvd_initial_deposition_date: str,
|
||||
/**
|
||||
* This code indicates whether the entry belongs to
|
||||
* Structural Genomics Project.
|
||||
*/
|
||||
SG_entry: Aliased<'Y' | 'N'>(str),
|
||||
/**
|
||||
* The site where the file was deposited.
|
||||
*/
|
||||
deposit_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BMRB' | 'BNL'>(str),
|
||||
/**
|
||||
* The site where the file was deposited.
|
||||
*/
|
||||
process_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BNL'>(str),
|
||||
/**
|
||||
* Code for status of chemical shift data file.
|
||||
*/
|
||||
status_code_cs: Aliased<'PROC' | 'WAIT' | 'AUTH' | 'POLC' | 'REPL' | 'REL' | 'HOLD' | 'HPUB' | 'OBS' | 'RMVD' | 'WDRN'>(str),
|
||||
/**
|
||||
* The methods development category in which this
|
||||
* entry has been placed.
|
||||
*/
|
||||
methods_development_category: Aliased<'CAPRI' | 'CASP' | 'CASD-NMR' | 'FoldIt' | 'GPCR Dock' | 'D3R' | 'RNA-Puzzles'>(str),
|
||||
/**
|
||||
* A flag indicating that the entry is compatible with the PDB format.
|
||||
*
|
||||
* A value of 'N' indicates that the no PDB format data file is
|
||||
* corresponding to this entry is available in the PDB archive.
|
||||
*/
|
||||
pdb_format_compatible: Aliased<'Y' | 'N'>(str),
|
||||
},
|
||||
/**
|
||||
* The PDBX_NONPOLY_SCHEME category provides residue level nomenclature
|
||||
* mapping for non-polymer entities.
|
||||
@@ -1519,6 +1575,28 @@ export const mmCIF_Schema = {
|
||||
*/
|
||||
pdb_ins_code: str,
|
||||
},
|
||||
/**
|
||||
* Data items in PDBX_DATABASE_RELATED contain references to entries
|
||||
* that are related to the this entry.
|
||||
*/
|
||||
pdbx_database_related: {
|
||||
/**
|
||||
* The name of the database containing the related entry.
|
||||
*/
|
||||
db_name: str,
|
||||
/**
|
||||
* A description of the related entry.
|
||||
*/
|
||||
details: str,
|
||||
/**
|
||||
* The identifying code in the related database.
|
||||
*/
|
||||
db_id: str,
|
||||
/**
|
||||
* The identifying content type of the related entry.
|
||||
*/
|
||||
content_type: Aliased<'minimized average structure' | 'representative structure' | 'ensemble' | 'derivative structure' | 'native structure' | 'associated EM volume' | 'other EM volume' | 'associated NMR restraints' | 'associated structure factors' | 'associated SAS data' | 'protein target sequence and/or protocol data' | 'split' | 're-refinement' | 'complete structure' | 'unspecified' | 'other'>(str),
|
||||
},
|
||||
/**
|
||||
* Data items in the CHEM_COMP_IDENTIFIER category provide
|
||||
* identifiers for chemical components.
|
||||
@@ -1549,6 +1627,85 @@ export const mmCIF_Schema = {
|
||||
*/
|
||||
program_version: str,
|
||||
},
|
||||
/**
|
||||
* Data items in the PDBX_UNOBS_OR_ZERO_OCC_RESIDUES category list the
|
||||
* residues within the entry that are not observed or have zero occupancy.
|
||||
*/
|
||||
pdbx_unobs_or_zero_occ_residues: {
|
||||
/**
|
||||
* The value of _pdbx_unobs_or_zero_occ_residues.id must uniquely identify
|
||||
* each item in the PDBX_UNOBS_OR_ZERO_OCC_RESIDUES list.
|
||||
*
|
||||
* This is an integer serial number.
|
||||
*/
|
||||
id: int,
|
||||
/**
|
||||
* The value of polymer flag indicates whether the unobserved or
|
||||
* zero occupancy residue is part of a polymer chain or not
|
||||
*/
|
||||
polymer_flag: Aliased<'Y' | 'N'>(str),
|
||||
/**
|
||||
* The value of occupancy flag indicates whether the residue
|
||||
* is unobserved (= 1) or the coordinates have an occupancy of zero (=0)
|
||||
*/
|
||||
occupancy_flag: Aliased<'1' | '0'>(int),
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.pdbx_PDB_model_num in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
PDB_model_num: int,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.auth_asym_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
auth_asym_id: str,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.auth_comp_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
auth_comp_id: str,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.auth_seq_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
auth_seq_id: str,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.pdbx_PDB_ins_code in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
PDB_ins_code: str,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.label_asym_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
label_asym_id: str,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.label_comp_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
label_comp_id: str,
|
||||
/**
|
||||
* Part of the identifier for the unobserved or zero occupancy residue.
|
||||
*
|
||||
* This data item is a pointer to _atom_site.label_seq_id in the
|
||||
* ATOM_SITE category.
|
||||
*/
|
||||
label_seq_id: int,
|
||||
},
|
||||
/**
|
||||
* Data items in the PDBX_STRUCT_MOD_RESIDUE category list the
|
||||
* modified polymer components in the entry and provide some
|
||||
@@ -2079,10 +2236,10 @@ export const mmCIF_Schema = {
|
||||
pdbx_end_seq_num: int,
|
||||
},
|
||||
/**
|
||||
* Data items in the PDBX_ENTITY_DESCRIPTOR category provide
|
||||
* Data items in the PDBX_ENTITY_BRANCH_DESCRIPTOR category provide
|
||||
* string descriptors of entity chemical structure.
|
||||
*/
|
||||
pdbx_entity_descriptor: {
|
||||
pdbx_entity_branch_descriptor: {
|
||||
/**
|
||||
* This data item is a pointer to _entity_poly.entity_id in the ENTITY
|
||||
* category.
|
||||
@@ -2892,7 +3049,7 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* The name of the database containing the dataset entry.
|
||||
*/
|
||||
db_name: Aliased<'PDB' | 'BMRB' | 'EMDB' | 'EMPIAR' | 'SASBDB' | 'PRIDE' | 'MODEL ARCHIVE' | 'MASSIVE' | 'BioGRID' | 'Other'>(str),
|
||||
db_name: Aliased<'PDB' | 'PDB-Dev' | 'BMRB' | 'EMDB' | 'EMPIAR' | 'SASBDB' | 'PRIDE' | 'MODEL ARCHIVE' | 'MASSIVE' | 'BioGRID' | 'Other'>(str),
|
||||
/**
|
||||
* The accession code for the database entry.
|
||||
*/
|
||||
|
||||
@@ -84,7 +84,7 @@ function handleMolecule(state: State) {
|
||||
molecule.mol_comment = getTokenString(tokenizer)
|
||||
}
|
||||
|
||||
function isStatus_bit(aString: String): Boolean {
|
||||
function isStatus_bit(aString: string): boolean {
|
||||
if (aString.includes('DSPMOD') || aString.includes('TYPECOL') || aString.includes('CAP')
|
||||
|| aString.includes('BACKBONE') || aString.includes('DICT') || aString.includes('ESSENTIAL')
|
||||
|| aString.includes('WATER') || aString.includes('DIRECT')) {
|
||||
|
||||
183
src/mol-io/writer/_spec/cif.spec.ts
Normal file
183
src/mol-io/writer/_spec/cif.spec.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import * as Data from '../../reader/cif/data-model'
|
||||
import { CifWriter } from '../cif';
|
||||
import decodeMsgPack from '../../common/msgpack/decode'
|
||||
import { EncodedFile, EncodedCategory } from '../../common/binary-cif';
|
||||
import Field from '../../reader/cif/binary/field';
|
||||
import * as C from '../cif/encoder';
|
||||
|
||||
const cartn_x = Data.CifField.ofNumbers([1.001, 1.002, 1.003, 1.004, 1.005, 1.006, 1.007, 1.008, 1.009]);
|
||||
const cartn_y = Data.CifField.ofNumbers([-3.0, -2.666, -2.3333, -2.0, -1.666, -1.333, -1.0, -0.666, -0.333]);
|
||||
const cartn_z = Data.CifField.ofNumbers([1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => Math.sqrt(i)));
|
||||
const label_seq_id = Data.CifField.ofNumbers([1, 2, 3, 6, 11, 23, 47, 106, 235]);
|
||||
const atom_site = Data.CifCategory.ofFields('atom_site', { 'Cartn_x': cartn_x, 'Cartn_y': cartn_y, 'Cartn_z': cartn_z, 'label_seq_id': label_seq_id });
|
||||
const field1 = Data.CifField.ofNumbers([1, 2, 3, 6, 11, 23, 47, 106, 235]);
|
||||
const field2 = Data.CifField.ofNumbers([-1, -2, -3, -6, -11, -23, -47, -106, -235]);
|
||||
const other_fields = Data.CifCategory.ofFields('other_fields', { 'field1': field1, 'field2': field2 });
|
||||
|
||||
const encoding_aware_encoder = CifWriter.createEncoder({
|
||||
binary: true,
|
||||
binaryAutoClassifyEncoding: true,
|
||||
binaryEncodingPovider: CifWriter.createEncodingProviderFromJsonConfig([
|
||||
{
|
||||
'categoryName': 'atom_site',
|
||||
'columnName': 'Cartn_y',
|
||||
'encoding': 'rle',
|
||||
'precision': 0
|
||||
},
|
||||
{
|
||||
'categoryName': 'atom_site',
|
||||
'columnName': 'Cartn_z',
|
||||
'encoding': 'delta',
|
||||
'precision': 1
|
||||
},
|
||||
{
|
||||
'categoryName': 'atom_site',
|
||||
'columnName': 'label_seq_id',
|
||||
'encoding': 'delta-rle'
|
||||
}
|
||||
])
|
||||
});
|
||||
|
||||
describe('encoding-config', () => {
|
||||
const decoded = process(encoding_aware_encoder);
|
||||
|
||||
const decoded_atom_site = decoded.blocks[0].categories['atom_site'];
|
||||
const decoded_cartn_x = decoded_atom_site.getField('Cartn_x')!;
|
||||
const decoded_cartn_y = decoded_atom_site.getField('Cartn_y')!;
|
||||
const decoded_cartn_z = decoded_atom_site.getField('Cartn_z')!;
|
||||
const decoded_label_seq_id = decoded_atom_site.getField('label_seq_id')!;
|
||||
|
||||
const delta = 0.001;
|
||||
function assert(e: ArrayLike<number>, a: ArrayLike<number>) {
|
||||
expect(e.length).toBe(a.length);
|
||||
for (let i = 0; i < e.length; i++) {
|
||||
expect(Math.abs(e[i] - a[i])).toBeLessThan(delta);
|
||||
}
|
||||
}
|
||||
|
||||
function join(field: Data.CifField) {
|
||||
return field.binaryEncoding!.map(e => e.kind).join();
|
||||
}
|
||||
|
||||
it('strategy', () => {
|
||||
expect(join(decoded_cartn_x)).toBe('FixedPoint,Delta,IntegerPacking,ByteArray');
|
||||
expect(join(decoded_cartn_y)).toBe('FixedPoint,RunLength,IntegerPacking,ByteArray');
|
||||
expect(join(decoded_cartn_z)).toBe('FixedPoint,Delta,IntegerPacking,ByteArray');
|
||||
expect(join(decoded_label_seq_id)).toBe('Delta,RunLength,IntegerPacking,ByteArray');
|
||||
});
|
||||
|
||||
it('precision', () => {
|
||||
assert(decoded_cartn_x.toFloatArray(), cartn_x.toFloatArray());
|
||||
assert(decoded_cartn_y.toFloatArray(), cartn_y.toFloatArray().map(d => Math.round(d)));
|
||||
assert(decoded_cartn_z.toFloatArray(), cartn_z.toFloatArray().map(d => Math.round(d * 10) / 10));
|
||||
assert(decoded_label_seq_id.toIntArray(), label_seq_id.toIntArray());
|
||||
});
|
||||
});
|
||||
|
||||
const filter_aware_encoder1 = CifWriter.createEncoder({
|
||||
binary: true,
|
||||
binaryAutoClassifyEncoding: true
|
||||
});
|
||||
filter_aware_encoder1.setFilter(C.Category.filterOf('atom_site\n' +
|
||||
'\n' +
|
||||
'atom_site.Cartn_x\n' +
|
||||
'atom_site.Cartn_y\n'));
|
||||
|
||||
const filter_aware_encoder2 = CifWriter.createEncoder({
|
||||
binary: true
|
||||
});
|
||||
filter_aware_encoder2.setFilter(C.Category.filterOf('!atom_site\n' +
|
||||
'\n' +
|
||||
'!other_fields.field2\n'));
|
||||
|
||||
describe('filtering-config', () => {
|
||||
const decoded1 = process(filter_aware_encoder1);
|
||||
|
||||
const atom_site1 = decoded1.blocks[0].categories['atom_site'];
|
||||
const cartn_x1 = atom_site1.getField('Cartn_x');
|
||||
const cartn_y1 = atom_site1.getField('Cartn_y');
|
||||
const cartn_z1 = atom_site1.getField('Cartn_z');
|
||||
const label_seq_id1 = atom_site1.getField('label_seq_id');
|
||||
const fields1 = decoded1.blocks[0].categories['other_fields'];
|
||||
|
||||
it('whitelist-filtering', () => {
|
||||
expect(atom_site1).toBeDefined();
|
||||
expect(cartn_x1).toBeDefined();
|
||||
expect(cartn_y1).toBeDefined();
|
||||
expect(cartn_z1).toBeUndefined();
|
||||
expect(label_seq_id1).toBeUndefined();
|
||||
expect(fields1).toBeUndefined();
|
||||
});
|
||||
|
||||
const decoded2 = process(filter_aware_encoder2);
|
||||
|
||||
const atom_site2 = decoded2.blocks[0].categories['atom_site'];
|
||||
const fields2 = decoded2.blocks[0].categories['other_fields'];
|
||||
const field12 = fields2.getField('field1');
|
||||
const field22 = fields2.getField('field2');
|
||||
|
||||
it('blacklist-filtering', () => {
|
||||
expect(atom_site2).toBeUndefined();
|
||||
expect(fields2).toBeDefined();
|
||||
expect(field12).toBeDefined();
|
||||
expect(field22).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
function process(encoder: C.Encoder) {
|
||||
encoder.startDataBlock('test');
|
||||
|
||||
for (const cat of [atom_site, other_fields]) {
|
||||
const fields: CifWriter.Field[] = [];
|
||||
for (const f of cat.fieldNames) {
|
||||
fields.push(wrap(f, cat.getField(f)!))
|
||||
}
|
||||
encoder.writeCategory(getCategoryInstanceProvider(cat, fields));
|
||||
}
|
||||
|
||||
const encoded = encoder.getData() as Uint8Array;
|
||||
|
||||
const unpacked = decodeMsgPack(encoded) as EncodedFile;
|
||||
return Data.CifFile(unpacked.dataBlocks.map(block => {
|
||||
const cats = Object.create(null);
|
||||
for (const cat of block.categories) cats[cat.name.substr(1)] = Category(cat);
|
||||
return Data.CifBlock(block.categories.map(c => c.name.substr(1)), cats, block.header);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
function getCategoryInstanceProvider(cat: Data.CifCategory, fields: CifWriter.Field[]): CifWriter.Category {
|
||||
return {
|
||||
name: cat.name,
|
||||
instance: () => CifWriter.categoryInstance(fields, { data: cat, rowCount: cat.rowCount })
|
||||
};
|
||||
}
|
||||
|
||||
function wrap(name: string, field: Data.CifField): CifWriter.Field {
|
||||
const type = Data.getCifFieldType(field);
|
||||
if (type['@type'] === 'str') {
|
||||
return { name, type: CifWriter.Field.Type.Str, value: field.str, valueKind: field.valueKind };
|
||||
} else if (type['@type'] === 'float') {
|
||||
return { name, type: CifWriter.Field.Type.Float, value: field.float, valueKind: field.valueKind };
|
||||
} else {
|
||||
return { name, type: CifWriter.Field.Type.Int, value: field.int, valueKind: field.valueKind };
|
||||
}
|
||||
}
|
||||
|
||||
function Category(data: EncodedCategory): Data.CifCategory {
|
||||
const map = Object.create(null);
|
||||
const cache = Object.create(null);
|
||||
for (const col of data.columns) map[col.name] = col;
|
||||
return {
|
||||
rowCount: data.rowCount,
|
||||
name: data.name.substr(1),
|
||||
fieldNames: data.columns.map(c => c.name),
|
||||
getField(name) {
|
||||
const col = map[name];
|
||||
if (!col) return void 0;
|
||||
if (!!cache[name]) return cache[name];
|
||||
cache[name] = Field(col);
|
||||
return cache[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,5 +53,64 @@ export namespace CifWriter {
|
||||
return ff && ff.binaryEncoding ? ArrayEncoder.fromEncoding(ff.binaryEncoding) : void 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function createEncodingProviderFromJsonConfig(hints: EncodingStrategyHint[]): EncodingProvider {
|
||||
return {
|
||||
get(c, f) {
|
||||
for (let i = 0; i < hints.length; i++) {
|
||||
const hint = hints[i];
|
||||
if (hint.categoryName === c && hint.columnName === f) {
|
||||
return resolveEncoding(hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolveEncoding(hint: EncodingStrategyHint): ArrayEncoder {
|
||||
const precision: number | undefined = hint.precision;
|
||||
if (precision !== void 0) {
|
||||
const multiplier = Math.pow(10, precision);
|
||||
const fixedPoint = E.by(E.fixedPoint(multiplier));
|
||||
switch (hint.encoding) {
|
||||
case 'pack':
|
||||
return fixedPoint.and(E.integerPacking);
|
||||
case 'rle':
|
||||
return fixedPoint.and(E.runLength).and(E.integerPacking);
|
||||
case 'delta':
|
||||
return fixedPoint.and(E.delta).and(E.integerPacking);
|
||||
case 'delta-rle':
|
||||
return fixedPoint.and(E.delta).and(E.runLength).and(E.integerPacking);
|
||||
};
|
||||
} else {
|
||||
switch (hint.encoding) {
|
||||
case 'pack':
|
||||
return E.by(E.integerPacking);
|
||||
case 'rle':
|
||||
return E.by(E.runLength).and(E.integerPacking);
|
||||
case 'delta':
|
||||
return E.by(E.delta).and(E.integerPacking);
|
||||
case 'delta-rle':
|
||||
return E.by(E.delta).and(E.runLength).and(E.integerPacking);
|
||||
}
|
||||
}
|
||||
throw new Error('cannot be reached');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the information needed to encode certain fields: category and column name as well as encoding tag, precision is optional and identifies float columns.
|
||||
*/
|
||||
export interface EncodingStrategyHint {
|
||||
categoryName: string,
|
||||
columnName: string,
|
||||
// TODO would be nice to infer strategy and precision if needed
|
||||
encoding: EncodingType,
|
||||
/**
|
||||
* number of decimal places to keep - must be specified to float columns
|
||||
*/
|
||||
precision?: number
|
||||
}
|
||||
|
||||
type EncodingType = 'pack' | 'rle' | 'delta' | 'delta-rle'
|
||||
@@ -132,6 +132,60 @@ export namespace Category {
|
||||
includeField(categoryName: string, fieldName: string): boolean,
|
||||
}
|
||||
|
||||
export function filterOf(directives: string): Filter {
|
||||
const cat_whitelist: string[] = [];
|
||||
const cat_blacklist: string[] = [];
|
||||
const field_whitelist: string[] = [];
|
||||
const field_blacklist: string[] = [];
|
||||
|
||||
for (let d of directives.split(/[\r\n]+/)) {
|
||||
d = d.trim();
|
||||
// allow for empty lines in config
|
||||
if (d.length === 0) continue;
|
||||
// let ! denote blacklisted entries
|
||||
const blacklist = /^!/.test(d);
|
||||
if (blacklist) d = d.substr(1);
|
||||
const split = d.split(/\./);
|
||||
const field = split[1];
|
||||
const list = blacklist ? (field ? field_blacklist : cat_blacklist) : (field ? field_whitelist : cat_whitelist);
|
||||
|
||||
list[list.length] = d;
|
||||
|
||||
// ensure categories are aware about whitelisted columns
|
||||
if (field && !cat_whitelist.includes(split[0])) {
|
||||
cat_whitelist[cat_whitelist.length] = split[0];
|
||||
}
|
||||
}
|
||||
|
||||
const wlcatcol = field_whitelist.map(it => it.split('.')[0]);
|
||||
// blacklist has higher priority
|
||||
return {
|
||||
includeCategory(cat) {
|
||||
// block if category in black
|
||||
if (cat_blacklist.includes(cat)) {
|
||||
return false;
|
||||
} else {
|
||||
// if there is a whitelist, the category has to be explicitly allowed
|
||||
return cat_whitelist.length <= 0 ||
|
||||
// otherwise include if whitelist contains category
|
||||
cat_whitelist.indexOf(cat) !== -1;
|
||||
}
|
||||
},
|
||||
includeField(cat, field) {
|
||||
// column names are assumed to follow the pattern 'category_name.column_name'
|
||||
const full = cat + '.' + field;
|
||||
if (field_blacklist.includes(full)) {
|
||||
return false;
|
||||
} else {
|
||||
// if for this category no whitelist entries exist
|
||||
return !wlcatcol.includes(cat) ||
|
||||
// otherwise must be specifically allowed
|
||||
field_whitelist.includes(full);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const DefaultFilter: Filter = {
|
||||
includeCategory(cat) { return true; },
|
||||
includeField(cat, field) { return true; }
|
||||
|
||||
@@ -17,7 +17,9 @@ export interface PositionData {
|
||||
/** subset of indices into the x/y/z/radius arrays */
|
||||
indices: OrderedSet,
|
||||
/** optional element radius */
|
||||
radius?: ArrayLike<number>
|
||||
radius?: ArrayLike<number>,
|
||||
/** optional element id */
|
||||
id?: ArrayLike<number>,
|
||||
}
|
||||
|
||||
export type DensityData = {
|
||||
|
||||
@@ -15,7 +15,7 @@ export async function GaussianDensityCPU(ctx: RuntimeContext, position: Position
|
||||
const { resolution, radiusOffset, smoothness } = props
|
||||
const scaleFactor = 1 / resolution
|
||||
|
||||
const { indices, x, y, z } = position
|
||||
const { indices, x, y, z, id } = position
|
||||
const n = OrderedSet.size(indices)
|
||||
const radii = new Float32Array(n)
|
||||
|
||||
@@ -100,7 +100,7 @@ export async function GaussianDensityCPU(ctx: RuntimeContext, position: Position
|
||||
data[idx] += dens
|
||||
if (dens > densData[idx]) {
|
||||
densData[idx] = dens
|
||||
idData[idx] = i
|
||||
idData[idx] = id ? id[i] : i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ function prepareGaussianDensityData(position: PositionData, box: Box3D, radius:
|
||||
const { resolution, radiusOffset } = props
|
||||
const scaleFactor = 1 / resolution
|
||||
|
||||
const { indices, x, y, z } = position
|
||||
const { indices, x, y, z, id } = position
|
||||
const n = OrderedSet.size(indices)
|
||||
|
||||
const positions = new Float32Array(n * 3)
|
||||
@@ -242,7 +242,7 @@ function prepareGaussianDensityData(position: PositionData, box: Box3D, radius:
|
||||
const r = radius(j) + radiusOffset
|
||||
if (maxRadius < r) maxRadius = r
|
||||
radii[i] = r
|
||||
groups[i] = i
|
||||
groups[i] = id ? id[i] : i
|
||||
}
|
||||
|
||||
const pad = maxRadius * 2 + resolution * 4
|
||||
|
||||
@@ -169,7 +169,7 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
const dd = rad - d
|
||||
if (dd < data[idx]) {
|
||||
data[idx] = dd
|
||||
idData[idx] = i
|
||||
idData[idx] = id[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,7 +282,7 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
// Is this grid point closer to a or b?
|
||||
// Take dot product of atob and gridpoint->p (dx, dy, dz)
|
||||
const dp = dx * atob[0] + dy * atob[1] + dz * atob[2]
|
||||
idData[idx] = OrderedSet.indexOf(position.indices, dp < 0.0 ? b : a)
|
||||
idData[idx] = id[OrderedSet.indexOf(position.indices, dp < 0.0 ? b : a)]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,16 +291,22 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
}
|
||||
}
|
||||
|
||||
async function projectTorii () {
|
||||
for (let i = 0; i < n; ++i) {
|
||||
function projectToriiRange (begI: number, endI: number) {
|
||||
for (let i = begI; i < endI; ++i) {
|
||||
const k = OrderedSet.getAt(indices, i)
|
||||
lookup3d.find(px[k], py[k], pz[k], radius[k])
|
||||
for (let j = 0, jl = neighbours.count; j < jl; ++j) {
|
||||
const l = OrderedSet.getAt(indices, neighbours.indices[j])
|
||||
if (k < l) projectTorus(k, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i % updateChunk === 0 && ctx.shouldUpdate) {
|
||||
async function projectTorii() {
|
||||
for (let i = 0; i < n; i += updateChunk) {
|
||||
projectToriiRange(i, Math.min(i + updateChunk, n))
|
||||
|
||||
if (ctx.shouldUpdate) {
|
||||
await ctx.update({ message: 'projecting torii', current: i, max: n })
|
||||
}
|
||||
}
|
||||
@@ -317,7 +323,7 @@ export async function calcMolecularSurface(ctx: RuntimeContext, position: Requir
|
||||
const lookup3d = GridLookup3D(position, cellSize)
|
||||
const neighbours = lookup3d.result
|
||||
const box = lookup3d.boundary.box
|
||||
const { indices, x: px, y: py, z: pz, radius } = position
|
||||
const { indices, x: px, y: py, z: pz, id, radius } = position
|
||||
const n = OrderedSet.size(indices)
|
||||
|
||||
const pad = maxRadius * 2 + resolution
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Spacegroup {
|
||||
|
||||
export function getSymmetryOperator(spacegroup: Spacegroup, index: number, i: number, j: number, k: number): SymmetryOperator {
|
||||
const operator = updateOperatorMatrix(spacegroup, index, i, j, k, Mat4.zero());
|
||||
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k));
|
||||
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k), index);
|
||||
}
|
||||
|
||||
function getOperatorMatrix(ids: number[]) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { Vec3, Mat4, Mat3, Quat } from '../linear-algebra/3d'
|
||||
import { lerp as scalar_lerp } from '../../mol-math/interpolate';
|
||||
import { defaults } from '../../mol-util';
|
||||
|
||||
interface SymmetryOperator {
|
||||
readonly name: string,
|
||||
@@ -21,6 +22,8 @@ interface SymmetryOperator {
|
||||
readonly ncsId: string,
|
||||
|
||||
readonly hkl: Vec3,
|
||||
/** spacegroup symmetry operator index, -1 if not applicable */
|
||||
readonly spgrOp: number,
|
||||
|
||||
readonly matrix: Mat4,
|
||||
// cache the inverse of the transform
|
||||
@@ -35,12 +38,13 @@ namespace SymmetryOperator {
|
||||
|
||||
export const RotationTranslationEpsilon = 0.005;
|
||||
|
||||
export function create(name: string, matrix: Mat4, assembly: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3): SymmetryOperator {
|
||||
export function create(name: string, matrix: Mat4, assembly: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3, spgrOp?: number): SymmetryOperator {
|
||||
const _hkl = hkl ? Vec3.clone(hkl) : Vec3.zero();
|
||||
spgrOp = defaults(spgrOp, -1)
|
||||
ncsId = ncsId || ''
|
||||
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, ncsId };
|
||||
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId };
|
||||
if (!Mat4.isRotationAndTranslation(matrix, RotationTranslationEpsilon)) throw new Error(`Symmetry operator (${name}) must be a composition of rotation and translation.`);
|
||||
return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, ncsId };
|
||||
return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, spgrOp, ncsId };
|
||||
}
|
||||
|
||||
export function checkIfRotationAndTranslation(rot: Mat3, offset: Vec3) {
|
||||
@@ -106,11 +110,11 @@ namespace SymmetryOperator {
|
||||
|
||||
/**
|
||||
* Apply the 1st and then 2nd operator. ( = second.matrix * first.matrix).
|
||||
* Keep `name`, `assembly`, `ncsId` and `hkl` properties from second.
|
||||
* Keep `name`, `assembly`, `ncsId`, `hkl` and `spgrOpId` properties from second.
|
||||
*/
|
||||
export function compose(first: SymmetryOperator, second: SymmetryOperator) {
|
||||
const matrix = Mat4.mul(Mat4.zero(), second.matrix, first.matrix);
|
||||
return create(second.name, matrix, second.assembly, second.ncsId, second.hkl);
|
||||
return create(second.name, matrix, second.assembly, second.ncsId, second.hkl, second.spgrOp);
|
||||
}
|
||||
|
||||
export interface CoordinateMapper<T extends number> { (index: T, slot: Vec3): Vec3 }
|
||||
|
||||
@@ -435,6 +435,10 @@ namespace Quat {
|
||||
|
||||
return normalize(out, Quat.fromMat3(out, axesTmpMat));
|
||||
}
|
||||
|
||||
export function toString(a: Quat, precision?: number) {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)} ${a[3].toPrecision(precision)}]`;
|
||||
}
|
||||
}
|
||||
|
||||
export default Quat
|
||||
@@ -166,6 +166,10 @@ namespace Vec2 {
|
||||
export function areEqual(a: Vec2, b: Vec2) {
|
||||
return a[0] === b[0] && a[1] === b[1];
|
||||
}
|
||||
|
||||
export function toString(a: Vec2, precision?: number) {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)}}]`;
|
||||
}
|
||||
}
|
||||
|
||||
export default Vec2
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import Mat4 from './mat4';
|
||||
import { Quat, Mat3, EPSILON } from '../3d';
|
||||
import { spline as _spline, clamp } from '../../interpolate'
|
||||
import { spline as _spline, quadraticBezier as _quadraticBezier, clamp } from '../../interpolate'
|
||||
import { NumberArray } from '../../../mol-util/type-helpers';
|
||||
|
||||
interface Vec3 extends Array<number> { [d: number]: number, '@type': 'vec3', length: 3 }
|
||||
@@ -344,6 +344,14 @@ namespace Vec3 {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function quadraticBezier(out: Vec3, a: Vec3, b: Vec3, c: Vec3, t: number) {
|
||||
out[0] = _quadraticBezier(a[0], b[0], c[0], t);
|
||||
out[1] = _quadraticBezier(a[1], b[1], c[1], t);
|
||||
out[2] = _quadraticBezier(a[2], b[2], c[2], t);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a spline interpolation with two control points and a tension parameter
|
||||
*/
|
||||
@@ -525,8 +533,8 @@ namespace Vec3 {
|
||||
return normalize(out, cross(out, triangleNormalTmpAB, triangleNormalTmpAC));
|
||||
}
|
||||
|
||||
export function toString(a: Vec3) {
|
||||
return `[${a[0]} ${a[1]} ${a[2]}]`;
|
||||
export function toString(a: Vec3, precision?: number) {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)}]`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -225,6 +225,10 @@ namespace Vec4 {
|
||||
Math.abs(a2 - b2) <= EPSILON.Value * Math.max(1.0, Math.abs(a2), Math.abs(b2)) &&
|
||||
Math.abs(a3 - b3) <= EPSILON.Value * Math.max(1.0, Math.abs(a3), Math.abs(b3)));
|
||||
}
|
||||
|
||||
export function toString(a: Vec4, precision?: number) {
|
||||
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)} ${a[3].toPrecision(precision)}]`;
|
||||
}
|
||||
}
|
||||
|
||||
export default Vec4
|
||||
@@ -16,7 +16,7 @@ import { ChunkedArray } from '../../mol-data/util';
|
||||
import { arrayMax, fillSerial } from '../../mol-util/array';
|
||||
import { Column } from '../../mol-data/db';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../mol-util/color/tables';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { deepClone } from '../../mol-util/object';
|
||||
|
||||
// TODO support 'edge' element, see https://www.mathworks.com/help/vision/ug/the-ply-format.html
|
||||
|
||||
156
src/mol-model-formats/structure/common/component.ts
Normal file
156
src/mol-model-formats/structure/common/component.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { _parse_mmCif } from '../mmcif/parser';
|
||||
import { CifCategory, CifField } from '../../../mol-io/reader/cif';
|
||||
import { Table, Column } from '../../../mol-data/db';
|
||||
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { WaterNames } from '../../../mol-model/structure/model/types';
|
||||
import { SetUtils } from '../../../mol-util/set';
|
||||
|
||||
type Component = Table.Row<Pick<mmCIF_Schema['chem_comp'], 'id' | 'name' | 'type'>>
|
||||
|
||||
const ProteinAtomIdsList = [
|
||||
new Set([ 'CA' ]),
|
||||
new Set([ 'C' ]),
|
||||
new Set([ 'N' ])
|
||||
]
|
||||
const RnaAtomIdsList = [
|
||||
new Set([ 'P', 'O3\'', 'O3*' ]),
|
||||
new Set([ 'C4\'', 'C4*' ]),
|
||||
new Set([ 'O2\'', 'O2*', 'F2\'', 'F2*' ])
|
||||
]
|
||||
const DnaAtomIdsList = [
|
||||
new Set([ 'P', 'O3\'', 'O3*' ]),
|
||||
new Set([ 'C3\'', 'C3*' ]),
|
||||
new Set([ 'O2\'', 'O2*', 'F2\'', 'F2*' ])
|
||||
]
|
||||
|
||||
const StandardComponents = (function() {
|
||||
const map = new Map<string, Component>()
|
||||
const components: Component[] = [
|
||||
{ id: 'HIS', name: 'HISTIDINE', type: 'L-peptide linking' },
|
||||
{ id: 'ARG', name: 'ARGININE', type: 'L-peptide linking' },
|
||||
{ id: 'LYS', name: 'LYSINE', type: 'L-peptide linking' },
|
||||
{ id: 'ILE', name: 'ISOLEUCINE', type: 'L-peptide linking' },
|
||||
{ id: 'PHE', name: 'PHENYLALANINE', type: 'L-peptide linking' },
|
||||
{ id: 'LEU', name: 'LEUCINE', type: 'L-peptide linking' },
|
||||
{ id: 'TRP', name: 'TRYPTOPHAN', type: 'L-peptide linking' },
|
||||
{ id: 'ALA', name: 'ALANINE', type: 'L-peptide linking' },
|
||||
{ id: 'MET', name: 'METHIONINE', type: 'L-peptide linking' },
|
||||
{ id: 'CYS', name: 'CYSTEINE', type: 'L-peptide linking' },
|
||||
{ id: 'ASN', name: 'ASPARAGINE', type: 'L-peptide linking' },
|
||||
{ id: 'VAL', name: 'VALINE', type: 'L-peptide linking' },
|
||||
{ id: 'GLY', name: 'GLYCINE', type: 'peptide linking' },
|
||||
{ id: 'SER', name: 'SERINE', type: 'L-peptide linking' },
|
||||
{ id: 'GLN', name: 'GLUTAMINE', type: 'L-peptide linking' },
|
||||
{ id: 'TYR', name: 'TYROSINE', type: 'L-peptide linking' },
|
||||
{ id: 'ASP', name: 'ASPARTIC ACID', type: 'L-peptide linking' },
|
||||
{ id: 'GLU', name: 'GLUTAMIC ACID', type: 'L-peptide linking' },
|
||||
{ id: 'THR', name: 'THREONINE', type: 'L-peptide linking' },
|
||||
{ id: 'SEC', name: 'SELENOCYSTEINE', type: 'L-peptide linking' },
|
||||
{ id: 'PYL', name: 'PYRROLYSINE', type: 'L-peptide linking' },
|
||||
|
||||
{ id: 'A', name: 'ADENOSINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
|
||||
{ id: 'C', name: 'CYTIDINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
|
||||
{ id: 'T', name: 'THYMIDINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
|
||||
{ id: 'G', name: 'GUANOSINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
|
||||
{ id: 'I', name: 'INOSINIC ACID', type: 'RNA linking' },
|
||||
{ id: 'U', name: 'URIDINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
|
||||
|
||||
{ id: 'DA', name: '2\'-DEOXYADENOSINE-5\'-MONOPHOSPHATE', type: 'DNA linking' },
|
||||
{ id: 'DC', name: '2\'-DEOXYCYTIDINE-5\'-MONOPHOSPHATE', type: 'DNA linking' },
|
||||
{ id: 'DT', name: 'THYMIDINE-5\'-MONOPHOSPHATE', type: 'DNA linking' },
|
||||
{ id: 'DG', name: '2\'-DEOXYGUANOSINE-5\'-MONOPHOSPHATE', type: 'DNA linking' },
|
||||
{ id: 'DI', name: '2\'-DEOXYINOSINE-5\'-MONOPHOSPHATE', type: 'DNA linking' },
|
||||
{ id: 'DU', name: '2\'-DEOXYURIDINE-5\'-MONOPHOSPHATE', type: 'DNA linking' },
|
||||
]
|
||||
components.forEach(c => map.set(c.id, c))
|
||||
return map
|
||||
})()
|
||||
|
||||
export class ComponentBuilder {
|
||||
private namesMap = new Map<string, string>()
|
||||
private comps = new Map<string, Component>()
|
||||
private ids: string[] = []
|
||||
private names: string[] = []
|
||||
private types: mmCIF_Schema['chem_comp']['type']['T'][] = []
|
||||
|
||||
private set(c: Component) {
|
||||
this.comps.set(c.id, c)
|
||||
this.ids.push(c.id)
|
||||
this.names.push(c.name)
|
||||
this.types.push(c.type)
|
||||
}
|
||||
|
||||
private getAtomIds(index: number) {
|
||||
const atomIds = new Set<string>()
|
||||
let prevSeqId = this.seqId.value(index)
|
||||
while (index < this.seqId.rowCount) {
|
||||
const seqId = this.seqId.value(index)
|
||||
if (seqId !== prevSeqId) break
|
||||
atomIds.add(this.atomId.value(index))
|
||||
prevSeqId - seqId
|
||||
index += 1
|
||||
}
|
||||
return atomIds
|
||||
}
|
||||
|
||||
private hasAtomIds (atomIds: Set<string>, atomIdsList: Set<string>[]) {
|
||||
for (let i = 0, il = atomIdsList.length; i < il; ++i) {
|
||||
if (!SetUtils.areIntersecting(atomIds, atomIdsList[i])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private getType(atomIds: Set<string>): Component['type'] {
|
||||
if (this.hasAtomIds(atomIds, ProteinAtomIdsList)) {
|
||||
return 'peptide linking'
|
||||
} else if (this.hasAtomIds(atomIds, RnaAtomIdsList)) {
|
||||
return 'RNA linking'
|
||||
} else if (this.hasAtomIds(atomIds, DnaAtomIdsList)) {
|
||||
return 'DNA linking'
|
||||
} else {
|
||||
return 'other'
|
||||
}
|
||||
}
|
||||
|
||||
has(compId: string) { return this.comps.has(compId) }
|
||||
get(compId: string) { return this.comps.get(compId) }
|
||||
|
||||
add(compId: string, index: number) {
|
||||
if (!this.has(compId)) {
|
||||
if (StandardComponents.has(compId)) {
|
||||
this.set(StandardComponents.get(compId)!)
|
||||
} else if (WaterNames.has(compId)) {
|
||||
this.set({ id: compId, name: 'WATER', type: 'non-polymer' })
|
||||
} else {
|
||||
const type = this.getType(this.getAtomIds(index))
|
||||
this.set({ id: compId, name: this.namesMap.get(compId) || compId, type })
|
||||
}
|
||||
}
|
||||
return this.get(compId)!
|
||||
}
|
||||
|
||||
getChemCompCategory() {
|
||||
const chemComp: CifCategory.SomeFields<mmCIF_Schema['chem_comp']> = {
|
||||
id: CifField.ofStrings(this.ids),
|
||||
name: CifField.ofStrings(this.names),
|
||||
type: CifField.ofStrings(this.types),
|
||||
}
|
||||
return CifCategory.ofFields('chem_comp', chemComp)
|
||||
}
|
||||
|
||||
setNames(names: [string, string][]) {
|
||||
names.forEach(n => this.namesMap.set(n[0], n[1]))
|
||||
}
|
||||
|
||||
constructor(private seqId: Column<number>, private atomId: Column<string>) {
|
||||
|
||||
}
|
||||
}
|
||||
80
src/mol-model-formats/structure/common/entity.ts
Normal file
80
src/mol-model-formats/structure/common/entity.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CifCategory, CifField } from '../../../mol-io/reader/cif';
|
||||
import { MoleculeType, isPolymer } from '../../../mol-model/structure/model/types';
|
||||
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
|
||||
|
||||
export type EntityCompound = { chains: string[], description: string }
|
||||
|
||||
export class EntityBuilder {
|
||||
private count = 0
|
||||
private ids: string[] = []
|
||||
private types: string[] = []
|
||||
private descriptions: string[] = []
|
||||
|
||||
private compoundsMap = new Map<string, string>()
|
||||
private namesMap = new Map<string, string>()
|
||||
private heteroMap = new Map<string, string>()
|
||||
private chainMap = new Map<string, string>()
|
||||
private waterId?: string
|
||||
|
||||
private set(type: string, description: string) {
|
||||
this.count += 1
|
||||
this.ids.push(`${this.count}`)
|
||||
this.types.push(type)
|
||||
this.descriptions.push(description)
|
||||
}
|
||||
|
||||
getEntityId(compId: string, moleculeType: MoleculeType, chainId: string): string {
|
||||
if (moleculeType === MoleculeType.water) {
|
||||
if (this.waterId === undefined) {
|
||||
this.set('water', 'Water')
|
||||
this.waterId = `${this.count}`
|
||||
}
|
||||
return this.waterId;
|
||||
} else if (isPolymer(moleculeType)) {
|
||||
if (this.compoundsMap.has(chainId)) {
|
||||
return this.compoundsMap.get(chainId)!
|
||||
} else {
|
||||
if (!this.chainMap.has(chainId)) {
|
||||
this.set('polymer', `Polymer ${this.chainMap.size + 1}`)
|
||||
this.chainMap.set(chainId, `${this.count}`)
|
||||
}
|
||||
return this.chainMap.get(chainId)!
|
||||
}
|
||||
} else {
|
||||
if (!this.heteroMap.has(compId)) {
|
||||
this.set('non-polymer', this.namesMap.get(compId) || compId)
|
||||
this.heteroMap.set(compId, `${this.count}`)
|
||||
}
|
||||
return this.heteroMap.get(compId)!
|
||||
}
|
||||
}
|
||||
|
||||
getEntityCategory() {
|
||||
const entity: CifCategory.SomeFields<mmCIF_Schema['entity']> = {
|
||||
id: CifField.ofStrings(this.ids),
|
||||
type: CifField.ofStrings(this.types),
|
||||
pdbx_description: CifField.ofStrings(this.descriptions),
|
||||
}
|
||||
return CifCategory.ofFields('entity', entity)
|
||||
}
|
||||
|
||||
setCompounds(compounds: EntityCompound[]) {
|
||||
for (let i = 0, il = compounds.length; i < il; ++i) {
|
||||
const { chains, description } = compounds[i]
|
||||
this.set('polymer', description)
|
||||
for (let j = 0, jl = chains.length; j < jl; ++j) {
|
||||
this.compoundsMap.set(chains[j], `${this.count}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setNames(names: [string, string][]) {
|
||||
names.forEach(n => this.namesMap.set(n[0], n[1]))
|
||||
}
|
||||
}
|
||||
27
src/mol-model-formats/structure/common/util.ts
Normal file
27
src/mol-model-formats/structure/common/util.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { memoize1 } from '../../../mol-util/memoize';
|
||||
|
||||
const ChainIdAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
|
||||
function _getChainId (index: number) {
|
||||
const n = ChainIdAlphabet.length
|
||||
let j = index
|
||||
let k = 0
|
||||
let chainId = ChainIdAlphabet[j % n]
|
||||
while (j >= n) {
|
||||
j = Math.floor(j / n)
|
||||
chainId += ChainIdAlphabet[j % n]
|
||||
k += 1
|
||||
}
|
||||
if (k >= 5) {
|
||||
console.warn('getChainId overflow')
|
||||
}
|
||||
return chainId
|
||||
}
|
||||
|
||||
export const getChainId = memoize1(_getChainId)
|
||||
@@ -13,62 +13,103 @@ import { CifCategory, CifField } from '../../mol-io/reader/cif';
|
||||
import { Column } from '../../mol-data/db';
|
||||
import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
|
||||
import { guessElementSymbolString } from './util';
|
||||
import { MoleculeType, getMoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { ComponentBuilder } from './common/component';
|
||||
import { getChainId } from './common/util';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
|
||||
// TODO multi model files
|
||||
// TODO seperate chains
|
||||
// TODO better entity handling
|
||||
// TODO improve performance
|
||||
|
||||
function _entity(): { [K in keyof mmCIF_Schema['entity']]?: CifField } {
|
||||
return {
|
||||
id: CifField.ofStrings(['1', '2', '3']),
|
||||
type: CifField.ofStrings(['polymer', 'non-polymer', 'water'])
|
||||
}
|
||||
}
|
||||
|
||||
function _atom_site(atoms: GroAtoms): { [K in keyof mmCIF_Schema['atom_site']]?: CifField } {
|
||||
const auth_asym_id = CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str))
|
||||
function getCategories(atoms: GroAtoms) {
|
||||
const auth_atom_id = CifField.ofColumn(atoms.atomName)
|
||||
const auth_comp_id = CifField.ofColumn(atoms.residueName)
|
||||
const auth_seq_id = CifField.ofColumn(atoms.residueNumber)
|
||||
|
||||
return {
|
||||
const entityIds = new Array<string>(atoms.count)
|
||||
const asymIds = new Array<string>(atoms.count)
|
||||
const seqIds = new Uint32Array(atoms.count)
|
||||
const ids = new Uint32Array(atoms.count)
|
||||
|
||||
const entityBuilder = new EntityBuilder()
|
||||
const componentBuilder = new ComponentBuilder(atoms.residueNumber, atoms.atomName)
|
||||
|
||||
let currentEntityId = ''
|
||||
let currentAsymIndex = 0
|
||||
let currentAsymId = ''
|
||||
let currentSeqId = 0
|
||||
let prevMoleculeType = MoleculeType.unknown
|
||||
let prevResidueNumber = -1
|
||||
|
||||
for (let i = 0, il = atoms.count; i < il; ++i) {
|
||||
const residueNumber = atoms.residueNumber.value(i)
|
||||
if (residueNumber !== prevResidueNumber) {
|
||||
const compId = atoms.residueName.value(i)
|
||||
const moleculeType = getMoleculeType(componentBuilder.add(compId, i).type, compId)
|
||||
|
||||
if (moleculeType !== prevMoleculeType || (
|
||||
residueNumber !== prevResidueNumber + 1 && !(
|
||||
// gro format allows only for 5 character residueNumbers, handle overflow here
|
||||
prevResidueNumber === 99999 && residueNumber === 0
|
||||
)
|
||||
)) {
|
||||
currentAsymId = getChainId(currentAsymIndex)
|
||||
currentAsymIndex += 1
|
||||
currentSeqId = 0
|
||||
}
|
||||
|
||||
currentEntityId = entityBuilder.getEntityId(compId, moleculeType, currentAsymId)
|
||||
currentSeqId += 1
|
||||
|
||||
prevResidueNumber = residueNumber
|
||||
prevMoleculeType = moleculeType
|
||||
}
|
||||
|
||||
entityIds[i] = currentEntityId
|
||||
asymIds[i] = currentAsymId
|
||||
seqIds[i] = currentSeqId
|
||||
ids[i] = i
|
||||
}
|
||||
|
||||
const auth_asym_id = CifField.ofColumn(Column.ofStringArray(asymIds))
|
||||
|
||||
const atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = {
|
||||
auth_asym_id,
|
||||
auth_atom_id,
|
||||
auth_comp_id,
|
||||
auth_seq_id,
|
||||
auth_seq_id: CifField.ofColumn(atoms.residueNumber),
|
||||
B_iso_or_equiv: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.float)),
|
||||
Cartn_x: CifField.ofNumbers(Column.mapToArray(atoms.x, x => x * 10, Float32Array)),
|
||||
Cartn_y: CifField.ofNumbers(Column.mapToArray(atoms.y, y => y * 10, Float32Array)),
|
||||
Cartn_z: CifField.ofNumbers(Column.mapToArray(atoms.z, z => z * 10, Float32Array)),
|
||||
group_PDB: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
|
||||
id: CifField.ofColumn(atoms.atomNumber),
|
||||
id: CifField.ofColumn(Column.ofIntArray(ids)),
|
||||
|
||||
label_alt_id: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
|
||||
|
||||
label_asym_id: auth_asym_id,
|
||||
label_atom_id: auth_atom_id,
|
||||
label_comp_id: auth_comp_id,
|
||||
label_seq_id: auth_seq_id,
|
||||
label_entity_id: CifField.ofColumn(Column.ofConst('1', atoms.count, Column.Schema.str)),
|
||||
label_seq_id: CifField.ofColumn(Column.ofIntArray(seqIds)),
|
||||
label_entity_id: CifField.ofColumn(Column.ofStringArray(entityIds)),
|
||||
|
||||
occupancy: CifField.ofColumn(Column.ofConst(1, atoms.count, Column.Schema.float)),
|
||||
type_symbol: CifField.ofStrings(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))),
|
||||
// type_symbol: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
|
||||
|
||||
pdbx_PDB_ins_code: CifField.ofColumn(Column.Undefined(atoms.count, Column.Schema.str)),
|
||||
pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst('1', atoms.count, Column.Schema.str)),
|
||||
}
|
||||
|
||||
return {
|
||||
entity: entityBuilder.getEntityCategory(),
|
||||
chem_comp: componentBuilder.getChemCompCategory(),
|
||||
atom_site: CifCategory.ofFields('atom_site', atom_site)
|
||||
}
|
||||
}
|
||||
|
||||
async function groToMmCif(gro: GroFile) {
|
||||
const categories = {
|
||||
entity: CifCategory.ofFields('entity', _entity()),
|
||||
atom_site: CifCategory.ofFields('atom_site', _atom_site(gro.structures[0].atoms))
|
||||
} as any;
|
||||
const categories = getCategories(gro.structures[0].atoms)
|
||||
|
||||
return {
|
||||
header: 'GRO',
|
||||
header: gro.structures[0].header.title,
|
||||
categoryNames: Object.keys(categories),
|
||||
categories
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Tensor, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
import UUID from '../../../mol-util/uuid';
|
||||
import { Model } from '../../../mol-model/structure/model/model';
|
||||
import { Entities } from '../../../mol-model/structure/model/properties/common';
|
||||
import { Entities, ChemicalComponent, MissingResidue } from '../../../mol-model/structure/model/properties/common';
|
||||
import { CustomProperties } from '../../../mol-model/structure';
|
||||
import { ModelSymmetry } from '../../../mol-model/structure/model/properties/symmetry';
|
||||
import { createAssemblies } from './assembly';
|
||||
@@ -23,7 +23,6 @@ import { getSecondaryStructure } from './secondary-structure';
|
||||
import { getSequence } from './sequence';
|
||||
import { sortAtomSite } from './sort';
|
||||
import { StructConn } from './bonds/struct_conn';
|
||||
import { ChemicalComponent } from '../../../mol-model/structure/model/properties/chemical-component';
|
||||
import { getMoleculeType, MoleculeType, getEntityType } from '../../../mol-model/structure/model/types';
|
||||
import { ModelFormat } from '../format';
|
||||
import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from '../../../mol-model/structure/structure/carbohydrates/constants';
|
||||
@@ -98,14 +97,36 @@ function getModifiedResidueNameMap(format: mmCIF_Format): Model['properties']['m
|
||||
return { parentId, details };
|
||||
}
|
||||
|
||||
function getMissingResidues(format: mmCIF_Format): Model['properties']['missingResidues'] {
|
||||
const map = new Map<string, MissingResidue>();
|
||||
const c = format.data.pdbx_unobs_or_zero_occ_residues
|
||||
|
||||
const getKey = (model_num: number, asym_id: string, seq_id: number) => {
|
||||
return `${model_num}|${asym_id}|${seq_id}`
|
||||
}
|
||||
|
||||
for (let i = 0, il = c._rowCount; i < il; ++i) {
|
||||
const key = getKey(c.PDB_model_num.value(i), c.label_asym_id.value(i), c.label_seq_id.value(i))
|
||||
map.set(key, { polymer_flag: c.polymer_flag.value(i), occupancy_flag: c.occupancy_flag.value(i) })
|
||||
}
|
||||
|
||||
return {
|
||||
has: (model_num: number, asym_id: string, seq_id: number) => {
|
||||
return map.has(getKey(model_num, asym_id, seq_id))
|
||||
},
|
||||
get: (model_num: number, asym_id: string, seq_id: number) => {
|
||||
return map.get(getKey(model_num, asym_id, seq_id))
|
||||
},
|
||||
size: map.size
|
||||
}
|
||||
}
|
||||
|
||||
function getChemicalComponentMap(format: mmCIF_Format): Model['properties']['chemicalComponentMap'] {
|
||||
const map = new Map<string, ChemicalComponent>();
|
||||
const { chem_comp } = format.data
|
||||
if (chem_comp._rowCount > 0) {
|
||||
const { id } = format.data.chem_comp
|
||||
for (let i = 0, il = id.rowCount; i < il; ++i) {
|
||||
map.set(id.value(i), Table.getRow(format.data.chem_comp, i))
|
||||
}
|
||||
const { id } = chem_comp
|
||||
for (let i = 0, il = id.rowCount; i < il; ++i) {
|
||||
map.set(id.value(i), Table.getRow(chem_comp, i))
|
||||
}
|
||||
return map
|
||||
}
|
||||
@@ -158,6 +179,7 @@ const getUniqueComponentNames = memoize1((format: mmCIF_Format) => {
|
||||
|
||||
export interface FormatData {
|
||||
modifiedResidues: Model['properties']['modifiedResidues']
|
||||
missingResidues: Model['properties']['missingResidues']
|
||||
chemicalComponentMap: Model['properties']['chemicalComponentMap']
|
||||
saccharideComponentMap: Model['properties']['saccharideComponentMap']
|
||||
}
|
||||
@@ -165,6 +187,7 @@ export interface FormatData {
|
||||
function getFormatData(format: mmCIF_Format): FormatData {
|
||||
return {
|
||||
modifiedResidues: getModifiedResidueNameMap(format),
|
||||
missingResidues: getMissingResidues(format),
|
||||
chemicalComponentMap: getChemicalComponentMap(format),
|
||||
saccharideComponentMap: getSaccharideComponentMap(format)
|
||||
}
|
||||
@@ -172,27 +195,32 @@ function getFormatData(format: mmCIF_Format): FormatData {
|
||||
|
||||
function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, formatData: FormatData, previous?: Model): Model {
|
||||
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, formatData, previous);
|
||||
const modelNum = atom_site.pdbx_PDB_model_num.value(0)
|
||||
if (previous && atomic.sameAsPrevious) {
|
||||
return {
|
||||
...previous,
|
||||
id: UUID.create22(),
|
||||
modelNum: atom_site.pdbx_PDB_model_num.value(0),
|
||||
modelNum,
|
||||
atomicConformation: atomic.conformation,
|
||||
_dynamicPropertyData: Object.create(null)
|
||||
};
|
||||
}
|
||||
|
||||
const coarse = EmptyIHMCoarse;
|
||||
const label = format.data.entry.id.valueKind(0) === Column.ValueKind.Present
|
||||
const entry = format.data.entry.id.valueKind(0) === Column.ValueKind.Present
|
||||
? format.data.entry.id.value(0)
|
||||
: format.data._name;
|
||||
|
||||
const label: string[] = []
|
||||
if (entry) label.push(entry)
|
||||
if (format.data.struct.title.valueKind(0) === Column.ValueKind.Present) label.push(format.data.struct.title.value(0))
|
||||
|
||||
return {
|
||||
id: UUID.create22(),
|
||||
label,
|
||||
entry: label,
|
||||
label: label.join(' | '),
|
||||
entry,
|
||||
sourceData: format,
|
||||
modelNum: atom_site.pdbx_PDB_model_num.value(0),
|
||||
modelNum,
|
||||
entities,
|
||||
symmetry: getSymmetry(format),
|
||||
sequence: getSequence(format.data, entities, atomic.hierarchy, formatData.modifiedResidues.parentId),
|
||||
@@ -216,11 +244,16 @@ function createModelIHM(format: mmCIF_Format, data: IHMData, formatData: FormatD
|
||||
const entry = format.data.entry.id.valueKind(0) === Column.ValueKind.Present
|
||||
? format.data.entry.id.value(0)
|
||||
: format.data._name;
|
||||
const label = data.model_group_name ? `${data.model_name}: ${data.model_group_name}` : data.model_name
|
||||
|
||||
const label: string[] = []
|
||||
if (entry) label.push(entry)
|
||||
if (format.data.struct.title.valueKind(0) === Column.ValueKind.Present) label.push(format.data.struct.title.value(0))
|
||||
if (data.model_group_name) label.push(data.model_name)
|
||||
if (data.model_group_name) label.push(data.model_group_name)
|
||||
|
||||
return {
|
||||
id: UUID.create22(),
|
||||
label,
|
||||
label: label.join(' | '),
|
||||
entry,
|
||||
sourceData: format,
|
||||
modelNum: data.model_id,
|
||||
@@ -259,14 +292,16 @@ function getEntities(format: mmCIF_Format): Entities {
|
||||
|
||||
if (!format.data.entity.id.isDefined) {
|
||||
const entityIds = new Set<string>()
|
||||
const entityList: Partial<Table.Row<mmCIF_Schema['entity']>>[] = []
|
||||
|
||||
const ids: mmCIF_Schema['entity']['id']['T'][] = []
|
||||
const types: mmCIF_Schema['entity']['type']['T'][] = []
|
||||
|
||||
const { label_entity_id, label_comp_id } = format.data.atom_site;
|
||||
for (let i = 0 as ElementIndex, il = format.data.atom_site._rowCount; i < il; i++) {
|
||||
const entityId = label_entity_id.value(i);
|
||||
if (!entityIds.has(entityId)) {
|
||||
entityList.push({ id: entityId, type: getEntityType(label_comp_id.value(i)) })
|
||||
entityIds.add(entityId)
|
||||
ids.push(entityId)
|
||||
types.push(getEntityType(label_comp_id.value(i)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,8 +309,8 @@ function getEntities(format: mmCIF_Format): Entities {
|
||||
for (let i = 0 as ElementIndex, il = format.data.ihm_sphere_obj_site._rowCount; i < il; i++) {
|
||||
const entityId = sphere_entity_id.value(i);
|
||||
if (!entityIds.has(entityId)) {
|
||||
entityList.push({ id: entityId, type: 'polymer' })
|
||||
entityIds.add(entityId)
|
||||
ids.push(entityId)
|
||||
types.push('polymer')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,12 +318,16 @@ function getEntities(format: mmCIF_Format): Entities {
|
||||
for (let i = 0 as ElementIndex, il = format.data.ihm_gaussian_obj_site._rowCount; i < il; i++) {
|
||||
const entityId = gaussian_entity_id.value(i);
|
||||
if (!entityIds.has(entityId)) {
|
||||
entityList.push({ id: entityId, type: 'polymer' })
|
||||
entityIds.add(entityId)
|
||||
ids.push(entityId)
|
||||
types.push('polymer')
|
||||
}
|
||||
}
|
||||
|
||||
entityData = Table.ofRows(mmCIF_Schema.entity, entityList)
|
||||
entityData = Table.ofColumns(mmCIF_Schema.entity, {
|
||||
...format.data.entity,
|
||||
id: Column.ofArray({ array: ids, schema: mmCIF_Schema.entity.id }),
|
||||
type: Column.ofArray({ array: types, schema: mmCIF_Schema.entity.type }),
|
||||
})
|
||||
} else {
|
||||
entityData = format.data.entity;
|
||||
}
|
||||
|
||||
95
src/mol-model-formats/structure/pdb/entity.ts
Normal file
95
src/mol-model-formats/structure/pdb/entity.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Tokens } from '../../../mol-io/reader/common/text/tokenizer';
|
||||
import { EntityCompound } from '../common/entity';
|
||||
|
||||
const Spec = {
|
||||
'MOL_ID': '',
|
||||
'MOLECULE': '',
|
||||
'CHAIN': '',
|
||||
'FRAGMENT': '',
|
||||
'SYNONYM': '',
|
||||
'EC': '',
|
||||
'ENGINEERED': '',
|
||||
'MUTATION': '',
|
||||
'OTHER_DETAILS': ''
|
||||
}
|
||||
type Spec = keyof typeof Spec
|
||||
|
||||
export function parseCmpnd(lines: Tokens, lineStart: number, lineEnd: number) {
|
||||
const getLine = (n: number) => lines.data.substring(lines.indices[2 * n], lines.indices[2 * n + 1])
|
||||
|
||||
let currentSpec: Spec | undefined
|
||||
let currentCompound: EntityCompound = { chains: [], description: '' }
|
||||
const Compounds: EntityCompound[] = []
|
||||
|
||||
for (let i = lineStart; i < lineEnd; i++) {
|
||||
let line = getLine(i)
|
||||
// COLUMNS DATA TYPE FIELD DEFINITION
|
||||
// ----------------------------------------------------------------------------------
|
||||
// 1 - 6 Record name "COMPND"
|
||||
// 8 - 10 Continuation continuation Allows concatenation of multiple records.
|
||||
// 11 - 80 Specification compound Description of the molecular components.
|
||||
// list
|
||||
|
||||
const cmpnd = line.substr(10, 70).trim()
|
||||
const cmpndSpecEnd = cmpnd.indexOf(':')
|
||||
const cmpndSpec = cmpnd.substring(0, cmpndSpecEnd)
|
||||
|
||||
let value: string
|
||||
|
||||
if (cmpndSpec in Spec) {
|
||||
currentSpec = cmpndSpec as Spec
|
||||
value = cmpnd.substring(cmpndSpecEnd + 2)
|
||||
} else {
|
||||
value = cmpnd
|
||||
}
|
||||
value = value.replace(/;$/, '')
|
||||
|
||||
if (currentSpec === 'MOL_ID') {
|
||||
currentCompound = {
|
||||
chains: [],
|
||||
description: ''
|
||||
}
|
||||
Compounds.push(currentCompound)
|
||||
} else if (currentSpec === 'MOLECULE') {
|
||||
if (currentCompound.description) currentCompound.description += ' '
|
||||
currentCompound.description += value
|
||||
} else if (currentSpec === 'CHAIN') {
|
||||
Array.prototype.push.apply(currentCompound.chains, value.split(/\s*,\s*/))
|
||||
}
|
||||
}
|
||||
|
||||
return Compounds
|
||||
}
|
||||
|
||||
export function parseHetnam(lines: Tokens, lineStart: number, lineEnd: number) {
|
||||
const getLine = (n: number) => lines.data.substring(lines.indices[2 * n], lines.indices[2 * n + 1])
|
||||
|
||||
const hetnams = new Map<string, string>()
|
||||
|
||||
for (let i = lineStart; i < lineEnd; i++) {
|
||||
let line = getLine(i)
|
||||
// COLUMNS DATA TYPE FIELD DEFINITION
|
||||
// ----------------------------------------------------------------------------
|
||||
// 1 - 6 Record name "HETNAM"
|
||||
// 9 - 10 Continuation continuation Allows concatenation of multiple records.
|
||||
// 12 - 14 LString(3) hetID Het identifier, right-justified.
|
||||
// 16 - 70 String text Chemical name.
|
||||
|
||||
const het = line.substr(11, 3).trim()
|
||||
const name = line.substr(15).trim()
|
||||
|
||||
if (hetnams.has(het)) {
|
||||
hetnams.set(het, `${hetnams.get(het)!} ${name}`)
|
||||
} else {
|
||||
hetnams.set(het, name)
|
||||
}
|
||||
}
|
||||
|
||||
return hetnams
|
||||
}
|
||||
@@ -11,19 +11,16 @@ import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { TokenBuilder, Tokenizer } from '../../../mol-io/reader/common/text/tokenizer';
|
||||
import { PdbFile } from '../../../mol-io/reader/pdb/schema';
|
||||
import { parseCryst1, parseRemark350, parseMtrix } from './assembly';
|
||||
import { WaterNames } from '../../../mol-model/structure/model/types';
|
||||
import { parseHelix, parseSheet } from './secondary-structure';
|
||||
import { guessElementSymbolTokens } from '../util';
|
||||
import { parseCmpnd, parseHetnam } from './entity';
|
||||
import { ComponentBuilder } from '../common/component';
|
||||
import { EntityBuilder } from '../common/entity';
|
||||
import { Column } from '../../../mol-data/db';
|
||||
import { getMoleculeType } from '../../../mol-model/structure/model/types';
|
||||
|
||||
function _entity(): { [K in keyof mmCIF_Schema['entity']]?: CifField } {
|
||||
return {
|
||||
id: CifField.ofStrings(['1', '2', '3']),
|
||||
type: CifField.ofStrings(['polymer', 'non-polymer', 'water'])
|
||||
}
|
||||
}
|
||||
|
||||
type AtomSiteTemplate = typeof atom_site_template extends (...args: any) => infer T ? T : never
|
||||
function atom_site_template(data: string, count: number) {
|
||||
type AtomSiteTemplate = typeof getAtomSiteTemplate extends (...args: any) => infer T ? T : never
|
||||
function getAtomSiteTemplate(data: string, count: number) {
|
||||
const str = () => [] as string[];
|
||||
const ts = () => TokenBuilder.create(data, 2 * count);
|
||||
return {
|
||||
@@ -48,7 +45,7 @@ function atom_site_template(data: string, count: number) {
|
||||
};
|
||||
}
|
||||
|
||||
function _atom_site(sites: AtomSiteTemplate): { [K in keyof mmCIF_Schema['atom_site']]?: CifField } {
|
||||
function getAomSite(sites: AtomSiteTemplate): { [K in keyof mmCIF_Schema['atom_site']]?: CifField } {
|
||||
const auth_asym_id = CifField.ofTokens(sites.auth_asym_id);
|
||||
const auth_atom_id = CifField.ofTokens(sites.auth_atom_id);
|
||||
const auth_comp_id = CifField.ofTokens(sites.auth_comp_id);
|
||||
@@ -82,15 +79,7 @@ function _atom_site(sites: AtomSiteTemplate): { [K in keyof mmCIF_Schema['atom_s
|
||||
};
|
||||
}
|
||||
|
||||
function getEntityId(residueName: string, isHet: boolean) {
|
||||
if (isHet) {
|
||||
if (WaterNames.has(residueName)) return '3';
|
||||
return '2';
|
||||
}
|
||||
return '1';
|
||||
}
|
||||
|
||||
function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: number, e: number, isHet: boolean) {
|
||||
function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: number, e: number) {
|
||||
const { data: str } = data;
|
||||
const length = e - s;
|
||||
|
||||
@@ -118,7 +107,6 @@ function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: num
|
||||
|
||||
// 18 - 20 Residue name Residue name.
|
||||
TokenBuilder.addToken(sites.auth_comp_id, Tokenizer.trim(data, s + 17, s + 20));
|
||||
const residueName = str.substring(data.tokenStart, data.tokenEnd);
|
||||
|
||||
// 22 Character Chain identifier.
|
||||
TokenBuilder.add(sites.auth_asym_id, s + 21, s + 22);
|
||||
@@ -169,7 +157,6 @@ function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: num
|
||||
guessElementSymbolTokens(sites.type_symbol, str, s + 12, s + 16)
|
||||
}
|
||||
|
||||
sites.label_entity_id[sites.index] = getEntityId(residueName, isHet);
|
||||
sites.pdbx_PDB_model_num[sites.index] = model;
|
||||
|
||||
sites.index++;
|
||||
@@ -194,9 +181,10 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
}
|
||||
}
|
||||
|
||||
const atom_site = atom_site_template(data, atomCount);
|
||||
|
||||
const atomSite = getAtomSiteTemplate(data, atomCount);
|
||||
const entityBuilder = new EntityBuilder();
|
||||
const helperCategories: CifCategory[] = [];
|
||||
const heteroNames: [string, string][] = [];
|
||||
|
||||
let modelNum = 0, modelStr = '';
|
||||
|
||||
@@ -206,19 +194,28 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
case 'A':
|
||||
if (!substringStartsWith(data, s, e, 'ATOM ')) continue;
|
||||
if (!modelNum) { modelNum++; modelStr = '' + modelNum; }
|
||||
addAtom(atom_site, modelStr, tokenizer, s, e, false);
|
||||
addAtom(atomSite, modelStr, tokenizer, s, e);
|
||||
break;
|
||||
case 'C':
|
||||
if (substringStartsWith(data, s, e, 'CRYST1')) {
|
||||
helperCategories.push(...parseCryst1(pdb.id || '?', data.substring(s, e)));
|
||||
} else if (substringStartsWith(data, s, e, 'CONNECT')) {
|
||||
// TODO: CONNECT records => struct_conn
|
||||
} else if (substringStartsWith(data, s, e, 'COMPND')) {
|
||||
let j = i + 1;
|
||||
while (true) {
|
||||
s = indices[2 * j]; e = indices[2 * j + 1];
|
||||
if (!substringStartsWith(data, s, e, 'COMPND')) break;
|
||||
j++;
|
||||
}
|
||||
entityBuilder.setCompounds(parseCmpnd(lines, i, j))
|
||||
i = j - 1;
|
||||
}
|
||||
// TODO CONNECT records => struct_conn
|
||||
// TODO COMPND records => entity
|
||||
break;
|
||||
case 'H':
|
||||
if (substringStartsWith(data, s, e, 'HETATM')) {
|
||||
if (!modelNum) { modelNum++; modelStr = '' + modelNum; }
|
||||
addAtom(atom_site, modelStr, tokenizer, s, e, true);
|
||||
addAtom(atomSite, modelStr, tokenizer, s, e);
|
||||
} else if (substringStartsWith(data, s, e, 'HELIX')) {
|
||||
let j = i + 1;
|
||||
while (true) {
|
||||
@@ -228,8 +225,16 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
}
|
||||
helperCategories.push(parseHelix(lines, i, j));
|
||||
i = j - 1;
|
||||
} else if (substringStartsWith(data, s, e, 'HETNAM')) {
|
||||
let j = i + 1;
|
||||
while (true) {
|
||||
s = indices[2 * j]; e = indices[2 * j + 1];
|
||||
if (!substringStartsWith(data, s, e, 'HETNAM')) break;
|
||||
j++;
|
||||
}
|
||||
heteroNames.push(...Array.from(parseHetnam(lines, i, j).entries()))
|
||||
i = j - 1;
|
||||
}
|
||||
// TODO HETNAM records => chem_comp (at least partially, needs to be completed with common bases and amino acids)
|
||||
break;
|
||||
case 'M':
|
||||
if (substringStartsWith(data, s, e, 'MODEL ')) {
|
||||
@@ -246,10 +251,10 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
helperCategories.push(...parseMtrix(lines, i, j));
|
||||
i = j - 1;
|
||||
}
|
||||
// TODO MODRES records => pdbx_struct_mod_residue
|
||||
// TODO: MODRES records => pdbx_struct_mod_residue
|
||||
break;
|
||||
case 'O':
|
||||
// TODO ORIGX record => cif.database_PDB_matrix.origx, cif.database_PDB_matrix.origx_vector
|
||||
// TODO: ORIGX record => cif.database_PDB_matrix.origx, cif.database_PDB_matrix.origx_vector
|
||||
break;
|
||||
case 'R':
|
||||
if (substringStartsWith(data, s, e, 'REMARK 350')) {
|
||||
@@ -274,14 +279,29 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
helperCategories.push(parseSheet(lines, i, j));
|
||||
i = j - 1;
|
||||
}
|
||||
// TODO SCALE record => cif.atom_sites.fract_transf_matrix, cif.atom_sites.fract_transf_vector
|
||||
// TODO: SCALE record => cif.atom_sites.fract_transf_matrix, cif.atom_sites.fract_transf_vector
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// build entity and chem_comp categories
|
||||
const seqIds = Column.ofIntTokens(atomSite.auth_seq_id)
|
||||
const atomIds = Column.ofStringTokens(atomSite.auth_atom_id)
|
||||
const compIds = Column.ofStringTokens(atomSite.auth_comp_id)
|
||||
const asymIds = Column.ofStringTokens(atomSite.auth_asym_id)
|
||||
const componentBuilder = new ComponentBuilder(seqIds, atomIds)
|
||||
componentBuilder.setNames(heteroNames)
|
||||
entityBuilder.setNames(heteroNames)
|
||||
for (let i = 0, il = compIds.rowCount; i < il; ++i) {
|
||||
const compId = compIds.value(i)
|
||||
const moleculeType = getMoleculeType(componentBuilder.add(compId, i).type, compId)
|
||||
atomSite.label_entity_id[i] = entityBuilder.getEntityId(compId, moleculeType, asymIds.value(i))
|
||||
}
|
||||
|
||||
const categories = {
|
||||
entity: CifCategory.ofFields('entity', _entity()),
|
||||
atom_site: CifCategory.ofFields('atom_site', _atom_site(atom_site))
|
||||
entity: entityBuilder.getEntityCategory(),
|
||||
chem_comp: componentBuilder.getChemCompCategory(),
|
||||
atom_site: CifCategory.ofFields('atom_site', getAomSite(atomSite))
|
||||
} as any;
|
||||
|
||||
for (const c of helperCategories) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Task } from '../../mol-task';
|
||||
import { ThemeDataContext, ThemeProvider } from '../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../mol-theme/color';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { TableLegend } from '../../mol-util/color/tables';
|
||||
import { TableLegend } from '../../mol-util/color/lists';
|
||||
import { Loci } from '../../mol-model/loci';
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { StructureElement } from '../../../mol-model/structure';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { TableLegend } from '../../../mol-util/color/tables';
|
||||
import { TableLegend } from '../../../mol-util/color/lists';
|
||||
|
||||
const ValidationColors = [
|
||||
Color.fromRgb(170, 170, 170), // not applicable
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -18,7 +18,6 @@ import { CifCategory } from '../../mol-io/reader/cif';
|
||||
import { PropertyWrapper } from '../../mol-model-props/common/wrapper';
|
||||
import { Task, RuntimeContext } from '../../mol-task';
|
||||
import { GraphQLClient } from '../../mol-util/graphql-client';
|
||||
import { ajaxGet } from '../../mol-util/data-source';
|
||||
|
||||
const { str, int, float, Aliased, Vector, List } = Column.Schema;
|
||||
|
||||
@@ -183,13 +182,12 @@ export function AssemblySymmetry(db: AssemblySymmetry.Database): AssemblySymmetr
|
||||
type SymmetryKind = 'GLOBAL' | 'LOCAL' | 'PSEUDO'
|
||||
type SymmetryType = 'ASYMMETRIC' | 'CYCLIC' | 'DIHEDRAL' | 'HELICAL' | 'ICOSAHEDRAL' | 'OCTAHEDRAL' | 'TETRAHEDRAL'
|
||||
|
||||
const Client = new GraphQLClient(AssemblySymmetry.GraphQLEndpointURL, ajaxGet)
|
||||
|
||||
export namespace AssemblySymmetry {
|
||||
export function is(x: any): x is AssemblySymmetry {
|
||||
return x['@type'] === 'rcsb_assembly_symmetry'
|
||||
}
|
||||
export const GraphQLEndpointURL = '//rest-dev.rcsb.org/graphql'
|
||||
export const GraphQLEndpointURL = '//rest-staging.rcsb.org/graphql'
|
||||
|
||||
export const Schema = {
|
||||
rcsb_assembly_symmetry_info: {
|
||||
updated_datetime_utc: Column.Schema.str
|
||||
@@ -257,7 +255,7 @@ export namespace AssemblySymmetry {
|
||||
|
||||
export const Descriptor = _Descriptor;
|
||||
|
||||
export async function attachFromCifOrAPI(model: Model, client: GraphQLClient = Client, ctx?: RuntimeContext) {
|
||||
export async function attachFromCifOrAPI(model: Model, client: GraphQLClient, ctx?: RuntimeContext) {
|
||||
if (model.customProperties.has(Descriptor)) return true;
|
||||
|
||||
let db: Database
|
||||
@@ -266,8 +264,10 @@ export namespace AssemblySymmetry {
|
||||
db = createDatabaseFromCif(model)
|
||||
} else {
|
||||
let result: AssemblySymmetryGraphQL.Query
|
||||
console.log('model.label.toLowerCase()', model.label.toLowerCase())
|
||||
const variables: AssemblySymmetryGraphQL.Variables = { pdbId: model.label.toLowerCase() };
|
||||
try {
|
||||
console.log('foo', client)
|
||||
result = await client.request<AssemblySymmetryGraphQL.Query>(ctx || RuntimeContext.Synchronous, query, variables);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated in 2019-06-04T12:44:49-07:00
|
||||
// Generated in 2019-08-09T10:26:08-07:00
|
||||
export type Maybe<T> = T | null;
|
||||
|
||||
/** Built-in scalar representing an instant in time */
|
||||
|
||||
@@ -31,7 +31,7 @@ export const AssemblySymmetryAxesParams = {
|
||||
sizeFactor: PD.Numeric(0.4, { min: 0, max: 3, step: 0.01 }),
|
||||
|
||||
...ComplexMeshParams,
|
||||
radialSegments: PD.Numeric(16, { min: 3, max: 56, step: 1 }),
|
||||
radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }),
|
||||
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }),
|
||||
}
|
||||
export type AssemblySymmetryAxesParams = typeof AssemblySymmetryAxesParams
|
||||
@@ -54,7 +54,8 @@ export const AssemblySymmetryAxesRepresentationProvider: StructureRepresentation
|
||||
getParams: getAssemblySymmetryAxesParams,
|
||||
defaultValues: PD.getDefaultValues(AssemblySymmetryAxesParams),
|
||||
defaultColorTheme: 'uniform',
|
||||
defaultSizeTheme: 'uniform'
|
||||
defaultSizeTheme: 'uniform',
|
||||
isApplicable: (structure: Structure) => AssemblySymmetry.get(structure.models[0]) !== undefined
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -14,8 +14,8 @@ import { Unit, StructureElement, StructureProperties } from '../../../mol-model/
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { ScaleLegend } from '../../../mol-util/color/scale';
|
||||
import { getSymmetrySelectParam } from '../util';
|
||||
import { getPalette, getPaletteParams } from '../../../mol-theme/color/util';
|
||||
import { TableLegend } from '../../../mol-util/color/tables';
|
||||
import { getPalette, getPaletteParams } from '../../../mol-util/color/palette';
|
||||
import { TableLegend } from '../../../mol-util/color/lists';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC)
|
||||
|
||||
@@ -34,7 +34,7 @@ function clusterMemberKey(assemblyId: string, asymId: string, operList: string[]
|
||||
}
|
||||
|
||||
export const AssemblySymmetryClusterColorThemeParams = {
|
||||
...getPaletteParams({ scaleList: 'RedYellowBlue' }),
|
||||
...getPaletteParams({ scaleList: 'red-yellow-blue' }),
|
||||
symmetryId: getSymmetrySelectParam(),
|
||||
}
|
||||
export type AssemblySymmetryClusterColorThemeParams = typeof AssemblySymmetryClusterColorThemeParams
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user