mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 14:04:36 +08:00
Compare commits
263 Commits
safari-deb
...
v3.18.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa414485a5 | ||
|
|
3a35a5d66a | ||
|
|
43b0a72b09 | ||
|
|
521ac2d13f | ||
|
|
30520c50c2 | ||
|
|
819f07eba3 | ||
|
|
d8d6aa7136 | ||
|
|
ed75a365d8 | ||
|
|
f5ff13ffe4 | ||
|
|
44c69b1716 | ||
|
|
559ca7ffb8 | ||
|
|
524f34e8c1 | ||
|
|
d749be11f0 | ||
|
|
13dc9ff3cb | ||
|
|
24b4fce326 | ||
|
|
f506210bf8 | ||
|
|
cb0cbd06ce | ||
|
|
3356239089 | ||
|
|
9a5b2edc08 | ||
|
|
2d41b4bd83 | ||
|
|
58f7758ee1 | ||
|
|
9dbb642883 | ||
|
|
c5222e4d1d | ||
|
|
a5a695a17c | ||
|
|
7d1dc86cfb | ||
|
|
03224f914a | ||
|
|
1cf1f07232 | ||
|
|
838d36a74e | ||
|
|
6c9300d01b | ||
|
|
3059f7efef | ||
|
|
fbce7d9afa | ||
|
|
1c9f3ed9fa | ||
|
|
8c47d2d400 | ||
|
|
8a18f25b5d | ||
|
|
e7ae0058ed | ||
|
|
98bf3a3e33 | ||
|
|
379fcd4494 | ||
|
|
8589777bac | ||
|
|
c10a21ecbd | ||
|
|
eddc616b14 | ||
|
|
70fc1a9579 | ||
|
|
f27ec4d6a4 | ||
|
|
1e6e956e2d | ||
|
|
0a2a3530d1 | ||
|
|
9e4c820e26 | ||
|
|
05290bfe9e | ||
|
|
607f4ce353 | ||
|
|
4b819ead1d | ||
|
|
d07d9d3f31 | ||
|
|
d08776bf19 | ||
|
|
7a61fd44fd | ||
|
|
151da1487c | ||
|
|
e3f6dfad5b | ||
|
|
32080ce918 | ||
|
|
aedb2138c8 | ||
|
|
90e6938f1c | ||
|
|
eadff35250 | ||
|
|
487450ec64 | ||
|
|
f2f730bab5 | ||
|
|
ceecee37a7 | ||
|
|
394377bea9 | ||
|
|
2b47818deb | ||
|
|
2935717a06 | ||
|
|
2c8d2cfa21 | ||
|
|
d67c0eb757 | ||
|
|
7bcbcd5a7f | ||
|
|
1c06e7f36e | ||
|
|
407297adc0 | ||
|
|
b082b31562 | ||
|
|
fcbeb0f82f | ||
|
|
7094f8f265 | ||
|
|
48aaa13420 | ||
|
|
d2434cf91f | ||
|
|
8dbe0d2793 | ||
|
|
7b308cf984 | ||
|
|
520af504aa | ||
|
|
4bee130599 | ||
|
|
19a9ed3e19 | ||
|
|
0dac1b93ae | ||
|
|
e824863de1 | ||
|
|
9ff8becd62 | ||
|
|
fcaa1bcfa8 | ||
|
|
39f51bcc4f | ||
|
|
1b904ee2c9 | ||
|
|
e2baafc426 | ||
|
|
6dabe73002 | ||
|
|
d9b2b99c86 | ||
|
|
bdf23a7c4e | ||
|
|
c1723e0806 | ||
|
|
93590bd482 | ||
|
|
fbaa9d9e58 | ||
|
|
ba78a8558c | ||
|
|
513be04352 | ||
|
|
15317aa11b | ||
|
|
93e107f333 | ||
|
|
655b334b0a | ||
|
|
95cc1c58a6 | ||
|
|
0511d3e599 | ||
|
|
92a41b5c46 | ||
|
|
be16837c8c | ||
|
|
bf5f26cb12 | ||
|
|
ccbcef7eff | ||
|
|
d02a97b7f0 | ||
|
|
e0ca413c54 | ||
|
|
a272fc1c05 | ||
|
|
2c3f74d4ea | ||
|
|
c65b2fc0fd | ||
|
|
fd725adf27 | ||
|
|
ceba6da91f | ||
|
|
064e7d42ee | ||
|
|
cfdbf0c614 | ||
|
|
436777fe34 | ||
|
|
f08aa46222 | ||
|
|
e099ac514a | ||
|
|
873755f619 | ||
|
|
2094b7cf83 | ||
|
|
6ffdd81bb1 | ||
|
|
a69cb17602 | ||
|
|
7c82a9fd6e | ||
|
|
10d9a6c83d | ||
|
|
f6ed650ef6 | ||
|
|
df9ce6add9 | ||
|
|
28ee47d501 | ||
|
|
df2da479ad | ||
|
|
46eb9d8baf | ||
|
|
b6be871a21 | ||
|
|
ce2367fc0a | ||
|
|
7789e8cfea | ||
|
|
51a9effcaa | ||
|
|
fc3b953a8e | ||
|
|
a2ded3cecc | ||
|
|
ffb1390b51 | ||
|
|
3b92591c05 | ||
|
|
f173ddcf00 | ||
|
|
f78306f624 | ||
|
|
9852c9301e | ||
|
|
2e4f3de604 | ||
|
|
300dd97353 | ||
|
|
8e29ade83a | ||
|
|
971c770f6a | ||
|
|
7d31a06ae4 | ||
|
|
c5319ad7b1 | ||
|
|
f8bdb28ea6 | ||
|
|
2f8806d7c2 | ||
|
|
7d0a181c12 | ||
|
|
27cb7e53ed | ||
|
|
ee5154b510 | ||
|
|
080837201a | ||
|
|
656e6c0d94 | ||
|
|
b018f61bab | ||
|
|
b03b306848 | ||
|
|
19bf5c2b3e | ||
|
|
c22a716cf9 | ||
|
|
220c65da92 | ||
|
|
675f4b86f8 | ||
|
|
d31b3522b2 | ||
|
|
4ed2bab1d7 | ||
|
|
a572872806 | ||
|
|
e3ca23db0b | ||
|
|
67eb16a53f | ||
|
|
d7421cd1a3 | ||
|
|
c2e68ced66 | ||
|
|
10cf0db050 | ||
|
|
08f1a1dcfe | ||
|
|
11bf352295 | ||
|
|
5fd560b30a | ||
|
|
e6d01ca246 | ||
|
|
0ddf2fa00d | ||
|
|
8776143ec2 | ||
|
|
080c8e7af3 | ||
|
|
64998e762b | ||
|
|
b508da5ccc | ||
|
|
84a492655a | ||
|
|
9b9cfe4138 | ||
|
|
f362a7086a | ||
|
|
9e9ec57a5f | ||
|
|
da6a153985 | ||
|
|
b4bde3f510 | ||
|
|
8a5cebd635 | ||
|
|
ddf2733d3c | ||
|
|
cf65bfbcd0 | ||
|
|
03cce830bc | ||
|
|
913cf4c3e9 | ||
|
|
2ccce0beaf | ||
|
|
52239f71cd | ||
|
|
d9265af2e8 | ||
|
|
5877f6a627 | ||
|
|
e3e982c051 | ||
|
|
17f09ff3de | ||
|
|
7a73416c03 | ||
|
|
0f799d44ad | ||
|
|
24ebd44f87 | ||
|
|
572b10e655 | ||
|
|
60361c176b | ||
|
|
b232a2c58f | ||
|
|
2a44ac56fb | ||
|
|
d0340a3257 | ||
|
|
e708a53ddb | ||
|
|
23cdd70198 | ||
|
|
ba4bc30a78 | ||
|
|
e516ea146d | ||
|
|
61af638fe4 | ||
|
|
7a0af4142f | ||
|
|
1aa8d596a3 | ||
|
|
a457810623 | ||
|
|
4bccb7ab84 | ||
|
|
fcd5b2ce0a | ||
|
|
57a1184a16 | ||
|
|
ef176efed8 | ||
|
|
9943e0317d | ||
|
|
ae11c1c904 | ||
|
|
f937e069ca | ||
|
|
c9326da47b | ||
|
|
4698c05f9c | ||
|
|
15fd2cd5a0 | ||
|
|
193eb11095 | ||
|
|
59cc0096cd | ||
|
|
6bd7390eb8 | ||
|
|
eabeb18f34 | ||
|
|
4b6f539ba3 | ||
|
|
ea55fc5f41 | ||
|
|
94787229a4 | ||
|
|
0cb162022c | ||
|
|
5ff2ca311e | ||
|
|
44b8d452a8 | ||
|
|
82ccb1ab23 | ||
|
|
feb8b94e30 | ||
|
|
5adc73e277 | ||
|
|
c4ac983fc7 | ||
|
|
5ba7ba3aac | ||
|
|
e879479b3d | ||
|
|
7b49463297 | ||
|
|
66600c3373 | ||
|
|
19401c4bc6 | ||
|
|
bfc8660c5e | ||
|
|
6a83dc56ba | ||
|
|
82df9d8cad | ||
|
|
dd30fef078 | ||
|
|
79feb5a1cc | ||
|
|
0665524b11 | ||
|
|
d45367e840 | ||
|
|
cb0d988efc | ||
|
|
fc0c556967 | ||
|
|
00970164db | ||
|
|
7c3d76e9fe | ||
|
|
190c1f9620 | ||
|
|
f532325147 | ||
|
|
278dcb8808 | ||
|
|
309c25e10b | ||
|
|
6df728ea3e | ||
|
|
dcf4ef6d74 | ||
|
|
4de1369a5a | ||
|
|
2ccfdb1280 | ||
|
|
9fbf800639 | ||
|
|
577daf64df | ||
|
|
0b1943e9b3 | ||
|
|
30bd2dd876 | ||
|
|
cecd4d4179 | ||
|
|
364baab18d | ||
|
|
bb3d4d2171 | ||
|
|
2355faf899 | ||
|
|
858e0b24ff | ||
|
|
f7d0ed3988 |
32
CHANGELOG.md
32
CHANGELOG.md
@@ -6,6 +6,38 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v3.18.0] - 2022-09-17
|
||||
|
||||
- Integration of Dual depth peeling - OIT method
|
||||
- Stereo camera improvements
|
||||
- Fix param updates not applied
|
||||
- Better param ranges and description
|
||||
- Add timer.mark for left/right camera
|
||||
|
||||
## [v3.17.0] - 2022-09-11
|
||||
|
||||
- [Fix] Clone ``Canvas3DParams`` when creating a ``Canvas3D`` instance to prevent shared state between multiple instances
|
||||
- Add ``includeResidueTest`` option to ``alignAndSuperposeWithSIFTSMapping``
|
||||
- Add ``parentDisplay`` param for interactions representation.
|
||||
- [Experimental] Add support for PyMOL, VMD, and Jmol atom expressions in selection scripts
|
||||
- Support for ``failIfMajorPerformanceCaveat`` webgl attribute. Add ``PluginConfig.General.AllowMajorPerformanceCaveat`` and ``allow-major-performance-caveat`` Viewer GET param.
|
||||
- Fix handling of PDB TER records (#549)
|
||||
- Add support for getting multiple loci from a representation (``.getAllLoci()``)
|
||||
- Add ``key`` property to intra- and inter-bonds for referencing source data
|
||||
- Fix click event triggered after move
|
||||
|
||||
## [v3.16.0] - 2022-08-25
|
||||
|
||||
- Support ``globalColorParams`` and ``globalSymmetryParams`` in common representation params
|
||||
- Support ``label`` parameter in ``Viewer.loadStructureFromUrl``
|
||||
- Fix ``ViewportHelpContent`` Mouse Controls section
|
||||
|
||||
## [v3.15.0] - 2022-08-23
|
||||
|
||||
- Fix wboit in Safari >=15 (add missing depth renderbuffer to wboit pass)
|
||||
- Add 'Around Camera' option to Volume streaming
|
||||
- Avoid queuing more than one update in Volume streaming
|
||||
|
||||
## [v3.14.0] - 2022-08-20
|
||||
|
||||
- Expose inter-bonds compute params in structure
|
||||
|
||||
3043
package-lock.json
generated
3043
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.14.0",
|
||||
"version": "3.18.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -89,57 +89,60 @@
|
||||
"Ludovic Autin <autin@scripps.edu>",
|
||||
"Michal Malý <michal.maly@ibt.cas.cz>",
|
||||
"Jiří Černý <jiri.cerny@ibt.cas.cz>",
|
||||
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>"
|
||||
"Panagiotis Tourlas <panagiot_tourlov@hotmail.com>",
|
||||
"Adam Midlik <midlik@gmail.com>",
|
||||
"Koya Sakuma <koya.sakuma.work@gmail.com>",
|
||||
"Gianluca Tomasello <giagitom@gmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^3.2.1",
|
||||
"@graphql-codegen/cli": "^2.11.6",
|
||||
"@graphql-codegen/cli": "^2.12.0",
|
||||
"@graphql-codegen/time": "^3.2.1",
|
||||
"@graphql-codegen/typescript": "^2.7.3",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.2.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.3",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.5.4",
|
||||
"@graphql-codegen/typescript-operations": "^2.5.3",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/gl": "^4.1.1",
|
||||
"@types/jest": "^28.1.7",
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/jest": "^29.0.3",
|
||||
"@types/react": "^18.0.20",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
||||
"@typescript-eslint/parser": "^5.33.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.37.0",
|
||||
"@typescript-eslint/parser": "^5.37.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^7.3.0",
|
||||
"concurrently": "^7.4.0",
|
||||
"cpx2": "^4.2.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint": "^8.23.1",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"graphql": "^16.6.0",
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "^28.1.3",
|
||||
"jest": "^29.0.3",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sass": "^1.54.5",
|
||||
"sass": "^1.54.9",
|
||||
"sass-loader": "^13.0.2",
|
||||
"simple-git": "^3.12.0",
|
||||
"simple-git": "^3.14.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^28.0.8",
|
||||
"typescript": "^4.7.4",
|
||||
"ts-jest": "^29.0.1",
|
||||
"typescript": "^4.8.3",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^2.0.10",
|
||||
"@types/benchmark": "^2.1.1",
|
||||
"@types/benchmark": "^2.1.2",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^16.11.51",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/node": "^16.11.59",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"argparse": "^2.0.1",
|
||||
|
||||
@@ -166,7 +166,7 @@ class Viewer {
|
||||
structures.push({ ref: structureProperties?.ref || structure.ref });
|
||||
}
|
||||
|
||||
// remove current structuresfrom hierarchy as they will be merged
|
||||
// remove current structures from hierarchy as they will be merged
|
||||
// TODO only works with using loadStructuresFromUrlsAndMerge once
|
||||
// need some more API metho to work with the hierarchy
|
||||
this.plugin.managers.structure.hierarchy.updateCurrent(this.plugin.managers.structure.hierarchy.current.structures, 'remove');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -202,14 +202,14 @@ const InteractionsPreset = StructureRepresentationPresetProvider({
|
||||
const components = {
|
||||
ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
|
||||
surroundings: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandSurroundings, `surroundings`),
|
||||
interactions: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandPlusSurroundings, `interactions`)
|
||||
interactions: await presetStaticComponent(plugin, structureCell, 'ligand'),
|
||||
};
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
|
||||
ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
|
||||
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
|
||||
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial, includeParent: true, parentDisplay: 'between' }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
|
||||
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, material: CustomMaterial, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
|
||||
};
|
||||
|
||||
|
||||
@@ -88,7 +88,9 @@ const DefaultViewerOptions = {
|
||||
pickScale: PluginConfig.General.PickScale.defaultValue,
|
||||
pickPadding: PluginConfig.General.PickPadding.defaultValue,
|
||||
enableWboit: PluginConfig.General.EnableWboit.defaultValue,
|
||||
enableDpoit: PluginConfig.General.EnableDpoit.defaultValue,
|
||||
preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue,
|
||||
allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue,
|
||||
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
|
||||
@@ -158,7 +160,9 @@ export class Viewer {
|
||||
[PluginConfig.General.PickScale, o.pickScale],
|
||||
[PluginConfig.General.PickPadding, o.pickPadding],
|
||||
[PluginConfig.General.EnableWboit, o.enableWboit],
|
||||
[PluginConfig.General.EnableDpoit, o.enableDpoit],
|
||||
[PluginConfig.General.PreferWebGl1, o.preferWebgl1],
|
||||
[PluginConfig.General.AllowMajorPerformanceCaveat, o.allowMajorPerformanceCaveat],
|
||||
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
|
||||
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
|
||||
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
|
||||
@@ -199,7 +203,7 @@ export class Viewer {
|
||||
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
|
||||
}
|
||||
|
||||
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
|
||||
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions & { label?: string }) {
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
@@ -208,6 +212,7 @@ export class Viewer {
|
||||
url: Asset.Url(url),
|
||||
format: format as any,
|
||||
isBinary,
|
||||
label: options?.label,
|
||||
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
|
||||
}
|
||||
}
|
||||
@@ -496,4 +501,4 @@ export const ViewerAutoPreset = StructureRepresentationPresetProvider({
|
||||
return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,6 +38,15 @@
|
||||
viewer.loadPdb('7bv2');
|
||||
viewer.loadEmdb('EMD-30210', { detail: 6 });
|
||||
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
|
||||
// viewer.loadStructureFromUrl('my url', 'pdb', false, {
|
||||
// representationParams: {
|
||||
// theme: {
|
||||
// globalName: 'uniform',
|
||||
// globalColorParams: { value: 0xff0000 }
|
||||
// }
|
||||
// },
|
||||
// label: 'my structure'
|
||||
// });
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -60,7 +60,9 @@
|
||||
var pickScale = getParam('pick-scale', '[^&]+').trim();
|
||||
var pickPadding = getParam('pick-padding', '[^&]+').trim();
|
||||
var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
|
||||
var enableDpoit = getParam('enable-dpoit', '[^&]+').trim() === '1';
|
||||
var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1' || void 0;
|
||||
var allowMajorPerformanceCaveat = getParam('allow-major-performance-caveat', '[^&]+').trim() === '1';
|
||||
|
||||
molstar.Viewer.create('app', {
|
||||
layoutShowControls: !hideControls,
|
||||
@@ -74,8 +76,10 @@
|
||||
pixelScale: parseFloat(pixelScale) || 1,
|
||||
pickScale: parseFloat(pickScale) || 0.25,
|
||||
pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding),
|
||||
enableWboit: disableWboit ? false : void 0, // use default value if disable-wboit is not set
|
||||
enableWboit: (disableWboit || enableDpoit) ? false : void 0, // use default value if disable-wboit is not set
|
||||
enableDpoit: enableDpoit ? true : void 0,
|
||||
preferWebgl1: preferWebgl1,
|
||||
allowMajorPerformanceCaveat: allowMajorPerformanceCaveat,
|
||||
}).then(viewer => {
|
||||
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
|
||||
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);
|
||||
|
||||
@@ -60,6 +60,8 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
const merged = merge(
|
||||
this.controls.behaviors.params,
|
||||
this.plugin.canvas3d!.reprCount
|
||||
|
||||
@@ -118,11 +118,13 @@ export class Mp4Controls extends PluginComponent {
|
||||
}
|
||||
|
||||
private init() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
this.subscribe(this.plugin.managers.animation.events.updated.pipe(debounceTime(16)), () => {
|
||||
this.sync();
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.canvas3d?.resized!, () => this.syncInfo());
|
||||
this.subscribe(this.plugin.canvas3d.resized, () => this.syncInfo());
|
||||
this.subscribe(this.plugin.helpers.viewportScreenshot?.events.previewed!, () => this.syncInfo());
|
||||
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, b => this.updateCanApply(b));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 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>
|
||||
@@ -13,8 +13,8 @@ import { Camera, ICamera } from '../camera';
|
||||
import { Viewport } from './util';
|
||||
|
||||
export const StereoCameraParams = {
|
||||
eyeSeparation: PD.Numeric(0.064, { min: 0.01, max: 0.5, step: 0.001 }),
|
||||
focus: PD.Numeric(10, { min: 1, max: 100, step: 0.01 }),
|
||||
eyeSeparation: PD.Numeric(0.062, { min: 0.02, max: 0.1, step: 0.001 }, { description: 'Distance between left and right camera.' }),
|
||||
focus: PD.Numeric(10, { min: 1, max: 20, step: 0.1 }, { description: 'Apparent object distance.' }),
|
||||
};
|
||||
export const DefaultStereoCameraProps = PD.getDefaultValues(StereoCameraParams);
|
||||
export type StereoCameraProps = PD.Values<typeof StereoCameraParams>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
@@ -39,9 +40,10 @@ import { Helper } from './helper/helper';
|
||||
import { Passes } from './passes/passes';
|
||||
import { shallowEqual } from '../mol-util';
|
||||
import { MarkingParams } from './passes/marking';
|
||||
import { GraphicsRenderVariantsBlended, GraphicsRenderVariantsWboit } from '../mol-gl/webgl/render-item';
|
||||
import { GraphicsRenderVariantsBlended, GraphicsRenderVariantsWboit, GraphicsRenderVariantsDpoit } from '../mol-gl/webgl/render-item';
|
||||
import { degToRad, radToDeg } from '../mol-math/misc';
|
||||
import { AssetManager } from '../mol-util/assets';
|
||||
import { deepClone } from '../mol-util/object';
|
||||
|
||||
export const Canvas3DParams = {
|
||||
camera: PD.Group({
|
||||
@@ -83,6 +85,7 @@ export const Canvas3DParams = {
|
||||
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
|
||||
sceneRadiusFactor: PD.Numeric(1, { min: 1, max: 10, step: 0.1 }),
|
||||
transparentBackground: PD.Boolean(false),
|
||||
dpoitIterations: PD.Numeric(2, { min: 1, max: 10, step: 1 }),
|
||||
|
||||
multiSample: PD.Group(MultiSampleParams),
|
||||
postprocessing: PD.Group(PostprocessingParams),
|
||||
@@ -116,6 +119,7 @@ interface Canvas3DContext {
|
||||
|
||||
namespace Canvas3DContext {
|
||||
export const DefaultAttribs = {
|
||||
failIfMajorPerformanceCaveat: false,
|
||||
/** true by default to avoid issues with Safari (Jan 2021) */
|
||||
antialias: true,
|
||||
/** true to support multiple Canvas3D objects with a single context */
|
||||
@@ -125,14 +129,19 @@ namespace Canvas3DContext {
|
||||
/** extra pixels to around target to check in case target is empty */
|
||||
pickPadding: 1,
|
||||
enableWboit: true,
|
||||
enableDpoit: false,
|
||||
preferWebGl1: false
|
||||
};
|
||||
export type Attribs = typeof DefaultAttribs
|
||||
|
||||
export function fromCanvas(canvas: HTMLCanvasElement, assetManager: AssetManager, attribs: Partial<Attribs> = {}): Canvas3DContext {
|
||||
const a = { ...DefaultAttribs, ...attribs };
|
||||
const { antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a;
|
||||
|
||||
if (a.enableWboit && a.enableDpoit) throw new Error('Multiple transparency methods not allowed.');
|
||||
|
||||
const { failIfMajorPerformanceCaveat, antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a;
|
||||
const gl = getGLContext(canvas, {
|
||||
failIfMajorPerformanceCaveat,
|
||||
antialias,
|
||||
preserveDrawingBuffer,
|
||||
alpha: true, // the renderer requires an alpha channel
|
||||
@@ -285,7 +294,7 @@ namespace Canvas3D {
|
||||
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
|
||||
|
||||
export function create({ webgl, input, passes, attribs, assetManager }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D {
|
||||
const p: Canvas3DProps = { ...DefaultCanvas3DParams, ...props };
|
||||
const p: Canvas3DProps = { ...deepClone(DefaultCanvas3DParams), ...deepClone(props) };
|
||||
|
||||
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>();
|
||||
const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>();
|
||||
@@ -302,8 +311,7 @@ namespace Canvas3D {
|
||||
let width = 128;
|
||||
let height = 128;
|
||||
updateViewport();
|
||||
|
||||
const scene = Scene.create(webgl, passes.draw.wboitEnabled ? GraphicsRenderVariantsWboit : GraphicsRenderVariantsBlended);
|
||||
const scene = Scene.create(webgl, passes.draw.dpoitEnabled ? GraphicsRenderVariantsDpoit : (passes.draw.wboitEnabled ? GraphicsRenderVariantsWboit : GraphicsRenderVariantsBlended));
|
||||
|
||||
function getSceneRadius() {
|
||||
return scene.boundingSphere.radius * p.sceneRadiusFactor;
|
||||
@@ -683,6 +691,7 @@ namespace Canvas3D {
|
||||
cameraResetDurationMs: p.cameraResetDurationMs,
|
||||
sceneRadiusFactor: p.sceneRadiusFactor,
|
||||
transparentBackground: p.transparentBackground,
|
||||
dpoitIterations: p.dpoitIterations,
|
||||
viewport: p.viewport,
|
||||
|
||||
postprocessing: { ...p.postprocessing },
|
||||
@@ -817,9 +826,13 @@ namespace Canvas3D {
|
||||
|
||||
if (props.camera?.helper) helper.camera.setProps(props.camera.helper);
|
||||
if (props.camera?.manualReset !== undefined) p.camera.manualReset = props.camera.manualReset;
|
||||
if (props.camera?.stereo !== undefined) Object.assign(p.camera.stereo, props.camera.stereo);
|
||||
if (props.camera?.stereo !== undefined) {
|
||||
Object.assign(p.camera.stereo, props.camera.stereo);
|
||||
stereoCamera.setProps(p.camera.stereo.params);
|
||||
}
|
||||
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs;
|
||||
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground;
|
||||
if (props.dpoitIterations !== undefined) p.dpoitIterations = props.dpoitIterations;
|
||||
if (props.viewport !== undefined) {
|
||||
const doNotUpdate = p.viewport === props.viewport ||
|
||||
(p.viewport.name === props.viewport.name && shallowEqual(p.viewport.params, props.viewport.params));
|
||||
@@ -855,7 +868,7 @@ namespace Canvas3D {
|
||||
}
|
||||
},
|
||||
getImagePass: (props: Partial<ImageProps> = {}) => {
|
||||
return new ImagePass(webgl, assetManager, renderer, scene, camera, helper, passes.draw.wboitEnabled, props);
|
||||
return new ImagePass(webgl, assetManager, renderer, scene, camera, helper, passes.draw.wboitEnabled, passes.draw.dpoitEnabled, props);
|
||||
},
|
||||
getRenderObjects(): GraphicsRenderObject[] {
|
||||
const renderObjects: GraphicsRenderObject[] = [];
|
||||
@@ -920,4 +933,4 @@ namespace Canvas3D {
|
||||
Viewport.set(controls.viewport, x, y, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
309
src/mol-canvas3d/passes/dpoit.ts
Normal file
309
src/mol-canvas3d/passes/dpoit.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*
|
||||
* Adapted from https://github.com/tsherif/webgl2examples, The MIT License, Copyright © 2017 Tarek Sherif, Shuai Shao
|
||||
*/
|
||||
|
||||
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
|
||||
import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
|
||||
import { TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
|
||||
import { ShaderCode } from '../../mol-gl/shader-code';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { quad_vert } from '../../mol-gl/shader/quad.vert';
|
||||
import { evaluateDpoit_frag } from '../../mol-gl/shader/evaluate-dpoit.frag';
|
||||
import { blendBackDpoit_frag } from '../../mol-gl/shader/blend-back-dpoit.frag';
|
||||
import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
|
||||
import { Vec2 } from '../../mol-math/linear-algebra';
|
||||
import { isDebugMode, isTimingMode } from '../../mol-util/debug';
|
||||
import { isWebGL2 } from '../../mol-gl/webgl/compat';
|
||||
|
||||
const BlendBackDpoitSchema = {
|
||||
...QuadSchema,
|
||||
tDpoitBackColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
uTexSize: UniformSpec('v2'),
|
||||
};
|
||||
const BlendBackDpoitShaderCode = ShaderCode('blend-back-dpoit', quad_vert, blendBackDpoit_frag);
|
||||
type BlendBackDpoitRenderable = ComputeRenderable<Values<typeof BlendBackDpoitSchema>>
|
||||
|
||||
function getBlendBackDpoitRenderable(ctx: WebGLContext, dopitBlendBackTexture: Texture): BlendBackDpoitRenderable {
|
||||
const values: Values<typeof BlendBackDpoitSchema> = {
|
||||
...QuadValues,
|
||||
tDpoitBackColor: ValueCell.create(dopitBlendBackTexture),
|
||||
uTexSize: ValueCell.create(Vec2.create(dopitBlendBackTexture.getWidth(), dopitBlendBackTexture.getHeight())),
|
||||
};
|
||||
|
||||
const schema = { ...BlendBackDpoitSchema };
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', BlendBackDpoitShaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
const EvaluateDpoitSchema = {
|
||||
...QuadSchema,
|
||||
tDpoitFrontColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
uTexSize: UniformSpec('v2'),
|
||||
};
|
||||
const EvaluateDpoitShaderCode = ShaderCode('evaluate-dpoit', quad_vert, evaluateDpoit_frag);
|
||||
type EvaluateDpoitRenderable = ComputeRenderable<Values<typeof EvaluateDpoitSchema>>
|
||||
|
||||
function getEvaluateDpoitRenderable(ctx: WebGLContext, dpoitFrontColorTexture: Texture): EvaluateDpoitRenderable {
|
||||
const values: Values<typeof EvaluateDpoitSchema> = {
|
||||
...QuadValues,
|
||||
tDpoitFrontColor: ValueCell.create(dpoitFrontColorTexture),
|
||||
uTexSize: ValueCell.create(Vec2.create(dpoitFrontColorTexture.getWidth(), dpoitFrontColorTexture.getHeight())),
|
||||
};
|
||||
|
||||
const schema = { ...EvaluateDpoitSchema };
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', EvaluateDpoitShaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
export class DpoitPass {
|
||||
private readonly DEPTH_CLEAR_VALUE = -99999.0; // NOTE same constant is set in shaders
|
||||
private readonly MAX_DEPTH = 1.0;
|
||||
private readonly MIN_DEPTH = 0.0;
|
||||
|
||||
private passCount = 0;
|
||||
private writeId: number;
|
||||
private readId: number;
|
||||
|
||||
private readonly blendBackRenderable: BlendBackDpoitRenderable;
|
||||
private readonly renderable: EvaluateDpoitRenderable;
|
||||
|
||||
private readonly depthFramebuffers: Framebuffer[];
|
||||
private readonly colorFramebuffers: Framebuffer[];
|
||||
|
||||
private readonly depthTextures: Texture[];
|
||||
private readonly colorFrontTextures: Texture[];
|
||||
private readonly colorBackTextures: Texture[];
|
||||
|
||||
private _supported = false;
|
||||
get supported() {
|
||||
return this._supported;
|
||||
}
|
||||
|
||||
bind() {
|
||||
const { state, gl, extensions: { blendMinMax } } = this.webgl;
|
||||
|
||||
// initialize
|
||||
this.passCount = 0;
|
||||
|
||||
this.depthFramebuffers[0].bind();
|
||||
state.clearColor(this.DEPTH_CLEAR_VALUE, this.DEPTH_CLEAR_VALUE, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.depthFramebuffers[1].bind();
|
||||
state.clearColor(-this.MIN_DEPTH, this.MAX_DEPTH, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.colorFramebuffers[0].bind();
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.colorFramebuffers[1].bind();
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.depthFramebuffers[0].bind();
|
||||
state.blendEquation(blendMinMax!.MAX);
|
||||
state.depthMask(false);
|
||||
|
||||
return {
|
||||
depth: this.depthTextures[1],
|
||||
frontColor: this.colorFrontTextures[1],
|
||||
backColor: this.colorBackTextures[1]
|
||||
};
|
||||
}
|
||||
|
||||
bindDualDepthPeeling() {
|
||||
const { state, gl, extensions: { blendMinMax } } = this.webgl;
|
||||
|
||||
this.readId = this.passCount % 2;
|
||||
this.writeId = 1 - this.readId; // ping-pong: 0 or 1
|
||||
|
||||
this.passCount += 1; // increment for next pass
|
||||
|
||||
this.depthFramebuffers[this.writeId].bind();
|
||||
state.clearColor(this.DEPTH_CLEAR_VALUE, this.DEPTH_CLEAR_VALUE, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.colorFramebuffers[this.writeId].bind();
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this.depthFramebuffers[this.writeId].bind();
|
||||
state.blendEquation(blendMinMax!.MAX);
|
||||
state.depthMask(false);
|
||||
|
||||
return {
|
||||
depth: this.depthTextures[this.readId],
|
||||
frontColor: this.colorFrontTextures[this.readId],
|
||||
backColor: this.colorBackTextures[this.readId]
|
||||
};
|
||||
}
|
||||
|
||||
renderBlendBack() {
|
||||
if (isTimingMode) this.webgl.timer.mark('DpoitPass.renderBlendBack');
|
||||
const { state, gl } = this.webgl;
|
||||
|
||||
state.blendEquation(gl.FUNC_ADD);
|
||||
state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
ValueCell.update(this.blendBackRenderable.values.tDpoitBackColor, this.colorBackTextures[this.writeId]);
|
||||
|
||||
this.blendBackRenderable.update();
|
||||
this.blendBackRenderable.render();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.renderBlendBack');
|
||||
}
|
||||
|
||||
render() {
|
||||
if (isTimingMode) this.webgl.timer.mark('DpoitPass.render');
|
||||
const { state, gl } = this.webgl;
|
||||
|
||||
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
ValueCell.update(this.renderable.values.tDpoitFrontColor, this.colorFrontTextures[this.writeId]);
|
||||
|
||||
this.renderable.update();
|
||||
this.renderable.render();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.render');
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
const [w, h] = this.renderable.values.uTexSize.ref.value;
|
||||
if (width !== w || height !== h) {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
this.depthTextures[i].define(width, height);
|
||||
this.colorFrontTextures[i].define(width, height);
|
||||
this.colorBackTextures[i].define(width, height);
|
||||
}
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.blendBackRenderable.values.uTexSize, Vec2.set(this.blendBackRenderable.values.uTexSize.ref.value, width, height));
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this._supported) this._init();
|
||||
}
|
||||
|
||||
private _init() {
|
||||
const { extensions: { drawBuffers } } = this.webgl;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
// depth
|
||||
this.depthFramebuffers[i].bind();
|
||||
drawBuffers!.drawBuffers([
|
||||
drawBuffers!.COLOR_ATTACHMENT0,
|
||||
drawBuffers!.COLOR_ATTACHMENT1,
|
||||
drawBuffers!.COLOR_ATTACHMENT2
|
||||
]);
|
||||
|
||||
this.colorFrontTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color0');
|
||||
this.colorBackTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color1');
|
||||
this.depthTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color2');
|
||||
|
||||
// color
|
||||
this.colorFramebuffers[i].bind();
|
||||
drawBuffers!.drawBuffers([
|
||||
drawBuffers!.COLOR_ATTACHMENT0,
|
||||
drawBuffers!.COLOR_ATTACHMENT1
|
||||
]);
|
||||
|
||||
this.colorFrontTextures[i].attachFramebuffer(this.colorFramebuffers[i], 'color0');
|
||||
this.colorBackTextures[i].attachFramebuffer(this.colorFramebuffers[i], 'color1');
|
||||
}
|
||||
}
|
||||
|
||||
static isSupported(webgl: WebGLContext) {
|
||||
const { extensions: { drawBuffers, textureFloat, colorBufferFloat, blendMinMax } } = webgl;
|
||||
if (!textureFloat || !colorBufferFloat || !drawBuffers || !blendMinMax) {
|
||||
if (isDebugMode) {
|
||||
const missing: string[] = [];
|
||||
if (!textureFloat) missing.push('textureFloat');
|
||||
if (!colorBufferFloat) missing.push('colorBufferFloat');
|
||||
if (!drawBuffers) missing.push('drawBuffers');
|
||||
if (!blendMinMax) missing.push('blendMinMax');
|
||||
console.log(`Missing "${missing.join('", "')}" extensions required for "dpoit"`);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, width: number, height: number) {
|
||||
if (!DpoitPass.isSupported(webgl)) return;
|
||||
|
||||
const { resources, extensions: { colorBufferHalfFloat, textureHalfFloat } } = webgl;
|
||||
|
||||
// textures
|
||||
|
||||
if (isWebGL2(webgl.gl)) {
|
||||
this.depthTextures = [
|
||||
resources.texture('image-float32', 'rg', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rg', 'float', 'nearest')
|
||||
];
|
||||
|
||||
this.colorFrontTextures = colorBufferHalfFloat && textureHalfFloat ? [
|
||||
resources.texture('image-float16', 'rgba', 'fp16', 'nearest'),
|
||||
resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
|
||||
] : [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
|
||||
this.colorBackTextures = colorBufferHalfFloat && textureHalfFloat ? [
|
||||
resources.texture('image-float16', 'rgba', 'fp16', 'nearest'),
|
||||
resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
|
||||
] : [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
} else {
|
||||
// in webgl1 drawbuffers must be in the same format for some reason
|
||||
|
||||
this.depthTextures = [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
|
||||
this.colorFrontTextures = [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
|
||||
this.colorBackTextures = [
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest'),
|
||||
resources.texture('image-float32', 'rgba', 'float', 'nearest')
|
||||
];
|
||||
}
|
||||
|
||||
this.depthTextures[0].define(width, height);
|
||||
this.depthTextures[1].define(width, height);
|
||||
|
||||
this.colorFrontTextures[0].define(width, height);
|
||||
this.colorFrontTextures[1].define(width, height);
|
||||
|
||||
this.colorBackTextures[0].define(width, height);
|
||||
this.colorBackTextures[1].define(width, height);
|
||||
|
||||
// framebuffers
|
||||
|
||||
this.depthFramebuffers = [resources.framebuffer(), resources.framebuffer()];
|
||||
this.colorFramebuffers = [resources.framebuffer(), resources.framebuffer()];
|
||||
|
||||
// renderables
|
||||
|
||||
this.blendBackRenderable = getBlendBackDpoitRenderable(webgl, this.colorBackTextures[0]);
|
||||
this.renderable = getEvaluateDpoitRenderable(webgl, this.colorFrontTextures[0]);
|
||||
|
||||
this._supported = true;
|
||||
this._init();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
@@ -17,6 +18,7 @@ import { Helper } from '../helper/helper';
|
||||
|
||||
import { StereoCamera } from '../camera/stereo';
|
||||
import { WboitPass } from './wboit';
|
||||
import { DpoitPass } from './dpoit';
|
||||
import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
|
||||
import { MarkingPass, MarkingProps } from './marking';
|
||||
import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util';
|
||||
@@ -27,6 +29,7 @@ type Props = {
|
||||
postprocessing: PostprocessingProps;
|
||||
marking: MarkingProps;
|
||||
transparentBackground: boolean;
|
||||
dpoitIterations: number;
|
||||
}
|
||||
|
||||
type RenderContext = {
|
||||
@@ -52,6 +55,7 @@ export class DrawPass {
|
||||
private copyFboPostprocessing: CopyRenderable;
|
||||
|
||||
private readonly wboit: WboitPass | undefined;
|
||||
private readonly dpoit: DpoitPass | undefined;
|
||||
private readonly marking: MarkingPass;
|
||||
readonly postprocessing: PostprocessingPass;
|
||||
private readonly antialiasing: AntialiasingPass;
|
||||
@@ -60,9 +64,12 @@ export class DrawPass {
|
||||
return !!this.wboit?.supported;
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, width: number, height: number, enableWboit: boolean) {
|
||||
const { extensions, resources, isWebGL2 } = webgl;
|
||||
get dpoitEnabled() {
|
||||
return !!this.dpoit?.supported;
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, width: number, height: number, enableWboit: boolean, enableDpoit: boolean) {
|
||||
const { extensions, resources, isWebGL2 } = webgl;
|
||||
this.drawTarget = createNullRenderTarget(webgl.gl);
|
||||
this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear');
|
||||
this.packedDepth = !extensions.depthTexture;
|
||||
@@ -78,6 +85,7 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined;
|
||||
this.dpoit = enableDpoit ? new DpoitPass(webgl, width, height) : undefined;
|
||||
this.marking = new MarkingPass(webgl, width, height);
|
||||
this.postprocessing = new PostprocessingPass(webgl, assetManager, this);
|
||||
this.antialiasing = new AntialiasingPass(webgl, this);
|
||||
@@ -88,6 +96,7 @@ export class DrawPass {
|
||||
|
||||
reset() {
|
||||
this.wboit?.reset();
|
||||
this.dpoit?.reset();
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
@@ -111,12 +120,68 @@ export class DrawPass {
|
||||
this.wboit.setSize(width, height);
|
||||
}
|
||||
|
||||
if (this.dpoit?.supported) {
|
||||
this.dpoit.setSize(width, height);
|
||||
}
|
||||
|
||||
this.marking.setSize(width, height);
|
||||
this.postprocessing.setSize(width, height);
|
||||
this.antialiasing.setSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
private _renderDpoit(renderer: Renderer, camera: ICamera, scene: Scene, iterations: number, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
|
||||
if (!this.dpoit?.supported) throw new Error('expected dpoit to be supported');
|
||||
|
||||
this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
|
||||
renderer.clear(true);
|
||||
|
||||
// render opaque primitives
|
||||
if (scene.hasOpaque) {
|
||||
renderer.renderDpoitOpaque(scene.primitives, camera, null);
|
||||
}
|
||||
|
||||
if (PostprocessingPass.isEnabled(postprocessingProps)) {
|
||||
if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
|
||||
this.depthTargetTransparent.bind();
|
||||
renderer.clearDepth(true);
|
||||
if (scene.opacityAverage < 1) {
|
||||
renderer.renderDepthTransparent(scene.primitives, camera, this.depthTextureOpaque);
|
||||
}
|
||||
}
|
||||
|
||||
this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
|
||||
}
|
||||
|
||||
// render transparent primitives
|
||||
if (scene.opacityAverage < 1) {
|
||||
const target = PostprocessingPass.isEnabled(postprocessingProps)
|
||||
? this.postprocessing.target : this.colorTarget;
|
||||
|
||||
const dpoitTextures = this.dpoit.bind();
|
||||
renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures);
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
if (isTimingMode) this.webgl.timer.mark('DpoitPass.layer');
|
||||
const dpoitTextures = this.dpoit.bindDualDepthPeeling();
|
||||
renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures);
|
||||
|
||||
target.bind();
|
||||
this.dpoit.renderBlendBack();
|
||||
if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.layer');
|
||||
}
|
||||
|
||||
// evaluate dpoit
|
||||
target.bind();
|
||||
this.dpoit.render();
|
||||
}
|
||||
|
||||
// render transparent volumes
|
||||
if (scene.volumes.renderables.length > 0) {
|
||||
renderer.renderDpoitVolume(scene.volumes, camera, this.depthTextureOpaque);
|
||||
}
|
||||
}
|
||||
|
||||
private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
|
||||
if (!this.wboit?.supported) throw new Error('expected wboit to be supported');
|
||||
|
||||
@@ -254,13 +319,15 @@ export class DrawPass {
|
||||
|
||||
if (this.wboitEnabled) {
|
||||
this._renderWboit(renderer, camera, scene, transparentBackground, props.postprocessing);
|
||||
} else if (this.dpoitEnabled) {
|
||||
this._renderDpoit(renderer, camera, scene, props.dpoitIterations, transparentBackground, props.postprocessing);
|
||||
} else {
|
||||
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, props.postprocessing);
|
||||
}
|
||||
|
||||
const target = postprocessingEnabled
|
||||
? this.postprocessing.target
|
||||
: !toDrawingBuffer || volumeRendering || this.wboitEnabled
|
||||
: !toDrawingBuffer || volumeRendering || this.wboitEnabled || this.dpoitEnabled
|
||||
? this.colorTarget
|
||||
: this.drawTarget;
|
||||
|
||||
@@ -303,7 +370,7 @@ export class DrawPass {
|
||||
this.webgl.state.disable(this.webgl.gl.DEPTH_TEST);
|
||||
if (postprocessingEnabled) {
|
||||
this.copyFboPostprocessing.render();
|
||||
} else if (volumeRendering || this.wboitEnabled) {
|
||||
} else if (volumeRendering || this.wboitEnabled || this.dpoitEnabled) {
|
||||
this.copyFboTarget.render();
|
||||
}
|
||||
}
|
||||
@@ -323,8 +390,12 @@ export class DrawPass {
|
||||
renderer.setPixelRatio(this.webgl.pixelRatio);
|
||||
|
||||
if (StereoCamera.is(camera)) {
|
||||
if (isTimingMode) this.webgl.timer.mark('StereoCamera.left');
|
||||
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, props);
|
||||
if (isTimingMode) this.webgl.timer.markEnd('StereoCamera.left');
|
||||
if (isTimingMode) this.webgl.timer.mark('StereoCamera.right');
|
||||
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, props);
|
||||
if (isTimingMode) this.webgl.timer.markEnd('StereoCamera.right');
|
||||
} else {
|
||||
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
|
||||
}
|
||||
@@ -339,4 +410,4 @@ export class DrawPass {
|
||||
}
|
||||
return this.colorTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { AssetManager } from '../../mol-util/assets';
|
||||
|
||||
export const ImageParams = {
|
||||
transparentBackground: PD.Boolean(false),
|
||||
dpoitIterations: PD.Numeric(2, { min: 1, max: 10, step: 1 }),
|
||||
multiSample: PD.Group(MultiSampleParams),
|
||||
postprocessing: PD.Group(PostprocessingParams),
|
||||
marking: PD.Group(MarkingParams),
|
||||
@@ -48,10 +49,10 @@ export class ImagePass {
|
||||
get width() { return this._width; }
|
||||
get height() { return this._height; }
|
||||
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, props: Partial<ImageProps>) {
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, enableDpoit: boolean, props: Partial<ImageProps>) {
|
||||
this.props = { ...PD.getDefaultValues(ImageParams), ...props };
|
||||
|
||||
this.drawPass = new DrawPass(webgl, assetManager, 128, 128, enableWboit);
|
||||
this.drawPass = new DrawPass(webgl, assetManager, 128, 128, enableWboit, enableDpoit);
|
||||
this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass);
|
||||
this.multiSampleHelper = new MultiSampleHelper(this.multiSamplePass);
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ type Props = {
|
||||
postprocessing: PostprocessingProps
|
||||
marking: MarkingProps
|
||||
transparentBackground: boolean;
|
||||
dpoitIterations: number;
|
||||
}
|
||||
|
||||
type RenderContext = {
|
||||
|
||||
@@ -15,9 +15,9 @@ export class Passes {
|
||||
readonly pick: PickPass;
|
||||
readonly multiSample: MultiSamplePass;
|
||||
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, attribs: Partial<{ pickScale: number, enableWboit: boolean }> = {}) {
|
||||
constructor(private webgl: WebGLContext, assetManager: AssetManager, attribs: Partial<{ pickScale: number, enableWboit: boolean, enableDpoit: boolean }> = {}) {
|
||||
const { gl } = webgl;
|
||||
this.draw = new DrawPass(webgl, assetManager, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false);
|
||||
this.draw = new DrawPass(webgl, assetManager, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false, attribs.enableDpoit || false);
|
||||
this.pick = new PickPass(webgl, this.draw, attribs.pickScale || 0.25);
|
||||
this.multiSample = new MultiSamplePass(webgl, this.draw);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import { evaluateWboit_frag } from '../../mol-gl/shader/evaluate-wboit.frag';
|
||||
import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
|
||||
import { Vec2 } from '../../mol-math/linear-algebra';
|
||||
import { isDebugMode, isTimingMode } from '../../mol-util/debug';
|
||||
import { isWebGL2 } from '../../mol-gl/webgl/compat';
|
||||
import { Renderbuffer } from '../../mol-gl/webgl/renderbuffer';
|
||||
|
||||
const EvaluateWboitSchema = {
|
||||
...QuadSchema,
|
||||
@@ -50,6 +52,7 @@ export class WboitPass {
|
||||
private readonly framebuffer: Framebuffer;
|
||||
private readonly textureA: Texture;
|
||||
private readonly textureB: Texture;
|
||||
private readonly depthRenderbuffer: Renderbuffer;
|
||||
|
||||
private _supported = false;
|
||||
get supported() {
|
||||
@@ -87,6 +90,7 @@ export class WboitPass {
|
||||
if (width !== w || height !== h) {
|
||||
this.textureA.define(width, height);
|
||||
this.textureB.define(width, height);
|
||||
this.depthRenderbuffer.setSize(width, height);
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
}
|
||||
}
|
||||
@@ -106,6 +110,8 @@ export class WboitPass {
|
||||
|
||||
this.textureA.attachFramebuffer(this.framebuffer, 'color0');
|
||||
this.textureB.attachFramebuffer(this.framebuffer, 'color1');
|
||||
|
||||
this.depthRenderbuffer.attachFramebuffer(this.framebuffer);
|
||||
}
|
||||
|
||||
static isSupported(webgl: WebGLContext) {
|
||||
@@ -128,7 +134,7 @@ export class WboitPass {
|
||||
constructor(private webgl: WebGLContext, width: number, height: number) {
|
||||
if (!WboitPass.isSupported(webgl)) return;
|
||||
|
||||
const { resources } = webgl;
|
||||
const { resources, gl } = webgl;
|
||||
|
||||
this.textureA = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
this.textureA.define(width, height);
|
||||
@@ -136,6 +142,10 @@ export class WboitPass {
|
||||
this.textureB = resources.texture('image-float32', 'rgba', 'float', 'nearest');
|
||||
this.textureB.define(width, height);
|
||||
|
||||
this.depthRenderbuffer = isWebGL2(gl)
|
||||
? resources.renderbuffer('depth32f', 'depth', width, height)
|
||||
: resources.renderbuffer('depth16', 'depth', width, height);
|
||||
|
||||
this.renderable = getEvaluateWboitRenderable(webgl, this.textureA, this.textureB);
|
||||
this.framebuffer = resources.framebuffer();
|
||||
|
||||
|
||||
@@ -53,17 +53,17 @@ describe('renderer', () => {
|
||||
scene.commit();
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(9);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 5 : 0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(5);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(10);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 6 : 0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(6);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(12);
|
||||
|
||||
scene.remove(points);
|
||||
scene.commit();
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(1);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(5);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(10);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(6);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(12);
|
||||
|
||||
ctx.resources.destroy();
|
||||
expect(ctx.stats.resourceCounts.program).toBe(0);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { ValueCell } from '../../mol-util';
|
||||
@@ -167,6 +168,11 @@ export type GlobalUniformValues = Values<GlobalUniformSchema>
|
||||
|
||||
export const GlobalTextureSchema = {
|
||||
tDepth: TextureSpec('texture', 'depth', 'ushort', 'nearest'),
|
||||
|
||||
// dpoit
|
||||
tDpoitDepth: TextureSpec('texture', 'rg', 'float', 'nearest'),
|
||||
tDpoitFrontColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
tDpoitBackColor: TextureSpec('texture', 'rgba', 'float', 'nearest')
|
||||
} as const;
|
||||
export type GlobalTextureSchema = typeof GlobalTextureSchema
|
||||
export type GlobalTextureValues = Values<GlobalTextureSchema>
|
||||
@@ -236,7 +242,7 @@ export const TransparencySchema = {
|
||||
uTransparencyGridDim: UniformSpec('v3'),
|
||||
uTransparencyGridTransform: UniformSpec('v4'),
|
||||
tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'),
|
||||
dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
|
||||
dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance'])
|
||||
} as const;
|
||||
export type TransparencySchema = typeof TransparencySchema
|
||||
export type TransparencyValues = Values<TransparencySchema>
|
||||
@@ -327,4 +333,4 @@ export const BaseSchema = {
|
||||
invariantBoundingSphere: ValueSpec('sphere'),
|
||||
} as const;
|
||||
export type BaseSchema = typeof BaseSchema
|
||||
export type BaseValues = Values<BaseSchema>
|
||||
export type BaseValues = Values<BaseSchema>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { Viewport } from '../mol-canvas3d/camera/util';
|
||||
@@ -70,6 +71,9 @@ interface Renderer {
|
||||
renderBlendedVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
renderWboitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
renderWboitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
renderDpoitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
renderDpoitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, dpoitTextures: { depth: Texture, frontColor: Texture, backColor: Texture }) => void
|
||||
renderDpoitVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
|
||||
|
||||
setProps: (props: Partial<RendererProps>) => void
|
||||
setViewport: (x: number, y: number, width: number, height: number) => void
|
||||
@@ -141,7 +145,7 @@ namespace Renderer {
|
||||
const enum Flag {
|
||||
None = 0,
|
||||
BlendedFront = 1,
|
||||
BlendedBack = 2
|
||||
BlendedBack = 2,
|
||||
}
|
||||
|
||||
const enum Mask {
|
||||
@@ -268,7 +272,7 @@ namespace Renderer {
|
||||
}
|
||||
|
||||
if (r.values.dGeometryType.ref.value === 'directVolume') {
|
||||
if (variant !== 'colorWboit' && variant !== 'colorBlended') {
|
||||
if (variant !== 'colorDpoit' && variant !== 'colorWboit' && variant !== 'colorBlended') {
|
||||
return; // only color supported
|
||||
}
|
||||
|
||||
@@ -602,6 +606,71 @@ namespace Renderer {
|
||||
if (isTimingMode) ctx.timer.markEnd('Renderer.renderWboitTransparent');
|
||||
};
|
||||
|
||||
const renderDpoitOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
|
||||
if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitOpaque');
|
||||
state.disable(gl.BLEND);
|
||||
state.enable(gl.DEPTH_TEST);
|
||||
state.depthMask(true);
|
||||
|
||||
updateInternal(group, camera, depthTexture, Mask.Opaque, false);
|
||||
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
// TODO: simplify, handle in renderable.state???
|
||||
// uAlpha is updated in "render" so we need to recompute it here
|
||||
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
|
||||
if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
|
||||
renderObject(r, 'colorDpoit', Flag.None);
|
||||
}
|
||||
}
|
||||
if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitOpaque');
|
||||
};
|
||||
|
||||
const renderDpoitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, dpoitTextures: { depth: Texture, frontColor: Texture, backColor: Texture }) => {
|
||||
if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitTransparent');
|
||||
|
||||
state.enable(gl.BLEND);
|
||||
|
||||
arrayMapUpsert(sharedTexturesList, 'tDpoitDepth', dpoitTextures.depth);
|
||||
arrayMapUpsert(sharedTexturesList, 'tDpoitFrontColor', dpoitTextures.frontColor);
|
||||
arrayMapUpsert(sharedTexturesList, 'tDpoitBackColor', dpoitTextures.backColor);
|
||||
|
||||
updateInternal(group, camera, depthTexture, Mask.Transparent, false);
|
||||
|
||||
const { renderables } = group;
|
||||
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
|
||||
// TODO: simplify, handle in renderable.state???
|
||||
// uAlpha is updated in "render" so we need to recompute it here
|
||||
const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
|
||||
if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dPointStyle?.ref.value === 'fuzzy' || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
|
||||
renderObject(r, 'colorDpoit', Flag.None);
|
||||
}
|
||||
}
|
||||
if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitTransparent');
|
||||
};
|
||||
|
||||
const renderDpoitVolume = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
|
||||
if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitVolume');
|
||||
state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
state.enable(gl.BLEND);
|
||||
|
||||
updateInternal(group, camera, depthTexture, Mask.Transparent, false);
|
||||
|
||||
const { renderables } = group;
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
if (r.values.dGeometryType.ref.value === 'directVolume') {
|
||||
renderObject(r, 'colorDpoit', Flag.None);
|
||||
}
|
||||
}
|
||||
if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitVolume');
|
||||
};
|
||||
|
||||
return {
|
||||
clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => {
|
||||
state.enable(gl.SCISSOR_TEST);
|
||||
@@ -645,6 +714,9 @@ namespace Renderer {
|
||||
renderBlendedVolume,
|
||||
renderWboitOpaque,
|
||||
renderWboitTransparent,
|
||||
renderDpoitOpaque,
|
||||
renderDpoitTransparent,
|
||||
renderDpoitVolume,
|
||||
|
||||
setProps: (props: Partial<RendererProps>) => {
|
||||
if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) {
|
||||
@@ -762,4 +834,4 @@ namespace Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
export { Renderer };
|
||||
export { Renderer };
|
||||
|
||||
@@ -45,8 +45,8 @@ function calculateBoundingSphere(renderables: GraphicsRenderable[], boundingSphe
|
||||
}
|
||||
|
||||
function renderableSort(a: GraphicsRenderable, b: GraphicsRenderable) {
|
||||
const drawProgramIdA = (a.getProgram('colorBlended') || a.getProgram('colorWboit')).id;
|
||||
const drawProgramIdB = (b.getProgram('colorBlended') || a.getProgram('colorWboit')).id;
|
||||
const drawProgramIdA = (a.getProgram('colorBlended') || a.getProgram('colorWboit') || a.getProgram('colorDpoit')).id;
|
||||
const drawProgramIdB = (b.getProgram('colorBlended') || b.getProgram('colorWboit') || b.getProgram('colorDpoit')).id;
|
||||
const materialIdA = a.materialId;
|
||||
const materialIdB = b.materialId;
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ import { texture3d_from_1d_trilinear } from './shader/chunks/texture3d-from-1d-t
|
||||
import { texture3d_from_2d_linear } from './shader/chunks/texture3d-from-2d-linear.glsl';
|
||||
import { texture3d_from_2d_nearest } from './shader/chunks/texture3d-from-2d-nearest.glsl';
|
||||
import { wboit_write } from './shader/chunks/wboit-write.glsl';
|
||||
import { dpoit_write } from './shader/chunks/dpoit-write.glsl';
|
||||
|
||||
const ShaderChunks: { [k: string]: string } = {
|
||||
apply_fog,
|
||||
@@ -99,7 +100,8 @@ const ShaderChunks: { [k: string]: string } = {
|
||||
texture3d_from_1d_trilinear,
|
||||
texture3d_from_2d_linear,
|
||||
texture3d_from_2d_nearest,
|
||||
wboit_write
|
||||
wboit_write,
|
||||
dpoit_write
|
||||
};
|
||||
|
||||
const reInclude = /^(?!\/\/)\s*#include\s+(\S+)/gm;
|
||||
|
||||
20
src/mol-gl/shader/blend-back-dpoit.frag.ts
Normal file
20
src/mol-gl/shader/blend-back-dpoit.frag.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
export const blendBackDpoit_frag = `
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D tDpoitBackColor;
|
||||
uniform vec2 uTexSize;
|
||||
|
||||
void main() {
|
||||
vec2 coords = gl_FragCoord.xy / uTexSize;
|
||||
gl_FragColor = texture2D(tDpoitBackColor, coords);
|
||||
if (gl_FragColor.a == 0.0) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -12,8 +12,19 @@ if (!uTransparentBackground) {
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
|
||||
}
|
||||
} else {
|
||||
// pre-multiplied alpha expected for transparent background
|
||||
gl_FragColor.rgb *= fogAlpha;
|
||||
gl_FragColor.a = fogAlpha;
|
||||
#if defined(dRenderVariant_colorDpoit)
|
||||
if (gl_FragColor.a < 1.0) {
|
||||
// transparent objects are blended with background color
|
||||
gl_FragColor.a = fogAlpha;
|
||||
} else {
|
||||
// opaque objects need to be pre-multiplied alpha
|
||||
gl_FragColor.rgb *= fogAlpha;
|
||||
gl_FragColor.a = fogAlpha;
|
||||
}
|
||||
#else
|
||||
// pre-multiplied alpha expected for transparent background
|
||||
gl_FragColor.rgb *= fogAlpha;
|
||||
gl_FragColor.a = fogAlpha;
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
@@ -86,7 +86,7 @@ export const assign_material_color = `
|
||||
// apply per-group transparency
|
||||
#if defined(dTransparency) && (defined(dRenderVariant_pick) || defined(dRenderVariant_color))
|
||||
float ta = 1.0 - vTransparency;
|
||||
if (vTransparency < 0.2) ta = 1.0; // hard cutoff looks better
|
||||
if (vTransparency < 0.09) ta = 1.0; // hard cutoff looks better
|
||||
|
||||
#if defined(dRenderVariant_pick)
|
||||
if (ta < uPickingAlphaThreshold)
|
||||
|
||||
@@ -39,6 +39,12 @@ uniform int uMarkingType;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(dRenderVariant_colorDpoit)
|
||||
#define MAX_DPOIT_DEPTH 99999.0 // NOTE constant also set in TypeScript
|
||||
uniform sampler2D tDpoitDepth;
|
||||
uniform sampler2D tDpoitFrontColor;
|
||||
#endif
|
||||
|
||||
varying vec3 vModelPosition;
|
||||
varying vec3 vViewPosition;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const common = `
|
||||
// TODO find a better place for these convenience defines
|
||||
|
||||
#if defined(dRenderVariant_colorBlended) || defined(dRenderVariant_colorWboit)
|
||||
#if defined(dRenderVariant_colorBlended) || defined(dRenderVariant_colorWboit) || defined(dRenderVariant_colorDpoit)
|
||||
#define dRenderVariant_color
|
||||
#endif
|
||||
|
||||
|
||||
70
src/mol-gl/shader/chunks/dpoit-write.glsl.ts
Normal file
70
src/mol-gl/shader/chunks/dpoit-write.glsl.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
export const dpoit_write = `
|
||||
#if defined(dRenderVariant_colorDpoit)
|
||||
if (uRenderMask == MaskOpaque) {
|
||||
if (preFogAlpha < 1.0) {
|
||||
discard;
|
||||
}
|
||||
} else if (uRenderMask == MaskTransparent) {
|
||||
// the 'fragmentDepth > 0.99' check is to handle precision issues with packed depth
|
||||
vec2 coords = gl_FragCoord.xy / uDrawingBufferSize;
|
||||
if (preFogAlpha != 1.0 && (fragmentDepth < getDepth(coords) || fragmentDepth > 0.99)) {
|
||||
#ifdef dTransparentBackfaces_off
|
||||
if (interior) discard;
|
||||
#endif
|
||||
|
||||
// adapted from https://github.com/tsherif/webgl2examples
|
||||
// The MIT License, Copyright 2017 Tarek Sherif, Shuai Shao
|
||||
|
||||
vec2 lastDepth = texture2D(tDpoitDepth, coords).rg;
|
||||
vec4 lastFrontColor = texture2D(tDpoitFrontColor, coords);
|
||||
|
||||
vec4 fragColor = gl_FragColor;
|
||||
|
||||
// depth value always increases
|
||||
// so we can use MAX blend equation
|
||||
gl_FragData[2].rg = vec2(-MAX_DPOIT_DEPTH);
|
||||
|
||||
// front color always increases
|
||||
// so we can use MAX blend equation
|
||||
gl_FragColor = lastFrontColor;
|
||||
|
||||
// back color is separately blend afterwards each pass
|
||||
gl_FragData[1] = vec4(0.0);
|
||||
|
||||
float nearestDepth = - lastDepth.x;
|
||||
float furthestDepth = lastDepth.y;
|
||||
float alphaMultiplier = 1.0 - lastFrontColor.a;
|
||||
|
||||
if (fragmentDepth < nearestDepth || fragmentDepth > furthestDepth) {
|
||||
// Skip this depth since it's been peeled.
|
||||
return;
|
||||
}
|
||||
|
||||
if (fragmentDepth > nearestDepth && fragmentDepth < furthestDepth) {
|
||||
// This needs to be peeled.
|
||||
// The ones remaining after MAX blended for
|
||||
// all need-to-peel will be peeled next pass.
|
||||
gl_FragData[2].rg = vec2(-fragmentDepth, fragmentDepth);
|
||||
return;
|
||||
}
|
||||
|
||||
// write to back and front color buffer
|
||||
if (fragmentDepth == nearestDepth) {
|
||||
gl_FragColor.rgb += fragColor.rgb * fragColor.a * alphaMultiplier;
|
||||
gl_FragColor.a = 1.0 - alphaMultiplier * (1.0 - fragColor.a);
|
||||
} else {
|
||||
gl_FragData[1] += fragColor;
|
||||
}
|
||||
|
||||
} else {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
`;
|
||||
@@ -109,14 +109,14 @@ void main() {
|
||||
|
||||
vec3 vViewPosition = vModelPosition + intersection.x * rayDir;
|
||||
vViewPosition = (uView * vec4(vViewPosition, 1.0)).xyz;
|
||||
gl_FragDepthEXT = calcDepth(vViewPosition);
|
||||
float fragmentDepth = calcDepth(vViewPosition);
|
||||
|
||||
if (fragmentDepth < 0.0) discard;
|
||||
if (fragmentDepth > 1.0) discard;
|
||||
|
||||
gl_FragDepthEXT = fragmentDepth;
|
||||
|
||||
vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz;
|
||||
|
||||
if (gl_FragDepthEXT < 0.0) discard;
|
||||
if (gl_FragDepthEXT > 1.0) discard;
|
||||
|
||||
float fragmentDepth = gl_FragDepthEXT;
|
||||
#include assign_material_color
|
||||
|
||||
#if defined(dRenderVariant_pick)
|
||||
@@ -142,6 +142,7 @@ void main() {
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -356,4 +356,4 @@ void main() {
|
||||
float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0);
|
||||
#include wboit_write
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
17
src/mol-gl/shader/evaluate-dpoit.frag.ts
Normal file
17
src/mol-gl/shader/evaluate-dpoit.frag.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
export const evaluateDpoit_frag = `
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D tDpoitFrontColor;
|
||||
uniform vec2 uTexSize;
|
||||
|
||||
void main() {
|
||||
vec2 coords = gl_FragCoord.xy / uTexSize;
|
||||
gl_FragColor = texture2D(tDpoitFrontColor, coords);
|
||||
}
|
||||
`;
|
||||
@@ -159,6 +159,7 @@ void main() {
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -39,6 +39,7 @@ void main(){
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -62,6 +62,7 @@ void main() {
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -55,6 +55,7 @@ void main(){
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -70,17 +70,17 @@ void main(void){
|
||||
}
|
||||
|
||||
vec3 vViewPosition = cameraPos;
|
||||
gl_FragDepthEXT = calcDepth(vViewPosition);
|
||||
if (!flag && gl_FragDepthEXT >= 0.0) {
|
||||
gl_FragDepthEXT = 0.0 + (0.0000001 / vRadius);
|
||||
float fragmentDepth = calcDepth(vViewPosition);
|
||||
if (!flag && fragmentDepth >= 0.0) {
|
||||
fragmentDepth = 0.0 + (0.0000001 / vRadius);
|
||||
}
|
||||
|
||||
if (fragmentDepth < 0.0) discard;
|
||||
if (fragmentDepth > 1.0) discard;
|
||||
|
||||
gl_FragDepthEXT = fragmentDepth;
|
||||
|
||||
vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz;
|
||||
|
||||
if (gl_FragDepthEXT < 0.0) discard;
|
||||
if (gl_FragDepthEXT > 1.0) discard;
|
||||
|
||||
float fragmentDepth = gl_FragDepthEXT;
|
||||
#include assign_material_color
|
||||
|
||||
#if defined(dRenderVariant_pick)
|
||||
@@ -105,6 +105,7 @@ void main(void){
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -83,6 +83,7 @@ void main(){
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#include wboit_write
|
||||
#include dpoit_write
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { createAttributeBuffers, ElementsBuffer, AttributeKind } from './buffer';
|
||||
@@ -49,11 +50,12 @@ export interface RenderItem<T extends string> {
|
||||
|
||||
//
|
||||
|
||||
const GraphicsRenderVariant = { colorBlended: '', colorWboit: '', pick: '', depth: '', marking: '' };
|
||||
const GraphicsRenderVariant = { colorBlended: '', colorWboit: '', colorDpoit: '', pick: '', depth: '', marking: '' };
|
||||
export type GraphicsRenderVariant = keyof typeof GraphicsRenderVariant
|
||||
export const GraphicsRenderVariants = Object.keys(GraphicsRenderVariant) as GraphicsRenderVariant[];
|
||||
export const GraphicsRenderVariantsBlended = GraphicsRenderVariants.filter(v => v !== 'colorWboit');
|
||||
export const GraphicsRenderVariantsWboit = GraphicsRenderVariants.filter(v => v !== 'colorBlended');
|
||||
export const GraphicsRenderVariantsBlended = GraphicsRenderVariants.filter(v => !['colorWboit', 'colorDpoit'].includes(v));
|
||||
export const GraphicsRenderVariantsWboit = GraphicsRenderVariants.filter(v => !['colorBlended', 'colorDpoit'].includes(v));
|
||||
export const GraphicsRenderVariantsDpoit = GraphicsRenderVariants.filter(v => !['colorWboit', 'colorBlended'].includes(v));
|
||||
|
||||
const ComputeRenderVariant = { compute: '' };
|
||||
export type ComputeRenderVariant = keyof typeof ComputeRenderVariant
|
||||
@@ -367,4 +369,4 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
import { WebGLContext } from './context';
|
||||
@@ -31,7 +32,7 @@ export type TextureKindValue = {
|
||||
export type TextureValueType = ValueOf<TextureKindValue>
|
||||
export type TextureKind = keyof TextureKindValue
|
||||
export type TextureType = 'ubyte' | 'ushort' | 'float' | 'fp16' | 'int'
|
||||
export type TextureFormat = 'alpha' | 'rgb' | 'rgba' | 'depth'
|
||||
export type TextureFormat = 'alpha' | 'rg' | 'rgb' | 'rgba' | 'depth'
|
||||
/** Numbers are shortcuts for color attachment */
|
||||
export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
|
||||
export type TextureFilter = 'nearest' | 'linear'
|
||||
@@ -63,6 +64,10 @@ export function getFormat(gl: GLRenderingContext, format: TextureFormat, type: T
|
||||
case 'rgb':
|
||||
if (isWebGL2(gl) && type === 'int') return gl.RGB_INTEGER;
|
||||
return gl.RGB;
|
||||
case 'rg':
|
||||
if (isWebGL2(gl) && type === 'float') return gl.RG;
|
||||
else if (isWebGL2(gl) && type === 'int') return gl.RG_INTEGER;
|
||||
else throw new Error('texture format "rg" requires webgl2 and type "float" or int"');
|
||||
case 'rgba':
|
||||
if (isWebGL2(gl) && type === 'int') return gl.RGBA_INTEGER;
|
||||
return gl.RGBA;
|
||||
@@ -80,6 +85,13 @@ export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat,
|
||||
case 'fp16': return gl.R16F;
|
||||
case 'int': return gl.R32I;
|
||||
}
|
||||
case 'rg':
|
||||
switch (type) {
|
||||
case 'ubyte': return gl.RG;
|
||||
case 'float': return gl.RG32F;
|
||||
case 'fp16': return gl.RG16F;
|
||||
case 'int': return gl.RG32I;
|
||||
}
|
||||
case 'rgb':
|
||||
switch (type) {
|
||||
case 'ubyte': return gl.RGB;
|
||||
@@ -112,6 +124,7 @@ function getByteCount(format: TextureFormat, type: TextureType, width: number, h
|
||||
function getFormatSize(format: TextureFormat) {
|
||||
switch (format) {
|
||||
case 'alpha': return 1;
|
||||
case 'rg': return 2;
|
||||
case 'rgb': return 3;
|
||||
case 'rgba': return 4;
|
||||
case 'depth': return 4;
|
||||
|
||||
@@ -35,7 +35,7 @@ function Vec3() {
|
||||
|
||||
namespace Vec3 {
|
||||
export function zero(): Vec3 {
|
||||
const out = [0.1, 0.0, 0.0];
|
||||
const out = [0.1, 0.0, 0.0]; // ensure backing array of type double
|
||||
out[0] = 0;
|
||||
return out as any;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ const DnaAtomIdsList = [
|
||||
/** Used to reduce false positives for atom name-based type guessing */
|
||||
const NonPolymerNames = new Set([
|
||||
'FMN', 'NCN', 'FNS', 'FMA', 'ATP', 'ADP', 'AMP', 'GTP', 'GDP', 'GMP', // Mononucleotides
|
||||
'LIG'
|
||||
]);
|
||||
|
||||
const StandardComponents = (function () {
|
||||
|
||||
@@ -39,7 +39,7 @@ export function getAtomSiteTemplate(data: string, count: number) {
|
||||
};
|
||||
}
|
||||
|
||||
export function getAtomSite(sites: AtomSiteTemplate, hasTer: boolean): { [K in keyof mmCIF_Schema['atom_site'] | 'partial_charge']?: CifField } {
|
||||
export function getAtomSite(sites: AtomSiteTemplate, terIndices: Set<number>): { [K in keyof mmCIF_Schema['atom_site'] | 'partial_charge']?: CifField } {
|
||||
const pdbx_PDB_model_num = CifField.ofStrings(sites.pdbx_PDB_model_num);
|
||||
const auth_asym_id = CifField.ofTokens(sites.auth_asym_id);
|
||||
const auth_seq_id = CifField.ofTokens(sites.auth_seq_id);
|
||||
@@ -67,21 +67,17 @@ export function getAtomSite(sites: AtomSiteTemplate, hasTer: boolean): { [K in k
|
||||
const seqId = auth_seq_id.int(i);
|
||||
let atomId = auth_atom_id.str(i);
|
||||
|
||||
let asymIdChanged = false;
|
||||
|
||||
if (modelNum !== currModelNum) {
|
||||
asymIdCounts.clear();
|
||||
atomIdCounts.clear();
|
||||
currModelNum = modelNum;
|
||||
currAsymId = asymId;
|
||||
currSeqId = seqId;
|
||||
asymIdChanged = true;
|
||||
currLabelAsymId = asymId;
|
||||
} else if (currAsymId !== asymId) {
|
||||
atomIdCounts.clear();
|
||||
currAsymId = asymId;
|
||||
currSeqId = seqId;
|
||||
asymIdChanged = true;
|
||||
currLabelAsymId = asymId;
|
||||
} else if (currSeqId !== seqId) {
|
||||
atomIdCounts.clear();
|
||||
@@ -91,7 +87,7 @@ export function getAtomSite(sites: AtomSiteTemplate, hasTer: boolean): { [K in k
|
||||
if (asymIdCounts.has(asymId)) {
|
||||
// only change the chains name if there are TER records
|
||||
// otherwise assume repeated chain name use is from interleaved chains
|
||||
if (hasTer && asymIdChanged) {
|
||||
if (terIndices.has(i)) {
|
||||
const asymIdCount = asymIdCounts.get(asymId)! + 1;
|
||||
asymIdCounts.set(asymId, asymIdCount);
|
||||
currLabelAsymId = `${asymId}_${asymIdCount}`;
|
||||
|
||||
@@ -51,7 +51,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
|
||||
let modelNum = 0, modelStr = '';
|
||||
let conectRange: [number, number] | undefined = undefined;
|
||||
let hasTer = false;
|
||||
const terIndices = new Set<number>();
|
||||
|
||||
for (let i = 0, _i = lines.count; i < _i; i++) {
|
||||
let s = indices[2 * i], e = indices[2 * i + 1];
|
||||
@@ -164,7 +164,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
break;
|
||||
case 'T':
|
||||
if (substringStartsWith(data, s, e, 'TER')) {
|
||||
hasTer = true;
|
||||
terIndices.add(atomSite.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> {
|
||||
atomSite.label_entity_id[i] = entityBuilder.getEntityId(compId, moleculeType, asymIds.value(i));
|
||||
}
|
||||
|
||||
const atom_site = getAtomSite(atomSite, hasTer);
|
||||
const atom_site = getAtomSite(atomSite, terIndices);
|
||||
if (!isPdbqt) delete atom_site.partial_charge;
|
||||
|
||||
if (conectRange) {
|
||||
|
||||
@@ -65,7 +65,7 @@ export namespace ComponentBond {
|
||||
return e;
|
||||
}
|
||||
|
||||
const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount } = data;
|
||||
const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount, pdbx_ordinal } = data;
|
||||
|
||||
let entry = addEntry(comp_id.value(0)!);
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
@@ -74,6 +74,7 @@ export namespace ComponentBond {
|
||||
const nameB = atom_id_2.value(i)!;
|
||||
const order = value_order.value(i)!;
|
||||
const aromatic = pdbx_aromatic_flag.value(i) === 'y';
|
||||
const key = pdbx_ordinal.value(i);
|
||||
|
||||
if (entry.id !== id) {
|
||||
entry = addEntry(id);
|
||||
@@ -89,29 +90,29 @@ export namespace ComponentBond {
|
||||
case 'quad': ord = 4; break;
|
||||
}
|
||||
|
||||
entry.add(nameA, nameB, ord, flags);
|
||||
entry.add(nameA, nameB, ord, flags, key);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
export class Entry {
|
||||
readonly map: Map<string, Map<string, { order: number, flags: number }>> = new Map();
|
||||
readonly map: Map<string, Map<string, { order: number, flags: number, key: number }>> = new Map();
|
||||
|
||||
add(a: string, b: string, order: number, flags: number, swap = true) {
|
||||
add(a: string, b: string, order: number, flags: number, key: number, swap = true) {
|
||||
const e = this.map.get(a);
|
||||
if (e !== void 0) {
|
||||
const f = e.get(b);
|
||||
if (f === void 0) {
|
||||
e.set(b, { order, flags });
|
||||
e.set(b, { order, flags, key });
|
||||
}
|
||||
} else {
|
||||
const map = new Map<string, { order: number, flags: number }>();
|
||||
map.set(b, { order, flags });
|
||||
const map = new Map<string, { order: number, flags: number, key: number }>();
|
||||
map.set(b, { order, flags, key });
|
||||
this.map.set(a, map);
|
||||
}
|
||||
|
||||
if (swap) this.add(b, a, order, flags, false);
|
||||
if (swap) this.add(b, a, order, flags, key, false);
|
||||
}
|
||||
|
||||
constructor(public readonly id: string) { }
|
||||
|
||||
@@ -22,6 +22,8 @@ import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { InteractionFlag } from '../interactions/common';
|
||||
import { Unit } from '../../../mol-model/structure/structure';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { assertUnreachable } from '../../../mol-util/type-helpers';
|
||||
import { InteractionsSharedParams } from './shared';
|
||||
|
||||
function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InteractionsInterUnitParams>, mesh?: Mesh) {
|
||||
if (!structure.hasAtomic) return Mesh.createEmpty(mesh);
|
||||
@@ -31,7 +33,7 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
|
||||
const { contacts, unitsFeatures } = interactions;
|
||||
|
||||
const { edgeCount, edges } = contacts;
|
||||
const { sizeFactor } = props;
|
||||
const { sizeFactor, parentDisplay } = props;
|
||||
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh);
|
||||
|
||||
@@ -70,14 +72,48 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
|
||||
|
||||
if (child) {
|
||||
const b = edges[edgeIndex];
|
||||
const childUnitA = child.unitMap.get(b.unitA);
|
||||
if (!childUnitA) return true;
|
||||
|
||||
const unitA = structure.unitMap.get(b.unitA);
|
||||
const { offsets, members } = unitsFeatures.get(b.unitA);
|
||||
for (let i = offsets[b.indexA], il = offsets[b.indexA + 1]; i < il; ++i) {
|
||||
const eA = unitA.elements[members[i]];
|
||||
if (!SortedArray.has(childUnitA.elements, eA)) return true;
|
||||
if (parentDisplay === 'stub') {
|
||||
const childUnitA = child.unitMap.get(b.unitA);
|
||||
if (!childUnitA) return true;
|
||||
|
||||
const unitA = structure.unitMap.get(b.unitA);
|
||||
const { offsets, members } = unitsFeatures.get(b.unitA);
|
||||
for (let i = offsets[b.indexA], il = offsets[b.indexA + 1]; i < il; ++i) {
|
||||
const eA = unitA.elements[members[i]];
|
||||
if (!SortedArray.has(childUnitA.elements, eA)) return true;
|
||||
}
|
||||
} else if (parentDisplay === 'full' || parentDisplay === 'between') {
|
||||
let flagA = false;
|
||||
let flagB = false;
|
||||
|
||||
const childUnitA = child.unitMap.get(b.unitA);
|
||||
if (!childUnitA) {
|
||||
flagA = true;
|
||||
} else {
|
||||
const unitA = structure.unitMap.get(b.unitA);
|
||||
const { offsets, members } = unitsFeatures.get(b.unitA);
|
||||
for (let i = offsets[b.indexA], il = offsets[b.indexA + 1]; i < il; ++i) {
|
||||
const eA = unitA.elements[members[i]];
|
||||
if (!SortedArray.has(childUnitA.elements, eA)) flagA = true;
|
||||
}
|
||||
}
|
||||
|
||||
const childUnitB = child.unitMap.get(b.unitB);
|
||||
if (!childUnitB) {
|
||||
flagB = true;
|
||||
} else {
|
||||
const unitB = structure.unitMap.get(b.unitB);
|
||||
const { offsets, members } = unitsFeatures.get(b.unitB);
|
||||
for (let i = offsets[b.indexB], il = offsets[b.indexB + 1]; i < il; ++i) {
|
||||
const eB = unitB.elements[members[i]];
|
||||
if (!SortedArray.has(childUnitB.elements, eB)) flagB = true;
|
||||
}
|
||||
}
|
||||
|
||||
return parentDisplay === 'full' ? flagA && flagB : flagA === flagB;
|
||||
} else {
|
||||
assertUnreachable(parentDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,10 +137,7 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
|
||||
export const InteractionsInterUnitParams = {
|
||||
...ComplexMeshParams,
|
||||
...LinkCylinderParams,
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
|
||||
dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
|
||||
includeParent: PD.Boolean(false),
|
||||
...InteractionsSharedParams,
|
||||
};
|
||||
export type InteractionsInterUnitParams = typeof InteractionsInterUnitParams
|
||||
|
||||
@@ -121,7 +154,8 @@ export function InteractionsInterUnitVisual(materialId: number): ComplexVisual<I
|
||||
newProps.dashCount !== currentProps.dashCount ||
|
||||
newProps.dashScale !== currentProps.dashScale ||
|
||||
newProps.dashCap !== currentProps.dashCap ||
|
||||
newProps.radialSegments !== currentProps.radialSegments
|
||||
newProps.radialSegments !== currentProps.radialSegments ||
|
||||
newProps.parentDisplay !== currentProps.parentDisplay
|
||||
);
|
||||
|
||||
const interactionsHash = InteractionsProvider.get(newStructure).version;
|
||||
|
||||
@@ -22,6 +22,8 @@ import { Interactions } from '../interactions/interactions';
|
||||
import { InteractionFlag } from '../interactions/common';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { StructureGroup } from '../../../mol-repr/structure/visual/util/common';
|
||||
import { assertUnreachable } from '../../../mol-util/type-helpers';
|
||||
import { InteractionsSharedParams } from './shared';
|
||||
|
||||
async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<InteractionsIntraUnitParams>, mesh?: Mesh) {
|
||||
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
|
||||
@@ -38,7 +40,7 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
|
||||
|
||||
const { x, y, z, members, offsets } = features;
|
||||
const { edgeCount, a, b, edgeProps: { flag } } = contacts;
|
||||
const { sizeFactor } = props;
|
||||
const { sizeFactor, parentDisplay } = props;
|
||||
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh);
|
||||
|
||||
@@ -60,10 +62,31 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
|
||||
if (flag[edgeIndex] === InteractionFlag.Filtered) return true;
|
||||
|
||||
if (childUnit) {
|
||||
const f = a[edgeIndex];
|
||||
for (let i = offsets[f], jl = offsets[f + 1]; i < jl; ++i) {
|
||||
const e = unit.elements[members[offsets[i]]];
|
||||
if (!SortedArray.has(childUnit.elements, e)) return true;
|
||||
if (parentDisplay === 'stub') {
|
||||
const f = a[edgeIndex];
|
||||
for (let i = offsets[f], il = offsets[f + 1]; i < il; ++i) {
|
||||
const e = unit.elements[members[offsets[i]]];
|
||||
if (!SortedArray.has(childUnit.elements, e)) return true;
|
||||
}
|
||||
} else if (parentDisplay === 'full' || parentDisplay === 'between') {
|
||||
let flagA = false;
|
||||
let flagB = false;
|
||||
|
||||
const fA = a[edgeIndex];
|
||||
for (let i = offsets[fA], il = offsets[fA + 1]; i < il; ++i) {
|
||||
const eA = unit.elements[members[offsets[i]]];
|
||||
if (!SortedArray.has(childUnit.elements, eA)) flagA = true;
|
||||
}
|
||||
|
||||
const fB = b[edgeIndex];
|
||||
for (let i = offsets[fB], il = offsets[fB + 1]; i < il; ++i) {
|
||||
const eB = unit.elements[members[offsets[i]]];
|
||||
if (!SortedArray.has(childUnit.elements, eB)) flagB = true;
|
||||
}
|
||||
|
||||
return parentDisplay === 'full' ? flagA && flagB : flagA === flagB;
|
||||
} else {
|
||||
assertUnreachable(parentDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,10 +109,7 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
|
||||
export const InteractionsIntraUnitParams = {
|
||||
...UnitsMeshParams,
|
||||
...LinkCylinderParams,
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
|
||||
dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
|
||||
includeParent: PD.Boolean(false),
|
||||
...InteractionsSharedParams,
|
||||
};
|
||||
export type InteractionsIntraUnitParams = typeof InteractionsIntraUnitParams
|
||||
|
||||
@@ -106,7 +126,8 @@ export function InteractionsIntraUnitVisual(materialId: number): UnitsVisual<Int
|
||||
newProps.dashCount !== currentProps.dashCount ||
|
||||
newProps.dashScale !== currentProps.dashScale ||
|
||||
newProps.dashCap !== currentProps.dashCap ||
|
||||
newProps.radialSegments !== currentProps.radialSegments
|
||||
newProps.radialSegments !== currentProps.radialSegments ||
|
||||
newProps.parentDisplay !== currentProps.parentDisplay
|
||||
);
|
||||
|
||||
const interactionsHash = InteractionsProvider.get(newStructureGroup.structure).version;
|
||||
|
||||
16
src/mol-model-props/computed/representations/shared.ts
Normal file
16
src/mol-model-props/computed/representations/shared.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
|
||||
export const InteractionsSharedParams = {
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
|
||||
dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
|
||||
includeParent: PD.Boolean(false),
|
||||
parentDisplay: PD.Select('stub', PD.arrayToOptions(['stub', 'full', 'between'] as const), { description: 'Only has an effect when "includeParent" is enabled. "Stub" shows just the child side of interactions to the parent. "Full" shows both sides of interactions to the parent. "Between" shows only interactions to the parent.' }),
|
||||
};
|
||||
export type InteractionsSharedParams = typeof InteractionsSharedParams
|
||||
@@ -12,6 +12,7 @@ import * as modifiers from './query/queries/modifiers';
|
||||
import * as filters from './query/queries/filters';
|
||||
import * as combinators from './query/queries/combinators';
|
||||
import * as internal from './query/queries/internal';
|
||||
import * as atomset from './query/queries/atom-set';
|
||||
import { Predicates as pred } from './query/predicates';
|
||||
|
||||
export const Queries = {
|
||||
@@ -20,7 +21,8 @@ export const Queries = {
|
||||
modifiers,
|
||||
combinators,
|
||||
pred,
|
||||
internal
|
||||
internal,
|
||||
atomset
|
||||
};
|
||||
|
||||
export { StructureSelection, StructureQuery };
|
||||
export { StructureSelection, StructureQuery };
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Structure, StructureElement, Unit } from '../structure';
|
||||
@@ -113,6 +114,7 @@ class QueryContextBondInfo<U extends Unit = Unit> {
|
||||
bIndex: StructureElement.UnitIndex = 0 as StructureElement.UnitIndex;
|
||||
type: BondType = BondType.Flag.None;
|
||||
order: number = 0;
|
||||
key: number = -1;
|
||||
|
||||
private testFn: QueryPredicate = defaultBondTest;
|
||||
|
||||
|
||||
36
src/mol-model/structure/query/queries/atom-set.ts
Normal file
36
src/mol-model/structure/query/queries/atom-set.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Koya Sakuma
|
||||
* Adapted from MolQL implemtation of atom-set.ts
|
||||
*
|
||||
* Copyright (c) 2017 MolQL contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { StructureQuery } from '../query';
|
||||
import { StructureSelection } from '../selection';
|
||||
import { getCurrentStructureProperties } from './filters';
|
||||
import { QueryContext, QueryFn } from '../context';
|
||||
|
||||
|
||||
export function atomCount(ctx: QueryContext) {
|
||||
return ctx.currentStructure.elementCount;
|
||||
}
|
||||
|
||||
|
||||
export function countQuery(query: StructureQuery) {
|
||||
return (ctx: QueryContext) => {
|
||||
const sel = query(ctx);
|
||||
return StructureSelection.structureCount(sel);
|
||||
};
|
||||
}
|
||||
|
||||
export function propertySet(prop: QueryFn<any>) {
|
||||
return (ctx: QueryContext) => {
|
||||
const set = new Set();
|
||||
return getCurrentStructureProperties(ctx, prop, set);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 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 { SetUtils } from '../../../../mol-util/set';
|
||||
import { Unit } from '../../structure';
|
||||
import { QueryContext, QueryFn, QueryPredicate } from '../context';
|
||||
import { QueryContext, QueryFn } from '../context';
|
||||
import { StructureQuery } from '../query';
|
||||
import { StructureSelection } from '../selection';
|
||||
import { structureAreIntersecting } from '../utils/structure-set';
|
||||
@@ -16,7 +17,7 @@ import { Structure } from '../../structure/structure';
|
||||
import { StructureElement } from '../../structure/element';
|
||||
import { SortedArray } from '../../../../mol-data/int';
|
||||
|
||||
export function pick(query: StructureQuery, pred: QueryPredicate): StructureQuery {
|
||||
export function pick(query: StructureQuery, pred: QueryFn<any>): StructureQuery {
|
||||
return ctx => {
|
||||
const sel = query(ctx);
|
||||
const ret = StructureSelection.LinearBuilder(ctx.inputStructure);
|
||||
@@ -50,9 +51,7 @@ export function first(query: StructureQuery): StructureQuery {
|
||||
};
|
||||
}
|
||||
|
||||
export interface UnitTypeProperties { atomic?: QueryFn, coarse?: QueryFn }
|
||||
|
||||
export function getCurrentStructureProperties(ctx: QueryContext, props: UnitTypeProperties, set: Set<any>) {
|
||||
export function getCurrentStructureProperties(ctx: QueryContext, props: QueryFn<any>, set: Set<any>) {
|
||||
const { units } = ctx.currentStructure;
|
||||
const l = ctx.pushCurrentElement();
|
||||
|
||||
@@ -61,9 +60,9 @@ export function getCurrentStructureProperties(ctx: QueryContext, props: UnitType
|
||||
l.unit = unit;
|
||||
const elements = unit.elements;
|
||||
|
||||
let fn;
|
||||
if (Unit.isAtomic(unit)) fn = props.atomic;
|
||||
else fn = props.coarse;
|
||||
const fn = props;
|
||||
// if (Unit.isAtomic(unit)) fn = props.atomic;
|
||||
// else fn = props.coarse;
|
||||
if (!fn) continue;
|
||||
|
||||
for (let j = 0, _j = elements.length; j < _j; j++) {
|
||||
@@ -77,7 +76,7 @@ export function getCurrentStructureProperties(ctx: QueryContext, props: UnitType
|
||||
return set;
|
||||
}
|
||||
|
||||
function getSelectionProperties(ctx: QueryContext, query: StructureQuery, props: UnitTypeProperties) {
|
||||
function getSelectionProperties(ctx: QueryContext, query: StructureQuery, props: QueryFn<any>) {
|
||||
const set = new Set();
|
||||
|
||||
const sel = query(ctx);
|
||||
@@ -92,7 +91,7 @@ function getSelectionProperties(ctx: QueryContext, query: StructureQuery, props:
|
||||
return set;
|
||||
}
|
||||
|
||||
export function withSameAtomProperties(query: StructureQuery, propertySource: StructureQuery, props: UnitTypeProperties): StructureQuery {
|
||||
export function withSameAtomProperties(query: StructureQuery, propertySource: StructureQuery, props: QueryFn<any>): StructureQuery {
|
||||
return ctx => {
|
||||
const sel = query(ctx);
|
||||
const propSet = getSelectionProperties(ctx, propertySource, props);
|
||||
@@ -102,7 +101,7 @@ export function withSameAtomProperties(query: StructureQuery, propertySource: St
|
||||
StructureSelection.forEach(sel, (s, i) => {
|
||||
ctx.currentStructure = s;
|
||||
const currentProps = getCurrentStructureProperties(ctx, props, new Set());
|
||||
if (SetUtils.isSuperset(currentProps, propSet)) {
|
||||
if (SetUtils.isSuperset(propSet, currentProps)) {
|
||||
ret.add(s);
|
||||
}
|
||||
|
||||
@@ -248,7 +247,7 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
|
||||
|
||||
const inputUnit = input.unitMap.get(unit.id) as Unit.Atomic;
|
||||
|
||||
const { offset, b, edgeProps: { flags, order } } = inputUnit.bonds;
|
||||
const { offset, b, edgeProps: { flags, order, key } } = inputUnit.bonds;
|
||||
const bondedUnits = interBonds.getConnectedUnits(unit.id);
|
||||
const buCount = bondedUnits.length;
|
||||
|
||||
@@ -273,6 +272,7 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
|
||||
atomicBond.bIndex = b[l] as StructureElement.UnitIndex;
|
||||
atomicBond.type = flags[l];
|
||||
atomicBond.order = order[l];
|
||||
atomicBond.key = key[l];
|
||||
if (atomicBond.test(queryCtx, true)) return true;
|
||||
}
|
||||
|
||||
@@ -295,6 +295,7 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) {
|
||||
atomicBond.bIndex = bond.indexB;
|
||||
atomicBond.type = bond.props.flag;
|
||||
atomicBond.order = bond.props.order;
|
||||
atomicBond.key = bond.props.key;
|
||||
if (atomicBond.test(queryCtx, true)) return true;
|
||||
}
|
||||
}
|
||||
@@ -342,4 +343,4 @@ export function isConnectedTo({ query, target, disjunct, invert, bondTest }: IsC
|
||||
|
||||
return ret.getSelection();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 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>
|
||||
@@ -322,7 +322,7 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery {
|
||||
for (const unit of structure.units) {
|
||||
if (unit.kind !== Unit.Kind.Atomic) continue;
|
||||
|
||||
const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order } } = unit.bonds;
|
||||
const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order, key } } = unit.bonds;
|
||||
atomicBond.a.unit = unit;
|
||||
atomicBond.b.unit = unit;
|
||||
for (let i = 0 as StructureElement.UnitIndex, _i = unit.elements.length; i < _i; i++) {
|
||||
@@ -335,6 +335,7 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery {
|
||||
atomicBond.b.element = unit.elements[intraBondB[lI]];
|
||||
atomicBond.type = flags[lI];
|
||||
atomicBond.order = order[lI];
|
||||
atomicBond.key = key[lI];
|
||||
// No need to "swap test" because each bond direction will be visited eventually.
|
||||
if (atomicBond.test(ctx, false)) {
|
||||
const b = structure.subsetBuilder(false);
|
||||
@@ -358,6 +359,7 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery {
|
||||
atomicBond.bIndex = bond.indexB;
|
||||
atomicBond.order = bond.props.order;
|
||||
atomicBond.type = bond.props.flag;
|
||||
atomicBond.key = bond.props.key;
|
||||
|
||||
// No need to "swap test" because each bond direction will be visited eventually.
|
||||
if (atomicBond.test(ctx, false)) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 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 { Segmentation, SortedArray } from '../../../../mol-data/int';
|
||||
@@ -370,7 +371,7 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
|
||||
}
|
||||
|
||||
const inputUnitA = inputStructure.unitMap.get(unit.id) as Unit.Atomic;
|
||||
const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order } } = inputUnitA.bonds;
|
||||
const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order, key } } = inputUnitA.bonds;
|
||||
|
||||
atomicBond.setStructure(inputStructure);
|
||||
|
||||
@@ -397,6 +398,7 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
|
||||
atomicBond.b.element = bElement;
|
||||
atomicBond.type = flags[lI];
|
||||
atomicBond.order = order[lI];
|
||||
atomicBond.key = key[lI];
|
||||
|
||||
if (atomicBond.test(ctx, true)) {
|
||||
builder.addToUnit(unit.id, bElement);
|
||||
@@ -427,6 +429,7 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
|
||||
atomicBond.b.element = bElement;
|
||||
atomicBond.type = bond.props.flag;
|
||||
atomicBond.order = bond.props.order;
|
||||
atomicBond.key = bond.props.key;
|
||||
|
||||
if (atomicBond.test(ctx, true)) {
|
||||
builder.addToUnit(bondedUnit.unitB, bElement);
|
||||
|
||||
@@ -15,16 +15,17 @@ import { InterUnitGraph } from '../../../../../mol-math/graph/inter-unit-graph';
|
||||
type IntraUnitBonds = IntAdjacencyGraph<StructureElement.UnitIndex, {
|
||||
readonly order: ArrayLike<number>,
|
||||
readonly flags: ArrayLike<BondType.Flag>
|
||||
readonly key: ArrayLike<number>,
|
||||
}, {
|
||||
/** can remap even with dynamicBonds on, e.g., for water molecules */
|
||||
readonly canRemap?: boolean
|
||||
}>
|
||||
|
||||
namespace IntraUnitBonds {
|
||||
export const Empty: IntraUnitBonds = IntAdjacencyGraph.create([], [], [], 0, { flags: [], order: [] });
|
||||
export const Empty: IntraUnitBonds = IntAdjacencyGraph.create([], [], [], 0, { flags: [], order: [], key: [] });
|
||||
}
|
||||
|
||||
type InterUnitEdgeProps = { readonly order: number, readonly flag: BondType.Flag }
|
||||
type InterUnitEdgeProps = { readonly order: number, readonly flag: BondType.Flag, readonly key: number }
|
||||
|
||||
class InterUnitBonds extends InterUnitGraph<number, StructureElement.UnitIndex, InterUnitEdgeProps> {
|
||||
/** Get inter-unit bond given a bond-location */
|
||||
|
||||
@@ -80,7 +80,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
|
||||
if (!props.forceCompute && indexPairs) {
|
||||
const { maxDistance } = indexPairs;
|
||||
const { offset, b, edgeProps: { order, distance, flag } } = indexPairs.bonds;
|
||||
const { offset, b, edgeProps: { order, distance, flag, key } } = indexPairs.bonds;
|
||||
|
||||
const srcA = sourceIndex.value(aI);
|
||||
const aeI = getElementIdx(type_symbolA.value(aI));
|
||||
@@ -113,7 +113,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
}
|
||||
|
||||
if (add) {
|
||||
builder.add(_aI, _bI, { order: order[i], flag: flag[i] });
|
||||
builder.add(_aI, _bI, { order: order[i], flag: flag[i], key: key[i] });
|
||||
}
|
||||
}
|
||||
continue; // assume `indexPairs` supplies all bonds
|
||||
@@ -131,7 +131,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
// check if the bond is within MAX_RADIUS for this pair of units
|
||||
if (getDistance(unitA, aI, unitB, p.atomIndex) > maxRadius) continue;
|
||||
|
||||
builder.add(_aI, _bI, { order: se.order, flag: se.flags });
|
||||
builder.add(_aI, _bI, { order: se.order, flag: se.flags, key: se.rowIndex });
|
||||
added = true;
|
||||
}
|
||||
// assume, for an atom, that if any inter unit bond is given
|
||||
@@ -187,7 +187,8 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
const compIdB = label_comp_idB.value(residueIndexB[bI]);
|
||||
builder.add(_aI, _bI, {
|
||||
order: getInterBondOrderFromTable(compIdA, compIdB, atomIdA, atomIdB),
|
||||
flag: (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed
|
||||
flag: (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed,
|
||||
key: -1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,17 +24,19 @@ import { Model } from '../../../model/model';
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3distance = Vec3.distance;
|
||||
|
||||
function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], atomCount: number, canRemap: boolean): IntraUnitBonds {
|
||||
function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], _key: number[], atomCount: number, canRemap: boolean): IntraUnitBonds {
|
||||
const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB);
|
||||
const flags = new Uint16Array(builder.slotCount);
|
||||
const order = new Int8Array(builder.slotCount);
|
||||
const key = new Uint32Array(builder.slotCount);
|
||||
for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
|
||||
builder.addNextEdge();
|
||||
builder.assignProperty(flags, _flags[i]);
|
||||
builder.assignProperty(order, _order[i]);
|
||||
builder.assignProperty(key, _key[i]);
|
||||
}
|
||||
|
||||
return builder.createGraph({ flags, order }, { canRemap });
|
||||
return builder.createGraph({ flags, order, key }, { canRemap });
|
||||
}
|
||||
|
||||
const tmpDistVecA = Vec3();
|
||||
@@ -53,7 +55,7 @@ function findIndexPairBonds(unit: Unit.Atomic) {
|
||||
const { type_symbol } = unit.model.atomicHierarchy.atoms;
|
||||
const atomCount = unit.elements.length;
|
||||
const { maxDistance } = indexPairs;
|
||||
const { offset, b, edgeProps: { order, distance, flag } } = indexPairs.bonds;
|
||||
const { offset, b, edgeProps: { order, distance, flag, key } } = indexPairs.bonds;
|
||||
|
||||
const { atomSourceIndex: sourceIndex } = unit.model.atomicHierarchy;
|
||||
const { invertedIndex } = Model.getInvertedAtomSourceIndex(unit.model);
|
||||
@@ -62,6 +64,7 @@ function findIndexPairBonds(unit: Unit.Atomic) {
|
||||
const atomB: StructureElement.UnitIndex[] = [];
|
||||
const flags: number[] = [];
|
||||
const orders: number[] = [];
|
||||
const keys: number[] = [];
|
||||
|
||||
for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) {
|
||||
const aI = atoms[_aI];
|
||||
@@ -104,11 +107,12 @@ function findIndexPairBonds(unit: Unit.Atomic) {
|
||||
atomB[atomB.length] = _bI;
|
||||
orders[orders.length] = order[i];
|
||||
flags[flags.length] = flag[i];
|
||||
keys[keys.length] = key[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return getGraph(atomA, atomB, orders, flags, atomCount, false);
|
||||
return getGraph(atomA, atomB, orders, flags, keys, atomCount, false);
|
||||
}
|
||||
|
||||
function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBonds {
|
||||
@@ -132,9 +136,10 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
const atomB: StructureElement.UnitIndex[] = [];
|
||||
const flags: number[] = [];
|
||||
const order: number[] = [];
|
||||
const key: number[] = [];
|
||||
|
||||
let lastResidue = -1;
|
||||
let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0;
|
||||
let componentMap: Map<string, Map<string, { flags: number, order: number, key: number }>> | undefined = void 0;
|
||||
|
||||
let isWatery = true, isDictionaryBased = true, isSequenced = true;
|
||||
|
||||
@@ -162,6 +167,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
atomB[atomB.length] = _bI;
|
||||
flags[flags.length] = se.flags;
|
||||
order[order.length] = se.order;
|
||||
key[key.length] = se.rowIndex;
|
||||
|
||||
if (!hasStructConn) structConnAdded.clear();
|
||||
hasStructConn = true;
|
||||
@@ -230,6 +236,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
flag |= BondType.Flag.MetallicCoordination;
|
||||
}
|
||||
flags[flags.length] = flag;
|
||||
key[key.length] = e.key;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -243,6 +250,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
atomB[atomB.length] = _bI;
|
||||
order[order.length] = getIntraBondOrderFromTable(compId, atomIdA, label_atom_id.value(bI));
|
||||
flags[flags.length] = (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed;
|
||||
key[key.length] = -1;
|
||||
|
||||
const seqIdB = label_seq_id.value(rbI);
|
||||
|
||||
@@ -253,7 +261,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
}
|
||||
|
||||
const canRemap = isWatery || (isDictionaryBased && isSequenced);
|
||||
return getGraph(atomA, atomB, order, flags, atomCount, canRemap);
|
||||
return getGraph(atomA, atomB, order, flags, key, atomCount, canRemap);
|
||||
}
|
||||
|
||||
function computeIntraUnitBonds(unit: Unit.Atomic, props?: Partial<BondComputationProps>) {
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
import { Segmentation } from '../../../../mol-data/int';
|
||||
import { MinimizeRmsd } from '../../../../mol-math/linear-algebra/3d/minimize-rmsd';
|
||||
import { SIFTSMapping } from '../../../../mol-model-props/sequence/sifts-mapping';
|
||||
import { ElementIndex } from '../../model/indexing';
|
||||
import { ElementIndex, ResidueIndex } from '../../model/indexing';
|
||||
import { StructureElement } from '../element';
|
||||
import { Structure } from '../structure';
|
||||
import { Unit } from '../unit';
|
||||
|
||||
@@ -24,11 +25,16 @@ export interface AlignmentResult {
|
||||
failedPairs: [number, number][]
|
||||
}
|
||||
|
||||
export function alignAndSuperposeWithSIFTSMapping(structures: Structure[], options?: { traceOnly?: boolean }): AlignmentResult {
|
||||
type IncludeResidueTest = (traceElementOrFirstAtom: StructureElement.Location<Unit.Atomic>, residueIndex: ResidueIndex, startIndex: ElementIndex, endIndex: ElementIndex) => boolean
|
||||
|
||||
export function alignAndSuperposeWithSIFTSMapping(
|
||||
structures: Structure[],
|
||||
options?: { traceOnly?: boolean, includeResidueTest?: IncludeResidueTest }
|
||||
): AlignmentResult {
|
||||
const indexMap = new Map<string, IndexEntry>();
|
||||
|
||||
for (let i = 0; i < structures.length; i++) {
|
||||
buildIndex(structures[i], indexMap, i, options?.traceOnly ?? true);
|
||||
buildIndex(structures[i], indexMap, i, options?.traceOnly ?? true, options?.includeResidueTest ?? _includeAllResidues);
|
||||
}
|
||||
|
||||
const index = Array.from(indexMap.values());
|
||||
@@ -137,11 +143,16 @@ interface IndexEntry {
|
||||
pivots: { [i: number]: [unit: Unit.Atomic, start: ElementIndex, end: ElementIndex] | undefined }
|
||||
}
|
||||
|
||||
function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: number, traceOnly: boolean) {
|
||||
function _includeAllResidues() { return true; }
|
||||
|
||||
function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: number, traceOnly: boolean, includeTest: IncludeResidueTest) {
|
||||
const loc = StructureElement.Location.create<Unit.Atomic>(structure);
|
||||
|
||||
for (const unit of structure.units) {
|
||||
if (unit.kind !== Unit.Kind.Atomic) continue;
|
||||
|
||||
const { elements, model } = unit;
|
||||
loc.unit = unit;
|
||||
|
||||
const map = SIFTSMapping.Provider.get(model).value;
|
||||
if (!map) return;
|
||||
@@ -161,9 +172,11 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
|
||||
|
||||
if (!dbName[rI]) continue;
|
||||
|
||||
const traceElement = traceElementIndex[rI];
|
||||
|
||||
let start, end;
|
||||
if (traceOnly) {
|
||||
start = traceElementIndex[rI];
|
||||
start = traceElement;
|
||||
if (start === -1) continue;
|
||||
end = start + 1 as ElementIndex;
|
||||
} else {
|
||||
@@ -171,6 +184,9 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu
|
||||
end = elements[residueSegment.end - 1] + 1 as ElementIndex;
|
||||
}
|
||||
|
||||
loc.element = (traceElement >= 0 ? traceElement : start) as ElementIndex;
|
||||
if (!includeTest(loc, rI, start, end)) continue;
|
||||
|
||||
const key = `${dbName[rI]}-${accession[rI]}-${num[rI]}`;
|
||||
|
||||
if (!index.has(key)) {
|
||||
|
||||
@@ -90,6 +90,7 @@ const DownloadStructure = StateAction.build({
|
||||
url: PD.Url(''),
|
||||
format: PD.Select<BuiltInTrajectoryFormat>('mmcif', PD.arrayToOptions(BuiltInTrajectoryFormats.map(f => f[0]), f => f)),
|
||||
isBinary: PD.Boolean(false),
|
||||
label: PD.Optional(PD.Text('')),
|
||||
options
|
||||
}, { isFlat: true, label: 'URL' })
|
||||
})
|
||||
@@ -104,7 +105,7 @@ const DownloadStructure = StateAction.build({
|
||||
|
||||
switch (src.name) {
|
||||
case 'url':
|
||||
downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary }];
|
||||
downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary, label: src.params.label || undefined }];
|
||||
format = src.params.format;
|
||||
break;
|
||||
case 'pdb':
|
||||
|
||||
@@ -41,8 +41,10 @@ export namespace StructureRepresentationPresetProvider {
|
||||
quality: PD.Optional(PD.Select<VisualQuality>('auto', VisualQualityOptions)),
|
||||
theme: PD.Optional(PD.Group({
|
||||
globalName: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
|
||||
globalColorParams: PD.Optional(PD.Value<any>({}, { isHidden: true })),
|
||||
carbonColor: PD.Optional(PD.Select('chain-id', PD.arrayToOptions(['chain-id', 'operator-name', 'element-symbol'] as const))),
|
||||
symmetryColor: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
|
||||
symmetryColorParams: PD.Optional(PD.Value<any>({}, { isHidden: true })),
|
||||
focus: PD.Optional(PD.Group({
|
||||
name: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
|
||||
params: PD.Optional(PD.Value<ColorTheme.BuiltInParams<ColorTheme.BuiltIn>>({} as any))
|
||||
@@ -76,13 +78,15 @@ export namespace StructureRepresentationPresetProvider {
|
||||
if (params.ignoreLight !== void 0) typeParams.ignoreLight = !!params.ignoreLight;
|
||||
const color: ColorTheme.BuiltIn | undefined = params.theme?.globalName ? params.theme?.globalName : void 0;
|
||||
const ballAndStickColor: ColorTheme.BuiltInParams<'element-symbol'> = params.theme?.carbonColor !== undefined
|
||||
? { carbonColor: getCarbonColorParams(params.theme?.carbonColor) }
|
||||
: { };
|
||||
? { carbonColor: getCarbonColorParams(params.theme?.carbonColor), ...params.theme?.globalColorParams }
|
||||
: { ...params.theme?.globalColorParams };
|
||||
const symmetryColor: ColorTheme.BuiltIn | undefined = structure && params.theme?.symmetryColor
|
||||
? isSymmetry(structure) ? params.theme?.symmetryColor : color
|
||||
: color;
|
||||
const symmetryColorParams = params.theme?.symmetryColorParams ? { ...params.theme?.globalColorParams, ...params.theme?.symmetryColorParams } : { ...params.theme?.globalColorParams };
|
||||
const globalColorParams = params.theme?.globalColorParams ? { ...params.theme?.globalColorParams } : undefined;
|
||||
|
||||
return { update, builder, color, symmetryColor, typeParams, ballAndStickColor };
|
||||
return { update, builder, color, symmetryColor, symmetryColorParams, globalColorParams, typeParams, ballAndStickColor };
|
||||
}
|
||||
|
||||
export function updateFocusRepr<T extends ColorTheme.BuiltIn>(plugin: PluginContext, structure: Structure, themeName: T | undefined, themeParams: ColorTheme.BuiltInParams<T> | undefined) {
|
||||
@@ -177,18 +181,18 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
|
||||
const waterType = (components.water?.obj?.data?.elementCount || 0) > 50_000 ? 'line' : 'ball-and-stick';
|
||||
const lipidType = (components.lipid?.obj?.data?.elementCount || 0) > 20_000 ? 'line' : 'ball-and-stick';
|
||||
|
||||
const { update, builder, typeParams, color, symmetryColor, ballAndStickColor } = reprBuilder(plugin, params, structure);
|
||||
const { update, builder, typeParams, color, symmetryColor, symmetryColorParams, globalColorParams, ballAndStickColor } = reprBuilder(plugin, params, structure);
|
||||
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'polymer' }),
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'polymer' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'ligand' }),
|
||||
nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'non-standard' }),
|
||||
branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.3 }, color, colorParams: ballAndStickColor }, { tag: 'branched-ball-and-stick' }),
|
||||
branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }, { tag: 'branched-snfg-3d' }),
|
||||
water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6, visuals: waterType === 'line' ? ['intra-bond', 'element-point'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'water' }),
|
||||
ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ion' }),
|
||||
lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6, visuals: lipidType === 'line' ? ['intra-bond'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'lipid' }),
|
||||
coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id' }, { tag: 'coarse' })
|
||||
branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color, colorParams: globalColorParams }, { tag: 'branched-snfg-3d' }),
|
||||
water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6, visuals: waterType === 'line' ? ['intra-bond', 'element-point'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams } }, { tag: 'water' }),
|
||||
ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams } }, { tag: 'ion' }),
|
||||
lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6, visuals: lipidType === 'line' ? ['intra-bond'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams } }, { tag: 'lipid' }),
|
||||
coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id', colorParams: globalColorParams }, { tag: 'coarse' })
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: false });
|
||||
@@ -223,11 +227,11 @@ const proteinAndNucleic = StructureRepresentationPresetProvider({
|
||||
smoothness: structure.isCoarseGrained ? 1.0 : 1.5,
|
||||
};
|
||||
|
||||
const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
|
||||
const { update, builder, typeParams, symmetryColor, symmetryColorParams } = reprBuilder(plugin, params, structure);
|
||||
|
||||
const representations = {
|
||||
protein: builder.buildRepresentation(update, components.protein, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'protein' }),
|
||||
nucleic: builder.buildRepresentation(update, components.nucleic, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'nucleic' })
|
||||
protein: builder.buildRepresentation(update, components.protein, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'protein' }),
|
||||
nucleic: builder.buildRepresentation(update, components.nucleic, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'nucleic' })
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -275,11 +279,11 @@ const coarseSurface = StructureRepresentationPresetProvider({
|
||||
});
|
||||
}
|
||||
|
||||
const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
|
||||
const { update, builder, typeParams, symmetryColor, symmetryColorParams } = reprBuilder(plugin, params, structure);
|
||||
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'polymer' }),
|
||||
lipid: builder.buildRepresentation(update, components.lipid, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'lipid' })
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'polymer' }),
|
||||
lipid: builder.buildRepresentation(update, components.lipid, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'lipid' })
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -309,10 +313,10 @@ const polymerCartoon = StructureRepresentationPresetProvider({
|
||||
sizeFactor: structure.isCoarseGrained ? 0.8 : 0.2
|
||||
};
|
||||
|
||||
const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
|
||||
const { update, builder, typeParams, symmetryColor, symmetryColorParams } = reprBuilder(plugin, params, structure);
|
||||
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'polymer' })
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'polymer' })
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -367,9 +371,9 @@ const atomicDetail = StructureRepresentationPresetProvider({
|
||||
});
|
||||
}
|
||||
|
||||
const { update, builder, typeParams, color, ballAndStickColor } = reprBuilder(plugin, params, structure);
|
||||
const { update, builder, typeParams, color, ballAndStickColor, globalColorParams } = reprBuilder(plugin, params, structure);
|
||||
const colorParams = lowResidueElementRatio && !bondsGiven
|
||||
? { carbonColor: { name: 'element-symbol', params: {} } }
|
||||
? { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams }
|
||||
: ballAndStickColor;
|
||||
|
||||
const representations = {
|
||||
@@ -377,7 +381,7 @@ const atomicDetail = StructureRepresentationPresetProvider({
|
||||
};
|
||||
if (showCarbohydrateSymbol) {
|
||||
Object.assign(representations, {
|
||||
snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color }, { tag: 'snfg-3d' }),
|
||||
snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color, colorParams: globalColorParams }, { tag: 'snfg-3d' }),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ export class StructureRepresentationBuilder {
|
||||
}
|
||||
|
||||
applyPreset<K extends keyof PresetStructureRepresentations>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, preset: K, params?: StructureRepresentationPresetProvider.Params<PresetStructureRepresentations[K]>): Promise<StructureRepresentationPresetProvider.State<PresetStructureRepresentations[K]>> | undefined
|
||||
applyPreset<P = any, S = {}>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, provider: StructureRepresentationPresetProvider<P, S>, params?: P): Promise<S> | undefined
|
||||
applyPreset<P = any, S extends {} = {}>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, provider: StructureRepresentationPresetProvider<P, S>, params?: P): Promise<S> | undefined
|
||||
applyPreset(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, providerId: string, params?: any): Promise<any> | undefined
|
||||
applyPreset(parent: StateObjectRef, providerRef: string | StructureRepresentationPresetProvider, params?: any): Promise<any> | undefined {
|
||||
const provider = this.resolveProvider(providerRef);
|
||||
|
||||
@@ -42,7 +42,7 @@ export class PluginComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export class StatefulPluginComponent<State> extends PluginComponent {
|
||||
export class StatefulPluginComponent<State extends {}> extends PluginComponent {
|
||||
private _state: State;
|
||||
|
||||
protected updateState(...states: Partial<State>[]): boolean {
|
||||
|
||||
@@ -75,7 +75,7 @@ export const CifCoreProvider: TrajectoryFormatProvider = {
|
||||
visuals: defaultVisuals
|
||||
};
|
||||
|
||||
function directTrajectory<P>(transformer: StateTransformer<PluginStateObject.Data.String | PluginStateObject.Data.Binary, PluginStateObject.Molecule.Trajectory, P>, transformerParams?: P): TrajectoryFormatProvider['parse'] {
|
||||
function directTrajectory<P extends {}>(transformer: StateTransformer<PluginStateObject.Data.String | PluginStateObject.Data.Binary, PluginStateObject.Molecule.Trajectory, P>, transformerParams?: P): TrajectoryFormatProvider['parse'] {
|
||||
return async (plugin, data, params) => {
|
||||
const state = plugin.state.data;
|
||||
const trajectory = await state.build().to(data)
|
||||
|
||||
@@ -1079,4 +1079,4 @@ const ShapeFromPly = PluginStateTransform.BuiltIn({
|
||||
return new SO.Shape.Provider(shape, props);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Icon, ArrowRightSvg, ArrowDropDownSvg } from './controls/icons';
|
||||
|
||||
export const PluginReactContext = React.createContext(void 0 as any as PluginUIContext);
|
||||
|
||||
export abstract class PluginUIComponent<P = {}, S = {}, SS = {}> extends React.Component<P & { children?: any }, S, SS> {
|
||||
export abstract class PluginUIComponent<P extends {} = {}, S = {}, SS = {}> extends React.Component<P & { children?: any }, S, SS> {
|
||||
static contextType = PluginReactContext;
|
||||
readonly plugin: PluginUIContext;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorListEntry } from '../../mol-util/color/color';
|
||||
@@ -22,7 +23,7 @@ import { PluginUIContext } from '../context';
|
||||
import { ActionMenu } from './action-menu';
|
||||
import { ColorOptions, ColorValueOption, CombinedColorControl } from './color';
|
||||
import { Button, ControlGroup, ControlRow, ExpandGroup, IconButton, TextInput, ToggleButton } from './common';
|
||||
import { ArrowDownwardSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowUpwardSvg, BookmarksOutlinedSvg, CheckSvg, ClearSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, MoreHorizSvg } from './icons';
|
||||
import { ArrowDownwardSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowUpwardSvg, BookmarksOutlinedSvg, CheckSvg, ClearSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, MoreHorizSvg, WarningSvg } from './icons';
|
||||
import { legendFor } from './legend';
|
||||
import { LineGraphComponent } from './line-graph/line-graph-component';
|
||||
import { Slider, Slider2 } from './slider';
|
||||
@@ -1466,31 +1467,38 @@ export class ConvertedControl extends React.PureComponent<ParamProps<PD.Converte
|
||||
}
|
||||
}
|
||||
|
||||
export class ScriptControl extends SimpleParam<PD.Script> {
|
||||
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
if (value !== this.props.value.expression) {
|
||||
this.update({ language: this.props.value.language, expression: value });
|
||||
export class ScriptControl extends React.PureComponent<ParamProps<PD.Script>> {
|
||||
onChange: ParamOnChange = ({ name, value }) => {
|
||||
const k = name as 'language' | 'expression';
|
||||
if (value !== this.props.value[k]) {
|
||||
this.props.onChange({ param: this.props.param, name: this.props.name, value: { ...this.props.value, [k]: value } });
|
||||
}
|
||||
};
|
||||
|
||||
onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if ((e.keyCode === 13 || e.charCode === 13 || e.key === 'Enter')) {
|
||||
if (this.props.onEnter) this.props.onEnter();
|
||||
}
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
renderControl() {
|
||||
render() {
|
||||
// TODO: improve!
|
||||
|
||||
const placeholder = this.props.param.label || camelCaseToWords(this.props.name);
|
||||
return <input type='text'
|
||||
value={this.props.value.expression || ''}
|
||||
placeholder={placeholder}
|
||||
onChange={this.onChange}
|
||||
onKeyPress={this.props.onEnter ? this.onKeyPress : void 0}
|
||||
disabled={this.props.isDisabled}
|
||||
/>;
|
||||
const selectParam: PD.Select<PD.Script['defaultValue']['language']> = {
|
||||
defaultValue: this.props.value.language,
|
||||
options: PD.objectToOptions(Script.Info),
|
||||
type: 'select',
|
||||
};
|
||||
const select = <SelectControl param={selectParam}
|
||||
isDisabled={this.props.isDisabled} onChange={this.onChange} onEnter={this.props.onEnter}
|
||||
name='language' value={this.props.value.language} />;
|
||||
|
||||
const textParam: PD.Text = {
|
||||
defaultValue: this.props.value.language,
|
||||
type: 'text',
|
||||
};
|
||||
const text = <TextControl param={textParam} isDisabled={this.props.isDisabled} onChange={this.onChange} name='expression' value={this.props.value.expression} />;
|
||||
|
||||
return <>
|
||||
{select}
|
||||
{this.props.value.language !== 'mol-script' && <div className='msp-help-text' style={{ padding: '10px' }}>
|
||||
<Icon svg={WarningSvg} /> Support for PyMOL, VMD, and Jmol selections is an experimental feature and may not always work as intended.
|
||||
</div>}
|
||||
{text}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 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 * as React from 'react';
|
||||
@@ -25,7 +26,7 @@ export interface ScreenshotPreviewProps {
|
||||
const _ScreenshotPreview = (props: ScreenshotPreviewProps) => {
|
||||
const { plugin, cropFrameColor } = props;
|
||||
|
||||
const helper = plugin.helpers.viewportScreenshot!;
|
||||
const helper = plugin.helpers.viewportScreenshot;
|
||||
const [currentCanvas, setCurrentCanvas] = useState<HTMLCanvasElement | null>(null);
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const propsRef = useRef(props);
|
||||
@@ -70,8 +71,8 @@ const _ScreenshotPreview = (props: ScreenshotPreviewProps) => {
|
||||
subscribe(plugin.state.data.behaviors.isUpdating, v => {
|
||||
if (!v) isDirty = true;
|
||||
});
|
||||
subscribe(helper.behaviors.values, () => isDirty = true);
|
||||
subscribe(helper.behaviors.cropParams, () => isDirty = true);
|
||||
subscribe(helper?.behaviors.values, () => isDirty = true);
|
||||
subscribe(helper?.behaviors.cropParams, () => isDirty = true);
|
||||
|
||||
let resizeObserver: any = void 0;
|
||||
if (typeof ResizeObserver !== 'undefined') {
|
||||
@@ -108,7 +109,9 @@ export const ScreenshotPreview = React.memo(_ScreenshotPreview, (prev, next) =>
|
||||
|
||||
declare const ResizeObserver: any;
|
||||
|
||||
function drawPreview(helper: ViewportScreenshotHelper, target: HTMLCanvasElement, customBackground?: string, borderColor?: string, borderWidth?: number) {
|
||||
function drawPreview(helper: ViewportScreenshotHelper | undefined, target: HTMLCanvasElement, customBackground?: string, borderColor?: string, borderWidth?: number) {
|
||||
if (!helper) return;
|
||||
|
||||
const { canvas, width, height } = helper.getPreview()!;
|
||||
const ctx = target.getContext('2d');
|
||||
if (!ctx) return;
|
||||
@@ -151,9 +154,9 @@ function drawPreview(helper: ViewportScreenshotHelper, target: HTMLCanvasElement
|
||||
|
||||
function ViewportFrame({ plugin, canvas, color = 'rgba(255, 87, 45, 0.75)' }: { plugin: PluginContext, canvas: HTMLCanvasElement | null, color?: string }) {
|
||||
const helper = plugin.helpers.viewportScreenshot;
|
||||
const params = useBehavior(helper?.behaviors.values!);
|
||||
const cropParams = useBehavior(helper?.behaviors.cropParams!);
|
||||
const crop = useBehavior(helper?.behaviors.relativeCrop!);
|
||||
const params = useBehavior(helper?.behaviors.values);
|
||||
const cropParams = useBehavior(helper?.behaviors.cropParams);
|
||||
const crop = useBehavior(helper?.behaviors.relativeCrop);
|
||||
const cropFrameRef = useRef<Viewport>({ x: 0, y: 0, width: 0, height: 0 });
|
||||
useBehavior(params?.resolution.name === 'viewport' ? plugin.canvas3d?.resized : void 0);
|
||||
|
||||
@@ -161,7 +164,7 @@ function ViewportFrame({ plugin, canvas, color = 'rgba(255, 87, 45, 0.75)' }: {
|
||||
const [start, setStart] = useState([0, 0]);
|
||||
const [current, setCurrent] = useState([0, 0]);
|
||||
|
||||
if (!helper || !canvas) return null;
|
||||
if (!helper || !canvas || !crop) return null;
|
||||
|
||||
const { width, height } = helper.getSizeAndViewport();
|
||||
|
||||
@@ -267,7 +270,7 @@ function ViewportFrame({ plugin, canvas, color = 'rgba(255, 87, 45, 0.75)' }: {
|
||||
|
||||
function finish() {
|
||||
const cropFrame = cropFrameRef.current;
|
||||
if (cropParams.auto) {
|
||||
if (cropParams?.auto) {
|
||||
helper?.behaviors.cropParams.next({ ...cropParams, auto: false });
|
||||
}
|
||||
helper?.behaviors.relativeCrop.next({
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginUIComponent } from '../base';
|
||||
@@ -199,6 +200,9 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
const viewParams = { ...oldView };
|
||||
if (value.name === 'selection-box') {
|
||||
viewParams.radius = value.params.radius;
|
||||
} else if (value.name === 'camera-target') {
|
||||
viewParams.radius = value.params.radius;
|
||||
viewParams.dynamicDetailLevel = value.params.dynamicDetailLevel;
|
||||
} else if (value.name === 'box') {
|
||||
viewParams.bottomLeft = value.params.bottomLeft;
|
||||
viewParams.topRight = value.params.topRight;
|
||||
@@ -240,13 +244,23 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
const pivot = isEM ? 'em' : '2fo-fc';
|
||||
|
||||
const params = this.props.params as VolumeStreaming.Params;
|
||||
const entry = ((this.props.info.params as VolumeStreaming.ParamDefinition)
|
||||
.entry.map(params.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>);
|
||||
const entry = (this.props.info.params as VolumeStreaming.ParamDefinition)
|
||||
.entry.map(params.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>;
|
||||
const detailLevel = entry.params.detailLevel;
|
||||
const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as Volume.IsoValue).kind === 'relative';
|
||||
const dynamicDetailLevel = {
|
||||
...detailLevel,
|
||||
label: 'Dynamic Detail',
|
||||
defaultValue: (entry.params.view as any).map('camera-target').params.dynamicDetailLevel.defaultValue,
|
||||
};
|
||||
const selectionDetailLevel = {
|
||||
...detailLevel,
|
||||
label: 'Selection Detail',
|
||||
defaultValue: (entry.params.view as any).map('auto').params.selectionDetailLevel.defaultValue,
|
||||
};
|
||||
|
||||
const sampling = b.info.header.sampling[0];
|
||||
|
||||
const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as Volume.IsoValue).kind === 'relative';
|
||||
const isRelativeParam = PD.Boolean(isRelative, { description: 'Use normalized or absolute isocontour scale.', label: 'Normalized' });
|
||||
|
||||
const isUnbounded = !!(params.entry.params.view.params as any).isUnbounded;
|
||||
@@ -274,6 +288,13 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
isRelative: isRelativeParam,
|
||||
isUnbounded: isUnboundedParam,
|
||||
}, { description: 'Box around focused element.' }),
|
||||
'camera-target': PD.Group({
|
||||
radius: PD.Numeric(0.5, { min: 0, max: 1, step: 0.05 }, { description: 'Radius within which the volume is shown (relative to the field of view).' }),
|
||||
detailLevel: { ...detailLevel, isHidden: true },
|
||||
dynamicDetailLevel: dynamicDetailLevel,
|
||||
isRelative: isRelativeParam,
|
||||
isUnbounded: isUnboundedParam,
|
||||
}, { description: 'Box around camera target.' }),
|
||||
'cell': PD.Group({
|
||||
detailLevel,
|
||||
isRelative: isRelativeParam,
|
||||
@@ -282,12 +303,11 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
'auto': PD.Group({
|
||||
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
|
||||
detailLevel,
|
||||
selectionDetailLevel: { ...detailLevel, label: 'Selection Detail' },
|
||||
selectionDetailLevel: selectionDetailLevel,
|
||||
isRelative: isRelativeParam,
|
||||
isUnbounded: isUnboundedParam,
|
||||
}, { description: 'Box around focused element.' }),
|
||||
// 'auto': PD.Group({ }), // TODO based on camera distance/active selection/whatever, show whole structure or slice.
|
||||
}, { options: VolumeStreaming.ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Focus" shows the volume around the element/atom last interacted with. "Whole Structure" shows the volume for the whole structure.' })
|
||||
}, { options: VolumeStreaming.ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Focus" shows the volume around the element/atom last interacted with. "Around Camera" shows the volume around the point the camera is targeting. "Whole Structure" shows the volume for the whole structure.' })
|
||||
};
|
||||
const options = {
|
||||
entry: params.entry.name,
|
||||
@@ -299,6 +319,7 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
bottomLeft: (params.entry.params.view.params as any).bottomLeft,
|
||||
topRight: (params.entry.params.view.params as any).topRight,
|
||||
selectionDetailLevel: (params.entry.params.view.params as any).selectionDetailLevel,
|
||||
dynamicDetailLevel: (params.entry.params.view.params as any).dynamicDetailLevel,
|
||||
isRelative,
|
||||
isUnbounded
|
||||
}
|
||||
|
||||
@@ -141,11 +141,13 @@ class FullSettings extends PluginUIComponent {
|
||||
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
|
||||
this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
|
||||
|
||||
this.subscribe(this.plugin.canvas3d!.camera.stateChanged, state => {
|
||||
if (state.radiusMax !== undefined || state.radius !== undefined) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
if (this.plugin.canvas3d) {
|
||||
this.subscribe(this.plugin.canvas3d.camera.stateChanged, state => {
|
||||
if (state.radiusMax !== undefined || state.radius !== undefined) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 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>
|
||||
@@ -288,7 +288,12 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
|
||||
for (const loci of this.lociArray) {
|
||||
this.plugin.managers.interactivity.lociHighlights.highlight({ loci }, false);
|
||||
}
|
||||
this.plugin.managers.interactivity.lociHighlights.highlight({ loci: this.props.cell.obj?.data.repr.getLoci()! }, false);
|
||||
const reprLocis = this.props.cell.obj?.data.repr.getAllLoci();
|
||||
if (reprLocis) {
|
||||
for (const loci of reprLocis) {
|
||||
this.plugin.managers.interactivity.lociHighlights.highlight({ loci }, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
clearHighlight = () => {
|
||||
|
||||
@@ -59,7 +59,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View
|
||||
return <div className='msp-no-webgl'>
|
||||
<div>
|
||||
<p><b>WebGL does not seem to be available.</b></p>
|
||||
<p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps.</p>
|
||||
<p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps. Also, make sure hardware acceleration is enabled in your browser.</p>
|
||||
<p>For a list of supported browsers, refer to <a href='http://caniuse.com/#feat=webgl' target='_blank'>http://caniuse.com/#feat=webgl</a>.</p>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
@@ -7,14 +7,15 @@
|
||||
import * as React from 'react';
|
||||
import { Binding } from '../../mol-util/binding';
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { StateTransformer, StateSelection } from '../../mol-state';
|
||||
import { StateTransformer, StateSelection, State } from '../../mol-state';
|
||||
import { SelectLoci } from '../../mol-plugin/behavior/dynamic/representation';
|
||||
import { FocusLoci } from '../../mol-plugin/behavior/dynamic/representation';
|
||||
import { Icon, ArrowDropDownSvg, ArrowRightSvg, CameraSvg } from '../controls/icons';
|
||||
import { Button } from '../controls/common';
|
||||
import { memoizeLatest } from '../../mol-util/memoize';
|
||||
|
||||
function getBindingsList(bindings: { [k: string]: Binding }) {
|
||||
return Object.keys(bindings).map(k => [k, bindings[k]] as [string, Binding]);
|
||||
return Object.keys(bindings).map(k => [k, bindings[k]] as [string, Binding]).filter(b => Binding.isBinding(b[1]));
|
||||
}
|
||||
|
||||
export class BindingsHelp extends React.PureComponent<{ bindings: { [k: string]: Binding } }> {
|
||||
@@ -77,19 +78,30 @@ export class ViewportHelpContent extends PluginUIComponent<{ selectOnly?: boolea
|
||||
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
|
||||
}
|
||||
|
||||
render() {
|
||||
const interactionBindings: { [k: string]: Binding } = {};
|
||||
this.plugin.spec.behaviors.forEach(b => {
|
||||
const { bindings } = b.defaultParams;
|
||||
if (bindings) Object.assign(interactionBindings, bindings);
|
||||
getInteractionBindings = memoizeLatest((cells: State.Cells) => {
|
||||
let interactionBindings: { [k: string]: Binding } | undefined = void 0;
|
||||
|
||||
cells.forEach(c => {
|
||||
const params = c.params?.values;
|
||||
if (params?.bindings && Object.keys(params.bindings).length > 0) {
|
||||
if (!interactionBindings) interactionBindings = { };
|
||||
Object.assign(interactionBindings, params.bindings);
|
||||
}
|
||||
});
|
||||
|
||||
return interactionBindings;
|
||||
});
|
||||
|
||||
render() {
|
||||
const interactionBindings = this.getInteractionBindings(this.plugin.state.behaviors.cells);
|
||||
|
||||
return <>
|
||||
{(!this.props.selectOnly && this.plugin.canvas3d) && <HelpGroup key='trackball' header='Moving in 3D'>
|
||||
<BindingsHelp bindings={this.plugin.canvas3d.props.trackball.bindings} />
|
||||
</HelpGroup>}
|
||||
<HelpGroup key='interactions' header='Mouse Controls'>
|
||||
{!!interactionBindings && <HelpGroup key='interactions' header='Mouse Controls'>
|
||||
<BindingsHelp bindings={interactionBindings} />
|
||||
</HelpGroup>
|
||||
</HelpGroup>}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 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>
|
||||
@@ -96,18 +96,21 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
|
||||
}
|
||||
|
||||
function ScreenshotParams({ plugin, isDisabled }: { plugin: PluginContext, isDisabled: boolean }) {
|
||||
const helper = plugin.helpers.viewportScreenshot!;
|
||||
const values = useBehavior(helper.behaviors.values);
|
||||
const helper = plugin.helpers.viewportScreenshot;
|
||||
|
||||
const values = useBehavior(helper?.behaviors.values);
|
||||
if (!helper) return null;
|
||||
|
||||
return <ParameterControls params={helper.params} values={values} onChangeValues={v => helper.behaviors.values.next(v)} isDisabled={isDisabled} />;
|
||||
}
|
||||
|
||||
function CropControls({ plugin }: { plugin: PluginContext }) {
|
||||
const helper = plugin.helpers.viewportScreenshot;
|
||||
const cropParams = useBehavior(helper?.behaviors.cropParams!);
|
||||
|
||||
const cropParams = useBehavior(helper?.behaviors.cropParams);
|
||||
useBehavior(helper?.behaviors.relativeCrop);
|
||||
|
||||
if (!helper) return null;
|
||||
if (!helper || !cropParams) return null;
|
||||
|
||||
return <div style={{ width: '100%', height: '24px', marginTop: '8px' }}>
|
||||
<ToggleButton icon={CropOrginalSvg} title='Auto-crop' inline isSelected={cropParams.auto}
|
||||
|
||||
@@ -22,6 +22,8 @@ import { ViewportHelpContent } from './help';
|
||||
|
||||
export class SimpleSettingsControl extends PluginUIComponent {
|
||||
componentDidMount() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
|
||||
|
||||
this.subscribe(this.plugin.canvas3d!.camera.stateChanged, state => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginStateTransform, PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
@@ -39,7 +40,7 @@ namespace PluginBehavior {
|
||||
'misc': 'Miscellaneous'
|
||||
};
|
||||
|
||||
export interface CreateParams<P> {
|
||||
export interface CreateParams<P extends {}> {
|
||||
name: string,
|
||||
category: keyof typeof Categories,
|
||||
ctor: Ctor<P>,
|
||||
@@ -72,7 +73,7 @@ namespace PluginBehavior {
|
||||
return categoryMap.get(t.id)!;
|
||||
}
|
||||
|
||||
export function create<P>(params: CreateParams<P>) {
|
||||
export function create<P extends {}>(params: CreateParams<P>) {
|
||||
const t = PluginStateTransform.CreateBuiltIn<Category, Behavior, P>({
|
||||
name: params.name,
|
||||
display: params.display,
|
||||
@@ -112,7 +113,7 @@ namespace PluginBehavior {
|
||||
};
|
||||
}
|
||||
|
||||
export abstract class Handler<P = { }> implements PluginBehavior<P> {
|
||||
export abstract class Handler<P extends {} = {}> implements PluginBehavior<P> {
|
||||
private subs: PluginCommand.Subscription[] = [];
|
||||
protected subscribeCommand<T>(cmd: PluginCommand<T>, action: PluginCommand.Action<T>) {
|
||||
this.subs.push(cmd.subscribe(this.ctx, action));
|
||||
@@ -144,8 +145,18 @@ namespace PluginBehavior {
|
||||
protected subscribeCommand<T>(cmd: PluginCommand<T>, action: PluginCommand.Action<T>) {
|
||||
this.subs.push(cmd.subscribe(this.plugin, action));
|
||||
}
|
||||
protected subscribeObservable<T>(o: Observable<T>, action: (v: T) => void) {
|
||||
this.subs.push(o.subscribe(action));
|
||||
protected subscribeObservable<T>(o: Observable<T>, action: (v: T) => void): PluginCommand.Subscription {
|
||||
const sub = o.subscribe(action);
|
||||
this.subs.push(sub);
|
||||
return {
|
||||
unsubscribe: () => {
|
||||
const idx = this.subs.indexOf(sub);
|
||||
if (idx >= 0) {
|
||||
this.subs.splice(idx, 1);
|
||||
sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
dispose(): void {
|
||||
for (const s of this.subs) s.unsubscribe();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 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>
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
@@ -24,6 +25,10 @@ import { PluginContext } from '../../../context';
|
||||
import { EmptyLoci, Loci, isEmptyLoci } from '../../../../mol-model/loci';
|
||||
import { Asset } from '../../../../mol-util/assets';
|
||||
import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform';
|
||||
import { distinctUntilChanged, filter, map, Observable, throttleTime } from 'rxjs';
|
||||
import { Camera } from '../../../../mol-canvas3d/camera';
|
||||
import { PluginCommand } from '../../../command';
|
||||
import { SingleAsyncQueue } from '../../../../mol-util/single-async-queue';
|
||||
|
||||
export class VolumeStreaming extends PluginStateObject.CreateBehavior<VolumeStreaming.Behavior>({ name: 'Volume Streaming' }) { }
|
||||
|
||||
@@ -53,7 +58,7 @@ export namespace VolumeStreaming {
|
||||
valuesInfo: [{ mean: 0, min: -1, max: 1, sigma: 0.1 }, { mean: 0, min: -1, max: 1, sigma: 0.1 }]
|
||||
};
|
||||
|
||||
export function createParams(options: { data?: VolumeServerInfo.Data, defaultView?: ViewTypes, channelParams?: DefaultChannelParams } = { }) {
|
||||
export function createParams(options: { data?: VolumeServerInfo.Data, defaultView?: ViewTypes, channelParams?: DefaultChannelParams } = {}) {
|
||||
const { data, defaultView, channelParams } = options;
|
||||
const map = new Map<string, VolumeServerInfo.EntryData>();
|
||||
if (data) data.entries.forEach(d => map.set(d.dataId, d));
|
||||
@@ -68,7 +73,7 @@ export namespace VolumeStreaming {
|
||||
export type EntryParams = PD.Values<EntryParamDefinition>
|
||||
|
||||
export function createEntryParams(options: { entryData?: VolumeServerInfo.EntryData, defaultView?: ViewTypes, structure?: Structure, channelParams?: DefaultChannelParams }) {
|
||||
const { entryData, defaultView, structure, channelParams = { } } = options;
|
||||
const { entryData, defaultView, structure, channelParams = {} } = options;
|
||||
|
||||
// fake the info
|
||||
const info = entryData || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: Volume.IsoValue.relative(0) };
|
||||
@@ -86,19 +91,24 @@ export namespace VolumeStreaming {
|
||||
bottomLeft: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
|
||||
topRight: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
|
||||
}, { description: 'Box around focused element.', isFlat: true }),
|
||||
'camera-target': PD.Group({
|
||||
radius: PD.Numeric(0.5, { min: 0, max: 1, step: 0.05 }, { description: 'Radius within which the volume is shown (relative to the field of view).' }),
|
||||
// Minimal detail level for the inside of the zoomed region (real detail can be higher, depending on the region size)
|
||||
dynamicDetailLevel: createDetailParams(info.header.availablePrecisions, 0, { label: 'Dynamic Detail' }),
|
||||
bottomLeft: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
|
||||
topRight: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
|
||||
}, { description: 'Box around camera target.', isFlat: true }),
|
||||
'cell': PD.Group<{}>({}),
|
||||
// Show selection-box if available and cell otherwise.
|
||||
'auto': PD.Group({
|
||||
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
|
||||
selectionDetailLevel: PD.Select<number>(Math.min(6, info.header.availablePrecisions.length - 1),
|
||||
info.header.availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]), { label: 'Selection Detail', description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 0 (0.52M voxels) to 6 (25.17M voxels).' }),
|
||||
selectionDetailLevel: createDetailParams(info.header.availablePrecisions, 6, { label: 'Selection Detail' }),
|
||||
isSelection: PD.Boolean(false, { isHidden: true }),
|
||||
bottomLeft: PD.Vec3(box.min, {}, { isHidden: true }),
|
||||
topRight: PD.Vec3(box.max, {}, { isHidden: true }),
|
||||
}, { description: 'Box around focused element.', isFlat: true })
|
||||
}, { options: ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Interaction" shows the volume around the focused element/atom. "Whole Structure" shows the volume for the whole structure.' }),
|
||||
detailLevel: PD.Select<number>(Math.min(3, info.header.availablePrecisions.length - 1),
|
||||
info.header.availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]), { description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 0 (0.52M voxels) to 6 (25.17M voxels).' }),
|
||||
detailLevel: createDetailParams(info.header.availablePrecisions, 3),
|
||||
channels: info.kind === 'em'
|
||||
? PD.Group({
|
||||
'em': channelParam('EM', Color(0x638F8F), info.emDefaultContourLevel || Volume.IsoValue.relative(1), info.header.sampling[0].valuesInfo[0], channelParams['em'])
|
||||
@@ -111,13 +121,40 @@ export namespace VolumeStreaming {
|
||||
};
|
||||
}
|
||||
|
||||
export const ViewTypeOptions = [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Around Focus'], ['cell', 'Whole Structure'], ['auto', 'Auto']] as [ViewTypes, string][];
|
||||
function createDetailParams(availablePrecisions: VolumeServerHeader.DetailLevel[], preferredPrecision: number, info?: PD.Info) {
|
||||
return PD.Select<number>(Math.min(preferredPrecision, availablePrecisions.length - 1),
|
||||
availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]),
|
||||
{
|
||||
description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 1 (0.52M voxels) to 7 (25.17M voxels).',
|
||||
...info
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export type ViewTypes = 'off' | 'box' | 'selection-box' | 'cell' | 'auto'
|
||||
export function copyParams(origParams: Params): Params {
|
||||
return {
|
||||
entry: {
|
||||
name: origParams.entry.name,
|
||||
params: {
|
||||
detailLevel: origParams.entry.params.detailLevel,
|
||||
channels: origParams.entry.params.channels,
|
||||
view: {
|
||||
name: origParams.entry.params.view.name,
|
||||
params: { ...origParams.entry.params.view.params } as any,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const ViewTypeOptions = [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Around Focus'], ['camera-target', 'Around Camera'], ['cell', 'Whole Structure'], ['auto', 'Auto']] as [ViewTypes, string][];
|
||||
|
||||
export type ViewTypes = 'off' | 'box' | 'selection-box' | 'camera-target' | 'cell' | 'auto'
|
||||
|
||||
export type ParamDefinition = ReturnType<typeof createParams>
|
||||
export type Params = PD.Values<ParamDefinition>
|
||||
|
||||
|
||||
type ChannelsInfo = { [name in ChannelType]?: { isoValue: Volume.IsoValue, color: Color, wireframe: boolean, opacity: number } }
|
||||
type ChannelsData = { [name in 'EM' | '2FO-FC' | 'FO-FC']?: Volume }
|
||||
|
||||
@@ -140,6 +177,14 @@ export namespace VolumeStreaming {
|
||||
private lastLoci: StructureElement.Loci | EmptyLoci = EmptyLoci;
|
||||
private ref: string = '';
|
||||
public infoMap: Map<string, VolumeServerInfo.EntryData>;
|
||||
private updateQueue: SingleAsyncQueue;
|
||||
private cameraTargetObservable = this.plugin.canvas3d!.didDraw!.pipe(
|
||||
throttleTime(500, undefined, { 'leading': true, 'trailing': true }),
|
||||
map(() => this.plugin.canvas3d?.camera.getSnapshot()),
|
||||
distinctUntilChanged((a, b) => this.isCameraTargetSame(a, b)),
|
||||
filter(a => a !== undefined),
|
||||
) as Observable<Camera.Snapshot>;
|
||||
private cameraTargetSubscription?: PluginCommand.Subscription = undefined;
|
||||
|
||||
channels: Channels = {};
|
||||
|
||||
@@ -163,6 +208,9 @@ export namespace VolumeStreaming {
|
||||
if (this.params.entry.params.view.name === 'auto' && this.params.entry.params.view.params.isSelection) {
|
||||
detail = this.params.entry.params.view.params.selectionDetailLevel;
|
||||
}
|
||||
if (this.params.entry.params.view.name === 'camera-target' && box) {
|
||||
detail = this.decideDetail(box, this.params.entry.params.view.params.dynamicDetailLevel);
|
||||
}
|
||||
|
||||
url += `?detail=${detail}`;
|
||||
|
||||
@@ -201,58 +249,21 @@ export namespace VolumeStreaming {
|
||||
return ret;
|
||||
}
|
||||
|
||||
private updateSelectionBoxParams(box: Box3D) {
|
||||
if (this.params.entry.params.view.name !== 'selection-box') return;
|
||||
private async updateParams(box: Box3D | undefined, autoIsSelection: boolean = false) {
|
||||
const newParams = copyParams(this.params);
|
||||
const viewType = newParams.entry.params.view.name;
|
||||
if (viewType !== 'off' && viewType !== 'cell') {
|
||||
newParams.entry.params.view.params.bottomLeft = box?.min || Vec3.zero();
|
||||
newParams.entry.params.view.params.topRight = box?.max || Vec3.zero();
|
||||
}
|
||||
if (viewType === 'auto') {
|
||||
newParams.entry.params.view.params.isSelection = autoIsSelection;
|
||||
}
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
const newParams: Params = {
|
||||
...this.params,
|
||||
entry: {
|
||||
name: this.params.entry.name,
|
||||
params: {
|
||||
...this.params.entry.params,
|
||||
view: {
|
||||
name: 'selection-box' as const,
|
||||
params: {
|
||||
radius: this.params.entry.params.view.params.radius,
|
||||
bottomLeft: box.min,
|
||||
topRight: box.max
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const update = state.build().to(this.ref).update(newParams);
|
||||
|
||||
PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
|
||||
}
|
||||
|
||||
private updateAutoParams(box: Box3D | undefined, isSelection: boolean) {
|
||||
if (this.params.entry.params.view.name !== 'auto') return;
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
const newParams: Params = {
|
||||
...this.params,
|
||||
entry: {
|
||||
name: this.params.entry.name,
|
||||
params: {
|
||||
...this.params.entry.params,
|
||||
view: {
|
||||
name: 'auto' as const,
|
||||
params: {
|
||||
radius: this.params.entry.params.view.params.radius,
|
||||
selectionDetailLevel: this.params.entry.params.view.params.selectionDetailLevel,
|
||||
isSelection,
|
||||
bottomLeft: box?.min || Vec3.zero(),
|
||||
topRight: box?.max || Vec3.zero()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const update = state.build().to(this.ref).update(newParams);
|
||||
|
||||
PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } });
|
||||
}
|
||||
|
||||
private getStructureRoot() {
|
||||
@@ -303,6 +314,18 @@ export namespace VolumeStreaming {
|
||||
}
|
||||
}
|
||||
|
||||
private isCameraTargetSame(a?: Camera.Snapshot, b?: Camera.Snapshot): boolean {
|
||||
if (!a || !b) return false;
|
||||
const targetSame = Vec3.equals(a.target, b.target);
|
||||
const sqDistA = Vec3.squaredDistance(a.target, a.position);
|
||||
const sqDistB = Vec3.squaredDistance(b.target, b.position);
|
||||
const distanceSame = Math.abs(sqDistA - sqDistB) / sqDistA < 1e-3;
|
||||
return targetSame && distanceSame;
|
||||
}
|
||||
private cameraTargetDistance(snapshot: Camera.Snapshot): number {
|
||||
return Vec3.distance(snapshot.target, snapshot.position);
|
||||
}
|
||||
|
||||
private _invTransform: Mat4 = Mat4();
|
||||
private getBoxFromLoci(loci: StructureElement.Loci | EmptyLoci): Box3D {
|
||||
if (Loci.isEmpty(loci) || isEmptyLoci(loci)) {
|
||||
@@ -328,39 +351,82 @@ export namespace VolumeStreaming {
|
||||
}
|
||||
|
||||
private updateAuto(loci: StructureElement.Loci | EmptyLoci) {
|
||||
// if (Loci.areEqual(this.lastLoci, loci)) {
|
||||
// this.lastLoci = EmptyLoci;
|
||||
// this.updateSelectionBoxParams(Box3D.empty());
|
||||
// return;
|
||||
// }
|
||||
|
||||
this.lastLoci = loci;
|
||||
|
||||
if (isEmptyLoci(loci)) {
|
||||
this.updateAutoParams(this.info.kind === 'x-ray' ? this.data.structure.boundary.box : void 0, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const box = this.getBoxFromLoci(loci);
|
||||
this.updateAutoParams(box, true);
|
||||
this.updateQueue.enqueue(async () => {
|
||||
this.lastLoci = loci;
|
||||
if (isEmptyLoci(loci)) {
|
||||
await this.updateParams(this.info.kind === 'x-ray' ? this.data.structure.boundary.box : void 0, false);
|
||||
} else {
|
||||
await this.updateParams(this.getBoxFromLoci(loci), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateSelectionBox(loci: StructureElement.Loci | EmptyLoci) {
|
||||
if (Loci.areEqual(this.lastLoci, loci)) {
|
||||
this.lastLoci = EmptyLoci;
|
||||
this.updateSelectionBoxParams(Box3D());
|
||||
return;
|
||||
this.updateQueue.enqueue(async () => {
|
||||
if (Loci.areEqual(this.lastLoci, loci)) {
|
||||
this.lastLoci = EmptyLoci;
|
||||
} else {
|
||||
this.lastLoci = loci;
|
||||
}
|
||||
const box = this.getBoxFromLoci(this.lastLoci);
|
||||
await this.updateParams(box);
|
||||
});
|
||||
}
|
||||
|
||||
private updateCameraTarget(snapshot: Camera.Snapshot) {
|
||||
this.updateQueue.enqueue(async () => {
|
||||
const origManualReset = this.plugin.canvas3d?.props.camera.manualReset;
|
||||
try {
|
||||
if (!origManualReset) this.plugin.canvas3d?.setProps({ camera: { manualReset: true } });
|
||||
const box = this.boxFromCameraTarget(snapshot, true);
|
||||
await this.updateParams(box);
|
||||
} finally {
|
||||
if (!origManualReset) this.plugin.canvas3d?.setProps({ camera: { manualReset: origManualReset } });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boxFromCameraTarget(snapshot: Camera.Snapshot, boundByBoundarySize: boolean): Box3D {
|
||||
const target = snapshot.target;
|
||||
const distance = this.cameraTargetDistance(snapshot);
|
||||
const top = Math.tan(0.5 * snapshot.fov) * distance;
|
||||
let radius = top;
|
||||
const viewport = this.plugin.canvas3d?.camera.viewport;
|
||||
if (viewport && viewport.width > viewport.height) {
|
||||
radius *= viewport.width / viewport.height;
|
||||
}
|
||||
|
||||
this.lastLoci = loci;
|
||||
|
||||
if (isEmptyLoci(loci)) {
|
||||
this.updateSelectionBoxParams(Box3D());
|
||||
return;
|
||||
const relativeRadius = this.params.entry.params.view.name === 'camera-target' ? this.params.entry.params.view.params.radius : 0.5;
|
||||
radius *= relativeRadius;
|
||||
let radiusX, radiusY, radiusZ;
|
||||
if (boundByBoundarySize) {
|
||||
const bBoxSize = Vec3.zero();
|
||||
Box3D.size(bBoxSize, this.data.structure.boundary.box);
|
||||
radiusX = Math.min(radius, 0.5 * bBoxSize[0]);
|
||||
radiusY = Math.min(radius, 0.5 * bBoxSize[1]);
|
||||
radiusZ = Math.min(radius, 0.5 * bBoxSize[2]);
|
||||
} else {
|
||||
radiusX = radiusY = radiusZ = radius;
|
||||
}
|
||||
return Box3D.create(
|
||||
Vec3.create(target[0] - radiusX, target[1] - radiusY, target[2] - radiusZ),
|
||||
Vec3.create(target[0] + radiusX, target[1] + radiusY, target[2] + radiusZ)
|
||||
);
|
||||
}
|
||||
|
||||
const box = this.getBoxFromLoci(loci);
|
||||
this.updateSelectionBoxParams(box);
|
||||
private decideDetail(box: Box3D, baseDetail: number): number {
|
||||
const cellVolume = this.info.kind === 'x-ray'
|
||||
? Box3D.volume(this.data.structure.boundary.box)
|
||||
: this.info.header.spacegroup.size.reduce((a, b) => a * b, 1);
|
||||
const boxVolume = Box3D.volume(box);
|
||||
let ratio = boxVolume / cellVolume;
|
||||
const maxDetail = this.info.header.availablePrecisions.length - 1;
|
||||
let detail = baseDetail;
|
||||
while (ratio <= 0.5 && detail < maxDetail) {
|
||||
ratio *= 2;
|
||||
detail += 1;
|
||||
}
|
||||
// console.log(`Decided dynamic detail: ${detail}, (base detail: ${baseDetail}, box/cell volume ratio: ${boxVolume / cellVolume})`);
|
||||
return detail;
|
||||
}
|
||||
|
||||
async update(params: Params) {
|
||||
@@ -369,6 +435,11 @@ export namespace VolumeStreaming {
|
||||
this.params = params;
|
||||
let box: Box3D | undefined = void 0, emptyData = false;
|
||||
|
||||
if (params.entry.params.view.name !== 'camera-target' && this.cameraTargetSubscription) {
|
||||
this.cameraTargetSubscription.unsubscribe();
|
||||
this.cameraTargetSubscription = undefined;
|
||||
}
|
||||
|
||||
switch (params.entry.params.view.name) {
|
||||
case 'off':
|
||||
emptyData = true;
|
||||
@@ -388,6 +459,12 @@ export namespace VolumeStreaming {
|
||||
Box3D.expand(box, box, Vec3.create(r, r, r));
|
||||
break;
|
||||
}
|
||||
case 'camera-target':
|
||||
if (!this.cameraTargetSubscription) {
|
||||
this.cameraTargetSubscription = this.subscribeObservable(this.cameraTargetObservable, (e) => this.updateCameraTarget(e));
|
||||
}
|
||||
box = this.boxFromCameraTarget(this.plugin.canvas3d!.camera.getSnapshot(), true);
|
||||
break;
|
||||
case 'cell':
|
||||
box = this.info.kind === 'x-ray'
|
||||
? this.data.structure.boundary.box
|
||||
@@ -439,6 +516,7 @@ export namespace VolumeStreaming {
|
||||
|
||||
getDescription() {
|
||||
if (this.params.entry.params.view.name === 'selection-box') return 'Selection';
|
||||
if (this.params.entry.params.view.name === 'camera-target') return 'Camera';
|
||||
if (this.params.entry.params.view.name === 'box') return 'Static Box';
|
||||
if (this.params.entry.params.view.name === 'cell') return 'Cell';
|
||||
return '';
|
||||
@@ -449,6 +527,7 @@ export namespace VolumeStreaming {
|
||||
|
||||
this.infoMap = new Map<string, VolumeServerInfo.EntryData>();
|
||||
this.data.entries.forEach(info => this.infoMap.set(info.dataId, info));
|
||||
this.updateQueue = new SingleAsyncQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 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>
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
|
||||
@@ -219,6 +220,7 @@ const CreateVolumeStreamingBehavior = PluginStateTransform.BuiltIn({
|
||||
canAutoUpdate: ({ oldParams, newParams }) => {
|
||||
return oldParams.entry.params.view === newParams.entry.params.view
|
||||
|| newParams.entry.params.view.name === 'selection-box'
|
||||
|| newParams.entry.params.view.name === 'camera-target'
|
||||
|| newParams.entry.params.view.name === 'off';
|
||||
},
|
||||
apply: ({ a, params }, plugin: PluginContext) => Task.create('Volume streaming', async _ => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 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>
|
||||
@@ -119,7 +119,9 @@ export function Highlight(ctx: PluginContext) {
|
||||
ctx.managers.interactivity.lociHighlights.highlight({ loci: Structure.Loci(cell.obj.data) }, false);
|
||||
} else if (cell && SO.isRepresentation3D(cell.obj)) {
|
||||
const { repr } = cell.obj.data;
|
||||
ctx.managers.interactivity.lociHighlights.highlight({ loci: repr.getLoci(), repr }, false);
|
||||
for (const loci of repr.getAllLoci()) {
|
||||
ctx.managers.interactivity.lociHighlights.highlight({ loci, repr }, false);
|
||||
}
|
||||
} else if (SO.Molecule.Structure.Selections.is(cell.obj)) {
|
||||
for (const entry of cell.obj.data) {
|
||||
ctx.managers.interactivity.lociHighlights.highlight({ loci: entry.loci }, false);
|
||||
|
||||
@@ -31,10 +31,12 @@ export const PluginConfig = {
|
||||
PixelScale: item('plugin-config.pixel-scale', 1),
|
||||
PickScale: item('plugin-config.pick-scale', 0.25),
|
||||
PickPadding: item('plugin-config.pick-padding', 3),
|
||||
EnableWboit: item('plugin-config.enable-wboit', PluginFeatureDetection.wboit),
|
||||
EnableWboit: item('plugin-config.enable-wboit', true),
|
||||
EnableDpoit: item('plugin-config.enable-dpoit', false),
|
||||
// as of Oct 1 2021, WebGL 2 doesn't work on iOS 15.
|
||||
// TODO: check back in a few weeks to see if it was fixed
|
||||
PreferWebGl1: item('plugin-config.prefer-webgl1', PluginFeatureDetection.preferWebGl1),
|
||||
AllowMajorPerformanceCaveat: item('plugin-config.allow-major-performance-caveat', false),
|
||||
},
|
||||
State: {
|
||||
DefaultServer: item('plugin-state.server', 'https://webchem.ncbr.muni.cz/molstar-state'),
|
||||
@@ -92,4 +94,4 @@ export class PluginConfigManager {
|
||||
if (!initial) return;
|
||||
initial.forEach(([k, v]) => this._config.set(k, v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,8 +200,10 @@ export class PluginContext {
|
||||
const pickScale = this.config.get(PluginConfig.General.PickScale) || 0.25;
|
||||
const pickPadding = this.config.get(PluginConfig.General.PickPadding) ?? 1;
|
||||
const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
|
||||
const enableDpoit = this.config.get(PluginConfig.General.EnableDpoit) || false;
|
||||
const preferWebGl1 = this.config.get(PluginConfig.General.PreferWebGl1) || false;
|
||||
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, this.managers.asset, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, preferWebGl1 });
|
||||
const failIfMajorPerformanceCaveat = !(this.config.get(PluginConfig.General.AllowMajorPerformanceCaveat) ?? false);
|
||||
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, this.managers.asset, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, enableDpoit, preferWebGl1, failIfMajorPerformanceCaveat });
|
||||
}
|
||||
(this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
|
||||
this.canvas3dInit.next(true);
|
||||
|
||||
@@ -29,10 +29,4 @@ export const PluginFeatureDetection = {
|
||||
const isTouchScreen = navigator.maxTouchPoints >= 4; // true for iOS 13 (and hopefully beyond)
|
||||
return !(window as any).MSStream && (isIOS || (isAppleDevice && isTouchScreen));
|
||||
},
|
||||
get wboit() {
|
||||
if (typeof navigator === 'undefined' || typeof window === 'undefined') return true;
|
||||
|
||||
// disable Wboit in Safari 15
|
||||
return !/Version\/15.\d Safari/.test(navigator.userAgent);
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -154,8 +154,8 @@ interface Representation<D, P extends PD.Params = {}, S extends Representation.S
|
||||
createOrUpdate: (props?: Partial<PD.Values<P>>, data?: D) => Task<void>
|
||||
setState: (state: Partial<S>) => void
|
||||
setTheme: (theme: Theme) => void
|
||||
/** If no pickingId is given, returns a Loci for the whole representation */
|
||||
getLoci: (pickingId?: PickingId) => ModelLoci
|
||||
getLoci: (pickingId: PickingId) => ModelLoci
|
||||
getAllLoci: () => ModelLoci[]
|
||||
mark: (loci: ModelLoci, action: MarkerAction) => boolean
|
||||
destroy: () => void
|
||||
}
|
||||
@@ -227,6 +227,7 @@ namespace Representation {
|
||||
setState: () => {},
|
||||
setTheme: () => {},
|
||||
getLoci: () => EmptyLoci,
|
||||
getAllLoci: () => [],
|
||||
mark: () => false,
|
||||
destroy: () => {}
|
||||
};
|
||||
@@ -327,7 +328,7 @@ namespace Representation {
|
||||
},
|
||||
get state() { return currentState; },
|
||||
get theme() { return currentTheme; },
|
||||
getLoci: (pickingId?: PickingId) => {
|
||||
getLoci: (pickingId: PickingId) => {
|
||||
const { visuals } = currentProps;
|
||||
for (let i = 0, il = reprList.length; i < il; ++i) {
|
||||
if (!visuals || visuals.includes(reprMap[i])) {
|
||||
@@ -337,6 +338,16 @@ namespace Representation {
|
||||
}
|
||||
return EmptyLoci;
|
||||
},
|
||||
getAllLoci: () => {
|
||||
const loci: ModelLoci[] = [];
|
||||
const { visuals } = currentProps;
|
||||
for (let i = 0, il = reprList.length; i < il; ++i) {
|
||||
if (!visuals || visuals.includes(reprMap[i])) {
|
||||
loci.push(...reprList[i].getAllLoci());
|
||||
}
|
||||
}
|
||||
return loci;
|
||||
},
|
||||
mark: (loci: ModelLoci, action: MarkerAction) => {
|
||||
let marked = false;
|
||||
for (let i = 0, il = reprList.length; i < il; ++i) {
|
||||
@@ -399,6 +410,10 @@ namespace Representation {
|
||||
// TODO
|
||||
return EmptyLoci;
|
||||
},
|
||||
getAllLoci: () => {
|
||||
// TODO
|
||||
return [];
|
||||
},
|
||||
mark: (loci: ModelLoci, action: MarkerAction) => {
|
||||
// TODO
|
||||
return false;
|
||||
|
||||
@@ -213,14 +213,16 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
|
||||
get geometryVersion() { return geometryVersion; },
|
||||
updated,
|
||||
createOrUpdate,
|
||||
getLoci(pickingId?: PickingId) {
|
||||
if (pickingId === undefined) return Shape.Loci(_shape);
|
||||
getLoci(pickingId: PickingId) {
|
||||
const { objectId, groupId, instanceId } = pickingId;
|
||||
if (_renderObject && _renderObject.id === objectId) {
|
||||
return ShapeGroup.Loci(_shape, [{ ids: OrderedSet.ofSingleton(groupId), instance: instanceId }]);
|
||||
}
|
||||
return EmptyLoci;
|
||||
},
|
||||
getAllLoci() {
|
||||
return [Shape.Loci(_shape)];
|
||||
},
|
||||
mark(loci: Loci, action: MarkerAction) {
|
||||
if (!MarkerActions.is(_state.markerActions, action)) return false;
|
||||
if (ShapeGroup.isLoci(loci) || Shape.isLoci(loci)) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 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>
|
||||
@@ -72,11 +72,14 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
|
||||
});
|
||||
}
|
||||
|
||||
function getLoci(pickingId?: PickingId) {
|
||||
if (pickingId === undefined) return Structure.Loci(_structure.target);
|
||||
function getLoci(pickingId: PickingId) {
|
||||
return visual ? visual.getLoci(pickingId) : EmptyLoci;
|
||||
}
|
||||
|
||||
function getAllLoci() {
|
||||
return [Structure.Loci(_structure.target)];
|
||||
}
|
||||
|
||||
function mark(loci: Loci, action: MarkerAction) {
|
||||
if (!_structure) return false;
|
||||
if (!MarkerActions.is(_state.markerActions, action)) return false;
|
||||
@@ -157,6 +160,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
|
||||
setState,
|
||||
setTheme,
|
||||
getLoci,
|
||||
getAllLoci,
|
||||
mark,
|
||||
destroy
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 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>
|
||||
@@ -185,8 +185,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
});
|
||||
}
|
||||
|
||||
function getLoci(pickingId?: PickingId) {
|
||||
if (pickingId === undefined) return Structure.Loci(_structure.target);
|
||||
function getLoci(pickingId: PickingId) {
|
||||
let loci: Loci = EmptyLoci;
|
||||
visuals.forEach(({ visual }) => {
|
||||
const _loci = visual.getLoci(pickingId);
|
||||
@@ -195,6 +194,10 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
return loci;
|
||||
}
|
||||
|
||||
function getAllLoci() {
|
||||
return [Structure.Loci(_structure.target)];
|
||||
}
|
||||
|
||||
function mark(loci: Loci, action: MarkerAction) {
|
||||
if (!_structure) return false;
|
||||
if (!MarkerActions.is(_state.markerActions, action)) return false;
|
||||
@@ -302,6 +305,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
setState,
|
||||
setTheme,
|
||||
getLoci,
|
||||
getAllLoci,
|
||||
mark,
|
||||
destroy
|
||||
};
|
||||
|
||||
@@ -358,10 +358,12 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
|
||||
createOrUpdate,
|
||||
setState,
|
||||
setTheme,
|
||||
getLoci: (pickingId?: PickingId): Loci => {
|
||||
if (pickingId === undefined) return getLoci(_volume, _props);
|
||||
getLoci: (pickingId: PickingId): Loci => {
|
||||
return visual ? visual.getLoci(pickingId) : EmptyLoci;
|
||||
},
|
||||
getAllLoci: (): Loci[] => {
|
||||
return [getLoci(_volume, _props)];
|
||||
},
|
||||
mark,
|
||||
destroy
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 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>
|
||||
@@ -333,6 +333,7 @@ const bondProperty = {
|
||||
|
||||
flags: bondProp(Types.BondFlags),
|
||||
order: bondProp(Type.Num),
|
||||
key: bondProp(Type.Num),
|
||||
length: bondProp(Type.Num),
|
||||
atomA: bondProp(Types.ElementReference),
|
||||
atomB: bondProp(Types.ElementReference)
|
||||
@@ -356,5 +357,5 @@ export const structureQuery = {
|
||||
combinator,
|
||||
atomSet,
|
||||
atomProperty,
|
||||
bondProperty: bondProperty
|
||||
bondProperty
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 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>
|
||||
@@ -211,21 +211,21 @@ const symbols = [
|
||||
// ============= FILTERS ================
|
||||
D(MolScript.structureQuery.filter.pick, (ctx, xs) => Queries.filters.pick(xs[0] as any, xs['test'])(ctx)),
|
||||
D(MolScript.structureQuery.filter.first, (ctx, xs) => Queries.filters.first(xs[0] as any)(ctx)),
|
||||
D(MolScript.structureQuery.filter.withSameAtomProperties, (ctx, xs) => Queries.filters.withSameAtomProperties(xs[0] as any, xs['source'] as any, xs['property'] as any)(ctx)),
|
||||
D(MolScript.structureQuery.filter.withSameAtomProperties, (ctx, xs) => Queries.filters.withSameAtomProperties(xs[0] as any, xs['source'] as any, xs['property'])(ctx)),
|
||||
D(MolScript.structureQuery.filter.intersectedBy, (ctx, xs) => Queries.filters.areIntersectedBy(xs[0] as any, xs['by'] as any)(ctx)),
|
||||
D(MolScript.structureQuery.filter.within, (ctx, xs) => Queries.filters.within({
|
||||
query: xs[0] as any,
|
||||
target: xs['target'] as any,
|
||||
minRadius: xs['min-radius'] as any,
|
||||
maxRadius: xs['max-radius'] as any,
|
||||
minRadius: xs['min-radius']?.(ctx) as any,
|
||||
maxRadius: xs['max-radius']?.(ctx) as any,
|
||||
elementRadius: xs['atom-radius'] as any,
|
||||
invert: xs['invert'] as any
|
||||
invert: xs['invert']?.(ctx) as any
|
||||
})(ctx)),
|
||||
D(MolScript.structureQuery.filter.isConnectedTo, (ctx, xs) => Queries.filters.isConnectedTo({
|
||||
query: xs[0] as any,
|
||||
target: xs['target'] as any,
|
||||
disjunct: xs['disjunct'] as any,
|
||||
invert: xs['invert'] as any,
|
||||
disjunct: xs['disjunct']?.(ctx) as any,
|
||||
invert: xs['invert']?.(ctx) as any,
|
||||
bondTest: xs['bond-test']
|
||||
})(ctx)),
|
||||
|
||||
@@ -248,6 +248,9 @@ const symbols = [
|
||||
D(MolScript.structureQuery.generator.rings, function structureQuery_generator_rings(ctx, xs) {
|
||||
return Queries.generators.rings(xs?.['fingerprint']?.(ctx) as any, xs?.['only-aromatic']?.(ctx))(ctx);
|
||||
}),
|
||||
D(MolScript.structureQuery.generator.queryInSelection, function structureQuery_generator_queryInSelection(ctx, xs) {
|
||||
return Queries.generators.querySelection(xs[0] as any, xs['query'] as any, xs['in-complement']?.(ctx) as any)(ctx);
|
||||
}),
|
||||
|
||||
// ============= MODIFIERS ================
|
||||
|
||||
@@ -278,6 +281,7 @@ const symbols = [
|
||||
fixedPoint: xs['fixed-point']?.(ctx) ?? false
|
||||
})(ctx);
|
||||
}),
|
||||
D(MolScript.structureQuery.modifier.intersectBy, function structureQuery_modifier_intersectBy(ctx, xs) { return Queries.modifiers.intersectBy(xs[0] as any, xs['by'] as any)(ctx); }),
|
||||
|
||||
// ============= COMBINATORS ================
|
||||
|
||||
@@ -353,9 +357,27 @@ const symbols = [
|
||||
D(MolScript.structureQuery.atomProperty.macromolecular.secondaryStructureFlags, atomProp(StructureProperties.residue.secondary_structure_type)),
|
||||
D(MolScript.structureQuery.atomProperty.macromolecular.chemCompType, atomProp(StructureProperties.residue.chem_comp_type)),
|
||||
|
||||
// ============= ATOM SET ================
|
||||
|
||||
D(MolScript.structureQuery.atomSet.atomCount,
|
||||
function structureQuery_atomset_atomCount(ctx, xs) {
|
||||
return Queries.atomset.atomCount(ctx);
|
||||
}),
|
||||
|
||||
D(MolScript.structureQuery.atomSet.countQuery,
|
||||
function structureQuery_atomset_countQuery(ctx, xs) {
|
||||
return Queries.atomset.countQuery(xs[0] as any)(ctx);
|
||||
}),
|
||||
|
||||
D(MolScript.structureQuery.atomSet.propertySet,
|
||||
function structureQuery_atomset_propertySet(ctx, xs) {
|
||||
return Queries.atomset.propertySet(xs[0] as any)(ctx);
|
||||
}),
|
||||
|
||||
// ============= BOND PROPERTIES ================
|
||||
D(MolScript.structureQuery.bondProperty.order, (ctx, xs) => ctx.atomicBond.order),
|
||||
D(MolScript.structureQuery.bondProperty.flags, (ctx, xs) => ctx.atomicBond.type),
|
||||
D(MolScript.structureQuery.bondProperty.key, (ctx, xs) => ctx.atomicBond.key),
|
||||
D(MolScript.structureQuery.bondProperty.atomA, (ctx, xs) => ctx.atomicBond.a),
|
||||
D(MolScript.structureQuery.bondProperty.atomB, (ctx, xs) => ctx.atomicBond.b),
|
||||
D(MolScript.structureQuery.bondProperty.length, (ctx, xs) => ctx.atomicBond.length),
|
||||
@@ -406,4 +428,4 @@ function getArray<T = any>(ctx: QueryContext, xs: any): T[] {
|
||||
for (const s of symbols) {
|
||||
DefaultQueryRuntimeTable.addSymbol(s);
|
||||
}
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
|
||||
import { transpileMolScript } from './script/mol-script/symbols';
|
||||
import { parseMolScript } from './language/parser';
|
||||
import { parse } from './transpile';
|
||||
import { Expression } from './language/expression';
|
||||
import { StructureElement, QueryContext, StructureSelection, Structure, QueryFn, QueryContextOptions } from '../mol-model/structure';
|
||||
import { compile } from './runtime/query/compiler';
|
||||
import { MolScriptBuilder } from './language/builder';
|
||||
import { assertUnreachable } from '../mol-util/type-helpers';
|
||||
|
||||
export { Script };
|
||||
|
||||
@@ -20,7 +22,13 @@ function Script(expression: string, language: Script.Language): Script {
|
||||
}
|
||||
|
||||
namespace Script {
|
||||
export type Language = 'mol-script'
|
||||
export const Info = {
|
||||
'mol-script': 'Mol-Script',
|
||||
'pymol': 'PyMOL',
|
||||
'vmd': 'VMD',
|
||||
'jmol': 'Jmol',
|
||||
};
|
||||
export type Language = keyof typeof Info;
|
||||
|
||||
export function is(x: any): x is Script {
|
||||
return !!x && typeof (x as Script).expression === 'string' && !!(x as Script).language;
|
||||
@@ -36,8 +44,13 @@ namespace Script {
|
||||
const parsed = parseMolScript(script.expression);
|
||||
if (parsed.length === 0) throw new Error('No query');
|
||||
return transpileMolScript(parsed[0]);
|
||||
case 'pymol':
|
||||
case 'jmol':
|
||||
case 'vmd':
|
||||
return parse(script.language, script.expression);
|
||||
default:
|
||||
assertUnreachable(script.language);
|
||||
}
|
||||
throw new Error('unsupported script language');
|
||||
}
|
||||
|
||||
export function toQuery(script: Script): QueryFn<StructureSelection> {
|
||||
@@ -56,4 +69,4 @@ namespace Script {
|
||||
const query = compile<StructureSelection>(e);
|
||||
return query(new QueryContext(structure, options));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
src/mol-script/transpile.ts
Normal file
27
src/mol-script/transpile.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
|
||||
*
|
||||
* Adapted from MolQL src/transpile.ts
|
||||
*/
|
||||
|
||||
import { Transpiler } from './transpilers/transpiler';
|
||||
import { _transpiler } from './transpilers/all';
|
||||
import { Expression } from './language/expression';
|
||||
import { Script } from './script';
|
||||
const transpiler: {[index: string]: Transpiler} = _transpiler;
|
||||
|
||||
export function parse(lang: Script.Language, str: string): Expression {
|
||||
try {
|
||||
|
||||
const query = transpiler[lang](str);
|
||||
return query;
|
||||
|
||||
} catch (e) {
|
||||
|
||||
console.error(e.message);
|
||||
throw e;
|
||||
|
||||
}
|
||||
}
|
||||
26
src/mol-script/transpilers/_spec/examples.spec.ts
Normal file
26
src/mol-script/transpilers/_spec/examples.spec.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
|
||||
* Adapted from MolQL project
|
||||
**/
|
||||
|
||||
import { Transpiler } from '../transpiler';
|
||||
import { _transpiler as transpilers } from '../all';
|
||||
|
||||
function testTranspilerExamples(name: string, transpiler: Transpiler) {
|
||||
describe(`${name} examples`, () => {
|
||||
const examples = require(`../${name}/examples`).examples;
|
||||
// console.log(examples);
|
||||
for (const e of examples) {
|
||||
|
||||
it(e.name, () => {
|
||||
// check if it transpiles and compiles/typechecks.
|
||||
transpiler(e.value);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testTranspilerExamples('pymol', transpilers.pymol);
|
||||
testTranspilerExamples('vmd', transpilers.vmd);
|
||||
testTranspilerExamples('jmol', transpilers.jmol);
|
||||
115
src/mol-script/transpilers/_spec/jmol.spec.ts
Normal file
115
src/mol-script/transpilers/_spec/jmol.spec.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*
|
||||
* Adapted from MolQL project
|
||||
*/
|
||||
|
||||
import * as u from './utils';
|
||||
import { transpiler } from '../jmol/parser';
|
||||
import { keywords } from '../jmol/keywords';
|
||||
import { properties } from '../jmol/properties';
|
||||
import { operators } from '../jmol/operators';
|
||||
|
||||
const general = {
|
||||
supported: [
|
||||
// atom expressions
|
||||
'123',
|
||||
'-42',
|
||||
'_C',
|
||||
'.CA',
|
||||
'ALA',
|
||||
'%A',
|
||||
'^B',
|
||||
':C',
|
||||
'/2',
|
||||
'10^A:F.CA%C/0',
|
||||
'10^A:F.CA%C',
|
||||
'10^A:F.CA',
|
||||
'10^A:F',
|
||||
'10^A',
|
||||
'10:F.CA',
|
||||
'10/0',
|
||||
'32 or 42',
|
||||
'.CA/0 OR 42:A',
|
||||
'!23',
|
||||
'not ASP',
|
||||
'(ASP or .CA)',
|
||||
'ASP and .CA',
|
||||
'123.CA',
|
||||
'(1 or 2) and .CA',
|
||||
'(1 or 2) and (.CA or .N)',
|
||||
'.CA and (2 or 3)',
|
||||
'.CA and (2 or 3) and ^A',
|
||||
'!32 or :A and .CA',
|
||||
|
||||
// trimming
|
||||
' atomName = CA ',
|
||||
'atomName = CA ',
|
||||
' atomName = CA',
|
||||
|
||||
// value comparison
|
||||
'resno > 10',
|
||||
// atom expression
|
||||
'[LEU]100:A.CA',
|
||||
'[LEU]100:A',
|
||||
'[LEU]100.CA',
|
||||
'[LEU]:A.CA',
|
||||
'[LEU].CA',
|
||||
// comma as OR
|
||||
'100, 42, ALA',
|
||||
// residue numbering
|
||||
'(1-10,15,21-30)',
|
||||
// within
|
||||
'within(5,[HEM])',
|
||||
// within with parentheses
|
||||
'(within(5,[HEM])) and backbone',
|
||||
'( within(5,[HEM]) ) and backbone',
|
||||
// trimming
|
||||
'[ALA] and [VAL] ',
|
||||
' [ALA] and [VAL] ',
|
||||
' [ALA] and [VAL]',
|
||||
// within with whitespaces
|
||||
'within ( 5 , [HEM] ) ',
|
||||
// un-braketed residue name
|
||||
'LEU and ILE',
|
||||
// un-parenthesized residue index range
|
||||
'100-120,220',
|
||||
// un-parenthesized residue index
|
||||
'20',
|
||||
// within in the head or the middle of sentence
|
||||
'within ( 5 , [HEM] ) and backbone',
|
||||
|
||||
// atom expressions with ranges
|
||||
'19-32:A',
|
||||
'-2-32:B',
|
||||
'-10--2:C',
|
||||
'[1FO]19-32:A',
|
||||
],
|
||||
unsupported: [
|
||||
// values outside of comparisons
|
||||
'foobar',
|
||||
'protein or foobar',
|
||||
]
|
||||
};
|
||||
|
||||
describe('jmol general', () => {
|
||||
general.supported.forEach(str => {
|
||||
it(str, () => {
|
||||
transpiler(str);
|
||||
});
|
||||
});
|
||||
general.unsupported.forEach(str => {
|
||||
it(str, () => {
|
||||
const transpileStr = () => transpiler(str);
|
||||
expect(transpileStr).toThrow();
|
||||
expect(transpileStr).not.toThrowError(RangeError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('jmol keywords', () => u.testKeywords(keywords, transpiler));
|
||||
describe('jmol properties', () => u.testProperties(properties, transpiler));
|
||||
describe('jmol operators', () => u.testOperators(operators, transpiler));
|
||||
73
src/mol-script/transpilers/_spec/pymol.spec.ts
Normal file
73
src/mol-script/transpilers/_spec/pymol.spec.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
|
||||
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
|
||||
*/
|
||||
|
||||
import * as u from './utils';
|
||||
import { transpiler } from '../pymol/parser';
|
||||
import { keywords } from '../pymol/keywords';
|
||||
import { properties } from '../pymol/properties';
|
||||
import { operators } from '../pymol/operators';
|
||||
|
||||
const general = {
|
||||
supported: [
|
||||
// macros
|
||||
'10/cb',
|
||||
'a/10-12/ca',
|
||||
'lig/b/6+8/c+o',
|
||||
|
||||
// trimming
|
||||
' name CA ',
|
||||
'name CA ',
|
||||
' name CA',
|
||||
],
|
||||
unsupported: [
|
||||
// macros
|
||||
'pept/enz/c/3/n',
|
||||
'pept/enz///n',
|
||||
|
||||
'/pept/lig/',
|
||||
'/pept/lig/a',
|
||||
'/pept/lig/a/10',
|
||||
'/pept/lig/a/10/ca',
|
||||
'/pept//a/10',
|
||||
|
||||
// object
|
||||
'foobar',
|
||||
'protein and bazbar',
|
||||
]
|
||||
};
|
||||
|
||||
describe('pymol general', () => {
|
||||
general.supported.forEach(str => {
|
||||
it(str, () => {
|
||||
transpiler(str);
|
||||
// compile(expr);
|
||||
});
|
||||
});
|
||||
general.unsupported.forEach(str => {
|
||||
it(str, () => {
|
||||
const transpileStr = () => transpiler(str);
|
||||
expect(transpileStr).toThrow();
|
||||
expect(transpileStr).not.toThrowError(RangeError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// check against builder output
|
||||
// 'not (resi 42 or chain A)'
|
||||
// '!resi 42 or chain A'
|
||||
// 'b >= 0.3',
|
||||
// 'b != 0.3',
|
||||
// 'b>0.3',
|
||||
// 'b <0.3',
|
||||
// 'b <= 0.3',
|
||||
// 'b = 1',
|
||||
// 'fc.=.1',
|
||||
|
||||
describe('pymol keywords', () => u.testKeywords(keywords, transpiler));
|
||||
describe('pymol operators', () => u.testOperators(operators, transpiler));
|
||||
describe('pymol properties', () => u.testProperties(properties, transpiler));
|
||||
69
src/mol-script/transpilers/_spec/utils.ts
Normal file
69
src/mol-script/transpilers/_spec/utils.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Panagiotis Tourlas <panangiot_tourlov@hotmail.com>
|
||||
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
|
||||
*/
|
||||
|
||||
import { Transpiler } from '../transpiler';
|
||||
import { KeywordDict, PropertyDict, OperatorList } from '../types';
|
||||
|
||||
export function testKeywords(keywords: KeywordDict, transpiler: Transpiler) {
|
||||
for (const name in keywords) {
|
||||
it(name, () => {
|
||||
const k = keywords[name];
|
||||
if (k.map) {
|
||||
const expr = transpiler(name);
|
||||
expect(expr).toEqual(k.map());
|
||||
} else {
|
||||
const transpile = () => transpiler(name);
|
||||
expect(transpile).toThrow();
|
||||
expect(transpile).not.toThrowError(RangeError);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function testProperties(properties: PropertyDict, transpiler: Transpiler) {
|
||||
for (const name in properties) {
|
||||
const p = properties[name];
|
||||
p['@examples'].forEach(example => {
|
||||
it(name, () => {
|
||||
if (!p.isUnsupported) {
|
||||
transpiler(example);
|
||||
} else {
|
||||
const transpile = () => transpiler(example);
|
||||
expect(transpile).toThrow();
|
||||
expect(transpile).not.toThrowError(RangeError);
|
||||
}
|
||||
});
|
||||
});
|
||||
it(name, () => {
|
||||
if (!p['@examples'].length) {
|
||||
throw Error(`'${name}' property has no example(s)`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function testOperators(operators: OperatorList, transpiler: Transpiler) {
|
||||
operators.forEach(o => {
|
||||
o['@examples'].forEach(example => {
|
||||
it(o.name, () => {
|
||||
if (!o.isUnsupported) {
|
||||
transpiler(example);
|
||||
} else {
|
||||
const transpile = () => transpiler(example);
|
||||
expect(transpile).toThrow();
|
||||
expect(transpile).not.toThrowError(RangeError);
|
||||
}
|
||||
});
|
||||
});
|
||||
it(o.name, () => {
|
||||
if (!o['@examples'].length) {
|
||||
throw Error(`'${o.name}' operator has no example(s)`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
60
src/mol-script/transpilers/_spec/vmd.spec.ts
Normal file
60
src/mol-script/transpilers/_spec/vmd.spec.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
|
||||
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
|
||||
*/
|
||||
|
||||
import * as u from './utils';
|
||||
import { transpiler } from '../vmd/parser';
|
||||
import { keywords } from '../vmd/keywords';
|
||||
import { properties } from '../vmd/properties';
|
||||
import { operators } from '../vmd/operators';
|
||||
|
||||
const general = {
|
||||
supported: [
|
||||
// trimming
|
||||
' name CA ',
|
||||
'name CA ',
|
||||
' name CA',
|
||||
],
|
||||
unsupported: [
|
||||
// variables
|
||||
'name $atomname',
|
||||
'protein and @myselection',
|
||||
|
||||
// values outside of comparisons
|
||||
'foobar',
|
||||
'34',
|
||||
'name',
|
||||
'abs(-42)',
|
||||
'abs(21+21)',
|
||||
'sqr(3)',
|
||||
'sqr(x)',
|
||||
'sqr(x+33)',
|
||||
'protein or foobar',
|
||||
'34 and protein',
|
||||
'name or protein',
|
||||
]
|
||||
};
|
||||
|
||||
describe('vmd general', () => {
|
||||
general.supported.forEach(str => {
|
||||
it(str, () => {
|
||||
transpiler(str);
|
||||
// compile(expr);
|
||||
});
|
||||
});
|
||||
general.unsupported.forEach(str => {
|
||||
it(str, () => {
|
||||
const transpileStr = () => transpiler(str);
|
||||
expect(transpileStr).toThrow();
|
||||
expect(transpileStr).not.toThrowError(RangeError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('vmd keywords', () => u.testKeywords(keywords, transpiler));
|
||||
describe('vmd operators', () => u.testOperators(operators, transpiler));
|
||||
describe('vmd properties', () => u.testProperties(properties, transpiler));
|
||||
17
src/mol-script/transpilers/all.ts
Normal file
17
src/mol-script/transpilers/all.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*
|
||||
* Adapted from MolQL project
|
||||
*/
|
||||
|
||||
import { transpiler as jmol } from './jmol/parser';
|
||||
import { transpiler as pymol } from './pymol/parser';
|
||||
import { transpiler as vmd } from './vmd/parser';
|
||||
|
||||
export const _transpiler = {
|
||||
pymol,
|
||||
vmd,
|
||||
jmol,
|
||||
};
|
||||
385
src/mol-script/transpilers/helper.ts
Normal file
385
src/mol-script/transpilers/helper.ts
Normal file
@@ -0,0 +1,385 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>
|
||||
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
|
||||
*
|
||||
* Adapted from MolQL project
|
||||
*/
|
||||
|
||||
import * as P from '../../mol-util/monadic-parser';
|
||||
import { MolScriptBuilder } from '../../mol-script/language/builder';
|
||||
const B = MolScriptBuilder;
|
||||
import { Expression } from '../language/expression';
|
||||
import { KeywordDict, PropertyDict, FunctionDict, OperatorList } from './types';
|
||||
|
||||
export function escapeRegExp(s: String) {
|
||||
return String(s).replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
}
|
||||
|
||||
// Takes a parser for the prefix operator, and a parser for the base thing being
|
||||
// parsed, and parses as many occurrences as possible of the prefix operator.
|
||||
// Note that the parser is created using `P.lazy` because it's recursive. It's
|
||||
// valid for there to be zero occurrences of the prefix operator.
|
||||
export function prefix(opParser: P.MonadicParser<any>, nextParser: P.MonadicParser<any>, mapFn: any) {
|
||||
const parser: P.MonadicParser<any> = P.MonadicParser.lazy(() => {
|
||||
return P.MonadicParser.seq(opParser, parser)
|
||||
.map(x => mapFn(...x))
|
||||
.or(nextParser);
|
||||
});
|
||||
return parser;
|
||||
}
|
||||
|
||||
// Ideally this function would be just like `PREFIX` but reordered like
|
||||
// `P.seq(parser, opParser).or(nextParser)`, but that doesn't work. The
|
||||
// reason for that is that Parsimmon will get stuck in infinite recursion, since
|
||||
// the very first rule. Inside `parser` is to match parser again. Alternatively,
|
||||
// you might think to try `nextParser.or(P.seq(parser, opParser))`, but
|
||||
// that won't work either because in a call to `.or` (aka `P.alt`), Parsimmon
|
||||
// takes the first possible match, even if subsequent matches are longer, so the
|
||||
// parser will never actually look far enough ahead to see the postfix
|
||||
// operators.
|
||||
export function postfix(opParser: P.MonadicParser<any>, nextParser: P.MonadicParser<any>, mapFn: any) {
|
||||
// Because we can't use recursion like stated above, we just match a flat list
|
||||
// of as many occurrences of the postfix operator as possible, then use
|
||||
// `.reduce` to manually nest the list.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// INPUT :: "4!!!"
|
||||
// PARSE :: [4, "factorial", "factorial", "factorial"]
|
||||
// REDUCE :: ["factorial", ["factorial", ["factorial", 4]]]
|
||||
return P.MonadicParser.seqMap(
|
||||
nextParser,
|
||||
opParser.many(),
|
||||
(x: any, suffixes: any) =>
|
||||
suffixes.reduce((acc: any, x: any) => {
|
||||
return mapFn(x, acc);
|
||||
}, x)
|
||||
);
|
||||
}
|
||||
|
||||
// Takes a parser for all the operators at this precedence level, and a parser
|
||||
// that parsers everything at the next precedence level, and returns a parser
|
||||
// that parses as many binary operations as possible, associating them to the
|
||||
// right. (e.g. 1^2^3 is 1^(2^3) not (1^2)^3)
|
||||
export function binaryRight(opParser: P.MonadicParser<any>, nextParser: P.MonadicParser<any>, mapFn: any) {
|
||||
const parser: P.MonadicParser<any> = P.MonadicParser.lazy(() =>
|
||||
nextParser.chain(next =>
|
||||
P.MonadicParser.seq(
|
||||
opParser,
|
||||
P.MonadicParser.of(next),
|
||||
parser
|
||||
).map((x) => {
|
||||
return x;
|
||||
}).or(P.MonadicParser.of(next))
|
||||
)
|
||||
);
|
||||
return parser;
|
||||
}
|
||||
|
||||
// Takes a parser for all the operators at this precedence level, and a parser
|
||||
// that parsers everything at the next precedence level, and returns a parser
|
||||
// that parses as many binary operations as possible, associating them to the
|
||||
// left. (e.g. 1-2-3 is (1-2)-3 not 1-(2-3))
|
||||
export function binaryLeft(opParser: P.MonadicParser<any>, nextParser: P.MonadicParser<any>, mapFn: any) {
|
||||
// We run into a similar problem as with the `POSTFIX` parser above where we
|
||||
// can't recurse in the direction we want, so we have to resort to parsing an
|
||||
// entire list of operator chunks and then using `.reduce` to manually nest
|
||||
// them again.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// INPUT :: "1+2+3"
|
||||
// PARSE :: [1, ["+", 2], ["+", 3]]
|
||||
// REDUCE :: ["+", ["+", 1, 2], 3]
|
||||
return P.MonadicParser.seqMap(
|
||||
nextParser,
|
||||
P.MonadicParser.seq(opParser, nextParser).many(),
|
||||
(first: any, rest: any) => {
|
||||
return rest.reduce((acc: any, ch: any) => {
|
||||
const [op, another] = ch;
|
||||
return mapFn(op, acc, another);
|
||||
}, first);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* combine operators of decreasing binding strength
|
||||
*/
|
||||
export function combineOperators(opList: any[], rule: P.MonadicParser<any>) {
|
||||
const x = opList.reduce(
|
||||
(acc, level) => {
|
||||
const map = level.isUnsupported ? makeError(`operator '${level.name}' not supported`) : level.map;
|
||||
return level.type(level.rule, acc, map);
|
||||
},
|
||||
rule
|
||||
);
|
||||
return x;
|
||||
}
|
||||
|
||||
export function infixOp(re: RegExp, group: number = 0) {
|
||||
return P.MonadicParser.optWhitespace.then(P.MonadicParser.regexp(re, group).skip(P.MonadicParser.optWhitespace));
|
||||
}
|
||||
|
||||
export function prefixOp(re: RegExp, group: number = 0) {
|
||||
return P.MonadicParser.regexp(re, group).skip(P.MonadicParser.optWhitespace);
|
||||
}
|
||||
|
||||
export function postfixOp(re: RegExp, group: number = 0) {
|
||||
return P.MonadicParser.optWhitespace.then(P.MonadicParser.regexp(re, group));
|
||||
}
|
||||
|
||||
export function ofOp(name: string, short?: string) {
|
||||
const op = short ? `${name}|${escapeRegExp(short)}` : name;
|
||||
const re = RegExp(`(${op})\\s+([-+]?[0-9]*\\.?[0-9]+)\\s+OF`, 'i');
|
||||
return infixOp(re, 2).map(parseFloat);
|
||||
}
|
||||
|
||||
export function makeError(msg: string) {
|
||||
return function () {
|
||||
throw new Error(msg);
|
||||
};
|
||||
}
|
||||
|
||||
export function andExpr(selections: any[]) {
|
||||
if (selections.length === 1) {
|
||||
return selections[0];
|
||||
} else if (selections.length > 1) {
|
||||
return B.core.logic.and(selections);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function orExpr(selections: any[]) {
|
||||
if (selections.length === 1) {
|
||||
return selections[0];
|
||||
} else if (selections.length > 1) {
|
||||
return B.core.logic.or(selections);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function testExpr(property: any, args: any) {
|
||||
if (args && args.op !== undefined && args.val !== undefined) {
|
||||
const opArgs = [property, args.val];
|
||||
switch (args.op) {
|
||||
case '=': return B.core.rel.eq(opArgs);
|
||||
case '!=': return B.core.rel.neq(opArgs);
|
||||
case '>': return B.core.rel.gr(opArgs);
|
||||
case '<': return B.core.rel.lt(opArgs);
|
||||
case '>=': return B.core.rel.gre(opArgs);
|
||||
case '<=': return B.core.rel.lte(opArgs);
|
||||
default: throw new Error(`operator '${args.op}' not supported`);
|
||||
}
|
||||
} else if (args && args.flags !== undefined) {
|
||||
return B.core.flags.hasAny([property, args.flags]);
|
||||
} else if (args && args.min !== undefined && args.max !== undefined) {
|
||||
return B.core.rel.inRange([property, args.min, args.max]);
|
||||
} else if (!Array.isArray(args)) {
|
||||
return B.core.rel.eq([property, args]);
|
||||
} else if (args.length > 1) {
|
||||
return B.core.set.has([B.core.type.set(args), property]);
|
||||
} else {
|
||||
return B.core.rel.eq([property, args[0]]);
|
||||
}
|
||||
}
|
||||
|
||||
export function invertExpr(selection: Expression) {
|
||||
return B.struct.generator.queryInSelection({
|
||||
0: selection, query: B.struct.generator.all(), 'in-complement': true }
|
||||
);
|
||||
}
|
||||
|
||||
export function strLenSortFn(a: string, b: string) {
|
||||
return a.length < b.length ? 1 : -1;
|
||||
}
|
||||
|
||||
function getNamesRegex(name: string, abbr?: string[]) {
|
||||
const names = (abbr ? [name].concat(abbr) : [name])
|
||||
.sort(strLenSortFn).map(escapeRegExp).join('|');
|
||||
return RegExp(`${names}`, 'i');
|
||||
}
|
||||
|
||||
export function getPropertyRules(properties: PropertyDict) {
|
||||
// in keyof typeof properties
|
||||
const propertiesDict: { [name: string]: P.MonadicParser<any> } = {};
|
||||
|
||||
Object.keys(properties).sort(strLenSortFn).forEach(name => {
|
||||
const ps = properties[name];
|
||||
const errorFn = makeError(`property '${name}' not supported`);
|
||||
const rule = P.MonadicParser.regexp(ps.regex).map((x: any) => {
|
||||
if (ps.isUnsupported) errorFn();
|
||||
return testExpr(ps.property, ps.map(x));
|
||||
});
|
||||
|
||||
if (!ps.isNumeric) {
|
||||
propertiesDict[name] = rule;
|
||||
}
|
||||
});
|
||||
|
||||
return propertiesDict;
|
||||
}
|
||||
|
||||
export function getNamedPropertyRules(properties: PropertyDict) {
|
||||
const namedPropertiesList: P.MonadicParser<any>[] = [];
|
||||
|
||||
Object.keys(properties).sort(strLenSortFn).forEach(name => {
|
||||
const ps = properties[name];
|
||||
const errorFn = makeError(`property '${name}' not supported`);
|
||||
const rule = P.MonadicParser.regexp(ps.regex).map((x: any) => {
|
||||
if (ps.isUnsupported) errorFn();
|
||||
return testExpr(ps.property, ps.map(x));
|
||||
});
|
||||
const nameRule = P.MonadicParser.regexp(getNamesRegex(name, ps.abbr)).trim(P.MonadicParser.optWhitespace);
|
||||
const groupMap = (x: any) => B.struct.generator.atomGroups({ [ps.level]: x });
|
||||
|
||||
if (ps.isNumeric) {
|
||||
namedPropertiesList.push(
|
||||
nameRule.then(P.MonadicParser.seq(
|
||||
P.MonadicParser.regexp(/>=|<=|=|!=|>|</).trim(P.MonadicParser.optWhitespace),
|
||||
P.MonadicParser.regexp(ps.regex).map(ps.map)
|
||||
)).map((x: any) => {
|
||||
if (ps.isUnsupported) errorFn();
|
||||
return testExpr(ps.property, { op: x[0], val: x[1] });
|
||||
}).map(groupMap)
|
||||
);
|
||||
} else {
|
||||
namedPropertiesList.push(nameRule.then(rule).map(groupMap));
|
||||
}
|
||||
});
|
||||
|
||||
return namedPropertiesList;
|
||||
}
|
||||
|
||||
export function getKeywordRules(keywords: KeywordDict) {
|
||||
const keywordsList: P.MonadicParser<any>[] = [];
|
||||
|
||||
Object.keys(keywords).sort(strLenSortFn).forEach(name => {
|
||||
const ks = keywords[name];
|
||||
const mapFn = ks.map ? ks.map : makeError(`keyword '${name}' not supported`);
|
||||
const rule = P.MonadicParser.regexp(getNamesRegex(name, ks.abbr)).map(mapFn);
|
||||
keywordsList.push(rule);
|
||||
});
|
||||
|
||||
return keywordsList;
|
||||
}
|
||||
|
||||
export function getFunctionRules(functions: FunctionDict, argRule: P.MonadicParser<any>) {
|
||||
const functionsList: P.MonadicParser<any>[] = [];
|
||||
const begRule = P.MonadicParser.regexp(/\(\s*/);
|
||||
const endRule = P.MonadicParser.regexp(/\s*\)/);
|
||||
|
||||
Object.keys(functions).sort(strLenSortFn).forEach(name => {
|
||||
const fs = functions[name];
|
||||
const mapFn = fs.map ? fs.map : makeError(`function '${name}' not supported`);
|
||||
const rule = P.MonadicParser.regexp(new RegExp(name, 'i')).skip(begRule).then(argRule).skip(endRule).map(mapFn);
|
||||
functionsList.push(rule);
|
||||
});
|
||||
|
||||
return functionsList;
|
||||
}
|
||||
|
||||
export function getPropertyNameRules(properties: PropertyDict, lookahead: RegExp) {
|
||||
const list: P.MonadicParser<any>[] = [];
|
||||
Object.keys(properties).sort(strLenSortFn).forEach(name => {
|
||||
const ps = properties[name];
|
||||
const errorFn = makeError(`property '${name}' not supported`);
|
||||
const rule = (P.MonadicParser as any).regexp(getNamesRegex(name, ps.abbr)).lookahead(lookahead).map(() => {
|
||||
if (ps.isUnsupported) errorFn();
|
||||
return ps.property;
|
||||
});
|
||||
list.push(rule);
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
export function getReservedWords(properties: PropertyDict, keywords: KeywordDict, operators: OperatorList, functions?: FunctionDict) {
|
||||
const w: string[] = [];
|
||||
for (const name in properties) {
|
||||
w.push(name);
|
||||
if (properties[name].abbr) w.push(...properties[name].abbr!);
|
||||
}
|
||||
for (const name in keywords) {
|
||||
w.push(name);
|
||||
if (keywords[name].abbr) w.push(...keywords[name].abbr!);
|
||||
}
|
||||
operators.forEach(o => {
|
||||
w.push(o.name);
|
||||
if (o.abbr) w.push(...o.abbr);
|
||||
});
|
||||
return w;
|
||||
}
|
||||
|
||||
export function atomNameSet(ids: string[]) {
|
||||
return B.core.type.set(ids.map(B.atomName));
|
||||
}
|
||||
|
||||
export function asAtoms(e: Expression) {
|
||||
return B.struct.generator.queryInSelection({
|
||||
0: e,
|
||||
query: B.struct.generator.all()
|
||||
});
|
||||
}
|
||||
|
||||
export function wrapValue(property: any, value: any, sstrucDict?: any) {
|
||||
switch (property.head.name) {
|
||||
case 'structure-query.atom-property.macromolecular.label_atom_id':
|
||||
return B.atomName(value);
|
||||
case 'structure-query.atom-property.core.element-symbol':
|
||||
return B.es(value);
|
||||
case 'structure-query.atom-property.macromolecular.secondary-structure-flags':
|
||||
if (sstrucDict) {
|
||||
value = [sstrucDict[value.toUpperCase()] || 'none'];
|
||||
}
|
||||
return B.struct.type.secondaryStructureFlags([value]);
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
const propPrefix = 'structure-query.atom-property.macromolecular.';
|
||||
const entityProps = ['entityKey', 'label_entity_id', 'entityType'];
|
||||
const chainProps = ['chainKey', 'label_asym_id', 'label_entity_id', 'auth_asym_id', 'entityType'];
|
||||
const residueProps = ['residueKey', 'label_comp_id', 'label_seq_id', 'auth_comp_id', 'auth_seq_id', 'pdbx_formal_charge', 'secondaryStructureKey', 'secondaryStructureFlags', 'isModified', 'modifiedParentName'];
|
||||
export function testLevel(property: any) {
|
||||
if (property.head.name.startsWith(propPrefix)) {
|
||||
const name = property.head.name.substr(propPrefix.length);
|
||||
if (entityProps.includes(name)) return 'entity-test';
|
||||
if (chainProps.includes(name)) return 'chain-test';
|
||||
if (residueProps.includes(name)) return 'residue-test';
|
||||
}
|
||||
return 'atom-test';
|
||||
}
|
||||
|
||||
const flagProps = [
|
||||
'structure-query.atom-property.macromolecular.secondary-structure-flags'
|
||||
];
|
||||
export function valuesTest(property: any, values: any[]) {
|
||||
if (flagProps.includes(property.head.name)) {
|
||||
const name = values[0].head;
|
||||
const flags: any[] = [];
|
||||
values.forEach(v => flags.push(...v.args[0]));
|
||||
return B.core.flags.hasAny([property, { head: name, args: flags }]);
|
||||
} else {
|
||||
if (values.length === 1) {
|
||||
return B.core.rel.eq([property, values[0]]);
|
||||
} else if (values.length > 1) {
|
||||
return B.core.set.has([B.core.type.set(values), property]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function resnameExpr(resnameList: string[]) {
|
||||
return B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.set.has([
|
||||
B.core.type.set(resnameList),
|
||||
B.ammp('label_comp_id')
|
||||
])
|
||||
});
|
||||
}
|
||||
28
src/mol-script/transpilers/jmol/examples.ts
Normal file
28
src/mol-script/transpilers/jmol/examples.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 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>
|
||||
*
|
||||
* Adapted from MolQL project
|
||||
*/
|
||||
|
||||
export const examples = [{
|
||||
name: 'Residue 50 or 135',
|
||||
value: '50 or 135'
|
||||
}, {
|
||||
name: 'Atoms with no covalent bonds',
|
||||
value: 'bondcount = 0'
|
||||
}, {
|
||||
name: 'All 3-10 helices',
|
||||
value: 'substructure = "helix310"'
|
||||
}, {
|
||||
name: 'Metal atoms',
|
||||
value: 'metal'
|
||||
}, {
|
||||
name: 'Atoms invloved in aromatic bonds',
|
||||
value: 'isAromatic'
|
||||
}, {
|
||||
name: 'Pyrimidine residues',
|
||||
value: 'pyrimidine'
|
||||
}];
|
||||
571
src/mol-script/transpilers/jmol/keywords.ts
Normal file
571
src/mol-script/transpilers/jmol/keywords.ts
Normal file
@@ -0,0 +1,571 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*
|
||||
* Adapted from MolQL project
|
||||
*/
|
||||
|
||||
|
||||
import { MolScriptBuilder } from '../../../mol-script/language/builder';
|
||||
const B = MolScriptBuilder;
|
||||
import * as h from '../helper';
|
||||
import { KeywordDict } from '../types';
|
||||
|
||||
const ResDict = {
|
||||
acidic: ['ASP', 'GLU'],
|
||||
aliphatic: ['ALA', 'GLY', 'ILE', 'LEU', 'VAL'],
|
||||
amino: ['ALA', 'ARG', 'ASN', 'ASP', 'CYS', 'GLN', 'GLU', 'GLY', 'HIS', 'ILE', 'LEU', 'LYS', 'MET', 'PHE', 'PRO', 'SER', 'THR', 'TRP', 'TYR', 'VAL', 'ASX', 'GLX', 'UNK'],
|
||||
aromatic: ['HIS', 'PHE', 'TRP', 'TYR'],
|
||||
basic: ['ARG', 'HIS', 'LYS'],
|
||||
buried: ['ALA', 'CYS', 'ILE', 'LEU', 'MET', 'PHE', 'TRP', 'VAL'],
|
||||
cg: ['CYT', 'C', 'GUA', 'G'],
|
||||
cyclic: ['HIS', 'PHE', 'PRO', 'TRP', 'TYR'],
|
||||
hydrophobic: ['ALA', 'GLY', 'ILE', 'LEU', 'MET', 'PHE', 'PRO', 'TRP', 'TYR', 'VAL'],
|
||||
large: ['ARG', 'GLU', 'GLN', 'HIS', 'ILE', 'LEU', 'LYS', 'MET', 'PHE', 'TRP', 'TYR'],
|
||||
medium: ['ASN', 'ASP', 'CYS', 'PRO', 'THR', 'VAL'],
|
||||
small: ['ALA', 'GLY', 'SER'],
|
||||
|
||||
nucleic: ['G', 'C', 'A', 'T', 'U', 'I', 'DG', 'DC', 'DA', 'DT', 'DU', 'DI', '+G', '+C', '+A', '+T', '+U', '+I']
|
||||
};
|
||||
|
||||
const Backbone = {
|
||||
nucleic: ['P', "O3'", "O5'", "C5'", "C4'", "C3'", 'OP1', 'OP2', 'O3*', 'O5*', 'C5*', 'C4*', 'C3*',
|
||||
"C2'", "C1'", "O4'", "O2'"],
|
||||
protein: ['C', 'N', 'CA']
|
||||
};
|
||||
|
||||
function nucleicExpr() {
|
||||
return B.struct.combinator.merge([
|
||||
B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.set.has([
|
||||
B.set(...ResDict.nucleic),
|
||||
B.ammp('label_comp_id')
|
||||
])
|
||||
}),
|
||||
B.struct.filter.pick({
|
||||
0: B.struct.generator.atomGroups({
|
||||
'group-by': B.ammp('residueKey')
|
||||
}),
|
||||
test: B.core.logic.and([
|
||||
B.core.rel.eq([B.struct.atomSet.atomCount(), 1]),
|
||||
B.core.rel.eq([B.ammp('label_atom_id'), B.atomName('P')]),
|
||||
])
|
||||
}),
|
||||
B.struct.filter.pick({
|
||||
0: B.struct.generator.atomGroups({
|
||||
'group-by': B.ammp('residueKey')
|
||||
}),
|
||||
test: B.core.logic.or([
|
||||
B.core.set.isSubset([
|
||||
h.atomNameSet(["C1'", "C2'", "O3'", "C3'", "C4'", "C5'", "O5'"]),
|
||||
B.ammpSet('label_atom_id')
|
||||
]),
|
||||
B.core.set.isSubset([
|
||||
h.atomNameSet(['C1*', 'C2*', 'O3*', 'C3*', 'C4*', 'C5*', 'O5*']),
|
||||
B.ammpSet('label_atom_id')
|
||||
])
|
||||
])
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
// TODO: improve, see keywords.protein['@desc'] below
|
||||
function proteinExpr() {
|
||||
return B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.set.has([
|
||||
B.set(...ResDict.amino),
|
||||
B.ammp('label_comp_id')
|
||||
])
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: improve, see keywords.backbone['@desc'] below
|
||||
function backboneExpr() {
|
||||
return B.struct.combinator.merge([
|
||||
B.struct.modifier.intersectBy({
|
||||
0: B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.set.has([
|
||||
B.core.type.set(ResDict.amino),
|
||||
B.ammp('label_comp_id')
|
||||
])
|
||||
}),
|
||||
by: B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.set.has([
|
||||
B.core.type.set(Backbone.protein),
|
||||
B.ammp('label_atom_id')
|
||||
])
|
||||
})
|
||||
}),
|
||||
B.struct.modifier.intersectBy({
|
||||
0: B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.set.has([
|
||||
B.core.type.set(ResDict.nucleic),
|
||||
B.ammp('label_comp_id')
|
||||
])
|
||||
}),
|
||||
by: B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.set.has([
|
||||
B.core.type.set(Backbone.nucleic),
|
||||
B.ammp('label_atom_id')
|
||||
])
|
||||
})
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
export const keywords: KeywordDict = {
|
||||
// general terms
|
||||
all: {
|
||||
'@desc': 'all atoms; same as *',
|
||||
abbr: ['*'],
|
||||
map: () => B.struct.generator.all()
|
||||
},
|
||||
bonded: {
|
||||
'@desc': 'covalently bonded',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.rel.gr([B.struct.atomProperty.core.bondCount({
|
||||
flags: B.struct.type.bondFlags(['covalent', 'metallic', 'sulfide'])
|
||||
}), 0])
|
||||
})
|
||||
},
|
||||
clickable: {
|
||||
'@desc': 'actually visible -- having some visible aspect such as wireframe, spacefill, or a label showing, or the alpha-carbon or phosphorus atom in a biomolecule that is rendered with only cartoon, rocket, or other biomolecule-specific shape.'
|
||||
},
|
||||
connected: {
|
||||
'@desc': 'bonded in any way, including hydrogen bonds',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.rel.gr([B.struct.atomProperty.core.bondCount({
|
||||
flags: B.struct.type.bondFlags()
|
||||
}), 0])
|
||||
})
|
||||
},
|
||||
displayed: {
|
||||
'@desc': 'displayed using the display or hide command; not necessarily visible'
|
||||
},
|
||||
hidden: {
|
||||
'@desc': 'hidden using the display or hide command'
|
||||
},
|
||||
none: {
|
||||
'@desc': 'no atoms',
|
||||
map: () => B.struct.generator.empty()
|
||||
},
|
||||
selected: {
|
||||
'@desc': 'atoms that have been selected; defaults to all when a file is first loaded'
|
||||
},
|
||||
thisModel: {
|
||||
'@desc': 'atoms in the current frame set, as defined by frame, model, or animation commands. If more than one model is in this set, "thisModel" refers to all of them, regardless of atom displayed/hidden status.'
|
||||
},
|
||||
visible: {
|
||||
'@desc': 'visible in any way, including PDB residue atoms for which a cartoon or other such rendering makes their group visible, even if they themselves are not visible.'
|
||||
},
|
||||
subset: {
|
||||
'@desc': 'the currently defined subset. Note that if a subset is currently defined, then select/display all is the same as select/display subset, restrict none is the same as restrict not subset. In addition, select not subset selects nothing.'
|
||||
},
|
||||
specialPosition: {
|
||||
'@desc': 'atoms in crystal structures that are at special positions - that is, for which there is more than one operator that leads to them.'
|
||||
},
|
||||
unitcell: {
|
||||
'@desc': 'atoms within the current unitcell, which may be offset. This includes atoms on the faces and at the vertices of the unitcell.'
|
||||
},
|
||||
polyhedra: {
|
||||
'@desc': 'all central atoms for which polyhedra have been created. See also polyhera(n), below. (Jmol 14.4)'
|
||||
},
|
||||
nonmetal: {
|
||||
'@desc': '_H,_He,_B,_C,_N,_O,_F,_Ne,_Si,_P,_S,_Cl,_Ar,_As,_Se,_Br,_Kr,_Te,_I,_Xe,_At,_Rn',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.set.has([
|
||||
B.set(...['H', 'He', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Si', 'P', 'S', 'Cl', 'Ar', 'As', 'Se', 'Br', 'Kr', 'Te', 'I', 'Xe', 'At', 'Rn'].map(B.es)),
|
||||
B.acp('elementSymbol')
|
||||
])
|
||||
})
|
||||
},
|
||||
metal: {
|
||||
'@desc': '!nonmetal',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.logic.not([
|
||||
B.core.set.has([
|
||||
B.set(...['H', 'He', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Si', 'P', 'S', 'Cl', 'Ar', 'As', 'Se', 'Br', 'Kr', 'Te', 'I', 'Xe', 'At', 'Rn'].map(B.es)),
|
||||
B.acp('elementSymbol')
|
||||
])
|
||||
])
|
||||
})
|
||||
},
|
||||
alkaliMetal: {
|
||||
'@desc': '_Li,_Na,_K,_Rb,_Cs,_Fr',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.set.has([
|
||||
B.set(...['Li', 'Na', 'K', 'Rb', 'Cs', 'Fr'].map(B.es)),
|
||||
B.acp('elementSymbol')
|
||||
])
|
||||
})
|
||||
},
|
||||
alkalineEarth: {
|
||||
'@desc': '_Be,_Mg,_Ca,_Sr,_Ba,_Ra',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.set.has([
|
||||
B.set(...['Be', 'Mg', 'Ca', 'Sr', 'Ba', 'Ra'].map(B.es)),
|
||||
B.acp('elementSymbol')
|
||||
])
|
||||
})
|
||||
},
|
||||
nobleGas: {
|
||||
'@desc': '_He,_Ne,_Ar,_Kr,_Xe,_Rn',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.set.has([
|
||||
B.set(...['He', 'Ne', 'Ar', 'Kr', 'Xe', 'Rn'].map(B.es)),
|
||||
B.acp('elementSymbol')
|
||||
])
|
||||
})
|
||||
},
|
||||
metalloid: {
|
||||
'@desc': '_B,_Si,_Ge,_As,_Sb,_Te',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.set.has([
|
||||
B.set(...['B', 'Si', 'Ge', 'As', 'Sb', 'Te'].map(B.es)),
|
||||
B.acp('elementSymbol')
|
||||
])
|
||||
})
|
||||
},
|
||||
transitionMetal: {
|
||||
'@desc': '(includes La and Ac) elemno>=21 and elemno<=30, elemno=57, elemno=89, elemno>=39 and elemno<=48, elemno>=72 and elemno<=80, elemno>=104 and elemno<=112',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.logic.or([
|
||||
B.core.rel.inRange([B.acp('atomicNumber'), 21, 30]),
|
||||
B.core.rel.inRange([B.acp('atomicNumber'), 39, 48]),
|
||||
B.core.rel.inRange([B.acp('atomicNumber'), 72, 80]),
|
||||
B.core.rel.inRange([B.acp('atomicNumber'), 104, 112]),
|
||||
B.core.set.has([B.set(57, 89), B.acp('atomicNumber')])
|
||||
])
|
||||
})
|
||||
},
|
||||
lanthanide: {
|
||||
'@desc': '(does not include La) elemno>57 and elemno<=71',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.rel.inRange([B.acp('atomicNumber'), 57, 71])
|
||||
})
|
||||
},
|
||||
actinide: {
|
||||
'@desc': '(does not include Ac) elemno>89 and elemno<=103',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.rel.inRange([B.acp('atomicNumber'), 89, 103])
|
||||
})
|
||||
},
|
||||
isaromatic: {
|
||||
'@desc': 'atoms connected with the AROMATIC, AROMATICSINGLE, or AROMATICDOUBLE bond types',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.rel.gr([
|
||||
B.struct.atomProperty.core.bondCount({
|
||||
flags: B.struct.type.bondFlags(['aromatic'])
|
||||
}),
|
||||
0
|
||||
])
|
||||
})
|
||||
},
|
||||
|
||||
carbohydrate: {
|
||||
'@desc': ''
|
||||
},
|
||||
ions: {
|
||||
'@desc': '(specifically the PDB designations "PO4" and "SO4")'
|
||||
},
|
||||
ligand: {
|
||||
'@desc': '(originally "hetero and not solvent"; changed to "!(protein,nucleic,water,UREA)" for Jmol 12.2)'
|
||||
},
|
||||
nucleic: {
|
||||
'@desc': 'any group that (a) has one of the following group names: G, C, A, T, U, I, DG, DC, DA, DT, DU, DI, +G, +C, +A, +T, +U, +I; or (b) can be identified as a group that is only one atom, with name "P"; or (c) has all of the following atoms (prime, \', can replace * here): C1*, C2*, C3*, O3*, C4*, C5*, and O5*.',
|
||||
map: () => nucleicExpr()
|
||||
},
|
||||
purine: {
|
||||
'@desc': 'any nucleic group that (a) has one of the following group names: A, G, I, DA, DG, DI, +A, +G, or +I; or (b) also has atoms N7, C8, and N9.',
|
||||
map: () => B.struct.modifier.intersectBy({
|
||||
0: nucleicExpr(),
|
||||
by: B.struct.combinator.merge([
|
||||
B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.set.has([
|
||||
B.set(...['A', 'G', 'I', 'DA', 'DG', 'DI', '+A', '+G', '+I']),
|
||||
B.ammp('label_comp_id')
|
||||
])
|
||||
}),
|
||||
B.struct.filter.pick({
|
||||
0: B.struct.generator.atomGroups({
|
||||
'group-by': B.ammp('residueKey')
|
||||
}),
|
||||
test: B.core.set.isSubset([
|
||||
h.atomNameSet(['N7', 'C8', 'N9']),
|
||||
B.ammpSet('label_atom_id')
|
||||
])
|
||||
})
|
||||
])
|
||||
})
|
||||
},
|
||||
pyrimidine: {
|
||||
'@desc': 'any nucleic group that (a) has one of the following group names: C, T, U, DC, DT, DU, +C, +T, +U; or (b) also has atom O2.',
|
||||
map: () => B.struct.modifier.intersectBy({
|
||||
0: nucleicExpr(),
|
||||
by: B.struct.combinator.merge([
|
||||
B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.set.has([
|
||||
B.set(...['C', 'T', 'U', 'DC', 'DT', 'DU', '+C', '+T', '+U']),
|
||||
B.ammp('label_comp_id')
|
||||
])
|
||||
}),
|
||||
B.struct.filter.pick({
|
||||
0: B.struct.generator.atomGroups({
|
||||
'group-by': B.ammp('residueKey')
|
||||
}),
|
||||
test: B.core.logic.or([
|
||||
B.core.set.has([
|
||||
B.ammpSet('label_atom_id'),
|
||||
B.atomName('O2*')
|
||||
]),
|
||||
B.core.set.has([
|
||||
B.ammpSet('label_atom_id'),
|
||||
B.atomName("O2'")
|
||||
])
|
||||
])
|
||||
})
|
||||
])
|
||||
})
|
||||
},
|
||||
dna: {
|
||||
'@desc': 'any nucleic group that (a) has one of the following group names: DG, DC, DA, DT, DU, DI, T, +G, +C, +A, +T; or (b) has neither atom O2* or O2\'.',
|
||||
map: () => B.struct.modifier.intersectBy({
|
||||
0: nucleicExpr(),
|
||||
by: B.struct.combinator.merge([
|
||||
B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.set.has([
|
||||
B.set(...['DG', 'DC', 'DA', 'DT', 'DU', 'DI', 'T', '+G', '+C', '+A', '+T']),
|
||||
B.ammp('label_comp_id')
|
||||
])
|
||||
}),
|
||||
B.struct.filter.pick({
|
||||
0: B.struct.generator.atomGroups({
|
||||
'group-by': B.ammp('residueKey')
|
||||
}),
|
||||
test: B.core.logic.not([
|
||||
B.core.logic.or([
|
||||
B.core.set.has([
|
||||
B.ammpSet('label_atom_id'),
|
||||
B.atomName('O2*')
|
||||
]),
|
||||
B.core.set.has([
|
||||
B.ammpSet('label_atom_id'),
|
||||
B.atomName("O2'")
|
||||
])
|
||||
])
|
||||
])
|
||||
})
|
||||
])
|
||||
})
|
||||
},
|
||||
rna: {
|
||||
'@desc': 'any nucleic group that (a) has one of the following group names: G, C, A, U, I, +U, +I; or (b) has atom O2* or O2\'.',
|
||||
map: () => B.struct.modifier.intersectBy({
|
||||
0: nucleicExpr(),
|
||||
by: B.struct.combinator.merge([
|
||||
B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.set.has([
|
||||
B.set(...['G', 'C', 'A', 'U', 'I', '+U', '+I']),
|
||||
B.ammp('label_comp_id')
|
||||
])
|
||||
}),
|
||||
B.struct.filter.pick({
|
||||
0: B.struct.generator.atomGroups({
|
||||
'group-by': B.ammp('residueKey')
|
||||
}),
|
||||
test: B.core.logic.or([
|
||||
B.core.set.has([
|
||||
B.ammpSet('label_atom_id'),
|
||||
B.atomName('O2*')
|
||||
]),
|
||||
B.core.set.has([
|
||||
B.ammpSet('label_atom_id'),
|
||||
B.atomName("O2'")
|
||||
])
|
||||
])
|
||||
})
|
||||
])
|
||||
})
|
||||
},
|
||||
protein: {
|
||||
'@desc': 'defined as a group that (a) has one of the following group names: ALA, ARG, ASN, ASP, CYS, GLN, GLU, GLY, HIS, ILE, LEU, LYS, MET, PHE, PRO, SER, THR, TRP, TYR, VAL, ASX, GLX, or UNK; or (b) contains PDB atom designations [C, O, CA, and N] bonded correctly; or (c) does not contain "O" but contains [C, CA, and N] bonded correctly; or (d) has only one atom, which has name CA and does not have the group name CA (indicating a calcium atom).',
|
||||
map: () => proteinExpr()
|
||||
},
|
||||
acidic: {
|
||||
'@desc': 'ASP GLU',
|
||||
map: () => h.resnameExpr(ResDict.acidic)
|
||||
},
|
||||
acyclic: {
|
||||
'@desc': 'amino and not cyclic',
|
||||
map: () => B.struct.modifier.intersectBy({
|
||||
0: h.resnameExpr(ResDict.amino),
|
||||
by: h.invertExpr(h.resnameExpr(ResDict.cyclic))
|
||||
})
|
||||
},
|
||||
aliphatic: {
|
||||
'@desc': 'ALA GLY ILE LEU VAL',
|
||||
map: () => h.resnameExpr(ResDict.aliphatic)
|
||||
},
|
||||
amino: {
|
||||
'@desc': 'all twenty standard amino acids, plus ASX, GLX, UNK',
|
||||
map: () => h.resnameExpr(ResDict.amino)
|
||||
},
|
||||
aromatic: {
|
||||
'@desc': 'HIS PHE TRP TYR (see also "isaromatic" for aromatic bonds)',
|
||||
map: () => h.resnameExpr(ResDict.aromatic)
|
||||
},
|
||||
basic: {
|
||||
'@desc': 'ARG HIS LYS',
|
||||
map: () => h.resnameExpr(ResDict.basic)
|
||||
},
|
||||
buried: {
|
||||
'@desc': 'ALA CYS ILE LEU MET PHE TRP VAL',
|
||||
map: () => h.resnameExpr(ResDict.buried)
|
||||
},
|
||||
charged: {
|
||||
'@desc': 'same as acidic or basic -- ASP GLU, ARG HIS LYS',
|
||||
map: () => h.resnameExpr(ResDict.acidic.concat(ResDict.basic))
|
||||
},
|
||||
cyclic: {
|
||||
'@desc': 'HIS PHE PRO TRP TYR',
|
||||
map: () => h.resnameExpr(ResDict.cyclic)
|
||||
},
|
||||
helix: {
|
||||
'@desc': 'secondary structure-related.',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.flags.hasAny([
|
||||
B.struct.type.secondaryStructureFlags(['helix']),
|
||||
B.ammp('secondaryStructureFlags')
|
||||
])
|
||||
})
|
||||
},
|
||||
helixalpha: {
|
||||
'@desc': 'secondary structure-related.',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.flags.hasAny([
|
||||
B.struct.type.secondaryStructureFlags(['alpha']),
|
||||
B.ammp('secondaryStructureFlags')
|
||||
])
|
||||
})
|
||||
},
|
||||
helix310: {
|
||||
'@desc': 'secondary structure-related.',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.flags.hasAny([
|
||||
B.struct.type.secondaryStructureFlags(['3-10']),
|
||||
B.ammp('secondaryStructureFlags')
|
||||
])
|
||||
})
|
||||
},
|
||||
helixpi: {
|
||||
'@desc': 'secondary structure-related.',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.flags.hasAny([
|
||||
B.struct.type.secondaryStructureFlags(['pi']),
|
||||
B.ammp('secondaryStructureFlags')
|
||||
])
|
||||
})
|
||||
},
|
||||
hetero: {
|
||||
'@desc': 'PDB atoms designated as HETATM',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.ammp('isHet')
|
||||
})
|
||||
},
|
||||
hydrophobic: {
|
||||
'@desc': 'ALA GLY ILE LEU MET PHE PRO TRP TYR VAL',
|
||||
map: () => h.resnameExpr(ResDict.hydrophobic)
|
||||
},
|
||||
large: {
|
||||
'@desc': 'ARG GLU GLN HIS ILE LEU LYS MET PHE TRP TYR',
|
||||
map: () => h.resnameExpr(ResDict.large)
|
||||
},
|
||||
medium: {
|
||||
'@desc': 'ASN ASP CYS PRO THR VAL',
|
||||
map: () => h.resnameExpr(ResDict.medium)
|
||||
},
|
||||
negative: {
|
||||
'@desc': 'same as acidic -- ASP GLU',
|
||||
map: () => h.resnameExpr(ResDict.acidic)
|
||||
},
|
||||
neutral: {
|
||||
'@desc': 'amino and not (acidic or basic)',
|
||||
map: () => B.struct.modifier.intersectBy({
|
||||
0: h.resnameExpr(ResDict.amino),
|
||||
by: h.invertExpr(h.resnameExpr(ResDict.acidic.concat(ResDict.basic)))
|
||||
})
|
||||
},
|
||||
polar: {
|
||||
'@desc': 'amino and not hydrophobic',
|
||||
map: () => B.struct.modifier.intersectBy({
|
||||
0: h.resnameExpr(ResDict.amino),
|
||||
by: h.invertExpr(h.resnameExpr(ResDict.hydrophobic))
|
||||
})
|
||||
},
|
||||
positive: {
|
||||
'@desc': 'same as basic -- ARG HIS LYS',
|
||||
map: () => h.resnameExpr(ResDict.basic)
|
||||
},
|
||||
sheet: {
|
||||
'@desc': 'secondary structure-related',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.flags.hasAny([
|
||||
B.struct.type.secondaryStructureFlags(['sheet']),
|
||||
B.ammp('secondaryStructureFlags')
|
||||
])
|
||||
})
|
||||
},
|
||||
small: {
|
||||
'@desc': 'ALA GLY SER',
|
||||
map: () => h.resnameExpr(ResDict.small)
|
||||
},
|
||||
surface: {
|
||||
'@desc': 'amino and not buried',
|
||||
map: () => B.struct.modifier.intersectBy({
|
||||
0: h.resnameExpr(ResDict.amino),
|
||||
by: h.invertExpr(h.resnameExpr(ResDict.buried))
|
||||
})
|
||||
},
|
||||
turn: {
|
||||
'@desc': 'secondary structure-related',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'residue-test': B.core.flags.hasAny([
|
||||
B.struct.type.secondaryStructureFlags(['turn']),
|
||||
B.ammp('secondaryStructureFlags')
|
||||
])
|
||||
})
|
||||
},
|
||||
alpha: {
|
||||
'@desc': '(*.CA)',
|
||||
map: () => B.struct.generator.atomGroups({
|
||||
'atom-test': B.core.rel.eq([
|
||||
B.atomName('CA'),
|
||||
B.ammp('label_atom_id')
|
||||
])
|
||||
})
|
||||
},
|
||||
base: {
|
||||
'@desc': '(nucleic bases)'
|
||||
},
|
||||
backbone: {
|
||||
'@desc': '(*.C, *.CA, *.N, and all nucleic other than the bases themselves)',
|
||||
abbr: ['mainchain'],
|
||||
map: () => backboneExpr()
|
||||
},
|
||||
sidechain: {
|
||||
'@desc': '((protein or nucleic) and not backbone)'
|
||||
},
|
||||
spine: {
|
||||
'@desc': '(*.CA, *.N, *.C for proteins; *.P, *.O3\', *.O5\', *.C3\', *.C4\', *.C5 for nucleic acids)'
|
||||
},
|
||||
leadatom: {
|
||||
'@desc': '(*.CA, *.P, and terminal *.O5\')'
|
||||
},
|
||||
solvent: {
|
||||
'@desc': 'PDB "HOH", water, also the connected set of H-O-H in any model'
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
64
src/mol-script/transpilers/jmol/markdown-docs.ts
Normal file
64
src/mol-script/transpilers/jmol/markdown-docs.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Koya Sakuma <koya.sakuma.work@gmail.com>
|
||||
*
|
||||
* Adapted from MolQL project
|
||||
*/
|
||||
|
||||
import { properties } from './properties';
|
||||
import { operators } from './operators';
|
||||
import { keywords } from './keywords';
|
||||
|
||||
|
||||
const _docs: string[] = [
|
||||
'Jmol',
|
||||
'============',
|
||||
'--------------------------------',
|
||||
''
|
||||
];
|
||||
|
||||
_docs.push(`## Properties\n\n`);
|
||||
_docs.push('--------------------------------\n');
|
||||
for (const name in properties) {
|
||||
if (properties[name].isUnsupported) continue;
|
||||
|
||||
const names = [name];
|
||||
if (properties[name].abbr) names.push(...properties[name].abbr!);
|
||||
_docs.push(`\`\`\`\n${names.join(', ')}\n\`\`\`\n`);
|
||||
|
||||
if (properties[name]['@desc']) {
|
||||
_docs.push(`*${properties[name]['@desc']}*\n`);
|
||||
}
|
||||
}
|
||||
|
||||
_docs.push(`## Operators\n\n`);
|
||||
_docs.push('--------------------------------\n');
|
||||
operators.forEach(o => {
|
||||
if (o.isUnsupported) return;
|
||||
|
||||
const names = [o.name];
|
||||
if (o.abbr) names.push(...o.abbr!);
|
||||
_docs.push(`\`\`\`\n${names.join(', ')}\n\`\`\`\n`);
|
||||
|
||||
if (o['@desc']) {
|
||||
_docs.push(`*${o['@desc']}*\n`);
|
||||
}
|
||||
});
|
||||
|
||||
_docs.push(`## Keywords\n\n`);
|
||||
_docs.push('--------------------------------\n');
|
||||
for (const name in keywords) {
|
||||
if (!keywords[name].map) continue;
|
||||
|
||||
const names = [name];
|
||||
if (keywords[name].abbr) names.push(...keywords[name].abbr!);
|
||||
_docs.push(`\`\`\`\n${names.join(', ')}\n\`\`\`\n`);
|
||||
|
||||
if (keywords[name]['@desc']) {
|
||||
_docs.push(`*${keywords[name]['@desc']}*\n`);
|
||||
}
|
||||
}
|
||||
|
||||
export const docs = _docs.join('\n');
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user