Compare commits

...

137 Commits

Author SHA1 Message Date
dsehnal
7fac8a8f77 2.0.5 2021-04-26 16:02:31 +02:00
dsehnal
7266c67e32 2.0.5 changelog 2021-04-26 16:00:31 +02:00
dsehnal
50c8d09742 default camera radius set to 0 2021-04-26 15:13:46 +02:00
dsehnal
7377947975 Changelog 2021-04-25 15:15:26 +02:00
David Sehnal
a3c4daf30a Merge pull request #169 from sukolsak/texture-mesh-export
Add TextureMesh export support
2021-04-25 15:13:26 +02:00
Sukolsak Sakshuwong
9d7e6f1d99 add TextureMesh export support 2021-04-25 05:04:03 -07:00
dsehnal
9e105020e3 lazy volume loading 2021-04-25 12:10:09 +02:00
Alexander Rose
93df548cfe add torus primitive and fix render tests 2021-04-23 22:55:20 -07:00
Alexander Rose
a0b1593c82 add MeshBuilder.addMesh 2021-04-23 22:12:19 -07:00
Alexander Rose
fc81e08d73 Support full pausing (no draw) rendering 2021-04-23 22:10:01 -07:00
Alexander Rose
5369fa5adf canvas viewport support fixes and improvements
- restrict ssao to viewport bounds
- only downscale ssao buffer (not upscale)
- avoid zero camera radius/radiusMax to allow camera movements in empty scenes and to avoid ssao artifacts
2021-04-23 22:07:34 -07:00
dsehnal
316a77c716 guard against non-invertible matrices in Camera.update
+ relative viewports and dynamic updating of them sometimes caused non-invertible matrix
2021-04-23 19:05:29 +02:00
dsehnal
42dfa69ad7 Residue list selection helper 2021-04-21 21:24:28 +02:00
dsehnal
cae4eb8b0e await screenshot clipboard write & fallback to <img> on fail 2021-04-21 20:08:37 +02:00
David Sehnal
5514b24fdf Merge pull request #167 from molstar/multi-canvas3d
Multi-canvas support for PluginContext
2021-04-21 20:05:05 +02:00
dsehnal
d570bc352e "relative" canvas3d viewport and picking dimensions fix 2021-04-20 20:26:13 +02:00
dsehnal
8a76a3fa64 2.0.4 2021-04-20 11:23:23 +02:00
dsehnal
71bf4e21f5 changelog 2021-04-20 11:21:29 +02:00
dsehnal
e0d36c30d3 Fix measurement labels & interactions 2021-04-20 11:19:15 +02:00
David Sehnal
b53debcfef Merge pull request #163 from sukolsak/fix-zip-progress
Fix incorrect deflate progress
2021-04-14 15:02:15 +02:00
Sukolsak Sakshuwong
d0705ac226 fix deflate progress 2021-04-14 05:07:05 -07:00
Alexander Rose
e01eacb3fe changelog 2021-04-13 23:12:39 -07:00
Alexander Rose
d4102b476b Fix, read SDF multi-line values 2021-04-13 23:07:55 -07:00
dsehnal
83ce17174a changelog 2021-04-13 21:17:28 +02:00
dsehnal
18023d7f26 Merge branch 'master' of https://github.com/molstar/molstar 2021-04-13 21:16:28 +02:00
dsehnal
a8541d5967 Structure.eachAtomicHierarchyElement 2021-04-13 21:15:44 +02:00
David Sehnal
8b21818f2e Merge pull request #159 from sukolsak/obj-export
OBJ Export
2021-04-13 17:06:48 +02:00
Sukolsak Sakshuwong
0b290247dc show partial progress when exporting large meshes 2021-04-11 13:15:50 -07:00
Sukolsak Sakshuwong
fb5010e962 reorder arguments to addMeshWithColors() 2021-04-10 18:12:32 -07:00
Sukolsak Sakshuwong
178789d327 make RenderObjectExporter.add() async 2021-04-10 18:10:42 -07:00
Sukolsak Sakshuwong
4fae526073 wip 2021-04-09 15:59:14 -07:00
dsehnal
05f1d8085a 2.0.3 2021-04-09 17:47:12 +02:00
dsehnal
38bbabd742 fix test 2021-04-09 17:45:51 +02:00
dsehnal
3ab958a93c changelog 2021-04-09 17:43:23 +02:00
dsehnal
f59d589a30 CubeGridFormat 2021-04-09 17:41:13 +02:00
David Sehnal
11f7e54704 Merge pull request #158 from molstar/gradient-themes
Add ColorTheme.palette support
2021-04-09 10:01:59 +02:00
dsehnal
16ebd8266e changelog 2021-04-09 10:01:21 +02:00
dsehnal
7a796a4d3d Merge branch 'master' of https://github.com/molstar/molstar into gradient-themes 2021-04-09 09:59:46 +02:00
Alexander Rose
1cbb915962 started a changelog 2021-04-08 23:14:55 -07:00
Alexander Rose
80486d58c3 async deflate (and zip) 2021-04-08 23:13:30 -07:00
dsehnal
81bc116c4d TextureImage.filter 2021-04-08 09:49:35 +02:00
dsehnal
4249064dd1 Add ColorTheme.palette support
- add example to basic-wrapper that uses it
2021-04-07 15:55:54 +02:00
dsehnal
028c02f50d 2.0.2 2021-03-29 12:48:26 +02:00
dsehnal
76e97d7b59 fix VolumeFromDensityServerCif label 2021-03-29 12:46:10 +02:00
dsehnal
ad1181a75b interpolate ModelFromTrajectory transform 2021-03-29 12:03:13 +02:00
Alexander Rose
5d683462fb add common non-standard amino-acids 2021-03-28 15:38:15 -07:00
Alexander Rose
42422bb0ea add canvas3d.getRenderObjects 2021-03-28 15:36:40 -07:00
dsehnal
861e5c3e97 SDF data item test 2021-03-24 16:02:53 +01:00
dsehnal
614cffda96 2.0.1 2021-03-23 15:44:24 +01:00
dsehnal
2e0379d202 npm ignore 2021-03-23 15:42:52 +01:00
dsehnal
b5cfdcd2a3 2.0.0 2021-03-23 15:31:18 +01:00
dsehnal
c00de6fde0 Merge branch 'master' of https://github.com/molstar/molstar 2021-03-23 11:16:26 +01:00
Alexander Rose
da3a8e56f3 handle negative isovalues in gpu mc 2021-03-22 22:06:49 -07:00
dsehnal
103d6fe775 alpha orbitals tryUseGpu param 2021-03-22 20:18:48 +01:00
dsehnal
5df55e6bf7 SDF delimiter bugfix + multi-molecule SDF support in mol-plugin 2021-03-22 17:21:47 +01:00
dsehnal
3b285086d4 rename files called "macro" due to Jest not being able to process them 2021-03-22 16:53:48 +01:00
dsehnal
91793bc3cc 2.0.0-dev.13 2021-03-22 13:29:42 +01:00
dsehnal
fa3828e820 add model-server-query transform support 2021-03-22 12:51:03 +01:00
Alexander Rose
31ba8212da 2.0.0-dev.12 2021-03-21 16:47:53 -07:00
Alexander Rose
fe27d8e134 Merge pull request #150 from molstar/stubs2
basic support for bond stubs
2021-03-21 16:40:03 -07:00
Alexander Rose
83dcdfdc4b Merge commit '2faa821c50a6dfce700eb8072a61d01d937c18e5' into stubs2 2021-03-21 16:36:33 -07:00
Alexander Rose
f9aaabc1f7 fix interactions bounding sphere 2021-03-21 16:29:56 -07:00
Alexander Rose
034370b44c add includeParent support to interactions 2021-03-21 16:25:03 -07:00
Alexander Rose
b87666df3e don't pad empty bounding spheres 2021-03-21 16:24:25 -07:00
Alexander Rose
c98c3228fe fix structure.asParent 2021-03-21 16:23:57 -07:00
Alexander Rose
9419980dfc make structure state private (like before) 2021-03-21 12:39:21 -07:00
Alexander Rose
42d60420e5 added Structure.asParent
- refactored structure state handling
- removed Structure.WithChild
2021-03-21 12:10:24 -07:00
dsehnal
5b1df333a7 tsconfig jsx param 2021-03-21 16:18:01 +01:00
Alexander Rose
0bb376706d fix ellipsoid repr and support includeParent
- switch off adjustCylinderLength
- handle structure with child
2021-03-20 23:58:48 -07:00
Alexander Rose
eca7da2c72 add adjustCylinderLength param
- so it can be switched off
2021-03-20 23:54:50 -07:00
Alexander Rose
b0bdb3ddb6 tweak param help 2021-03-20 23:52:23 -07:00
Alexander Rose
3180d7c305 basic support for bond stubs
- line and ball & stick repr
- stubs support in link visual helper
- getData and mustRecreate methods for structure repr provider
- Structure.WithChild helper (needs Proxy support)
2021-03-20 18:05:58 -07:00
dsehnal
2faa821c50 2.0.0-dev.11 2021-03-19 17:29:29 +01:00
David Sehnal
7f355ae501 Merge pull request #141 from molstar/surrounding-ligands
Surrounding Ligands query
2021-03-19 17:16:25 +01:00
dsehnal
7f79ff9ff2 StructureSourceControls: show hierarchy preset is >1 trajectory is selected 2021-03-18 15:29:48 +01:00
dsehnal
02de871c59 StructureBoundingBox3D transform 2021-03-18 15:18:15 +01:00
dsehnal
00cb783d4c BoxShape3D transform 2021-03-18 14:15:04 +01:00
David Sehnal
c925919ee5 Merge pull request #148 from TomasKulhanek/master
FIX issue #147 CSS transform:scale cause molstar canvas to have incorrect size
2021-03-17 10:50:44 +01:00
dsehnal
324820890a Fix createModelProperty.isApplicable 2021-03-17 10:35:29 +01:00
Tomas Kulhanek
2687b29d4d FIX molstar/molstar#147 offsetWidth/offsetHeight is correct size of element when css transform:scale is used 2021-03-17 07:46:41 +00:00
dsehnal
7084aaee1a adjust text 2021-03-16 23:02:14 +01:00
dsehnal
520a2f7850 model-server: empty result console output 2021-03-16 22:47:34 +01:00
Alexander Rose
9264987817 camera helper tweaks
- add highlighting
- improved axes alignment
2021-03-15 23:16:19 -07:00
dsehnal
b736ed3ea4 readme tweaks 2021-03-15 21:35:17 +01:00
dsehnal
166d660fa7 2.0.0-dev.10 2021-03-15 20:20:21 +01:00
dsehnal
b8249cde4d interactive camera axis helper 2021-03-15 20:16:07 +01:00
dsehnal
f12f5eca90 Merge branch 'master' into surrounding-ligands 2021-03-15 16:45:53 +01:00
dsehnal
cd3798b46f disable SwaggerUI response syntax highlight 2021-03-15 16:44:36 +01:00
dsehnal
0240e54737 TrackballControlsParams.autoAdjustControls 2021-03-15 14:13:11 +01:00
dsehnal
6a735d902e fix XYZ parser bug 2021-03-15 13:31:44 +01:00
dsehnal
57a942ecb5 requestCameraReset SnapshotProvide
- allow to customize the snapshop based on the current scane/boundingbox/camera state
2021-03-15 12:47:45 +01:00
dsehnal
f67605a398 applyMarkerAction fix 2 2021-03-14 18:46:04 +01:00
dsehnal
aaafa1d5ad model-server: surroundingLigands query 2021-03-14 15:09:37 +01:00
dsehnal
a1d9a77653 surroundingLigands query 2021-03-14 15:00:13 +01:00
dsehnal
f2f1181af3 Merge branch 'master' into surrounding-ligands 2021-03-14 13:16:22 +01:00
dsehnal
864befc48a applyMarkerAction fix 2021-03-14 13:09:53 +01:00
dsehnal
73f6793bd8 surroundingLigands query wip 2021-03-14 12:22:01 +01:00
dsehnal
87ee9d88f2 ResidueSet helper (wip) 2021-03-14 11:21:12 +01:00
dsehnal
b1e245e913 add UndirectedGraph 2021-03-14 10:19:28 +01:00
Alexander Rose
78c0471f39 remove unused Structure.unitsSortedByVolume 2021-03-13 22:35:24 -08:00
Alexander Rose
c57b9b9214 improve preset for many polymer gaps
- show all atom instead
- for medium sized structures
- fixes #57
2021-03-13 22:27:51 -08:00
Alexander Rose
34f33c5bbb fix apply marker type error 2021-03-13 22:24:48 -08:00
Alexander Rose
57da2a7ebb optimized applyMarkerAction
- extract switch statement out of loop
- use int32 view to handle 4 byte together
- don't check for change (essentially done at a higher level anyway)
2021-03-13 12:22:53 -08:00
Alexander Rose
d45d5c0e55 add assertUnreachable helper
- to type check if, e.g. if/switch statements are exhaustive
- TODO use...
2021-03-13 12:20:00 -08:00
dsehnal
42ed425e65 fix secondary_structure_type 2021-03-13 19:58:50 +01:00
dsehnal
f752ee5094 plugin-state server: remove /clear and can't remotely remove sticky entries 2021-03-13 17:56:18 +01:00
dsehnal
044c796942 Fix getSymmetryOperatorRef indexing 2021-03-13 17:53:42 +01:00
dsehnal
0aabbcfaab add back CreateVolumeStreamingBehavior custom controls 2021-03-13 16:40:48 +01:00
Alexander Rose
24274cc53b 2.0.0-dev.9 2021-03-09 22:50:34 -08:00
Alexander Rose
870cef2fd4 add collapse-left-panel to viewer query params 2021-03-09 22:46:53 -08:00
Alexander Rose
bf7b1f5bfd move StateActions to PluginSpec 2021-03-09 22:46:13 -08:00
Alexander Rose
9c9a0312db gpu mc attribution 2021-03-09 22:42:52 -08:00
dsehnal
724fa2a7cd package lock 2021-03-08 18:32:27 +01:00
Alexander Rose
19b36e5942 2.0.0-dev.8 2021-03-07 14:13:51 -08:00
Alexander Rose
b0dd9ab026 Merge pull request #135 from molstar/split-plugin-context
Move part of PluginContext to mol-plugin-ui
2021-03-07 13:41:17 -08:00
Alexander Rose
b77f1d4dee move initDataActions to PluginContext 2021-03-07 13:39:30 -08:00
dsehnal
3770fd7706 move actions back to PluginSpec 2021-03-07 13:36:50 +01:00
dsehnal
e3175c3ed1 Merge branch 'master' into split-plugin-context 2021-03-07 13:24:17 +01:00
dsehnal
7c5dd5b15b fix build caused by some typing edge case 2021-03-07 11:41:56 +01:00
Alexander Rose
0872e11669 2.0.0-dev.7 2021-03-07 01:29:34 -08:00
Alexander Rose
a66da4defc fix missing vars 2021-03-07 01:27:16 -08:00
Alexander Rose
d4ba13a2f2 2.0.0-dev.6 2021-03-07 01:22:30 -08:00
Alexander Rose
3b25e037aa remove obsolete viewer query params 2021-03-07 01:18:37 -08:00
Alexander Rose
189fad3d84 better handle focus on structure update
- fixes #123
2021-03-07 00:30:35 -08:00
Alexander Rose
c3c22ee3bc fix typos in xyz format 2021-03-06 23:39:12 -08:00
Alexander Rose
8a3222005c fix calculated label_seq_id 2021-03-06 23:38:21 -08:00
Alexander Rose
a17da36410 coarse grained tweaks
- coarse grained if less than three times as many atoms as polymer residues
- don't try dssp if coarse grained
2021-03-06 23:37:48 -08:00
Alexander Rose
80323d8122 2.0.0-dev.5 2021-03-06 13:59:17 -08:00
Alexander Rose
cbd6aa0b6b use 32bit depth texture in webgl2 2021-03-06 13:27:04 -08:00
Alexander Rose
3831bd9941 improve handling of coarse grained models 2021-03-06 11:17:43 -08:00
dsehnal
3d3e2c3a86 packages 2021-03-05 00:48:49 +01:00
dsehnal
acf13fa46f split plugin context (wip) 2021-03-05 00:33:00 +01:00
dsehnal
bc5d796653 make pairingThreshold slightly larger 2021-03-04 19:14:35 +01:00
dsehnal
82dd0496c2 covalentlyBondedComponent query 2021-03-04 18:53:05 +01:00
dsehnal
056742ac74 model index animation loop direction 2021-03-04 18:37:47 +01:00
dsehnal
29d4cfbcca add xyz support 2021-03-04 18:10:39 +01:00
Alexander Rose
376449f7c8 add missing PRO to standard components 2021-03-03 17:51:17 -08:00
186 changed files with 7399 additions and 4227 deletions

View File

@@ -1 +1 @@
tsconfig.commonjs.buildinfo
tsconfig.commonjs.tsbuildinfo

58
CHANGELOG.md Normal file
View File

@@ -0,0 +1,58 @@
# Change Log
All notable changes to this project will be documented in this file, following the suggestions of [Keep a CHANGELOG](http://keepachangelog.com/). This project adheres to [Semantic Versioning](http://semver.org/) for its most widely used - and defacto - public interfaces.
Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here.
## [Unreleased]
- [empty]
## [v2.0.5] - 2021-04-26
- Ability to pass ``Canvas3DContext`` to ``PluginContext.fromCanvas``.
- Relative frame support for ``Canvas3D`` viewport.
- Fix bug in screenshot copy UI.
- Add ability to select residues from a list of identifiers to the Selection UI.
- Fix SSAO bugs when used with ``Canvas3D`` viewport.
- Support for full pausing (no draw) rendering: ``Canvas3D.pause(true)``.
- Add `MeshBuilder.addMesh`.
- Add `Torus` primitive.
- Lazy volume loading support.
- [Breaking] ``Viewer.loadVolumeFromUrl`` signature change.
- ``loadVolumeFromUrl(url, format, isBinary, isovalues, entryId)`` => ``loadVolumeFromUrl({ url, format, isBinary }, isovalues, { entryId, isLazy })``
- Add ``TextureMesh`` support to ``geo-export`` extension.
## [v2.0.4] - 2021-04-20
- [WIP] Mesh export extension
- ``Structure.eachAtomicHierarchyElement`` (#161)
- Fixed reading multi-line values in SDF format
- Fixed Measurements UI labels (#166)
## [v2.0.3] - 2021-04-09
### Added
- Support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
### Changed
- [Breaking] The `zip` function is now asynchronous and expects a `RuntimeContext`. Also added `Zip()` returning a `Task`.
- [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
## [v2.0.2] - 2021-03-29
### Added
- `Canvas3D.getRenderObjects`.
- [WIP] Animate state interpolating, including model trajectories
### Changed
- Recognise MSE, SEP, TPO, PTR and PCA as non-standard amino-acids.
### Fixed
- VolumeFromDensityServerCif transform label
## [v2.0.1] - 2021-03-23
### Fixed
- Exclude tsconfig.commonjs.tsbuildinfo from npm bundle
## [v2.0.0] - 2021-03-23
Too many changes to list as this is the start of the changelog... Notably, default exports are now forbidden.

View File

@@ -5,15 +5,12 @@
# Mol*
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that will serve as a basis for the next-generation data delivery and analysis tools for macromolecular structure data. This is a collaboration between PDBe and RCSB PDB teams and the development will be open-source and available to anyone who wants to use it for developing visualization tools for macromolecular structure data available from [PDB](https://www.wwpdb.org/) and other institutions.
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that serves as a basis for the next-generation data delivery and analysis tools for (not only) macromolecular structure data. Mol* development was jointly initiated by PDBe and RCSB PDB to combine and build on the strengths of [LiteMol](https://litemol.org) (developed by PDBe) and [NGL](https://nglviewer.org) (developed by RCSB PDB) viewers.
This particular project is the implementation of this technology (still under development).
*If you are looking for the "MOLeculAR structure annoTator", that package is now available on NPM as [MolArt](https://www.npmjs.com/package/molart).*
## Project Structure Overview
## Project Overview
The core of Mol* currently consists of these modules (see under `src/`):
The core of Mol* consists of these modules (see under `src/`):
- `mol-task` Computation abstraction with progress tracking and cancellation support.
- `mol-data` Collections (integer-based sets, interface to columns/tables, etc.)
@@ -29,7 +26,6 @@ The core of Mol* currently consists of these modules (see under `src/`):
- `mol-gl` A wrapper around WebGL.
- `mol-canvas3d` A low-level 3d view component. Uses `mol-geo` to generate geometries.
- `mol-state` State representation tree with state saving and automatic updates.
- `mol-app` Components for building UIs.
- `mol-plugin` Allow to define modular Mol* plugin instances utilizing `mol-state` and `mol-canvas3d`.
- `mol-plugin-state` State transformations, builders, and managers.
- `mol-plugin-ui` React-based user interface for the Mol* plugin. Some components of the UI are usable outside the main plugin and can be integrated into 3rd party solutions.
@@ -41,7 +37,7 @@ Moreover, the project contains the implementation of `servers`, including
- `servers/volume` A tool for accessing volumetric experimental data related to molecular structures.
- `servers/plugin-state` A basic server to store Mol* Plugin states.
The project also contains performance tests (`perf-tests`), `examples`, and basic proof of concept `cli` apps (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
The project also contains performance tests (`perf-tests`), `examples`, and `cli` apps (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
## Previous Work
This project builds on experience from previous solutions:
@@ -169,9 +165,6 @@ To get syntax highlighting for shader and graphql files add the following to Vis
## Contributing
Just open an issue or make a pull request. All contributions are welcome.
## Roadmap
Continually develop this prototype project. As individual modules become stable, make them into standalone libraries.
## Funding
Funding sources include but are not limited to:
* [RCSB PDB](https://www.rcsb.org) funding by a grant [DBI-1338415; PI: SK Burley] from the NSF, the NIH, and the US DoE

View File

@@ -27,3 +27,4 @@
* DNA (5D3G)
* Multiple models with different sets of ligands or missing ligands (1J6T, 1VRC, 2ICY, 1O2F)
* Long linear sugar chain (4HG6)
* Anisotropic B-factors/Ellipsoids (1EJG)

6220
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "2.0.0-dev.4",
"version": "2.0.5",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -14,6 +14,7 @@
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"test": "npm run lint && jest",
"jest": "jest",
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
"build-viewer": "npm run build-tsc && npm run build-extra && npm run build-webpack-viewer",
"build-tsc": "concurrently \"tsc --incremental\" \"tsc --build tsconfig.commonjs.json --incremental\"",
@@ -115,7 +116,7 @@
"simple-git": "^2.25.0",
"style-loader": "^2.0.0",
"ts-jest": "^26.4.4",
"typescript": "^4.1.2",
"typescript": "^4.2.3",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-version-file-plugin": "^0.4.0"
@@ -137,14 +138,14 @@
"cors": "^2.8.5",
"express": "^4.17.1",
"h264-mp4-encoder": "^1.0.12",
"immer": "^8.0.0",
"immer": "^8.0.1",
"immutable": "^3.8.2",
"node-fetch": "^2.6.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rxjs": "^6.6.3",
"rxjs": "^6.6.6",
"swagger-ui-dist": "^3.37.2",
"tslib": "^2.0.3",
"tslib": "^2.1.0",
"util.promisify": "^1.0.1",
"xhr2": "^0.2.0"
}

View File

@@ -5,31 +5,32 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import '../../mol-util/polyfill';
import { createPlugin } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import './index.html';
import { PluginContext } from '../../mol-plugin/context';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginSpec } from '../../mol-plugin/spec';
import { PluginConfig } from '../../mol-plugin/config';
import { ObjectKeys } from '../../mol-util/type-helpers';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { Structure } from '../../mol-model/structure';
import { PluginStateTransform, PluginStateObject as PSO } from '../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Task } from '../../mol-task';
import { StateObject } from '../../mol-state';
import { ViewportComponent, StructurePreset, ShowButtons } from './viewport';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
import { createPlugin } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginBehaviors } from '../../mol-plugin/behavior';
import { ColorNames } from '../../mol-util/color/names';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginSpec } from '../../mol-plugin/spec';
import { StateObject } from '../../mol-state';
import { Task } from '../../mol-task';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import '../../mol-util/polyfill';
import { ObjectKeys } from '../../mol-util/type-helpers';
import './index.html';
import { ShowButtons, StructurePreset, ViewportComponent } from './viewport';
require('mol-plugin-ui/skin/light.scss');
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setProductionMode, setDebugMode } from '../../mol-util/debug';
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
export { Viewer as DockingViewer };
const DefaultViewerOptions = {
extensions: ObjectKeys({}),
@@ -53,7 +54,7 @@ const DefaultViewerOptions = {
};
class Viewer {
plugin: PluginContext
plugin: PluginUIContext
constructor(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
const o = { ...DefaultViewerOptions, ...{
@@ -70,10 +71,10 @@ class Viewer {
viewportShowSelectionMode: false,
viewportShowAnimation: false,
} };
const defaultSpec = DefaultPluginSpec();
const defaultSpec = DefaultPluginUISpec();
const spec: PluginSpec = {
actions: [...defaultSpec.actions],
const spec: PluginUISpec = {
actions: defaultSpec.actions,
behaviors: [
PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci, { mark: false }),
PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
@@ -83,7 +84,7 @@ class Viewer {
PluginSpec.Behavior(PluginBehaviors.CustomProps.Interactions),
PluginSpec.Behavior(PluginBehaviors.CustomProps.SecondaryStructure),
],
animations: [...defaultSpec.animations || []],
animations: defaultSpec.animations,
customParamEditors: defaultSpec.customParamEditors,
layout: {
initial: {
@@ -91,15 +92,15 @@ class Viewer {
showControls: o.layoutShowControls,
controlsDisplay: o.layoutControlsDisplay,
},
controls: {
...defaultSpec.layout && defaultSpec.layout.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
}
},
components: {
...defaultSpec.components,
controls: {
...defaultSpec.components?.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
},
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
viewport: {
view: ViewportComponent
@@ -210,4 +211,3 @@ const MergeStructures = PluginStateTransform.BuiltIn({
});
(window as any).DockingViewer = Viewer;
export { Viewer as DockingViewer };

View File

@@ -4,24 +4,23 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react';
import { PluginUIComponent } from '../../mol-plugin-ui/base';
import { Viewport, ViewportControls } from '../../mol-plugin-ui/viewport';
import { BackgroundTaskProgress } from '../../mol-plugin-ui/task';
import { LociLabels } from '../../mol-plugin-ui/controls';
import { Toasts } from '../../mol-plugin-ui/toast';
import { Button } from '../../mol-plugin-ui/controls/common';
import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../mol-plugin-state/builder/structure/representation-preset';
import { StateObjectRef } from '../../mol-state';
import { StructureSelectionQueries, StructureSelectionQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { InteractionsRepresentationProvider } from '../../mol-model-props/computed/representations/interactions';
import { InteractionTypeColorThemeProvider } from '../../mol-model-props/computed/themes/interaction-type';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
import { presetStaticComponent, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
import { StructureSelectionQueries, StructureSelectionQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
import { StructureRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { Color } from '../../mol-util/color';
import { PluginUIComponent } from '../../mol-plugin-ui/base';
import { LociLabels } from '../../mol-plugin-ui/controls';
import { Button } from '../../mol-plugin-ui/controls/common';
import { BackgroundTaskProgress } from '../../mol-plugin-ui/task';
import { Toasts } from '../../mol-plugin-ui/toast';
import { Viewport, ViewportControls } from '../../mol-plugin-ui/viewport';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginContext } from '../../mol-plugin/context';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { StateObjectRef } from '../../mol-state';
import { Color } from '../../mol-util/color';
function shinyStyle(plugin: PluginContext) {
return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {

View File

@@ -21,7 +21,7 @@
<script type="text/javascript" src="./molstar.js"></script>
<script type="text/javascript">
var viewer = new molstar.Viewer('app', {
layoutIsExpanded: false,
layoutIsExpanded: true,
layoutShowControls: false,
layoutShowRemoteState: false,
layoutShowSequence: true,
@@ -37,6 +37,20 @@
});
viewer.loadPdb('7bv2');
viewer.loadEmdb('EMD-30210', { detail: 6 });
// viewer.loadVolumeFromUrl({
// url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
// format: 'dscif',
// isBinary: true
// }, [{
// type: 'relative',
// value: 1,
// color: 0x3377aa
// }], {
// entryId: 'EMD-30210',
// isLazy: true
// });
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
</script>
</body>

View File

@@ -48,18 +48,14 @@
var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
if (debugMode) molstar.setDebugMode(debugMode, debugMode);
var disableAntialiasing = getParam('disable-antialiasing', '[^&]+').trim() === '1';
var pixelScale = parseFloat(getParam('pixel-scale', '[^&]+').trim() || '1');
var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
var hideControls = getParam('hide-controls', '[^&]+').trim() === '1';
var collapseLeftPanel = getParam('collapse-left-panel', '[^&]+').trim() === '1';
var pdbProvider = getParam('pdb-provider', '[^&]+').trim().toLowerCase();
var emdbProvider = getParam('emdb-provider', '[^&]+').trim().toLowerCase();
var viewer = new molstar.Viewer('app', {
disableAntialiasing: disableAntialiasing,
pixelScale: pixelScale,
enableWboit: !disableWboit,
layoutShowControls: !hideControls,
viewportShowExpand: false,
collapseLeftPanel: collapseLeftPanel,
pdbProvider: pdbProvider || 'pdbe',
emdbProvider: emdbProvider || 'pdbe',
});

View File

@@ -5,43 +5,44 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import '../../mol-util/polyfill';
import { createPlugin } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import './index.html';
import './embedded.html';
import './favicon.ico';
import { PluginContext } from '../../mol-plugin/context';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginSpec } from '../../mol-plugin/spec';
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
import { PluginConfig } from '../../mol-plugin/config';
import { CellPack } from '../../extensions/cellpack';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { Asset } from '../../mol-util/assets';
import { ObjectKeys } from '../../mol-util/type-helpers';
import { PluginState } from '../../mol-plugin/state';
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
import { CellPack } from '../../extensions/cellpack';
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { Mp4Export } from '../../extensions/mp4-export';
import { GeometryExport } from '../../extensions/geo-export';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
import { StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
import { Color } from '../../mol-util/color';
import { StateObjectSelector } from '../../mol-state';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
import { Mp4Export } from '../../extensions/mp4-export';
import { StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
import { createPlugin } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginSpec } from '../../mol-plugin/spec';
import { PluginState } from '../../mol-plugin/state';
import { StateObjectSelector } from '../../mol-state';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import '../../mol-util/polyfill';
import { ObjectKeys } from '../../mol-util/type-helpers';
import './embedded.html';
import './favicon.ico';
import './index.html';
require('mol-plugin-ui/skin/light.scss');
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setProductionMode, setDebugMode } from '../../mol-util/debug';
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
const CustomFormats = [
['g3d', G3dProvider] as const
@@ -55,7 +56,8 @@ const Extensions = {
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
'g3d': PluginSpec.Behavior(G3DFormat),
'mp4-export': PluginSpec.Behavior(Mp4Export)
'mp4-export': PluginSpec.Behavior(Mp4Export),
'geo-export': PluginSpec.Behavior(GeometryExport)
};
const DefaultViewerOptions = {
@@ -68,6 +70,7 @@ const DefaultViewerOptions = {
layoutShowSequence: true,
layoutShowLog: true,
layoutShowLeftPanel: true,
collapseLeftPanel: false,
disableAntialiasing: false,
pixelScale: 1,
enableWboit: true,
@@ -86,14 +89,14 @@ const DefaultViewerOptions = {
type ViewerOptions = typeof DefaultViewerOptions;
export class Viewer {
plugin: PluginContext
plugin: PluginUIContext
constructor(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
const o = { ...DefaultViewerOptions, ...options };
const defaultSpec = DefaultPluginSpec();
const defaultSpec = DefaultPluginUISpec();
const spec: PluginSpec = {
actions: [...defaultSpec.actions],
const spec: PluginUISpec = {
actions: defaultSpec.actions,
behaviors: [
...defaultSpec.behaviors,
...o.extensions.map(e => Extensions[e]),
@@ -106,16 +109,22 @@ export class Viewer {
isExpanded: o.layoutIsExpanded,
showControls: o.layoutShowControls,
controlsDisplay: o.layoutControlsDisplay,
regionState: {
bottom: 'full',
left: o.collapseLeftPanel ? 'collapsed' : 'full',
right: 'full',
top: 'full',
}
},
controls: {
...defaultSpec.layout && defaultSpec.layout.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
}
},
components: {
...defaultSpec.components,
controls: {
...defaultSpec.components?.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
},
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
},
config: [
@@ -234,17 +243,29 @@ export class Viewer {
}));
}
async loadVolumeFromUrl(url: string, format: BuildInVolumeFormat, isBinary: boolean, isovalues: VolumeIsovalueInfo[], entryId?: string) {
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string, isLazy?: boolean }) {
const plugin = this.plugin;
if (!plugin.dataFormats.get(format)) {
throw new Error(`Unknown density format: ${format}`);
}
return plugin.dataTransaction(async () => {
const data = await plugin.builders.data.download({ url, isBinary, label: entryId }, { state: { isGhost: true } });
if (options?.isLazy) {
const update = this.plugin.build();
update.toRoot().apply(StateTransforms.Data.LazyVolume, {
url,
format,
entryId: options?.entryId,
isBinary,
isovalues: isovalues.map(v => ({ alpha: 1, ...v }))
});
return update.commit();
}
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId });
return plugin.dataTransaction(async () => {
const data = await plugin.builders.data.download({ url, isBinary, label: options?.entryId }, { state: { isGhost: true } });
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId });
const volume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
if (!volume?.isOk) throw new Error('Failed to parse any volume.');
@@ -252,7 +273,7 @@ export class Viewer {
for (const iso of isovalues) {
repr.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, volume.data!, {
type: 'isosurface',
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
color: 'uniform',
colorParams: { value: iso.color }
}));

View File

@@ -18,7 +18,6 @@ _.StateTransforms.Data.Download.id;
// Empty plugin context
const ctx = new PluginContext({
actions: [],
behaviors: []
});

View File

@@ -4,7 +4,6 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AlphaOrbitalsExample } from '.';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';

View File

@@ -4,25 +4,25 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { BehaviorSubject } from 'rxjs';
import { debounceTime, skip } from 'rxjs/operators';
import { AlphaOrbital, Basis } from '../../extensions/alpha-orbitals/data-model';
import { SphericalBasisOrder } from '../../extensions/alpha-orbitals/spherical-functions';
import { BasisAndOrbitals, CreateOrbitalDensityVolume, CreateOrbitalRepresentation3D, CreateOrbitalVolume, StaticBasisAndOrbitals } from '../../extensions/alpha-orbitals/transforms';
import { createPluginAsync } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { createPluginAsync } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginContext } from '../../mol-plugin/context';
import { StateObjectSelector, StateTransformer } from '../../mol-state';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition } from '../../mol-util/param-definition';
import { mountControls } from './controls';
import { DemoMoleculeSDF, DemoOrbitals } from './example-data';
import { BehaviorSubject } from 'rxjs';
import { debounceTime, skip } from 'rxjs/operators';
import './index.html';
import { Basis, AlphaOrbital } from '../../extensions/alpha-orbitals/data-model';
import { PluginCommands } from '../../mol-plugin/commands';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
require('mol-plugin-ui/skin/light.scss');
interface DemoInput {
@@ -50,10 +50,10 @@ type Selectors = {
}
export class AlphaOrbitalsExample {
plugin: PluginContext;
plugin: PluginUIContext;
async init(target: string | HTMLElement) {
const defaultSpec = DefaultPluginSpec();
const defaultSpec = DefaultPluginUISpec();
this.plugin = await createPluginAsync(typeof target === 'string' ? document.getElementById(target)! : target, {
...defaultSpec,
layout: {
@@ -61,15 +61,13 @@ export class AlphaOrbitalsExample {
isExpanded: false,
showControls: false
},
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' },
},
components: {
viewport: {
canvas3d: {
camera: {
helper: { axes: { name: 'off', params: { } } }
}
}
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' },
},
canvas3d: {
camera: {
helper: { axes: { name: 'off', params: { } } }
}
},
config: [
@@ -176,7 +174,8 @@ export class AlphaOrbitalsExample {
kind,
relativeIsovalue: this.state.value.isoValue,
pickable: false,
xrayShaded: true
xrayShaded: true,
tryUseGpu: false
};
}

View File

@@ -5,7 +5,6 @@
*/
import { PluginUIComponent } from '../../mol-plugin-ui/base';
import * as React from 'react';
export class CustomToastMessage extends PluginUIComponent {
render() {

View File

@@ -0,0 +1,51 @@
import { isPositionLocation } from '../../mol-geo/util/location-iterator';
import { Vec3 } from '../../mol-math/linear-algebra';
import { ColorTheme } from '../../mol-theme/color';
import { ThemeDataContext } from '../../mol-theme/theme';
import { Color } from '../../mol-util/color';
import { ColorNames } from '../../mol-util/color/names';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
export function CustomColorTheme(
ctx: ThemeDataContext,
props: PD.Values<{}>
): ColorTheme<{}> {
const { radius, center } = ctx.structure?.boundary.sphere!;
const radiusSq = Math.max(radius * radius, 0.001);
const scale = ColorTheme.PaletteScale;
return {
factory: CustomColorTheme,
granularity: 'vertex',
color: location => {
if (!isPositionLocation(location)) return ColorNames.black;
const dist = Vec3.squaredDistance(location.position, center);
const t = Math.min(dist / radiusSq, 1);
return ((t * scale) | 0) as Color;
},
palette: {
filter: 'nearest',
colors: [
ColorNames.red,
ColorNames.pink,
ColorNames.violet,
ColorNames.orange,
ColorNames.yellow,
ColorNames.green,
ColorNames.blue
]
},
props: props,
description: '',
};
}
export const CustomColorThemeProvider: ColorTheme.Provider<{}, 'basic-wrapper-custom-color-theme'> = {
name: 'basic-wrapper-custom-color-theme',
label: 'Custom Color Theme',
category: ColorTheme.Category.Misc,
factory: CustomColorTheme,
getParams: () => ({}),
defaultValues: { },
isApplicable: (ctx: ThemeDataContext) => true,
};

View File

@@ -97,6 +97,7 @@
addHeader('Misc');
addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes());
addControl('Apply Custom Theme', () => BasicMolStarWrapper.coloring.applyCustomTheme());
addControl('Default Coloring', () => BasicMolStarWrapper.coloring.applyDefault());
addHeader('Interactivity');

View File

@@ -4,39 +4,37 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { EmptyLoci } from '../../mol-model/loci';
import { StructureSelection } from '../../mol-model/structure';
import { createPlugin } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { createPlugin } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
import { Script } from '../../mol-script/script';
import { Asset } from '../../mol-util/assets';
import { Color } from '../../mol-util/color';
import { StripedResidues } from './coloring';
import { CustomToastMessage } from './controls';
import { CustomColorThemeProvider } from './custom-theme';
import './index.html';
import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData } from './superposition';
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
import { Asset } from '../../mol-util/assets';
require('mol-plugin-ui/skin/light.scss');
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }
class BasicWrapper {
plugin: PluginContext;
plugin: PluginUIContext;
init(target: string | HTMLElement) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec(),
...DefaultPluginUISpec(),
layout: {
initial: {
isExpanded: false,
showControls: false
},
controls: {
// left: 'none'
}
},
components: {
@@ -45,6 +43,7 @@ class BasicWrapper {
});
this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!);
this.plugin.representation.structure.themes.colorThemeRegistry.add(CustomColorThemeProvider);
this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
}
@@ -93,7 +92,7 @@ class BasicWrapper {
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'forward' } } }); },
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'backward' } } }); },
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'palindrome', params: {} } }); },
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: {} } }); },
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: { direction: 'forward' } } }); },
stop: () => this.plugin.managers.animation.stop()
}
}
@@ -106,6 +105,13 @@ class BasicWrapper {
}
});
},
applyCustomTheme: async () => {
this.plugin.dataTransaction(async () => {
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {
await this.plugin.managers.structure.component.updateRepresentationsTheme(s.components, { color: CustomColorThemeProvider.name as any });
}
});
},
applyDefault: async () => {
this.plugin.dataTransaction(async () => {
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {

View File

@@ -5,13 +5,13 @@
*/
import { Canvas3DProps } from '../../mol-canvas3d/canvas3d';
import { createPlugin } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { createPlugin } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
import './index.html';
import { Asset } from '../../mol-util/assets';
import './index.html';
require('mol-plugin-ui/skin/light.scss');
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }
@@ -62,7 +62,7 @@ const Canvas3DPresets = {
type Canvas3DPreset = keyof typeof Canvas3DPresets
class LightingDemo {
plugin: PluginContext;
plugin: PluginUIContext;
private radius = 5;
private bias = 1.1;
@@ -70,12 +70,14 @@ class LightingDemo {
init(target: string | HTMLElement) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec(),
...DefaultPluginUISpec(),
layout: {
initial: {
isExpanded: false,
showControls: false
},
},
components: {
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
}
});

View File

@@ -6,15 +6,15 @@
import * as ReactDOM from 'react-dom';
import { Canvas3DProps, DefaultCanvas3DParams } from '../../mol-canvas3d/canvas3d';
import { createPlugin } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index';
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
import { PluginStateObject, PluginStateObject as PSO } from '../../mol-plugin-state/objects';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { createPlugin } from '../../mol-plugin-ui';
import { PluginUIContext } from '../../mol-plugin-ui/context';
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
import { CreateVolumeStreamingInfo, InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
import { PluginState } from '../../mol-plugin/state';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
@@ -41,13 +41,13 @@ class MolStarProteopediaWrapper {
modelInfo: this._ev<ModelInfo>()
};
plugin: PluginContext;
plugin: PluginUIContext;
init(target: string | HTMLElement, options?: {
customColorList?: number[]
}) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec(),
...DefaultPluginUISpec(),
animations: [
AnimateModelIndex
],
@@ -282,7 +282,7 @@ class MolStarProteopediaWrapper {
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'forward' } } }); },
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'backward' } } }); },
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'palindrome', params: {} } }); },
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: {} } }); },
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: { direction: 'forward' } } }); },
stop: () => this.plugin.managers.animation.stop()
}
}

View File

@@ -4,14 +4,13 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { PluginUIContext } from '../../../mol-plugin-ui/context';
import { PluginContextContainer } from '../../../mol-plugin-ui/plugin';
import { TransformUpdaterControl } from '../../../mol-plugin-ui/state/update-transform';
import { PluginContext } from '../../../mol-plugin/context';
import { StateElements } from '../helpers';
export function volumeStreamingControls(plugin: PluginContext, parent: Element) {
export function volumeStreamingControls(plugin: PluginUIContext, parent: Element) {
ReactDOM.render(<PluginContextContainer plugin={plugin}>
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
</PluginContextContainer>, parent);

View File

@@ -9,6 +9,7 @@ import { Grid } from '../../mol-model/volume';
import { SphericalBasisOrder } from './spherical-functions';
import { Box3D, RegularGrid3d } from '../../mol-math/geometry';
import { arrayMin, arrayMax, arrayRms, arrayMean } from '../../mol-util/array';
import { ModelFormat } from '../../mol-model-formats/format';
// Note: generally contracted gaussians are currently not supported.
export interface SphericalElectronShell {
@@ -59,6 +60,17 @@ export interface CubeGrid {
isovalues?: { negative?: number; positive?: number };
}
export type CubeGridFormat = ModelFormat<CubeGrid>;
// eslint-disable-next-line
export function CubeGridFormat(grid: CubeGrid): CubeGridFormat {
return { name: 'custom grid', kind: 'cube-grid', data: grid };
}
export function isCubeGridData(f: ModelFormat): f is CubeGridFormat {
return f.kind === 'cube-grid';
}
export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
const geometry = params.basis.atoms.map(a => a.center);
const { gridSpacing: spacing, boxExpand: expand } = params;

View File

@@ -17,7 +17,7 @@ import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers
import { StateTransformer } from '../../mol-state';
import { Theme } from '../../mol-theme/theme';
import { VolumeRepresentation3DHelpers } from '../../mol-plugin-state/transforms/representation';
import { AlphaOrbital, Basis, CubeGrid } from './data-model';
import { AlphaOrbital, Basis, CubeGrid, CubeGridFormat, isCubeGridData } from './data-model';
import { createSphericalCollocationDensityGrid } from './density';
import { Tensor } from '../../mol-math/linear-algebra';
@@ -114,7 +114,7 @@ export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
}, a.data.orbitals[params.index], plugin.canvas3d?.webgl).runInContext(ctx);
const volume: Volume = {
grid: data.grid,
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
sourceData: CubeGridFormat(data),
customProperties: new CustomProperties(),
_propertyData: Object.create(null),
};
@@ -146,7 +146,7 @@ export const CreateOrbitalDensityVolume = PluginStateTransform.BuiltIn({
}, a.data.orbitals, plugin.canvas3d?.webgl).runInContext(ctx);
const volume: Volume = {
grid: data.grid,
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
sourceData: CubeGridFormat(data),
customProperties: new CustomProperties(),
_propertyData: Object.create(null),
};
@@ -172,7 +172,8 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
color: PD.Color(ColorNames.blue),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
xrayShaded: PD.Boolean(false),
pickable: PD.Boolean(true)
pickable: PD.Boolean(true),
tryUseGpu: PD.Boolean(true)
}
})({
canAutoUpdate() {
@@ -190,7 +191,7 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, params));
await repr.createOrUpdate(props, a.data).runInContext(ctx);
repr.setState({ pickable: srcParams.pickable });
return new PluginStateObject.Volume.Representation3D({ repr, source: a }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
return new PluginStateObject.Volume.Representation3D({ repr, sourceData: a.data }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
});
},
update({ a, b, newParams: srcParams }, plugin: PluginContext) {
@@ -200,6 +201,7 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
const props = { ...b.data.repr.props, ...newParams.type.params };
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, newParams));
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
b.data.sourceData = a.data;
b.data.repr.setState({ pickable: srcParams.pickable });
b.description = VolumeRepresentation3DHelpers.getDescription(props);
return StateTransformer.UpdateResult.Updated;
@@ -208,9 +210,9 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
});
function volumeParams(plugin: PluginContext, volume: PluginStateObject.Volume.Data, params: StateTransformer.Params<typeof CreateOrbitalRepresentation3D>) {
if (volume.data.sourceData.kind !== 'alpha-orbitals') throw new Error('Invalid data source kind.');
if (!isCubeGridData(volume.data.sourceData)) throw new Error('Invalid data source kind.');
const { isovalues } = volume.data.sourceData.data as CubeGrid;
const { isovalues } = volume.data.sourceData.data;
if (!isovalues) throw new Error('Isovalues are not computed.');
const value = isovalues[params.kind];
@@ -229,7 +231,7 @@ function volumeParams(plugin: PluginContext, volume: PluginStateObject.Volume.Da
colorParams: { value: params.color }
} : {
type: 'isosurface',
typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded },
typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded, tryUseGpu: params.tryUseGpu },
color: 'uniform',
colorParams: { value: params.color }
});

View File

@@ -121,7 +121,7 @@ const MembraneOrientation3D = PluginStateTransform.BuiltIn({
await MembraneOrientationProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
const repr = MembraneOrientationRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => MembraneOrientationParams);
await repr.createOrUpdate(params, a.data).runInContext(ctx);
return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: 'Membrane Orientation' });
return new PluginStateObject.Shape.Representation3D({ repr, sourceData: a.data }, { label: 'Membrane Orientation' });
});
},
update({ a, b, newParams }, plugin: PluginContext) {
@@ -129,6 +129,7 @@ const MembraneOrientation3D = PluginStateTransform.BuiltIn({
await MembraneOrientationProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
const props = { ...b.data.repr.props, ...newParams };
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
b.data.sourceData = a.data;
return StateTransformer.UpdateResult.Updated;
});
},

View File

@@ -414,7 +414,7 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
}
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
const structure = new Structure(units);
const structure = Structure.create(units);
for( let i = 0, il = structure.models.length; i < il; ++i) {
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { PluginComponent } from '../../mol-plugin-state/component';
import { PluginContext } from '../../mol-plugin/context';
import { Task } from '../../mol-task';
import { ObjExporter } from './export';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { StateSelection } from '../../mol-state';
import { SetUtils } from '../../mol-util/set';
import { zip } from '../../mol-util/zip/zip';
export class GeometryControls extends PluginComponent {
getFilename() {
const models = this.plugin.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Model)).map(s => s.obj!.data);
const uniqueIds = new Set<string>();
models.forEach(m => uniqueIds.add(m.entryId.toUpperCase()));
const idString = SetUtils.toArray(uniqueIds).join('-');
return `${idString || 'molstar-model'}`;
}
exportObj() {
const task = Task.create('Export OBJ', async ctx => {
try {
const renderObjects = this.plugin.canvas3d?.getRenderObjects()!;
const filename = this.getFilename();
const objExporter = new ObjExporter(filename);
for (let i = 0, il = renderObjects.length; i < il; ++i) {
await ctx.update({ message: `Exporting object ${i}/${il}` });
await objExporter.add(renderObjects[i], this.plugin.canvas3d?.webgl!, ctx);
}
const { obj, mtl } = objExporter.getData();
const asciiWrite = (data: Uint8Array, str: string) => {
for (let i = 0, il = str.length; i < il; ++i) {
data[i] = str.charCodeAt(i);
}
};
const objData = new Uint8Array(obj.length);
asciiWrite(objData, obj);
const mtlData = new Uint8Array(mtl.length);
asciiWrite(mtlData, mtl);
const zipDataObj = {
[filename + '.obj']: objData,
[filename + '.mtl']: mtlData
};
const zipData = await zip(ctx, zipDataObj);
return {
zipData,
filename: filename + '.zip'
};
} catch (e) {
this.plugin.log.error('' + e);
throw e;
}
});
return this.plugin.runTask(task, { useOverlay: true });
}
constructor(private plugin: PluginContext) {
super();
}
}

View File

@@ -0,0 +1,369 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { MeshValues } from '../../mol-gl/renderable/mesh';
import { LinesValues } from '../../mol-gl/renderable/lines';
import { PointsValues } from '../../mol-gl/renderable/points';
import { SpheresValues } from '../../mol-gl/renderable/spheres';
import { CylindersValues } from '../../mol-gl/renderable/cylinders';
import { TextureMeshValues } from '../../mol-gl/renderable/texture-mesh';
import { BaseValues, SizeValues } from '../../mol-gl/renderable/schema';
import { TextureImage } from '../../mol-gl/renderable/util';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
import { RuntimeContext } from '../../mol-task';
import { StringBuilder } from '../../mol-util';
import { Color } from '../../mol-util/color/color';
import { decodeFloatRGB } from '../../mol-util/float-packing';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3fromArray = Vec3.fromArray;
const v3transformMat4 = Vec3.transformMat4;
const v3transformMat3 = Vec3.transformMat3;
const mat3directionTransform = Mat3.directionTransform;
type RenderObjectExportData = {
[k: string]: string | Uint8Array | undefined
}
interface RenderObjectExporter<D extends RenderObjectExportData> {
add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext): Promise<void> | undefined
getData(): D
}
// http://paulbourke.net/dataformats/obj/
// http://paulbourke.net/dataformats/mtl/
export type ObjData = {
obj: string
mtl: string
}
export class ObjExporter implements RenderObjectExporter<ObjData> {
private obj = StringBuilder.create();
private mtl = StringBuilder.create();
private vertexOffset = 0;
private currentColor: Color | undefined;
private currentAlpha: number | undefined;
private materialSet = new Set<string>();
private static getSizeFromTexture(tSize: TextureImage<Uint8Array>, i: number): number {
const r = tSize.array[i * 3];
const g = tSize.array[i * 3 + 1];
const b = tSize.array[i * 3 + 2];
return decodeFloatRGB(r, g, b);
}
private static getSize(values: BaseValues & SizeValues, instanceIndex: number, group: number): number {
const tSize = values.tSize.ref.value;
let size = 0;
switch (values.dSizeType.ref.value) {
case 'uniform':
size = values.uSize.ref.value;
break;
case 'instance':
size = ObjExporter.getSizeFromTexture(tSize, instanceIndex) / 100;
break;
case 'group':
size = ObjExporter.getSizeFromTexture(tSize, group) / 100;
break;
case 'groupInstance':
const groupCount = values.uGroupCount.ref.value;
size = ObjExporter.getSizeFromTexture(tSize, instanceIndex * groupCount + group) / 100;
break;
}
return size * values.uSizeFactor.ref.value;
}
private static getGroup(groups: Float32Array | Uint8Array, i: number): number {
const i4 = i * 4;
const r = groups[i4];
const g = groups[i4 + 1];
const b = groups[i4 + 2];
if (groups instanceof Float32Array) {
return decodeFloatRGB(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
}
return decodeFloatRGB(r, g, b);
}
private updateMaterial(color: Color, alpha: number) {
if (this.currentColor === color && this.currentAlpha === alpha) return;
this.currentColor = color;
this.currentAlpha = alpha;
const material = Color.toHexString(color) + alpha;
StringBuilder.writeSafe(this.obj, `usemtl ${material}`);
StringBuilder.newline(this.obj);
if (!this.materialSet.has(material)) {
this.materialSet.add(material);
const [r, g, b] = Color.toRgbNormalized(color);
const mtl = this.mtl;
StringBuilder.writeSafe(mtl, `newmtl ${material}\n`);
StringBuilder.writeSafe(mtl, 'illum 2\n'); // illumination model
StringBuilder.writeSafe(mtl, 'Ns 163\n'); // specular exponent
StringBuilder.writeSafe(mtl, 'Ni 0.001\n'); // optical density a.k.a. index of refraction
StringBuilder.writeSafe(mtl, 'Ka 0 0 0\n'); // ambient reflectivity
StringBuilder.writeSafe(mtl, 'Kd '); // diffuse reflectivity
StringBuilder.writeFloat(mtl, r, 1000);
StringBuilder.whitespace1(mtl);
StringBuilder.writeFloat(mtl, g, 1000);
StringBuilder.whitespace1(mtl);
StringBuilder.writeFloat(mtl, b, 1000);
StringBuilder.newline(mtl);
StringBuilder.writeSafe(mtl, 'Ks 0.25 0.25 0.25\n'); // specular reflectivity
StringBuilder.writeSafe(mtl, 'd '); // dissolve
StringBuilder.writeFloat(mtl, alpha, 1000);
StringBuilder.newline(mtl);
}
}
private async addMeshWithColors(vertices: Float32Array, normals: Float32Array, indices: Uint32Array | undefined, groups: Float32Array | Uint8Array, vertexCount: number, drawCount: number, values: BaseValues, instanceIndex: number, geoTexture: boolean, ctx: RuntimeContext) {
const obj = this.obj;
const t = Mat4();
const n = Mat3();
const tmpV = Vec3();
const stride = geoTexture ? 4 : 3;
const colorType = values.dColorType.ref.value;
const tColor = values.tColor.ref.value.array;
const uAlpha = values.uAlpha.ref.value;
const aTransform = values.aTransform.ref.value;
Mat4.fromArray(t, aTransform, instanceIndex * 16);
mat3directionTransform(n, t);
const currentProgress = (vertexCount * 2 + drawCount) * instanceIndex;
await ctx.update({ isIndeterminate: false, current: currentProgress, max: (vertexCount * 2 + drawCount) * values.uInstanceCount.ref.value });
// position
for (let i = 0; i < vertexCount; ++i) {
if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + i });
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
StringBuilder.writeSafe(obj, 'v ');
StringBuilder.writeFloat(obj, tmpV[0], 1000);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[1], 1000);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[2], 1000);
StringBuilder.newline(obj);
}
// normal
for (let i = 0; i < vertexCount; ++i) {
if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount + i });
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
StringBuilder.writeSafe(obj, 'vn ');
StringBuilder.writeFloat(obj, tmpV[0], 100);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[1], 100);
StringBuilder.whitespace1(obj);
StringBuilder.writeFloat(obj, tmpV[2], 100);
StringBuilder.newline(obj);
}
// face
for (let i = 0; i < drawCount; i += 3) {
if (i % 3000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount * 2 + i });
let color: Color;
switch (colorType) {
case 'uniform':
color = Color.fromNormalizedArray(values.uColor.ref.value, 0);
break;
case 'instance':
color = Color.fromArray(tColor, instanceIndex * 3);
break;
case 'group': {
const group = geoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
color = Color.fromArray(tColor, group * 3);
break;
}
case 'groupInstance': {
const groupCount = values.uGroupCount.ref.value;
const group = geoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
break;
}
case 'vertex':
color = Color.fromArray(tColor, i * 3);
break;
case 'vertexInstance':
color = Color.fromArray(tColor, (instanceIndex * drawCount + i) * 3);
break;
default: throw new Error('Unsupported color type.');
}
this.updateMaterial(color, uAlpha);
const v1 = this.vertexOffset + (geoTexture ? i : indices![i]) + 1;
const v2 = this.vertexOffset + (geoTexture ? i + 1 : indices![i + 1]) + 1;
const v3 = this.vertexOffset + (geoTexture ? i + 2 : indices![i + 2]) + 1;
StringBuilder.writeSafe(obj, 'f ');
StringBuilder.writeInteger(obj, v1);
StringBuilder.writeSafe(obj, '//');
StringBuilder.writeIntegerAndSpace(obj, v1);
StringBuilder.writeInteger(obj, v2);
StringBuilder.writeSafe(obj, '//');
StringBuilder.writeIntegerAndSpace(obj, v2);
StringBuilder.writeInteger(obj, v3);
StringBuilder.writeSafe(obj, '//');
StringBuilder.writeInteger(obj, v3);
StringBuilder.newline(obj);
}
this.vertexOffset += vertexCount;
}
private async addMesh(values: MeshValues, ctx: RuntimeContext) {
const aPosition = values.aPosition.ref.value;
const aNormal = values.aNormal.ref.value;
const elements = values.elements.ref.value;
const aGroup = values.aGroup.ref.value;
const instanceCount = values.instanceCount.ref.value;
const vertexCount = values.uVertexCount.ref.value;
const drawCount = values.drawCount.ref.value;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
await this.addMeshWithColors(aPosition, aNormal, elements, aGroup, vertexCount, drawCount, values, instanceIndex, false, ctx);
}
}
private async addLines(values: LinesValues, ctx: RuntimeContext) {
// TODO
}
private async addPoints(values: PointsValues, ctx: RuntimeContext) {
// TODO
}
private async addSpheres(values: SpheresValues, ctx: RuntimeContext) {
const center = Vec3();
const aPosition = values.aPosition.ref.value;
const aGroup = values.aGroup.ref.value;
const instanceCount = values.instanceCount.ref.value;
const vertexCount = values.uVertexCount.ref.value;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
const state = MeshBuilder.createState(512, 256);
for (let i = 0; i < vertexCount; i += 4) {
v3fromArray(center, aPosition, i * 3);
const group = aGroup[i];
const radius = ObjExporter.getSize(values, instanceIndex, group);
state.currentGroup = group;
addSphere(state, center, radius, 2);
}
const mesh = MeshBuilder.getMesh(state);
const vertices = mesh.vertexBuffer.ref.value;
const normals = mesh.normalBuffer.ref.value;
const indices = mesh.indexBuffer.ref.value;
const groups = mesh.groupBuffer.ref.value;
await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, false, ctx);
}
}
private async addCylinders(values: CylindersValues, ctx: RuntimeContext) {
const start = Vec3();
const end = Vec3();
const aStart = values.aStart.ref.value;
const aEnd = values.aEnd.ref.value;
const aScale = values.aScale.ref.value;
const aCap = values.aCap.ref.value;
const aGroup = values.aGroup.ref.value;
const instanceCount = values.instanceCount.ref.value;
const vertexCount = values.uVertexCount.ref.value;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
const state = MeshBuilder.createState(512, 256);
for (let i = 0; i < vertexCount; i += 6) {
v3fromArray(start, aStart, i * 3);
v3fromArray(end, aEnd, i * 3);
const group = aGroup[i];
const radius = ObjExporter.getSize(values, instanceIndex, group) * aScale[i];
const cap = aCap[i];
const topCap = cap === 1 || cap === 3;
const bottomCap = cap >= 2;
const cylinderProps = { radiusTop: radius, radiusBottom: radius, topCap, bottomCap, radialSegments: 32 };
state.currentGroup = aGroup[i];
addCylinder(state, start, end, 1, cylinderProps);
}
const mesh = MeshBuilder.getMesh(state);
const vertices = mesh.vertexBuffer.ref.value;
const normals = mesh.normalBuffer.ref.value;
const indices = mesh.indexBuffer.ref.value;
const groups = mesh.groupBuffer.ref.value;
await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, false, ctx);
}
}
private async addTextureMesh(values: TextureMeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
const GeoExportName = 'geo-export';
if (!webgl.namedFramebuffers[GeoExportName]) {
webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
}
const framebuffer = webgl.namedFramebuffers[GeoExportName];
const [ width, height ] = values.uGeoTexDim.ref.value;
const vertices = new Float32Array(width * height * 4);
const normals = new Float32Array(width * height * 4);
const groups = webgl.isWebGL2 ? new Uint8Array(width * height * 4) : new Float32Array(width * height * 4);
framebuffer.bind();
values.tPosition.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, vertices);
values.tNormal.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, normals);
values.tGroup.ref.value.attachFramebuffer(framebuffer, 0);
webgl.readPixels(0, 0, width, height, groups);
const vertexCount = values.uVertexCount.ref.value;
const instanceCount = values.instanceCount.ref.value;
const drawCount = values.drawCount.ref.value;
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
await this.addMeshWithColors(vertices, normals, undefined, groups, vertexCount, drawCount, values, instanceIndex, true, ctx);
}
}
add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext) {
if (!renderObject.state.visible) return;
switch (renderObject.type) {
case 'mesh':
return this.addMesh(renderObject.values as MeshValues, ctx);
case 'lines':
return this.addLines(renderObject.values as LinesValues, ctx);
case 'points':
return this.addPoints(renderObject.values as PointsValues, ctx);
case 'spheres':
return this.addSpheres(renderObject.values as SpheresValues, ctx);
case 'cylinders':
return this.addCylinders(renderObject.values as CylindersValues, ctx);
case 'texture-mesh':
return this.addTextureMesh(renderObject.values as TextureMeshValues, webgl, ctx);
}
}
getData() {
return {
obj: StringBuilder.getString(this.obj),
mtl: StringBuilder.getString(this.mtl)
};
}
constructor(filename: string) {
StringBuilder.writeSafe(this.obj, `mtllib ${filename}.mtl\n`);
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
import { GeometryExporterUI } from './ui';
export const GeometryExport = PluginBehavior.create<{ }>({
name: 'extension-geo-export',
category: 'misc',
display: {
name: 'Geometry Export'
},
ctor: class extends PluginBehavior.Handler<{ }> {
register(): void {
this.ctx.customStructureControls.set('geo-export', GeometryExporterUI as any);
}
update() {
return false;
}
unregister() {
this.ctx.customStructureControls.delete('geo-export');
}
},
params: () => ({ })
});

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
*/
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
import { Button } from '../../mol-plugin-ui/controls/common';
import { GetAppSvg, CubeSendSvg } from '../../mol-plugin-ui/controls/icons';
import { download } from '../../mol-util/download';
import { GeometryControls } from './controls';
interface State {
busy?: boolean
}
export class GeometryExporterUI extends CollapsableControls<{}, State> {
private _controls: GeometryControls | undefined;
get controls() {
return this._controls || (this._controls = new GeometryControls(this.plugin));
}
protected defaultState(): State & CollapsableState {
return {
header: 'Export Geometries',
isCollapsed: true,
brand: { accent: 'cyan', svg: CubeSendSvg }
};
}
protected renderControls(): JSX.Element {
return <>
<Button icon={GetAppSvg}
onClick={this.saveObj} style={{ marginTop: 1 }}
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
Save OBJ + MTL
</Button>
</>;
}
componentDidMount() {
this.subscribe(this.plugin.canvas3d!.reprCount, () => {
if (!this.state.isCollapsed) this.forceUpdate();
});
}
componentWillUnmount() {
this._controls?.dispose();
this._controls = void 0;
}
saveObj = async () => {
try {
this.setState({ busy: true });
const data = await this.controls.exportObj();
this.setState({ busy: false });
download(new Blob([data.zipData]), data.filename);
} catch {
this.setState({ busy: false });
}
}
}

View File

@@ -4,7 +4,6 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import React from 'react';
import { merge } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';

View File

@@ -124,7 +124,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
const repr = AssemblySymmetryRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => AssemblySymmetryParams);
await repr.createOrUpdate(params, a.data).runInContext(ctx);
const { type, kind, symbol } = assemblySymmetry;
return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: kind, description: `${type} (${symbol})` });
return new PluginStateObject.Shape.Representation3D({ repr, sourceData: a.data }, { label: kind, description: `${type} (${symbol})` });
});
},
update({ a, b, newParams }, plugin: PluginContext) {
@@ -138,6 +138,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
}
const props = { ...b.data.repr.props, ...newParams };
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
b.data.sourceData = a.data;
const { type, kind, symbol } = assemblySymmetry;
b.label = kind;
b.description = `${type} (${symbol})`;

View File

@@ -4,7 +4,6 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react';
import { CollapsableState, CollapsableControls } from '../../../mol-plugin-ui/base';
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry } from './behavior';

View File

@@ -9,6 +9,7 @@ import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra';
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
import { CameraTransitionManager } from './camera/transition';
import { BehaviorSubject } from 'rxjs';
import { Scene } from '../mol-gl/scene';
export { ICamera, Camera };
@@ -86,7 +87,12 @@ class Camera implements ICamera {
if (changed) {
Mat4.mul(this.projectionView, this.projection, this.view);
Mat4.invert(this.inverseProjectionView, this.projectionView);
if (!Mat4.tryInvert(this.inverseProjectionView, this.projectionView)) {
Mat4.copy(this.view, this.prevView);
Mat4.copy(this.projection, this.prevProjection);
Mat4.mul(this.projectionView, this.projection, this.view);
return false;
}
Mat4.copy(this.prevView, this.view);
Mat4.copy(this.prevProjection, this.projection);
@@ -126,6 +132,23 @@ class Camera implements ICamera {
return state;
}
getInvariantFocus(target: Vec3, radius: number, up: Vec3, dir: Vec3): Partial<Camera.Snapshot> {
const r = Math.max(radius, 0.01);
const targetDistance = this.getTargetDistance(r);
Vec3.copy(this.deltaDirection, dir);
Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance);
Vec3.sub(this.newPosition, target, this.deltaDirection);
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
state.target = Vec3.clone(target);
state.radius = r;
state.position = Vec3.clone(this.newPosition);
Vec3.copy(state.up, up);
return state;
}
focus(target: Vec3, radius: number, durationMs?: number, up?: Vec3, dir?: Vec3) {
if (radius > 0) {
this.setState(this.getFocus(target, radius, up, dir), durationMs);
@@ -150,6 +173,8 @@ class Camera implements ICamera {
namespace Camera {
export type Mode = 'perspective' | 'orthographic'
export type SnapshotProvider = Partial<Snapshot> | ((scene: Scene, camera: Camera) => Partial<Snapshot>)
/**
* Sets an offseted view in a larger frustum. This is useful for
* - multi-window or multi-monitor/multi-machine setups
@@ -210,7 +235,7 @@ namespace Camera {
target: Vec3.create(0, 0, 0),
radius: 0,
radiusMax: 0,
radiusMax: 10,
fog: 50,
clipFar: true
};
@@ -247,6 +272,18 @@ namespace Camera {
return out;
}
export function areSnapshotsEqual(a: Snapshot, b: Snapshot) {
return a.mode === b.mode
&& a.fov === b.fov
&& a.radius === b.radius
&& a.radiusMax === b.radiusMax
&& a.fog === b.fog
&& a.clipFar === b.clipFar
&& Vec3.exactEquals(a.position, b.position)
&& Vec3.exactEquals(a.up, b.up)
&& Vec3.exactEquals(a.target, b.target);
}
}
function updateOrtho(camera: Camera) {

View File

@@ -39,6 +39,9 @@ class CameraTransitionManager {
this._target.radius = this._target.radiusMax;
}
if (this._target.radius < 0.01) this._target.radius = 0.01;
if (this._target.radiusMax < 0.01) this._target.radiusMax = 0.01;
if (!this.inTransition && durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
this.finish(this._target);
return;

View File

@@ -61,11 +61,17 @@ export const Canvas3DParams = {
}, { pivot: 'radius' }),
viewport: PD.MappedStatic('canvas', {
canvas: PD.Group({}),
custom: PD.Group({
'static-frame': PD.Group({
x: PD.Numeric(0),
y: PD.Numeric(0),
width: PD.Numeric(128),
height: PD.Numeric(128)
}),
'relative-frame': PD.Group({
x: PD.Numeric(0.33, { min: 0, max: 1, step: 0.01 }),
y: PD.Numeric(0.33, { min: 0, max: 1, step: 0.01 }),
width: PD.Numeric(0.5, { min: 0.01, max: 1, step: 0.01 }),
height: PD.Numeric(0.5, { min: 0.01, max: 1, step: 0.01 })
})
}),
@@ -100,7 +106,7 @@ interface Canvas3DContext {
}
namespace Canvas3DContext {
const DefaultAttribs = {
export const DefaultAttribs = {
/** true by default to avoid issues with Safari (Jan 2021) */
antialias: true,
/** true to support multiple Canvas3D objects with a single context */
@@ -201,7 +207,7 @@ interface Canvas3D {
*/
commit(isSynchronous?: boolean): void
/**
* Funcion for external "animation" control
* Function for external "animation" control
* Calls commit.
*/
tick(t: now.Timestamp, options?: { isSynchronous?: boolean, manualDraw?: boolean }): void
@@ -214,7 +220,11 @@ interface Canvas3D {
/** Reset the timers, used by "animate" */
resetTime(t: number): void
animate(): void
pause(): void
/**
* Pause animation loop and optionally any rendering
* @param noDraw pause any rendering
*/
pause(noDraw?: boolean): void
identify(x: number, y: number): PickData | undefined
mark(loci: Representation.Loci, action: MarkerAction): void
getLoci(pickingId: PickingId | undefined): Representation.Loci
@@ -229,11 +239,12 @@ interface Canvas3D {
/** performs handleResize on the next animation frame */
requestResize(): void
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
requestCameraReset(options?: { durationMs?: number, snapshot?: Partial<Camera.Snapshot> }): void
requestCameraReset(options?: { durationMs?: number, snapshot?: Camera.SnapshotProvider }): void
readonly camera: Camera
readonly boundingSphere: Readonly<Sphere3D>
setProps(props: PartialCanvas3DProps | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void), doNotRequestDraw?: boolean /* = false */): void
getImagePass(props: Partial<ImageProps>): ImagePass
getRenderObjects(): GraphicsRenderObject[]
/** Returns a copy of the current Canvas3D instance props */
readonly props: Readonly<Canvas3DProps>
@@ -296,7 +307,7 @@ namespace Canvas3D {
let drawPending = false;
let cameraResetRequested = false;
let nextCameraResetDuration: number | undefined = void 0;
let nextCameraResetSnapshot: Partial<Camera.Snapshot> | undefined = void 0;
let nextCameraResetSnapshot: Camera.SnapshotProvider | undefined = void 0;
let resizeRequested = false;
let notifyDidDraw = true;
@@ -305,7 +316,11 @@ namespace Canvas3D {
let loci: Loci = EmptyLoci;
let repr: Representation.Any = Representation.Empty;
if (pickingId) {
const cameraHelperLoci = helper.camera.getLoci(pickingId);
if (cameraHelperLoci !== EmptyLoci) return { loci: cameraHelperLoci, repr };
loci = helper.handle.getLoci(pickingId);
reprRenderObjects.forEach((_, _repr) => {
const _loci = _repr.getLoci(pickingId);
if (!isEmptyLoci(_loci)) {
@@ -327,11 +342,13 @@ namespace Canvas3D {
changed = repr.mark(loci, action);
} else {
changed = helper.handle.mark(loci, action);
changed = helper.camera.mark(loci, action) || changed;
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
}
if (changed) {
scene.update(void 0, true);
helper.handle.scene.update(void 0, true);
helper.camera.scene.update(void 0, true);
const prevPickDirty = pickHelper.dirty;
draw(true);
pickHelper.dirty = prevPickDirty; // marking does not change picking buffers
@@ -379,8 +396,10 @@ namespace Canvas3D {
let forceNextDraw = false;
let forceDrawAfterAllCommited = false;
let currentTime = 0;
let drawPaused = false;
function draw(force?: boolean) {
if (drawPaused) return;
if (render(!!force || forceNextDraw) && notifyDidDraw) {
didDraw.next(now() - startTime as now.Timestamp);
}
@@ -422,11 +441,13 @@ namespace Canvas3D {
}
function animate() {
drawPaused = false;
controls.start(now());
if (animationFrameHandle === 0) _animate();
}
function pause() {
function pause(noDraw = false) {
drawPaused = noDraw;
cancelAnimationFrame(animationFrameHandle);
animationFrameHandle = 0;
}
@@ -453,11 +474,21 @@ namespace Canvas3D {
function resolveCameraReset() {
if (!cameraResetRequested) return;
const { center, radius } = scene.boundingSphereVisible;
const boundingSphere = scene.boundingSphereVisible;
const { center, radius } = boundingSphere;
const autoAdjustControls = controls.props.autoAdjustMinMaxDistance;
if (autoAdjustControls.name === 'on') {
const minDistance = autoAdjustControls.params.minDistanceFactor * radius + autoAdjustControls.params.minDistancePadding;
const maxDistance = Math.max(autoAdjustControls.params.maxDistanceFactor * radius, autoAdjustControls.params.maxDistanceMin);
controls.setProps({ minDistance, maxDistance });
}
if (radius > 0) {
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration;
const focus = camera.getFocus(center, radius);
const snapshot = nextCameraResetSnapshot ? { ...focus, ...nextCameraResetSnapshot } : focus;
const next = typeof nextCameraResetSnapshot === 'function' ? nextCameraResetSnapshot(scene, camera) : nextCameraResetSnapshot;
const snapshot = next ? { ...focus, ...next } : focus;
camera.setState({ ...snapshot, radiusMax: scene.boundingSphere.radius }, duration);
}
@@ -753,6 +784,11 @@ namespace Canvas3D {
getImagePass: (props: Partial<ImageProps> = {}) => {
return new ImagePass(webgl, renderer, scene, camera, helper, passes.draw.wboitEnabled, props);
},
getRenderObjects(): GraphicsRenderObject[] {
const renderObjects: GraphicsRenderObject[] = [];
scene.forEach((_, ro) => renderObjects.push(ro));
return renderObjects;
},
get props() {
return getProps();
@@ -783,12 +819,21 @@ namespace Canvas3D {
y = 0;
width = gl.drawingBufferWidth;
height = gl.drawingBufferHeight;
} else {
} else if (p.viewport.name === 'static-frame') {
x = p.viewport.params.x * webgl.pixelRatio;
y = p.viewport.params.y * webgl.pixelRatio;
width = p.viewport.params.width * webgl.pixelRatio;
height = p.viewport.params.height * webgl.pixelRatio;
y = gl.drawingBufferHeight - height - p.viewport.params.y * webgl.pixelRatio;
width = p.viewport.params.width * webgl.pixelRatio;
} else if (p.viewport.name === 'relative-frame') {
x = Math.round(p.viewport.params.x * gl.drawingBufferWidth);
height = Math.round(p.viewport.params.height * gl.drawingBufferHeight);
y = Math.round(gl.drawingBufferHeight - height - p.viewport.params.y * gl.drawingBufferHeight);
width = Math.round(p.viewport.params.width * gl.drawingBufferWidth);
// if (x + width >= gl.drawingBufferWidth) width = gl.drawingBufferWidth - x;
// if (y + height >= gl.drawingBufferHeight) height = gl.drawingBufferHeight - y - 1;
// console.log({ x, y, width, height });
}
}
function syncViewport() {

View File

@@ -49,7 +49,21 @@ export const TrackballControlsParams = {
minDistance: PD.Numeric(0.01, {}, { isHidden: true }),
maxDistance: PD.Numeric(1e150, {}, { isHidden: true }),
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true })
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true }),
/**
* minDistance = minDistanceFactor * boundingSphere.radius + minDistancePadding
* maxDistance = max(maxDistanceFactor * boundingSphere.radius, maxDistanceMin)
*/
autoAdjustMinMaxDistance: PD.MappedStatic('on', {
off: PD.EmptyGroup(),
on: PD.Group({
minDistanceFactor: PD.Numeric(0),
minDistancePadding: PD.Numeric(5),
maxDistanceFactor: PD.Numeric(10),
maxDistanceMin: PD.Numeric(20)
})
}, { isHidden: true })
};
export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams>

View File

@@ -1,30 +1,35 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Scene } from '../../mol-gl/scene';
import { Camera, ICamera } from '../camera';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { ColorNames } from '../../mol-util/color/names';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { Viewport } from '../camera/util';
import { Sphere3D } from '../../mol-math/geometry';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import produce from 'immer';
import { Interval } from '../../mol-data/int/interval';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { PickingId } from '../../mol-geo/geometry/picking';
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { Scene } from '../../mol-gl/scene';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Sphere3D } from '../../mol-math/geometry';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { DataLoci, EmptyLoci, Loci } from '../../mol-model/loci';
import { Shape } from '../../mol-model/shape';
import { Visual } from '../../mol-repr/visual';
import { ColorNames } from '../../mol-util/color/names';
import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Camera, ICamera } from '../camera';
import { Viewport } from '../camera/util';
// TODO add scale line/grid
const AxesParams = {
...Mesh.Params,
alpha: { ...Mesh.Params.alpha, defaultValue: 0.33 },
alpha: { ...Mesh.Params.alpha, defaultValue: 0.51 },
ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
colorX: PD.Color(ColorNames.red, { isEssential: true }),
colorY: PD.Color(ColorNames.green, { isEssential: true }),
@@ -87,6 +92,32 @@ export class CameraHelper {
return this.props.axes.name === 'on';
}
getLoci(pickingId: PickingId) {
const { objectId, groupId, instanceId } = pickingId;
if (!this.renderObject || objectId !== this.renderObject.id || groupId === CameraHelperAxis.None) return EmptyLoci;
return CameraAxesLoci(this, groupId, instanceId);
}
private eachGroup = (loci: Loci, apply: (interval: Interval) => boolean): boolean => {
if (!this.renderObject) return false;
if (!isCameraAxesLoci(loci)) return false;
let changed = false;
const groupCount = this.renderObject.values.uGroupCount.ref.value;
const { elements } = loci;
for (const { groupId, instanceId } of elements) {
const idx = instanceId * groupCount + groupId;
if (apply(Interval.ofSingleton(idx))) changed = true;
}
return changed;
}
mark(loci: Loci, action: MarkerAction) {
if (!MarkerActions.is(MarkerActions.Highlighting, action)) return false;
if (!isCameraAxesLoci(loci)) return false;
if (loci.data !== this) return false;
return Visual.mark(this.renderObject, loci, action, this.eachGroup);
}
update(camera: ICamera) {
if (!this.renderObject) return;
@@ -102,6 +133,38 @@ export class CameraHelper {
}
}
export const enum CameraHelperAxis {
None = 0,
X,
Y,
Z,
XY,
XZ,
YZ
}
function getAxisLabel(axis: number) {
switch (axis) {
case CameraHelperAxis.X: return 'X Axis';
case CameraHelperAxis.Y: return 'Y Axis';
case CameraHelperAxis.Z: return 'Z Axis';
case CameraHelperAxis.XY: return 'XY Plane';
case CameraHelperAxis.XZ: return 'XZ Plane';
case CameraHelperAxis.YZ: return 'YZ Plane';
default: return 'Axes';
}
}
function CameraAxesLoci(cameraHelper: CameraHelper, groupId: number, instanceId: number) {
return DataLoci('camera-axes', cameraHelper, [{ groupId, instanceId }],
void 0 /** bounding sphere */,
() => getAxisLabel(groupId));
}
export type CameraAxesLoci = ReturnType<typeof CameraAxesLoci>
export function isCameraAxesLoci(x: Loci): x is CameraAxesLoci {
return x.kind === 'data-loci' && x.tag === 'camera-axes';
}
function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.ViewOffset) {
const { near, far } = camera;
@@ -134,27 +197,52 @@ function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.Vie
function createAxesMesh(scale: number, mesh?: Mesh) {
const state = MeshBuilder.createState(512, 256, mesh);
const radius = 0.05 * scale;
const radius = 0.075 * scale;
const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
state.currentGroup = 0;
state.currentGroup = CameraHelperAxis.None;
addSphere(state, Vec3.origin, radius, 2);
state.currentGroup = 1;
state.currentGroup = CameraHelperAxis.X;
addSphere(state, x, radius, 2);
addCylinder(state, Vec3.origin, x, 1, cylinderProps);
state.currentGroup = 2;
state.currentGroup = CameraHelperAxis.Y;
addSphere(state, y, radius, 2);
addCylinder(state, Vec3.origin, y, 1, cylinderProps);
state.currentGroup = 3;
state.currentGroup = CameraHelperAxis.Z;
addSphere(state, z, radius, 2);
addCylinder(state, Vec3.origin, z, 1, cylinderProps);
Vec3.scale(x, x, 0.5);
Vec3.scale(y, y, 0.5);
Vec3.scale(z, z, 0.5);
state.currentGroup = CameraHelperAxis.XY;
MeshBuilder.addTriangle(state, Vec3.origin, x, y);
MeshBuilder.addTriangle(state, Vec3.origin, y, x);
const xy = Vec3.add(Vec3(), x, y);
MeshBuilder.addTriangle(state, xy, x, y);
MeshBuilder.addTriangle(state, xy, y, x);
state.currentGroup = CameraHelperAxis.XZ;
MeshBuilder.addTriangle(state, Vec3.origin, x, z);
MeshBuilder.addTriangle(state, Vec3.origin, z, x);
const xz = Vec3.add(Vec3(), x, z);
MeshBuilder.addTriangle(state, xz, x, z);
MeshBuilder.addTriangle(state, xz, z, x);
state.currentGroup = CameraHelperAxis.YZ;
MeshBuilder.addTriangle(state, Vec3.origin, y, z);
MeshBuilder.addTriangle(state, Vec3.origin, z, y);
const yz = Vec3.add(Vec3(), y, z);
MeshBuilder.addTriangle(state, yz, y, z);
MeshBuilder.addTriangle(state, yz, z, y);
return MeshBuilder.getMesh(state);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -100,7 +100,7 @@ export class DrawPass {
}
constructor(private webgl: WebGLContext, width: number, height: number, enableWboit: boolean) {
const { extensions, resources } = webgl;
const { extensions, resources, isWebGL2 } = webgl;
this.drawTarget = createNullRenderTarget(webgl.gl);
@@ -113,8 +113,8 @@ export class DrawPass {
this.depthTargetPrimitives = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
this.depthTargetVolumes = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
this.depthTexturePrimitives = this.depthTargetPrimitives ? this.depthTargetPrimitives.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest');
this.depthTextureVolumes = this.depthTargetVolumes ? this.depthTargetVolumes.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest');
this.depthTexturePrimitives = this.depthTargetPrimitives ? this.depthTargetPrimitives.texture : resources.texture('image-depth', 'depth', isWebGL2 ? 'float' : 'ushort', 'nearest');
this.depthTextureVolumes = this.depthTargetVolumes ? this.depthTargetVolumes.texture : resources.texture('image-depth', 'depth', isWebGL2 ? 'float' : 'ushort', 'nearest');
if (!this.packedDepth) {
this.depthTexturePrimitives.define(width, height);
this.depthTextureVolumes.define(width, height);

View File

@@ -66,14 +66,20 @@ export class PickPass {
private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant) {
const depth = this.drawPass.depthTexturePrimitives;
renderer.clear(false);
renderer.update(camera);
renderer.renderPick(scene.primitives, camera, variant, null);
renderer.renderPick(scene.volumes, camera, variant, depth);
renderer.renderPick(helper.handle.scene, camera, variant, null);
if (helper.camera.isEnabled) {
helper.camera.update(camera);
renderer.update(helper.camera.camera);
renderer.renderPick(helper.camera.scene, helper.camera.camera, variant, null);
}
}
render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper) {
renderer.update(camera);
this.objectPickTarget.bind();
this.renderVariant(renderer, camera, scene, helper, 'pickObject');
@@ -122,8 +128,8 @@ export class PickHelper {
this.pickX = Math.ceil(x * this.pickScale);
this.pickY = Math.ceil(y * this.pickScale);
const pickWidth = Math.ceil(width * this.pickScale);
const pickHeight = Math.ceil(height * this.pickScale);
const pickWidth = Math.floor(width * this.pickScale);
const pickHeight = Math.floor(height * this.pickScale);
if (pickWidth !== this.pickWidth || pickHeight !== this.pickHeight) {
this.pickWidth = pickWidth;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -13,7 +13,7 @@ import { Texture } from '../../mol-gl/webgl/texture';
import { ValueCell } from '../../mol-util';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable';
import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { Mat4, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { RenderTarget } from '../../mol-gl/webgl/render-target';
import { DrawPass } from './draw';
@@ -70,6 +70,7 @@ const SsaoSchema = {
uProjection: UniformSpec('m4'),
uInvProjection: UniformSpec('m4'),
uBounds: UniformSpec('v4'),
uTexSize: UniformSpec('v2'),
@@ -89,6 +90,7 @@ function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture): SsaoRender
uProjection: ValueCell.create(Mat4.identity()),
uInvProjection: ValueCell.create(Mat4.identity()),
uBounds: ValueCell.create(Vec4()),
uTexSize: ValueCell.create(Vec2.create(ctx.gl.drawingBufferWidth, ctx.gl.drawingBufferHeight)),
@@ -118,6 +120,7 @@ const SsaoBlurSchema = {
uNear: UniformSpec('f'),
uFar: UniformSpec('f'),
uBounds: UniformSpec('v4'),
dOrthographic: DefineSpec('number'),
};
@@ -139,6 +142,7 @@ function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, dir
uNear: ValueCell.create(0.0),
uFar: ValueCell.create(10000.0),
uBounds: ValueCell.create(Vec4()),
dOrthographic: ValueCell.create(0),
};
@@ -286,10 +290,14 @@ export class PostprocessingPass {
private readonly renderable: PostprocessingRenderable
private scale: number
private ssaoScale: number
private calcSsaoScale() {
// downscale ssao for high pixel-ratios
return Math.min(1, 1 / this.webgl.pixelRatio);
}
constructor(private webgl: WebGLContext, drawPass: DrawPass) {
this.scale = 1 / this.webgl.pixelRatio;
this.ssaoScale = this.calcSsaoScale();
const { colorTarget, depthTexture } = drawPass;
const width = colorTarget.getWidth();
@@ -298,7 +306,7 @@ export class PostprocessingPass {
this.nSamples = 1;
this.blurKernelSize = 1;
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'nearest');
this.outlinesTarget = webgl.createRenderTarget(width, height, false);
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTexture);
@@ -317,14 +325,14 @@ export class PostprocessingPass {
this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
this.ssaoBlurSecondPassFramebuffer = webgl.resources.framebuffer();
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
const sw = Math.floor(width * this.ssaoScale);
const sh = Math.floor(height * this.ssaoScale);
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
this.ssaoDepthTexture.define(sw, sh);
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
this.ssaoDepthBlurProxyTexture.define(sw, sh);
this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
@@ -338,9 +346,13 @@ export class PostprocessingPass {
setSize(width: number, height: number) {
const [w, h] = this.renderable.values.uTexSize.ref.value;
if (width !== w || height !== h) {
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
const ssaoScale = this.calcSsaoScale();
if (width !== w || height !== h || this.ssaoScale !== ssaoScale) {
this.ssaoScale = ssaoScale;
const sw = Math.floor(width * this.ssaoScale);
const sh = Math.floor(height * this.ssaoScale);
this.target.setSize(width, height);
this.outlinesTarget.setSize(width, height);
this.ssaoDepthTexture.define(sw, sh);
@@ -349,8 +361,8 @@ export class PostprocessingPass {
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
}
}
@@ -367,8 +379,22 @@ export class PostprocessingPass {
Mat4.invert(invProjection, camera.projection);
if (props.occlusion.name === 'on') {
ValueCell.updateIfChanged(this.ssaoRenderable.values.uProjection, camera.projection);
ValueCell.updateIfChanged(this.ssaoRenderable.values.uInvProjection, invProjection);
ValueCell.update(this.ssaoRenderable.values.uProjection, camera.projection);
ValueCell.update(this.ssaoRenderable.values.uInvProjection, invProjection);
const [w, h] = this.renderable.values.uTexSize.ref.value;
const b = this.ssaoRenderable.values.uBounds;
const v = camera.viewport;
const s = this.ssaoScale;
Vec4.set(b.ref.value,
Math.floor(v.x * s) / (w * s),
Math.floor(v.y * s) / (h * s),
Math.ceil((v.x + v.width) * s) / (w * s),
Math.ceil((v.y + v.height) * s) / (h * s)
);
ValueCell.update(b, b.ref.value);
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uBounds, b.ref.value);
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uBounds, b.ref.value);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uNear, camera.near);
@@ -376,7 +402,9 @@ export class PostprocessingPass {
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uFar, camera.far);
if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateSsaoBlur = true; }
if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) {
needsUpdateSsaoBlur = true;
}
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOrthographic, orthographic);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOrthographic, orthographic);
@@ -384,7 +412,7 @@ export class PostprocessingPass {
needsUpdateSsao = true;
this.nSamples = props.occlusion.params.samples;
ValueCell.updateIfChanged(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples));
ValueCell.update(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples));
ValueCell.updateIfChanged(this.ssaoRenderable.values.dNSamples, this.nSamples);
}
ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, Math.pow(2, props.occlusion.params.radius));
@@ -394,10 +422,10 @@ export class PostprocessingPass {
needsUpdateSsaoBlur = true;
this.blurKernelSize = props.occlusion.params.blurKernelSize;
let kernel = getBlurKernel(this.blurKernelSize);
const kernel = getBlurKernel(this.blurKernelSize);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
}
@@ -467,10 +495,10 @@ export class PostprocessingPass {
if (props.occlusion.name === 'on') {
const { x, y, width, height } = camera.viewport;
const sx = Math.floor(x * this.scale);
const sy = Math.floor(y * this.scale);
const sw = Math.floor(width * this.scale);
const sh = Math.floor(height * this.scale);
const sx = Math.floor(x * this.ssaoScale);
const sy = Math.floor(y * this.ssaoScale);
const sw = Math.ceil(width * this.ssaoScale);
const sh = Math.ceil(height * this.ssaoScale);
this.webgl.gl.viewport(sx, sy, sw, sh);
this.webgl.gl.scissor(sx, sy, sw, sh);

View File

@@ -12,13 +12,13 @@ export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height:
}
/** Resize canvas to container element taking `devicePixelRatio` into account */
export function resizeCanvas (canvas: HTMLCanvasElement, container: Element, scale = 1) {
export function resizeCanvas (canvas: HTMLCanvasElement, container: HTMLElement, scale = 1) {
let width = window.innerWidth;
let height = window.innerHeight;
if (container !== document.body) {
let bounds = container.getBoundingClientRect();
width = bounds.right - bounds.left;
height = bounds.bottom - bounds.top;
// fixes issue #molstar/molstar#147, offsetWidth/offsetHeight is correct size when css transform:scale is used
width = container.offsetWidth;
height = container.offsetHeight;
}
setCanvasSize(canvas, width, height, scale);
}

View File

@@ -18,11 +18,24 @@ export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 've
export type ColorData = {
uColor: ValueCell<Vec3>,
tColor: ValueCell<TextureImage<Uint8Array>>,
tPalette: ValueCell<TextureImage<Uint8Array>>,
uColorTexDim: ValueCell<Vec2>,
dColorType: ValueCell<string>,
dUsePalette: ValueCell<boolean>,
}
export function createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData {
const data = _createColors(locationIt, positionIt, colorTheme, colorData);
if (colorTheme.palette) {
ValueCell.updateIfChanged(data.dUsePalette, true);
updatePaletteTexture(colorTheme.palette, data.tPalette);
} else {
ValueCell.updateIfChanged(data.dUsePalette, false);
}
return data;
}
function _createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData {
switch (Geometry.getGranularity(locationIt, colorTheme.granularity)) {
case 'uniform': return createUniformColor(locationIt, colorTheme.color, colorData);
case 'instance': return createInstanceColor(locationIt, colorTheme.color, colorData);
@@ -42,18 +55,20 @@ export function createValueColor(value: Color, colorData?: ColorData): ColorData
return {
uColor: ValueCell.create(Color.toVec3Normalized(Vec3(), value)),
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
dColorType: ValueCell.create('uniform'),
dUsePalette: ValueCell.create(false),
};
}
}
/** Creates color uniform */
export function createUniformColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
function createUniformColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
return createValueColor(color(NullLocation, false), colorData);
}
export function createTextureColor(colors: TextureImage<Uint8Array>, type: ColorType, colorData?: ColorData): ColorData {
function createTextureColor(colors: TextureImage<Uint8Array>, type: ColorType, colorData?: ColorData): ColorData {
if (colorData) {
ValueCell.update(colorData.tColor, colors);
ValueCell.update(colorData.uColorTexDim, Vec2.create(colors.width, colors.height));
@@ -63,14 +78,16 @@ export function createTextureColor(colors: TextureImage<Uint8Array>, type: Color
return {
uColor: ValueCell.create(Vec3()),
tColor: ValueCell.create(colors),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(colors.width, colors.height)),
dColorType: ValueCell.create(type),
dUsePalette: ValueCell.create(false),
};
}
}
/** Creates color texture with color for each instance */
export function createInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
function createInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { instanceCount } = locationIt;
const colors = createTextureImage(Math.max(1, instanceCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
locationIt.reset();
@@ -83,7 +100,7 @@ export function createInstanceColor(locationIt: LocationIterator, color: Locatio
}
/** Creates color texture with color for each group (i.e. shared across instances) */
export function createGroupColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
function createGroupColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { groupCount } = locationIt;
const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
locationIt.reset();
@@ -95,7 +112,7 @@ export function createGroupColor(locationIt: LocationIterator, color: LocationCo
}
/** Creates color texture with color for each group in each instance */
export function createGroupInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
function createGroupInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { groupCount, instanceCount } = locationIt;
const count = instanceCount * groupCount;
const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
@@ -108,7 +125,7 @@ export function createGroupInstanceColor(locationIt: LocationIterator, color: Lo
}
/** Creates color texture with color for each vertex (i.e. shared across instances) */
export function createVertexColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
function createVertexColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { groupCount, stride } = locationIt;
const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
locationIt.reset();
@@ -124,7 +141,7 @@ export function createVertexColor(locationIt: LocationIterator, color: LocationC
}
/** Creates color texture with color for each vertex in each instance */
export function createVertexInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
function createVertexInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
const { groupCount, instanceCount, stride } = locationIt;
const count = instanceCount * groupCount;
const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
@@ -138,3 +155,34 @@ export function createVertexInstanceColor(locationIt: LocationIterator, color: L
}
return createTextureColor(colors, 'vertexInstance', colorData);
}
function updatePaletteTexture(palette: ColorTheme.Palette, cell: ValueCell<TextureImage<Uint8Array>>) {
let isSynced = true;
const texture = cell.ref.value;
if (palette.colors.length !== texture.width || texture.filter !== palette.filter) {
isSynced = false;
} else {
const data = texture.array;
let o = 0;
for (const c of palette.colors) {
const [r, g, b] = Color.toRgb(c);
if (data[o++] !== r || data[o++] !== g || data[o++] !== b) {
isSynced = false;
break;
}
}
}
if (isSynced) return;
const array = new Uint8Array(palette.colors.length * 3);
let o = 0;
for (const c of palette.colors) {
const [r, g, b] = Color.toRgb(c);
array[o++] = r;
array[o++] = g;
array[o++] = b;
}
ValueCell.update(cell, { array, height: 1, width: palette.colors.length, filter: palette.filter });
}

View File

@@ -55,7 +55,7 @@ export interface GeometryUtils<G extends Geometry, P extends PD.Params = Geometr
createValuesSimple(geometry: G, props: Partial<PD.Values<P>>, colorValue: Color, sizeValue: number, transform?: TransformData): V
updateValues(values: V, props: PD.Values<P>): void
updateBoundingSphere(values: V, geometry: G): void
createRenderableState(props: Partial<PD.Values<P>>): RenderableState
createRenderableState(props: PD.Values<P>): RenderableState
updateRenderableState(state: RenderableState, props: PD.Values<P>): void
createPositionIterator(geometry: G, transform: TransformData): LocationIterator
}

View File

@@ -144,6 +144,14 @@ export namespace MeshBuilder {
}
}
export function addMesh(state: State, t: Mat4, mesh: Mesh) {
addPrimitive(state, t, {
vertices: mesh.vertexBuffer.ref.value.subarray(0, mesh.vertexCount * 3),
normals: mesh.normalBuffer.ref.value.subarray(0, mesh.vertexCount * 3),
indices: mesh.indexBuffer.ref.value.subarray(0, mesh.triangleCount * 3),
});
}
export function getMesh (state: State): Mesh {
const { vertices, normals, indices, groups, mesh } = state;
const vb = ChunkedArray.compact(vertices, true) as Float32Array;

View File

@@ -177,7 +177,7 @@ export namespace Spheres {
const counts = { drawCount: spheres.sphereCount * 2 * 3, vertexCount: spheres.sphereCount * 4, groupCount, instanceCount };
const padding = getMaxSize(size) * props.sizeFactor;
const padding = spheres.boundingSphere.radius ? getMaxSize(size) * props.sizeFactor : 0;
const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), spheres.boundingSphere, padding);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
@@ -222,7 +222,9 @@ export namespace Spheres {
}
function updateBoundingSphere(values: SpheresValues, spheres: Spheres) {
const padding = getMaxSize(values) * values.uSizeFactor.ref.value;
const padding = spheres.boundingSphere.radius
? getMaxSize(values) * values.uSizeFactor.ref.value
: 0;
const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), spheres.boundingSphere, padding);
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);

View File

@@ -0,0 +1,78 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
// adapted from three.js, MIT License Copyright 2010-2021 three.js authors
import { Vec3 } from '../../mol-math/linear-algebra';
import { Primitive } from './primitive';
export const DefaultTorusProps = {
radius: 1,
tube: 0.4,
radialSegments: 8,
tubularSegments: 6,
arc: Math.PI * 2,
};
export type TorusProps = Partial<typeof DefaultTorusProps>
export function Torus(props?: TorusProps): Primitive {
const { radius, tube, radialSegments, tubularSegments, arc } = { ...DefaultTorusProps, ...props };
// buffers
const indices: number[] = [];
const vertices: number[] = [];
const normals: number[] = [];
// helper variables
const center = Vec3();
const vertex = Vec3();
const normal = Vec3();
// generate vertices and normals
for (let j = 0; j <= radialSegments; ++j) {
for (let i = 0; i <= tubularSegments; ++i) {
const u = i / tubularSegments * arc;
const v = j / radialSegments * Math.PI * 2;
// vertex
Vec3.set(
vertex,
(radius + tube * Math.cos(v)) * Math.cos(u),
(radius + tube * Math.cos(v)) * Math.sin(u),
tube * Math.sin(v)
);
vertices.push(...vertex);
// normal
Vec3.set(center, radius * Math.cos(u), radius * Math.sin(u), 0 );
Vec3.sub(normal, vertex, center);
Vec3.normalize(normal, normal);
normals.push(...normal);
}
}
// generate indices
for (let j = 1; j <= radialSegments; ++j) {
for (let i = 1; i <= tubularSegments; ++i) {
// indices
const a = (tubularSegments + 1) * j + i - 1;
const b = (tubularSegments + 1) * (j - 1) + i - 1;
const c = (tubularSegments + 1) * (j - 1) + i;
const d = (tubularSegments + 1) * j + i;
// faces
indices.push(a, b, d);
indices.push(b, c, d);
}
}
return {
vertices: new Float32Array(vertices),
normals: new Float32Array(normals),
indices: new Uint32Array(indices)
};
}

View File

@@ -133,7 +133,7 @@ describe('renderer', () => {
scene.add(points);
scene.commit();
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
expect(ctx.stats.resourceCounts.texture).toBe(6);
expect(ctx.stats.resourceCounts.texture).toBe(7);
expect(ctx.stats.resourceCounts.vertexArray).toBe(6);
expect(ctx.stats.resourceCounts.program).toBe(6);
expect(ctx.stats.resourceCounts.shader).toBe(12);

View File

@@ -32,6 +32,7 @@ const IsosurfaceSchema = {
uSize: UniformSpec('f'),
uLevels: UniformSpec('f'),
uCount: UniformSpec('f'),
uInvert: UniformSpec('b'),
uGridDim: UniformSpec('v3'),
uGridTexDim: UniformSpec('v3'),
@@ -44,7 +45,7 @@ type IsosurfaceValues = Values<typeof IsosurfaceSchema>
const IsosurfaceName = 'isosurface';
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, packedGroup: boolean): ComputeRenderable<IsosurfaceValues> {
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean): ComputeRenderable<IsosurfaceValues> {
if (ctx.namedComputeRenderables[IsosurfaceName]) {
const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues;
@@ -56,6 +57,7 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
ValueCell.updateIfChanged(v.uSize, Math.pow(2, levels));
ValueCell.updateIfChanged(v.uLevels, levels);
ValueCell.updateIfChanged(v.uCount, count);
ValueCell.updateIfChanged(v.uInvert, invert);
ValueCell.update(v.uGridDim, gridDim);
ValueCell.update(v.uGridTexDim, gridTexDim);
@@ -66,12 +68,12 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
ctx.namedComputeRenderables[IsosurfaceName].update();
} else {
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, packedGroup);
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup);
}
return ctx.namedComputeRenderables[IsosurfaceName];
}
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, packedGroup: boolean) {
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean) {
// console.log('uSize', Math.pow(2, levels))
const values: IsosurfaceValues = {
...QuadValues,
@@ -85,6 +87,7 @@ function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Text
uSize: ValueCell.create(Math.pow(2, levels)),
uLevels: ValueCell.create(levels),
uCount: ValueCell.create(count),
uInvert: ValueCell.create(invert),
uGridDim: ValueCell.create(gridDim),
uGridTexDim: ValueCell.create(gridTexDim),
@@ -112,7 +115,7 @@ function setRenderingDefaults(ctx: WebGLContext) {
state.clearColor(0, 0, 0, 0);
}
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
const { gl, resources, extensions } = ctx;
const { pyramidTex, height, levels, scale, count } = histogramPyramid;
const width = pyramidTex.getWidth();
@@ -167,7 +170,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
groupTexture.attachFramebuffer(framebuffer, 1);
normalTexture.attachFramebuffer(framebuffer, 2);
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, packedGroup);
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup);
ctx.state.currentRenderItemId = -1;
const { drawBuffers } = ctx.extensions;
@@ -192,7 +195,16 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
//
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
/**
* GPU isosurface extraction
*
* Algorithm from "Highspeed Marching Cubes using HistoPyramids"
* by C Dyken, G Ziegler, C Theobalt, HP Seidel
* https://doi.org/10.1111/j.1467-8659.2008.01182.x
*
* Implementation based on http://www.miaumiau.cat/2016/10/stream-compaction-in-webgl/
*/
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
// console.time('calcActiveVoxels');
const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
// ctx.waitForGpuCommandsCompleteSync();
@@ -204,7 +216,7 @@ export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDi
// console.timeEnd('createHistogramPyramid');
// console.time('createIsosurfaceBuffers');
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, packedGroup, vertexTexture, groupTexture, normalTexture);
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, vertexTexture, groupTexture, normalTexture);
// ctx.waitForGpuCommandsCompleteSync();
// console.timeEnd('createIsosurfaceBuffers');

View File

@@ -185,7 +185,9 @@ export const ColorSchema = {
uColor: UniformSpec('v3', 'material'),
uColorTexDim: UniformSpec('v2'),
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
tPalette: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
dUsePalette: DefineSpec('boolean'),
} as const;
export type ColorSchema = typeof ColorSchema
export type ColorValues = Values<ColorSchema>

View File

@@ -7,6 +7,7 @@
import { Sphere3D } from '../../mol-math/geometry';
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
import { TextureFilter } from '../webgl/texture';
export function calculateTextureInfo (n: number, itemSize: number) {
n = Math.max(n, 2); // observed issues with 1 pixel textures
@@ -22,6 +23,7 @@ export interface TextureImage<T extends Uint8Array | Float32Array | Int32Array>
readonly width: number
readonly height: number
readonly flipY?: boolean
readonly filter?: TextureFilter
}
export interface TextureVolume<T extends Uint8Array | Float32Array> {

View File

@@ -14,6 +14,10 @@ export const assign_color_varying = `
vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb;
#endif
#ifdef dUsePalette
vPaletteV = ((vColor.r * 256.0 * 256.0 * 255.0 + vColor.g * 256.0 * 255.0 + vColor.b * 255.0) - 1.0) / 16777215.0;
#endif
#ifdef dOverpaint
vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
#endif

View File

@@ -1,6 +1,8 @@
export const assign_material_color = `
#if defined(dRenderVariant_color)
#if defined(dColorType_uniform)
#if defined(dUsePalette)
vec4 material = vec4(texture2D(tPalette, vec2(vPaletteV, 0.5)).rgb, uAlpha);
#elif defined(dColorType_uniform)
vec4 material = vec4(uColor, uAlpha);
#elif defined(dColorType_varying)
vec4 material = vec4(vColor.rgb, uAlpha);

View File

@@ -21,4 +21,9 @@ export const color_frag_params = `
varying float vGroup;
varying float vTransparency;
#endif
#ifdef dUsePalette
uniform sampler2D tPalette;
varying float vPaletteV;
#endif
`;

View File

@@ -30,4 +30,8 @@ export const color_vert_params = `
uniform vec2 uTransparencyTexDim;
uniform sampler2D tTransparency;
#endif
#ifdef dUsePalette
varying float vPaletteV;
#endif
`;

View File

@@ -18,6 +18,7 @@ uniform float uIsoValue;
uniform float uLevels;
uniform float uSize;
uniform float uCount;
uniform bool uInvert;
uniform vec3 uGridDim;
uniform vec3 uGridTexDim;
@@ -163,6 +164,13 @@ void main(void) {
// current vertex for the up to 15 MC cases
int currentVertex = vI - idot4(m, starts);
// ensure winding-order is the same for negative and positive iso-levels
if (uInvert) {
int v = imod(currentVertex + 1, 3);
if (v == 1) currentVertex += 2;
else if (v == 0) currentVertex -= 2;
}
// get index into triIndices table
int mcIndex = 16 * int(edgeIndex) + currentVertex;
vec4 mcData = texture2D(tTriIndices, vec2(imod(mcIndex, 64), mcIndex / 64) / 64.);
@@ -273,11 +281,18 @@ void main(void) {
voxelPadded(b1 - c3).a - voxelPadded(b1 + c3).a,
voxelPadded(b1 - c4).a - voxelPadded(b1 + c4).a
));
mat3 normalMatrix = transpose3(inverse3(mat3(uGridTransform)));
gl_FragData[2].xyz = normalMatrix * -vec3(
gl_FragData[2].xyz = -vec3(
n0.x + t * (n0.x - n1.x),
n0.y + t * (n0.y - n1.y),
n0.z + t * (n0.z - n1.z)
);
// ensure normal-direction is the same for negative and positive iso-levels
if (uInvert) {
gl_FragData[2].xyz *= -1.0;
}
// apply normal matrix
gl_FragData[2].xyz *= transpose3(inverse3(mat3(uGridTransform)));
}
`;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -88,7 +88,8 @@ float getSsao(vec2 coords) {
} else if (rawSsao > 0.001) {
return rawSsao;
}
return 0.0;
// treat values close to 0.0 as errors and return no occlusion
return 1.0;
}
void main(void) {

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
export const ssaoBlur_frag = `
@@ -11,6 +12,7 @@ precision highp sampler2D;
uniform sampler2D tSsaoDepth;
uniform vec2 uTexSize;
uniform vec4 uBounds;
uniform float uKernel[dOcclusionKernelSize];
@@ -36,16 +38,25 @@ bool isBackground(const in float depth) {
return depth == 1.0;
}
bool outsideBounds(const in vec2 p) {
return p.x < uBounds.x || p.y < uBounds.y || p.x > uBounds.z || p.y > uBounds.w;
}
void main(void) {
vec2 coords = gl_FragCoord.xy / uTexSize;
vec2 packedDepth = texture2D(tSsaoDepth, coords).zw;
if (outsideBounds(coords)) {
gl_FragColor = vec4(packUnitIntervalToRG(1.0), packedDepth);
return;
}
float selfDepth = unpackRGToUnitInterval(packedDepth);
// if background and if second pass
if (isBackground(selfDepth) && uBlurDirectionY != 0.0) {
gl_FragColor = vec4(packUnitIntervalToRG(1.0), packedDepth);
return;
gl_FragColor = vec4(packUnitIntervalToRG(1.0), packedDepth);
return;
}
float selfViewZ = getViewZ(selfDepth);
@@ -57,6 +68,9 @@ void main(void) {
// only if kernelSize is odd
for (int i = -dOcclusionKernelSize / 2; i <= dOcclusionKernelSize / 2; i++) {
vec2 sampleCoords = coords + float(i) * offset;
if (outsideBounds(sampleCoords)) {
continue;
}
vec4 sampleSsaoDepth = texture2D(tSsaoDepth, sampleCoords);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -13,14 +13,14 @@ precision highp sampler2D;
#include common
uniform sampler2D tDepth;
uniform vec2 uTexSize;
uniform vec4 uBounds;
uniform vec3 uSamples[dNSamples];
uniform mat4 uProjection;
uniform mat4 uInvProjection;
uniform vec2 uTexSize;
uniform float uRadius;
uniform float uBias;
@@ -46,8 +46,12 @@ bool isBackground(const in float depth) {
return depth == 1.0;
}
bool outsideBounds(const in vec2 p) {
return p.x < uBounds.x || p.y < uBounds.y || p.x > uBounds.z || p.y > uBounds.w;
}
float getDepth(const in vec2 coords) {
return unpackRGBAToDepth(texture2D(tDepth, coords));
return outsideBounds(coords) ? 1.0 : unpackRGBAToDepth(texture2D(tDepth, coords));
}
vec3 normalFromDepth(const in float depth, const in float depth1, const in float depth2, vec2 offset1, vec2 offset2) {

View File

@@ -8,7 +8,7 @@ import { idFactory } from '../../mol-util/id-factory';
import { createNullTexture, Texture, TextureFilter } from './texture';
import { createNullFramebuffer, Framebuffer } from './framebuffer';
import { WebGLResources } from './resources';
import { GLRenderingContext } from './compat';
import { GLRenderingContext, isWebGL2 } from './compat';
const getNextRenderTargetId = idFactory();
@@ -35,9 +35,11 @@ export function createRenderTarget(gl: GLRenderingContext, resources: WebGLResou
? resources.texture('image-float32', 'rgba', 'float', filter)
: resources.texture('image-uint8', 'rgba', 'ubyte', filter);
// make a depth renderbuffer of the same size as the targetTexture
const depthRenderbuffer = depth
? resources.renderbuffer('depth16', 'depth', _width, _height)
: null;
const depthRenderbuffer = !depth
? null
: isWebGL2(gl)
? resources.renderbuffer('depth32f', 'depth', _width, _height)
: resources.renderbuffer('depth16', 'depth', _width, _height);
function init() {
targetTexture.define(_width, _height);

View File

@@ -1,17 +1,17 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { idFactory } from '../../mol-util/id-factory';
import { GLRenderingContext } from './compat';
import { GLRenderingContext, isWebGL2 } from './compat';
import { Framebuffer, checkFramebufferStatus } from './framebuffer';
import { isDebugMode } from '../../mol-util/debug';
const getNextRenderbufferId = idFactory();
export type RenderbufferFormat = 'depth16' | 'stencil8' | 'rgba4' | 'depth-stencil'
export type RenderbufferFormat = 'depth16' | 'stencil8' | 'rgba4' | 'depth-stencil' | 'depth32f'
export type RenderbufferAttachment = 'depth' | 'stencil' | 'depth-stencil' | 'color0'
export function getFormat(gl: GLRenderingContext, format: RenderbufferFormat) {
@@ -20,6 +20,9 @@ export function getFormat(gl: GLRenderingContext, format: RenderbufferFormat) {
case 'stencil8': return gl.STENCIL_INDEX8;
case 'rgba4': return gl.RGBA4;
case 'depth-stencil': return gl.DEPTH_STENCIL;
case 'depth32f':
if (isWebGL2(gl)) return gl.DEPTH_COMPONENT32F;
else throw new Error('WebGL2 needed for `depth32f` renderbuffer format');
}
}

View File

@@ -94,7 +94,10 @@ export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat,
case 'int': return gl.RGBA32I;
}
case 'depth':
return gl.DEPTH_COMPONENT16;
switch (type) {
case 'ushort': return gl.DEPTH_COMPONENT16;
case 'float': return gl.DEPTH_COMPONENT32F;
}
}
}
return getFormat(gl, format, type);
@@ -229,7 +232,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
(kind.endsWith('float16') && _type !== 'fp16') ||
(kind.endsWith('uint8') && _type !== 'ubyte') ||
(kind.endsWith('int32') && _type !== 'int') ||
(kind.endsWith('depth') && _type !== 'ushort')
(kind.endsWith('depth') && _type !== 'ushort' && _type !== 'float')
) {
throw new Error(`texture kind '${kind}' and type '${_type}' are incompatible`);
}
@@ -280,6 +283,9 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, format, type, data);
} else if (isTexture2d(data, target, gl)) {
const _filter = data.filter ? getFilter(gl, data.filter) : filter;
gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, _filter);
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, _filter);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !!data.flipY);
if (sub) {
gl.texSubImage2D(target, 0, 0, 0, data.width, data.height, format, type, data.array);

View File

@@ -2,7 +2,7 @@
import { parseSdf } from '../sdf/parser';
const SdfString = `
Mrv1718007121815122D
Mrv1718007121815122D
5 4 0 0 0 0 999 V2000
0.0000 0.8250 0.0000 O 0 5 0 0 0 0 0 0 0 0 0 0
@@ -17,7 +17,7 @@ const SdfString = `
M CHG 3 1 -1 3 -1 5 -1
M END
> <DATABASE_ID>
DB14523
0
> <DATABASE_NAME>
drugbank
@@ -112,7 +112,225 @@ Phosphate ion
> <SYNONYMS>
Orthophosphate; Phosphate
$$$$`;
$$$$
Comp 2
5 4 0 0 0 0 999 V2000
0.0000 0.8250 0.0000 O 0 5 0 0 0 0 0 0 0 0 0 0
-0.8250 0.0000 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 -0.8250 0.0000 O 0 5 0 0 0 0 0 0 0 0 0 0
0.0000 0.0000 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
0.8250 0.0000 0.0000 O 0 5 0 0 0 0 0 0 0 0 0 0
4 1 1 0 0 0 0
4 2 2 0 0 0 0
4 3 1 0 0 0 0
4 5 1 0 0 0 0
M CHG 3 1 -1 3 -1 5 -1
M END
> <DATABASE_ID>
1
$$$$
2244
-OEChem-04122119123D
21 21 0 0 0 0 0 0 0999 V2000
1.2333 0.5540 0.7792 O 0 0 0 0 0 0 0 0 0 0 0 0
-0.6952 -2.7148 -0.7502 O 0 0 0 0 0 0 0 0 0 0 0 0
0.7958 -2.1843 0.8685 O 0 0 0 0 0 0 0 0 0 0 0 0
1.7813 0.8105 -1.4821 O 0 0 0 0 0 0 0 0 0 0 0 0
-0.0857 0.6088 0.4403 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.7927 -0.5515 0.1244 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.7288 1.8464 0.4133 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.1426 -0.4741 -0.2184 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.0787 1.9238 0.0706 C 0 0 0 0 0 0 0 0 0 0 0 0
-2.7855 0.7636 -0.2453 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.1409 -1.8536 0.1477 C 0 0 0 0 0 0 0 0 0 0 0 0
2.1094 0.6715 -0.3113 C 0 0 0 0 0 0 0 0 0 0 0 0
3.5305 0.5996 0.1635 C 0 0 0 0 0 0 0 0 0 0 0 0
-0.1851 2.7545 0.6593 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.7247 -1.3605 -0.4564 H 0 0 0 0 0 0 0 0 0 0 0 0
-2.5797 2.8872 0.0506 H 0 0 0 0 0 0 0 0 0 0 0 0
-3.8374 0.8238 -0.5090 H 0 0 0 0 0 0 0 0 0 0 0 0
3.7290 1.4184 0.8593 H 0 0 0 0 0 0 0 0 0 0 0 0
4.2045 0.6969 -0.6924 H 0 0 0 0 0 0 0 0 0 0 0 0
3.7105 -0.3659 0.6426 H 0 0 0 0 0 0 0 0 0 0 0 0
-0.2555 -3.5916 -0.7337 H 0 0 0 0 0 0 0 0 0 0 0 0
1 5 1 0 0 0 0
1 12 1 0 0 0 0
2 11 1 0 0 0 0
2 21 1 0 0 0 0
3 11 2 0 0 0 0
4 12 2 0 0 0 0
5 6 1 0 0 0 0
5 7 2 0 0 0 0
6 8 2 0 0 0 0
6 11 1 0 0 0 0
7 9 1 0 0 0 0
7 14 1 0 0 0 0
8 10 1 0 0 0 0
8 15 1 0 0 0 0
9 10 2 0 0 0 0
9 16 1 0 0 0 0
10 17 1 0 0 0 0
12 13 1 0 0 0 0
13 18 1 0 0 0 0
13 19 1 0 0 0 0
13 20 1 0 0 0 0
M END
> <PUBCHEM_COMPOUND_CID>
2244
> <PUBCHEM_CONFORMER_RMSD>
0.6
> <PUBCHEM_CONFORMER_DIVERSEORDER>
1
11
10
3
15
17
13
5
16
7
14
9
8
4
18
6
12
2
> <PUBCHEM_MMFF94_PARTIAL_CHARGES>
18
1 -0.23
10 -0.15
11 0.63
12 0.66
13 0.06
14 0.15
15 0.15
16 0.15
17 0.15
2 -0.65
21 0.5
3 -0.57
4 -0.57
5 0.08
6 0.09
7 -0.15
8 -0.15
9 -0.15
> <PUBCHEM_EFFECTIVE_ROTOR_COUNT>
3
> <PUBCHEM_PHARMACOPHORE_FEATURES>
5
1 2 acceptor
1 3 acceptor
1 4 acceptor
3 2 3 11 anion
6 5 6 7 8 9 10 rings
> <PUBCHEM_HEAVY_ATOM_COUNT>
13
> <PUBCHEM_ATOM_DEF_STEREO_COUNT>
0
> <PUBCHEM_ATOM_UDEF_STEREO_COUNT>
0
> <PUBCHEM_BOND_DEF_STEREO_COUNT>
0
> <PUBCHEM_BOND_UDEF_STEREO_COUNT>
0
> <PUBCHEM_ISOTOPIC_ATOM_COUNT>
0
> <PUBCHEM_COMPONENT_COUNT>
1
> <PUBCHEM_CACTVS_TAUTO_COUNT>
1
> <PUBCHEM_CONFORMER_ID>
000008C400000001
> <PUBCHEM_MMFF94_ENERGY>
39.5952
> <PUBCHEM_FEATURE_SELFOVERLAP>
25.432
> <PUBCHEM_SHAPE_FINGERPRINT>
1 1 18265615372930943622
100427 49 16967750034970055351
12138202 97 18271247217817981012
12423570 1 16692715976000295083
12524768 44 16753525617747228747
12716758 59 18341332292274886536
13024252 1 17968377969333732145
14181834 199 17830728755827362645
14614273 12 18262232214645093005
15207287 21 17703787037639964108
15775835 57 18340488876329928641
16945 1 18271533103414939405
193761 8 17907860604865584321
20645476 183 17677348215414174190
20871998 184 18198632231250704846
21040471 1 18411412921197846465
21501502 16 18123463883164380929
23402539 116 18271795865171824860
23419403 2 13539898140662769886
23552423 10 18048876295495619569
23559900 14 18272369794190581304
241688 4 16179044415907240795
257057 1 17478316999871287486
2748010 2 18339085878070479087
305870 269 18263645056784260212
528862 383 18117272558388284091
53812653 8 18410289211719108569
7364860 26 17910392788380644719
81228 2 18050568744116491203
> <PUBCHEM_SHAPE_MULTIPOLES>
244.06
3.86
2.45
0.89
1.95
1.58
0.15
-1.85
0.38
-0.61
-0.02
0.29
0.01
-0.33
> <PUBCHEM_SHAPE_SELFOVERLAP>
513.037
> <PUBCHEM_SHAPE_VOLUME>
136
> <PUBCHEM_COORDINATE_TYPE>
2
5
10
$$$$
`;
describe('sdf reader', () => {
it('basic', async () => {
@@ -120,14 +338,21 @@ describe('sdf reader', () => {
if (parsed.isError) {
throw new Error(parsed.message);
}
const compound = parsed.result.compounds[0];
const { molFile, dataItems } = compound;
const compound1 = parsed.result.compounds[0];
const compound2 = parsed.result.compounds[1];
const compound3 = parsed.result.compounds[2];
const { molFile, dataItems } = compound1;
const { atoms, bonds } = molFile;
expect(parsed.result.compounds.length).toBe(3);
// number of structures
expect(atoms.count).toBe(5);
expect(bonds.count).toBe(4);
expect(compound2.molFile.atoms.count).toBe(5);
expect(compound2.molFile.bonds.count).toBe(4);
expect(atoms.x.value(0)).toBeCloseTo(0, 0.001);
expect(atoms.y.value(0)).toBeCloseTo(0.8250, 0.0001);
expect(atoms.z.value(0)).toBeCloseTo(0, 0.0001);
@@ -138,12 +363,21 @@ describe('sdf reader', () => {
expect(bonds.order.value(3)).toBe(1);
expect(dataItems.dataHeader.value(0)).toBe('DATABASE_ID');
expect(dataItems.data.value(0)).toBe('DB14523');
expect(dataItems.data.value(0)).toBe('0');
expect(dataItems.dataHeader.value(1)).toBe('DATABASE_NAME');
expect(dataItems.data.value(1)).toBe('drugbank');
expect(dataItems.dataHeader.value(31)).toBe('SYNONYMS');
expect(dataItems.data.value(31)).toBe('Orthophosphate; Phosphate');
expect(compound1.dataItems.data.value(0)).toBe('0');
expect(compound2.dataItems.data.value(0)).toBe('1');
expect(compound3.dataItems.dataHeader.value(2)).toBe('PUBCHEM_CONFORMER_DIVERSEORDER');
expect(compound3.dataItems.data.value(2)).toBe('1\n11\n10\n3\n15\n17\n13\n5\n16\n7\n14\n9\n8\n4\n18\n6\n12\n2');
expect(compound3.dataItems.dataHeader.value(21)).toBe('PUBCHEM_COORDINATE_TYPE');
expect(compound3.dataItems.data.value(21)).toBe('2\n5\n10');
});
});

View File

@@ -91,12 +91,21 @@ namespace Tokenizer {
return eatLine(state);
}
/** Advance the state by the given number of lines and return line as string. */
/** Advance the state and return line as string. */
export function readLine(state: Tokenizer): string {
markLine(state);
return getTokenString(state);
}
/** Advance the state and return trimmed line as string. */
export function readLineTrim(state: Tokenizer): string {
markLine(state);
const position = state.position;
trim(state, state.tokenStart, state.tokenEnd);
state.position = position;
return getTokenString(state);
}
function readLinesChunk(state: Tokenizer, count: number, tokens: Tokens) {
let read = 0;
for (let i = 0; i < count; i++) {

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Column } from '../../../mol-data/db';
@@ -22,24 +23,36 @@ export interface SdfFile {
}[]
}
const delimiter = '$$$$';
function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, data: Column<string> } {
const dataHeader = TokenBuilder.create(tokenizer.data, 32);
const data = TokenBuilder.create(tokenizer.data, 32);
let sawHeaderToken = false;
while (tokenizer.position < tokenizer.length) {
const line = Tokenizer.readLine(tokenizer);
if (!!line) {
if (line.startsWith('> <')) {
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 3, tokenizer.tokenEnd - 1);
sawHeaderToken = true;
} else if (sawHeaderToken) {
TokenBuilder.add(data, tokenizer.tokenStart, tokenizer.tokenEnd);
sawHeaderToken = false;
// TODO can there be multiline values?
if (line.startsWith(delimiter)) break;
if (!line) continue;
if (line.startsWith('> <')) {
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 3, tokenizer.tokenEnd - 1);
Tokenizer.markLine(tokenizer);
const start = tokenizer.tokenStart;
let end = tokenizer.tokenEnd;
let added = false;
while (tokenizer.position < tokenizer.length) {
const line2 = Tokenizer.readLine(tokenizer);
if (!line2 || line2.startsWith(delimiter) || line2.startsWith('> <')) {
TokenBuilder.add(data, start, end);
added = true;
break;
}
end = tokenizer.tokenEnd;
}
if (!added) {
TokenBuilder.add(data, start, end);
}
} else {
sawHeaderToken = false;
}
}
@@ -49,9 +62,7 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
};
}
function handleMolFile(data: string) {
const tokenizer = Tokenizer(data);
function handleMolFile(tokenizer: Tokenizer) {
const title = Tokenizer.readLine(tokenizer).trim();
const program = Tokenizer.readLine(tokenizer).trim();
const comment = Tokenizer.readLine(tokenizer).trim();
@@ -60,6 +71,15 @@ function handleMolFile(data: string) {
const atomCount = +counts.substr(0, 3), bondCount = +counts.substr(3, 3);
if (Number.isNaN(atomCount) || Number.isNaN(bondCount)) {
// try to skip to next molecule
while (tokenizer.position < tokenizer.length) {
const line = Tokenizer.readLine(tokenizer);
if (line.startsWith(delimiter)) break;
}
return;
}
const atoms = handleAtoms(tokenizer, atomCount);
const bonds = handleBonds(tokenizer, bondCount);
const dataItems = handleDataItems(tokenizer);
@@ -70,10 +90,16 @@ function handleMolFile(data: string) {
};
}
const delimiter = '$$$$';
function parseInternal(data: string): Result<SdfFile> {
const result: SdfFile = { compounds: data.split(delimiter).map(d => handleMolFile(d)) };
return Result.success(result);
const tokenizer = Tokenizer(data);
const compounds: SdfFile['compounds'] = [];
while (tokenizer.position < tokenizer.length) {
const c = handleMolFile(tokenizer);
if (c) compounds.push(c);
}
return Result.success({ compounds });
}
export function parseSdf(data: string) {

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Column } from '../../../mol-data/db';
import { Task } from '../../../mol-task';
import { Tokenizer } from '../common/text/tokenizer';
import { ReaderResult as Result } from '../result';
export interface XyzFile {
readonly molecules: {
readonly comment: string,
readonly count: number,
readonly x: Column<number>,
readonly y: Column<number>,
readonly z: Column<number>,
readonly type_symbol: Column<string>
}[],
}
function handleMolecule(tokenizer: Tokenizer): XyzFile['molecules'][number] {
let count = tokenizer.position >= tokenizer.data.length - 1 ? 0 : +Tokenizer.readLine(tokenizer);
if (isNaN(count)) count = 0;
const comment = Tokenizer.readLine(tokenizer);
const x = new Float64Array(count);
const y = new Float64Array(count);
const z = new Float64Array(count);
const type_symbol = new Array<string>(count);
for (let i = 0; i < count; ++i) {
const line = Tokenizer.readLineTrim(tokenizer);
const fields = line.split(/\s+/g);
type_symbol[i] = fields[0];
x[i] = +fields[1];
y[i] = +fields[2];
z[i] = +fields[3];
}
return {
count,
comment,
x: Column.ofFloatArray(x),
y: Column.ofFloatArray(y),
z: Column.ofFloatArray(z),
type_symbol: Column.ofStringArray(type_symbol)
};
}
function parseInternal(data: string): Result<XyzFile> {
const tokenizer = Tokenizer(data);
const molecules: XyzFile['molecules'] = [];
while (true) {
const mol = handleMolecule(tokenizer);
if (mol.count === 0) break;
molecules.push(mol);
}
const result: XyzFile = { molecules };
return Result.success(result);
}
export function parseXyz(data: string) {
return Task.create<Result<XyzFile>>('Parse Mol', async () => {
return parseInternal(data);
});
}

View File

@@ -111,33 +111,41 @@ namespace Spacegroup {
const _translationRef = Vec3();
const _translationRefSymop = Vec3();
const _translationRefOffset = Vec3();
const _translationSymop = Vec3();
export function setOperatorMatrixRef(spacegroup: Spacegroup, index: number, i: number, j: number, k: number, ref: Vec3, target: Mat4) {
Vec3.set(_ijkVec, i, j, k);
Vec3.floor(_translationRef, ref);
Mat4.copy(target, spacegroup.operators[index]);
Vec3.floor(_translationRefSymop, Vec3.transformMat4(_translationRefSymop, ref, target));
Mat4.getTranslation(_translationSymop, target);
Vec3.sub(_translationSymop, _translationSymop, _translationRefSymop);
Vec3.add(_translationSymop, _translationSymop, _translationRef);
Vec3.add(_translationSymop, _translationSymop, _ijkVec);
Mat4.setTranslation(target, _translationSymop);
Mat4.mul(target, spacegroup.cell.fromFractional, target);
Mat4.mul(target, target, spacegroup.cell.toFractional);
return target;
}
/**
* Get Symmetry operator for transformation around the given
* reference point `ref` in fractional coordinates
*/
export function getSymmetryOperatorRef(spacegroup: Spacegroup, spgrOp: number, i: number, j: number, k: number, ref: Vec3) {
const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
return SymmetryOperator.create(`${spgrOp + 1}_${5 + i}${5 + j}${5 + k}`, operator, { hkl: Vec3.create(i, j, k), spgrOp });
const operator = Mat4.zero();
Vec3.set(_ijkVec, i, j, k);
Vec3.floor(_translationRef, ref);
Mat4.copy(operator, spacegroup.operators[spgrOp]);
Vec3.floor(_translationRefSymop, Vec3.transformMat4(_translationRefSymop, ref, operator));
Mat4.getTranslation(_translationSymop, operator);
Vec3.sub(_translationSymop, _translationSymop, _translationRefSymop);
Vec3.add(_translationSymop, _translationSymop, _translationRef);
Vec3.add(_translationSymop, _translationSymop, _ijkVec);
Mat4.setTranslation(operator, _translationSymop);
Mat4.mul(operator, spacegroup.cell.fromFractional, operator);
Mat4.mul(operator, operator, spacegroup.cell.toFractional);
Vec3.sub(_translationRefOffset, _translationRefSymop, _translationRef);
const _i = i - _translationRefOffset[0];
const _j = j - _translationRefOffset[1];
const _k = k - _translationRefOffset[2];
// const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
return SymmetryOperator.create(`${spgrOp + 1}_${5 + _i}${5 + _j}${5 + _k}`, operator, { hkl: Vec3.create(_i, _j, _k), spgrOp });
}
function getOperatorMatrix(ids: number[]) {

View File

@@ -333,7 +333,7 @@ namespace Mat4 {
return out;
}
export function invert(out: Mat4, a: Mat4) {
export function tryInvert(out: Mat4, a: Mat4) {
const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
@@ -356,8 +356,7 @@ namespace Mat4 {
let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
if (!det) {
console.warn('non-invertible matrix.', a);
return out;
return false;
}
det = 1.0 / det;
@@ -378,6 +377,13 @@ namespace Mat4 {
out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
return true;
}
export function invert(out: Mat4, a: Mat4) {
if (!tryInvert(out, a)) {
console.warn('non-invertible matrix.', a);
}
return out;
}

View File

@@ -590,6 +590,9 @@ namespace Vec3 {
export const unitX: ReadonlyVec3 = create(1, 0, 0);
export const unitY: ReadonlyVec3 = create(0, 1, 0);
export const unitZ: ReadonlyVec3 = create(0, 0, 1);
export const negUnitX: ReadonlyVec3 = create(-1, 0, 0);
export const negUnitY: ReadonlyVec3 = create(0, -1, 0);
export const negUnitZ: ReadonlyVec3 = create(0, 0, -1);
}
export { Vec3 };

View File

@@ -74,7 +74,7 @@ function createHierarchyData(atom_site: AtomSite, sourceIndex: Column<number>, o
let cI = 0;
let seqId = 0;
for (let i = 0, il = seqIds.length; i < il; ++i) {
if (residueOffsets[i] > chainOffsets[cI + 1]) {
if (residueOffsets[i] >= chainOffsets[cI + 1]) {
cI += 1;
seqId = 0;
}

View File

@@ -50,9 +50,16 @@ const StandardComponents = (function() {
{ id: 'ASP', name: 'ASPARTIC ACID', type: 'L-peptide linking' },
{ id: 'GLU', name: 'GLUTAMIC ACID', type: 'L-peptide linking' },
{ id: 'THR', name: 'THREONINE', type: 'L-peptide linking' },
{ id: 'PRO', name: 'PROLINE', type: 'L-peptide linking' },
{ id: 'SEC', name: 'SELENOCYSTEINE', type: 'L-peptide linking' },
{ id: 'PYL', name: 'PYRROLYSINE', type: 'L-peptide linking' },
{ id: 'MSE', name: 'SELENOMETHIONINE', type: 'L-peptide linking' },
{ id: 'SEP', name: 'PHOSPHOSERINE', type: 'L-peptide linking' },
{ id: 'TPO', name: 'PHOSPHOTHREONINE', type: 'L-peptide linking' },
{ id: 'PTR', name: 'O-PHOSPHOTYROSINE', type: 'L-peptide linking' },
{ id: 'PCA', name: 'PYROGLUTAMIC ACID', type: 'L-peptide linking' },
{ id: 'A', name: 'ADENOSINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
{ id: 'C', name: 'CYTIDINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
{ id: 'T', name: 'THYMIDINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },

View File

@@ -0,0 +1,108 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Column, Table } from '../../mol-data/db';
import { XyzFile } from '../../mol-io/reader/xyz/parser';
import { Trajectory } from '../../mol-model/structure';
import { MoleculeType } from '../../mol-model/structure/model/types';
import { RuntimeContext, Task } from '../../mol-task';
import { ModelFormat } from '../format';
import { createModels } from './basic/parser';
import { BasicSchema, createBasic } from './basic/schema';
import { ComponentBuilder } from './common/component';
import { EntityBuilder } from './common/entity';
function getModels(mol: XyzFile, ctx: RuntimeContext) {
const { molecules } = mol;
let count = 0;
for (const m of molecules) count += m.count;
const type_symbols = new Array<string>(count);
const id = new Int32Array(count);
const x = new Float32Array(count);
const y = new Float32Array(count);
const z = new Float32Array(count);
const model_num = new Int32Array(count);
let offset = 0;
for (let i = 0; i < molecules.length; i++) {
const m = molecules[i];
for (let j = 0; j < m.count; j++) {
type_symbols[offset] = m.type_symbol.value(j);
x[offset] = m.x.value(j);
y[offset] = m.y.value(j);
z[offset] = m.z.value(j);
id[offset] = j;
model_num[offset] = i;
offset++;
}
}
const MOL = Column.ofConst('MOL', count, Column.Schema.str);
const A = Column.ofConst('A', count, Column.Schema.str);
const seq_id = Column.ofConst(1, count, Column.Schema.int);
const type_symbol = Column.ofStringArray(type_symbols);
const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
auth_asym_id: A,
auth_atom_id: type_symbol,
auth_comp_id: MOL,
auth_seq_id: seq_id,
Cartn_x: Column.ofFloatArray(x),
Cartn_y: Column.ofFloatArray(y),
Cartn_z: Column.ofFloatArray(z),
id: Column.ofIntArray(id),
label_asym_id: A,
label_atom_id: type_symbol,
label_comp_id: MOL,
label_seq_id: seq_id,
label_entity_id: Column.ofConst('1', count, Column.Schema.str),
occupancy: Column.ofConst(1, count, Column.Schema.float),
type_symbol,
pdbx_PDB_model_num: Column.ofIntArray(model_num),
}, count);
const entityBuilder = new EntityBuilder();
entityBuilder.setNames([['MOL', 'Unknown Entity']]);
entityBuilder.getEntityId('MOL', MoleculeType.Unknown, 'A');
const componentBuilder = new ComponentBuilder(seq_id, type_symbol);
componentBuilder.setNames([['MOL', 'Unknown Molecule']]);
componentBuilder.add('MOL', 0);
const basics = createBasic({
entity: entityBuilder.getEntityTable(),
chem_comp: componentBuilder.getChemCompTable(),
atom_site
});
return createModels(basics, XyzFormat.create(mol), ctx);
}
//
export { XyzFormat };
type XyzFormat = ModelFormat<XyzFile>
namespace XyzFormat {
export function is(x?: ModelFormat): x is XyzFormat {
return x?.kind === 'xyz';
}
export function create(mol: XyzFile): XyzFormat {
return { kind: 'xyz', name: 'xyz', data: mol };
}
}
export function trajectoryFromXyz(mol: XyzFile): Task<Trajectory> {
return Task.create('Parse XYZ', ctx => getModels(mol, ctx));
}

View File

@@ -61,7 +61,7 @@ namespace CustomElementProperty {
type: builder.type || 'dynamic',
defaultParams: {},
getParams: (data: Model) => ({}),
isApplicable: (data: Model) => !!builder.isApplicable?.(data),
isApplicable: (data: Model) => !builder.isApplicable || !!builder.isApplicable(data),
obtain: async (ctx: CustomProperty.Context, data: Model) => {
return await builder.getData(data, ctx);
}

View File

@@ -28,7 +28,7 @@ export const InteractionsProvider: CustomStructureProperty.Provider<Interactions
type: 'local',
defaultParams: InteractionsParams,
getParams: (data: Structure) => InteractionsParams,
isApplicable: (data: Structure) => true,
isApplicable: (data: Structure) => !data.isCoarseGrained,
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<InteractionsProps>) => {
const p = { ...PD.getDefaultValues(InteractionsParams), ...props };
return { value: await computeInteractions(ctx, data, p) };

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -15,7 +15,7 @@ import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../../../mo
import { VisualUpdateState } from '../../../mol-repr/util';
import { PickingId } from '../../../mol-geo/geometry/picking';
import { EmptyLoci, Loci } from '../../../mol-model/loci';
import { Interval, OrderedSet } from '../../../mol-data/int';
import { Interval, OrderedSet, SortedArray } from '../../../mol-data/int';
import { Interactions } from '../interactions/interactions';
import { InteractionsProvider } from '../interactions';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
@@ -35,6 +35,8 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
if (!edgeCount) return Mesh.createEmpty(mesh);
const { child } = structure;
const builderProps = {
linkCount: edgeCount,
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
@@ -63,12 +65,28 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
const sizeB = theme.size.size(l);
return Math.min(sizeA, sizeB) * sizeFactor;
},
ignore: (edgeIndex: number) => edges[edgeIndex].props.flag === InteractionFlag.Filtered
ignore: (edgeIndex: number) => {
if (edges[edgeIndex].props.flag === InteractionFlag.Filtered) return true;
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 fA = unitsFeatures.get(b.unitA);
// TODO: check all members
const eA = unitA.elements[fA.members[fA.offsets[b.indexA]]];
if (!SortedArray.has(childUnitA.elements, eA)) return true;
}
return false;
}
};
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * sizeFactor);
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * sizeFactor);
m.setBoundingSphere(sphere);
return m;
@@ -80,6 +98,7 @@ export const InteractionsInterUnitParams = {
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),
};
export type InteractionsInterUnitParams = typeof InteractionsInterUnitParams

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -7,7 +7,7 @@
import { Unit, Structure, StructureElement } from '../../../mol-model/structure';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { Loci, EmptyLoci } from '../../../mol-model/loci';
import { Interval, OrderedSet } from '../../../mol-data/int';
import { Interval, OrderedSet, SortedArray } from '../../../mol-data/int';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { PickingId } from '../../../mol-geo/geometry/picking';
@@ -25,6 +25,10 @@ import { Sphere3D } from '../../../mol-math/geometry';
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);
const { child } = structure;
const childUnit = child?.unitMap.get(unit.id);
if (child && !childUnit) return Mesh.createEmpty(mesh);
const location = StructureElement.Location.create(structure, unit);
const interactions = InteractionsProvider.get(structure).value!;
@@ -51,12 +55,16 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
const sizeB = theme.size.size(location);
return Math.min(sizeA, sizeB) * sizeFactor;
},
ignore: (edgeIndex: number) => flag[edgeIndex] === InteractionFlag.Filtered
ignore: (edgeIndex: number) => (
flag[edgeIndex] === InteractionFlag.Filtered ||
// TODO: check all members
(!!childUnit && !SortedArray.has(childUnit.elements, unit.elements[members[offsets[a[edgeIndex]]]]))
)
};
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * sizeFactor);
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * sizeFactor);
m.setBoundingSphere(sphere);
return m;
@@ -68,6 +76,7 @@ export const InteractionsIntraUnitParams = {
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),
};
export type InteractionsIntraUnitParams = typeof InteractionsIntraUnitParams
@@ -147,7 +156,7 @@ function eachInteraction(loci: Loci, structureGroup: StructureGroup, apply: (int
const { offset } = contacts;
const { offsets: fOffsets, indices: fIndices } = features.elementsIndex;
// TODO when isMarking, all elements of contact features need to be in the loci
// TODO: when isMarking, all elements of contact features need to be in the loci
for (const e of loci.elements) {
const unitIdx = group.unitIndexMap.get(e.unit.id);
if (unitIdx !== undefined) continue;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -46,9 +46,15 @@ export const InteractionsRepresentationProvider = StructureRepresentationProvide
defaultValues: PD.getDefaultValues(InteractionsParams),
defaultColorTheme: { name: 'interaction-type' },
defaultSizeTheme: { name: 'uniform' },
isApplicable: (structure: Structure) => structure.elementCount > 0,
isApplicable: (structure: Structure) => structure.elementCount > 0 && InteractionsProvider.isApplicable(structure),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, structure: Structure) => InteractionsProvider.attach(ctx, structure, void 0, true),
detach: (data) => InteractionsProvider.ref(data, false)
},
getData: (structure: Structure, props: PD.Values<InteractionsParams>) => {
return props.includeParent ? structure.asParent() : structure;
},
mustRecreate: (oldProps: PD.Values<InteractionsParams>, newProps: PD.Values<InteractionsParams>) => {
return oldProps.includeParent !== newProps.includeParent;
}
});

View File

@@ -69,7 +69,7 @@ async function computeDssp(structure: Structure, props: DSSPComputationProps): P
const map = new Map<number, SecondaryStructure>();
for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
const u = structure.unitSymmetryGroups[i].units[0];
if (Unit.isAtomic(u)) {
if (Unit.isAtomic(u) && !Model.isCoarseGrained(u.model)) {
const secondaryStructure = await computeUnitDSSP(u, props);
map.set(u.invariantId, secondaryStructure);
}

View File

@@ -37,9 +37,10 @@ export interface DataLoci<T = unknown, E = unknown> {
readonly kind: 'data-loci',
readonly tag: string
readonly data: T,
readonly elements: ReadonlyArray<E>
readonly elements: ReadonlyArray<E>,
getBoundingSphere(boundingSphere: Sphere3D): Sphere3D
/** if undefined, won't zoom */
getBoundingSphere?(boundingSphere: Sphere3D): Sphere3D
getLabel(): string
}
export function isDataLoci(x?: Loci): x is DataLoci {
@@ -159,7 +160,7 @@ namespace Loci {
} else if (loci.kind === 'group-loci') {
return ShapeGroup.getBoundingSphere(loci, boundingSphere);
} else if (loci.kind === 'data-loci') {
return loci.getBoundingSphere(boundingSphere);
return loci.getBoundingSphere?.(boundingSphere);
} else if (loci.kind === 'volume-loci') {
return Volume.getBoundingSphere(loci.volume, boundingSphere);
} else if (loci.kind === 'isosurface-loci') {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2021 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>
@@ -29,6 +29,7 @@ import { CustomModelProperty } from '../../../mol-model-props/common/custom-mode
import { Trajectory, ArrayTrajectory } from '../trajectory';
import { Unit } from '../structure';
import { SortedArray } from '../../../mol-data/int/sorted-array';
import { PolymerType } from './types';
/**
* Interface to the "source data" of the molecule.
@@ -224,6 +225,39 @@ export namespace Model {
}
};
const CoarseGrainedProp = '__CoarseGrained__';
/**
* Has typical coarse grained atom names (BB, SC1) or less than three times as many
* atoms as polymer residues (C-alpha only models).
*/
export function isCoarseGrained(model: Model): boolean {
if (model._staticPropertyData[CoarseGrainedProp] !== undefined) return model._staticPropertyData[CoarseGrainedProp];
let polymerResidueCount = 0;
const { polymerType } = model.atomicHierarchy.derived.residue;
for (let i = 0; i < polymerType.length; ++i) {
if (polymerType[i] !== PolymerType.NA) polymerResidueCount += 1;
}
// check for coarse grained atom names
let hasBB = false, hasSC1 = false;
const { label_atom_id, _rowCount: atomCount } = model.atomicHierarchy.atoms;
for (let i = 0; i < atomCount; ++i) {
const atomName = label_atom_id.value(i);
if (!hasBB && atomName === 'BB') hasBB = true;
if (!hasSC1 && atomName === 'SC1') hasSC1 = true;
if (hasBB && hasSC1) break;
}
const coarseGrained = (hasBB && hasSC1) || (
polymerResidueCount && atomCount
? atomCount / polymerResidueCount < 3
: false
);
model._staticPropertyData[CoarseGrainedProp] = coarseGrained;
return coarseGrained;
}
//
export function hasCarbohydrate(model: Model): boolean {

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { StructureElement } from '../../../structure/element';
import { StructureProperties } from '../../../structure/properties';
export interface ResidueSetEntry {
label_asym_id: string,
label_comp_id: string,
label_seq_id: number,
label_alt_id: string,
ins_code: string,
// 1_555 by default
operator_name?: string
}
export class ResidueSet {
private index = new Map<string, Map<number, ResidueSetEntry[]>>();
private checkOperator: boolean = false;
add(entry: ResidueSetEntry) {
let root = this.index.get(entry.label_asym_id);
if (!root) {
root = new Map();
this.index.set(entry.label_asym_id, root);
}
let entries = root.get(entry.label_seq_id);
if (!entries) {
entries = [];
root.set(entry.label_seq_id, entries);
}
const exists = this._find(entry, entries);
if (!exists) {
entries.push(entry);
return true;
}
return false;
}
hasLabelAsymId(asym_id: string) {
return this.index.has(asym_id);
}
has(loc: StructureElement.Location) {
const asym_id = _asym_id(loc);
if (!this.index.has(asym_id)) return;
const root = this.index.get(asym_id)!;
const seq_id = _seq_id(loc);
if (!root.has(seq_id)) return;
const entries = root.get(seq_id)!;
const comp_id = _comp_id(loc);
const alt_id = _alt_id(loc);
const ins_code = _ins_code(loc);
const op_name = _op_name(loc) ?? '1_555';
for (const e of entries) {
if (e.label_comp_id !== comp_id || e.label_alt_id !== alt_id || e.ins_code !== ins_code) continue;
if (this.checkOperator && (e.operator_name ?? '1_555') !== op_name) continue;
return e;
}
}
static getLabel(entry: ResidueSetEntry, checkOperator = false) {
return `${entry.label_asym_id} ${entry.label_comp_id} ${entry.label_seq_id}:${entry.ins_code}:${entry.label_alt_id}${checkOperator ? ' ' + (entry.operator_name ?? '1_555') : ''}`;
}
static getEntryFromLocation(loc: StructureElement.Location): ResidueSetEntry {
return {
label_asym_id: _asym_id(loc),
label_comp_id: _comp_id(loc),
label_seq_id: _seq_id(loc),
label_alt_id: _alt_id(loc),
ins_code: _ins_code(loc),
operator_name: _op_name(loc) ?? '1_555'
};
}
private _find(entry: ResidueSetEntry, xs: ResidueSetEntry[]) {
for (const e of xs) {
if (e.label_comp_id !== entry.label_comp_id || e.label_alt_id !== entry.label_alt_id || e.ins_code !== entry.ins_code) continue;
if (this.checkOperator && (e.operator_name ?? '1_555') !== (entry.operator_name ?? '1_555')) continue;
return true;
}
return false;
}
constructor(options?: { checkOperator?: boolean }) {
this.checkOperator = options?.checkOperator ?? false;
}
}
const _asym_id = StructureProperties.chain.label_asym_id;
const _seq_id = StructureProperties.residue.label_seq_id;
const _comp_id = StructureProperties.atom.label_comp_id;
const _alt_id = StructureProperties.atom.label_alt_id;
const _ins_code = StructureProperties.residue.pdbx_PDB_ins_code;
const _op_name = StructureProperties.unit.operator_name;

View File

@@ -245,7 +245,9 @@ export const WaterNames = new Set([
export const AminoAcidNamesL = new Set([
'HIS', 'ARG', 'LYS', 'ILE', 'PHE', 'LEU', 'TRP', 'ALA', 'MET', 'PRO', 'CYS',
'ASN', 'VAL', 'GLY', 'SER', 'GLN', 'TYR', 'ASP', 'GLU', 'THR', 'SEC', 'PYL',
'UNK' // unknown amino acid from CCD
'UNK', // unknown amino acid from CCD
'MSE', 'SEP', 'TPO', 'PTR', 'PCA' // common
]);
export const AminoAcidNamesD = new Set([
'DAL', // D-ALANINE

View File

@@ -43,7 +43,7 @@ export function atomicSequence(): StructureQuery {
units.push(unit);
}
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
return StructureSelection.Singletons(inputStructure, Structure.create(units, { parent: inputStructure }));
};
}
@@ -62,7 +62,7 @@ export function water(): StructureQuery {
if (P.entity.type(l) !== 'water') continue;
units.push(unit);
}
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
return StructureSelection.Singletons(inputStructure, Structure.create(units, { parent: inputStructure }));
};
}
@@ -92,7 +92,7 @@ export function atomicHet(): StructureQuery {
units.push(unit);
}
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
return StructureSelection.Singletons(inputStructure, Structure.create(units, { parent: inputStructure }));
};
}
@@ -105,7 +105,7 @@ export function spheres(): StructureQuery {
if (unit.kind !== Unit.Kind.Spheres) continue;
units.push(unit);
}
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
return StructureSelection.Singletons(inputStructure, Structure.create(units, { parent: inputStructure }));
};
}

View File

@@ -11,10 +11,14 @@ import { StructureSelection } from '../selection';
import { UniqueStructuresBuilder } from '../utils/builders';
import { StructureUniqueSubsetBuilder } from '../../structure/util/unique-subset-builder';
import { QueryContext, QueryFn } from '../context';
import { structureIntersect, structureSubtract } from '../utils/structure-set';
import { structureIntersect, structureSubtract, structureUnion } from '../utils/structure-set';
import { UniqueArray } from '../../../../mol-data/generic';
import { StructureSubsetBuilder } from '../../structure/util/subset-builder';
import { StructureElement } from '../../structure/element';
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
import { ResidueSet, ResidueSetEntry } from '../../model/properties/utils/residue-set';
import { StructureProperties } from '../../structure/properties';
import { arraySetAdd } from '../../../../mol-util/array';
function getWholeResidues(ctx: QueryContext, source: Structure, structure: Structure) {
const builder = source.subsetBuilder(true);
@@ -306,10 +310,11 @@ export interface IncludeConnectedParams {
query: StructureQuery,
bondTest?: QueryFn<boolean>,
layerCount: number,
wholeResidues: boolean
wholeResidues: boolean,
fixedPoint: boolean
}
export function includeConnected({ query, layerCount, wholeResidues, bondTest }: IncludeConnectedParams): StructureQuery {
export function includeConnected({ query, layerCount, wholeResidues, bondTest, fixedPoint }: IncludeConnectedParams): StructureQuery {
const lc = Math.max(layerCount, 0);
return function query_includeConnected(ctx) {
const builder = StructureSelection.UniqueBuilder(ctx.inputStructure);
@@ -318,8 +323,17 @@ export function includeConnected({ query, layerCount, wholeResidues, bondTest }:
ctx.atomicBond.setTestFn(bondTest);
StructureSelection.forEach(src, (s, sI) => {
let incl = s;
for (let i = 0; i < lc; i++) {
incl = includeConnectedStep(ctx, wholeResidues, incl);
if (fixedPoint) {
while (true) {
const prevCount = incl.elementCount;
incl = includeConnectedStep(ctx, wholeResidues, incl);
if (incl.elementCount === prevCount) break;
}
} else {
for (let i = 0; i < lc; i++) {
incl = includeConnectedStep(ctx, wholeResidues, incl);
}
}
builder.add(incl);
if (sI % 10 === 0) ctx.throwIfTimedOut();
@@ -425,4 +439,252 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
return builder.getStructure();
}
export interface SurroundingLigandsParams {
query: StructureQuery,
radius: number,
includeWater: boolean
}
/**
* Includes expanded surrounding ligands based on radius from the source, struct_conn entries & pdbx_molecule entries.
*/
export function surroundingLigands({ query, radius, includeWater }: SurroundingLigandsParams): StructureQuery {
return function query_surroundingLigands(ctx) {
const inner = StructureSelection.unionStructure(query(ctx));
const surroundings = getWholeResidues(ctx, ctx.inputStructure, getIncludeSurroundings(ctx, ctx.inputStructure, inner, { radius }));
const prd = getPrdAsymIdx(ctx.inputStructure);
const graph = getStructConnInfo(ctx.inputStructure);
const l = StructureElement.Location.create(surroundings);
const includedPrdChains = new Map<string, string[]>();
const componentResidues = new ResidueSet({ checkOperator: true });
for (const unit of surroundings.units) {
if (unit.kind !== Unit.Kind.Atomic) continue;
l.unit = unit;
const { elements } = unit;
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
l.element = elements[chainSegment.start];
const asym_id = StructureProperties.chain.label_asym_id(l);
const op_name = StructureProperties.unit.operator_name(l);
// check for PRD molecules
if (prd.has(asym_id)) {
if (includedPrdChains.has(asym_id)) {
arraySetAdd(includedPrdChains.get(asym_id)!, op_name);
} else {
includedPrdChains.set(asym_id, [op_name]);
}
continue;
}
const entityType = StructureProperties.entity.type(l);
// test entity and chain
if (entityType === 'water' || entityType === 'polymer') continue;
residuesIt.setSegment(chainSegment);
while (residuesIt.hasNext) {
const residueSegment = residuesIt.move();
l.element = elements[residueSegment.start];
graph.addComponent(ResidueSet.getEntryFromLocation(l), componentResidues);
}
}
ctx.throwIfTimedOut();
}
// assemble the core structure
const builder = ctx.inputStructure.subsetBuilder(true);
for (const unit of ctx.inputStructure.units) {
if (unit.kind !== Unit.Kind.Atomic) continue;
l.unit = unit;
const { elements } = unit;
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
builder.beginUnit(unit.id);
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
l.element = elements[chainSegment.start];
const asym_id = StructureProperties.chain.label_asym_id(l);
const op_name = StructureProperties.unit.operator_name(l);
if (includedPrdChains.has(asym_id) && includedPrdChains.get(asym_id)!.indexOf(op_name) >= 0) {
builder.addElementRange(elements, chainSegment.start, chainSegment.end);
continue;
}
if (!componentResidues.hasLabelAsymId(asym_id)) {
continue;
}
residuesIt.setSegment(chainSegment);
while (residuesIt.hasNext) {
const residueSegment = residuesIt.move();
l.element = elements[residueSegment.start];
if (!componentResidues.has(l)) continue;
builder.addElementRange(elements, residueSegment.start, residueSegment.end);
}
}
builder.commitUnit();
ctx.throwIfTimedOut();
}
const components = structureUnion(ctx.inputStructure, [builder.getStructure(), inner]);
// add water
if (includeWater) {
const finalBuilder = new StructureUniqueSubsetBuilder(ctx.inputStructure);
const lookup = ctx.inputStructure.lookup3d;
for (const unit of components.units) {
const { x, y, z } = unit.conformation;
const elements = unit.elements;
for (let i = 0, _i = elements.length; i < _i; i++) {
const e = elements[i];
lookup.findIntoBuilderIf(x(e), y(e), z(e), radius, finalBuilder, testIsWater);
finalBuilder.addToUnit(unit.id, e);
}
ctx.throwIfTimedOut();
}
return StructureSelection.Sequence(ctx.inputStructure, [finalBuilder.getStructure()]);
} else {
return StructureSelection.Sequence(ctx.inputStructure, [components]);
}
};
}
const _entity_type = StructureProperties.entity.type;
function testIsWater(l: StructureElement.Location) {
return _entity_type(l) === 'water';
}
function getPrdAsymIdx(structure: Structure) {
const model = structure.models[0];
const ids = new Set<string>();
if (!MmcifFormat.is(model.sourceData)) return ids;
const { _rowCount, asym_id } = model.sourceData.data.db.pdbx_molecule;
for (let i = 0; i < _rowCount; i++) {
ids.add(asym_id.value(i));
}
return ids;
}
function getStructConnInfo(structure: Structure) {
const model = structure.models[0];
const graph = new StructConnGraph();
if (!MmcifFormat.is(model.sourceData)) return graph;
const struct_conn = model.sourceData.data.db.struct_conn;
const { conn_type_id } = struct_conn;
const { ptnr1_label_asym_id, ptnr1_label_comp_id, ptnr1_label_seq_id, ptnr1_symmetry, pdbx_ptnr1_label_alt_id, pdbx_ptnr1_PDB_ins_code } = struct_conn;
const { ptnr2_label_asym_id, ptnr2_label_comp_id, ptnr2_label_seq_id, ptnr2_symmetry, pdbx_ptnr2_label_alt_id, pdbx_ptnr2_PDB_ins_code } = struct_conn;
for (let i = 0; i < struct_conn._rowCount; i++) {
const bondType = conn_type_id.value(i);
if (bondType !== 'covale' && bondType !== 'metalc') continue;
const a: ResidueSetEntry = {
label_asym_id: ptnr1_label_asym_id.value(i),
label_comp_id: ptnr1_label_comp_id.value(i),
label_seq_id: ptnr1_label_seq_id.value(i),
label_alt_id: pdbx_ptnr1_label_alt_id.value(i),
ins_code: pdbx_ptnr1_PDB_ins_code.value(i),
operator_name: ptnr1_symmetry.value(i) ?? '1_555'
};
const b: ResidueSetEntry = {
label_asym_id: ptnr2_label_asym_id.value(i),
label_comp_id: ptnr2_label_comp_id.value(i),
label_seq_id: ptnr2_label_seq_id.value(i),
label_alt_id: pdbx_ptnr2_label_alt_id.value(i),
ins_code: pdbx_ptnr2_PDB_ins_code.value(i),
operator_name: ptnr2_symmetry.value(i) ?? '1_555'
};
graph.addEdge(a, b);
}
return graph;
}
class StructConnGraph {
vertices = new Map<string, ResidueSetEntry>();
edges = new Map<string, string[]>();
private addVertex(e: ResidueSetEntry, label: string) {
if (this.vertices.has(label)) return;
this.vertices.set(label, e);
this.edges.set(label, []);
}
addEdge(a: ResidueSetEntry, b: ResidueSetEntry) {
const al = ResidueSet.getLabel(a);
const bl = ResidueSet.getLabel(b);
this.addVertex(a, al);
this.addVertex(b, bl);
arraySetAdd(this.edges.get(al)!, bl);
arraySetAdd(this.edges.get(bl)!, al);
}
addComponent(start: ResidueSetEntry, set: ResidueSet) {
const startLabel = ResidueSet.getLabel(start);
if (!this.vertices.has(startLabel)) {
set.add(start);
return;
}
const visited = new Set<string>();
const added = new Set<string>();
const stack = [startLabel];
added.add(startLabel);
set.add(start);
while (stack.length > 0) {
const a = stack.pop()!;
visited.add(a);
const u = this.vertices.get(a)!;
for (const b of this.edges.get(a)!) {
if (visited.has(b)) continue;
stack.push(b);
if (added.has(b)) continue;
added.add(b);
const v = this.vertices.get(b)!;
if (u.operator_name === v.operator_name) {
set.add({ ...v, operator_name: start.operator_name });
} else {
set.add(v);
}
}
}
}
}
// TODO: unionBy (skip this one?), cluster

View File

@@ -23,7 +23,7 @@ export function checkStructureMaxRadiusDistance(ctx: QueryContext, a: Structure,
}
namespace MinMaxDist {
const enum Result {
export const enum Result {
BelowMin,
WithinMax,
Miss

View File

@@ -23,6 +23,9 @@ import { StructureProperties } from '../properties';
import { BoundaryHelper } from '../../../../mol-math/geometry/boundary-helper';
import { Boundary } from '../../../../mol-math/geometry/boundary';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const osSize = OrderedSet.size;
/** Represents multiple structure element index locations */
export interface Loci {
readonly kind: 'element-loci',
@@ -71,7 +74,7 @@ export namespace Loci {
export function size(loci: Loci) {
let s = 0;
for (const u of loci.elements) s += OrderedSet.size(u.indices);
for (const u of loci.elements) s += osSize(u.indices);
return s;
}

View File

@@ -98,12 +98,12 @@ const residue = {
microheterogeneityCompIds: p(microheterogeneityCompIds),
secondary_structure_type: p(l => {
if (!Unit.isAtomic(l.unit)) notAtomic();
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.id);
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
return secStruc?.type[l.unit.residueIndex[l.element]] ?? SecondaryStructureType.Flag.NA;
}),
secondary_structure_key: p(l => {
if (!Unit.isAtomic(l.unit)) notAtomic();
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.id);
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
return secStruc?.key[l.unit.residueIndex[l.element]] ?? -1;
}),
chem_comp_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.type),

View File

@@ -23,7 +23,7 @@ import { Carbohydrates } from './carbohydrates/data';
import { computeCarbohydrates } from './carbohydrates/compute';
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
import { idFactory } from '../../../mol-util/id-factory';
import { Box3D, GridLookup3D } from '../../../mol-math/geometry';
import { GridLookup3D } from '../../../mol-math/geometry';
import { UUID } from '../../../mol-util';
import { CustomProperties } from '../../custom-property';
import { AtomicHierarchy } from '../model/properties/atomic';
@@ -35,90 +35,73 @@ import { Trajectory } from '../trajectory';
import { RuntimeContext, Task } from '../../../mol-task';
import { computeStructureBoundary } from './util/boundary';
/** Internal structure state */
type State = {
parent?: Structure,
boundary?: Boundary,
lookup3d?: StructureLookup3D,
interUnitBonds?: InterUnitBonds,
unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
unitSymmetryGroupsIndexMap?: IntMap<number>,
unitsSortedByVolume?: ReadonlyArray<Unit>;
carbohydrates?: Carbohydrates,
models?: ReadonlyArray<Model>,
model?: Model,
masterModel?: Model,
representativeModel?: Model,
uniqueResidueNames?: Set<string>,
uniqueElementSymbols?: Set<ElementSymbol>,
entityIndices?: ReadonlyArray<EntityIndex>,
uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
serialMapping?: SerialMapping,
hashCode: number,
transformHash: number,
elementCount: number,
bondCount: number,
uniqueElementCount: number,
atomicResidueCount: number,
polymerResidueCount: number,
polymerGapCount: number,
polymerUnitCount: number,
coordinateSystem: SymmetryOperator,
label: string,
propertyData?: any,
customProps?: CustomProperties
}
class Structure {
/** Maps unit.id to unit */
readonly unitMap: IntMap<Unit>;
/** Maps unit.id to index of unit in units array */
readonly unitIndexMap: IntMap<number>;
/** Array of all units in the structure, sorted by unit.id */
readonly units: ReadonlyArray<Unit>;
private _props: {
parent?: Structure,
boundary?: Boundary,
lookup3d?: StructureLookup3D,
interUnitBonds?: InterUnitBonds,
unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
unitSymmetryGroupsIndexMap?: IntMap<number>,
unitsSortedByVolume?: ReadonlyArray<Unit>;
carbohydrates?: Carbohydrates,
models?: ReadonlyArray<Model>,
model?: Model,
masterModel?: Model,
representativeModel?: Model,
uniqueResidueNames?: Set<string>,
uniqueElementSymbols?: Set<ElementSymbol>,
entityIndices?: ReadonlyArray<EntityIndex>,
uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
serialMapping?: SerialMapping,
hashCode: number,
/** Hash based on all unit.id values in the structure, reflecting the units transformation */
transformHash: number,
elementCount: number,
bondCount: number,
uniqueElementCount: number,
atomicResidueCount: number,
polymerResidueCount: number,
polymerUnitCount: number,
coordinateSystem: SymmetryOperator,
label: string,
propertyData?: any,
customProps?: CustomProperties
} = {
hashCode: -1,
transformHash: -1,
elementCount: -1,
bondCount: -1,
uniqueElementCount: -1,
atomicResidueCount: -1,
polymerResidueCount: -1,
polymerUnitCount: -1,
coordinateSystem: SymmetryOperator.Default,
label: ''
};
subsetBuilder(isSorted: boolean) {
return new StructureSubsetBuilder(this, isSorted);
}
/** Count of all elements in the structure, i.e. the sum of the elements in the units */
get elementCount() {
return this._props.elementCount;
return this.state.elementCount;
}
/** Count of all bonds (intra- and inter-unit) in the structure */
get bondCount() {
if (this._props.bondCount === -1) {
this._props.bondCount = this.interUnitBonds.edgeCount + Bond.getIntraUnitBondCount(this);
if (this.state.bondCount === -1) {
this.state.bondCount = this.interUnitBonds.edgeCount + Bond.getIntraUnitBondCount(this);
}
return this._props.bondCount;
return this.state.bondCount;
}
get hasCustomProperties() {
return !!this._props.customProps && this._props.customProps.all.length > 0;
return !!this.state.customProps && this.state.customProps.all.length > 0;
}
get customPropertyDescriptors() {
if (!this._props.customProps) this._props.customProps = new CustomProperties();
return this._props.customProps;
if (!this.state.customProps) this.state.customProps = new CustomProperties();
return this.state.customProps;
}
/**
* Property data unique to this instance of the structure.
*/
get currentPropertyData() {
if (!this._props.propertyData) this._props.propertyData = Object.create(null);
return this._props.propertyData;
if (!this.state.propertyData) this.state.propertyData = Object.create(null);
return this.state.propertyData;
}
/**
@@ -130,41 +113,47 @@ class Structure {
/** Count of all polymer residues in the structure */
get polymerResidueCount() {
if (this._props.polymerResidueCount === -1) {
this._props.polymerResidueCount = getPolymerResidueCount(this);
if (this.state.polymerResidueCount === -1) {
this.state.polymerResidueCount = getPolymerResidueCount(this);
}
return this._props.polymerResidueCount;
return this.state.polymerResidueCount;
}
/** Count of all polymer gaps in the structure */
get polymerGapCount() {
if (this.state.polymerGapCount === -1) {
this.state.polymerGapCount = getPolymerGapCount(this);
}
return this.state.polymerGapCount;
}
get polymerUnitCount() {
if (this._props.polymerUnitCount === -1) {
this._props.polymerUnitCount = getPolymerUnitCount(this);
if (this.state.polymerUnitCount === -1) {
this.state.polymerUnitCount = getPolymerUnitCount(this);
}
return this._props.polymerUnitCount;
return this.state.polymerUnitCount;
}
get uniqueElementCount() {
if (this._props.uniqueElementCount === -1) {
this._props.uniqueElementCount = getUniqueElementCount(this);
if (this.state.uniqueElementCount === -1) {
this.state.uniqueElementCount = getUniqueElementCount(this);
}
return this._props.uniqueElementCount;
return this.state.uniqueElementCount;
}
get atomicResidueCount() {
if (this._props.atomicResidueCount === -1) {
this._props.atomicResidueCount = getAtomicResidueCount(this);
if (this.state.atomicResidueCount === -1) {
this.state.atomicResidueCount = getAtomicResidueCount(this);
}
return this._props.atomicResidueCount;
return this.state.atomicResidueCount;
}
/**
* Coarse-grained structure, defined as containing less than
* twice as many elements as polymer residues
* True if any model the structure is based on is coarse grained.
* @see Model.isCoarseGrained
*/
get isCoarseGrained() {
const ec = this.elementCount;
const prc = this.polymerResidueCount;
return prc && ec ? ec / prc < 2 : false;
return this.models.some(m => Model.isCoarseGrained(m));
}
get isEmpty() {
@@ -172,15 +161,15 @@ class Structure {
}
get hashCode() {
if (this._props.hashCode !== -1) return this._props.hashCode;
if (this.state.hashCode !== -1) return this.state.hashCode;
return this.computeHash();
}
/** Hash based on all unit.id values in the structure, reflecting the units transformation */
get transformHash() {
if (this._props.transformHash !== -1) return this._props.transformHash;
this._props.transformHash = hashFnv32a(this.units.map(u => u.id));
return this._props.transformHash;
if (this.state.transformHash !== -1) return this.state.transformHash;
this.state.transformHash = hashFnv32a(this.units.map(u => u.id));
return this.state.transformHash;
}
private computeHash() {
@@ -193,7 +182,7 @@ class Structure {
hash = (31 * hash + this.elementCount) | 0;
hash = hash1(hash);
if (hash === -1) hash = 0;
this._props.hashCode = hash;
this.state.hashCode = hash;
return hash;
}
@@ -204,12 +193,12 @@ class Structure {
/** The parent or itself in case this is the root */
get root() {
return this._props.parent || this;
return this.state.parent || this;
}
/** The root/top-most parent or `undefined` in case this is the root */
get parent() {
return this._props.parent;
return this.state.parent;
}
/**
@@ -220,86 +209,79 @@ class Structure {
* by the consumer.
*/
get coordinateSystem(): SymmetryOperator {
return this._props.coordinateSystem;
return this.state.coordinateSystem;
}
get label() {
return this._props.label;
return this.state.label;
}
get boundary() {
if (this._props.boundary) return this._props.boundary;
this._props.boundary = computeStructureBoundary(this);
return this._props.boundary;
if (this.state.boundary) return this.state.boundary;
this.state.boundary = computeStructureBoundary(this);
return this.state.boundary;
}
get lookup3d() {
if (this._props.lookup3d) return this._props.lookup3d;
this._props.lookup3d = new StructureLookup3D(this);
return this._props.lookup3d;
if (this.state.lookup3d) return this.state.lookup3d;
this.state.lookup3d = new StructureLookup3D(this);
return this.state.lookup3d;
}
get interUnitBonds() {
if (this._props.interUnitBonds) return this._props.interUnitBonds;
this._props.interUnitBonds = computeInterUnitBonds(this);
return this._props.interUnitBonds;
if (this.state.interUnitBonds) return this.state.interUnitBonds;
this.state.interUnitBonds = computeInterUnitBonds(this);
return this.state.interUnitBonds;
}
get unitSymmetryGroups(): ReadonlyArray<Unit.SymmetryGroup> {
if (this._props.unitSymmetryGroups) return this._props.unitSymmetryGroups;
this._props.unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this);
return this._props.unitSymmetryGroups;
if (this.state.unitSymmetryGroups) return this.state.unitSymmetryGroups;
this.state.unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this);
return this.state.unitSymmetryGroups;
}
/** Maps unit.id to index of SymmetryGroup in unitSymmetryGroups array */
get unitSymmetryGroupsIndexMap(): IntMap<number> {
if (this._props.unitSymmetryGroupsIndexMap) return this._props.unitSymmetryGroupsIndexMap;
this._props.unitSymmetryGroupsIndexMap = Unit.SymmetryGroup.getUnitSymmetryGroupsIndexMap(this.unitSymmetryGroups);
return this._props.unitSymmetryGroupsIndexMap;
}
/** Array of all units in the structure, sorted by their boundary volume */
get unitsSortedByVolume(): ReadonlyArray<Unit> {
if (this._props.unitsSortedByVolume) return this._props.unitsSortedByVolume;
this._props.unitsSortedByVolume = getUnitsSortedByVolume(this);
return this._props.unitsSortedByVolume;
if (this.state.unitSymmetryGroupsIndexMap) return this.state.unitSymmetryGroupsIndexMap;
this.state.unitSymmetryGroupsIndexMap = Unit.SymmetryGroup.getUnitSymmetryGroupsIndexMap(this.unitSymmetryGroups);
return this.state.unitSymmetryGroupsIndexMap;
}
get carbohydrates(): Carbohydrates {
if (this._props.carbohydrates) return this._props.carbohydrates;
this._props.carbohydrates = computeCarbohydrates(this);
return this._props.carbohydrates;
if (this.state.carbohydrates) return this.state.carbohydrates;
this.state.carbohydrates = computeCarbohydrates(this);
return this.state.carbohydrates;
}
get models(): ReadonlyArray<Model> {
if (this._props.models) return this._props.models;
this._props.models = getModels(this);
return this._props.models;
if (this.state.models) return this.state.models;
this.state.models = getModels(this);
return this.state.models;
}
get uniqueResidueNames() {
return this._props.uniqueResidueNames
|| (this._props.uniqueResidueNames = getUniqueResidueNames(this));
return this.state.uniqueResidueNames
|| (this.state.uniqueResidueNames = getUniqueResidueNames(this));
}
get uniqueElementSymbols() {
return this._props.uniqueElementSymbols
|| (this._props.uniqueElementSymbols = getUniqueElementSymbols(this));
return this.state.uniqueElementSymbols
|| (this.state.uniqueElementSymbols = getUniqueElementSymbols(this));
}
get entityIndices() {
return this._props.entityIndices
|| (this._props.entityIndices = getEntityIndices(this));
return this.state.entityIndices
|| (this.state.entityIndices = getEntityIndices(this));
}
get uniqueAtomicResidueIndices() {
return this._props.uniqueAtomicResidueIndices
|| (this._props.uniqueAtomicResidueIndices = getUniqueAtomicResidueIndices(this));
return this.state.uniqueAtomicResidueIndices
|| (this.state.uniqueAtomicResidueIndices = getUniqueAtomicResidueIndices(this));
}
/** Contains only atomic units */
get isAtomic() {
for (const u of this.units) if (Unit.isAtomic(u)) return false;
for (const u of this.units) if (!Unit.isAtomic(u)) return false;
return true;
}
@@ -311,7 +293,7 @@ class Structure {
/** Contains only coarse units */
get isCoarse() {
for (const u of this.units) if (Unit.isCoarse(u)) return false;
for (const u of this.units) if (!Unit.isCoarse(u)) return false;
return true;
}
@@ -329,7 +311,7 @@ class Structure {
* to address elements in a structure.
*/
get serialMapping() {
return this._props.serialMapping || (this._props.serialMapping = getSerialMapping(this));
return this.state.serialMapping || (this.state.serialMapping = getSerialMapping(this));
}
/**
@@ -337,25 +319,25 @@ class Structure {
* Otherwise throw an exception.
*/
get model(): Model {
if (this._props.model) return this._props.model;
if (this._props.representativeModel) return this._props.representativeModel;
if (this._props.masterModel) return this._props.masterModel;
if (this.state.model) return this.state.model;
if (this.state.representativeModel) return this.state.representativeModel;
if (this.state.masterModel) return this.state.masterModel;
const models = this.models;
if (models.length > 1) {
throw new Error('The structure is based on multiple models and has neither a master- nor a representative-model.');
}
this._props.model = models[0];
return this._props.model;
this.state.model = models[0];
return this.state.model;
}
/** The master-model, other models can have bonds to it */
get masterModel(): Model | undefined {
return this._props.masterModel;
return this.state.masterModel;
}
/** A representative model, e.g. the first model of a trajectory */
get representativeModel(): Model | undefined {
return this._props.representativeModel;
return this.state.representativeModel;
}
hasElement(e: StructureElement.Location) {
@@ -379,51 +361,39 @@ class Structure {
}
return Structure.create(units, {
label: this.label,
interUnitBonds: this._props.interUnitBonds,
interUnitBonds: this.state.interUnitBonds,
});
}
private initUnits(units: ArrayLike<Unit>) {
const unitMap = IntMap.Mutable<Unit>();
const unitIndexMap = IntMap.Mutable<number>();
let elementCount = 0;
let isSorted = true;
let lastId = units.length > 0 ? units[0].id : 0;
for (let i = 0, _i = units.length; i < _i; i++) {
const u = units[i];
unitMap.set(u.id, u);
elementCount += u.elements.length;
if (u.id < lastId) isSorted = false;
lastId = u.id;
}
if (!isSorted) sort(units, 0, units.length, cmpUnits, arraySwap);
for (let i = 0, _i = units.length; i < _i; i++) {
unitIndexMap.set(units[i].id, i);
}
this._props.elementCount = elementCount;
return { unitMap, unitIndexMap };
private _child: Structure | undefined;
private _target: Structure | undefined;
/**
* For `structure` with `parent` this returns a proxy that
* targets `parent` and has `structure` attached as a child.
*/
asParent(): Structure {
return this.parent ? new Structure(this.parent.units, this.parent.unitMap, this.parent.unitIndexMap, this.parent.state, { child: this, target: this.parent }) : this;
}
constructor(units: ArrayLike<Unit>, props: Structure.Props = {}) {
const { unitMap, unitIndexMap } = this.initUnits(units);
this.unitMap = unitMap;
this.unitIndexMap = unitIndexMap;
this.units = units as ReadonlyArray<Unit>;
get child(): Structure | undefined {
return this._child;
}
if (props.parent) this._props.parent = props.parent.parent || props.parent;
if (props.interUnitBonds) this._props.interUnitBonds = props.interUnitBonds;
/** Get the proxy target. Usefull for equality checks. */
get target(): Structure {
return this._target ?? this;
}
if (props.coordinateSystem) this._props.coordinateSystem = props.coordinateSystem;
else if (props.parent) this._props.coordinateSystem = props.parent.coordinateSystem;
if (props.label) this._props.label = props.label;
else if (props.parent) this._props.label = props.parent.label;
if (props.masterModel) this._props.masterModel = props.masterModel;
else if (props.parent) this._props.masterModel = props.parent.masterModel;
if (props.representativeModel) this._props.representativeModel = props.representativeModel;
else if (props.parent) this._props.representativeModel = props.parent.representativeModel;
/**
* @param units Array of all units in the structure, sorted by unit.id
* @param unitMap Maps unit.id to index of unit in units array
* @param unitIndexMap Array of all units in the structure, sorted by unit.id
*/
constructor(readonly units: ReadonlyArray<Unit>, readonly unitMap: IntMap<Unit>, readonly unitIndexMap: IntMap<number>, private readonly state: State, asParent?: { child: Structure, target: Structure }) {
// always assign to ensure object shape
this._child = asParent?.child;
this._target = asParent?.target;
}
}
@@ -431,24 +401,6 @@ function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) {
return units[i].id - units[j].id;
}
function cmpUnitGroupVolume(units: ArrayLike<[index: number, volume: number]>, i: number, j: number) {
const d = units[i][1] - units[j][1];
if (d === 0) return units[i][0] - units[j][0];
return d;
}
function getUnitsSortedByVolume(structure: Structure) {
const { unitSymmetryGroups } = structure;
const groups = unitSymmetryGroups.map((g, i) => [i, Box3D.volume(g.units[0].lookup3d.boundary.box)] as [number, number]);
sort(groups, 0, groups.length, cmpUnitGroupVolume, arraySwap);
const ret: Unit[] = [];
for (const [i] of groups) {
for (const u of unitSymmetryGroups[i].units) {
ret.push(u);
}
}
return ret;
}
function getModels(s: Structure) {
const { units } = s;
@@ -570,6 +522,15 @@ function getPolymerResidueCount(structure: Structure): number {
return polymerResidueCount;
}
function getPolymerGapCount(structure: Structure): number {
const { units } = structure;
let polymerGapCount = 0;
for (let i = 0, _i = units.length; i < _i; i++) {
polymerGapCount += units[i].gapElements.length / 2;
}
return polymerGapCount;
}
function getPolymerUnitCount(structure: Structure): number {
const { units } = structure;
let polymerUnitCount = 0;
@@ -634,7 +595,7 @@ function getSerialMapping(structure: Structure): SerialMapping {
}
namespace Structure {
export const Empty = new Structure([]);
export const Empty = create([]);
export interface Props {
parent?: Structure
@@ -647,7 +608,7 @@ namespace Structure {
representativeModel?: Model
}
/** Serial index of an element in the structure accross all units */
/** Serial index of an element in the structure across all units */
export type SerialIndex = { readonly '@type': 'serial-index' } & number
/** Represents a single structure */
@@ -688,8 +649,57 @@ namespace Structure {
return Loci(structure);
}
export function create(units: ReadonlyArray<Unit>, props?: Props): Structure {
return new Structure(units, props);
export function create(units: ReadonlyArray<Unit>, props: Props = {}): Structure {
// init units
const unitMap = IntMap.Mutable<Unit>();
const unitIndexMap = IntMap.Mutable<number>();
let elementCount = 0;
let isSorted = true;
let lastId = units.length > 0 ? units[0].id : 0;
for (let i = 0, _i = units.length; i < _i; i++) {
const u = units[i];
unitMap.set(u.id, u);
elementCount += u.elements.length;
if (u.id < lastId) isSorted = false;
lastId = u.id;
}
if (!isSorted) sort(units, 0, units.length, cmpUnits, arraySwap);
for (let i = 0, _i = units.length; i < _i; i++) {
unitIndexMap.set(units[i].id, i);
}
// initial state
const state: State = {
hashCode: -1,
transformHash: -1,
elementCount,
bondCount: -1,
uniqueElementCount: -1,
atomicResidueCount: -1,
polymerResidueCount: -1,
polymerGapCount: -1,
polymerUnitCount: -1,
coordinateSystem: SymmetryOperator.Default,
label: ''
};
// handle props
if (props.parent) state.parent = props.parent.parent || props.parent;
if (props.interUnitBonds) state.interUnitBonds = props.interUnitBonds;
if (props.coordinateSystem) state.coordinateSystem = props.coordinateSystem;
else if (props.parent) state.coordinateSystem = props.parent.coordinateSystem;
if (props.label) state.label = props.label;
else if (props.parent) state.label = props.parent.label;
if (props.masterModel) state.masterModel = props.masterModel;
else if (props.parent) state.masterModel = props.parent.masterModel;
if (props.representativeModel) state.representativeModel = props.representativeModel;
else if (props.parent) state.representativeModel = props.parent.representativeModel;
return new Structure(units, unitMap, unitIndexMap, state);
}
export async function ofTrajectory(trajectory: Trajectory, ctx: RuntimeContext): Promise<Structure> {
@@ -895,7 +905,7 @@ namespace Structure {
const cs = s.coordinateSystem;
const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs), cs);
return new Structure(units, { parent: s, coordinateSystem: newCS });
return create(units, { parent: s, coordinateSystem: newCS });
}
export class StructureBuilder {
@@ -1157,6 +1167,57 @@ namespace Structure {
}
}
export interface ForEachAtomicHierarchyElementParams {
// Called for 1st element of each chain
// Note that chains can be split, meaning each chain would be called multiple times.
chain?: (e: StructureElement.Location<Unit.Atomic>) => void,
// Called for 1st element of each residue
residue?: (e: StructureElement.Location<Unit.Atomic>) => void,
// Called for each element
atom?: (e: StructureElement.Location<Unit.Atomic>) => void,
};
export function eachAtomicHierarchyElement(structure: Structure, { chain, residue, atom }: ForEachAtomicHierarchyElementParams) {
const l = StructureElement.Location.create<Unit.Atomic>(structure);
for (const unit of structure.units) {
if (unit.kind !== Unit.Kind.Atomic) continue;
l.unit = unit;
const { elements } = unit;
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
if (chain) {
l.element = elements[chainSegment.start];
chain(l);
}
if (!residue && !atom) continue;
residuesIt.setSegment(chainSegment);
while (residuesIt.hasNext) {
const residueSegment = residuesIt.move();
if (residue) {
l.element = elements[residueSegment.start];
residue(l);
}
if (!atom) continue;
for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
l.element = elements[j];
atom(l);
}
}
}
}
}
//
export const DefaultSizeThresholds = {

View File

@@ -153,7 +153,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
? thresholdAB
: beI < 0
? thresholdA
: (thresholdA + getElementThreshold(beI)) / 2; // not sure if avg or min but max is too big
: (thresholdA + getElementThreshold(beI)) / 1.95; // not sure if avg or min but max is too big
if (dist <= pairingThreshold) {
const atomIdB = label_atom_idB.value(bI);
@@ -176,7 +176,7 @@ export interface InterBondComputationProps extends BondComputationProps {
function findBonds(structure: Structure, props: InterBondComputationProps) {
const builder = new InterUnitGraph.Builder<number, StructureElement.UnitIndex, InterUnitEdgeProps>();
if (props.noCompute) {
if (props.noCompute || structure.isCoarseGrained) {
// TODO add function that only adds bonds defined in structConn and avoids using
// structure.lookup and unit.lookup (expensive for large structure and not
// needed for archival files or files with an MD topology)

View File

@@ -19,6 +19,7 @@ import { StructConn } from '../../../../../mol-model-formats/structure/property/
import { Vec3 } from '../../../../../mol-math/linear-algebra';
import { ElementIndex } from '../../../model/indexing';
import { equalEps } from '../../../../../mol-math/linear-algebra/3d/common';
import { Model } from '../../../model/model';
function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], atomCount: number, canRemap: boolean): IntraUnitBonds {
const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB);
@@ -211,7 +212,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
? thresholdAB
: beI < 0
? thresholdA
: (thresholdA + getElementThreshold(beI)) / 2; // not sure if avg or min but max is too big
: (thresholdA + getElementThreshold(beI)) / 1.95; // not sure if avg or min but max is too big
if (dist <= pairingThreshold) {
atomA[atomA.length] = _aI;
@@ -233,7 +234,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
function computeIntraUnitBonds(unit: Unit.Atomic, props?: Partial<BondComputationProps>) {
const p = { ...DefaultBondComputationProps, ...props };
if (p.noCompute) {
if (p.noCompute || Model.isCoarseGrained(unit.model)) {
// TODO add function that only adds bonds defined in structConn of chemCompBond
// and avoid using unit.lookup
return IntraUnitBonds.Empty;

View File

@@ -94,6 +94,36 @@ export class StructureLookup3D {
}
}
findIntoBuilderIf(x: number, y: number, z: number, radius: number, builder: StructureUniqueSubsetBuilder, test: (l: StructureElement.Location) => boolean) {
const { units } = this.structure;
const closeUnits = this.unitLookup.find(x, y, z, radius);
if (closeUnits.count === 0) return;
const loc = StructureElement.Location.create(this.structure);
for (let t = 0, _t = closeUnits.count; t < _t; t++) {
const unit = units[closeUnits.indices[t]];
Vec3.set(this.pivot, x, y, z);
if (!unit.conformation.operator.isIdentity) {
Vec3.transformMat4(this.pivot, this.pivot, unit.conformation.operator.inverse);
}
const unitLookup = unit.lookup3d;
const groupResult = unitLookup.find(this.pivot[0], this.pivot[1], this.pivot[2], radius);
if (groupResult.count === 0) continue;
const elements = unit.elements;
loc.unit = unit;
builder.beginUnit(unit.id);
for (let j = 0, _j = groupResult.count; j < _j; j++) {
loc.element = elements[groupResult.indices[j]];
if (test(loc)) {
builder.addElement(loc.element);
}
}
builder.commitUnit();
}
}
findIntoBuilderWithRadius(x: number, y: number, z: number, pivotR: number, maxRadius: number, radius: number, eRadius: StructureElement.Property<number>, builder: StructureUniqueSubsetBuilder) {
const { units } = this.structure;
const closeUnits = this.unitLookup.find(x, y, z, radius);

View File

@@ -18,7 +18,7 @@ export const AnimateModelIndex = PluginStateAnimation.create({
params: () => ({
mode: PD.MappedStatic('loop', {
palindrome: PD.Group({ }),
loop: PD.Group({ }),
loop: PD.Group({ direction: PD.Select('forward', [['forward', 'Forward'], ['backward', 'Backward']]) }),
once: PD.Group({ direction: PD.Select('forward', [['forward', 'Forward'], ['backward', 'Backward']]) }, { isFlat: true })
}, { options: [['palindrome', 'Palindrome'], ['loop', 'Loop'], ['once', 'Once']] }),
duration: PD.MappedStatic('fixed', {
@@ -125,8 +125,11 @@ export const AnimateModelIndex = PluginStateAnimation.create({
: Math.ceil(1000 * traj.data.frameCount / params.duration.params.targetFps);
let phase: number = (t.current % durationInMs) / durationInMs;
if (params.mode.name === 'palindrome') {
if (params.mode.name === 'loop') {
if (params.mode.params.direction === 'backward') {
phase = 1 - phase;
}
} if (params.mode.name === 'palindrome') {
phase = 2 * phase;
if (phase > 1) phase = 2 - phase;
}

View File

@@ -6,12 +6,13 @@
import { PluginCommands } from '../../../mol-plugin/commands';
import { StateTransform } from '../../../mol-state';
import { shallowEqual } from '../../../mol-util';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { PluginStateAnimation } from '../model';
export const AnimateStateInterpolation = PluginStateAnimation.create({
name: 'built-in.animate-state-interpolation',
display: { name: 'Animate State Interpolation' },
display: { name: 'Animate State (experimental)' },
params: () => ({
transtionDurationInMs: PD.Numeric(2000, { min: 100, max: 30000, step: 10 })
}),
@@ -42,15 +43,25 @@ export const AnimateStateInterpolation = PluginStateAnimation.create({
for (const s of src) {
for (const t of tar) {
// TODO: better than quadratic alg.
// TODO: support for adding/removing nodes
if (t.ref !== s.ref) continue;
if (t.version === s.version) continue;
const e = StateTransform.fromJSON(s), f = StateTransform.fromJSON(t);
const oldState = state.cells.get(s.ref);
if (!oldState) continue;
let newState;
if (!e.transformer.definition.interpolate) {
update.to(s.ref).update(currentT <= 0.5 ? e.params : f.params);
newState = currentT <= 0.5 ? e.params : f.params;
} else {
update.to(s.ref).update(e.transformer.definition.interpolate(e.params, f.params, currentT, ctx.plugin));
newState = e.transformer.definition.interpolate(e.params, f.params, currentT, ctx.plugin);
}
if (!shallowEqual(oldState, newState)) {
update.to(s.ref).update(newState);
}
}
}

View File

@@ -22,6 +22,8 @@ import { ChainIdColorThemeProvider } from '../../../mol-theme/color/chain-id';
import { OperatorNameColorThemeProvider } from '../../../mol-theme/color/operator-name';
import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair';
import { StructConn } from '../../../mol-model-formats/structure/property/bonds/struct_conn';
import { StructureRepresentationRegistry } from '../../../mol-repr/structure/registry';
import { assertUnreachable } from '../../../mol-util/type-helpers';
export interface StructureRepresentationPresetProvider<P = any, S extends _Result = _Result> extends PresetProvider<PluginStateObject.Molecule.Structure, P, S> { }
export function StructureRepresentationPresetProvider<P, S extends _Result>(repr: StructureRepresentationPresetProvider<P, S>) { return repr; }
@@ -110,6 +112,8 @@ const auto = StructureRepresentationPresetProvider({
const thresholds = plugin.config.get(PluginConfig.Structure.SizeThresholds) || Structure.DefaultSizeThresholds;
const size = Structure.getSize(structure, thresholds);
const gapFraction = structure.polymerResidueCount / structure.polymerGapCount;
switch (size) {
case Structure.Size.Gigantic:
case Structure.Size.Huge:
@@ -117,10 +121,14 @@ const auto = StructureRepresentationPresetProvider({
case Structure.Size.Large:
return polymerCartoon.apply(ref, params, plugin);
case Structure.Size.Medium:
return polymerAndLigand.apply(ref, params, plugin);
if (gapFraction > 3) {
return polymerAndLigand.apply(ref, params, plugin);
} // else fall through
case Structure.Size.Small:
// `showCarbohydrateSymbol: true` is nice e.g. for PDB 1aga
// `showCarbohydrateSymbol: true` is nice, e.g., for PDB 1aga
return atomicDetail.apply(ref, { ...params, showCarbohydrateSymbol: true }, plugin);
default:
assertUnreachable(size);
}
}
});
@@ -230,7 +238,7 @@ const coarseSurface = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-coarse-surface',
display: {
name: 'Coarse Surface', group: BuiltInPresetGroupName,
description: 'Shows polymers as coarse Gaussian Surface.'
description: 'Shows polymers and lipids as coarse Gaussian Surface.'
},
params: () => CommonParams,
async apply(ref, params, plugin) {
@@ -238,7 +246,8 @@ const coarseSurface = StructureRepresentationPresetProvider({
if (!structureCell) return {};
const components = {
polymer: await presetStaticComponent(plugin, structureCell, 'polymer')
polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
lipid: await presetStaticComponent(plugin, structureCell, 'lipid'),
};
const structure = structureCell.obj!.data;
@@ -246,7 +255,7 @@ const coarseSurface = StructureRepresentationPresetProvider({
const gaussianProps = Object.create(null);
if (size === Structure.Size.Gigantic) {
Object.assign(gaussianProps, {
traceOnly: true,
traceOnly: !structure.isCoarseGrained,
radiusOffset: 2,
smoothness: 1,
visuals: ['structure-gaussian-surface-mesh']
@@ -266,7 +275,8 @@ const coarseSurface = StructureRepresentationPresetProvider({
const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
const representations = {
polymer: builder.buildRepresentation(update, components.polymer, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'polymer' })
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' })
};
await update.commit({ revertOnError: true });
@@ -337,9 +347,15 @@ const atomicDetail = StructureRepresentationPresetProvider({
const m = structure.models[0];
const bondsGiven = !!IndexPairBonds.Provider.get(m) || StructConn.isExhaustive(m);
const atomicType = lowResidueElementRatio && !bondsGiven
? 'spacefill' : highElementCount
? 'line' : 'ball-and-stick';
let atomicType: StructureRepresentationRegistry.BuiltIn = 'ball-and-stick';
if (structure.isCoarseGrained) {
// TODO make configurable?
atomicType = structure.elementCount > 1_000_000 ? 'point' : 'spacefill';
} else if (lowResidueElementRatio && !bondsGiven) {
atomicType = 'spacefill';
} else if (highElementCount) {
atomicType = 'line';
}
const showCarbohydrateSymbol = params.showCarbohydrateSymbol && !highElementCount && !lowResidueElementRatio;
if (showCarbohydrateSymbol) {

View File

@@ -103,6 +103,15 @@ export const PdbqtProvider: TrajectoryFormatProvider = {
visuals: defaultVisuals
};
export const XyzProvider: TrajectoryFormatProvider = {
label: 'XYZ',
description: 'XYZ',
category: TrajectoryFormatCategory,
stringExtensions: ['xyz'],
parse: directTrajectory(StateTransforms.Model.TrajectoryFromXYZ),
visuals: defaultVisuals
};
export const GroProvider: TrajectoryFormatProvider = {
label: 'GRO',
description: 'GRO',
@@ -114,14 +123,23 @@ export const GroProvider: TrajectoryFormatProvider = {
};
export const MolProvider: TrajectoryFormatProvider = {
label: 'MOL/SDF',
description: 'MOL/SDF',
label: 'MOL',
description: 'MOL',
category: TrajectoryFormatCategory,
stringExtensions: ['mol', 'sdf', 'sd'],
stringExtensions: ['mol'],
parse: directTrajectory(StateTransforms.Model.TrajectoryFromMOL),
visuals: defaultVisuals
};
export const SdfProvider: TrajectoryFormatProvider = {
label: 'SDF',
description: 'SDF',
category: TrajectoryFormatCategory,
stringExtensions: ['sdf', 'sd'],
parse: directTrajectory(StateTransforms.Model.TrajectoryFromSDF),
visuals: defaultVisuals
};
export const Mol2Provider: TrajectoryFormatProvider = {
label: 'MOL2',
description: 'MOL2',
@@ -137,7 +155,9 @@ export const BuiltInTrajectoryFormats = [
['pdb', PdbProvider] as const,
['pdbqt', PdbqtProvider] as const,
['gro', GroProvider] as const,
['xyz', XyzProvider] as const,
['mol', MolProvider] as const,
['sdf', SdfProvider] as const,
['mol2', Mol2Provider] as const,
] as const;

View File

@@ -21,7 +21,7 @@ export async function setStructureClipping(plugin: PluginContext, components: St
await eachRepr(plugin, components, async (update, repr, clippingCell) => {
if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return;
const structure = repr.obj!.data.source.data;
const structure = repr.obj!.data.sourceData;
// always use the root structure to get the loci so the clipping
// stays applicable as long as the root structure does not change
const loci = await lociGetter(structure.root);

View File

@@ -22,7 +22,7 @@ export async function setStructureOverpaint(plugin: PluginContext, components: S
await eachRepr(plugin, components, async (update, repr, overpaintCell) => {
if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return;
const structure = repr.obj!.data.source.data;
const structure = repr.obj!.data.sourceData;
// always use the root structure to get the loci so the overpaint
// stays applicable as long as the root structure does not change
const loci = await lociGetter(structure.root);

View File

@@ -425,6 +425,18 @@ const surroundings = StructureSelectionQuery('Surrounding Residues (5 \u212B) of
referencesCurrent: true
});
const surroundingLigands = StructureSelectionQuery('Surrounding Ligands (5 \u212B) of Selection', MS.struct.modifier.union([
MS.struct.modifier.surroundingLigands({
0: MS.internal.generator.current(),
radius: 5,
'include-water': true
})
]), {
description: 'Select ligand components within 5 \u212B of the current selection.',
category: StructureSelectionCategory.Manipulate,
referencesCurrent: true
});
const complement = StructureSelectionQuery('Inverse / Complement of Selection', MS.struct.modifier.union([
MS.struct.modifier.exceptBy({
0: MS.struct.generator.all(),
@@ -446,6 +458,16 @@ const covalentlyBonded = StructureSelectionQuery('Residues Covalently Bonded to
referencesCurrent: true
});
const covalentlyBondedComponent = StructureSelectionQuery('Covalently Bonded Component', MS.struct.modifier.union([
MS.struct.modifier.includeConnected({
0: MS.internal.generator.current(), 'fixed-point': true
})
]), {
description: 'Select covalently bonded component based on current selection.',
category: StructureSelectionCategory.Manipulate,
referencesCurrent: true
});
const covalentlyOrMetallicBonded = StructureSelectionQuery('Residues with Cov. or Metallic Bond to Selection', MS.struct.modifier.union([
MS.struct.modifier.includeConnected({
0: MS.internal.generator.current(),
@@ -635,9 +657,11 @@ export const StructureSelectionQueries = {
ring,
aromaticRing,
surroundings,
surroundingLigands,
complement,
covalentlyBonded,
covalentlyOrMetallicBonded,
covalentlyBondedComponent,
wholeResidues,
};

View File

@@ -21,7 +21,7 @@ export async function setStructureTransparency(plugin: PluginContext, components
await eachRepr(plugin, components, async (update, repr, transparencyCell) => {
if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return;
const structure = repr.obj!.data.source.data;
const structure = repr.obj!.data.sourceData;
// always use the root structure to get the loci so the transparency
// stays applicable as long as the root structure does not change
const loci = await lociGetter(structure.root);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -12,7 +12,7 @@ import { StatefulPluginComponent } from '../component';
import { PluginContext } from '../../mol-plugin/context';
import { utf8ByteCount, utf8Write } from '../../mol-io/common/utf8';
import { Asset } from '../../mol-util/assets';
import { zip } from '../../mol-util/zip/zip';
import { Zip } from '../../mol-util/zip/zip';
import { readFromFile } from '../../mol-util/data-source';
import { objectForEach } from '../../mol-util/object';
import { PLUGIN_VERSION } from '../../mol-plugin/version';
@@ -217,7 +217,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
zipDataObj['assets.json'] = data;
}
const zipFile = zip(zipDataObj);
const zipFile = await this.plugin.runTask(Zip(zipDataObj));
return new Blob([zipFile], {type : 'application/zip'});
}
}

View File

@@ -164,7 +164,10 @@ export class StructureFocusManager extends StatefulPluginComponent<StructureFocu
plugin.state.data.events.object.updated.subscribe(({ oldData, obj, action }) => {
if (!PluginStateObject.Molecule.Structure.is(obj)) return;
// structure NOT changed, keep everything as is; fixes #123
if (oldData === obj.data) return;
// structure changed (e.g. coordinates), try to remap and re-focus
if (action === 'in-place') {
const current = this.state.current;
const structure = obj.data as Structure;

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