Compare commits

...

819 Commits

Author SHA1 Message Date
dsehnal
d527609b6d 2.0.0-dev.3 2021-02-21 19:23:06 +01:00
dsehnal
e628f580a7 add missing React key 2021-02-21 16:28:17 +01:00
Alexander Rose
b662179b4d improved lighting example 2021-02-20 01:59:46 -08:00
Alexander Rose
fa2b8542bf 2.0.0-dev.2 2021-02-18 21:20:14 -08:00
Alexander Rose
901522f500 added atom-id and entity-id color theme 2021-02-18 21:16:46 -08:00
dsehnal
62b63c1aa5 apply magic to solve GPU MC rendering issue 2021-02-18 19:05:07 +01:00
Alexander Rose
24b36f41da 2.0.0-dev.1 2021-02-15 22:09:05 -08:00
Alexander Rose
c9c890782c try re-use boundingSphere in element visuals
- if it has not changed much
2021-02-15 21:38:13 -08:00
Alexander Rose
f2c539ebd8 psf parser, support lammps "full" style 2021-02-15 18:04:38 -08:00
dsehnal
feb922ca91 Merge branch 'gpu' 2021-02-14 20:13:16 +01:00
dsehnal
25127bb84b Merge branch 'master' of https://github.com/molstar/molstar 2021-02-14 20:13:12 +01:00
dsehnal
8fb01d2157 Merge remote-tracking branch 'origin' into gpu 2021-02-14 20:11:11 +01:00
dsehnal
c09357ea75 updateImmediate for modelIndex 2021-02-14 20:00:37 +01:00
dsehnal
9f2513dae0 fix examples 2021-02-14 19:38:58 +01:00
dsehnal
11a52c0390 add missing TrajectoryInfo 2021-02-14 19:34:32 +01:00
dsehnal
e955dc7e94 exportable trajectory animation 2021-02-14 19:26:06 +01:00
Alexander Rose
c8107272f6 2.0.0-dev.0 2021-02-13 23:11:05 -08:00
Alexander Rose
fb08fe7545 remove extra files 2021-02-13 23:06:17 -08:00
Alexander Rose
b6f054ea28 Merge pull request #130 from molstar/lint-dep
Lint dep
2021-02-13 13:24:34 -08:00
Alexander Rose
dc7e85133c moved DefaultPluginSpec to spec.ts 2021-02-13 13:04:53 -08:00
Alexander Rose
90cddf4e41 allow named tuples
- beter use downlevel-dts for backwards compatibility
2021-02-13 13:00:11 -08:00
Alexander Rose
2cddbb72a6 fix tests, remove more deps from mat4, quat 2021-02-13 12:29:45 -08:00
Alexander Rose
a16faaac4e avoid some static dependencies
- those can lead to errors due to circular dependencies
- making them runtime dependencies fixes this
2021-02-13 12:06:07 -08:00
Alexander Rose
6c5224f33e new linting rules
- no default exports
- no named tuples
2021-02-13 11:36:21 -08:00
Alexander Rose
77d013b775 webgl, ensure active attribute with divisor 0
- workaround for FF <85
- needed for `texture-mesh` geometry
2021-02-13 11:30:13 -08:00
Alexander Rose
02a466e8b9 only update repr visibility when changed
- avoids superfluous scene rendering, e.g., when animating
2021-02-13 11:27:44 -08:00
Alexander Rose
3cb65cbe3d reduce ssao quality defaults a bit
- less texture fetches
2021-02-13 11:24:07 -08:00
Alexander Rose
fe8838542c 1.3.0 2021-02-07 13:29:46 -08:00
Alexander Rose
78b5c9aac4 Merge pull request #129 from molstar/gpu
Gpu acceleration for isosurface & gaussian-surface
2021-02-07 13:25:22 -08:00
Alexander Rose
021fa7b79b clearer param names for using gpu/impostors 2021-02-07 13:23:36 -08:00
Alexander Rose
0443589b09 allow gaussian volume without blendMinMax 2021-02-07 13:18:24 -08:00
Alexander Rose
415288de9f tweak quality settings
- lower resolution
2021-02-07 12:52:10 -08:00
Alexander Rose
ecbafb086a fix mc example 2021-02-06 12:46:25 -08:00
Alexander Rose
e5dae6c0dd increased default quality for larger structures
- rational is that larger structures can take a bit longer to create
2021-02-06 12:45:00 -08:00
Alexander Rose
16f4524bdb improved gpu support for representations
- enabled in volume isosurface & structure gaussian surface
- only if suitable (check memory requirements and resolution)
- falls back to cpu code
2021-02-06 12:43:24 -08:00
Alexander Rose
6b33021f43 fix webgl stats on render-item disposal 2021-02-06 12:30:12 -08:00
Alexander Rose
fdf37100c2 improved gpu-mc
- lower memory usage
- support for 2^32 vertices in webgl2
- fix rounding issue when creating volume texture
2021-02-06 12:28:52 -08:00
Alexander Rose
e28674d0dc model format improvements
- add a source format to the mmcif format
- add pdb format
- allow undefined in typeguard .is helpers
2021-02-03 19:36:01 -08:00
Alexander Rose
fb7456286a fix label_seq_id assignment when undefined column 2021-02-03 19:32:20 -08:00
Alexander Rose
9d240f8928 allow views of undefined columns 2021-02-03 19:30:14 -08:00
David Sehnal
48ef5efb21 Merge pull request #127 from molstar/pp-res
lower resolution ssao
2021-02-01 12:59:53 +01:00
Alexander Rose
52b2e7c144 lower res ssao 2021-01-31 13:22:22 -08:00
Alexander Rose
f2d1d60f6b fix cellpack & unit creation issues
- cellpack generate color theme can be applied to 1 model trajectories
- don't clone supplied props in Unit.create
- fix cellpack structure building to share unit.props
2021-01-31 12:06:40 -08:00
dsehnal
5a176a378a 1.2.15 2021-01-31 18:32:08 +01:00
dsehnal
60151c2c24 fix getUnitsSortedByVolume 2021-01-31 18:30:13 +01:00
dsehnal
a5db6350a2 only normalize Canvas3D props when loading from a saved state 2021-01-31 18:01:42 +01:00
dsehnal
0618eb18ba 1.2.14 2021-01-31 17:10:17 +01:00
Alexander Rose
bffdff6aad gaussian surface visual improvements
- add structure.unitsSortedByVolume
- increase gaussian smoothness in coarse presets
- use slice area instead of volume to ensure reasonable resolution
- use largest units (by volume) for reasonable resolution calculation
2021-01-30 22:55:29 -08:00
Alexander Rose
7753a6ec56 renderable schema cleanup
- use base schema in direct-volume schema
2021-01-30 12:57:19 -08:00
Alexander Rose
b8aafa1d78 mol-gl improvements
- int textures (webgl2)
- read into Int32Array (webgl2)
- fix ctx.parameters.maxDrawingBuffers (webgl1)
- support setting frag out type (webgl2)
2021-01-30 12:42:48 -08:00
Alexander Rose
672875187b add renderable.state.disposed flag
- set when disposing render-objects
- don't render disposed objects (can be temporarily still in a scene)
2021-01-30 12:39:33 -08:00
Alexander Rose
547d60d573 fix texture-mesh vertex count 2021-01-30 12:33:59 -08:00
Alexander Rose
99471d2a7b add xray shading edge fallof parameter 2021-01-30 12:32:20 -08:00
Alexander Rose
45d249b71a log renderItemId in debug mode 2021-01-30 11:23:46 -08:00
Alexander Rose
1382edd81c improved trackball rotate on wide canvases 2021-01-30 11:23:15 -08:00
dsehnal
89a6102f8d 1.2.13 2021-01-30 14:49:17 +01:00
David Sehnal
163929477e Merge pull request #106 from molstar/cylinders
Cylinders geomery and link visual improvements
2021-01-30 14:45:26 +01:00
dsehnal
c10a8369e8 Canvas3d: force render on viewport resize 2021-01-30 14:10:50 +01:00
dsehnal
8fbba52de8 PD.normalizeParams update 2021-01-30 13:41:22 +01:00
dsehnal
ca3174b2c3 Fix computeUnitGaussianDensity 2021-01-30 13:08:42 +01:00
dsehnal
b9864fba80 Fix loci label custom text 2021-01-27 18:01:47 +01:00
Alexander Rose
f8e9bc1e7f limit max resolution for (gpu) gaussian-surface 2021-01-24 21:45:01 -08:00
Alexander Rose
f79f1507f7 dispose of volume & repr associated textures 2021-01-24 21:44:02 -08:00
Alexander Rose
61ab205a5d recreate visuals based on param changes
- impostor/mesh (spacefill, ball+stick, ellipsoids)
- gpu/cpu mc (isosurface, gaussian surface)
2021-01-23 16:57:21 -08:00
Alexander Rose
2c65260a4f Merge remote-tracking branch 'origin/param-normalization' into cylinders 2021-01-23 16:39:23 -08:00
Alexander Rose
0597a1ef24 Merge branch 'master' into cylinders 2021-01-23 16:35:18 -08:00
Alexander Rose
8d6557e51c moved label-options out of palette-params 2021-01-23 16:25:32 -08:00
Alexander Rose
5cff0dff3d wip, gpu mc
- int float div
- clamp 2d texture access to 3d grid bounds
2021-01-23 15:46:29 -08:00
Alexander Rose
93206e76d7 fix DVR with orthographic projection 2021-01-23 13:36:28 -08:00
dsehnal
40933a8539 1.2.12 2021-01-20 14:18:55 +01:00
dsehnal
989800783b dihedral visual update 2021-01-20 14:13:57 +01:00
dsehnal
d83b0d2c4d better typing for PD.MultiSelect & StructureMeasurementManager visualParams support 2021-01-20 10:30:59 +01:00
dsehnal
5e5d5a63dc mol-plugin: subscribe to events in initViewer
+ handle plugin resize in main render loop
2021-01-19 15:09:57 +01:00
Alexander Rose
b1755604e2 fix webgl context loss handling 2021-01-18 19:48:55 -08:00
Alexander Rose
e58da9b574 add Canvas3DContext
- can be used to create multiple Canvas3D objects
2021-01-18 11:30:42 -08:00
dsehnal
f5d6498601 ParamDefinition.normalizeParams tweaks 2021-01-17 11:50:15 +01:00
dsehnal
07f351888f add doNotForceWebGLContextLoss option 2021-01-17 11:15:15 +01:00
Alexander Rose
4588fdd5d5 wip, gpu mc webgl1 tweak 2021-01-16 17:24:26 -08:00
Alexander Rose
c3b32baf6a wip, gpu mc & vol isosurface 2021-01-16 15:18:41 -08:00
Alexander Rose
b8d60cea9b Canvas3d.fromCanvas attribs
- simplified antialias handling
- expose preserveDrawingBuffer
2021-01-16 13:20:32 -08:00
Alexander Rose
25b8956712 moved rgba/float conversion to glsl chunks 2021-01-16 11:40:54 -08:00
Alexander Rose
7015309db6 added more interesting pdb entries 2021-01-16 11:37:48 -08:00
Alexander Rose
aad861db37 use var in webpack version template 2021-01-16 11:37:20 -08:00
Alexander Rose
ae7811705d fix volume isosurface picking 2021-01-16 11:35:55 -08:00
dsehnal
7e26dac50b 1.2.11 2021-01-15 18:40:05 +01:00
dsehnal
75f43d038c PluginConfig.General.ForceWboitAntialiasing 2021-01-15 18:37:29 +01:00
dsehnal
b9ba940510 dihedral visual updates
- fix "extenders"
- add "arms" visual
2021-01-15 15:10:33 +01:00
dsehnal
35603baaaa 1.2.10 2021-01-15 13:28:01 +01:00
dsehnal
19dc32c491 Canvas3D.dispose lose webgl context 2021-01-15 13:21:34 +01:00
dsehnal
95997e6a61 PickScale plugin config 2021-01-15 12:54:02 +01:00
dsehnal
03e19a2ad7 css tweaks 2021-01-15 12:50:30 +01:00
dsehnal
765b133369 Merge branch 'master' into cylinders 2021-01-13 16:50:11 +01:00
dsehnal
703e729514 PD.normalizeParams options 2021-01-13 16:49:33 +01:00
dsehnal
b0216c4ce6 Merge branch 'master' into cylinders 2021-01-13 16:10:39 +01:00
dsehnal
6796fc1cd4 use PD.normalizeParams in canvas3d.setProps 2021-01-13 16:09:48 +01:00
dsehnal
87c504f9a8 mol-state: use PD.normalizeParams first time a cell is evaluated 2021-01-13 15:59:50 +01:00
dsehnal
2e770cb733 ParamDefinition.normalizeParams 2021-01-13 15:44:05 +01:00
David Sehnal
9f440f68e0 Merge pull request #118 from JonStargaryen/modelserverfixes
ModelServer: filename parameter
2021-01-13 12:57:01 +01:00
JonStargaryen
40028b27ba cleanup 2021-01-12 10:47:18 -08:00
JonStargaryen
4676ad8738 gracefully handle empty param category 2021-01-12 10:44:47 -08:00
JonStargaryen
e1c7833826 filename param 2021-01-12 10:25:55 -08:00
Alexander Rose
dd1bca0fee Merge branch 'master' into cylinders 2021-01-10 00:45:18 -08:00
Alexander Rose
c38ab2c638 use existing gl context for capability testing
- fixes unit-test
2021-01-10 00:38:16 -08:00
Alexander Rose
459c5aa5a7 wip, gpu mc
- reduced texture sizes
- structure gaussian surface texture-mesh
2021-01-10 00:17:41 -08:00
Alexander Rose
b8bf07d393 byto-count info for webgl resources 2021-01-10 00:09:20 -08:00
Alexander Rose
ea87ac2094 add and use gpu half-float support
- add texture_half_float, texture_half_float_linear, color_buffer_half_float
- use in multie-sample, gaussian-density
2021-01-10 00:07:12 -08:00
Alexander Rose
e1b830a59d improved atomicDetail preset 2021-01-09 14:23:19 -08:00
Alexander Rose
41e1ac76c0 improve peptide entity-subtype derivation 2021-01-09 12:51:34 -08:00
Alexander Rose
98b118fd1e parse pdb conect records
- heuristic to test if list of bonds is exhaustive to skip auto-bonding
2021-01-09 12:49:44 -08:00
Alexander Rose
5f691913e4 fix webgl1 screendoor transparency
- webgl1 only allows const array access
2021-01-09 11:16:32 -08:00
Alexander Rose
26e2516097 fix traceOnly param getting ignored 2021-01-09 11:15:13 -08:00
dsehnal
3d2e4115ed 1.2.9 2021-01-08 15:17:42 +01:00
dsehnal
dbce1ccb3d alpha-orbitals: ability to clamp volume values 2021-01-08 15:16:08 +01:00
David Sehnal
03aa2be978 Merge pull request #116 from JonStargaryen/modelserverfixes
ModelServer: Add option to download text files
2021-01-07 17:33:38 +01:00
JonStargaryen
8dfc52e1ab cleanup 2021-01-07 14:49:32 +01:00
JonStargaryen
6058179f10 cleanup 2021-01-07 14:36:11 +01:00
JonStargaryen
ea9e25b03c ResultWriterParams 2021-01-07 14:30:01 +01:00
JonStargaryen
d60c3ddce3 handle non-string params faithfully 2021-01-07 14:21:32 +01:00
JonStargaryen
724e79bddf version/changelog 2021-01-07 12:53:44 +01:00
JonStargaryen
2de61215c4 better description 2021-01-07 12:35:16 +01:00
JonStargaryen
e783d9a9f1 Merge remote-tracking branch 'upstream/master' into modelserverfixes 2021-01-07 12:31:26 +01:00
JonStargaryen
e9e971d4f3 lock 2021-01-07 12:31:20 +01:00
JonStargaryen
96dea14cb1 cleanup 2021-01-07 12:26:50 +01:00
JonStargaryen
04fc157340 ModelServer: Save As param 2021-01-07 11:53:24 +01:00
dsehnal
cfc24fa99e SequenceView: fix polymers & everything modes 2021-01-07 11:22:57 +01:00
dsehnal
19c1088209 1.2.8 2021-01-06 15:29:29 +01:00
dsehnal
ee6c2e0841 canvas3d: add commited event 2021-01-06 15:22:54 +01:00
Alexander Rose
20ee659b00 wip, gaussian surface
- fix webgl1 gaussian volume broken
- fix 2d volume slice missing first row
- use scissor test to avoid useless calculations
- reduce radius for which gaussians are calculated
-
2021-01-03 15:15:03 -08:00
Alexander Rose
b6514a4a50 fix structure bond count calculation 2021-01-03 14:57:18 -08:00
Alexander Rose
07b8bdb951 noClip option for renderables
- exclude handle and axes helper from clip objects
2021-01-03 14:56:40 -08:00
Alexander Rose
afd18cabd4 tweaked renderer params to enable AO by default 2021-01-03 14:42:35 -08:00
Alexander Rose
1117ce05d5 improved picking alpha and fog shader code 2021-01-03 14:37:49 -08:00
Alexander Rose
fc15e952bf fix smaa viewport handling 2021-01-03 14:34:34 -08:00
dsehnal
249e5a3e0b SequenceView improvements
- add ability to show all polymers/chains at once
- do not redraw when structure list doesn't change (saves 3 re-renders for a typical structure load)
2020-12-29 16:29:16 +01:00
Alexander Rose
4bfe3f6bde postprocesing tweaks
- better distingush objects close to far plane from background
- draw outlines last to be cleaner
- allow larger AO radius
2020-12-21 22:38:30 -08:00
Alexander Rose
75b7e0b4d9 support to 'invert' clip object test
- e.g. to cut away everything but a sphere
2020-12-20 20:06:52 -08:00
Alexander Rose
ee4ce2fd7a simplify copy shader 2020-12-20 14:35:17 -08:00
Alexander Rose
db0aa12e75 Merge branch 'master' into cylinders 2020-12-20 13:45:50 -08:00
Alexander Rose
6d2578d3d0 repr/geo param update fixes
- texture-mesh geo
- text visual
2020-12-20 13:40:33 -08:00
Alexander Rose
99d61f48b4 Merge branch 'master' into cylinders 2020-12-20 13:00:18 -08:00
Alexander Rose
146022dc12 wip, gaussian surface & mc
- fix iso-level
- reuse gpu resources for mc (patched many memory leaks)
2020-12-20 12:55:54 -08:00
Alexander Rose
92730cad01 Merge branch 'master' into cylinders 2020-12-19 21:33:24 -08:00
Alexander Rose
d6b68b06da Merge branch 'cylinders' of https://github.com/molstar/molstar into cylinders 2020-12-19 21:31:16 -08:00
Alexander Rose
b174fbf0c6 postprocessing tweaks and fixes/improvements
- AO defaults: darker, larger radius
- handle transparent bg for outlines & AO
- handle fog for AO
- fix fog for outlines
- fragmentDepth for fog (instead of camera distance)
- webgl1 compat
2020-12-19 21:26:06 -08:00
Alexander Rose
fde1557955 Merge branch 'postprocessing' into cylinders
- added AntialiasingPass
2020-12-19 17:38:57 -08:00
AronKovacs
24a0753881 fix fog for outlines 2020-12-19 21:59:56 +01:00
dsehnal
5664e1d8be 1.2.7 2020-12-19 11:53:09 +01:00
dsehnal
4881a41256 set default camera radius/max = 0 2020-12-19 11:46:00 +01:00
dsehnal
235e41ee03 PluginConfig EnableWboit => true 2020-12-19 11:26:13 +01:00
AronKovacs
94d293a4d3 renaming, better defaults, ao bias, better outline thresholding, whitespace changes 2020-12-18 16:10:37 +01:00
AronKovacs
40f1ca207f replaced placeholder value with the correct uniform 2020-12-16 17:58:00 +01:00
AronKovacs
926fb38c1e added contributors 2020-12-16 17:43:20 +01:00
AronKovacs
5a14fcabc5 small ssao changes, e.g. better vec2 noise 2020-12-16 17:38:38 +01:00
AronKovacs
560e40773f added renderBlended postprocessing 2020-12-16 17:21:26 +01:00
AronKovacs
6561732f57 Merge remote-tracking branch 'upstream/master' into postprocessing 2020-12-15 13:30:41 +01:00
AronKovacs
b45cf206fd postprocessing init 2020-12-15 13:27:11 +01:00
Alexander Rose
70e07be64d anvil tweaks
- remove unused/broken bilayer-spheres visual
- ensure anvil prop is calculated
2020-12-12 18:23:07 -08:00
Alexander Rose
f3013f0e46 smaa param tweaks 2020-12-12 17:31:27 -08:00
Alexander Rose
2e7041bd78 remove debug statement 2020-12-12 17:29:19 -08:00
Alexander Rose
5d0447c9bb enable wboit by default 2020-12-12 16:20:59 -08:00
Alexander Rose
9eba0b91a8 add smaa antialiasing option (new default) 2020-12-12 16:13:53 -08:00
Alexander Rose
58bc6722a9 moved fxaa to separate pass 2020-12-12 15:42:56 -08:00
Alexander Rose
1acfed3233 naming and doc tweaks 2020-12-12 15:39:45 -08:00
dsehnal
8147b3aa34 1.2.6 2020-12-10 10:46:26 +01:00
dsehnal
b21552ff36 fix wboit rendering when updating alpha 2020-12-10 10:44:35 +01:00
dsehnal
c683cbe962 1.2.5 2020-12-09 15:06:17 +01:00
dsehnal
bd270e4428 fix pdbx_PDB_ins_code "prefixed" names in CIF exporter 2020-12-09 15:03:41 +01:00
dsehnal
23d942d8a5 updated packages 2020-12-09 14:55:25 +01:00
Alexander Rose
cbcd6b99d2 Merge pull request #107 from molstar/remove-3dg
remove 3dg in favor of g3d (#93)
2020-12-05 20:44:16 -08:00
Alexander Rose
ee5c098a9f remove 3dg in favor of g3d (#93) 2020-12-05 20:38:50 -08:00
Alexander Rose
070a15d679 antialiasing related tweaks (combat blurriness)
- increase default line and point size
- reduce subpixel quality in fxaa
2020-12-05 16:02:08 -08:00
Alexander Rose
befa5174f8 cylinder impostors for bonds
- inter/intra bonds
- ball & stick, ellipsoids
- new link visual helper
2020-12-05 15:49:59 -08:00
Alexander Rose
d6c4366f40 link visual helper improvements
- more configurable dashes
- better cap handling
2020-12-05 15:28:22 -08:00
Alexander Rose
181cfefa63 use bond location for repr bond iterator
- fix themes to handle Bond.Location (some did not)
2020-12-05 15:22:04 -08:00
Alexander Rose
0e7c885961 fix typo, remove unused code 2020-12-05 15:17:14 -08:00
Alexander Rose
d58e90d93f add cylinders geometry and shader 2020-12-05 15:15:29 -08:00
David Sehnal
cd872b47e6 1.2.4 2020-12-03 15:30:30 +01:00
David Sehnal
2683c5b318 Merge pull request #105 from molstar/gpu-grid
GPU grid 3d computation wrapper
2020-12-03 15:24:43 +01:00
David Sehnal
c71f60a164 ParamDefinition.DataRef 2020-12-03 15:21:42 +01:00
David Sehnal
881cbc1947 tweaks 2020-12-03 13:54:51 +01:00
David Sehnal
f3e7febbd1 Merge branch 'master' of https://github.com/molstar/molstar into gpu-grid 2020-12-03 06:33:19 +01:00
David Sehnal
e68ad13031 createGrid3dComputeRenderable yieldPeriod param 2020-12-02 12:29:01 +01:00
Alexander Rose
7fbbe1e63a representation state and hightlight fixes
- recreate state when repr changes
- take repr into account for non-hover hightlights (eg from state tree)
2020-12-01 17:48:40 -08:00
Alexander Rose
a5ca72af3c postprocessing tweaks and fixes
- fix missing enable scissor state
- better antialiasing defaults
- always allow fxaa
2020-12-01 17:46:51 -08:00
David Sehnal
1ce6641eb3 grid3d-compute util code 2020-12-01 20:58:27 +01:00
David Sehnal
5dc413ab8c wip grid3d renderable 2020-12-01 19:33:05 +01:00
David Sehnal
50b615e86c 1.2.3 2020-11-28 14:50:15 +01:00
David Sehnal
5b4c6743e7 GlobalModelTransformInfo
- support in volume streaming
- export in ModelServer if transform param is present
2020-11-28 14:46:58 +01:00
Alexander Rose
99a3906978 1.2.2 2020-11-26 11:17:22 -08:00
Alexander Rose
981db34736 Merge branch 'master' of https://github.com/molstar/molstar 2020-11-26 11:12:35 -08:00
Alexander Rose
c079a8c5a8 fixed triple linkstyle in visuals
- was ignored
2020-11-26 11:12:11 -08:00
Alexander Rose
ad70adf6ce improved & fixed fxaa
- enable linear texture interpolation to actually do subpixel fetches...
- higher quality fxaa profile with edge exploration
- exposed parameters
- enable during temproal multi sampling
2020-11-26 11:11:14 -08:00
David Sehnal
89110b52bd copyright info 2020-11-26 15:55:48 +01:00
David Sehnal
8a69f050a6 1.2.1 2020-11-26 15:28:12 +01:00
David Sehnal
9e38a44406 lint 2020-11-25 19:49:38 +01:00
David Sehnal
3514ab23c3 remove unused import 2020-11-25 17:08:08 +01:00
David Sehnal
b59e3c383d tweak 2020-11-25 17:05:38 +01:00
David Sehnal
eeba565d78 alpha-orbitals: fix async computation 2020-11-25 17:05:12 +01:00
David Sehnal
687e54cc87 alpha-orbitals: density support 2020-11-25 16:27:42 +01:00
David Sehnal
ac73939440 alpha-orbitals: data model improvements 2020-11-25 15:32:15 +01:00
David Sehnal
7a3eb8d03f fix Canvas3dInteractionHelper.leave 2020-11-25 11:00:05 +01:00
David Sehnal
3d26904e0b Merge branch 'master' of https://github.com/molstar/molstar into alpha-orbitals-density 2020-11-25 10:52:09 +01:00
Alexander Rose
468e14bc35 add fxaa option to postprocessing pass 2020-11-25 01:50:04 -08:00
David Sehnal
e2dc61212e alpha orbitals: density proof of concept 2020-11-24 15:12:53 +01:00
David Sehnal
aa911ad4bc viewer loadAllModelsOrAssemblyFromUrl options 2020-11-24 10:34:12 +01:00
Alexander Rose
bb5494264c wboit tweaks and fixes
- disable by default for now (until we settly on aa option)
- fix depth repcision issue in large scenes
- fix wrong depth tex bound
- undo pixelScale change as it was not doing anything
2020-11-23 23:47:54 -08:00
David Sehnal
c0116a3baa fix geometry quality access on empty structures 2020-11-23 22:38:29 +01:00
David Sehnal
9c7497b447 canvas3d: camera reset take 3 2020-11-23 22:31:37 +01:00
David Sehnal
fa3a79fdeb fix canvas3d camera reset condition 2020-11-23 22:21:33 +01:00
David Sehnal
2987240df4 canvas3d: do not autoreset camera if the "breaking" sphere mutually overlaps with current camera sphere 2020-11-23 22:11:35 +01:00
David Sehnal
17a1640da5 1.2.0 2020-11-23 14:11:05 +01:00
David Sehnal
a86da8ee11 mp4 encoder fixes 2020-11-23 14:04:48 +01:00
David Sehnal
20e373115d add xrayShaded option to orbital visuals 2020-11-23 13:33:28 +01:00
David Sehnal
531260fbc5 fix screenshot autocrop with transparent bg 2020-11-23 12:56:54 +01:00
David Sehnal
47a3dfcef9 enable wboit by default, use devicePixelRatio by default 2020-11-23 12:54:31 +01:00
Alexander Rose
c5ca51fd80 Merge branch 'master' into wboit 2020-11-21 23:30:37 -08:00
Alexander Rose
2f2e44c032 added xray-shaded option
- mesh geometry
- direct-volume geometry
- spheres geometry
2020-11-21 23:04:13 -08:00
Alexander Rose
26acb37098 remove transparency variant
- keep only single layer screndoor transparency
- fallback if wboit is not available
2020-11-21 22:06:35 -08:00
Alexander Rose
466308cde8 transparent object rendering improvement
- changed output to be pre-multiplied alpha
- point and text tweaks
- better handle opaque vs transparent volumes in blended rendering
2020-11-21 21:56:32 -08:00
Alexander Rose
dc9af9d8b0 fix drawing buffer uniform assignment
- fixes issues with texture fetches when size differs from canvas size
2020-11-21 11:25:41 -08:00
Alexander Rose
c17bfd65e7 ensure subtype assignment for all entities, #100 2020-11-21 11:13:15 -08:00
Alexander Rose
6de07ab8c2 interaction improvements
- add page xy to click event
- check if inside viewport before hover event
2020-11-21 11:11:59 -08:00
David Sehnal
0b8aab802c StateSelection improvements
- add findAncestor
- add findAncestorOfType
- add findRoot
2020-11-21 12:55:35 +01:00
David Sehnal
13f28fbe33 symmetryColor representation preset option 2020-11-19 14:18:20 +01:00
David Sehnal
2eda679966 Revert "Merge pull request #98 from AronKovacs/wboit"
- Move to a separate branch while issues are fixed.
2020-11-19 13:10:53 +01:00
David Sehnal
35c778b644 fix use-behavior bug 2020-11-19 11:48:09 +01:00
David Sehnal
96f8ba5a80 Merge pull request #98 from AronKovacs/wboit
Wboit
2020-11-18 11:38:00 +01:00
AronKovacs
0daaa94958 fix tests 2020-11-17 16:15:14 +01:00
AronKovacs
e672503fda fog patch by arose 2020-11-17 16:05:17 +01:00
AronKovacs
4d86c9e0ae renaming and bug fixes 2020-11-16 18:08:27 +01:00
AronKovacs
80fbc474f6 Merge pull request #2 from arose/wboit
wip, wboit integration
2020-11-16 13:50:45 +01:00
AronKovacs
dacdc6abfc Merge branch 'wboit' into wboit 2020-11-16 13:50:31 +01:00
Alexander Rose
58bc8b58de wip, wboit integration 2020-11-15 19:41:25 -08:00
AronKovacs
07ead670dd split drawpass._render() into _renderStandard() and _renderWboit() 2020-11-15 18:03:26 +01:00
David Sehnal
00fd760f71 1.1.33 2020-11-14 13:59:46 +01:00
David Sehnal
a71186905d animation improvements/fixes
- added AnimateStateSnapshots
- added "getAnimationDuration" to exportable animations
2020-11-14 13:38:39 +01:00
AronKovacs
a7e0524d01 Merge pull request #1 from arose/wboit
tweaks and fixes
2020-11-14 12:50:34 +01:00
David Sehnal
7d7c1241d4 fix undefined navigator.clipboard 2020-11-14 10:49:56 +01:00
Alexander Rose
1f6e928d78 Merge branch 'master' of https://github.com/molstar/molstar into wboit 2020-11-13 22:23:16 -08:00
Alexander Rose
9bc256bdab refactored wboit into seperate pass 2020-11-13 22:19:06 -08:00
Alexander Rose
734096260d add null-texture and use for tDepth 2020-11-13 20:52:42 -08:00
Alexander Rose
1b4b6f9435 add GlobalTextureSchema missing from renderables 2020-11-13 20:51:27 -08:00
David Sehnal
54fe5c85d6 1.1.32 2020-11-13 20:31:54 +01:00
David Sehnal
f336891bf3 Merge pull request #97 from molstar/mp4-export
MP4 Export
2020-11-13 20:27:28 +01:00
David Sehnal
d2a3c9c61f Merge branch 'master' of https://github.com/molstar/molstar into mp4-export 2020-11-13 20:24:48 +01:00
David Sehnal
6968959fe2 mp4 encoder fix background reset 2020-11-13 19:18:13 +01:00
David Sehnal
7749fe5000 fix encoder viewport check 2020-11-13 16:44:25 +01:00
David Sehnal
bf45d2df5d draw screenshot preview at ~8fps 2020-11-13 15:38:48 +01:00
David Sehnal
b2222844ae redraw preview on param change 2020-11-13 15:28:28 +01:00
Aron Kovacs
3d21f1ecc6 patch and background transparency 2020-11-13 15:01:57 +01:00
Alexander Rose
cde280de60 Merge pull request #96 from JonStargaryen/modelserverfixes
handle metal ions correctly for ModelServer ligand export
2020-11-12 22:02:25 -08:00
David Sehnal
9b415ddff2 screenshot frame touch support 2020-11-12 22:43:44 +01:00
David Sehnal
906c3ac2b6 screenshot fixes 2020-11-12 21:29:39 +01:00
JonStargaryen
498611d4d4 version & CHANGELOG 2020-11-12 12:02:25 -08:00
JonStargaryen
a11bc73d68 consistency 2020-11-12 11:52:10 -08:00
JonStargaryen
9616ae5d63 handle metal ion ligand export 2020-11-12 11:50:36 -08:00
Aron Kovacs
c81476d2a7 bug fixes and wboit for text and images 2020-11-12 18:31:09 +01:00
David Sehnal
397f001352 copy screenshot workaround for browsers without ClipboardItem support 2020-11-12 18:18:50 +01:00
Aron Kovacs
7edf274477 bug fixes and reenable rendering of the helpers 2020-11-12 17:13:24 +01:00
David Sehnal
3c1a26c4f5 basic overlay task support 2020-11-12 14:55:19 +01:00
David Sehnal
1c695846d5 mp4 animation export wip 2020-11-12 14:29:10 +01:00
David Sehnal
a4c6d1e0e6 mp4 export extension wip 2020-11-12 12:20:07 +01:00
David Sehnal
e51fe83800 screenshot controls 2020-11-12 11:56:41 +01:00
David Sehnal
316076d81e screenshot: autocrop 2020-11-12 09:50:46 +01:00
Aron Kovacs
4073055d8d better direct volume frag depth 2020-11-11 22:32:44 +01:00
David Sehnal
c6e0ec1c06 screenshot cropping 2020-11-11 20:33:08 +01:00
Aron Kovacs
49aaa48e6e bug fixes 2020-11-11 18:53:15 +01:00
David Sehnal
0eb882883e screenshot perf improvements 2020-11-11 17:24:12 +01:00
David Sehnal
a6c25551dd screenshot: copy to clipboard 2020-11-11 15:17:19 +01:00
David Sehnal
0a3f73860a mp4 encoder wip 2020-11-11 14:17:52 +01:00
David Sehnal
1de159d65c ImagePass custom viewport 2020-11-10 15:32:55 +01:00
David Sehnal
e2c411fefe pass state to animation teardown 2020-11-10 15:02:00 +01:00
David Sehnal
3cf1c64e12 Camera: fix "non-invertible-matrix" error when clearing state 2020-11-10 13:12:00 +01:00
David Sehnal
b159752b72 elementLabel edgecase 2020-11-10 13:08:06 +01:00
David Sehnal
0d7db59c9e Merge branch 'master' of https://github.com/molstar/molstar into mp4-export 2020-11-10 12:48:07 +01:00
David Sehnal
a8bf90a68b camera spin animation 2020-11-10 11:26:59 +01:00
David Sehnal
96aff39272 Canvas3d: tick manualDraw option 2020-11-10 09:42:51 +01:00
Aron Kovacs
a9ae08fc1f linting fixes 2020-11-09 18:03:08 +01:00
Aron Kovacs
a24f989c01 Merge branch 'master' into wboit 2020-11-09 17:58:23 +01:00
Aron Kovacs
41ff45d14c bug fixes 2020-11-09 17:54:22 +01:00
David Sehnal
6ad80bf66b mp4 encoder animation test 2020-11-09 13:55:14 +01:00
David Sehnal
eeed48a1f7 PluginAnimationLoop 2020-11-09 13:12:06 +01:00
David Sehnal
232bc0d076 wip animation loop 2020-11-09 12:51:55 +01:00
David Sehnal
ac6b87add4 wip animation loop 2020-11-09 12:28:34 +01:00
Alexander Rose
2e3bff7d48 wip, direct-volume rendering
- fix cellDim uniform
- add per unit gaussian-volume
- add option to try to jump over empty space
- fix complex structure visual not reusing renderObject
- add support for instance transforms
- only raymarch within intersection of bounding sphere a clip planes
2020-11-08 17:09:42 -08:00
Alexander Rose
bd223b4c39 added multiSample helper
- fixes multiSample pass wih more than one viewport
2020-11-08 16:00:00 -08:00
Aron Kovacs
a75dc11427 WBOIT init 2020-11-08 20:06:01 +01:00
David Sehnal
30acaffb72 Canvas3d.setCurrentTime 2020-11-06 11:58:33 +01:00
David Sehnal
2818102b8b tweak Camera.updateClip 2020-11-04 21:33:10 +01:00
David Sehnal
519e5a6f92 1.1.31 2020-11-04 20:49:51 +01:00
David Sehnal
071740e7c1 fix "zero radius" bounding sphere issue 2020-11-04 20:45:28 +01:00
David Sehnal
7aafb2f4c3 fix camera.near for small molecules 2020-11-04 19:50:35 +01:00
David Sehnal
3c72988d77 CreateOrbitalRepresentation3D.pickable option 2020-11-04 19:15:49 +01:00
David Sehnal
3c01dfbd42 mp4 export prototype 2020-11-04 14:34:24 +01:00
David Sehnal
3a5829aa3e add mp4 encoder 2020-11-04 09:38:58 +01:00
David Sehnal
ffeeddb37a debug.ts tweak 2020-11-02 21:02:23 +01:00
David Sehnal
50945493c1 fix canvas3d.setProps 2020-11-02 20:58:08 +01:00
David Sehnal
fa80c4797a 1.1.30 2020-11-02 19:19:24 +01:00
David Sehnal
650e8bf703 canvas3d.setProps fixes/improvements 2020-11-02 19:16:29 +01:00
David Sehnal
13d57737ae mol2 schema 2020-11-02 19:09:47 +01:00
David Sehnal
a6d1a3dfdd fix elements bounding spheres 2020-11-02 19:06:48 +01:00
David Sehnal
afffdc06e5 1.1.29 2020-11-02 14:59:32 +01:00
David Sehnal
80f1b1c795 volume-servery: local query add outputFilename param 2020-11-02 14:55:10 +01:00
David Sehnal
06111e2731 1.1.28 2020-11-02 10:45:48 +01:00
David Sehnal
adb49371bb fix build error 2020-11-02 10:43:01 +01:00
Alexander Rose
7b726ded20 gaussian density, render to float texture when available 2020-11-01 17:11:13 -08:00
Alexander Rose
9f85a0c840 better webgl context resource encapsulation
- added .namedComputeRenderables, .namedFramebuffer and .namedTexture
- use for shared resourced instead of storing in closure
- required to have multiple molstar webgl contexts simultaneously
2020-11-01 16:10:13 -08:00
Alexander Rose
f92755c920 factored out Camera.targetDistance 2020-11-01 14:09:16 -08:00
Alexander Rose
d141c27765 wip, direct-volume rendering
- better near/far clipping plane support
- use quality props in volume representations
2020-11-01 14:08:31 -08:00
Alexander Rose
062ac65f0f added pageX & pageY to WheelInput 2020-10-31 15:44:34 -07:00
Alexander Rose
bb420d0806 fix IndexPairBonds created with wrong count
- needs atom count (not bond count)
2020-10-31 15:42:18 -07:00
Alexander Rose
0018032423 detect AS and BR in guessElementSymbol 2020-10-31 15:40:46 -07:00
Alexander Rose
3dd48ac73c render passes refactoring
- simpler viewport handling
- shared render targets
- stereo camera fixes & improvements
2020-10-31 15:35:50 -07:00
David Sehnal
4632a6f305 Merge branch 'master' of https://github.com/molstar/molstar 2020-10-31 20:19:37 +01:00
David Sehnal
eda570d4f1 alpha-orbitals: re-enable cutoff in GPU shader 2020-10-31 20:19:13 +01:00
Alexander Rose
b0127d746d add bool uniform support 2020-10-31 12:10:15 -07:00
Alexander Rose
5a66ca69c4 tweaks/fixes
- avoid using global `name` property
- set Promise return value for TS 4.1 compat
- avoid tuple type names for TS <= 3.9 compat
2020-10-31 11:37:19 -07:00
Alexander Rose
1c17277f03 picking improvements
- get 3d position from depth
- option to render an object only in color pass
2020-10-31 11:34:44 -07:00
David Sehnal
d771bdc8ff Merge branch 'master' into alpha-orbitals 2020-10-31 18:58:55 +01:00
David Sehnal
eace3f4259 alpha-orbitals: webgl1 support 2020-10-31 18:48:05 +01:00
David Sehnal
8f88da70a6 alpha-orbitals: use square texture for GPU comp 2020-10-31 18:11:34 +01:00
David Sehnal
b797be9642 alpha-orbitals: refactoring 2020-10-31 17:59:14 +01:00
David Sehnal
2395b7a10a alpha-orbitals: force CPU computation 2020-10-31 17:08:51 +01:00
David Sehnal
0764795c08 alpha-orbitals: optimization & webgl1 support 2020-10-31 16:52:09 +01:00
David Sehnal
ea8b7a1d56 viewer.loadVolumeFromUrl 2020-10-30 11:03:21 +01:00
David Sehnal
7c6827f5f5 load all models or assembly preset 2020-10-30 10:21:26 +01:00
David Sehnal
5a6f16ef8d viewer: option to disable volume streaming 2020-10-30 10:01:54 +01:00
David Sehnal
8dfdcdd0b7 alpha-orbitals: gpu surface option 2020-10-28 20:04:34 +01:00
David Sehnal
67a2594108 immediate mode slider, alpha-orbitals improvements 2020-10-28 19:16:53 +01:00
David Sehnal
871f9635e3 alpha-orbitals: controls 2020-10-28 18:49:25 +01:00
David Sehnal
25251f3546 Merge pull request #92 from lidaof/master
Add chromosome and region query for g3d format
2020-10-28 16:00:55 +01:00
David Sehnal
98824f477e Merge pull request #94 from JonStargaryen/modelserverfixes
create-table: add protonation variants to cca.bcif
2020-10-27 22:26:36 +01:00
David Sehnal
aae9a117e8 alpha-orbitals: simplify shader 2020-10-27 22:23:13 +01:00
David Sehnal
452639c3ce alpha-orbitals: unused import 2020-10-27 21:41:35 +01:00
David Sehnal
6bd45e0a9b alpha-orbitals: improvements and fixes 2020-10-27 21:28:19 +01:00
David Sehnal
be100a3ac6 alpha-orbitals: optimize iso value computation 2020-10-27 20:09:26 +01:00
David Sehnal
96a8cd789c alpha-orbitals: optimization 2020-10-27 17:37:30 +01:00
David Sehnal
d195e1dbf5 alpha-orbitals: gpu wip 2020-10-27 15:38:08 +01:00
David Sehnal
e6a8e788f5 alpha-orbitals: add unit test 2020-10-26 18:07:57 +01:00
David Sehnal
a755ed441e alpha-orbitals: data model tweak 2020-10-26 15:37:52 +01:00
David Sehnal
de8f294329 alpha-orbitals extension and example 2020-10-24 16:32:40 +02:00
Daofeng Li
021171c07d expose chroms array to G3dInfoData for later data based decoration 2020-10-23 13:36:20 -05:00
David Sehnal
013ddb72ed multisample camera update fix 2020-10-21 09:05:48 +02:00
JonStargaryen
207c226f66 naming 2020-10-20 12:57:31 -07:00
JonStargaryen
b4ff98499b avoid overiding of entries for atom data 2020-10-20 12:36:36 -07:00
JonStargaryen
8471d337a2 add protvar atoms to cca.bcif 2020-10-20 12:31:02 -07:00
David Sehnal
2f84b94227 Basic stereo rendering support 2020-10-20 18:36:20 +02:00
Daofeng Li
8dcd6063b7 update according to @arose's review 2020-10-20 00:08:08 -05:00
Alexander Rose
caefe7ba67 wip, direct-volume rendering
- volume marking
- position iterator
- trilinear position/vertex color interpolation
- fix missing update on traceOnly param change
2020-10-18 22:40:34 -07:00
Alexander Rose
ad6cebc59b canvas3d: fixes for custom pixel-scale & viewport 2020-10-18 13:46:32 -07:00
Daofeng Li
e8d2e6d806 update chain-test as @arose suggested 2020-10-18 10:15:19 -05:00
Alexander Rose
39352c40d1 fix indention 2020-10-18 00:34:40 -07:00
Alexander Rose
9994262abc Merge branch 'master' of https://github.com/molstar/molstar 2020-10-18 00:25:41 -07:00
Alexander Rose
5bcf923381 canvas3d: custom pixel-scale & viewport 2020-10-18 00:25:00 -07:00
Alexander Rose
ab5dd0b733 webgl & canvas3d helpers
- webgl.clear()
- canvas3d.pause()
- canvas3d pickScale adustable on creation
2020-10-17 12:48:01 -07:00
Alexander Rose
5a8a6310f8 webgl resource reuse improvements
- reuse common compute renderables
- add dRenderVariant to existing schema object
2020-10-17 12:18:52 -07:00
Alexander Rose
a634c7a587 adjust lines mapping/vertex indices
- to work well with position iterator for coloring
2020-10-17 11:12:37 -07:00
Alexander Rose
353c5d6d95 set TrajectoryInfo & AsymIdCount to dynamic model property
- fixes model-index coloring
2020-10-17 11:10:49 -07:00
David Sehnal
92698c486c Fix MappedControl edge case 2020-10-15 09:11:06 +02:00
Daofeng Li
898dd1161d Update g3dRegionQuery function params 2020-10-13 09:05:51 -05:00
Daofeng Li
361f289d0e Add chromosome and region query for g3d format 2020-10-13 00:53:45 -05:00
Alexander Rose
b49d036fcd wip, volume rendering
- fix, unset UNPACK_FLIP_Y_WEBGL for 3D tex
- gaussian-volume visual
- fixed, slice visual picking
- fixed, direct-volume renderable picking use depth
2020-10-12 00:35:53 -07:00
Alexander Rose
cfee9d86c0 multi-sample pass improvements & fixes
- use float rt when possible
- test color-float-buffer support
- fix, limit samples per frame
- include camera-helper scene
2020-10-11 23:01:00 -07:00
Alexander Rose
92622dfbd7 fix element loci .toExpression for multi model structures, #56 2020-10-10 16:55:01 -07:00
Alexander Rose
80c2876350 ui, guard focus entry list against too many items 2020-10-10 16:53:21 -07:00
Alexander Rose
fb99f6db8a tighter spheres geo bounding sphere 2020-10-10 16:52:32 -07:00
Alexander Rose
862f8193ef wip, direct-volume-rendering
- support for flip-sided and double-sided
- add single-layer isosurface option
- fix webgl1 depth pass not clearing
- fix slow out-of-bounds access when creating texture
2020-10-04 14:01:42 -07:00
Alexander Rose
490c6679eb add support for vertex colors
use in color theme with
- PositionLocation
- 'vertex'/'vertexInstance' granularity
2020-10-04 00:36:34 -07:00
Alexander Rose
dd278ca964 model & repr update/selection fixes and tweaks 2020-10-03 17:27:41 -07:00
Alexander Rose
0892bb24d0 geometry building improements
- add and use PrimitiveBuilder.addQuad
- avoid namespace lookups for ribbon building
- allow triangluar prism
2020-10-03 11:52:31 -07:00
Alexander Rose
83ce5e9422 add config item for state history-capacity 2020-10-03 11:50:37 -07:00
Alexander Rose
c4ba92c7cb input-observer: allow some non pritable keys 2020-10-03 11:49:53 -07:00
David Sehnal
846bdf10b0 1.1.27 2020-10-03 18:50:04 +02:00
David Sehnal
91a46ea7df another Slider2 fix 2020-10-03 18:47:53 +02:00
David Sehnal
e4ec68a86c 1.1.26 2020-10-03 18:20:04 +02:00
David Sehnal
410655052f fix Slider2 bug 2020-10-03 18:16:06 +02:00
Alexander Rose
17162e967a fix create-ion, add chem-comp-dict util 2020-09-30 23:55:35 -07:00
Alexander Rose
c119a1bc21 Merge pull request #88 from McMenemy/extract_ion_names_from_ccd
Extract ion names from ccd
2020-09-30 23:18:41 -07:00
David Sehnal
7f2e98f714 1.1.25 2020-09-30 18:29:45 +02:00
David Sehnal
82f5d8be21 Merge pull request #89 from JonStargaryen/modelserver-transform
Add transform functionality to ModelServer
2020-09-30 18:24:15 +02:00
JonStargaryen
73fa675346 version & CHANGELOG 2020-09-30 09:15:26 -07:00
JonStargaryen
1f2812b2e3 standardize QueryParamType 2020-09-30 08:56:25 -07:00
JonStargaryen
6f05179db8 remove createMat4 2020-09-30 08:38:12 -07:00
McMenemy
fc8848e97c revert release filter 2020-09-29 21:43:39 -07:00
JonStargaryen
b991102bfa rename param info 2020-09-29 12:24:22 -07:00
JonStargaryen
e3768805a6 rename to transform 2020-09-29 12:21:52 -07:00
JonStargaryen
cdb65665a6 transform 2020-09-29 11:33:05 -07:00
David Sehnal
3765bc410c 1.1.24 2020-09-29 18:58:15 +02:00
David Sehnal
69bbd76f33 mol-plugin: fix initViewer when settings canvas3d props in spec 2020-09-29 18:54:32 +02:00
McMenemy
b61b3e1115 fix linter error 2020-09-29 08:53:26 -07:00
McMenemy
835f717e47 only keep REL ions 2020-09-29 08:35:51 -07:00
McMenemy
6a35a3ece0 change date to 2020 2020-09-28 19:41:45 -07:00
McMenemy
518621a1bd add command to README.md 2020-09-28 16:28:48 -07:00
McMenemy
51acfa1dce revert package-lock.json changes 2020-09-28 16:26:46 -07:00
McMenemy
5be6c9176a move to separate script 2020-09-28 16:23:19 -07:00
David Sehnal
dfa83c94f7 ParamDefinition.ValueRef defaultRef option 2020-09-28 21:12:21 +02:00
David Sehnal
67aedd4770 ParamDefinition.ValueRef 2020-09-28 20:20:20 +02:00
David Sehnal
346eb59da9 color list value offset support 2020-09-28 14:05:42 +02:00
Alexander Rose
3195594ef3 Merge branch 'master' of https://github.com/molstar/molstar 2020-09-27 23:56:54 -07:00
Alexander Rose
489b412308 wip, direct-volume rendering
- separate pass to check against depth texture
- support precalculated normals
- shading in volume mode
- fixed orthographic projection
- fixed skewed grid (David)
- step size prop
- isosurface picking (needs more work)
2020-09-27 23:56:06 -07:00
Alexander Rose
2d0e8d4ca0 geo & repr data update tweaks 2020-09-27 12:12:11 -07:00
Alexander Rose
27f94c81a2 applied event for animation manager 2020-09-27 12:08:48 -07:00
Alexander Rose
1e865ecacc key event for input-observer 2020-09-27 12:08:27 -07:00
McMenemy
f293a02485 better arg help text 2020-09-25 20:13:33 -07:00
McMenemy
ddf00600c6 add command to README.md 2020-09-25 20:00:52 -07:00
McMenemy
88cd639493 add code generated types ion file 2020-09-25 19:37:06 -07:00
McMenemy
0a30ed45f9 first pass working extracting ion names 2020-09-25 19:17:42 -07:00
Josh McMenemy
b5b282c141 Merge pull request #1 from molstar/master
merge molstar master
2020-09-25 16:57:50 -07:00
David Sehnal
c4c60cb263 1.1.23 2020-09-25 19:45:51 +02:00
JonStargaryen
e3cf4e928e propagate 2020-09-21 15:33:10 -07:00
JonStargaryen
d8a08ef900 normalize query param 2020-09-21 13:54:22 -07:00
JonStargaryen
8b8f3bf492 query param def for transformation matrix 2020-09-21 13:28:22 -07:00
David Sehnal
3983073d6c mol-plugin: fix canvas bg color when setting partial renderer params 2020-09-21 12:07:49 +02:00
David Sehnal
82b22fa3f2 fix highlightOn in basic-wrapper example 2020-09-21 11:38:41 +02:00
Alexander Rose
8a38f73771 more approximate math functions 2020-09-19 18:13:08 -07:00
Alexander Rose
37da82b138 atomic detail preset tweaks
- handle structures with low residue to element ratio
2020-09-19 18:10:53 -07:00
Alexander Rose
dd17cb23d9 support label for file param controls; check event.key as well 2020-09-19 18:09:52 -07:00
Alexander Rose
8f3afd9f7c try use spacegroup cell to estimate structure volume 2020-09-19 11:32:17 -07:00
Alexander Rose
6e39188f0b unitcell repr attachment parameter 2020-09-19 11:31:32 -07:00
Alexander Rose
667cacea12 various structure update related fixes- fixes #87- update on hierarchy changes- fix select mark on object update 2020-09-19 11:29:02 -07:00
Alexander Rose
49f0ec981c check js files as well with eslint
- moved ts eslint config into overrides
2020-09-19 11:13:13 -07:00
David Sehnal
a60d6e9223 1.1.22 2020-09-19 11:07:33 +02:00
David Sehnal
2d111c1e25 mol-plugin: ability to ignore loci in highlight/select behavior 2020-09-19 10:55:40 +02:00
Alexander Rose
874cde4f72 1.1.21 2020-09-14 10:09:38 -07:00
David Sehnal
826318760e Canvas3DParams.camera.manualReset 2020-09-14 18:51:24 +02:00
Alexander Rose
de790051aa schema updates
- note that ncs.id has changed from string to number
2020-09-13 02:54:12 -07:00
Alexander Rose
00bf75839e Merge branch 'master' of https://github.com/molstar/molstar 2020-09-13 02:26:38 -07:00
Alexander Rose
b9d4501dcc more fine-grained model/structure/unit updates 2020-09-13 02:25:49 -07:00
Alexander Rose
46fb1789b0 trajectory cell handling improvements 2020-09-12 17:06:58 -07:00
Alexander Rose
a1e8bf841b color theme tweaks, more acurate granularity 2020-09-12 14:53:40 -07:00
Alexander Rose
6662dbfdd6 use unit/structure boundary for interactions geo 2020-09-12 14:51:36 -07:00
Alexander Rose
39b9710d16 fix unneccessary render & geo updates
- uniforms & defines
- bufferSubData instead of bufferData
- scene remove
- drawCount
- geo builder
2020-09-12 14:50:14 -07:00
Alexander Rose
4fe303da72 add missing DMPC lipid 2020-09-12 14:42:19 -07:00
Alexander Rose
0662506d35 add code to experiment with linear RGB workflow 2020-09-12 14:41:23 -07:00
Alexander Rose
ea987f5601 package script tweaks
- serve: use gzip
- state: set default working dir
2020-09-12 14:35:17 -07:00
Alexander Rose
bcae586122 math geo helpers 2020-09-12 14:34:14 -07:00
David Sehnal
e56f188a12 TypeScript 4.0 & update packages 2020-09-12 12:40:15 +02:00
David Sehnal
8fda9beb7b Merge pull request #86 from molstar/dependabot/npm_and_yarn/node-fetch-2.6.1
Bump node-fetch from 2.6.0 to 2.6.1
2020-09-11 17:23:18 +02:00
dependabot[bot]
0f50a6682b Bump node-fetch from 2.6.0 to 2.6.1
Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/bitinn/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-10 20:13:07 +00:00
David Sehnal
524d38c450 fix bounding box bug 2020-09-10 12:43:47 +02:00
Alexander Rose
68a2e52355 increase mediumResidueCount threshold to 5000, #85 2020-09-10 00:31:05 -07:00
David Sehnal
447d1f940f Grid.getHistogram
+ do not exlude any code from main build because it turned off VS Code features in the affected files
2020-09-09 18:38:54 +02:00
David Sehnal
1cbb59b5d0 1.1.20 2020-09-09 17:52:31 +02:00
Alexander Rose
6f5bcdef90 sphere3d extrema fixes 2020-09-08 23:54:54 -07:00
David Sehnal
f6f1c5a350 mol-model: optimize atomGroups query 2020-09-09 08:16:01 +02:00
Alexander Rose
f6545c38be 1.1.19 2020-09-08 01:38:58 -07:00
Alexander Rose
53fe73d3ee Structure.ofModel: merge consecutive water chains 2020-09-08 01:34:20 -07:00
Alexander Rose
3bf5ab1ef7 psf parser: segment name as auth asym id 2020-09-08 01:13:24 -07:00
Alexander Rose
b4813ff866 remove tryAdjustBoundary for now
- not precise enough/too costly
2020-09-08 01:12:43 -07:00
Alexander Rose
845269e9a5 tryAdjustBoundary tweaks 2020-09-08 01:10:50 -07:00
Alexander Rose
59968d92ab 1.1.18 2020-09-07 18:30:23 -07:00
Alexander Rose
d5b7cd370b wip, direct-volume rendering
- re-enabled for volume data
- fix normal calc
- support negative isovalues
- flat-shaded support
- ignore-light support
- support cell picking & marking
- support depth calculation/write
- support fog
- support interior coloring
- maxSteps loop counter as uniform in webgl2
- fix shifted coordinates and boundary issues
- improved geo/repr params
2020-09-07 18:02:07 -07:00
Alexander Rose
b4434cce17 fix picking in webgl1 for isosurface and slice 2020-09-07 17:55:04 -07:00
Alexander Rose
e73227519b factored out/moved common volume helpers
- createIsoValueParam
- eachVolumeLoci
2020-09-07 13:35:37 -07:00
Alexander Rose
f8e6d5cbfb add option to disable hardware antialiasing- wastful for direct volume rendering 2020-09-07 13:29:36 -07:00
Alexander Rose
70bde8c899 bounding sphere calculation for transformed boxes
- add Sphere.fromDimensionsAndTransform
- use for unitcell cage & volume
- precise image bounding sphere
2020-09-07 13:06:23 -07:00
Alexander Rose
361dce2b96 more precise sphere shader sizes 2020-09-07 12:55:56 -07:00
Alexander Rose
c83ce28bf4 no need for fragDepth in image shader 2020-09-05 14:43:24 -07:00
Alexander Rose
b9a3620a4c get EXT_float_blend together with EXT_color_buffer_float
- firfox warns to do that for best support
2020-09-05 14:42:44 -07:00
Alexander Rose
a939a57811 only dispatch hover event on changes
- camera, input changes
- better handle empty picking ids
2020-09-05 14:41:45 -07:00
Alexander Rose
8388ee8f1e make putty repr independent of secondary structure
- yields performance when secondary structure would need to be calculated
2020-09-05 14:37:23 -07:00
Alexander Rose
befa40f5a2 more boundary calc tweaks
- lower thresholds for using coarse calc
2020-09-04 00:18:34 -07:00
Alexander Rose
36257e2b0f fix SelectLoci on object-updated checks 2020-09-03 23:22:02 -07:00
Alexander Rose
769022e88c avoid creating unneccessary visual props objects 2020-09-03 23:20:26 -07:00
Alexander Rose
51c180a8f4 boundary calculation optimizations
- fast path for single transform renderables
- check conformation when remapping unit
2020-09-03 23:18:45 -07:00
Alexander Rose
2ffc5dc5c0 mol-ql: account for source when extending to whole residues 2020-09-03 23:14:18 -07:00
Alexander Rose
431ba01117 check if renderable transform matrix has reflection
- force double-sided draw to ensure the front is drawn
2020-09-03 23:11:03 -07:00
Alexander Rose
c6a4350b81 removed unused Canvas3D.getPixelData
- use .getImagePass instead
2020-09-02 22:22:25 -07:00
Alexander Rose
9ef8d0c9f8 fix missing camera radiusMax update 2020-09-02 22:20:25 -07:00
Alexander Rose
e7606477c2 fix wrong texture3d_from_2d_linear shader chunk path 2020-09-02 22:18:40 -07:00
Alexander Rose
de093b5472 support origin and model as reference for model unitcell representation 2020-09-02 22:14:44 -07:00
Alexander Rose
88cd9184d8 add FormatPropertyProvider.delete 2020-09-02 22:12:14 -07:00
Alexander Rose
055c169c1f add dontCompose arg to StructureBuilder.addWithOperator 2020-09-02 22:11:45 -07:00
Alexander Rose
1988275695 workaround for 1 pixel texture issues
- always use at least 2x2 textures
2020-09-02 22:10:54 -07:00
Alexander Rose
82451bff00 mol2 fixes
- handle multi model files
- fix optional column parsing
- correct type symbol assignment
2020-09-02 22:10:02 -07:00
Alexander Rose
e6fd0202a6 bond & interaction fixes
- add all inter-bond from index-pair (indexA >= indexB is handled by eachUnit loop)
- add inter contacts only once (not twice)
- allow same index contacts when between different units
- more granular each loci marking for interactions and bonds
- use bounding sphere from structure for inter bonds
2020-09-02 22:08:25 -07:00
David Sehnal
c21d84dd62 1.1.17 2020-09-01 12:58:52 +02:00
David Sehnal
f392ac21cd mol-state: transaction rethrowErrors option 2020-09-01 12:55:41 +02:00
David Sehnal
d4e5473b86 1.1.16 2020-09-01 11:45:21 +02:00
David Sehnal
760298c6bf mol-state: fix transation error reporting & improve task abort 2020-09-01 11:41:21 +02:00
David Sehnal
d949b99629 1.1.15 2020-09-01 09:48:16 +02:00
David Sehnal
fff9719e48 mol-plugin: TaskManager.requestAbortAll
+ modified requestAbort to accept taskId
2020-09-01 09:45:06 +02:00
Alexander Rose
5858a6eb19 Merge branch 'master' of https://github.com/molstar/molstar 2020-08-29 14:02:11 -07:00
Alexander Rose
46acc1b95e object controls movement helper 2020-08-29 14:00:03 -07:00
Alexander Rose
da4654b859 use device pixel ratio in trackball panning 2020-08-29 13:54:55 -07:00
Alexander Rose
32869a9a45 added drag event to canvas3d interactivity 2020-08-29 13:52:24 -07:00
Alexander Rose
4cfbccc5d6 added CPU unpackRGBAToDepth 2020-08-28 18:48:45 -07:00
David Sehnal
6301196e67 1.1.14 2020-08-28 10:46:27 +02:00
David Sehnal
0c61c2badd fix multi-model loading & intra bonds caching
- moved atomSourceIndex to a separate property outside the data so diffing works
2020-08-28 10:25:23 +02:00
David Sehnal
1207526161 1.1.13 2020-08-27 13:54:28 +02:00
David Sehnal
405d9d524f measurements & labels options 2020-08-27 13:48:03 +02:00
David Sehnal
f724717821 1.1.12 2020-08-27 12:30:05 +02:00
David Sehnal
6247efa8b6 mol-model: Unit.Atomic.tryRemapBonds 2020-08-27 12:26:20 +02:00
David Sehnal
7045545419 mol-model: check if bonds can be remapped in Unit.Atomic 2020-08-27 08:11:20 +02:00
Alexander Rose
17a0a2be6f support picking threshold with screendoor transparency 2020-08-26 20:29:03 -07:00
Alexander Rose
0e92cfa007 more acurate trackball panning 2020-08-26 20:28:26 -07:00
David Sehnal
04e17872d0 mol-repr: link cylinder offset based on atom radius 2020-08-26 16:48:04 +02:00
Alexander Rose
59255c720d 1.1.11 2020-08-25 18:49:11 -07:00
Alexander Rose
eb71e2c606 use only unit ids in inter unit graph
not units themselves, so the graph can be reused for structures with same topology but different coordinates
2020-08-25 18:44:54 -07:00
Alexander Rose
db2905ba9f ensure themes with dependencies are updated 2020-08-25 18:42:32 -07:00
Alexander Rose
d097a4abd2 use isH in ellipsoid mesh builder 2020-08-25 18:41:09 -07:00
Alexander Rose
459cfd7ab8 avoid using lookup3d when boundary is sufficient 2020-08-25 18:40:24 -07:00
Alexander Rose
317229afee Merge branch 'master' of https://github.com/molstar/molstar 2020-08-25 14:49:31 -07:00
David Sehnal
1e2f16d6b3 1.1.10 2020-08-24 08:52:23 +02:00
David Sehnal
be07c1668f Merge branch 'master' of https://github.com/molstar/molstar 2020-08-23 10:35:06 +02:00
David Sehnal
90ddb3dc34 remove Model.customData 2020-08-23 10:34:43 +02:00
Alexander Rose
f968e86387 move remapModel to structure & pass interUnitBonds 2020-08-22 23:59:21 -07:00
Alexander Rose
56639f0bda use structure.root.boundary for volume 2020-08-22 23:58:41 -07:00
Alexander Rose
8ae40bfd7c get boundary from unit (not lookup) 2020-08-22 23:58:17 -07:00
Alexander Rose
a7901c53ce added approx log 2020-08-22 23:57:20 -07:00
Alexander Rose
76f856fa4f Merge branch 'master' of https://github.com/molstar/molstar 2020-08-22 13:55:33 -07:00
Alexander Rose
63a4cda442 handle in-place structure updates in focus manager 2020-08-22 13:55:24 -07:00
Alexander Rose
603aa89609 increase threshold for tryAdjustBoundary to 5% 2020-08-22 13:54:26 -07:00
Alexander Rose
51dd388912 refactored bond & element ignore test 2020-08-22 13:54:04 -07:00
Alexander Rose
c4708f0260 add derived atomicNumber 2020-08-22 13:02:51 -07:00
Alexander Rose
dcfe2e3072 unit boundary handling improvements
- tryAdjustBoundary from previous boundary
- box3d & boundary tweaks
2020-08-22 12:34:02 -07:00
Alexander Rose
9d536fefff add oldData to in-place State.ObjectEvent 2020-08-22 12:26:55 -07:00
David Sehnal
ed69d15ee1 gaussian surface getUnitExtraRadius 2020-08-22 14:21:25 +02:00
David Sehnal
4cd7f0575e mol-model: Model.customData & G3d support improvements 2020-08-22 14:12:42 +02:00
Alexander Rose
ad1507dadf Merge branch 'master' of https://github.com/molstar/molstar 2020-08-21 11:12:49 -07:00
David Sehnal
c358259437 Improved G3d support 2020-08-21 17:28:19 +02:00
Alexander Rose
6b961c532f avoid unnecessary theme recreation 2020-08-20 17:59:23 -07:00
Alexander Rose
bb8f872a13 removed unused code 2020-08-20 17:58:04 -07:00
Alexander Rose
93df8a65cd add names to anonymous function for easier debugging 2020-08-20 17:55:59 -07:00
Alexander Rose
de9fd2fcd7 increase max allowed fps parameter 2020-08-20 17:55:09 -07:00
Alexander Rose
24ae8dfda8 use array.fill instead of for loop 2020-08-20 17:54:42 -07:00
Alexander Rose
909e4b3a9f perf improvement for intra unit bond visuals
- reorder some if statement to have common case first
- avoid no-op and fixed functions
- const enum
2020-08-20 17:54:22 -07:00
Alexander Rose
09c46447d9 approx exp, use for gaussian summation 2020-08-20 17:50:59 -07:00
Alexander Rose
239a7cc072 small perf tweak for chunked array 2020-08-20 17:49:31 -07:00
Alexander Rose
f7ccff61e0 avoid namespace lookups for better performance
- in performance critical code
2020-08-20 17:48:20 -07:00
Alexander Rose
f8a7483467 mol-geo: add cos sin cache, remove unused wave param 2020-08-20 17:44:05 -07:00
David Sehnal
0a7b3fa396 Fix gaussian surface bounding sphere radius 2020-08-20 21:57:36 +02:00
David Sehnal
e5cf8bcc04 dihedral angle label fix 2020-08-18 21:54:04 +02:00
David Sehnal
f93230ce44 1.1.9 2020-08-18 11:21:21 +02:00
David Sehnal
bdde2cea31 mol-math: IntAdjacencyGraph.areEqual, remove hashCode 2020-08-18 11:17:45 +02:00
David Sehnal
d44bb6c908 mol-model: Fast unit conformation comparison 2020-08-18 11:02:31 +02:00
David Sehnal
052e30b739 1.1.8 2020-08-18 10:14:28 +02:00
Alexander Rose
48a7ac80f5 arg fix 2020-08-17 19:10:38 -07:00
Alexander Rose
c9717c2332 avoid unneccesary marking 2020-08-17 18:58:12 -07:00
Alexander Rose
aaed0a9a63 hashCode for bonds to avoid re-creating repr
- TODO might be better solved with a unique id/version/identity check when unit bond can be reused
2020-08-17 18:57:35 -07:00
Alexander Rose
c29648ac9b more reusable units, added model.parent, finer repr updates 2020-08-17 18:51:22 -07:00
Alexander Rose
cb1f52a8f4 index-pairs bonds fixes & improvements, better cif-core bond handling 2020-08-17 18:38:39 -07:00
Alexander Rose
5f759edc1b optimized cylinder meshBuilder 2020-08-17 18:34:50 -07:00
Alexander Rose
8897261836 decoupled structure.boundary from lookup 2020-08-17 18:34:21 -07:00
David Sehnal
23b1426567 fix computeIntraUnitBonds from IndexPairBonds 2020-08-17 11:37:41 +02:00
David Sehnal
329658ff54 fix computeInterUnitBonds from IndexPairBonds 2020-08-17 07:42:32 +02:00
David Sehnal
482059cc9b first class Trajectory object
- baseline for interpolation and async fetching support
2020-08-14 12:47:41 +02:00
David Sehnal
4e509fc479 Fix clipping object angle (convert to rad) 2020-08-14 11:35:01 +02:00
David Sehnal
6b1edd9d10 basic G3D support 2020-08-13 12:56:42 +02:00
David Sehnal
d0c692fe03 1.1.7 2020-08-06 16:38:55 +02:00
David Sehnal
36ee1089a8 updated packages 2020-08-06 16:34:46 +02:00
David Sehnal
723bf3e657 model-server: 404 response on missing inputs 2020-08-06 10:38:32 +02:00
David Sehnal
dc08e524b3 1.1.6 2020-07-29 17:09:51 +02:00
David Sehnal
3ecd305adf Merge pull request #73 from JonStargaryen/chem-comp
Support export of charges, stereo flag and SYBYL atom types for ModelServer ligand queries
2020-07-29 17:06:36 +02:00
JonStargaryen
60eb42391e use isNonMetalBond 2020-07-28 12:35:59 -07:00
JonStargaryen
26f5210518 make ctx-aware 2020-07-28 12:33:56 -07:00
JonStargaryen
7b41d800c0 use for of to check non-metals 2020-07-28 12:19:50 -07:00
JonStargaryen
3aa9ef7595 rename chem_comp_bond to chem_comp_dict 2020-07-28 11:28:11 -07:00
JonStargaryen
fad5a40ec4 Merge remote-tracking branch 'upstream/master' into chem-comp 2020-07-28 11:27:18 -07:00
JonStargaryen
58e0ba016e rename to atoms/chem_comp and bonds/chem_comp 2020-07-28 11:20:01 -07:00
JonStargaryen
b8f168ebf5 iterators 2020-07-28 10:42:41 -07:00
JonStargaryen
560c26c73f wip mol2 2020-07-27 10:34:35 -07:00
JonStargaryen
73ada6b1f1 rewire mol/sdf 2020-07-27 10:27:30 -07:00
JonStargaryen
4f2cee3b56 introduce map, ditch getLabel 2020-07-27 09:36:57 -07:00
David Sehnal
ad6975c99d 1.1.5 2020-07-27 13:37:10 +02:00
David Sehnal
bea462b8b2 Fix servers build error 2020-07-26 20:52:22 +02:00
David Sehnal
5652fa55db updated packages 2020-07-23 12:27:09 +02:00
David Sehnal
d2208a0814 mol-plugin: createPluginAsync, initial Canvas3DProps in PluginSpec 2020-07-22 12:47:34 +02:00
David Sehnal
62a456ce82 mol-plugin-state: support ArrayBuffer/Uint8Array raw binary data 2020-07-22 12:23:19 +02:00
David Sehnal
bef1142a31 1.1.4 2020-07-21 22:37:29 +02:00
David Sehnal
8e07de62dc fix auth_comp_id property access 2020-07-21 22:33:37 +02:00
JonStargaryen
347b9ead6b add atomTablePath to default config 2020-07-20 11:31:28 -07:00
JonStargaryen
6d9a09620c propagate chem_comp_atom data 2020-07-20 11:28:20 -07:00
JonStargaryen
17349b8529 sybyl atom types 2020-07-20 10:16:37 -07:00
Alexander Rose
3a200fe2d7 add label_seq_id when undefined- serial no, 1-based for each chain, see #72 2020-07-17 23:36:53 -07:00
Alexander Rose
10bd513680 refactored pdb format label_seq_id handling
- leave undefined so it can be added later with hierarchy info
- see #72
2020-07-17 23:31:35 -07:00
Alexander Rose
9214c54e7d add CifField.ofUndefined 2020-07-17 23:28:40 -07:00
Alexander Rose
64da492d63 fix non-standard residue check when filed not given 2020-07-17 21:35:29 -07:00
JonStargaryen
cbb65aaaac fix missing data in bond table 2020-07-17 18:09:30 -07:00
JonStargaryen
78c9dda257 wip sybyl 2020-07-17 17:22:02 -07:00
JonStargaryen
20450e352f wip sybyl 2020-07-17 11:31:40 -07:00
JonStargaryen
9559c22858 mol2 encoder reports bonds again 2020-07-17 11:14:22 -07:00
Alexander Rose
0d39dc69f1 use label asym-id for coloring coarse models (pdbdev), fixes #69 2020-07-14 23:25:14 -07:00
Alexander Rose
1a29159dfd crystal-contacts preset, color theme tweaks 2020-07-14 23:10:40 -07:00
Alexander Rose
8c21d3b9d9 Merge branch 'master' of https://github.com/molstar/molstar 2020-07-13 21:11:19 -07:00
Alexander Rose
24143d7078 build docking-viewer as app
not as example (so it has umd)
2020-07-13 21:11:11 -07:00
Alexander Rose
22ead527f2 expose chain-id color params to element-symbol and illustrative 2020-07-13 21:09:39 -07:00
Alexander Rose
104666a13e reset focus repr color params
to element-symbol, when no theme given
2020-07-13 21:08:02 -07:00
David Sehnal
f9ea48fd7b mol-model: atom_site.B_iso_or_equiv added to mmCIF exporter 2020-07-11 15:19:25 +02:00
JonStargaryen
80598dc102 typo 2020-07-10 13:16:53 -07:00
JonStargaryen
fbaaa57ca2 sybyl criteria 2020-07-10 13:16:22 -07:00
JonStargaryen
b0113d6189 mol2: charges, fix to actually use label_atom_id 2020-07-09 21:27:36 -07:00
JonStargaryen
da1fa03a5f fix mol2 substructure line 2020-07-09 21:18:19 -07:00
JonStargaryen
48d0418f0e mol writing with inline charges and chiral flag 2020-07-09 21:15:54 -07:00
JonStargaryen
cf0122ce23 add component atom 2020-07-09 17:24:43 -07:00
JonStargaryen
7216a25877 creation of charge and stereo config data 2020-07-09 17:08:34 -07:00
David Sehnal
89eae0807e docs fix 2020-07-06 15:13:13 +02:00
Alexander Rose
bfc52fbc6b 1.1.3 2020-07-03 16:04:52 -07:00
Alexander Rose
94cd5d3395 support for recommended iso value volumes
- automaticlly get for volumes with emdb entryId
2020-07-03 16:02:13 -07:00
David Sehnal
30d34584bf model-server: added optional assemblyName param to /residueInteraction 2020-07-03 18:32:59 +02:00
David Sehnal
c81166d04b 1.1.2 2020-07-02 23:09:15 +02:00
David Sehnal
3384a8630b Merge pull request #71 from JonStargaryen/sdf
SDF/MOL/MOL2 export by ModelServer
2020-07-02 23:03:00 +02:00
JonStargaryen
e39c7c4e98 move encoders to mol-io 2020-07-02 09:41:41 -07:00
JonStargaryen
38e838a352 remove label 2020-07-02 09:19:14 -07:00
JonStargaryen
cea22c0ea1 use spread 2020-07-02 09:18:52 -07:00
David Sehnal
6b73361963 mol-plugin-ui: added ViewportCanvas component 2020-07-02 14:00:26 +02:00
David Sehnal
096a4ee63e mol-plugin: volume streaming 'auto' view mode 2020-07-02 13:46:58 +02:00
JonStargaryen
28c8d6bef9 description 2020-07-01 13:28:19 -07:00
JonStargaryen
37fdbfe12a omit redundant code 2020-07-01 10:35:13 -07:00
David Sehnal
295cb84cc8 Fix docking-viewer presets 2020-07-01 12:50:24 +02:00
Alexander Rose
b5252516e3 added docking-viewer example
- expects a pdbqt and a mol2 file in the url get params
2020-06-30 21:57:25 -07:00
Alexander Rose
8e2c0327d6 added Viewport.ShowControls/Settings options to plugin config 2020-06-30 21:28:53 -07:00
Alexander Rose
eaa92b75a3 tweak, use const instead of let 2020-06-30 21:18:34 -07:00
Alexander Rose
ab81c89a9a bool param for mark repr in Highlight/SelectLoci behaviors 2020-06-30 21:18:02 -07:00
Alexander Rose
e49af151c1 use mol2 mol_name for entity name when available 2020-06-30 21:16:11 -07:00
Alexander Rose
78ca5cbb43 use chain-id coloring as base for illiustrative coloring 2020-06-30 21:15:26 -07:00
Alexander Rose
48c242d59d fix chain-id coloring for multi-model structures 2020-06-30 21:14:41 -07:00
JonStargaryen
8b4603d5a1 semicolon 2020-06-30 10:05:42 -07:00
JonStargaryen
cc6dce8845 numModels param for first model 2020-06-30 09:37:35 -07:00
JonStargaryen
b2eac8092c cleanup 2020-06-29 15:33:36 -07:00
JonStargaryen
a22362bac8 hydrogen flag 2020-06-29 15:32:37 -07:00
JonStargaryen
298b283686 revert config 2020-06-29 15:24:01 -07:00
JonStargaryen
14f4de2e3f revert config 2020-06-29 15:23:02 -07:00
JonStargaryen
9bde4c40b0 enforce first model 2020-06-29 11:22:35 -07:00
JonStargaryen
61390cb64f group by query 2020-06-29 10:00:26 -07:00
Alexander Rose
1b67bc41f5 explicit pdbqt provider
- instead of trying to deduce it during parsing
2020-06-26 22:04:27 -07:00
Alexander Rose
5b698b816e basic partial charge and pdbt support
- AtomPartialCharge format property provider
- add from PDBQT and MOL2
- color theme
2020-06-26 00:36:21 -07:00
Alexander Rose
bf9303ea80 number parser fix, handle prefixed plus sign 2020-06-26 00:33:47 -07:00
JonStargaryen
017676c148 path TODO 2020-06-25 16:31:53 -07:00
JonStargaryen
526f7700b2 cleanup 2020-06-25 16:05:43 -07:00
JonStargaryen
220e01106f lint 2020-06-25 10:35:25 -07:00
JonStargaryen
240de5b24d cleanup 2020-06-25 10:30:30 -07:00
JonStargaryen
17a001427b spelling 2020-06-25 09:57:48 -07:00
JonStargaryen
a423970b9c basic ligand writing 2020-06-25 09:53:22 -07:00
JonStargaryen
56f4c8775f cleanup 2020-06-24 16:17:31 -07:00
JonStargaryen
5ed4aa0fae SdfFile tweaks 2020-06-24 14:08:25 -07:00
JonStargaryen
4f4245b895 sdf def, reader, spec 2020-06-24 12:23:31 -07:00
JonStargaryen
bc6d2112e2 refactoring 2020-06-23 16:41:38 -07:00
JonStargaryen
3b2b87d264 tweaks to sdf output 2020-06-23 09:01:15 -07:00
Alexander Rose
181646f052 membrane orientation tweaks
- registered custom repr so it shows up in components
- some renames to make it more in line with the other extensions
- fix shapes to reuse existing geometries (fails to update on prop changes otherwise)
- added label to repr visuals on hover/highlight
- ignore light forbilayer planes mesh
- TODO: layer spheres seem to be broken (e.g. 3pqr)
2020-06-23 01:08:58 -07:00
Alexander Rose
ea5a945810 Merge pull request #47 from JonStargaryen/anvil
ANVIL / Membrane placement prediction implementation
2020-06-22 23:09:59 -07:00
JonStargaryen
7cfdc8ab1b lint 2020-06-22 14:58:03 -07:00
JonStargaryen
52e011ade9 wip query mode 2020-06-22 14:53:34 -07:00
JonStargaryen
895e2ede2d mol2 stub 2020-06-22 09:49:23 -07:00
JonStargaryen
3a25769a94 style 2020-06-22 09:26:38 -07:00
JonStargaryen
5db4b48a86 putative check for compatible encoding 2020-06-22 09:25:41 -07:00
Alexander Rose
aa2899bfbd fix sequence label for non-standard residues, #70 2020-06-21 18:54:10 -07:00
JonStargaryen
40bbd87c4e unique group check 2020-06-19 20:12:30 -07:00
JonStargaryen
fb7dd66600 cleanup 2020-06-19 17:45:00 -07:00
JonStargaryen
e12dc2b089 tweak mapping 2020-06-19 17:37:40 -07:00
JonStargaryen
78242b18c3 hydrogen handling 2020-06-19 15:28:51 -07:00
JonStargaryen
9043e4c8e1 propagate atoms only if present 2020-06-19 12:05:16 -07:00
JonStargaryen
54b5e3a0cc hydrogen option 2020-06-19 10:19:23 -07:00
JonStargaryen
b6719a2f57 always write error category 2020-06-19 09:57:23 -07:00
JonStargaryen
087d5fbb68 stub for sdf writing 2020-06-18 21:12:06 -07:00
JonStargaryen
4f60c91256 impl is-transmembrane symbol correctly 2020-06-17 15:49:33 -07:00
JonStargaryen
99415ef290 reintroduce custom prop 2020-06-17 15:02:15 -07:00
JonStargaryen
d446a2d047 fix serialmapping 2020-06-17 13:07:22 -07:00
JonStargaryen
b476f738bc style of symbol description 2020-06-17 13:05:54 -07:00
JonStargaryen
4c0d1383b2 fix label_comp_id retrieval for color themes 2020-06-16 15:24:44 -07:00
JonStargaryen
6a924bf732 ui label 2020-06-16 11:18:20 -07:00
JonStargaryen
5e1f1220af cleanup 2020-06-16 11:09:42 -07:00
JonStargaryen
d13ee0a2cc ui integration 2020-06-16 10:54:55 -07:00
JonStargaryen
826127672a move symbol 2020-06-15 17:26:25 -07:00
JonStargaryen
df8dd7278a label_comp_id move 2020-06-15 16:38:34 -07:00
JonStargaryen
cbdc4a3e9d drop DefaultQueryRuntimeTable, wip moving 2020-06-15 16:11:07 -07:00
JonStargaryen
75266ad257 merge 2020-06-15 13:30:27 -07:00
JonStargaryen
0e93374b2d add to extensions 2020-06-15 13:25:52 -07:00
JonStargaryen
8e350617f2 wip sdf 2020-06-15 12:18:10 -07:00
Alexander Rose
f6acf0a60a 1.1.1 2020-06-14 20:21:24 -07:00
Alexander Rose
7982f25a45 ignoreHydrogens option for StructureFocusRepresentation
- also updated by StructureComponentManager
2020-06-13 17:03:25 -07:00
Alexander Rose
ddafa7aac1 added todo for gro parser regarding reusing static model parts 2020-06-13 17:01:22 -07:00
Alexander Rose
65946c3045 support for multi-model gro files 2020-06-13 13:24:42 -07:00
Alexander Rose
63d699d620 lower threshold for coarse boundary helper 2020-06-13 12:39:25 -07:00
Alexander Rose
e1170c0552 fix debug helper update missing 2020-06-13 12:30:55 -07:00
Alexander Rose
97612bf044 dcd nd coordinates fixes/tweaks
- cell optional
- elementCount only on frame
2020-06-13 11:58:34 -07:00
Alexander Rose
012ac33bd5 ignore water units for inter-unit bond calculation by default 2020-06-13 11:56:54 -07:00
Alexander Rose
272e208fd4 add ion & lipid as chem_comp type, charmm ion names 2020-06-12 20:11:29 -07:00
Alexander Rose
714f0623bb tweaked repr preset to better handle md systems 2020-06-12 20:10:33 -07:00
Alexander Rose
5327962409 support ignoreHydrogens & traceOnly for points visual 2020-06-11 20:28:22 -07:00
Alexander Rose
7c3529ae30 wip testing: don't partition
- partitioning does'nt agree with trajectories of water/lipid molecules...
2020-06-10 19:53:37 -07:00
Alexander Rose
61c63df9e9 ignore H-H index-pair bonds 2020-06-10 19:52:00 -07:00
Alexander Rose
9b6fcaeb79 lines fixes and tweaks 2020-06-10 19:51:35 -07:00
Alexander Rose
ef3f035f27 allow int for ply vertex property 2020-06-10 17:46:27 -07:00
Alexander Rose
e9bc67fbf4 add .visuals to PlyProvider 2020-06-10 17:46:12 -07:00
Alexander Rose
745f2aecf8 fix props destructuring in ShapeRepresentation3D 2020-06-10 17:45:55 -07:00
Alexander Rose
eff80ad5ff line representation 2020-06-09 18:57:48 -07:00
Alexander Rose
27ef44b833 throwing in hasNdbStructNtcCategories breaks non-mmcif files 2020-06-09 17:29:29 -07:00
JonStargaryen
9d0190c11c color 2020-06-09 11:53:39 -07:00
JonStargaryen
0069687233 simplify params 2020-06-09 10:49:47 -07:00
David Sehnal
478033d405 mol-plugin: added excludeTargetFromSurroundings to Focus behavior 2020-06-09 19:27:56 +02:00
JonStargaryen
58f57d5ad2 typo 2020-06-09 09:24:24 -07:00
JonStargaryen
3f80230f4f membrane orientation behavior 2020-06-09 09:14:11 -07:00
JonStargaryen
da35f0ea16 additional membrane layer scaling param 2020-06-05 11:28:26 -07:00
JonStargaryen
72bdd5bc05 asaCutoff param 2020-06-05 11:20:16 -07:00
JonStargaryen
255cc620a5 dashedLines param 2020-06-05 11:16:07 -07:00
Alexander Rose
4753271a6d 1.1.0 2020-06-04 18:02:00 -07:00
Alexander Rose
11c7024edd tweaks for dnatco confal pyramids
- added Michal and Jiří as contributors
- add preset to annotation group
- legend for color theme
- add as extension to viewer
2020-06-02 17:49:50 -07:00
Alexander Rose
47ba54199f Merge pull request #39 from MadCatX/confal_v2
Confal v2
2020-06-02 17:29:42 -07:00
JonStargaryen
cd7643e79b params 2020-06-02 09:22:02 -07:00
Michal Malý
9a797e39e5 Add README for DNATCO extensions 2020-06-02 09:42:43 +02:00
Michal Malý
7898840003 Add a preset for DNATCO Confal Pyramids representation 2020-06-02 09:01:34 +02:00
Michal Malý
8e91cb6d54 Implement Loci getter in DNATCO Confal Pyramids representation 2020-06-02 09:01:32 +02:00
Michal Malý
893401f7c4 Implement DNATCO Confal Pyramids color theme 2020-06-02 09:00:29 +02:00
Michal Malý
021e6ffeb5 Implement DNATCO Confal Pyramids location iterator 2020-06-02 09:00:27 +02:00
Michal Malý
4080c1e005 Add DNATCO Confal Pyramids representation provider 2020-06-02 08:55:24 +02:00
Michal Malý
5063e99761 Implement basic structure of DNATCO Confal Pyramids extension 2020-06-02 08:50:38 +02:00
Alexander Rose
a59f6546c5 more comp_id fixes 2020-06-01 18:13:10 -07:00
Alexander Rose
3a737099ad add micro het todos and example pdb id 2020-06-01 17:49:47 -07:00
Alexander Rose
d1f76fd48e check all micro het comp ids for isNonStandard prop 2020-06-01 17:44:22 -07:00
Alexander Rose
437d52e484 fix comp_id issues from making it a atom prop, #66 2020-06-01 17:34:57 -07:00
JonStargaryen
610977cc08 params 2020-06-01 15:36:51 -07:00
JonStargaryen
6c78adb353 adds visuals 2020-06-01 10:25:57 -07:00
Alexander Rose
abef75bfa2 fixed sequence label regression
- show comp id only when non-standard, i.e. code is X
2020-05-30 14:47:51 -07:00
Alexander Rose
8137d4acdb fix spelling of isNonStandardCrystalFrame 2020-05-30 13:38:35 -07:00
Alexander Rose
899a186808 take micoheterogeneity into account in getUniqueResidueNames 2020-05-30 13:36:42 -07:00
Alexander Rose
bb7d3e075c change comp_id to be atom property to support micro heterogeneity 2020-05-30 13:32:49 -07:00
David Sehnal
78ba9df263 tweak 2020-05-28 19:15:21 +02:00
David Sehnal
9126416389 mol-plugin: structure repr preset updates
- added "theme" object to params
- added carbonByChainId option
- added Structure.DefaultRepresentationPresetParams plugin config option
2020-05-28 18:58:05 +02:00
David Sehnal
eef944b617 StructureFocusRepresentationParams fix 2020-05-28 14:12:08 +02:00
Alexander Rose
208cc2e48e schema updates and cli path fixes 2020-05-27 17:46:11 -07:00
David Sehnal
1522bf4ae4 package.json cli fix 2020-05-27 22:07:50 +02:00
David Sehnal
422f4567f1 full commonjs build for servers and cli tools 2020-05-27 20:37:12 +02:00
David Sehnal
6c8ae32ff9 mol-plugin: do not update focus theme if not set 2020-05-27 19:46:03 +02:00
David Sehnal
e67c610b84 mol-plugin: fix Volume streaming when clearing state 2020-05-27 19:40:00 +02:00
David Sehnal
96aa003702 mol-plugin: focusTheme params for presets 2020-05-27 19:35:53 +02:00
JonStargaryen
f6262f4be5 remove density param 2020-05-27 10:28:51 -07:00
JonStargaryen
efc5bf6a45 use set 2020-05-27 09:41:11 -07:00
JonStargaryen
cbc9801477 cleanup 2020-05-27 09:40:01 -07:00
Alexander Rose
94e21e8a3a min resolution in repr quality 2020-05-26 17:35:17 -07:00
JonStargaryen
2ed118604c cleanup 2020-05-26 15:38:15 -07:00
JonStargaryen
219d4f4d33 more generic options 2020-05-26 11:16:51 -07:00
Alexander Rose
a937e3c57d lipid query and component 2020-05-25 14:24:10 -07:00
Alexander Rose
9fe16e321e add script to generate martini lipid names 2020-05-25 14:23:55 -07:00
Alexander Rose
697e9986b4 can't ignore atoms with zero occupancy
- is too often the case when just not set
2020-05-25 14:10:40 -07:00
Alexander Rose
613cdc3145 fix building of cli tools 2020-05-25 13:46:46 -07:00
David Sehnal
a2bf489017 fix bond ordering when creating structure from Topology 2020-05-25 03:52:35 +02:00
David Sehnal
aca49b9ba5 PSF parser fix, correct topology/coordinates ordering (wip) 2020-05-25 03:12:14 +02:00
David Sehnal
11d5e301f3 xtc format support
+ build tweaks
2020-05-24 17:44:29 +02:00
Alexander Rose
2797b451fb add interesting pdb entries info 2020-05-22 17:10:03 -07:00
JonStargaryen
e82918db6a membrane orientation representation 2020-05-22 13:53:18 -07:00
Alexander Rose
e0b98f70f0 1.0.5 2020-05-22 10:30:11 -07:00
Alexander Rose
297b9bd3ff update focus color in repr presets 2020-05-22 10:27:31 -07:00
Alexander Rose
013a59857d updated packages
- set "@typescript-eslint/ban-types": "off" (TODO investigate)
- remove node 8 in travis
2020-05-22 09:34:56 -07:00
David Sehnal
0b14381255 mol-io: XTC parser 2020-05-22 16:32:32 +02:00
Alexander Rose
e1e0b0f2da 1.0.4 2020-05-21 16:20:31 -07:00
Alexander Rose
2142290300 add prd_id as entity property, treat prd as ligand 2020-05-21 16:12:09 -07:00
Alexander Rose
aeb7c7033d 0.7.1-dev.13 2020-05-21 11:15:11 -07:00
Alexander Rose
0f8540e7fc trace iterator fixes 2020-05-21 11:08:25 -07:00
Alexander Rose
f14b57fe30 don't split small single atom polymer chains 2020-05-21 11:08:04 -07:00
Alexander Rose
abfb1d5992 ensure sec struct for putty repr 2020-05-21 10:16:51 -07:00
Alexander Rose
35c53b27fe 0.7.1-dev.12 2020-05-21 00:33:50 -07:00
Alexander Rose
a7f144e810 Merge branch 'master' of https://github.com/molstar/molstar 2020-05-21 00:31:40 -07:00
Alexander Rose
f4cb3aeed7 default coloring improvments
- changed chain-id theme to assign distinct color accros the whole scene
- made chain-id the default
- added 'many-distinct' color list
- changed element-symbol theme to use cahin-id theme for carbon coloring
- indicate focus repr with translucent halo
2020-05-21 00:31:29 -07:00
Alexander Rose
bceb044552 add structure-info behavior and plugin-wide properties
- Model.AsymIdOffset
- Model.Index
- Structure.Index
2020-05-21 00:26:44 -07:00
Alexander Rose
5e41e959f8 custom property improvements
- isHidden (always attached)
- getParams uses current props as defaultValues
- add .createSimple to model & structure property provider
2020-05-21 00:23:59 -07:00
Alexander Rose
c95d54f9cd param-definition: fix areEqual, add setDefaultValues 2020-05-21 00:21:30 -07:00
Alexander Rose
8149a25ad4 fix unit cell spelling 2020-05-20 14:13:26 -07:00
Alexander Rose
d500393501 added Model.TrajectoryInfo
- replaces model.trajectoryInfo
2020-05-20 13:07:34 -07:00
David Sehnal
9d2fa3e749 mol-plugin: StructureFocus component customization 2020-05-20 20:32:59 +02:00
JonStargaryen
ee886244fc begin separating calc and representation 2020-05-20 10:45:57 -07:00
JonStargaryen
f2557fe80a refactor membrane orientation 2020-05-20 10:01:51 -07:00
JonStargaryen
bf904a5b32 OPM stub 2020-05-20 09:53:25 -07:00
Alexander Rose
76a5ce8f14 Merge branch 'master' of https://github.com/molstar/molstar 2020-05-20 09:34:25 -07:00
JonStargaryen
1ff83d9648 parameters 2020-05-20 09:26:09 -07:00
Alexander Rose
d121ed8b6c always calculate stats when all stats related values are zero, fixes #48 2020-05-20 01:52:23 -07:00
JonStargaryen
6c27deed74 unused import 2020-05-19 16:53:48 -07:00
JonStargaryen
7e2d15f329 unused import 2020-05-19 16:52:58 -07:00
JonStargaryen
10bd7853f3 move ASA request 2020-05-19 16:52:17 -07:00
JonStargaryen
4a7bfe953c package-lock 2020-05-19 16:34:47 -07:00
JonStargaryen
3598d13a3d package-lock 2020-05-19 16:33:51 -07:00
JonStargaryen
96ac561279 merge 2020-05-19 16:30:39 -07:00
JonStargaryen
2c81267ca7 avoid overlap 2020-05-19 16:26:21 -07:00
JonStargaryen
395fa5dad1 less sphere points 2020-05-19 16:10:39 -07:00
JonStargaryen
d15340e62e typo 2020-05-19 16:04:05 -07:00
JonStargaryen
eab4c08836 reintroduce ASA calc 2020-05-19 15:59:37 -07:00
JonStargaryen
69c5bf0094 lint 2020-05-19 15:54:55 -07:00
JonStargaryen
6c112f83e8 cleanup 2020-05-19 15:50:12 -07:00
JonStargaryen
f49c34c551 cleanup 2020-05-19 15:41:52 -07:00
Alexander Rose
3ab4458cb2 add OT1 and OT2 to protein backbone atoms- commonly used 2020-05-19 15:34:40 -07:00
JonStargaryen
ad3c07c634 use correct score 2020-05-19 14:49:47 -07:00
JonStargaryen
68525c2109 fix bugs 2020-05-19 12:07:02 -07:00
JonStargaryen
9e9851472d debugging 2020-05-19 10:31:42 -07:00
JonStargaryen
c0edb27323 circumvent repr provider for now 2020-05-18 17:30:16 -07:00
JonStargaryen
0a70783b5e draft for ANVIL impl 2020-05-18 17:29:09 -07:00
Alexander Rose
a3d101cdf9 0.7.1-dev.11 2020-05-18 17:18:46 -07:00
Alexander Rose
7255e08ecf tweak combined color ui
- use short label
2020-05-18 17:16:51 -07:00
Alexander Rose
b1bdb8e66b fix partial polymer trace curve calculation 2020-05-18 16:20:50 -07:00
Alexander Rose
49c8c7f396 focus manager: only union loci when from same structure 2020-05-18 15:21:24 -07:00
Alexander Rose
d3b4280589 ensure transformed structure is used in camera.focusLoci 2020-05-18 14:51:36 -07:00
Alexander Rose
bb9acaaa9c fix intra-interactions visual doubly transformed 2020-05-18 14:13:40 -07:00
Alexander Rose
fc10b9bf7b ensure rendering after scene changes
- add/remove/update representations
2020-05-18 11:55:15 -07:00
JonStargaryen
5fcb495d24 centroid and extent calculation 2020-05-18 11:40:27 -07:00
Alexander Rose
b2fdcba674 mouse control tweak
- always do camera center/focus on right click
2020-05-18 11:14:07 -07:00
Alexander Rose
0d01948ba9 0.7.1-dev.10 2020-05-18 08:59:56 -07:00
Alexander Rose
c4370670cb changed nucleic trace atom to O3' 2020-05-16 22:21:10 -07:00
Alexander Rose
1b19136c18 remove alpha/opacity from overpaint params
- does not play well with layers and freely assigning colors as it is per representation
2020-05-16 11:52:27 -07:00
Alexander Rose
217e983da8 add transparency to component theme 2020-05-16 11:37:54 -07:00
Alexander Rose
57338bdad1 ui, always delete full component
- one click less, undo to get back
2020-05-15 19:44:46 -07:00
Alexander Rose
0e84bf9513 component ui related tweaks and fixes
- theme reset should only act on selection
- on-screen ui act similar to controls-ui
- stick to component as name consistently
2020-05-15 19:07:05 -07:00
Alexander Rose
0be28cacdf support change of component label 2020-05-15 19:05:05 -07:00
Alexander Rose
017c608439 simplified cmara/focus binding defaults 2020-05-15 16:53:35 -07:00
Alexander Rose
20ee9496e3 add lighten/darken buttons to CombinedColorControl 2020-05-15 16:52:52 -07:00
Alexander Rose
6fd81d0961 0.7.1-dev.9 2020-05-15 12:08:03 -07:00
Alexander Rose
823a68f9bf package updates 2020-05-15 12:05:50 -07:00
Alexander Rose
deab18e805 only apply structure focus for appropriate granularity 2020-05-15 11:53:37 -07:00
Alexander Rose
19016b6730 seperated camera and repr focus more; always do camera focus with primary-alt 2020-05-15 11:39:21 -07:00
Alexander Rose
4319ae251c 0.7.1-dev.8 2020-05-15 10:25:29 -07:00
Alexander Rose
e5920e29b4 improved presets for coarse-grained structures 2020-05-15 09:58:57 -07:00
Alexander Rose
c376ddfc9d Merge branch 'master' of https://github.com/molstar/molstar 2020-05-14 22:59:46 -07:00
Alexander Rose
8fe2d3f724 Merge branch 'master' of https://github.com/molstar/molstar 2020-05-14 22:58:32 -07:00
Alexander Rose
4d7a128528 clip object support for non mesh geometries
- spheres
- lines
- points
- text
2020-05-14 22:57:57 -07:00
Alexander Rose
663ec9695e fixes for structure focus & selection 2020-05-14 17:18:20 -07:00
JonStargaryen
5e6eb7ed49 tmp vis of asa 2020-05-14 16:38:20 -07:00
Alexander Rose
dfaa4dacdb 0.7.1-dev.7 2020-05-14 16:26:27 -07:00
Alexander Rose
f7adb8b589 fix icon typo 2020-05-14 16:24:50 -07:00
Alexander Rose
cb6b1bf19d 0.7.1-dev.6 2020-05-14 16:09:37 -07:00
Alexander Rose
27a4e1d7d9 icon tweaks 2020-05-14 16:06:38 -07:00
JonStargaryen
2a9f6c88a0 modify structure-test 2020-05-14 15:25:11 -07:00
776 changed files with 130360 additions and 25432 deletions

View File

@@ -3,60 +3,21 @@
"browser": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["tsconfig.json", "tsconfig.servers.json"],
"sourceType": "module"
"ecmaVersion": 2018,
"sourceType": "module",
"ecmaFeatures": {
"impliedStrict": true
}
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/ban-types": "warn",
"@typescript-eslint/class-name-casing": "off",
"indent": "off",
"@typescript-eslint/indent": [
"error",
4
],
"@typescript-eslint/member-delimiter-style": [
"off",
{
"multiline": {
"delimiter": "none",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
],
"@typescript-eslint/prefer-namespace-keyword": "warn",
"@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true,
"allowTemplateLiterals": true
}
],
"@typescript-eslint/semi": [
"off",
null
],
"@typescript-eslint/type-annotation-spacing": "error",
"arrow-parens": [
"off",
"as-needed"
],
"brace-style": "off",
"@typescript-eslint/brace-style": [
"error",
"1tbs", { "allowSingleLine": true }
],
"comma-spacing": "off",
"@typescript-eslint/comma-spacing": "error",
"space-infix-ops": "error",
"comma-dangle": "off",
"eqeqeq": [
@@ -70,6 +31,66 @@
"no-unsafe-finally": "warn",
"no-var": "error",
"spaced-comment": "error",
"semi": "warn"
}
"semi": "warn",
"no-restricted-syntax": [
"error",
{
"selector": "ExportDefaultDeclaration",
"message": "Default exports are not allowed"
}
]
},
"overrides": [
{
"files": ["**/*.ts", "**/*.tsx"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["tsconfig.json", "tsconfig.commonjs.json"],
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/class-name-casing": "off",
"@typescript-eslint/indent": [
"error",
4
],
"@typescript-eslint/member-delimiter-style": [
"off",
{
"multiline": {
"delimiter": "none",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
],
"@typescript-eslint/prefer-namespace-keyword": "warn",
"@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true,
"allowTemplateLiterals": true
}
],
"@typescript-eslint/semi": [
"off",
null
],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/brace-style": [
"error",
"1tbs", { "allowSingleLine": true }
],
"@typescript-eslint/comma-spacing": "error"
}
}
]
}

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ node_modules/
debug.log
npm-debug.log
tsconfig.tsbuildinfo
tsconfig.commonjs.tsbuildinfo
*.sublime-workspace
.idea

View File

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

View File

@@ -14,6 +14,5 @@ before_install:
node_js:
- "12"
- "10"
- "8"
before_script:
- export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start

View File

@@ -41,7 +41,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 `apps` (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
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).
## Previous Work
This project builds on experience from previous solutions:
@@ -90,19 +90,28 @@ and navigate to `build/viewer`
### Code generation
**CIF schemas**
node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF
node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD
node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD
node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/cif-core.ts -p CifCore -aa
node ./lib/commonjs/cli/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF
node ./lib/commonjs/cli/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD
node ./lib/commonjs/cli/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD
node ./lib/commonjs/cli/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/cif-core.ts -p CifCore -aa
**Lipid names**
node lib/commonjs/cli/lipid-params -o src/mol-model/structure/model/types/lipids.ts
**Ion names**
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-ions.js src/mol-model/structure/model/types/ions.ts
**GraphQL schemas**
./node_modules/.bin/graphql-codegen -c ./src/extensions/rcsb/graphql/codegen.yml
node node_modules//@graphql-codegen/cli/bin -c src/extensions/rcsb/graphql/codegen.yml
### Other scripts
**Create chem comp bond table**
node --max-old-space-size=4096 lib/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-dict/create-table.js build/data/ccb.bcif -b
**Test model server**
@@ -120,7 +129,7 @@ To see all available commands, use ``node lib/servers/model/preprocess -h``.
Or
node ./lib/apps/cif2bcif
node lib/commonjs/cli/cif2bcif
## Development
@@ -168,3 +177,4 @@ Funding sources include but are not limited to:
* [RCSB PDB](https://www.rcsb.org) funding by a grant [DBI-1338415; PI: SK Burley] from the NSF, the NIH, and the US DoE
* [PDBe, EMBL-EBI](https://pdbe.org)
* [CEITEC](https://www.ceitec.eu/)
* [EntosAI](https://www.entos.ai) (``alpha-orbitals`` extension)

View File

@@ -0,0 +1,28 @@
* Cyclic polymers (1sfi, 6dny, 1HVZ)
* B-DNA (1bna)
* Missing carbonyl oxygen (1gfl)
* Mono-saccharides with alt locs (1B5F)
* Microheterogeneity
* Protein (1EJG, 3NIR)
* DNA (3VOK)
* PNA: peptide nucleic acid (5eme, 1xj9)
* Peptide derived residues
* GFP chromophores (5Z6Y)
* Nucleotides that dont have a parent base set, i.e. detect purine/pyrimidine from geometry (THX in 1AUL, OMC in e.g. 5D3G)
* Bases with modified ring atoms
* DZ has C1 instead of N1 (e.g. 6I4N)
* DP has N5 instead of C5 and C7 instead of N7 (e.g. 6I4N)
* Beta & Gamma peptides (e.g. 1GAC, 6PQF)
* Mixed (heterogeneous) all-atom/trace-only RNA model (1JGQ)
* Polymers with residues with missing trace atoms (e.g. 2QFJ)
* Modified RNA bases (1y26, 5L4O)
* Discontinuous chains, i.e. gaps in the sequence (3sn6)
* Lots of sheets (1cbs)
* DNA (2np2, 1d66)
* C-alpha only (2rcj)
* Not cyclic, but termini are backbone-only and within distance but seqIds are not compatible (6SW3)
* Close backbone atoms but not linked (e.g. 4HIV)
* Non-standard residues
* Protein (1BRR, 5Z6Y)
* DNA (5D3G)
* Multiple models with different sets of ligands or missing ligands (1J6T, 1VRC, 2ICY, 1O2F)

View File

@@ -24,7 +24,7 @@ npm run build-tsc
and run the server by
```
node lib/servers/model/server/server
node lib/commonjs/servers/model/server/server
```
## From NPM
@@ -54,12 +54,12 @@ Sometimes nodejs might run into problems with memory. This is usually resolved b
## Preprocessor
The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. ``node lib/servers/servers/model/preprocess`` or ``model-server-preprocess`` binary from the NPM package.
The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. ``node lib/commonjs/servers/model/preprocess`` or ``model-server-preprocess`` binary from the NPM package.
## Local Mode
The server can be run in local/file based mode using ``node lib/servers/servers/model/query`` (``model-server-query`` binary from the NPM package).
The server can be run in local/file based mode using ``node lib/commonjs/servers/model/query`` (``model-server-query`` binary from the NPM package).
Custom Properties
=================

View File

@@ -28,7 +28,7 @@ npm run build-tsc
and run the server by
```
node lib/servers/servers/volume/server
node lib/commonjs/servers/volume/server
```
## From NPM
@@ -60,11 +60,11 @@ Sometimes nodejs might run into problems with memory. This is usually resolved b
## Preparing the Data
For the server to work, CCP4/MAP (models 0, 1, 2 are supported) input data need to be converted into a custom block format.
To achieve this, use the ``pack`` application (``node lib/servers/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
To achieve this, use the ``pack`` application (``node lib/commonjs/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
## Local Mode
The program ``lib/servers/servers/volume/pack`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
The program ``lib/commonjs/servers/volume/pack`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
## Navigating the Source Code

46783
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.7.1-dev.5",
"version": "2.0.0-dev.3",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -11,12 +11,12 @@
"url": "https://github.com/molstar/molstar/issues"
},
"scripts": {
"lint": "eslint ./**/*.{ts,tsx}",
"lint-fix": "eslint ./**/*.{ts,tsx} --fix",
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"test": "npm run lint && 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": "tsc --incremental && tsc --build tsconfig.servers.json --incremental",
"build-tsc": "concurrently \"tsc --incremental\" \"tsc --build tsconfig.commonjs.json --incremental\"",
"build-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/",
"build-webpack": "webpack --mode production --config ./webpack.config.production.js",
"build-webpack-viewer": "webpack --mode production --config ./webpack.config.viewer.js",
@@ -24,16 +24,16 @@
"watch-viewer": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer\"",
"watch-viewer-debug": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer-debug\"",
"watch-tsc": "tsc --watch --incremental",
"watch-servers": "tsc --build tsconfig.servers.json --watch --incremental",
"watch-servers": "tsc --build tsconfig.commonjs.json --watch --incremental",
"watch-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/ --watch",
"watch-webpack": "webpack -w --mode development --display minimal",
"watch-webpack-viewer": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.js",
"watch-webpack-viewer-debug": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.debug.js",
"serve": "http-server -p 1338",
"model-server": "node lib/servers/servers/model/server.js",
"model-server-watch": "nodemon --watch lib lib/servers/servers/model/server.js",
"volume-server-test": "node lib/servers/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
"plugin-state": "node lib/servers/servers/plugin-state/index.js",
"serve": "http-server -p 1338 -g",
"model-server": "node lib/commonjs/servers/model/server.js",
"model-server-watch": "nodemon --watch lib lib/commonjs/servers/model/server.js",
"volume-server-test": "node lib/commonjs/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
"plugin-state": "node lib/commonjs/servers/plugin-state/index.js --working-folder ./build/state --port 1339",
"preversion": "npm run test",
"version": "npm run build",
"postversion": "git push && git push --tags"
@@ -43,14 +43,14 @@
"build/viewer/"
],
"bin": {
"cif2bcif": "lib/apps/cif2bcif/index.js",
"cifschema": "lib/apps/cifschema/index.js",
"model-server": "lib/servers/servers/model/server.js",
"model-server-query": "lib/servers/servers/model/query.js",
"model-server-preprocess": "lib/servers/servers/model/preprocess.js",
"volume-server": "lib/servers/servers/volume/server.js",
"volume-server-query": "lib/servers/servers/volume/query.js",
"volume-server-pack": "lib/servers/servers/volume/pack.js"
"cif2bcif": "lib/commonjs/cli/cif2bcif/index.js",
"cifschema": "lib/commonjs/cli/cifschema/index.js",
"model-server": "lib/commonjs/servers/model/server.js",
"model-server-query": "lib/commonjs/servers/model/query.js",
"model-server-preprocess": "lib/commonjs/servers/model/preprocess.js",
"volume-server": "lib/commonjs/servers/volume/server.js",
"volume-server-query": "lib/commonjs/servers/volume/query.js",
"volume-server-pack": "lib/commonjs/servers/volume/pack.js"
},
"nodemonConfig": {
"ignoreRoot": [
@@ -80,67 +80,71 @@
"Alexander Rose <alexander.rose@weirdbyte.de>",
"David Sehnal <david.sehnal@gmail.com>",
"Sebastian Bittrich <sebastian.bittrich@rcsb.org>",
"Ludovic Autin <autin@scripps.edu>"
"Áron Samuel Kovács <aron.kovacs@mail.muni.cz>",
"Ludovic Autin <autin@scripps.edu>",
"Michal Malý <michal.maly@ibt.cas.cz>",
"Jiří Černý <jiri.cerny@ibt.cas.cz>"
],
"license": "MIT",
"devDependencies": {
"@graphql-codegen/add": "^1.13.5",
"@graphql-codegen/cli": "^1.13.5",
"@graphql-codegen/time": "^1.13.5",
"@graphql-codegen/typescript": "^1.13.5",
"@graphql-codegen/typescript-graphql-files-modules": "^1.13.5",
"@graphql-codegen/typescript-graphql-request": "^1.13.5",
"@graphql-codegen/typescript-operations": "^1.13.5",
"@types/cors": "^2.8.6",
"@typescript-eslint/eslint-plugin": "^2.32.0",
"@typescript-eslint/parser": "^2.32.0",
"@graphql-codegen/add": "^2.0.2",
"@graphql-codegen/cli": "^1.19.4",
"@graphql-codegen/time": "^2.0.2",
"@graphql-codegen/typescript": "^1.19.0",
"@graphql-codegen/typescript-graphql-files-modules": "^1.18.1",
"@graphql-codegen/typescript-graphql-request": "^2.0.3",
"@graphql-codegen/typescript-operations": "^1.17.12",
"@types/cors": "^2.8.8",
"@typescript-eslint/eslint-plugin": "^4.9.1",
"@typescript-eslint/parser": "^4.9.1",
"benchmark": "^2.1.4",
"concurrently": "^5.2.0",
"cpx2": "^2.0.0",
"css-loader": "^3.5.3",
"eslint": "^7.0.0",
"concurrently": "^5.3.0",
"cpx2": "^3.0.0",
"css-loader": "^5.0.1",
"eslint": "^7.15.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.0.0",
"fs-extra": "^9.0.0",
"graphql": "^15.0.0",
"file-loader": "^6.2.0",
"fs-extra": "^9.0.1",
"graphql": "^15.4.0",
"http-server": "^0.12.3",
"jest": "^26.0.1",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.14.1",
"raw-loader": "^4.0.1",
"sass-loader": "^8.0.2",
"simple-git": "^2.4.0",
"style-loader": "^1.2.1",
"ts-jest": "^25.5.1",
"typescript": "^3.8.3",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"jest": "^26.6.3",
"mini-css-extract-plugin": "^1.3.2",
"node-sass": "^5.0.0",
"raw-loader": "^4.0.2",
"sass-loader": "^10.1.0",
"simple-git": "^2.25.0",
"style-loader": "^2.0.0",
"ts-jest": "^26.4.4",
"typescript": "^4.1.2",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-version-file-plugin": "^0.4.0"
},
"dependencies": {
"@types/argparse": "^1.0.38",
"@types/benchmark": "^1.0.31",
"@types/benchmark": "^2.1.0",
"@types/compression": "1.7.0",
"@types/express": "^4.17.6",
"@types/jest": "^25.2.1",
"@types/node": "^13.13.5",
"@types/express": "^4.17.9",
"@types/jest": "^26.0.18",
"@types/node": "^14.14.11",
"@types/node-fetch": "^2.5.7",
"@types/react": "^16.9.35",
"@types/react-dom": "^16.9.8",
"@types/swagger-ui-dist": "3.0.5",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/swagger-ui-dist": "3.30.0",
"argparse": "^1.0.10",
"body-parser": "^1.19.0",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.17.1",
"immer": "^6.0.5",
"h264-mp4-encoder": "^1.0.12",
"immer": "^8.0.0",
"immutable": "^3.8.2",
"node-fetch": "^2.6.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"rxjs": "^6.5.5",
"swagger-ui-dist": "^3.25.2",
"tslib": "^1.11.2",
"node-fetch": "^2.6.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rxjs": "^6.6.3",
"swagger-ui-dist": "^3.37.2",
"tslib": "^2.0.3",
"util.promisify": "^1.0.1",
"xhr2": "^0.2.0"
}

View File

@@ -4,40 +4,40 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
const git = require('simple-git')
const path = require('path')
const fs = require("fs")
const fse = require("fs-extra")
const git = require('simple-git');
const path = require('path');
const fs = require("fs");
const fse = require("fs-extra");
const remoteUrl = "https://github.com/molstar/molstar.github.io.git"
const buildDir = path.resolve(__dirname, '../build/')
const deployDir = path.resolve(buildDir, 'deploy/')
const localPath = path.resolve(deployDir, 'molstar.github.io/')
const remoteUrl = "https://github.com/molstar/molstar.github.io.git";
const buildDir = path.resolve(__dirname, '../build/');
const deployDir = path.resolve(buildDir, 'deploy/');
const localPath = path.resolve(deployDir, 'molstar.github.io/');
function log(command, stdout, stderr) {
if (command) {
console.log('\n###', command)
stdout.pipe(process.stdout)
stderr.pipe(process.stderr)
console.log('\n###', command);
stdout.pipe(process.stdout);
stderr.pipe(process.stderr);
}
}
function copyViewer() {
console.log('\n###', 'copy viewer files')
const viewerBuildPath = path.resolve(buildDir, '../build/viewer/')
const viewerDeployPath = path.resolve(localPath, 'viewer/')
fse.copySync(viewerBuildPath, viewerDeployPath, { overwrite: true })
console.log('\n###', 'copy viewer files');
const viewerBuildPath = path.resolve(buildDir, '../build/viewer/');
const viewerDeployPath = path.resolve(localPath, 'viewer/');
fse.copySync(viewerBuildPath, viewerDeployPath, { overwrite: true });
}
if (!fs.existsSync(localPath)) {
console.log('\n###', 'create localPath')
fs.mkdirSync(localPath, { recursive: true })
console.log('\n###', 'create localPath');
fs.mkdirSync(localPath, { recursive: true });
}
process.chdir(localPath);
if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
console.log('\n###', 'clone repository')
console.log('\n###', 'clone repository');
git()
.outputHandler(log)
.clone(remoteUrl, localPath)
@@ -45,9 +45,9 @@ if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
.exec(copyViewer)
.add(['-A'])
.commit('updated viewer')
.push()
.push();
} else {
console.log('\n###', 'update repository')
console.log('\n###', 'update repository');
git()
.outputHandler(log)
.fetch(['--all'])
@@ -55,5 +55,5 @@ if (!fs.existsSync(path.resolve(localPath, '.git/'))) {
.exec(copyViewer)
.add(['-A'])
.commit('updated viewer')
.push()
.push();
}

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* Docking Viewer</title>
<style>
#app {
position: absolute;
left: 100px;
top: 100px;
width: 800px;
height: 600px;
}
</style>
<link rel="stylesheet" type="text/css" href="molstar.css" />
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="./molstar.js"></script>
<script type="text/javascript">
var viewer = new DockingViewer('app', [0x33DD22, 0x1133EE], true);
function getParam(name, regex) {
var r = new RegExp(name + '=' + '(' + regex + ')[&]?', 'i');
return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || '');
}
var pdbqt = getParam('pdbqt', '[^&]+').trim();
var mol2 = getParam('mol2', '[^&]+').trim();
viewer.loadStructuresFromUrlsAndMerge([
{ url: pdbqt, format: 'pdbqt' },
{ url: mol2, format: 'mol2' }
]);
</script>
</body>
</html>

View File

@@ -0,0 +1,213 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
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 { PluginBehaviors } from '../../mol-plugin/behavior';
import { ColorNames } from '../../mol-util/color/names';
import { Color } from '../../mol-util/color';
require('mol-plugin-ui/skin/light.scss');
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setProductionMode, setDebugMode } from '../../mol-util/debug';
const DefaultViewerOptions = {
extensions: ObjectKeys({}),
layoutIsExpanded: true,
layoutShowControls: true,
layoutShowRemoteState: true,
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
layoutShowSequence: true,
layoutShowLog: true,
layoutShowLeftPanel: true,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
};
class Viewer {
plugin: PluginContext
constructor(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
const o = { ...DefaultViewerOptions, ...{
layoutIsExpanded: false,
layoutShowControls: false,
layoutShowRemoteState: false,
layoutShowSequence: true,
layoutShowLog: false,
layoutShowLeftPanel: true,
viewportShowExpand: true,
viewportShowControls: false,
viewportShowSettings: false,
viewportShowSelectionMode: false,
viewportShowAnimation: false,
} };
const defaultSpec = DefaultPluginSpec();
const spec: PluginSpec = {
actions: [...defaultSpec.actions],
behaviors: [
PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci, { mark: false }),
PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
PluginSpec.Behavior(PluginBehaviors.Camera.FocusLoci),
PluginSpec.Behavior(PluginBehaviors.CustomProps.StructureInfo),
PluginSpec.Behavior(PluginBehaviors.CustomProps.Interactions),
PluginSpec.Behavior(PluginBehaviors.CustomProps.SecondaryStructure),
],
animations: [...defaultSpec.animations || []],
customParamEditors: defaultSpec.customParamEditors,
layout: {
initial: {
isExpanded: o.layoutIsExpanded,
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,
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
viewport: {
view: ViewportComponent
}
},
config: [
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
[PluginConfig.State.DefaultServer, o.pluginStateServer],
[PluginConfig.State.CurrentServer, o.pluginStateServer],
[PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
[PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
[ShowButtons, showButtons]
]
};
const element = typeof elementOrId === 'string'
? document.getElementById(elementOrId)
: elementOrId;
if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
this.plugin = createPlugin(element, spec);
(this.plugin.customState as any) = {
colorPalette: {
name: 'colors',
params: { list: { colors } }
}
};
this.plugin.behaviors.canvas3d.initialized.subscribe(v => {
if (v) {
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
renderer: {
...this.plugin.canvas3d!.props.renderer,
backgroundColor: ColorNames.white,
},
camera: {
...this.plugin.canvas3d!.props.camera,
helper: { axes: { name: 'off', params: {} } }
}
} });
}
});
}
async loadStructuresFromUrlsAndMerge(sources: { url: string, format: BuiltInTrajectoryFormat, isBinary?: boolean }[]) {
const structures: { ref: string }[] = [];
for (const { url, format, isBinary } of sources) {
const data = await this.plugin.builders.data.download({ url, isBinary });
const trajectory = await this.plugin.builders.structure.parseTrajectory(data, format);
const model = await this.plugin.builders.structure.createModel(trajectory);
const modelProperties = await this.plugin.builders.structure.insertModelProperties(model);
const structure = await this.plugin.builders.structure.createStructure(modelProperties || model);
const structureProperties = await this.plugin.builders.structure.insertStructureProperties(structure);
structures.push({ ref: structureProperties?.ref || structure.ref });
}
// remove current structuresfrom hierarchy as they will be merged
// TODO only works with using loadStructuresFromUrlsAndMerge once
// need some more API metho to work with the hierarchy
this.plugin.managers.structure.hierarchy.updateCurrent(this.plugin.managers.structure.hierarchy.current.structures, 'remove');
const dependsOn = structures.map(({ ref }) => ref);
const data = this.plugin.state.data.build().toRoot().apply(MergeStructures, { structures }, { dependsOn });
const structure = await data.commit();
const structureProperties = await this.plugin.builders.structure.insertStructureProperties(structure);
this.plugin.behaviors.canvas3d.initialized.subscribe(async v => {
await this.plugin.builders.structure.representation.applyPreset(structureProperties || structure, StructurePreset);
});
}
}
type MergeStructures = typeof MergeStructures
const MergeStructures = PluginStateTransform.BuiltIn({
name: 'merge-structures',
display: { name: 'Merge Structures', description: 'Merge Structure' },
from: PSO.Root,
to: PSO.Molecule.Structure,
params: {
structures: PD.ObjectList({
ref: PD.Text('')
}, ({ ref }) => ref, { isHidden: true })
}
})({
apply({ params, dependencies }) {
return Task.create('Merge Structures', async ctx => {
if (params.structures.length === 0) return StateObject.Null;
const first = dependencies![params.structures[0].ref].data as Structure;
const builder = Structure.Builder({ masterModel: first.models[0] });
for (const { ref } of params.structures) {
const s = dependencies![ref].data as Structure;
for (const unit of s.units) {
// TODO invariantId
builder.addUnit(unit.kind, unit.model, unit.conformation.operator, unit.elements, unit.traits);
}
}
const structure = builder.getStructure();
return new PSO.Molecule.Structure(structure, { label: 'Merged Structure' });
});
}
});
(window as any).DockingViewer = Viewer;
export { Viewer as DockingViewer };

View File

@@ -0,0 +1,276 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @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 { StructureRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { Color } from '../../mol-util/color';
import { PluginConfig } from '../../mol-plugin/config';
function shinyStyle(plugin: PluginContext) {
return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
renderer: {
...plugin.canvas3d!.props.renderer,
style: { name: 'plastic', params: {} },
},
postprocessing: {
...plugin.canvas3d!.props.postprocessing,
occlusion: { name: 'off', params: {} },
outline: { name: 'off', params: {} }
}
} });
}
function occlusionStyle(plugin: PluginContext) {
return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
renderer: {
...plugin.canvas3d!.props.renderer,
style: { name: 'flat', params: {} }
},
postprocessing: {
...plugin.canvas3d!.props.postprocessing,
occlusion: { name: 'on', params: {
samples: 64,
radius: 8,
bias: 1.0,
blurKernelSize: 13
} },
outline: { name: 'on', params: {
scale: 1.0,
threshold: 0.33
} }
}
} });
}
const ligandPlusSurroundings = StructureSelectionQuery('Surrounding Residues (5 \u212B) of Ligand plus Ligand itself', MS.struct.modifier.union([
MS.struct.modifier.includeSurroundings({
0: StructureSelectionQueries.ligand.expression,
radius: 5,
'as-whole-residues': true
})
]));
const ligandSurroundings = StructureSelectionQuery('Surrounding Residues (5 \u212B) of Ligand', MS.struct.modifier.union([
MS.struct.modifier.exceptBy({
0: ligandPlusSurroundings.expression,
by: StructureSelectionQueries.ligand.expression
})
]));
const PresetParams = {
...StructureRepresentationPresetProvider.CommonParams,
};
export const StructurePreset = StructureRepresentationPresetProvider({
id: 'preset-structure',
display: { name: 'Structure' },
params: () => PresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const components = {
ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
};
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
};
await update.commit({ revertOnError: true });
await shinyStyle(plugin);
plugin.managers.interactivity.setProps({ granularity: 'residue' });
return { components, representations };
}
});
export const IllustrativePreset = StructureRepresentationPresetProvider({
id: 'preset-illustrative',
display: { name: 'Illustrative' },
params: () => PresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const components = {
ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
};
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'spacefill', typeParams: { ...typeParams }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'spacefill', typeParams: { ...typeParams }, color: 'illustrative', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
};
await update.commit({ revertOnError: true });
await occlusionStyle(plugin);
plugin.managers.interactivity.setProps({ granularity: 'residue' });
return { components, representations };
}
});
const SurfacePreset = StructureRepresentationPresetProvider({
id: 'preset-surface',
display: { name: 'Surface' },
params: () => PresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const structure = structureCell?.obj?.data;
if (!structureCell || !structure) return {};
const components = {
ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
};
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
polymer: builder.buildRepresentation(update, components.polymer, { type: 'molecular-surface', typeParams: { ...typeParams, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }),
};
await update.commit({ revertOnError: true });
await shinyStyle(plugin);
plugin.managers.interactivity.setProps({ granularity: 'residue' });
return { components, representations };
}
});
const PocketPreset = StructureRepresentationPresetProvider({
id: 'preset-pocket',
display: { name: 'Pocket' },
params: () => PresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const structure = structureCell?.obj?.data;
if (!structureCell || !structure) return {};
const components = {
ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
surroundings: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandSurroundings, `surroundings`),
};
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
surroundings: builder.buildRepresentation(update, components.surroundings, { type: 'molecular-surface', typeParams: { ...typeParams, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }),
};
await update.commit({ revertOnError: true });
await shinyStyle(plugin);
plugin.managers.interactivity.setProps({ granularity: 'element' });
return { components, representations };
}
});
const InteractionsPreset = StructureRepresentationPresetProvider({
id: 'preset-interactions',
display: { name: 'Interactions' },
params: () => PresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const structure = structureCell?.obj?.data;
if (!structureCell || !structure) return {};
const components = {
ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
surroundings: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandSurroundings, `surroundings`),
interactions: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandPlusSurroundings, `interactions`)
};
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
const representations = {
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
};
await update.commit({ revertOnError: true });
await shinyStyle(plugin);
plugin.managers.interactivity.setProps({ granularity: 'element' });
return { components, representations };
}
});
export const ShowButtons = PluginConfig.item('showButtons', true);
export class ViewportComponent extends PluginUIComponent {
async _set(structures: readonly StructureRef[], preset: StructureRepresentationPresetProvider) {
await this.plugin.managers.structure.component.clear(structures);
await this.plugin.managers.structure.component.applyPreset(structures, preset);
}
set = async (preset: StructureRepresentationPresetProvider) => {
await this._set(this.plugin.managers.structure.hierarchy.selection.structures, preset);
}
structurePreset = () => this.set(StructurePreset);
illustrativePreset = () => this.set(IllustrativePreset);
surfacePreset = () => this.set(SurfacePreset);
pocketPreset = () => this.set(PocketPreset);
interactionsPreset = () => this.set(InteractionsPreset);
get showButtons () {
return this.plugin.config.get(ShowButtons);
}
render() {
const VPControls = this.plugin.spec.components?.viewport?.controls || ViewportControls;
return <>
<Viewport />
{this.showButtons && <div className='msp-viewport-top-left-controls'>
<div style={{ marginBottom: '4px' }}>
<Button onClick={this.structurePreset} >Structure</Button>
</div>
<div style={{ marginBottom: '4px' }}>
<Button onClick={this.illustrativePreset}>Illustrative</Button>
</div>
<div style={{ marginBottom: '4px' }}>
<Button onClick={this.surfacePreset}>Surface</Button>
</div>
{/* <div style={{ marginBottom: '4px' }}>
<Button onClick={this.pocketPreset}>Pocket</Button>
</div> */}
<div style={{ marginBottom: '4px' }}>
<Button onClick={this.interactionsPreset}>Interactions</Button>
</div>
</div>}
<VPControls />
<BackgroundTaskProgress />
<div className='msp-highlight-toast-wrapper'>
<LociLabels />
<Toasts />
</div>
</>;
}
}

View File

@@ -36,28 +36,8 @@
emdbProvider: 'rcsb',
});
viewer.loadPdb('7bv2');
viewer.loadEmdb('EMD-30210');
// TODO add Volume.customProperty and load suggested isoValue via custom property
var sub = viewer.plugin.managers.volume.hierarchy.behaviors.selection.subscribe(function (value) {
if (value.volume?.representations[0]) {
var ref = value.volume.representations[0].cell;
var tree = viewer.plugin.state.data.build().to(ref).update({
type: {
name: 'isosurface',
params: {
isoValue: {
kind: 'relative',
relativeValue: 6
}
}
},
colorTheme: ref.transform.params?.colorTheme
});
viewer.plugin.runTask(viewer.plugin.state.data.updateTree(tree));
if (typeof sub !== 'undefined') sub.unsubscribe();
}
});
viewer.loadEmdb('EMD-30210', { detail: 6 });
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
</script>
</body>
</html>

View File

@@ -46,12 +46,18 @@
}
var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
if (debugMode) molstar.setDebugMode(debugMode);
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 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,
pdbProvider: pdbProvider || 'pdbe',

View File

@@ -6,7 +6,8 @@
*/
import '../../mol-util/polyfill';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import { createPlugin } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import './index.html';
import './embedded.html';
import './favicon.ico';
@@ -24,20 +25,41 @@ 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 { DnatcoConfalPyramids } from '../../extensions/dnatco';
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
import { Color } from '../../mol-util/color';
import { StateObjectSelector } from '../../mol-state';
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';
require('mol-plugin-ui/skin/light.scss');
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setProductionMode, setDebugMode } from '../../mol-util/debug';
const CustomFormats = [
['g3d', G3dProvider] as const
];
const Extensions = {
'cellpack': PluginSpec.Behavior(CellPack),
'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids),
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport)
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
'g3d': PluginSpec.Behavior(G3DFormat),
'mp4-export': PluginSpec.Behavior(Mp4Export)
};
const DefaultViewerOptions = {
customFormats: CustomFormats as [string, DataFormatProvider][],
extensions: ObjectKeys(Extensions),
layoutIsExpanded: true,
layoutShowControls: true,
@@ -46,12 +68,18 @@ const DefaultViewerOptions = {
layoutShowSequence: true,
layoutShowLog: true,
layoutShowLeftPanel: true,
disableAntialiasing: false,
pixelScale: 1,
enableWboit: true,
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue,
viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue,
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue,
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
};
@@ -62,15 +90,17 @@ export class Viewer {
constructor(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
const o = { ...DefaultViewerOptions, ...options };
const defaultSpec = DefaultPluginSpec();
const spec: PluginSpec = {
actions: [...DefaultPluginSpec.actions],
actions: [...defaultSpec.actions],
behaviors: [
...DefaultPluginSpec.behaviors,
...defaultSpec.behaviors,
...o.extensions.map(e => Extensions[e]),
],
animations: [...DefaultPluginSpec.animations || []],
customParamEditors: DefaultPluginSpec.customParamEditors,
animations: [...defaultSpec.animations || []],
customParamEditors: defaultSpec.customParamEditors,
customFormats: o?.customFormats,
layout: {
initial: {
isExpanded: o.layoutIsExpanded,
@@ -78,23 +108,29 @@ export class Viewer {
controlsDisplay: o.layoutControlsDisplay,
},
controls: {
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls,
...defaultSpec.layout && defaultSpec.layout.controls,
top: o.layoutShowSequence ? undefined : 'none',
bottom: o.layoutShowLog ? undefined : 'none',
left: o.layoutShowLeftPanel ? undefined : 'none',
}
},
components: {
...DefaultPluginSpec.components,
...defaultSpec.components,
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
},
config: [
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
[PluginConfig.General.PixelScale, o.pixelScale],
[PluginConfig.General.EnableWboit, o.enableWboit],
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
[PluginConfig.Viewport.ShowControls, o.viewportShowControls],
[PluginConfig.Viewport.ShowSettings, o.viewportShowSettings],
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
[PluginConfig.State.DefaultServer, o.pluginStateServer],
[PluginConfig.State.CurrentServer, o.pluginStateServer],
[PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
[PluginConfig.VolumeStreaming.Enabled, !o.volumeStreamingDisabled],
[PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider]
]
@@ -116,7 +152,7 @@ export class Viewer {
return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
}
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false) {
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
@@ -125,19 +161,28 @@ export class Viewer {
url: Asset.Url(url),
format: format as any,
isBinary,
options: params.source.params.options,
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
}
}
}));
}
async loadAllModelsOrAssemblyFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) {
const plugin = this.plugin;
const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } });
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'all-models', { useDefaultIfSingleModel: true, representationPresetParams: options?.representationParams });
}
async loadStructureFromData(data: string | number[], format: BuiltInTrajectoryFormat, options?: { dataLabel?: string }) {
const _data = await this.plugin.builders.data.rawData({ data, label: options?.dataLabel });
const trajectory = await this.plugin.builders.structure.parseTrajectory(_data, format);
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
}
loadPdb(pdb: string) {
loadPdb(pdb: string, options?: LoadStructureOptions) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!;
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
@@ -151,7 +196,7 @@ export class Viewer {
params: PdbDownloadProvider[provider].defaultValue as any
}
},
options: params.source.params.options,
options: { ...params.source.params.options, representationParams: options?.representationParams as any },
}
}
}));
@@ -173,7 +218,7 @@ export class Viewer {
}));
}
loadEmdb(emdb: string) {
loadEmdb(emdb: string, options?: { detail?: number }) {
const provider = this.plugin.config.get(PluginConfig.Download.DefaultEmdbProvider)!;
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, {
source: {
@@ -183,9 +228,52 @@ export class Viewer {
id: emdb,
server: provider,
},
detail: 3,
detail: options?.detail ?? 3,
}
}
}));
}
async loadVolumeFromUrl(url: string, format: BuildInVolumeFormat, isBinary: boolean, isovalues: VolumeIsovalueInfo[], entryId?: string) {
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 } });
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId });
const volume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
if (!volume?.isOk) throw new Error('Failed to parse any volume.');
const repr = plugin.build().to(volume);
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 } },
color: 'uniform',
colorParams: { value: iso.color }
}));
}
await repr.commit();
});
}
handleResize() {
this.plugin.layout.events.updated.next();
}
}
export interface LoadStructureOptions {
representationParams?: StructureRepresentationPresetProvider.CommonParams
}
export interface VolumeIsovalueInfo {
type: 'absolute' | 'relative',
value: number,
color: Color,
alpha?: number
}

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env node
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Josh McMenemy <josh.mcmenemy@gmail.com>
*/
import * as argparse from 'argparse';
import * as path from 'path';
import util from 'util';
import fs from 'fs';
require('util.promisify').shim();
const writeFile = util.promisify(fs.writeFile);
import { DatabaseCollection } from '../../mol-data/db';
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
import { ensureDataAvailable, readCCD } from './util';
function extractIonNames(ccd: DatabaseCollection<CCD_Schema>) {
const ionNames: string[] = [];
for (const k in ccd) {
const {chem_comp} = ccd[k];
if (chem_comp.name.value(0).toUpperCase().includes(' ION')) {
ionNames.push(chem_comp.id.value(0));
}
}
// these are extra ions that don't have ION in their name
ionNames.push('NCO', 'OHX');
return ionNames;
}
function writeIonNamesFile(filePath: string, ionNames: string[]) {
const output = `/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated ion names params file. Names extracted from CCD components.
*
* @author molstar/chem-comp-dict/create-table cli
*/
export const IonNames = new Set(${JSON.stringify(ionNames).replace(/"/g, "'").replace(/,/g, ', ')});
`;
writeFile(filePath, output);
}
async function run(out: string, forceDownload = false) {
await ensureDataAvailable(forceDownload);
const ccd = await readCCD();
const ionNames = extractIonNames(ccd);
if (!fs.existsSync(path.dirname(out))) {
fs.mkdirSync(path.dirname(out));
}
writeIonNamesFile(out, ionNames);
}
const parser = new argparse.ArgumentParser({
addHelp: true,
description: 'Extract and save IonNames from CCD.'
});
parser.addArgument('out', {
help: 'Generated file output path.'
});
parser.addArgument([ '--forceDownload', '-f' ], {
action: 'storeTrue',
help: 'Force download of CCD and PVCD.'
});
interface Args {
out: string,
forceDownload?: boolean,
}
const args: Args = parser.parseArgs();
run(args.out, args.forceDownload);

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -8,69 +9,16 @@ import * as argparse from 'argparse';
import * as util from 'util';
import * as path from 'path';
import * as fs from 'fs';
import * as zlib from 'zlib';
import fetch from 'node-fetch';
require('util.promisify').shim();
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
import { Progress } from '../../mol-task';
import { Database, Table, DatabaseCollection } from '../../mol-data/db';
import { CIF } from '../../mol-io/reader/cif';
import { CifWriter } from '../../mol-io/writer/cif';
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
import { SetUtils } from '../../mol-util/set';
import { DefaultMap } from '../../mol-util/map';
import { mmCIF_chemCompBond_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
export async function ensureAvailable(path: string, url: string) {
if (FORCE_DOWNLOAD || !fs.existsSync(path)) {
console.log(`downloading ${url}...`);
const data = await fetch(url);
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR);
}
if (url.endsWith('.gz')) {
await writeFile(path, zlib.gunzipSync(await data.buffer()));
} else {
await writeFile(path, await data.text());
}
console.log(`done downloading ${url}`);
}
}
export async function ensureDataAvailable() {
await ensureAvailable(CCD_PATH, CCD_URL);
await ensureAvailable(PVCD_PATH, PVCD_URL);
}
export async function readFileAsCollection<S extends Database.Schema>(path: string, schema: S) {
const parsed = await parseCif(await readFile(path, 'utf8'));
return CIF.toDatabaseCollection(schema, parsed.result);
}
export async function readCCD() {
return readFileAsCollection(CCD_PATH, CCD_Schema);
}
export async function readPVCD() {
return readFileAsCollection(PVCD_PATH, CCD_Schema);
}
async function parseCif(data: string | Uint8Array) {
const comp = CIF.parse(data);
console.time('parse cif');
const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
console.timeEnd('parse cif');
if (parsed.isError) throw parsed;
return parsed;
}
export function getEncodedCif(name: string, database: Database<Database.Schema>, binary = false) {
const encoder = CifWriter.createEncoder({ binary, encoderName: 'mol*' });
CifWriter.Encoder.writeDatabase(encoder, name, database);
return encoder.getData();
}
import { ccd_chemCompAtom_schema } from '../../mol-io/reader/cif/schema/ccd-extras';
import { ensureDataAvailable, getEncodedCif, readCCD, readPVCD } from './util';
type CCB = Table<CCD_Schema['chem_comp_bond']>
type CCA = Table<CCD_Schema['chem_comp_atom']>
@@ -79,6 +27,10 @@ function ccbKey(compId: string, atomId1: string, atomId2: string) {
return atomId1 < atomId2 ? `${compId}:${atomId1}-${atomId2}` : `${compId}:${atomId2}-${atomId1}`;
}
function ccaKey(compId: string, atomId: string) {
return `${compId}:${atomId}`;
}
function addChemCompBondToSet(set: Set<string>, ccb: CCB) {
for (let i = 0, il = ccb._rowCount; i < il; ++i) {
set.add(ccbKey(ccb.comp_id.value(i), ccb.atom_id_1.value(i), ccb.atom_id_2.value(i)));
@@ -88,7 +40,7 @@ function addChemCompBondToSet(set: Set<string>, ccb: CCB) {
function addChemCompAtomToSet(set: Set<string>, cca: CCA) {
for (let i = 0, il = cca._rowCount; i < il; ++i) {
set.add(cca.atom_id.value(i));
set.add(ccaKey(cca.comp_id.value(i), cca.atom_id.value(i)));
}
return set;
}
@@ -134,11 +86,32 @@ function checkAddingBondsFromPVCD(pvcd: DatabaseCollection<CCD_Schema>) {
}
}
async function createBonds() {
await ensureDataAvailable();
const ccd = await readCCD();
const pvcd = await readPVCD();
function checkAddingAtomsFromPVCD(pvcd: DatabaseCollection<CCD_Schema>) {
const ccaSetByParent = DefaultMap<string, Set<string>>(() => new Set());
for (const k in pvcd) {
const { chem_comp, chem_comp_atom } = pvcd[k];
if (chem_comp_atom._rowCount) {
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0);
if (parentIds.length === 0) {
const set = ccaSetByParent.getDefault(chem_comp.id.value(0));
addChemCompAtomToSet(set, chem_comp_atom);
} else {
for (let i = 0, il = parentIds.length; i < il; ++i) {
const parentId = parentIds[i];
const set = ccaSetByParent.getDefault(parentId);
addChemCompAtomToSet(set, chem_comp_atom);
}
}
}
}
}
async function createBonds(
ccd: DatabaseCollection<CCD_Schema>,
pvcd: DatabaseCollection<CCD_Schema>,
atomsRequested: boolean
) {
const ccbSet = new Set<string>();
const comp_id: string[] = [];
@@ -199,31 +172,97 @@ async function createBonds() {
});
const bondDatabase = Database.ofTables(
TABLE_NAME,
CCB_TABLE_NAME,
{ chem_comp_bond: mmCIF_chemCompBond_schema },
{ chem_comp_bond: bondTable }
);
return bondDatabase;
return { bonds: bondDatabase, atoms: atomsRequested ? createAtoms(ccd, pvcd) : void 0 };
}
async function run(out: string, binary = false) {
const bonds = await createBonds();
function createAtoms(ccd: DatabaseCollection<CCD_Schema>, pvcd: DatabaseCollection<CCD_Schema>) {
const ccaSet = new Set<string>();
const cif = getEncodedCif(TABLE_NAME, bonds, binary);
const comp_id: string[] = [];
const atom_id: string[] = [];
const charge: number[] = [];
const pdbx_stereo_config: typeof CCD_Schema.chem_comp_atom['pdbx_stereo_config']['T'][] = [];
function addAtoms(compId: string, cca: CCA) {
for (let i = 0, il = cca._rowCount; i < il; ++i) {
const atomId = cca.atom_id.value(i);
const k = ccaKey(compId, atomId);
if (!ccaSet.has(k)) {
atom_id.push(atomId);
comp_id.push(compId);
charge.push(cca.charge.value(i));
pdbx_stereo_config.push(cca.pdbx_stereo_config.value(i));
ccaSet.add(k);
}
}
}
// check adding atoms from PVCD
checkAddingAtomsFromPVCD(pvcd);
// add atoms from PVCD
for (const k in pvcd) {
const { chem_comp, chem_comp_atom } = pvcd[k];
if (chem_comp_atom._rowCount) {
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0);
if (parentIds.length === 0) {
addAtoms(chem_comp.id.value(0), chem_comp_atom);
} else {
for (let i = 0, il = parentIds.length; i < il; ++i) {
addAtoms(parentIds[i], chem_comp_atom);
}
}
}
}
// add atoms from CCD
for (const k in ccd) {
const { chem_comp, chem_comp_atom } = ccd[k];
if (chem_comp_atom._rowCount) {
addAtoms(chem_comp.id.value(0), chem_comp_atom);
}
}
const atomTable = Table.ofArrays(ccd_chemCompAtom_schema, {
comp_id, atom_id, charge, pdbx_stereo_config
});
return Database.ofTables(
CCA_TABLE_NAME,
{ chem_comp_atom: ccd_chemCompAtom_schema },
{ chem_comp_atom: atomTable }
);
}
async function run(out: string, binary = false, forceDownload = false, ccaOut?: string) {
await ensureDataAvailable(forceDownload);
const ccd = await readCCD();
const pvcd = await readPVCD();
const { bonds, atoms } = await createBonds(ccd, pvcd, !!ccaOut);
const ccbCif = getEncodedCif(CCB_TABLE_NAME, bonds, binary);
if (!fs.existsSync(path.dirname(out))) {
fs.mkdirSync(path.dirname(out));
}
writeFile(out, cif);
writeFile(out, ccbCif);
if (!!ccaOut) {
const ccaCif = getEncodedCif(CCA_TABLE_NAME, atoms, binary);
if (!fs.existsSync(path.dirname(ccaOut))) {
fs.mkdirSync(path.dirname(ccaOut));
}
writeFile(ccaOut, ccaCif);
}
}
const TABLE_NAME = 'CHEM_COMP_BONDS';
const DATA_DIR = path.join(__dirname, '..', '..', '..', 'build/data');
const CCD_PATH = path.join(DATA_DIR, 'components.cif');
const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif');
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif';
const PVCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/aa-variants-v1.cif';
const CCB_TABLE_NAME = 'CHEM_COMP_BONDS';
const CCA_TABLE_NAME = 'CHEM_COMP_ATOMS';
const parser = new argparse.ArgumentParser({
addHelp: true,
@@ -240,13 +279,16 @@ parser.addArgument([ '--binary', '-b' ], {
action: 'storeTrue',
help: 'Output as BinaryCIF.'
});
parser.addArgument(['--ccaOut', '-a'], {
help: 'Optional generated file output path for chem_comp_atom data.',
required: false
});
interface Args {
out: string
forceDownload?: boolean
binary?: boolean
out: string,
forceDownload?: boolean,
binary?: boolean,
ccaOut?: string
}
const args: Args = parser.parseArgs();
const FORCE_DOWNLOAD = args.forceDownload;
run(args.out, args.binary);
run(args.out, args.binary, args.forceDownload, args.ccaOut);

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as util from 'util';
import * as path from 'path';
import * as fs from 'fs';
import * as zlib from 'zlib';
import fetch from 'node-fetch';
require('util.promisify').shim();
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
import { Progress } from '../../mol-task';
import { Database } from '../../mol-data/db';
import { CIF } from '../../mol-io/reader/cif';
import { CifWriter } from '../../mol-io/writer/cif';
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
export async function ensureAvailable(path: string, url: string, forceDownload = false) {
if (forceDownload || !fs.existsSync(path)) {
console.log(`downloading ${url}...`);
const data = await fetch(url);
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR);
}
if (url.endsWith('.gz')) {
await writeFile(path, zlib.gunzipSync(await data.buffer()));
} else {
await writeFile(path, await data.text());
}
console.log(`done downloading ${url}`);
}
}
export async function ensureDataAvailable(forceDownload = false) {
await ensureAvailable(CCD_PATH, CCD_URL, forceDownload);
await ensureAvailable(PVCD_PATH, PVCD_URL, forceDownload);
}
export async function readFileAsCollection<S extends Database.Schema>(path: string, schema: S) {
const parsed = await parseCif(await readFile(path, 'utf8'));
return CIF.toDatabaseCollection(schema, parsed.result);
}
export async function readCCD() {
return readFileAsCollection(CCD_PATH, CCD_Schema);
}
export async function readPVCD() {
return readFileAsCollection(PVCD_PATH, CCD_Schema);
}
async function parseCif(data: string | Uint8Array) {
const comp = CIF.parse(data);
console.time('parse cif');
const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
console.timeEnd('parse cif');
if (parsed.isError) throw parsed;
return parsed;
}
export function getEncodedCif(name: string, database: Database<Database.Schema>, binary = false) {
const encoder = CifWriter.createEncoder({ binary, encoderName: 'mol*' });
CifWriter.Encoder.writeDatabase(encoder, name, database);
return encoder.getData();
}
const DATA_DIR = path.join(__dirname, '..', '..', '..', '..', 'build/data');
const CCD_PATH = path.join(DATA_DIR, 'components.cif');
const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif');
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif';
const PVCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/aa-variants-v1.cif';

View File

@@ -70,7 +70,7 @@ function classify(name: string, field: CifField): CifWriter.Field {
}
}
export default function convert(path: string, asText = false, hints?: EncodingStrategyHint[], filter?: string) {
export function convert(path: string, asText = false, hints?: EncodingStrategyHint[], filter?: string) {
return Task.create<Uint8Array>('BinaryCIF', async ctx => {
const encodingProvider: BinaryEncodingProvider = hints
? CifWriter.createEncodingProviderFromJsonConfig(hints)

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env node
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
@@ -9,7 +10,7 @@ import * as argparse from 'argparse';
import * as util from 'util';
import * as fs from 'fs';
import * as zlib from 'zlib';
import convert from './converter';
import { convert } from './converter';
require('util.promisify').shim();

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env node
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
@@ -11,7 +12,7 @@ import fetch from 'node-fetch';
import { parseCsv } from '../../mol-io/reader/csv/parser';
import { CifFrame, CifBlock } from '../../mol-io/reader/cif';
import parseText from '../../mol-io/reader/cif/text/parser';
import { parseCifText } from '../../mol-io/reader/cif/text/parser';
import { generateSchema } from './util/cif-dic';
import { generate } from './util/generate';
import { Filter, Database } from './util/schema';
@@ -27,19 +28,19 @@ function getDicNamespace(block: CifBlock) {
async function runGenerateSchemaMmcif(name: string, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
await ensureMmcifDicAvailable();
const mmcifDic = await parseText(fs.readFileSync(MMCIF_DIC_PATH, 'utf8')).run();
const mmcifDic = await parseCifText(fs.readFileSync(MMCIF_DIC_PATH, 'utf8')).run();
if (mmcifDic.isError) throw mmcifDic;
await ensureIhmDicAvailable();
const ihmDic = await parseText(fs.readFileSync(IHM_DIC_PATH, 'utf8')).run();
const ihmDic = await parseCifText(fs.readFileSync(IHM_DIC_PATH, 'utf8')).run();
if (ihmDic.isError) throw ihmDic;
await ensureCarbBranchDicAvailable();
const carbBranchDic = await parseText(fs.readFileSync(CARB_BRANCH_DIC_PATH, 'utf8')).run();
const carbBranchDic = await parseCifText(fs.readFileSync(CARB_BRANCH_DIC_PATH, 'utf8')).run();
if (carbBranchDic.isError) throw carbBranchDic;
await ensureCarbCompDicAvailable();
const carbCompDic = await parseText(fs.readFileSync(CARB_COMP_DIC_PATH, 'utf8')).run();
const carbCompDic = await parseCifText(fs.readFileSync(CARB_COMP_DIC_PATH, 'utf8')).run();
if (carbCompDic.isError) throw carbCompDic;
const mmcifDicVersion = getDicVersion(mmcifDic.result.blocks[0]);
@@ -55,7 +56,7 @@ async function runGenerateSchemaMmcif(name: string, fieldNamesPath: string, type
async function runGenerateSchemaCifCore(name: string, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
await ensureCifCoreDicAvailable();
const cifCoreDic = await parseText(fs.readFileSync(CIF_CORE_DIC_PATH, 'utf8')).run();
const cifCoreDic = await parseCifText(fs.readFileSync(CIF_CORE_DIC_PATH, 'utf8')).run();
if (cifCoreDic.isError) throw cifCoreDic;
const cifCoreDicVersion = getDicVersion(cifCoreDic.result.blocks[0]);
@@ -79,7 +80,7 @@ async function resolveImports(frames: CifFrame[], baseDir: string): Promise<Map<
if (!file) continue;
if (imports.has(file)) continue;
const dic = await parseText(fs.readFileSync(path.join(baseDir, file), 'utf8')).run();
const dic = await parseCifText(fs.readFileSync(path.join(baseDir, file), 'utf8')).run();
if (dic.isError) throw dic;
imports.set(file, [...dic.result.blocks[0].saveFrames]);
@@ -91,7 +92,7 @@ async function resolveImports(frames: CifFrame[], baseDir: string): Promise<Map<
}
async function runGenerateSchemaDic(name: string, dicPath: string, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
const dic = await parseText(fs.readFileSync(dicPath, 'utf8')).run();
const dic = await parseCifText(fs.readFileSync(dicPath, 'utf8')).run();
if (dic.isError) throw dic;
const dicVersion = getDicVersion(dic.result.blocks[0]);
@@ -159,7 +160,7 @@ async function ensureDicAvailable(dicPath: string, dicUrl: string) {
}
}
const DIC_DIR = path.resolve(__dirname, '../../../build/dics/');
const DIC_DIR = path.resolve(__dirname, '../../../../build/dics/');
const MMCIF_DIC_PATH = `${DIC_DIR}/mmcif_pdbx_v50.dic`;
const MMCIF_DIC_URL = 'http://mmcif.wwpdb.org/dictionaries/ascii/mmcif_pdbx_v50.dic';
const IHM_DIC_PATH = `${DIC_DIR}/ihm-extension.dic`;
@@ -237,22 +238,22 @@ switch (args.preset) {
case 'mmCIF':
args.name = 'mmCIF';
args.dic = 'mmCIF';
args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/mmcif-field-names.csv');
args.fieldNamesPath = path.resolve(__dirname, '../../../../data/cif-field-names/mmcif-field-names.csv');
break;
case 'CCD':
args.name = 'CCD';
args.dic = 'mmCIF';
args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/ccd-field-names.csv');
args.fieldNamesPath = path.resolve(__dirname, '../../../../data/cif-field-names/ccd-field-names.csv');
break;
case 'BIRD':
args.name = 'BIRD';
args.dic = 'mmCIF';
args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/bird-field-names.csv');
args.fieldNamesPath = path.resolve(__dirname, '../../../../data/cif-field-names/bird-field-names.csv');
break;
case 'CifCore':
args.name = 'CifCore';
args.dic = 'CifCore';
args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/cif-core-field-names.csv');
args.fieldNamesPath = path.resolve(__dirname, '../../../../data/cif-field-names/cif-core-field-names.csv');
break;
}

View File

@@ -17,15 +17,15 @@ function header (name: string, info: string, moldataImportPath: string) {
* @author molstar/ciftools package
*/
import { Database, Column } from '${moldataImportPath}/db'
import { Database, Column } from '${moldataImportPath}/db';
import Schema = Column.Schema`;
import Schema = Column.Schema;`;
}
function footer (name: string) {
return `
export type ${name}_Schema = typeof ${name}_Schema;
export interface ${name}_Database extends Database<${name}_Schema> {}`;
export interface ${name}_Database extends Database<${name}_Schema> {};`;
}
function getTypeShorthands(schema: Database, fields?: Filter) {
@@ -122,7 +122,7 @@ export function generate (name: string, info: string, schema: Database, fields:
});
codeLines.push(' },');
});
codeLines.push('}');
codeLines.push('};');
if (addAliases) {
codeLines.push('');
@@ -144,7 +144,7 @@ export function generate (name: string, info: string, schema: Database, fields:
});
codeLines.push(' ],');
});
codeLines.push('}');
codeLines.push('};');
}
return `${header(name, info, moldataImportPath)}\n\n${getTypeShorthands(schema, fields)}\n\n${codeLines.join('\n')}\n${footer(name)}`;

View File

@@ -0,0 +1,93 @@
#!/usr/bin/env node
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as argparse from 'argparse';
import * as fs from 'fs';
import * as path from 'path';
import fetch from 'node-fetch';
import { UniqueArray } from '../../mol-data/generic';
const LIPIDS_DIR = path.resolve(__dirname, '../../../../build/lipids/');
const MARTINI_LIPIDS_PATH = path.resolve(LIPIDS_DIR, 'martini_lipids.itp');
const MARTINI_LIPIDS_URL = 'http://www.cgmartini.nl/images/parameters/lipids/Collections/martini_v2.0_lipids_all_201506.itp';
async function ensureAvailable(path: string, url: string) {
if (FORCE_DOWNLOAD || !fs.existsSync(path)) {
const name = url.substr(url.lastIndexOf('/') + 1);
console.log(`downloading ${name}...`);
const data = await fetch(url);
if (!fs.existsSync(LIPIDS_DIR)) {
fs.mkdirSync(LIPIDS_DIR);
}
fs.writeFileSync(path, await data.text());
console.log(`done downloading ${name}`);
}
}
async function ensureLipidsAvailable() { await ensureAvailable(MARTINI_LIPIDS_PATH, MARTINI_LIPIDS_URL); }
const extraLipids = ['DMPC'];
async function run(out: string) {
await ensureLipidsAvailable();
const lipidsItpStr = fs.readFileSync(MARTINI_LIPIDS_PATH, 'utf8');
const lipids = UniqueArray.create<string>();
const reLipid = /\[moleculetype\]\n; molname nrexcl\n +([a-zA-Z]{3,5})/g;
let m: RegExpExecArray | null;
while ((m = reLipid.exec(lipidsItpStr)) !== null) {
const v = m[0].substr(m[0].lastIndexOf(' ') + 1);
UniqueArray.add(lipids, v, v);
}
for (const v of extraLipids) {
UniqueArray.add(lipids, v, v);
}
const lipidNames = JSON.stringify(lipids.array);
if (out) {
const output = `/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated lipid params file. Names extracted from Martini FF lipids itp.
*
* @author molstar/lipid-params cli
*/
export const LipidNames = new Set(${lipidNames.replace(/"/g, "'").replace(/,/g, ', ')});
`;
fs.writeFileSync(out, output);
} else {
console.log(lipidNames);
}
}
const parser = new argparse.ArgumentParser({
addHelp: true,
description: 'Create lipid params (from martini lipids itp)'
});
parser.addArgument([ '--out', '-o' ], {
help: 'Generated lipid params output path, if not given printed to stdout'
});
parser.addArgument([ '--forceDownload', '-f' ], {
action: 'storeTrue',
help: 'Force download of martini lipids itp'
});
interface Args {
out: string
forceDownload: boolean
}
const args: Args = parser.parseArgs();
const FORCE_DOWNLOAD = args.forceDownload;
run(args.out || '').catch(e => {
console.error(e);
});

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env node
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*

View File

@@ -26,6 +26,8 @@ function paramInfo(param: PD.Any, offset: number): string {
case 'file': return `JavaScript File Handle`;
case 'file-list': return `JavaScript FileList Handle`;
case 'select': return `One of ${oToS(param.options)}`;
case 'value-ref': return `Reference to a runtime defined value.`;
case 'data-ref': return `Reference to a computed data value.`;
case 'text': return 'String';
case 'interval': return `Interval [min, max]`;
case 'group': return `Object with:\n${getParams(param.params, offset + 2)}`;

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env node
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
@@ -9,7 +10,7 @@ import * as argparse from 'argparse';
require('util.promisify').shim();
import { CifFrame } from '../../mol-io/reader/cif';
import { Model, Structure, StructureElement, Unit, StructureProperties, UnitRing } from '../../mol-model/structure';
import { Model, Structure, StructureElement, Unit, StructureProperties, UnitRing, Trajectory } from '../../mol-model/structure';
// import { Run, Progress } from '../../mol-task'
import { OrderedSet } from '../../mol-data/int';
import { openCif, downloadCif } from './helpers';
@@ -18,6 +19,7 @@ import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
import { Sequence } from '../../mol-model/sequence';
import { ModelSecondaryStructure } from '../../mol-model-formats/structure/property/secondary-structure';
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
import { Task } from '../../mol-task';
async function downloadFromPdb(pdb: string) {
@@ -33,20 +35,22 @@ export async function readCifFile(path: string) {
export function atomLabel(model: Model, aI: number) {
const { atoms, residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy;
const { label_atom_id } = atoms;
const { label_comp_id, label_seq_id } = residues;
const { label_atom_id, label_comp_id } = atoms;
const { label_seq_id } = residues;
const { label_asym_id } = chains;
const rI = residueAtomSegments.index[aI];
const cI = chainAtomSegments.index[aI];
return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`;
return `${label_asym_id.value(cI)} ${label_comp_id.value(aI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`;
}
export function residueLabel(model: Model, rI: number) {
const { residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy;
const { label_comp_id, label_seq_id } = residues;
const { atoms, residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy;
const { label_comp_id } = atoms;
const { label_seq_id } = residues;
const { label_asym_id } = chains;
const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]];
return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`;
const aI = residueAtomSegments.offsets[rI];
const cI = chainAtomSegments.index[aI];
return `${label_asym_id.value(cI)} ${label_comp_id.value(aI)} ${label_seq_id.value(rI)}`;
}
export function printSecStructure(model: Model) {
@@ -95,15 +99,17 @@ export function printBonds(structure: Structure, showIntra: boolean, showInter:
for (const unit of structure.units) {
if (!Unit.isAtomic(unit)) continue;
for (const pairBonds of bonds.getConnectedUnits(unit)) {
for (const pairBonds of bonds.getConnectedUnits(unit.id)) {
if (!pairBonds.areUnitsOrdered || pairBonds.edgeCount === 0) continue;
const { unitA, unitB } = pairBonds;
console.log(`${pairBonds.unitA.id} - ${pairBonds.unitB.id}: ${pairBonds.edgeCount} bond(s)`);
const { unitA, unitB, edgeCount } = pairBonds;
const uA = structure.unitMap.get(unitA);
const uB = structure.unitMap.get(unitB);
console.log(`${unitA} - ${unitB}: ${edgeCount} bond(s)`);
for (const aI of pairBonds.connectedIndices) {
for (const bond of pairBonds.getEdges(aI)) {
console.log(`${atomLabel(unitA.model, unitA.elements[aI])} -- ${atomLabel(unitB.model, unitB.elements[bond.indexB])}`);
console.log(`${atomLabel(uA.model, uA.elements[aI])} -- ${atomLabel(uB.model, uB.elements[bond.indexB])}`);
}
}
}
@@ -180,10 +186,11 @@ export function printSymmetryInfo(model: Model) {
console.log(`NCS operators: ${symmetry.ncsOperators && symmetry.ncsOperators.map(a => a.name).join(', ')}`);
}
export function printModelStats(models: ReadonlyArray<Model>) {
export async function printModelStats(models: Trajectory) {
console.log('\nModels\n=============');
for (const m of models) {
for (let i = 0; i < models.frameCount; i++) {
const m = await Task.resolveInContext(models.getFrameAtIndex(i));
if (m.coarseHierarchy.isDefined) {
console.log(`${m.label} ${m.modelNum}: ${m.atomicHierarchy.atoms._rowCount} atom(s), ${m.coarseHierarchy.spheres.count} sphere(s), ${m.coarseHierarchy.gaussians.count} gaussian(s)`);
} else {
@@ -195,7 +202,7 @@ export function printModelStats(models: ReadonlyArray<Model>) {
export async function getModelsAndStructure(frame: CifFrame) {
const models = await trajectoryFromMmCIF(frame).run();
const structure = Structure.ofModel(models[0]);
const structure = Structure.ofModel(models.representative);
return { models, structure };
}
@@ -203,13 +210,13 @@ async function run(frame: CifFrame, args: Args) {
const { models, structure } = await getModelsAndStructure(frame);
if (args.models) printModelStats(models);
if (args.seq) printSequence(models[0]);
if (args.seq) printSequence(models.representative);
if (args.units) printUnits(structure);
if (args.sym) printSymmetryInfo(models[0]);
if (args.sym) printSymmetryInfo(models.representative);
if (args.rings) printRings(structure);
if (args.intraBonds) printBonds(structure, true, false);
if (args.interBonds) printBonds(structure, false, true);
if (args.sec) printSecStructure(models[0]);
if (args.sec) printSecStructure(models.representative);
}
async function runDL(pdb: string, args: Args) {

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env node
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AlphaOrbitalsExample } from '.';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
import { PluginContextContainer } from '../../mol-plugin-ui/plugin';
export function mountControls(orbitals: AlphaOrbitalsExample, parent: Element) {
ReactDOM.render(<PluginContextContainer plugin={orbitals.plugin}>
<Controls orbitals={orbitals} />
</PluginContextContainer>, parent);
}
function Controls({ orbitals }: { orbitals: AlphaOrbitalsExample }) {
const params = useBehavior(orbitals.params);
const values = useBehavior(orbitals.state);
return <ParameterControls params={params as any} values={values} onChangeValues={(vs: any) => orbitals.state.next(vs)} />;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* Alpha Orbitals Example</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
}
#controls {
position: absolute;
left: 8px;
top: 8px;
width: 300px;
}
</style>
<link rel="stylesheet" type="text/css" href="molstar.css" />
<script type="text/javascript" src="./index.js"></script>
</head>
<body>
<div id="app"></div>
<div id='controls'></div>
<script>
AlphaOrbitalsExample.init('app')
</script>
</body>
</html>

View File

@@ -0,0 +1,213 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
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 { PluginStateObject } from '../../mol-plugin-state/objects';
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 {
moleculeSdf: string,
basis: Basis,
order: SphericalBasisOrder,
orbitals: AlphaOrbital[]
}
interface Params {
show: { name: 'orbital', params: { index: number } } | { name: 'density', params: {} },
isoValue: number,
gpuSurface: boolean
}
type Selectors = {
type: 'orbital',
volume: StateObjectSelector<PluginStateObject.Volume.Data, typeof CreateOrbitalVolume>,
positive: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
negative: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
} | {
type: 'density',
volume: StateObjectSelector<PluginStateObject.Volume.Data, typeof CreateOrbitalDensityVolume>,
positive: StateObjectSelector<PluginStateObject.Volume.Representation3D, typeof CreateOrbitalRepresentation3D>
}
export class AlphaOrbitalsExample {
plugin: PluginContext;
async init(target: string | HTMLElement) {
this.plugin = await createPluginAsync(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec(),
layout: {
initial: {
isExpanded: false,
showControls: false
},
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' },
},
config: [
[PluginConfig.Viewport.ShowExpand, false],
[PluginConfig.Viewport.ShowControls, false],
[PluginConfig.Viewport.ShowSelectionMode, false],
[PluginConfig.Viewport.ShowAnimation, false],
]
});
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
PluginCommands.Toast.Show(this.plugin, {
title: 'Error',
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
});
return;
}
this.load({
moleculeSdf: DemoMoleculeSDF,
...DemoOrbitals
});
mountControls(this, document.getElementById('controls')!);
}
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
readonly state = new BehaviorSubject<Params>({ show: { name: 'orbital', params: { index: 32 } }, isoValue: 1, gpuSurface: false });
private selectors?: Selectors = void 0;
private basis?: StateObjectSelector<BasisAndOrbitals> = void 0;
private currentParams: Params = { ...this.state.value };
private clearVolume() {
if (!this.selectors) return;
const v = this.selectors.volume;
this.selectors = void 0;
return this.plugin.build().delete(v).commit();
}
private async syncVolume() {
if (!this.basis?.isOk) return;
const state = this.state.value;
if (state.show.name !== this.selectors?.type) {
await this.clearVolume();
}
const update = this.plugin.build();
if (state.show.name === 'orbital') {
if (!this.selectors) {
const volume = update
.to(this.basis)
.apply(CreateOrbitalVolume, { index: state.show.params.index });
const positive = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('positive', ColorNames.blue)).selector;
const negative = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('negative', ColorNames.red)).selector;
this.selectors = { type: 'orbital', volume: volume.selector, positive, negative };
} else {
const index = state.show.params.index;
update.to(this.selectors.volume).update(CreateOrbitalVolume, () => ({ index }));
}
} else {
if (!this.selectors) {
const volume = update
.to(this.basis)
.apply(CreateOrbitalDensityVolume);
const positive = volume.apply(CreateOrbitalRepresentation3D, this.volumeParams('positive', ColorNames.blue)).selector;
this.selectors = { type: 'density', volume: volume.selector, positive };
}
}
await update.commit();
if (this.currentParams.gpuSurface !== this.state.value.gpuSurface) {
await this.setIsovalue();
}
this.currentParams = this.state.value;
}
private setIsovalue() {
if (!this.selectors) return;
this.currentParams = this.state.value;
const update = this.plugin.build();
update.to(this.selectors.positive).update(this.volumeParams('positive', ColorNames.blue));
if (this.selectors?.type === 'orbital') {
update.to(this.selectors.negative).update(this.volumeParams('negative', ColorNames.red));
}
return update.commit();
}
private volumeParams(kind: 'positive' | 'negative', color: Color): StateTransformer.Params<typeof CreateOrbitalRepresentation3D> {
return {
alpha: 0.85,
color,
directVolume: this.state.value.gpuSurface,
kind,
relativeIsovalue: this.state.value.isoValue,
pickable: false,
xrayShaded: true
};
}
async load(input: DemoInput) {
await this.plugin.clear();
const data = await this.plugin.builders.data.rawData({ data: input.moleculeSdf }, { state: { isGhost: true } });
const trajectory = await this.plugin.builders.structure.parseTrajectory(data, 'mol');
const model = await this.plugin.builders.structure.createModel(trajectory);
const structure = await this.plugin.builders.structure.createStructure(model);
const all = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'all');
if (all) await this.plugin.builders.structure.representation.addRepresentation(all, { type: 'ball-and-stick', color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } });
this.basis = await this.plugin.build().toRoot()
.apply(StaticBasisAndOrbitals, { basis: input.basis, order: input.order, orbitals: input.orbitals })
.commit();
await this.syncVolume();
this.params.next({
show: ParamDefinition.MappedStatic('orbital', {
'orbital': ParamDefinition.Group({
index: ParamDefinition.Numeric(32, { min: 0, max: input.orbitals.length - 1 }, { immediateUpdate: true, isEssential: true }),
}),
'density': ParamDefinition.EmptyGroup()
}, { cycle: true }),
isoValue: ParamDefinition.Numeric(this.currentParams.isoValue, { min: 0.5, max: 3, step: 0.1 }, { immediateUpdate: true, isEssential: false }),
gpuSurface: ParamDefinition.Boolean(this.currentParams.gpuSurface, { isHidden: true })
});
this.state.pipe(skip(1), debounceTime(1000 / 24)).subscribe(async params => {
if (params.show.name !== this.currentParams.show.name
|| (params.show.name === 'orbital' && this.currentParams.show.name === 'orbital' && params.show.params.index !== this.currentParams.show.params.index)) {
this.syncVolume();
} else if (params.isoValue !== this.currentParams.isoValue || params.gpuSurface !== this.currentParams.gpuSurface) {
this.setIsovalue();
}
});
}
}
(window as any).AlphaOrbitalsExample = new AlphaOrbitalsExample();

View File

@@ -86,7 +86,7 @@
// adjust this number to make the animation faster or slower
// requires to "restart" the animation if changed
BasicMolStarWrapper.animate.modelIndex.maxFPS = 30;
BasicMolStarWrapper.animate.modelIndex.targetFps = 30;
addControl('Play To End', () => BasicMolStarWrapper.animate.modelIndex.onceForward());
addControl('Play To Start', () => BasicMolStarWrapper.animate.modelIndex.onceBackward());

View File

@@ -6,10 +6,10 @@
import { EmptyLoci } from '../../mol-model/loci';
import { StructureSelection } from '../../mol-model/structure';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in';
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 { PluginStateObject } from '../../mol-plugin-state/objects';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
import { Script } from '../../mol-script/script';
@@ -29,7 +29,7 @@ class BasicWrapper {
init(target: string | HTMLElement) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec,
...DefaultPluginSpec(),
layout: {
initial: {
isExpanded: false,
@@ -83,13 +83,17 @@ class BasicWrapper {
if (!this.plugin.canvas3d.props.trackball.spin) PluginCommands.Camera.Reset(this.plugin, {});
}
private animateModelIndexTargetFps() {
return Math.max(1, this.animate.modelIndex.targetFps | 0);
}
animate = {
modelIndex: {
maxFPS: 8,
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }); },
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }); },
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }); },
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }); },
targetFps: 8,
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: {} } }); },
stop: () => this.plugin.managers.animation.stop()
}
}
@@ -113,8 +117,10 @@ class BasicWrapper {
interactivity = {
highlightOn: () => {
const data = this.plugin.managers.structure.hierarchy.current.structures[0]?.cell.obj?.data;
if (!data) return;
const seq_id = 7;
const data = (this.plugin.state.data.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data;
const sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
'residue-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), seq_id]),
'group-by': Q.struct.atomProperty.macromolecular.residueKey()

View File

@@ -10,7 +10,7 @@ import { superpose } from '../../mol-model/structure/structure/util/superpositio
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
import { PluginContext } from '../../mol-plugin/context';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import Expression from '../../mol-script/language/expression';
import { Expression } from '../../mol-script/language/expression';
import { compile } from '../../mol-script/runtime/query/compiler';
import { StateObjectRef } from '../../mol-state';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';

View File

@@ -9,7 +9,7 @@ import { CifWriter } from '../../mol-io/writer/cif';
import * as S from './schemas';
// import { getCategoryInstanceProvider } from './utils'
export default function create(allData: any) {
export function createMapping(allData: any) {
const mols = Object.keys(allData);
const enc = CifWriter.createEncoder();
enc.startDataBlock(mols[0]);

View File

@@ -6,7 +6,7 @@
import express from 'express';
import fetch from 'node-fetch';
import createMapping from './mapping';
import { createMapping } from './mapping';
async function getMappings(id: string) {
const data = await fetch(`https://www.ebi.ac.uk/pdbe/api/mappings/${id}`);

View File

@@ -5,7 +5,7 @@
*/
import fetch from 'node-fetch';
import createMapping from './mapping';
import { createMapping } from './mapping';
(async function () {
const data = await fetch('https://www.ebi.ac.uk/pdbe/api/mappings/1tqn?pretty=true');

View File

@@ -12,18 +12,18 @@
}
#app {
position: absolute;
left: 160px;
top: 100px;
width: 600px;
height: 600px;
border: 1px solid #ccc;
width: 100%;
height: 100%;
}
#controls {
position: absolute;
width: 150px;
top: 100px;
left: 780px;
bottom: 100px;
right: 50px;
z-index: 10;
font-family: sans-serif;
font-size: smaller;
}
#controls > button {
@@ -46,13 +46,13 @@
<div id="app"></div>
<script>
LightingDemo.init('app')
LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' })
LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3)
addHeader('Example PDB IDs');
addControl('1M07', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' }));
addControl('6HY0', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6HY0.cif', assemblyId: '1' }));
addControl('6QVK', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6QVK.cif', assemblyId: '1' }));
addControl('1RB8', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1RB8.cif', assemblyId: '1' }));
addControl('4KTC', () => LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3));
addControl('5FJ5', () => LightingDemo.load({ url: 'https://models.rcsb.org/5FJ5.bcif', assemblyId: '1' }, 8, 1.8));
addControl('1UPN', () => LightingDemo.load({ url: 'https://models.rcsb.org/1UPN.bcif', assemblyId: '1' }, 7, 1.6));
addControl('1RB8', () => LightingDemo.load({ url: 'https://models.rcsb.org/1RB8.bcif', assemblyId: '1' }, 6, 1.3));
addSeparator()

View File

@@ -5,7 +5,8 @@
*/
import { Canvas3DProps } from '../../mol-canvas3d/canvas3d';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import { createPlugin } from '../../mol-plugin';
import { DefaultPluginSpec } from '../../mol-plugin/spec';
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
import { PluginCommands } from '../../mol-plugin/commands';
import { PluginContext } from '../../mol-plugin/context';
@@ -24,12 +25,11 @@ const Canvas3DPresets = {
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
},
postprocessing: {
occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } },
outline: { name: 'on', params: { scale: 1, threshold: 0.8 } }
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
outline: { name: 'on', params: { scale: 1, threshold: 0.1 } }
},
renderer: {
ambientIntensity: 1,
lightIntensity: 0,
style: { name: 'flat', params: {} }
}
},
occlusion: <Preset> {
@@ -37,12 +37,11 @@ const Canvas3DPresets = {
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
},
postprocessing: {
occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } },
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
outline: { name: 'off', params: { } }
},
renderer: {
ambientIntensity: 0.4,
lightIntensity: 0.6,
style: { name: 'matte', params: {} }
}
},
standard: <Preset> {
@@ -54,20 +53,24 @@ const Canvas3DPresets = {
outline: { name: 'off', params: { } }
},
renderer: {
ambientIntensity: 0.4,
lightIntensity: 0.6,
style: { name: 'matte', params: {} }
}
}
};
type Canvas3DPreset = keyof typeof Canvas3DPresets
class LightingDemo {
plugin: PluginContext;
private radius = 5;
private bias = 1.1;
private preset: Canvas3DPreset = 'illustrative';
init(target: string | HTMLElement) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec,
...DefaultPluginSpec(),
layout: {
initial: {
isExpanded: false,
@@ -82,6 +85,10 @@ class LightingDemo {
setPreset(preset: Canvas3DPreset) {
const props = Canvas3DPresets[preset];
if (props.postprocessing.occlusion?.name === 'on') {
props.postprocessing.occlusion.params.radius = this.radius;
props.postprocessing.occlusion.params.bias = this.bias;
}
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
...props,
multiSample: {
@@ -99,7 +106,7 @@ class LightingDemo {
}});
}
async load({ url, format = 'mmcif', isBinary = false, assemblyId = '' }: LoadParams) {
async load({ url, format = 'mmcif', isBinary = true, assemblyId = '' }: LoadParams, radius: number, bias: number) {
await this.plugin.clear();
const data = await this.plugin.builders.data.download({ url: Asset.Url(url), isBinary }, { state: { isGhost: true } });
@@ -111,7 +118,11 @@ class LightingDemo {
if (polymer) await this.plugin.builders.structure.representation.addRepresentation(polymer, { type: 'spacefill', color: 'illustrative' });
const ligand = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'ligand');
if (ligand) await this.plugin.builders.structure.representation.addRepresentation(ligand, { type: 'ball-and-stick' });
if (ligand) await this.plugin.builders.structure.representation.addRepresentation(ligand, { type: 'ball-and-stick', color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } });
this.radius = radius;
this.bias = bias;
this.setPreset(this.preset);
}
}

View File

@@ -58,7 +58,7 @@ export namespace ModelInfo {
const entityType = model.entities.data.type.value(eI);
if (entityType !== 'non-polymer' && entityType !== 'branched') continue;
const comp_id = model.atomicHierarchy.residues.label_comp_id.value(rI);
const comp_id = model.atomicHierarchy.atoms.label_comp_id.value(residueOffsets[rI]);
let lig = hetMap.get(comp_id);
if (!lig) {

View File

@@ -147,7 +147,7 @@
// adjust this number to make the animation faster or slower
// requires to "restart" the animation if changed
PluginWrapper.animate.modelIndex.maxFPS = 30;
PluginWrapper.animate.modelIndex.targetFps = 30;
addControl('Play To End', () => PluginWrapper.animate.modelIndex.onceForward());
addControl('Play To Start', () => PluginWrapper.animate.modelIndex.onceBackward());

View File

@@ -6,8 +6,9 @@
import * as ReactDOM from 'react-dom';
import { Canvas3DProps, DefaultCanvas3DParams } from '../../mol-canvas3d/canvas3d';
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in';
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';
@@ -46,7 +47,7 @@ class MolStarProteopediaWrapper {
customColorList?: number[]
}) {
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
...DefaultPluginSpec,
...DefaultPluginSpec(),
animations: [
AnimateModelIndex
],
@@ -271,13 +272,17 @@ class MolStarProteopediaWrapper {
resetPosition: () => PluginCommands.Camera.Reset(this.plugin, { })
}
private animateModelIndexTargetFps() {
return Math.max(1, this.animate.modelIndex.targetFps | 0);
}
animate = {
modelIndex: {
maxFPS: 8,
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }); },
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }); },
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }); },
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }); },
targetFps: 8,
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: {} } }); },
stop: () => this.plugin.managers.animation.stop()
}
}
@@ -403,7 +408,7 @@ class MolStarProteopediaWrapper {
},
download: async (type: 'molj' | 'molx' = 'molj', params?: PluginState.SnapshotParams) => {
const data = await this.plugin.managers.snapshot.serialize({ type, params });
download(data, `mol-star_state_${(name || getFormattedTime())}.${type}`);
download(data, `mol-star_state_${getFormattedTime()}.${type}`);
},
fetch: async (url: string, type: 'molj' | 'molx' = 'molj') => {
try {

View File

@@ -0,0 +1,214 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Box3D } from '../../../mol-math/geometry';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { RuntimeContext } from '../../../mol-task';
import { sphericalCollocation } from '../collocation';
import { Basis, CubeGridInfo } from '../data-model';
describe('alpha-orbitals-cubes', () => {
it('water', async () => {
const grid: CubeGridInfo = {
params: {
basis: _testBasis,
cutoffThreshold: 0,
sphericalOrder: 'cca-reverse',
boxExpand: 0,
gridSpacing: []
},
box: Box3D.create(Vec3.create(-1, -1, -1), Vec3.create(1, 1, 1)),
delta: Vec3.create(2, 2, 2),
dimensions: Vec3.create(2, 2, 2),
npoints: 8,
size: Vec3.create(2, 2, 2)
};
const matrix = await sphericalCollocation(grid, {
energy: 0,
occupancy: 0,
alpha: [-2.2623991420609075e-16, 0.6360205395000592, 0.6672122399886391, -0.3876927909355508, -1.6780131293332933e-16, 2.844782862661151e-16, 4.977960694176068e-19, -2.3945919908996803e-16]
}, RuntimeContext.Synchronous);
const expected = [-0.1451730622877498, 0.06479453956039086, -0.2777738736440713, -0.057116584776260436, 0.05929916178822645, 0.2742903371231049, -0.07221698722165386, 0.15389180241391376];
expect(matrix.length).toBe(expected.length);
for (let i = 0; i < matrix.length; i++) {
expect(Math.abs(matrix[i] - expected[i]) < 1e-6).toBe(true);
}
});
});
const _testBasis: Basis = {
'atoms': [
{
'center': [
0.025886090588624934,
0.019164790004065606,
-0.013539970104105408
] as Vec3,
'shells': [
{
'angularMomentum': [0],
'coefficients': [
[
-0.004151277818987536,
-0.02067024147993795,
-0.05150303336984537,
0.33462711739899537,
0.5621061300983125,
0.17129946969948573
]
],
'exponents': [
152.28769660788095,
27.928015215973073,
7.848374792384515,
1.1223350202705642,
0.5093846587907856,
0.24292266532510307
]
},
{
'angularMomentum': [1],
'coefficients': [
[
0.007924233646294425,
0.051441048251911314,
0.18984000600705359,
0.4049863191150474,
0.40123628611490797,
0.1051855189039082
]
],
'exponents': [
27.203421487167727,
7.09409912597673,
2.5383362605345954,
1.0610730767843852,
0.4851948916410433,
0.22938302550642545
]
}
]
},
{
'center': [
0.5082729578468134,
1.6880351220025265,
0.4963443067810461
] as Vec3,
'shells': [
{
'angularMomentum': [0],
'coefficients': [
[
0.009163596280542963,
0.04936149294292479,
0.16853830490998634,
0.37056279972195677,
0.4164915298246781,
0.13033408410772263
]
],
'exponents': [
33.710073211949485,
6.180705022740464,
1.7291385346152253,
0.5940057549921978,
0.2306698170449518,
0.09500256906284119
]
},
{
'angularMomentum': [0],
'coefficients': [
[
-0.32279868167000036,
3.209629817295221,
2.4672629224617935,
-0.048487066612842224,
-0.2611850111200143,
-0.8917817597810863,
-1.9607480081275706,
-2.203769342520311,
-0.6896328935259993
]
],
'exponents': [
10.256286070314905,
0.6227965325875392,
0.2391007667853915,
33.710073211949485,
6.180705022740464,
1.7291385346152253,
0.5940057549921978,
0.2306698170449518,
0.09500256906284119
]
}
]
},
{
'center': [
1.1367367844436005,
-0.47018519422670163,
-1.356802622574504
] as Vec3,
'shells': [
{
'angularMomentum': [0],
'coefficients': [
[
0.009163596280542963,
0.04936149294292479,
0.16853830490998634,
0.37056279972195677,
0.4164915298246781,
0.13033408410772263
]
],
'exponents': [
33.710073211949485,
6.180705022740464,
1.7291385346152253,
0.5940057549921978,
0.2306698170449518,
0.09500256906284119
]
},
{
'angularMomentum': [0],
'coefficients': [
[
-0.32279868167000036,
3.209629817295221,
2.4672629224617935,
-0.048487066612842224,
-0.2611850111200143,
-0.8917817597810863,
-1.9607480081275706,
-2.203769342520311,
-0.6896328935259993
]
],
'exponents': [
10.256286070314905,
0.6227965325875392,
0.2391007667853915,
33.710073211949485,
6.180705022740464,
1.7291385346152253,
0.5940057549921978,
0.2306698170449518,
0.09500256906284119
]
}
]
}
]
};

View File

@@ -0,0 +1,162 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Inspired by https://github.com/dgasmith/gau2grid.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Vec3 } from '../../mol-math/linear-algebra';
import { RuntimeContext } from '../../mol-task';
import { arrayMin } from '../../mol-util/array';
import { AlphaOrbital, CubeGridInfo } from './data-model';
import { normalizeBasicOrder, SphericalFunctions } from './spherical-functions';
export async function sphericalCollocation(
grid: CubeGridInfo,
orbital: AlphaOrbital,
taskCtx: RuntimeContext
) {
const { basis, sphericalOrder, cutoffThreshold } = grid.params;
let baseCount = 0;
for (const atom of basis.atoms) {
for (const shell of atom.shells) {
for (const L of shell.angularMomentum) {
if (L > 4) {
// TODO: will L > 4 be required? Would need to precompute more functions in that case.
throw new Error('Angular momentum L > 4 not supported.');
}
baseCount += 2 * L + 1;
}
}
}
const matrix = new Float32Array(grid.npoints);
let baseIndex = 0;
for (const atom of basis.atoms) {
for (const shell of atom.shells) {
let amIndex = 0;
for (const L of shell.angularMomentum) {
const alpha = normalizeBasicOrder(
L,
orbital.alpha.slice(baseIndex, baseIndex + 2 * L + 1),
sphericalOrder
);
baseIndex += 2 * L + 1;
collocationBasis(
matrix,
grid,
L,
shell.coefficients[amIndex++],
shell.exponents,
atom.center,
cutoffThreshold,
alpha
);
if (taskCtx.shouldUpdate) {
await taskCtx.update({
message: 'Computing...',
current: baseIndex,
max: baseCount,
isIndeterminate: false,
});
}
}
}
}
return matrix;
}
function collocationBasis(
matrix: Float32Array,
grid: CubeGridInfo,
L: number,
coefficients: number[],
exponents: number[],
center: Vec3,
cutoffThreshold: number,
alpha: number[]
) {
const ncoeff = exponents.length;
const sphericalFunc = SphericalFunctions[L];
const cx = center[0],
cy = center[1],
cz = center[2];
const ny = grid.dimensions[1],
nz = grid.dimensions[2];
const gdx = grid.delta[0],
gdy = grid.delta[1],
gdz = grid.delta[2];
const sx = grid.box.min[0],
sy = grid.box.min[1],
sz = grid.box.min[2];
const cutoffRadius =
cutoffThreshold > 0
? Math.sqrt(-Math.log(cutoffThreshold) / arrayMin(exponents))
: 10000;
const cutoffSquared = cutoffRadius * cutoffRadius;
const radiusBox = getRadiusBox(grid, center, cutoffRadius);
const iMin = radiusBox[0][0],
jMin = radiusBox[0][1],
kMin = radiusBox[0][2];
const iMax = radiusBox[1][0],
jMax = radiusBox[1][1],
kMax = radiusBox[1][2];
for (let i = iMin; i <= iMax; i++) {
const x = sx + gdx * i - cx;
const oX = i * ny * nz;
for (let j = jMin; j <= jMax; j++) {
const y = sy + gdy * j - cy;
const oY = oX + j * nz;
for (let k = kMin; k <= kMax; k++) {
const z = sz + gdz * k - cz;
const R2 = x * x + y * y + z * z;
if (R2 > cutoffSquared) {
continue;
}
let gaussianSum = 0;
for (let c = 0; c < ncoeff; c++) {
gaussianSum +=
coefficients[c] * Math.exp(-exponents[c] * R2);
}
const sphericalSum = L === 0 ? alpha[0] : sphericalFunc(alpha, x, y, z);
matrix[k + oY] += gaussianSum * sphericalSum;
}
}
}
}
function getRadiusBox(grid: CubeGridInfo, center: Vec3, radius: number) {
const r = Vec3.create(radius, radius, radius);
const min = Vec3.scaleAndAdd(Vec3(), center, r, -1);
const max = Vec3.add(Vec3(), center, r);
Vec3.sub(min, min, grid.box.min);
Vec3.sub(max, max, grid.box.min);
Vec3.div(min, min, grid.delta);
Vec3.floor(min, min);
Vec3.max(min, min, Vec3());
Vec3.div(max, max, grid.delta);
Vec3.ceil(max, max);
Vec3.min(max, max, Vec3.subScalar(Vec3(), grid.dimensions, 1));
return [min, max];
}

View File

@@ -0,0 +1,131 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Mat4, Tensor, Vec3 } from '../../mol-math/linear-algebra';
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';
// Note: generally contracted gaussians are currently not supported.
export interface SphericalElectronShell {
exponents: number[];
angularMomentum: number[];
// number[] for each angular momentum
coefficients: number[][];
}
export interface Basis {
atoms: {
// in Bohr units!
center: Vec3;
shells: SphericalElectronShell[];
}[];
}
export interface AlphaOrbital {
energy: number;
occupancy: number;
alpha: number[];
}
export interface CubeGridComputationParams {
basis: Basis;
/**
* for each electron shell compute a cutoff radius as
* const cutoffRadius = Math.sqrt(-Math.log(cutoffThreshold) / arrayMin(exponents));
*/
cutoffThreshold: number;
sphericalOrder: SphericalBasisOrder;
boxExpand: number;
gridSpacing: number | [atomCountThreshold: number, spacing: number][];
doNotComputeIsovalues?: boolean;
}
export interface CubeGridInfo {
params: CubeGridComputationParams;
dimensions: Vec3;
box: Box3D;
size: Vec3;
npoints: number;
delta: Vec3;
}
export interface CubeGrid {
grid: Grid;
isovalues?: { negative?: number; positive?: number };
}
export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
const geometry = params.basis.atoms.map(a => a.center);
const { gridSpacing: spacing, boxExpand: expand } = params;
const count = geometry.length;
const box = Box3D.expand(
Box3D(),
Box3D.fromVec3Array(Box3D(), geometry),
Vec3.create(expand, expand, expand)
);
const size = Box3D.size(Vec3(), box);
const spacingThresholds =
typeof spacing === 'number' ? [[0, spacing]] : [...spacing];
spacingThresholds.sort((a, b) => b[0] - a[0]);
let s = 0.4;
for (let i = 0; i <= spacingThresholds.length; i++) {
s = spacingThresholds[i][1];
if (spacingThresholds[i][0] <= count) break;
}
const dimensions = Vec3.ceil(Vec3(), Vec3.scale(Vec3(), size, 1 / s));
return {
params,
box,
dimensions,
size,
npoints: dimensions[0] * dimensions[1] * dimensions[2],
delta: Vec3.div(Vec3(), size, Vec3.subScalar(Vec3(), dimensions, 1)),
};
}
const BohrToAngstromFactor = 0.529177210859;
export function createGrid(gridInfo: RegularGrid3d, values: Float32Array, axisOrder: number[]) {
const boxSize = Box3D.size(Vec3(), gridInfo.box);
const boxOrigin = Vec3.clone(gridInfo.box.min);
Vec3.scale(boxSize, boxSize, BohrToAngstromFactor);
Vec3.scale(boxOrigin, boxOrigin, BohrToAngstromFactor);
const scale = Mat4.fromScaling(
Mat4(),
Vec3.div(
Vec3(),
boxSize,
Vec3.sub(Vec3(), gridInfo.dimensions, Vec3.create(1, 1, 1))
)
);
const translate = Mat4.fromTranslation(Mat4(), boxOrigin);
const matrix = Mat4.mul(Mat4(), translate, scale);
const grid: Grid = {
transform: { kind: 'matrix', matrix },
cells: Tensor.create(
Tensor.Space(gridInfo.dimensions, axisOrder, Float32Array),
(values as any) as Tensor.Data
),
stats: {
min: arrayMin(values),
max: arrayMax(values),
mean: arrayMean(values),
sigma: arrayRms(values),
},
};
return grid;
}

View File

@@ -0,0 +1,124 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { sortArray } from '../../mol-data/util';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Task } from '../../mol-task';
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
import { gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute';
export function createSphericalCollocationDensityGrid(
params: CubeGridComputationParams, orbitals: AlphaOrbital[], webgl?: WebGLContext
): Task<CubeGrid> {
return Task.create('Spherical Collocation Grid', async (ctx) => {
const cubeGrid = initCubeGrid(params);
let matrix: Float32Array;
if (canComputeGrid3dOnGPU(webgl)) {
// console.time('gpu');
matrix = await gpuComputeAlphaOrbitalsDensityGridValues(ctx, webgl!, cubeGrid, orbitals);
// console.timeEnd('gpu');
} else {
throw new Error('Missing OES_texture_float WebGL extension.');
}
const grid = createGrid(cubeGrid, matrix, [0, 1, 2]);
let isovalues: { negative?: number, positive?: number } | undefined;
if (!params.doNotComputeIsovalues) {
isovalues = computeDensityIsocontourValues(matrix, 0.85);
}
return { grid, isovalues };
});
}
export function computeDensityIsocontourValues(input: Float32Array, cumulativeThreshold: number) {
let weightSum = 0;
for (let i = 0, _i = input.length; i < _i; i++) {
const v = input[i];
const w = Math.abs(v);
weightSum += w;
}
const avgWeight = weightSum / input.length;
let minWeight = 3 * avgWeight;
// do not try to identify isovalues for degenerate data
// e.g. all values are almost same
if (Math.abs(avgWeight - input[0] * input[0]) < 1e-5) {
return { negative: void 0, positive: void 0 };
}
let size = 0;
while (true) {
let csum = 0;
size = 0;
for (let i = 0, _i = input.length; i < _i; i++) {
const v = input[i];
const w = Math.abs(v);
if (w >= minWeight) {
csum += w;
size++;
}
}
if (csum / weightSum > cumulativeThreshold) {
break;
}
minWeight -= avgWeight;
}
const values = new Float32Array(size);
const weights = new Float32Array(size);
const indices = new Int32Array(size);
let o = 0;
for (let i = 0, _i = input.length; i < _i; i++) {
const v = input[i];
const w = Math.abs(v);
if (w >= minWeight) {
values[o] = v;
weights[o] = w;
indices[o] = o;
o++;
}
}
sortArray(
indices,
(indices, i, j) => weights[indices[j]] - weights[indices[i]]
);
let cweight = 0,
cutoffIndex = 0;
for (let i = 0; i < size; i++) {
cweight += weights[indices[i]];
if (cweight / weightSum >= cumulativeThreshold) {
cutoffIndex = i;
break;
}
}
let positive = Number.POSITIVE_INFINITY,
negative = Number.NEGATIVE_INFINITY;
for (let i = 0; i < cutoffIndex; i++) {
const v = values[indices[i]];
if (v > 0) {
if (v < positive) positive = v;
} else if (v < 0) {
if (v > negative) negative = v;
}
}
return {
negative: negative !== Number.NEGATIVE_INFINITY ? negative : void 0,
positive: positive !== Number.POSITIVE_INFINITY ? positive : void 0,
};
}

View File

@@ -0,0 +1,170 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { createGrid3dComputeRenderable } from '../../../mol-gl/compute/grid3d';
import { TextureSpec, UnboxedValues, UniformSpec } from '../../../mol-gl/renderable/schema';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { RuntimeContext } from '../../../mol-task';
import { ValueCell } from '../../../mol-util';
import { arrayMin } from '../../../mol-util/array';
import { AlphaOrbital, Basis, CubeGridInfo } from '../data-model';
import { normalizeBasicOrder, SphericalBasisOrder } from '../spherical-functions';
import { MAIN, UTILS } from './shader.frag';
const Schema = {
tCenters: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
tInfo: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
tCoeff: TextureSpec('image-float32', 'rgb', 'float', 'nearest'),
tAlpha: TextureSpec('image-float32', 'alpha', 'float', 'nearest'),
uNCenters: UniformSpec('i'),
uNAlpha: UniformSpec('i'),
uNCoeff: UniformSpec('i'),
uMaxCoeffs: UniformSpec('i'),
};
const Orbitals = createGrid3dComputeRenderable({
schema: Schema,
loopBounds: ['uNCenters', 'uMaxCoeffs'],
mainCode: MAIN,
utilCode: UTILS,
returnCode: 'v',
values(params: { grid: CubeGridInfo, orbital: AlphaOrbital }) {
return createTextureData(params.grid, params.orbital);
}
});
const Density = createGrid3dComputeRenderable({
schema: {
...Schema,
uOccupancy: UniformSpec('f'),
},
loopBounds: ['uNCenters', 'uMaxCoeffs'],
mainCode: MAIN,
utilCode: UTILS,
returnCode: 'current + uOccupancy * v * v',
values(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
return {
...createTextureData(params.grid, params.orbitals[0]),
uOccupancy: 0
};
},
cumulative: {
states(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) {
return params.orbitals.filter(o => o.occupancy !== 0);
},
update({ grid }, state: AlphaOrbital, values) {
const alpha = getNormalizedAlpha(grid.params.basis, state.alpha, grid.params.sphericalOrder);
ValueCell.updateIfChanged(values.uOccupancy, state.occupancy);
ValueCell.update(values.tAlpha, { width: alpha.length, height: 1, array: alpha });
}
}
});
export function gpuComputeAlphaOrbitalsGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital) {
return Orbitals(ctx, webgl, grid, { grid, orbital });
}
export function gpuComputeAlphaOrbitalsDensityGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbitals: AlphaOrbital[]) {
return Density(ctx, webgl, grid, { grid, orbitals });
}
function getNormalizedAlpha(basis: Basis, alphaOrbitals: number[], sphericalOrder: SphericalBasisOrder) {
const alpha = new Float32Array(alphaOrbitals.length);
let aO = 0;
for (const atom of basis.atoms) {
for (const shell of atom.shells) {
for (const L of shell.angularMomentum) {
const a0 = normalizeBasicOrder(L, alphaOrbitals.slice(aO, aO + 2 * L + 1), sphericalOrder);
for (let i = 0; i < a0.length; i++) alpha[aO + i] = a0[i];
aO += 2 * L + 1;
}
}
}
return alpha;
}
function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital): UnboxedValues<typeof Schema> {
const { basis, sphericalOrder, cutoffThreshold } = grid.params;
let centerCount = 0;
let baseCount = 0;
let coeffCount = 0;
for (const atom of basis.atoms) {
for (const shell of atom.shells) {
for (const L of shell.angularMomentum) {
if (L > 4) {
// TODO: will L > 4 be required? Would need to precompute more functions in that case.
throw new Error('Angular momentum L > 4 not supported.');
}
centerCount++;
baseCount += 2 * L + 1;
coeffCount += shell.exponents.length;
}
}
}
const centers = new Float32Array(4 * centerCount);
// L, alpha_offset, coeff_offset_start, coeff_offset_end
const info = new Float32Array(4 * centerCount);
const alpha = new Float32Array(baseCount);
const coeff = new Float32Array(3 * coeffCount);
let maxCoeffs = 0;
let cO = 0, aO = 0, coeffO = 0;
for (const atom of basis.atoms) {
for (const shell of atom.shells) {
let amIndex = 0;
for (const L of shell.angularMomentum) {
const a0 = normalizeBasicOrder(L, orbital.alpha.slice(aO, aO + 2 * L + 1), sphericalOrder);
const cutoffRadius = cutoffThreshold > 0
? Math.sqrt(-Math.log(cutoffThreshold) / arrayMin(shell.exponents))
: 10000;
centers[4 * cO + 0] = atom.center[0];
centers[4 * cO + 1] = atom.center[1];
centers[4 * cO + 2] = atom.center[2];
centers[4 * cO + 3] = cutoffRadius * cutoffRadius;
info[4 * cO + 0] = L;
info[4 * cO + 1] = aO;
info[4 * cO + 2] = coeffO;
info[4 * cO + 3] = coeffO + shell.exponents.length;
for (let i = 0; i < a0.length; i++) alpha[aO + i] = a0[i];
const c0 = shell.coefficients[amIndex++];
for (let i = 0; i < shell.exponents.length; i++) {
coeff[3 * (coeffO + i) + 0] = c0[i];
coeff[3 * (coeffO + i) + 1] = shell.exponents[i];
}
if (c0.length > maxCoeffs) {
maxCoeffs = c0.length;
}
cO++;
aO += 2 * L + 1;
coeffO += shell.exponents.length;
}
}
}
return {
uNCenters: centerCount,
uNAlpha: baseCount,
uNCoeff: coeffCount,
uMaxCoeffs: maxCoeffs,
tCenters: { width: centerCount, height: 1, array: centers },
tInfo: { width: centerCount, height: 1, array: info },
tCoeff: { width: coeffCount, height: 1, array: coeff },
tAlpha: { width: baseCount, height: 1, array: alpha },
};
}

View File

@@ -0,0 +1,145 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
export const UTILS = `
float L1(vec3 p, float a0, float a1, float a2) {
return a0 * p.z + a1 * p.x + a2 * p.y;
}
float L2(vec3 p, float a0, float a1, float a2, float a3, float a4) {
float x = p.x, y = p.y, z = p.z;
float xx = x * x, yy = y * y, zz = z * z;
return (
a0 * (-0.5 * xx - 0.5 * yy + zz) +
a1 * (1.7320508075688772 * x * z) +
a2 * (1.7320508075688772 * y * z) +
a3 * (0.8660254037844386 * xx - 0.8660254037844386 * yy) +
a4 * (1.7320508075688772 * x * y)
);
}
float L3(vec3 p, float a0, float a1, float a2, float a3, float a4, float a5, float a6) {
float x = p.x, y = p.y, z = p.z;
float xx = x * x, yy = y * y, zz = z * z;
float xxx = xx * x, yyy = yy * y, zzz = zz * z;
return (
a0 * (-1.5 * xx * z - 1.5 * yy * z + zzz) +
a1 * (-0.6123724356957945 * xxx - 0.6123724356957945 * x * yy + 2.449489742783178 * x * zz) +
a2 * (-0.6123724356957945 * xx * y - 0.6123724356957945 * yyy + 2.449489742783178 * y * zz) +
a3 * (1.9364916731037085 * xx * z - 1.9364916731037085 * yy * z) +
a4 * (3.872983346207417 * x * y * z) +
a5 * (0.7905694150420949 * xxx - 2.3717082451262845 * x * yy) +
a6 * (2.3717082451262845 * xx * y - 0.7905694150420949 * yyy)
);
}
float L4(vec3 p, float a0, float a1, float a2, float a3, float a4, float a5, float a6, float a7, float a8) {
float x = p.x, y = p.y, z = p.z;
float xx = x * x, yy = y * y, zz = z * z;
float xxx = xx * x, yyy = yy * y, zzz = zz * z;
float xxxx = xxx * x, yyyy = yyy * y, zzzz = zzz * z;
return (
a0 * (0.375 * xxxx + 0.75 * xx * yy + 0.375 * yyyy - 3.0 * xx * zz - 3.0 * yy * zz + zzzz) +
a1 * (-2.3717082451262845 * xxx * z - 2.3717082451262845 * x * yy * z + 3.1622776601683795 * x * zzz) +
a2 * (-2.3717082451262845 * xx * y * z - 2.3717082451262845 * yyy * z + 3.1622776601683795 * y * zzz) +
a3 * (-0.5590169943749475 * xxxx + 0.5590169943749475 * yyyy + 3.3541019662496847 * xx * zz - 3.3541019662496847 * yy * zz) +
a4 * (-1.118033988749895 * xxx * y - 1.118033988749895 * x * yyy + 6.708203932499369 * x * y * zz) +
a5 * (2.091650066335189 * xxx * z + -6.274950199005566 * x * yy * z) +
a6 * (6.274950199005566 * xx * y * z + -2.091650066335189 * yyy * z) +
a7 * (0.739509972887452 * xxxx - 4.437059837324712 * xx * yy + 0.739509972887452 * yyyy) +
a8 * (2.958039891549808 * xxx * y + -2.958039891549808 * x * yyy)
);
}
float alpha(float offset, float f) {
#ifdef WEBGL1
// in webgl1, the value is in the alpha channel!
return texture2D(tAlpha, vec2(offset * f, 0.5)).a;
#else
return texture2D(tAlpha, vec2(offset * f, 0.5)).x;
#endif
}
float Y(int L, vec3 X, float aO, float fA) {
if (L == 0) {
return alpha(aO, fA);
} else if (L == 1) {
return L1(X,
alpha(aO, fA), alpha(aO + 1.0, fA), alpha(aO + 2.0, fA)
);
} else if (L == 2) {
return L2(X,
alpha(aO, fA), alpha(aO + 1.0, fA), alpha(aO + 2.0, fA), alpha(aO + 3.0, fA), alpha(aO + 4.0, fA)
);
} else if (L == 3) {
return L3(X,
alpha(aO, fA), alpha(aO + 1.0, fA), alpha(aO + 2.0, fA), alpha(aO + 3.0, fA), alpha(aO + 4.0, fA),
alpha(aO + 5.0, fA), alpha(aO + 6.0, fA)
);
} else if (L == 4) {
return L4(X,
alpha(aO, fA), alpha(aO + 1.0, fA), alpha(aO + 2.0, fA), alpha(aO + 3.0, fA), alpha(aO + 4.0, fA),
alpha(aO + 5.0, fA), alpha(aO + 6.0, fA), alpha(aO + 7.0, fA), alpha(aO + 8.0, fA)
);
}
// TODO: do we need L > 4?
return 0.0;
}
#ifndef WEBGL1
float R(float R2, int start, int end, float fCoeff) {
float gauss = 0.0;
for (int i = start; i < end; i++) {
vec2 c = texture2D(tCoeff, vec2(float(i) * fCoeff, 0.5)).xy;
gauss += c.x * exp(-c.y * R2);
}
return gauss;
}
#else
float R(float R2, int start, int end, float fCoeff) {
float gauss = 0.0;
int o = start;
for (int i = 0; i < uMaxCoeffs; i++) {
if (o >= end) break;
vec2 c = texture2D(tCoeff, vec2(float(o) * fCoeff, 0.5)).xy;
gauss += c.x * exp(-c.y * R2);
o++;
}
return gauss;
}
#endif
`;
export const MAIN = `
float fCenter = 1.0 / float(uNCenters - 1);
float fCoeff = 1.0 / float(uNCoeff - 1);
float fA = 1.0 / float(uNAlpha - 1);
float v = 0.0;
for (int i = 0; i < uNCenters; i++) {
vec2 cCoord = vec2(float(i) * fCenter, 0.5);
vec4 center = texture2D(tCenters, cCoord);
vec3 X = xyz - center.xyz;
float R2 = dot(X, X);
// center.w is squared cutoff radius
if (R2 > center.w) {
continue;
}
vec4 info = texture2D(tInfo, cCoord);
int L = int(info.x);
float aO = info.y;
int coeffStart = int(info.z);
int coeffEnd = int(info.w);
v += R(R2, coeffStart, coeffEnd, fCoeff) * Y(L, X, aO, fA);
}
`;

View File

@@ -0,0 +1,131 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Inspired by https://github.com/dgasmith/gau2grid.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { sortArray } from '../../mol-data/util';
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Task } from '../../mol-task';
import { sphericalCollocation } from './collocation';
import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model';
import { gpuComputeAlphaOrbitalsGridValues } from './gpu/compute';
// setDebugMode(true);
export function createSphericalCollocationGrid(
params: CubeGridComputationParams, orbital: AlphaOrbital, webgl?: WebGLContext
): Task<CubeGrid> {
return Task.create('Spherical Collocation Grid', async (ctx) => {
const cubeGrid = initCubeGrid(params);
let matrix: Float32Array;
if (canComputeGrid3dOnGPU(webgl)) {
// console.time('gpu');
matrix = await gpuComputeAlphaOrbitalsGridValues(ctx, webgl!, cubeGrid, orbital);
// console.timeEnd('gpu');
} else {
// console.time('cpu');
matrix = await sphericalCollocation(cubeGrid, orbital, ctx);
// console.timeEnd('cpu');
}
const grid = createGrid(cubeGrid, matrix, [0, 1, 2]);
let isovalues: { negative?: number, positive?: number } | undefined;
if (!params.doNotComputeIsovalues) {
isovalues = computeOrbitalIsocontourValues(matrix, 0.85);
}
return { grid, isovalues };
});
}
export function computeOrbitalIsocontourValues(input: Float32Array, cumulativeThreshold: number) {
let weightSum = 0;
for (let i = 0, _i = input.length; i < _i; i++) {
const v = input[i];
const w = v * v;
weightSum += w;
}
const avgWeight = weightSum / input.length;
let minWeight = 3 * avgWeight;
// do not try to identify isovalues for degenerate data
// e.g. all values are almost same
if (Math.abs(avgWeight - input[0] * input[0]) < 1e-5) {
return { negative: void 0, positive: void 0 };
}
let size = 0;
while (true) {
let csum = 0;
size = 0;
for (let i = 0, _i = input.length; i < _i; i++) {
const v = input[i];
const w = v * v;
if (w >= minWeight) {
csum += w;
size++;
}
}
if (csum / weightSum > cumulativeThreshold) {
break;
}
minWeight -= avgWeight;
}
const values = new Float32Array(size);
const weights = new Float32Array(size);
const indices = new Int32Array(size);
let o = 0;
for (let i = 0, _i = input.length; i < _i; i++) {
const v = input[i];
const w = v * v;
if (w >= minWeight) {
values[o] = v;
weights[o] = w;
indices[o] = o;
o++;
}
}
sortArray(
indices,
(indices, i, j) => weights[indices[j]] - weights[indices[i]]
);
let cweight = 0,
cutoffIndex = 0;
for (let i = 0; i < size; i++) {
cweight += weights[indices[i]];
if (cweight / weightSum >= cumulativeThreshold) {
cutoffIndex = i;
break;
}
}
let positive = Number.POSITIVE_INFINITY,
negative = Number.NEGATIVE_INFINITY;
for (let i = 0; i < cutoffIndex; i++) {
const v = values[indices[i]];
if (v > 0) {
if (v < positive) positive = v;
} else if (v < 0) {
if (v > negative) negative = v;
}
}
return {
negative: negative !== Number.NEGATIVE_INFINITY ? negative : void 0,
positive: positive !== Number.POSITIVE_INFINITY ? positive : void 0,
};
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Inspired by https://github.com/dgasmith/gau2grid.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
// gaussian:
// R_0, R^+_1, R^-_1, ..., R^+_l, R^-_l
// cca:
// R^-_(l), R^-_(l-1), ..., R_0, ..., R^+_(l-1), R^+_l
// cca-reverse:
// R^+_(l), R^+_(l-1), ..., R_0, ..., R^-_(l-1), R^-_l
export type SphericalBasisOrder = 'gaussian' | 'cca' | 'cca-reverse';
export function normalizeBasicOrder(
L: number,
alpha: number[],
order: SphericalBasisOrder
) {
if (order === 'gaussian' || L === 0) return alpha;
const ret: number[] = [alpha[L]];
for (let l = 0; l < L; l++) {
const a = alpha[L - l - 1],
b = alpha[L + l + 1];
if (order === 'cca') ret.push(b, a);
else ret.push(a, b);
}
return ret;
}
export type SphericalFunc = (
alpha: number[],
x: number,
y: number,
z: number
) => number;
export const SphericalFunctions: SphericalFunc[] = [L0, L1, L2, L3, L4];
// L_i functions were auto-generated.
function L0(alpha: number[], x: number, y: number, z: number) {
return alpha[0];
}
function L1(alpha: number[], x: number, y: number, z: number) {
return alpha[0] * z + alpha[1] * x + alpha[2] * y;
}
function L2(alpha: number[], x: number, y: number, z: number) {
const xx = x * x, yy = y * y, zz = z * z;
return (
alpha[0] * (-0.5 * xx - 0.5 * yy + zz) +
alpha[1] * (1.7320508075688772 * x * z) +
alpha[2] * (1.7320508075688772 * y * z) +
alpha[3] * (0.8660254037844386 * xx - 0.8660254037844386 * yy) +
alpha[4] * (1.7320508075688772 * x * y)
);
}
function L3(alpha: number[], x: number, y: number, z: number) {
const xx = x * x, yy = y * y, zz = z * z;
const xxx = xx * x, yyy = yy * y, zzz = zz * z;
return (
alpha[0] * (-1.5 * xx * z - 1.5 * yy * z + zzz) +
alpha[1] * (-0.6123724356957945 * xxx - 0.6123724356957945 * x * yy + 2.449489742783178 * x * zz) +
alpha[2] * (-0.6123724356957945 * xx * y - 0.6123724356957945 * yyy + 2.449489742783178 * y * zz) +
alpha[3] * (1.9364916731037085 * xx * z - 1.9364916731037085 * yy * z) +
alpha[4] * (3.872983346207417 * x * y * z) +
alpha[5] * (0.7905694150420949 * xxx - 2.3717082451262845 * x * yy) +
alpha[6] * (2.3717082451262845 * xx * y - 0.7905694150420949 * yyy)
);
}
function L4(alpha: number[], x: number, y: number, z: number) {
const xx = x * x, yy = y * y, zz = z * z;
const xxx = xx * x, yyy = yy * y, zzz = zz * z;
const xxxx = xxx * x, yyyy = yyy * y, zzzz = zzz * z;
return (
alpha[0] * (0.375 * xxxx + 0.75 * xx * yy + 0.375 * yyyy - 3.0 * xx * zz - 3.0 * yy * zz + zzzz) +
alpha[1] * (-2.3717082451262845 * xxx * z - 2.3717082451262845 * x * yy * z + 3.1622776601683795 * x * zzz) +
alpha[2] * (-2.3717082451262845 * xx * y * z - 2.3717082451262845 * yyy * z + 3.1622776601683795 * y * zzz) +
alpha[3] * (-0.5590169943749475 * xxxx + 0.5590169943749475 * yyyy + 3.3541019662496847 * xx * zz - 3.3541019662496847 * yy * zz) +
alpha[4] * (-1.118033988749895 * xxx * y - 1.118033988749895 * x * yyy + 6.708203932499369 * x * y * zz) +
alpha[5] * (2.091650066335189 * xxx * z + -6.274950199005566 * x * yy * z) +
alpha[6] * (6.274950199005566 * xx * y * z + -2.091650066335189 * yyy * z) +
alpha[7] * (0.739509972887452 * xxxx - 4.437059837324712 * xx * yy + 0.739509972887452 * yyyy) +
alpha[8] * (2.958039891549808 * xxx * y + -2.958039891549808 * x * yyy)
);
}

View File

@@ -0,0 +1,236 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { PluginStateObject, PluginStateTransform } from '../../mol-plugin-state/objects';
import { createSphericalCollocationGrid } from './orbitals';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Task } from '../../mol-task';
import { CustomProperties } from '../../mol-model/custom-property';
import { SphericalBasisOrder } from './spherical-functions';
import { Volume } from '../../mol-model/volume';
import { PluginContext } from '../../mol-plugin/context';
import { ColorNames } from '../../mol-util/color/names';
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
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 { createSphericalCollocationDensityGrid } from './density';
import { Tensor } from '../../mol-math/linear-algebra';
export class BasisAndOrbitals extends PluginStateObject.Create<{ basis: Basis, order: SphericalBasisOrder, orbitals: AlphaOrbital[] }>({ name: 'Basis', typeClass: 'Object' }) { }
export const StaticBasisAndOrbitals = PluginStateTransform.BuiltIn({
name: 'static-basis-and-orbitals',
display: 'Basis and Orbitals',
from: PluginStateObject.Root,
to: BasisAndOrbitals,
params: {
label: PD.Text('Orbital Data', { isHidden: true }),
basis: PD.Value<Basis>(void 0 as any, { isHidden: true }),
order: PD.Text<SphericalBasisOrder>('gaussian' as SphericalBasisOrder, { isHidden: true }),
orbitals: PD.Value<AlphaOrbital[]>([], { isHidden: true })
},
})({
apply({ params }) {
return new BasisAndOrbitals({ basis: params.basis, order: params.order, orbitals: params.orbitals }, { label: params.label });
}
});
const CreateOrbitalVolumeParamBase = {
cutoffThreshold: PD.Numeric(0.0015, { min: 0, max: 0.1, step: 0.0001 }),
boxExpand: PD.Numeric(4.5, { min: 0, max: 7, step: 0.1 }),
gridSpacing: PD.ObjectList({ atomCount: PD.Numeric(0), spacing: PD.Numeric(0.35, { min: 0.1, max: 2, step: 0.01 }) }, e => `Atoms ${e.atomCount}: ${e.spacing}`, {
defaultValue: [
{ atomCount: 55, spacing: 0.5 },
{ atomCount: 40, spacing: 0.45 },
{ atomCount: 25, spacing: 0.4 },
{ atomCount: 0, spacing: 0.35 },
]
}),
clampValues: PD.MappedStatic('off', {
off: PD.EmptyGroup(),
on: PD.Group({
sigma: PD.Numeric(8, { min: 1, max: 20 }, { description: 'Clamp values to range [sigma * negIsoValue, sigma * posIsoValue].' })
})
})
};
function clampData(matrix: Tensor.Data, min: number, max: number) {
for (let i = 0, _i = matrix.length; i < _i; i++) {
const v = matrix[i];
if (v < min) matrix[i] = min;
else if (v > max) matrix[i] = max;
}
}
function clampGrid(data: CubeGrid, v: number) {
const grid = data.grid;
const min = (data.isovalues?.negative ?? data.grid.stats.min) * v;
const max = (data.isovalues?.positive ?? data.grid.stats.max) * v;
// clamp values for better direct volume resolution
// current implementation uses Byte array for values
// if this is not enough, update mol* to use float
// textures instead
if (grid.stats.min < min || grid.stats.max > max) {
clampData(data.grid.cells.data, min, max);
if (grid.stats.min < min) {
(grid.stats.min as number) = min;
}
if (grid.stats.max > max) {
(grid.stats.max as number) = max;
}
}
}
export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
name: 'create-orbital-volume',
display: 'Orbital Volume',
from: BasisAndOrbitals,
to: PluginStateObject.Volume.Data,
params: (a) => {
if (!a) {
return { index: PD.Numeric(0), ...CreateOrbitalVolumeParamBase };
}
return {
index: PD.Select(0, a.data.orbitals.map((o, i) => [i, `[${i + 1}] ${o.energy.toFixed(4)}`])),
...CreateOrbitalVolumeParamBase
};
}
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Orbital Volume', async ctx => {
const data = await createSphericalCollocationGrid({
basis: a.data.basis,
cutoffThreshold: params.cutoffThreshold,
sphericalOrder: a.data.order,
boxExpand: params.boxExpand,
gridSpacing: params.gridSpacing.map(e => [e.atomCount, e.spacing] as [number, number])
}, a.data.orbitals[params.index], plugin.canvas3d?.webgl).runInContext(ctx);
const volume: Volume = {
grid: data.grid,
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
customProperties: new CustomProperties(),
_propertyData: Object.create(null),
};
if (params.clampValues?.name === 'on') {
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
}
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
});
}
});
export const CreateOrbitalDensityVolume = PluginStateTransform.BuiltIn({
name: 'create-orbital-density-volume',
display: 'Orbital Density Volume',
from: BasisAndOrbitals,
to: PluginStateObject.Volume.Data,
params: CreateOrbitalVolumeParamBase
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('Orbital Volume', async ctx => {
const data = await createSphericalCollocationDensityGrid({
basis: a.data.basis,
cutoffThreshold: params.cutoffThreshold,
sphericalOrder: a.data.order,
boxExpand: params.boxExpand,
gridSpacing: params.gridSpacing.map(e => [e.atomCount, e.spacing] as [number, number])
}, a.data.orbitals, plugin.canvas3d?.webgl).runInContext(ctx);
const volume: Volume = {
grid: data.grid,
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
customProperties: new CustomProperties(),
_propertyData: Object.create(null),
};
if (params.clampValues?.name === 'on') {
clampGrid(data, params.clampValues?.params?.sigma ?? 8);
}
return new PluginStateObject.Volume.Data(volume, { label: 'Orbital Volume' });
});
}
});
export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
name: 'create-orbital-representation-3d',
display: 'Orbital Representation 3D',
from: PluginStateObject.Volume.Data,
to: PluginStateObject.Volume.Representation3D,
params: {
directVolume: PD.Boolean(false),
relativeIsovalue: PD.Numeric(1, { min: 0.01, max: 5, step: 0.01 }),
kind: PD.Select<'positive' | 'negative'>('positive', [['positive', 'Positive'], ['negative', 'Negative']]),
color: PD.Color(ColorNames.blue),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
xrayShaded: PD.Boolean(false),
pickable: PD.Boolean(true)
}
})({
canAutoUpdate() {
return true;
},
apply({ a, params: srcParams }, plugin: PluginContext) {
return Task.create('Orbitals Representation 3D', async ctx => {
const params = volumeParams(plugin, a, srcParams);
const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset };
const provider = plugin.representation.volume.registry.get(params.type.name);
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
const props = params.type.params || {};
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
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) });
});
},
update({ a, b, newParams: srcParams }, plugin: PluginContext) {
return Task.create('Orbitals Representation 3D', async ctx => {
const newParams = volumeParams(plugin, a, srcParams);
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.repr.setState({ pickable: srcParams.pickable });
b.description = VolumeRepresentation3DHelpers.getDescription(props);
return StateTransformer.UpdateResult.Updated;
});
}
});
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.');
const { isovalues } = volume.data.sourceData.data as CubeGrid;
if (!isovalues) throw new Error('Isovalues are not computed.');
const value = isovalues[params.kind];
return createVolumeRepresentationParams(plugin, volume.data, params.directVolume ? {
type: 'direct-volume',
typeParams: {
alpha: params.alpha,
renderMode: {
name: 'isosurface',
params: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, singleLayer: false }
},
xrayShaded: params.xrayShaded
},
color: 'uniform',
colorParams: { value: params.color }
} : {
type: 'isosurface',
typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded },
color: 'uniform',
colorParams: { value: params.color }
});
}

View File

@@ -0,0 +1,370 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Structure, StructureElement, StructureProperties } from '../../mol-model/structure';
import { Task, RuntimeContext } from '../../mol-task';
import { CentroidHelper } from '../../mol-math/geometry/centroid-helper';
import { AccessibleSurfaceAreaProvider } from '../../mol-model-props/computed/accessible-surface-area';
import { Vec3 } from '../../mol-math/linear-algebra';
import { getElementMoleculeType } from '../../mol-model/structure/util';
import { MoleculeType } from '../../mol-model/structure/model/types';
import { AccessibleSurfaceArea } from '../../mol-model-props/computed/accessible-surface-area/shrake-rupley';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { MembraneOrientation } from './prop';
interface ANVILContext {
structure: Structure,
numberOfSpherePoints: number,
stepSize: number,
minThickness: number,
maxThickness: number,
asaCutoff: number,
offsets: ArrayLike<number>,
exposed: ArrayLike<boolean>,
centroid: Vec3,
extent: number
};
export const ANVILParams = {
numberOfSpherePoints: PD.Numeric(120, { min: 35, max: 700, step: 1 }, { description: 'Number of spheres/directions to test for membrane placement. Original value is 350.' }),
stepSize: PD.Numeric(1, { min: 0.25, max: 4, step: 0.25 }, { description: 'Thickness of membrane slices that will be tested' }),
minThickness: PD.Numeric(20, { min: 10, max: 30, step: 1}, { description: 'Minimum membrane thickness used during refinement' }),
maxThickness: PD.Numeric(40, { min: 30, max: 50, step: 1}, { description: 'Maximum membrane thickness used during refinement' }),
asaCutoff: PD.Numeric(40, { min: 10, max: 100, step: 1 }, { description: 'Absolute ASA cutoff above which residues will be considered' })
};
export type ANVILParams = typeof ANVILParams
export type ANVILProps = PD.Values<ANVILParams>
/**
* Implements:
* Membrane positioning for high- and low-resolution protein structures through a binary classification approach
* Guillaume Postic, Yassine Ghouzam, Vincent Guiraud, and Jean-Christophe Gelly
* Protein Engineering, Design & Selection, 2015, 15
* doi: 10.1093/protein/gzv063
*/
export function computeANVIL(structure: Structure, props: ANVILProps) {
return Task.create('Compute Membrane Orientation', async runtime => {
return await calculate(runtime, structure, props);
});
}
const centroidHelper = new CentroidHelper();
function initialize(structure: Structure, props: ANVILProps): ANVILContext {
const l = StructureElement.Location.create(structure);
const { label_atom_id, x, y, z } = StructureProperties.atom;
const elementCount = structure.polymerResidueCount;
centroidHelper.reset();
let offsets = new Int32Array(elementCount);
let exposed = new Array<boolean>(elementCount);
const accessibleSurfaceArea = structure && AccessibleSurfaceAreaProvider.get(structure);
const asa = accessibleSurfaceArea.value!;
const vec = Vec3();
let m = 0;
for (let i = 0, il = structure.units.length; i < il; ++i) {
const unit = structure.units[i];
const { elements } = unit;
l.unit = unit;
for (let j = 0, jl = elements.length; j < jl; ++j) {
const eI = elements[j];
l.element = eI;
// consider only amino acids
if (getElementMoleculeType(unit, eI) !== MoleculeType.Protein) {
continue;
}
// only CA is considered for downstream operations
if (label_atom_id(l) !== 'CA') {
continue;
}
// while iterating use first pass to compute centroid
Vec3.set(vec, x(l), y(l), z(l));
centroidHelper.includeStep(vec);
// keep track of offsets and exposed state to reuse
offsets[m] = structure.serialMapping.getSerialIndex(l.unit, l.element);
exposed[m] = AccessibleSurfaceArea.getValue(l, asa) > props.asaCutoff;
m++;
}
}
// omit potentially empty tail1
offsets = offsets.slice(0, m);
exposed = exposed.slice(0, m);
// calculate centroid and extent
centroidHelper.finishedIncludeStep();
const centroid = centroidHelper.center;
for (let k = 0, kl = offsets.length; k < kl; k++) {
setLocation(l, structure, offsets[k]);
Vec3.set(vec, x(l), y(l), z(l));
centroidHelper.radiusStep(vec);
}
const extent = 1.2 * Math.sqrt(centroidHelper.radiusSq);
return {
...props,
structure: structure,
offsets: offsets,
exposed: exposed,
centroid: centroid,
extent: extent
};
}
export async function calculate(runtime: RuntimeContext, structure: Structure, params: ANVILProps): Promise<MembraneOrientation> {
const { label_comp_id } = StructureProperties.atom;
const ctx = initialize(structure, params);
const initialHphobHphil = HphobHphil.filtered(ctx, label_comp_id);
const initialMembrane = findMembrane(ctx, generateSpherePoints(ctx, ctx.numberOfSpherePoints), initialHphobHphil, label_comp_id);
const alternativeMembrane = findMembrane(ctx, findProximateAxes(ctx, initialMembrane), initialHphobHphil, label_comp_id);
const membrane = initialMembrane.qmax! > alternativeMembrane.qmax! ? initialMembrane : alternativeMembrane;
return {
planePoint1: membrane.planePoint1,
planePoint2: membrane.planePoint2,
normalVector: membrane.normalVector!,
radius: ctx.extent,
centroid: ctx.centroid
};
}
interface MembraneCandidate {
planePoint1: Vec3,
planePoint2: Vec3,
stats: HphobHphil,
normalVector?: Vec3,
spherePoint?: Vec3,
qmax?: number
}
namespace MembraneCandidate {
export function initial(c1: Vec3, c2: Vec3, stats: HphobHphil): MembraneCandidate {
return {
planePoint1: c1,
planePoint2: c2,
stats: stats
};
}
export function scored(spherePoint: Vec3, c1: Vec3, c2: Vec3, stats: HphobHphil, qmax: number, centroid: Vec3): MembraneCandidate {
const diam_vect = Vec3();
Vec3.sub(diam_vect, centroid, spherePoint);
return {
planePoint1: c1,
planePoint2: c2,
stats: stats,
normalVector: diam_vect,
spherePoint: spherePoint,
qmax: qmax
};
}
}
function findMembrane(ctx: ANVILContext, spherePoints: Vec3[], initialStats: HphobHphil, label_comp_id: StructureElement.Property<string>): MembraneCandidate {
const { centroid, stepSize, minThickness, maxThickness } = ctx;
// best performing membrane
let membrane: MembraneCandidate;
// score of the best performing membrane
let qmax = 0;
// construct slices of thickness 1.0 along the axis connecting the centroid and the spherePoint
const diam = Vec3();
for (let i = 0, il = spherePoints.length; i < il; i++) {
const spherePoint = spherePoints[i];
Vec3.sub(diam, centroid, spherePoint);
Vec3.scale(diam, diam, 2);
const diamNorm = Vec3.magnitude(diam);
const qvartemp = [];
for (let i = 0, il = diamNorm - stepSize; i < il; i += stepSize) {
const c1 = Vec3();
const c2 = Vec3();
Vec3.scaleAndAdd(c1, spherePoint, diam, i / diamNorm);
Vec3.scaleAndAdd(c2, spherePoint, diam, (i + stepSize) / diamNorm);
// evaluate how well this membrane slice embeddeds the peculiar residues
const stats = HphobHphil.filtered(ctx, label_comp_id, (testPoint: Vec3) => isInMembranePlane(testPoint, diam, c1, c2));
qvartemp.push(MembraneCandidate.initial(c1, c2, stats));
}
let jmax = (minThickness / stepSize) - 1;
for (let width = 0, widthl = maxThickness; width < widthl;) {
const imax = qvartemp.length - 1 - jmax;
for (let i = 0, il = imax; i < il; i++) {
const c1 = qvartemp[i].planePoint1;
const c2 = qvartemp[i + jmax].planePoint2;
let hphob = 0;
let hphil = 0;
let total = 0;
for (let j = 0; j < jmax; j++) {
const ij = qvartemp[i + j];
if (j === 0 || j === jmax - 1) {
hphob += 0.5 * ij.stats.hphob;
hphil += 0.5 * ij.stats.hphil;
} else {
hphob += ij.stats.hphob;
hphil += ij.stats.hphil;
}
total += ij.stats.total;
}
const stats = HphobHphil.of(hphob, hphil, total);
if (hphob !== 0) {
const qvaltest = qValue(stats, initialStats);
if (qvaltest > qmax) {
qmax = qvaltest;
membrane = MembraneCandidate.scored(spherePoint, c1, c2, HphobHphil.of(hphob, hphil, total), qmax, centroid);
}
}
}
jmax++;
width = (jmax + 1) * stepSize;
}
}
return membrane!;
}
function qValue(currentStats: HphobHphil, initialStats: HphobHphil): number {
if(initialStats.hphob < 1) {
initialStats.hphob = 0.1;
}
if(initialStats.hphil < 1) {
initialStats.hphil += 1;
}
const part_tot = currentStats.hphob + currentStats.hphil;
return (currentStats.hphob * (initialStats.hphil - currentStats.hphil) - currentStats.hphil * (initialStats.hphob - currentStats.hphob)) /
Math.sqrt(part_tot * initialStats.hphob * initialStats.hphil * (initialStats.hphob + initialStats.hphil - part_tot));
}
export function isInMembranePlane(testPoint: Vec3, normalVector: Vec3, planePoint1: Vec3, planePoint2: Vec3): boolean {
const d1 = -Vec3.dot(normalVector, planePoint1);
const d2 = -Vec3.dot(normalVector, planePoint2);
const d = -Vec3.dot(normalVector, testPoint);
return d > Math.min(d1, d2) && d < Math.max(d1, d2);
}
// generates a defined number of points on a sphere with radius = extent around the specified centroid
function generateSpherePoints(ctx: ANVILContext, numberOfSpherePoints: number): Vec3[] {
const { centroid, extent } = ctx;
const points = [];
let oldPhi = 0, h, theta, phi;
for(let k = 1, kl = numberOfSpherePoints + 1; k < kl; k++) {
h = -1 + 2 * (k - 1) / (numberOfSpherePoints - 1);
theta = Math.acos(h);
phi = (k === 1 || k === numberOfSpherePoints) ? 0 : (oldPhi + 3.6 / Math.sqrt(numberOfSpherePoints * (1 - h * h))) % (2 * Math.PI);
const point = Vec3.create(
extent * Math.sin(phi) * Math.sin(theta) + centroid[0],
extent * Math.cos(theta) + centroid[1],
extent * Math.cos(phi) * Math.sin(theta) + centroid[2]
);
points[k - 1] = point;
oldPhi = phi;
}
return points;
}
// generates sphere points close to that of the initial membrane
function findProximateAxes(ctx: ANVILContext, membrane: MembraneCandidate): Vec3[] {
const { numberOfSpherePoints, extent } = ctx;
const points = generateSpherePoints(ctx, 30000);
let j = 4;
let sphere_pts2: Vec3[] = [];
while (sphere_pts2.length < numberOfSpherePoints) {
const d = 2 * extent / numberOfSpherePoints + j;
const dsq = d * d;
sphere_pts2 = [];
for (let i = 0, il = points.length; i < il; i++) {
if (Vec3.squaredDistance(points[i], membrane.spherePoint!) < dsq) {
sphere_pts2.push(points[i]);
}
}
j += 0.2;
}
return sphere_pts2;
}
interface HphobHphil {
hphob: number,
hphil: number,
total: number
}
namespace HphobHphil {
export function of(hphob: number, hphil: number, total?: number) {
return {
hphob: hphob,
hphil: hphil,
total: !!total ? total : hphob + hphil
};
}
const testPoint = Vec3();
export function filtered(ctx: ANVILContext, label_comp_id: StructureElement.Property<string>, filter?: (test: Vec3) => boolean): HphobHphil {
const { offsets, exposed, structure } = ctx;
const l = StructureElement.Location.create(structure);
const { x, y, z } = StructureProperties.atom;
let hphob = 0;
let hphil = 0;
for (let k = 0, kl = offsets.length; k < kl; k++) {
// ignore buried residues
if (!exposed[k]) {
continue;
}
setLocation(l, structure, offsets[k]);
Vec3.set(testPoint, x(l), y(l), z(l));
// testPoints have to be in putative membrane layer
if (filter && !filter(testPoint)) {
continue;
}
if (isHydrophobic(label_comp_id(l))) {
hphob++;
} else {
hphil++;
}
}
return of(hphob, hphil);
}
}
// ANVIL-specific (not general) definition of membrane-favoring amino acids
const HYDROPHOBIC_AMINO_ACIDS = new Set(['ALA', 'CYS', 'GLY', 'HIS', 'ILE', 'LEU', 'MET', 'PHE', 'SER', 'THR', 'VAL']);
export function isHydrophobic(label_comp_id: string): boolean {
return HYDROPHOBIC_AMINO_ACIDS.has(label_comp_id);
}
function setLocation(l: StructureElement.Location, structure: Structure, serialIndex: number) {
l.structure = structure;
l.unit = structure.units[structure.serialMapping.unitIndices[serialIndex]];
l.element = structure.serialMapping.elementIndices[serialIndex];
return l;
}

View File

@@ -0,0 +1,174 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
*/
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../mol-plugin-state/builder/structure/representation-preset';
import { MembraneOrientationProvider, MembraneOrientation } from './prop';
import { StateObjectRef, StateTransformer, StateTransform } from '../../mol-state';
import { Task } from '../../mol-task';
import { PluginBehavior } from '../../mol-plugin/behavior';
import { MembraneOrientationRepresentationProvider, MembraneOrientationParams, MembraneOrientationRepresentation } from './representation';
import { HydrophobicityColorThemeProvider } from '../../mol-theme/color/hydrophobicity';
import { PluginStateObject, PluginStateTransform } from '../../mol-plugin-state/objects';
import { PluginContext } from '../../mol-plugin/context';
import { DefaultQueryRuntimeTable } from '../../mol-script/runtime/query/compiler';
import { StructureSelectionQuery, StructureSelectionCategory } from '../../mol-plugin-state/helpers/structure-selection-query';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { GenericRepresentationRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
const Tag = MembraneOrientation.Tag;
export const ANVILMembraneOrientation = PluginBehavior.create<{ autoAttach: boolean }>({
name: 'anvil-membrane-orientation-prop',
category: 'custom-props',
display: {
name: 'Membrane Orientation',
description: 'Data calculated with ANVIL algorithm.'
},
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> {
private provider = MembraneOrientationProvider
register(): void {
DefaultQueryRuntimeTable.addCustomProp(this.provider.descriptor);
this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach);
this.ctx.representation.structure.registry.add(MembraneOrientationRepresentationProvider);
this.ctx.query.structure.registry.add(isTransmembrane);
this.ctx.genericRepresentationControls.set(Tag.Representation, selection => {
const refs: GenericRepresentationRef[] = [];
selection.structures.forEach(structure => {
const memRepr = structure.genericRepresentations?.filter(r => r.cell.transform.transformer.id === MembraneOrientation3D.id)[0];
if (memRepr) refs.push(memRepr);
});
return [refs, 'Membrane Orientation'];
});
this.ctx.builders.structure.representation.registerPreset(MembraneOrientationPreset);
}
update(p: { autoAttach: boolean }) {
let updated = this.params.autoAttach !== p.autoAttach;
this.params.autoAttach = p.autoAttach;
this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
return updated;
}
unregister() {
DefaultQueryRuntimeTable.removeCustomProp(this.provider.descriptor);
this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
this.ctx.representation.structure.registry.remove(MembraneOrientationRepresentationProvider);
this.ctx.query.structure.registry.remove(isTransmembrane);
this.ctx.genericRepresentationControls.delete(Tag.Representation);
this.ctx.builders.structure.representation.unregisterPreset(MembraneOrientationPreset);
}
},
params: () => ({
autoAttach: PD.Boolean(false)
})
});
//
export const isTransmembrane = StructureSelectionQuery('Residues Embedded in Membrane', MS.struct.modifier.union([
MS.struct.modifier.wholeResidues([
MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
'atom-test': MembraneOrientation.symbols.isTransmembrane.symbol(),
})
])
])
]), {
description: 'Select residues that are embedded between the membrane layers.',
category: StructureSelectionCategory.Residue,
ensureCustomProperties: (ctx, structure) => {
return MembraneOrientationProvider.attach(ctx, structure);
}
});
//
export { MembraneOrientation3D };
type MembraneOrientation3D = typeof MembraneOrientation3D
const MembraneOrientation3D = PluginStateTransform.BuiltIn({
name: 'membrane-orientation-3d',
display: {
name: 'Membrane Orientation',
description: 'Membrane Orientation planes and rims. Data calculated with ANVIL algorithm.'
},
from: PluginStateObject.Molecule.Structure,
to: PluginStateObject.Shape.Representation3D,
params: (a) => {
return {
...MembraneOrientationParams,
};
}
})({
canAutoUpdate({ oldParams, newParams }) {
return true;
},
apply({ a, params }, plugin: PluginContext) {
return Task.create('Membrane Orientation', async ctx => {
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' });
});
},
update({ a, b, newParams }, plugin: PluginContext) {
return Task.create('Membrane Orientation', async ctx => {
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);
return StateTransformer.UpdateResult.Updated;
});
},
isApplicable(a) {
return MembraneOrientationProvider.isApplicable(a.data);
}
});
export const MembraneOrientationPreset = StructureRepresentationPresetProvider({
id: 'preset-membrane-orientation',
display: {
name: 'Membrane Orientation', group: 'Annotation',
description: 'Shows orientation of membrane layers. Data calculated with ANVIL algorithm.' // TODO add ' or obtained via RCSB PDB'
},
isApplicable(a) {
return MembraneOrientationProvider.isApplicable(a.data);
},
params: () => StructureRepresentationPresetProvider.CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const structure = structureCell?.obj?.data;
if (!structureCell || !structure) return {};
if (!MembraneOrientationProvider.get(structure).value) {
await plugin.runTask(Task.create('Membrane Orientation', async runtime => {
await MembraneOrientationProvider.attach({ runtime, assetManager: plugin.managers.asset }, structure);
}));
}
const membraneOrientation = await tryCreateMembraneOrientation(plugin, structureCell);
const colorTheme = HydrophobicityColorThemeProvider.name as any;
const preset = await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
return { components: preset.components, representations: { ...preset.representations, membraneOrientation } };
}
});
export function tryCreateMembraneOrientation(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, params?: StateTransformer.Params<MembraneOrientation3D>, initialState?: Partial<StateTransform.State>) {
const state = plugin.state.data;
const membraneOrientation = state.build().to(structure)
.applyOrUpdateTagged('membrane-orientation-3d', MembraneOrientation3D, params, { state: initialState });
return membraneOrientation.commit({ revertOnError: true });
}

View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Structure, StructureProperties, Unit } from '../../mol-model/structure';
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
import { ANVILParams, ANVILProps, computeANVIL, isInMembranePlane } from './algorithm';
import { CustomStructureProperty } from '../../mol-model-props/common/custom-structure-property';
import { CustomProperty } from '../../mol-model-props/common/custom-property';
import { AccessibleSurfaceAreaProvider } from '../../mol-model-props/computed/accessible-surface-area';
import { Vec3 } from '../../mol-math/linear-algebra';
import { QuerySymbolRuntime } from '../../mol-script/runtime/query/base';
import { CustomPropSymbol } from '../../mol-script/language/symbol';
import { Type } from '../../mol-script/language/type';
export const MembraneOrientationParams = {
...ANVILParams
};
export type MembraneOrientationParams = typeof MembraneOrientationParams
export type MembraneOrientationProps = PD.Values<MembraneOrientationParams>
export { MembraneOrientation };
interface MembraneOrientation {
// point in membrane boundary
readonly planePoint1: Vec3,
// point in opposite side of membrane boundary
readonly planePoint2: Vec3,
// normal vector of membrane layer
readonly normalVector: Vec3,
// the radius of the membrane layer
readonly radius: number,
readonly centroid: Vec3
}
namespace MembraneOrientation {
export enum Tag {
Representation = 'membrane-orientation-3d'
}
const pos = Vec3();
export const symbols = {
isTransmembrane: QuerySymbolRuntime.Dynamic(CustomPropSymbol('computed', 'membrane-orientation.is-transmembrane', Type.Bool),
ctx => {
const { unit, structure } = ctx.element;
const { x, y, z } = StructureProperties.atom;
if (!Unit.isAtomic(unit)) return 0;
const membraneOrientation = MembraneOrientationProvider.get(structure).value;
if (!membraneOrientation) return 0;
Vec3.set(pos, x(ctx.element), y(ctx.element), z(ctx.element));
const { normalVector, planePoint1, planePoint2 } = membraneOrientation!;
return isInMembranePlane(pos, normalVector, planePoint1, planePoint2);
})
};
}
export const MembraneOrientationProvider: CustomStructureProperty.Provider<MembraneOrientationParams, MembraneOrientation> = CustomStructureProperty.createProvider({
label: 'Membrane Orientation',
descriptor: CustomPropertyDescriptor({
name: 'anvil_computed_membrane_orientation',
symbols: MembraneOrientation.symbols,
// TODO `cifExport`
}),
type: 'root',
defaultParams: MembraneOrientationParams,
getParams: (data: Structure) => MembraneOrientationParams,
isApplicable: (data: Structure) => true,
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<MembraneOrientationProps>) => {
const p = { ...PD.getDefaultValues(MembraneOrientationParams), ...props };
return { value: await computeAnvil(ctx, data, p) };
}
});
async function computeAnvil(ctx: CustomProperty.Context, data: Structure, props: Partial<ANVILProps>): Promise<MembraneOrientation> {
await AccessibleSurfaceAreaProvider.attach(ctx, data);
const p = { ...PD.getDefaultValues(ANVILParams), ...props };
return await computeANVIL(data, p).runInContext(ctx.runtime);
}

View File

@@ -0,0 +1,156 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
*/
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../mol-repr/representation';
import { Structure } from '../../mol-model/structure';
import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
import { StructureRepresentationProvider, StructureRepresentation, StructureRepresentationStateBuilder } from '../../mol-repr/structure/representation';
import { MembraneOrientation } from './prop';
import { ThemeRegistryContext } from '../../mol-theme/theme';
import { ShapeRepresentation } from '../../mol-repr/shape/representation';
import { Shape } from '../../mol-model/shape';
import { RuntimeContext } from '../../mol-task';
import { Lines } from '../../mol-geo/geometry/lines/lines';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { LinesBuilder } from '../../mol-geo/geometry/lines/lines-builder';
import { Circle } from '../../mol-geo/primitive/circle';
import { transformPrimitive } from '../../mol-geo/primitive/primitive';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { MembraneOrientationProvider } from './prop';
import { MarkerActions } from '../../mol-util/marker-action';
import { lociLabel } from '../../mol-theme/label';
import { ColorNames } from '../../mol-util/color/names';
import { CustomProperty } from '../../mol-model-props/common/custom-property';
const SharedParams = {
color: PD.Color(ColorNames.lightgrey),
radiusFactor: PD.Numeric(0.8333, { min: 0.1, max: 3.0, step: 0.01 }, { description: 'Scale the radius of the membrane layer' })
};
const BilayerSpheresParams = {
...Spheres.Params,
...SharedParams,
sphereSize: PD.Numeric(1, { min: 0.1, max: 10, step: 0.1 }, { description: 'Size of spheres that represent membrane planes' }),
density: PD.Numeric(1, { min: 0.25, max: 10, step: 0.25 }, { description: 'Distance between spheres'})
};
export type BilayerSpheresParams = typeof BilayerSpheresParams
export type BilayerSpheresProps = PD.Values<BilayerSpheresParams>
const BilayerPlanesParams = {
...Mesh.Params,
...SharedParams,
sectorOpacity: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
};
export type BilayerPlanesParams = typeof BilayerPlanesParams
export type BilayerPlanesProps = PD.Values<BilayerPlanesParams>
const BilayerRimsParams = {
...Lines.Params,
...SharedParams,
lineSizeAttenuation: PD.Boolean(true),
linesSize: PD.Numeric(0.3, { min: 0.01, max: 50, step: 0.01 }),
dashedLines: PD.Boolean(true),
};
export type BilayerRimsParams = typeof BilayerRimsParams
export type BilayerRimsProps = PD.Values<BilayerRimsParams>
const MembraneOrientationVisuals = {
'bilayer-planes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerPlanesParams>) => ShapeRepresentation(getBilayerPlanes, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }), modifyProps: p => ({ ...p, alpha: p.sectorOpacity, ignoreLight: true, doubleSided: false }) }),
'bilayer-rims': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerRimsParams>) => ShapeRepresentation(getBilayerRims, Lines.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) })
};
export const MembraneOrientationParams = {
...BilayerSpheresParams,
...BilayerPlanesParams,
...BilayerRimsParams,
visuals: PD.MultiSelect(['bilayer-planes', 'bilayer-rims'], PD.objectToOptions(MembraneOrientationVisuals)),
};
export type MembraneOrientationParams = typeof MembraneOrientationParams
export type MembraneOrientationProps = PD.Values<MembraneOrientationParams>
export function getMembraneOrientationParams(ctx: ThemeRegistryContext, structure: Structure) {
return PD.clone(MembraneOrientationParams);
}
export type MembraneOrientationRepresentation = StructureRepresentation<MembraneOrientationParams>
export function MembraneOrientationRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, MembraneOrientationParams>): MembraneOrientationRepresentation {
return Representation.createMulti('Membrane Orientation', ctx, getParams, StructureRepresentationStateBuilder, MembraneOrientationVisuals as unknown as Representation.Def<Structure, MembraneOrientationParams>);
}
export const MembraneOrientationRepresentationProvider = StructureRepresentationProvider({
name: 'membrane-orientation',
label: 'Membrane Orientation',
description: 'Displays a grid of points representing membrane layers.',
factory: MembraneOrientationRepresentation,
getParams: getMembraneOrientationParams,
defaultValues: PD.getDefaultValues(MembraneOrientationParams),
defaultColorTheme: { name: 'shape-group' },
defaultSizeTheme: { name: 'shape-group' },
isApplicable: (structure: Structure) => structure.elementCount > 0,
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, structure: Structure) => MembraneOrientationProvider.attach(ctx, structure, void 0, true),
detach: (data) => MembraneOrientationProvider.ref(data, false)
}
});
function membraneLabel(data: Structure) {
return `${lociLabel(Structure.Loci(data))} | Membrane Orientation`;
}
function getBilayerRims(ctx: RuntimeContext, data: Structure, props: BilayerRimsProps, shape?: Shape<Lines>): Shape<Lines> {
const { planePoint1: p1, planePoint2: p2, centroid, normalVector: normal, radius } = MembraneOrientationProvider.get(data).value!;
const scaledRadius = props.radiusFactor * radius;
const builder = LinesBuilder.create(128, 64, shape?.geometry);
getLayerCircle(builder, p1, centroid, normal, scaledRadius, props);
getLayerCircle(builder, p2, centroid, normal, scaledRadius, props);
return Shape.create('Bilayer rims', data, builder.getLines(), () => props.color, () => props.linesSize, () => membraneLabel(data));
}
function getLayerCircle(builder: LinesBuilder, p: Vec3, centroid: Vec3, normal: Vec3, radius: number, props: BilayerRimsProps, shape?: Shape<Lines>) {
const circle = getCircle(p, centroid, normal, radius);
const { indices, vertices } = circle;
for (let j = 0, jl = indices.length; j < jl; j += 3) {
if (props.dashedLines && j % 2 === 1) continue; // draw every other segment to get dashes
const start = indices[j] * 3;
const end = indices[j + 1] * 3;
const startX = vertices[start];
const startY = vertices[start + 1];
const startZ = vertices[start + 2];
const endX = vertices[end];
const endY = vertices[end + 1];
const endZ = vertices[end + 2];
builder.add(startX, startY, startZ, endX, endY, endZ, 0);
}
}
const tmpMat = Mat4();
function getCircle(p: Vec3, centroid: Vec3, normal: Vec3, radius: number) {
Mat4.targetTo(tmpMat, p, centroid, normal);
Mat4.setTranslation(tmpMat, p);
Mat4.mul(tmpMat, tmpMat, Mat4.rotX90);
const circle = Circle({ radius, segments: 64 });
return transformPrimitive(circle, tmpMat);
}
function getBilayerPlanes(ctx: RuntimeContext, data: Structure, props: BilayerPlanesProps, shape?: Shape<Mesh>): Shape<Mesh> {
const { planePoint1: p1, planePoint2: p2, centroid, normalVector: normal, radius } = MembraneOrientationProvider.get(data).value!;
const state = MeshBuilder.createState(128, 64, shape && shape.geometry);
const scaledRadius = props.radiusFactor * radius;
getLayerPlane(state, p1, centroid, normal, scaledRadius);
getLayerPlane(state, p2, centroid, normal, scaledRadius);
return Shape.create('Bilayer planes', data, MeshBuilder.getMesh(state), () => props.color, () => 1, () => membraneLabel(data));
}
function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, normal: Vec3, radius: number) {
const circle = getCircle(p, centroid, normal, radius);
state.currentGroup = 0;
MeshBuilder.addPrimitive(state, Mat4.id, circle);
MeshBuilder.addPrimitiveFlipped(state, Mat4.id, circle);
}

View File

@@ -1,5 +1,5 @@
/**
* 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>
*/
@@ -10,7 +10,7 @@ import { Color } from '../../../mol-util/color';
import { getPalette } from '../../../mol-util/color/palette';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
import { StructureElement, Bond } from '../../../mol-model/structure';
import { StructureElement, Bond, Model } from '../../../mol-model/structure';
import { Location } from '../../../mol-model/location';
import { CellPackInfoProvider } from '../property';
import { distinctColors } from '../../../mol-util/color/distinct';
@@ -40,28 +40,27 @@ export function CellPackGenerateColorTheme(ctx: ThemeDataContext, props: PD.Valu
const { models } = ctx.structure.root;
let size = 0;
for (const m of models) size = Math.max(size, m.trajectoryInfo.size);
for (const m of models) size = Math.max(size, Model.TrajectoryInfo.get(m).size);
const palette = getPalette(size, { palette: {
name: 'generate',
params: {
hue, chroma: [30, 80], luminance: [15, 85],
clusteringStepCount: 50, minSampleCount: 800, maxCount: 75,
minLabel: 'Min', maxLabel: 'Max', valueLabel: (i: number) => `${i + 1}`,
clusteringStepCount: 50, minSampleCount: 800, maxCount: 75
}
}});
}}, { minLabel: 'Min', maxLabel: 'Max' });
legend = palette.legend;
const modelColor = new Map<number, Color>();
for (let i = 0, il = models.length; i < il; ++i) {
const idx = models[i].trajectoryInfo.index;
modelColor.set(models[i].trajectoryInfo.index, palette.color(idx));
const idx = Model.TrajectoryInfo.get(models[i]).index;
modelColor.set(Model.TrajectoryInfo.get(models[i]).index, palette.color(idx));
}
color = (location: Location): Color => {
if (StructureElement.Location.is(location)) {
return modelColor.get(location.unit.model.trajectoryInfo.index)!;
return modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!;
} else if (Bond.isLocation(location)) {
return modelColor.get(location.aUnit.model.trajectoryInfo.index)!;
return modelColor.get(Model.TrajectoryInfo.get(location.aUnit.model).index)!;
}
return DefaultColor;
};
@@ -89,7 +88,6 @@ export const CellPackGenerateColorThemeProvider: ColorTheme.Provider<CellPackGen
isApplicable: (ctx: ThemeDataContext) => {
return (
!!ctx.structure && ctx.structure.elementCount > 0 &&
ctx.structure.models[0].trajectoryInfo.size > 1 &&
!!CellPackInfoProvider.get(ctx.structure).value
);
}

View File

@@ -9,7 +9,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Color } from '../../../mol-util/color';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
import { StructureElement } from '../../../mol-model/structure';
import { StructureElement, Model, Bond } from '../../../mol-model/structure';
import { Location } from '../../../mol-model/location';
import { CellPackInfoProvider } from '../property';
@@ -32,14 +32,17 @@ export function CellPackProvidedColorTheme(ctx: ThemeDataContext, props: PD.Valu
const { models } = ctx.structure.root;
const modelColor = new Map<number, Color>();
for (let i = 0, il = models.length; i < il; ++i) {
const idx = models[i].trajectoryInfo.index;
modelColor.set(models[i].trajectoryInfo.index, info.colors[idx]);
const idx = Model.TrajectoryInfo.get(models[i]).index;
modelColor.set(Model.TrajectoryInfo.get(models[i]).index, info.colors[idx]);
}
color = (location: Location): Color => {
return StructureElement.Location.is(location)
? modelColor.get(location.unit.model.trajectoryInfo.index)!
: DefaultColor;
if (StructureElement.Location.is(location)) {
return modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!;
} else if (Bond.isLocation(location)) {
return modelColor.get(Model.TrajectoryInfo.get(location.aUnit.model).index)!;
}
return DefaultColor;
};
} else {
color = () => DefaultColor;
@@ -65,7 +68,7 @@ export const CellPackProvidedColorThemeProvider: ColorTheme.Provider<CellPackPro
isApplicable: (ctx: ThemeDataContext) => {
return (
!!ctx.structure && ctx.structure.elementCount > 0 &&
ctx.structure.models[0].trajectoryInfo.size > 1 &&
Model.TrajectoryInfo.get(ctx.structure.models[0]).size > 1 &&
!!CellPackInfoProvider.get(ctx.structure).value?.colors
);
}

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>
*/
@@ -10,7 +10,7 @@ import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Ingredient, IngredientSource, CellPacking } from './data';
import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean, getFromOPM } from './util';
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../mol-model/structure';
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit, Trajectory } from '../../mol-model/structure';
import { trajectoryFromMmCIF, MmcifFormat } from '../../mol-model-formats/structure/mmcif';
import { trajectoryFromPDB } from '../../mol-model-formats/structure/pdb';
import { Mat4, Vec3, Quat } from '../../mol-math/linear-algebra';
@@ -36,8 +36,8 @@ function getCellPackModelUrl(fileName: string, baseUrl: string) {
}
class TrajectoryCache {
private map = new Map<string, Model.Trajectory>();
set(id: string, trajectory: Model.Trajectory) { this.map.set(id, trajectory); }
private map = new Map<string, Trajectory>();
set(id: string, trajectory: Trajectory) { this.map.set(id, trajectory); }
get(id: string) { return this.map.get(id); }
}
@@ -94,9 +94,9 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien
trajectory = await plugin.runTask(trajectoryFromMmCIF(data.mmcif));
}
}
trajCache.set(id, trajectory);
trajCache.set(id, trajectory!);
}
const model = trajectory[modelIndex];
const model = await plugin.resolveTask(trajectory?.getFrameAtIndex(modelIndex)!);
return { model, assets };
}
@@ -303,7 +303,7 @@ async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredi
const curveModelTask = Task.create('Curve Model', async ctx => {
const format = MmcifFormat.fromFrame(cif);
const models = await createModels(format.data.db, format, ctx);
return models[0];
return models.representative;
});
const curveModel = await plugin.runTask(curveModelTask);
@@ -396,25 +396,27 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
}
if (ctx.shouldUpdate) await ctx.update(`${name} - units`);
const builder = Structure.Builder({ label: name });
const units: Unit[] = [];
let offsetInvariantId = 0;
let offsetChainGroupId = 0;
for (const s of structures) {
if (ctx.shouldUpdate) await ctx.update(`${s.label}`);
let maxInvariantId = 0;
let maxChainGroupId = 0;
for (const u of s.units) {
const invariantId = u.invariantId + offsetInvariantId;
const chainGroupId = u.chainGroupId + offsetChainGroupId;
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId);
units.push(Unit.create(units.length, invariantId, chainGroupId, u.traits, u.kind, u.model, u.conformation.operator, u.elements, u.props));
}
offsetInvariantId += maxInvariantId + 1;
offsetChainGroupId += maxChainGroupId + 1;
}
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
const structure = builder.getStructure();
const structure = new Structure(units);
for( let i = 0, il = structure.models.length; i < il; ++i) {
const { trajectoryInfo } = structure.models[i];
trajectoryInfo.size = il;
trajectoryInfo.index = i;
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
}
return { structure, assets, colors: skipColors ? undefined : colors };
});
@@ -444,9 +446,9 @@ async function handleHivRna(plugin: PluginContext, packings: CellPacking[], base
async function loadMembrane(plugin: PluginContext, name: string, state: State, params: LoadCellPackModelParams) {
let file: Asset.File | undefined = undefined;
if (params.ingredients.files !== null) {
if (params.ingredients !== null) {
const fileName = `${name}.bcif`;
for (const f of params.ingredients.files) {
for (const f of params.ingredients) {
if (fileName === f.name) {
file = f;
break;
@@ -455,7 +457,7 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
if (!file){
// check for cif directly
const cifileName = `${name}.cif`;
for (const f of params.ingredients.files) {
for (const f of params.ingredients) {
if (cifileName === f.name) {
file = f;
break;
@@ -490,7 +492,7 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
}
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
const ingredientFiles = params.ingredients.files || [];
const ingredientFiles = params.ingredients || [];
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
if (params.source.name === 'id') {
@@ -565,9 +567,7 @@ const LoadCellPackModelParams = {
}, { options: [['id', 'Id'], ['file', 'File']] }),
baseUrl: PD.Text(DefaultCellPackBaseUrl),
membrane: PD.Boolean(true),
ingredients : PD.Group({
files: PD.FileList({ accept: '.cif,.bcif,.pdb' })
}, { isExpanded: true }),
ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredients' }),
preset: PD.Group({
traceOnly: PD.Boolean(false),
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation']))

View File

@@ -13,7 +13,7 @@ import { IngredientFiles } from './util';
import { Asset } from '../../mol-util/assets';
import { PluginContext } from '../../mol-plugin/context';
import { CellPackInfoProvider } from './property';
import { Structure, StructureSymmetry, Unit } from '../../mol-model/structure';
import { Structure, StructureSymmetry, Unit, Model } from '../../mol-model/structure';
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/';
@@ -125,7 +125,7 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
const s = await StructureSymmetry.buildAssembly(initial_structure, a.id).runInContext(ctx);
structures.push(s);
}
const builder = Structure.Builder({ label: name });
const builder = Structure.Builder();
let offsetInvariantId = 0;
for (const s of structures) {
let maxInvariantId = 0;
@@ -138,9 +138,7 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
}
structure = builder.getStructure();
for( let i = 0, il = structure.models.length; i < il; ++i) {
const { trajectoryInfo } = structure.models[i];
trajectoryInfo.size = il;
trajectoryInfo.index = i;
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
}
}
return new PSO.Molecule.Structure(structure, { label: a.label, description: `${a.description}` });
@@ -150,55 +148,3 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
b?.data.customPropertyDescriptors.dispose();
}
});
// export { GetAllAssamblyinOneStructure };
// type GetAllAssamblyinOneStructure = typeof GetAllAssamblyinOneStructure
// const GetAllAssamblyinOneStructure = PluginStateTransform.BuiltIn({
// name: 'get assambly from structure',
// display: { name: 'get assambly from structure' },
// isDecorator: true,
// from: PSO.Molecule.Structure,
// to: PSO.Molecule.Structure,
// params(a) {
// return { };
// }
// })({
// apply({ a, params }) {
// return Task.create('Build Structure Assemblies', async ctx => {
// // TODO: optimze
// // TODO: think of ways how to fast-track changes to this for animations
// const initial_structure = a.data;
// const structures: Structure[] = [];
// let structure: Structure = initial_structure;
// // the list of asambly *?
// const symmetry = ModelSymmetry.Provider.get(initial_structure.model);
// if (symmetry){
// if (symmetry.assemblies.length !== 0) {
// for (const a of symmetry.assemblies) {
// const s = await StructureSymmetry.buildAssembly(initial_structure, a.id!).runInContext(ctx);
// structures.push(s);
// }
// const builder = Structure.Builder({ label: name });
// let offsetInvariantId = 0;
// for (const s of structures) {
// let maxInvariantId = 0;
// for (const u of s.units) {
// const invariantId = u.invariantId + offsetInvariantId;
// if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
// builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId);
// }
// offsetInvariantId += maxInvariantId + 1;
// }
// structure = builder.getStructure();
// for( let i = 0, il = structure.models.length; i < il; ++i) {
// const { trajectoryInfo } = structure.models[i];
// trajectoryInfo.size = il;
// trajectoryInfo.index = i;
// }
// }
// }
// return new PSO.Molecule.Structure(structure, { label: a.label, description: `${a.description}` });
// });
// }
// });

View File

@@ -0,0 +1,10 @@
## DNATCO Extensions
### Confal Pyramids
The Confal Pyramids extensions displays tetrahedron-like pyramids. These pyramids are a simple visual representation of nucleotide conformer classes that can be assigned to individual steps in nucleic acid structures.
For more information, see:
* [Černý et al., Nucleic Acids Research, 44, W284 (2016)](http://dx.doi.org/10.1093/nar/gkw381)
* [Schneider et al., Acta Cryst D, 74, 52-64 (2018)](http://dx.doi.org/10.1107/S2059798318000050)
* [Schneider et al., Genes, 8(10), 278, (2017)](http://dx.doi.org/10.3390/genes8100278)

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { ConfalPyramidsColorThemeProvider } from './color';
import { ConfalPyramids, ConfalPyramidsProvider } from './property';
import { ConfalPyramidsRepresentationProvider } from './representation';
import { Loci } from '../../../mol-model/loci';
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../mol-plugin-state/builder/structure/representation-preset';
import { StateObjectRef } from '../../../mol-state';
import { Task } from '../../../mol-task';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
export const DnatcoConfalPyramidsPreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-confal-pyramids',
display: {
name: 'Confal Pyramids', group: 'Annotation',
description: 'Schematic depiction of conformer class and confal value.',
},
isApplicable(a) {
return a.data.models.length >= 1 && a.data.models.some(m => ConfalPyramids.isApplicable(m));
},
params: () => StructureRepresentationPresetProvider.CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const model = structureCell?.obj?.data.model;
if (!structureCell || !model) return {};
await plugin.runTask(Task.create('Confal Pyramids', async runtime => {
await ConfalPyramidsProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
}));
const { components, representations } = await PresetStructureRepresentations.auto.apply(ref, { ...params }, plugin);
const pyramids = await plugin.builders.structure.tryCreateComponentStatic(structureCell, 'nucleic', { label: 'Confal Pyramids' });
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
let pyramidsRepr;
if (representations)
pyramidsRepr = builder.buildRepresentation(update, pyramids, { type: ConfalPyramidsRepresentationProvider, typeParams, color: ConfalPyramidsColorThemeProvider }, { tag: 'confal-pyramdis' } );
await update.commit({ revertOnError: true });
return { components: { ...components, pyramids }, representations: { ...representations, pyramidsRepr } };
}
});
export const DnatcoConfalPyramids = PluginBehavior.create<{ autoAttach: boolean, showToolTip: boolean }>({
name: 'dnatco-confal-pyramids-prop',
category: 'custom-props',
display: {
name: 'Confal Pyramids',
description: 'Schematic depiction of conformer class and confal value.',
},
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showToolTip: boolean }> {
private provider = ConfalPyramidsProvider;
private labelConfalPyramids = {
label: (loci: Loci): string | undefined => {
if (!this.params.showToolTip) return void 0;
/* TODO: Implement this */
return void 0;
}
}
register(): void {
this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
this.ctx.managers.lociLabels.addProvider(this.labelConfalPyramids);
this.ctx.representation.structure.themes.colorThemeRegistry.add(ConfalPyramidsColorThemeProvider);
this.ctx.representation.structure.registry.add(ConfalPyramidsRepresentationProvider);
this.ctx.builders.structure.representation.registerPreset(DnatcoConfalPyramidsPreset);
}
update(p: { autoAttach: boolean, showToolTip: boolean }) {
const updated = this.params.autoAttach !== p.autoAttach;
this.params.autoAttach = p.autoAttach;
this.params.showToolTip = p.showToolTip;
this.ctx.customModelProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
return updated;
}
unregister() {
this.ctx.customModelProperties.unregister(ConfalPyramidsProvider.descriptor.name);
this.ctx.managers.lociLabels.removeProvider(this.labelConfalPyramids);
this.ctx.representation.structure.registry.remove(ConfalPyramidsRepresentationProvider);
this.ctx.representation.structure.themes.colorThemeRegistry.remove(ConfalPyramidsColorThemeProvider);
this.ctx.builders.structure.representation.unregisterPreset(DnatcoConfalPyramidsPreset);
}
},
params: () => ({
autoAttach: PD.Boolean(true),
showToolTip: PD.Boolean(true)
})
});

View File

@@ -0,0 +1,186 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { ConfalPyramids, ConfalPyramidsProvider } from './property';
import { ConfalPyramidsTypes as CPT } from './types';
import { Location } from '../../../mol-model/location';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { ColorTheme } from '../../../mol-theme/color';
import { ThemeDataContext } from '../../../mol-theme/theme';
import { Color } from '../../../mol-util/color';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { TableLegend } from '../../../mol-util/legend';
import { iterableToArray } from '../../../mol-data/util';
const DefaultColor = Color(0xCCCCCC);
const Description = 'Assigns colors to confal pyramids';
const ErrorColor = Color(0xFFA10A);
type ConformerClasses = 'A' | 'B' | 'BII' | 'miB' | 'Z' | 'IC' | 'OPN' | 'SYN' | 'N';
const ColorMapping: ReadonlyMap<ConformerClasses, Color> = new Map([
['A', Color(0xFFC1C1)],
['B', Color(0xC8CFFF)],
['BII', Color(0x0059DA)],
['miB', Color(0x3BE8FB)],
['Z', Color(0x01F60E)],
['IC', Color(0xFA5CFB)],
['OPN', Color(0xE90000)],
['SYN', Color(0xFFFF01)],
['N', Color(0xF2F2F2)],
]);
const NtCToClasses: ReadonlyMap<string, [ConformerClasses, ConformerClasses]> = new Map([
['NANT', ['N', 'N']],
['AA00', ['A', 'A']],
['AA02', ['A', 'A']],
['AA03', ['A', 'A']],
['AA04', ['A', 'A']],
['AA08', ['A', 'A']],
['AA09', ['A', 'A']],
['AA01', ['A', 'A']],
['AA05', ['A', 'A']],
['AA06', ['A', 'A']],
['AA10', ['A', 'A']],
['AA11', ['A', 'A']],
['AA07', ['A', 'A']],
['AA12', ['A', 'A']],
['AA13', ['A', 'A']],
['AB01', ['A', 'B']],
['AB02', ['A', 'B']],
['AB03', ['A', 'B']],
['AB04', ['A', 'B']],
['AB05', ['A', 'B']],
['BA01', ['B', 'A']],
['BA05', ['B', 'A']],
['BA09', ['B', 'A']],
['BA08', ['BII', 'A']],
['BA10', ['B', 'A']],
['BA13', ['BII', 'A']],
['BA16', ['BII', 'A']],
['BA17', ['BII', 'A']],
['BB00', ['B', 'B']],
['BB01', ['B', 'B']],
['BB17', ['B', 'B']],
['BB02', ['B', 'B']],
['BB03', ['B', 'B']],
['BB11', ['B', 'B']],
['BB16', ['B', 'B']],
['BB04', ['B', 'BII']],
['BB05', ['B', 'BII']],
['BB07', ['BII', 'BII']],
['BB08', ['BII', 'BII']],
['BB10', ['miB', 'miB']],
['BB12', ['miB', 'miB']],
['BB13', ['miB', 'miB']],
['BB14', ['miB', 'miB']],
['BB15', ['miB', 'miB']],
['BB20', ['miB', 'miB']],
['IC01', ['IC', 'IC']],
['IC02', ['IC', 'IC']],
['IC03', ['IC', 'IC']],
['IC04', ['IC', 'IC']],
['IC05', ['IC', 'IC']],
['IC06', ['IC', 'IC']],
['IC07', ['IC', 'IC']],
['OP01', ['OPN', 'OPN']],
['OP02', ['OPN', 'OPN']],
['OP03', ['OPN', 'OPN']],
['OP04', ['OPN', 'OPN']],
['OP05', ['OPN', 'OPN']],
['OP06', ['OPN', 'OPN']],
['OP07', ['OPN', 'OPN']],
['OP08', ['OPN', 'OPN']],
['OP09', ['OPN', 'OPN']],
['OP10', ['OPN', 'OPN']],
['OP11', ['OPN', 'OPN']],
['OP12', ['OPN', 'OPN']],
['OP13', ['OPN', 'OPN']],
['OP14', ['OPN', 'OPN']],
['OP15', ['OPN', 'OPN']],
['OP16', ['OPN', 'OPN']],
['OP17', ['OPN', 'OPN']],
['OP18', ['OPN', 'OPN']],
['OP19', ['OPN', 'OPN']],
['OP20', ['OPN', 'OPN']],
['OP21', ['OPN', 'OPN']],
['OP22', ['OPN', 'OPN']],
['OP23', ['OPN', 'OPN']],
['OP24', ['OPN', 'OPN']],
['OP25', ['OPN', 'OPN']],
['OP26', ['OPN', 'OPN']],
['OP27', ['OPN', 'OPN']],
['OP28', ['OPN', 'OPN']],
['OP29', ['OPN', 'OPN']],
['OP30', ['OPN', 'OPN']],
['OP31', ['OPN', 'OPN']],
['OPS1', ['OPN', 'OPN']],
['OP1S', ['OPN', 'SYN']],
['AAS1', ['SYN', 'A']],
['AB1S', ['A', 'SYN']],
['AB2S', ['A', 'SYN']],
['BB1S', ['B', 'SYN']],
['BB2S', ['B', 'SYN']],
['BBS1', ['SYN', 'B']],
['ZZ01', ['Z', 'Z']],
['ZZ02', ['Z', 'Z']],
['ZZ1S', ['Z', 'SYN']],
['ZZ2S', ['Z', 'SYN']],
['ZZS1', ['SYN', 'Z']],
['ZZS2', ['SYN', 'Z']],
]);
function getConformerColor(ntc: string, useLower: boolean): Color {
const item = NtCToClasses.get(ntc);
if (!item) return ErrorColor;
return ColorMapping.get(useLower ? item[1] : item[0]) ?? ErrorColor;
}
export const ConfalPyramidsColorThemeParams = {};
export type ConfalPyramidsColorThemeParams = typeof ConfalPyramidsColorThemeParams
export function getConfalPyramidsColorThemeParams(ctx: ThemeDataContext) {
return ConfalPyramidsColorThemeParams; // TODO return copy
}
export function ConfalPyramidsColorTheme(ctx: ThemeDataContext, props: PD.Values<ConfalPyramidsColorThemeParams>): ColorTheme<ConfalPyramidsColorThemeParams> {
function color(location: Location, isSecondary: boolean): Color {
if (CPT.isLocation(location)) {
const { pyramid, isLower } = location.data;
return getConformerColor(pyramid.NtC, isLower);
}
return DefaultColor;
}
return {
factory: ConfalPyramidsColorTheme,
granularity: 'group',
color,
props,
description: Description,
legend: TableLegend(iterableToArray(ColorMapping.entries()).map(([conformer, color]) => {
return [conformer, color] as [string, Color];
}).concat([
[ 'Error', ErrorColor ],
[ 'Unknown', DefaultColor ]
]))
};
}
export const ConfalPyramidsColorThemeProvider: ColorTheme.Provider<ConfalPyramidsColorThemeParams, 'confal-pyramids'> = {
name: 'confal-pyramids',
label: 'Confal Pyramids',
category: ColorTheme.Category.Residue,
factory: ConfalPyramidsColorTheme,
getParams: getConfalPyramidsColorThemeParams,
defaultValues: PD.getDefaultValues(ConfalPyramidsColorThemeParams),
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => ConfalPyramids.isApplicable(m)),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ConfalPyramidsProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ConfalPyramidsProvider.descriptor, false)
}
};

View File

@@ -0,0 +1,172 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { ConfalPyramidsTypes as CPT } from './types';
import { Column, Table } from '../../../mol-data/db';
import { toTable } from '../../../mol-io/reader/cif/schema';
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
import { Model } from '../../../mol-model/structure';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
import { PropertyWrapper } from '../../../mol-model-props/common/wrapper';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
export type ConfalPyramids = PropertyWrapper<CPT.PyramidsData | undefined >;
export namespace ConfalPyramids {
export const Schema = {
ndb_struct_ntc_step: {
id: Column.Schema.int,
name: Column.Schema.str,
PDB_model_number: Column.Schema.int,
label_entity_id_1: Column.Schema.int,
label_asym_id_1: Column.Schema.str,
label_seq_id_1: Column.Schema.int,
label_comp_id_1: Column.Schema.str,
label_alt_id_1: Column.Schema.str,
label_entity_id_2: Column.Schema.int,
label_asym_id_2: Column.Schema.str,
label_seq_id_2: Column.Schema.int,
label_comp_id_2: Column.Schema.str,
label_alt_id_2: Column.Schema.str,
auth_asym_id_1: Column.Schema.str,
auth_seq_id_1: Column.Schema.int,
auth_asym_id_2: Column.Schema.str,
auth_seq_id_2: Column.Schema.int,
PDB_ins_code_1: Column.Schema.str,
PDB_ins_code_2: Column.Schema.str,
},
ndb_struct_ntc_step_summary: {
step_id: Column.Schema.int,
assigned_CANA: Column.Schema.str,
assigned_NtC: Column.Schema.str,
confal_score: Column.Schema.int,
euclidean_distance_NtC_ideal: Column.Schema.float,
cartesian_rmsd_closest_NtC_representative: Column.Schema.float,
closest_CANA: Column.Schema.str,
closest_NtC: Column.Schema.str,
closest_step_golden: Column.Schema.str
}
};
export type Schema = typeof Schema;
export async function fromCif(ctx: CustomProperty.Context, model: Model, props: ConfalPyramidsProps): Promise<CustomProperty.Data<ConfalPyramids>> {
const info = PropertyWrapper.createInfo();
const data = getCifData(model);
if (data === undefined) return { value: { info, data: undefined } };
const fromCif = createPyramidsFromCif(model, data.steps, data.stepsSummary);
return { value: { info, data: fromCif } };
}
function getCifData(model: Model) {
if (!MmcifFormat.is(model.sourceData)) throw new Error('Data format must be mmCIF');
if (!hasNdbStructNtcCategories(model)) return undefined;
return {
steps: toTable(Schema.ndb_struct_ntc_step, model.sourceData.data.frame.categories.ndb_struct_ntc_step),
stepsSummary: toTable(Schema.ndb_struct_ntc_step_summary, model.sourceData.data.frame.categories.ndb_struct_ntc_step_summary)
};
}
function hasNdbStructNtcCategories(model: Model): boolean {
if (!MmcifFormat.is(model.sourceData)) return false;
const names = (model.sourceData).data.frame.categoryNames;
return names.includes('ndb_struct_ntc_step') && names.includes('ndb_struct_ntc_step_summary');
}
export function isApplicable(model?: Model): boolean {
return !!model && hasNdbStructNtcCategories(model);
}
}
export const ConfalPyramidsParams = {};
export type ConfalPyramidsParams = typeof ConfalPyramidsParams;
export type ConfalPyramidsProps = PD.Values<ConfalPyramidsParams>;
export const ConfalPyramidsProvider: CustomModelProperty.Provider<ConfalPyramidsParams, ConfalPyramids> = CustomModelProperty.createProvider({
label: 'Confal Pyramids',
descriptor: CustomPropertyDescriptor({
name: 'confal_pyramids',
}),
type: 'static',
defaultParams: ConfalPyramidsParams,
getParams: (data: Model) => ConfalPyramidsParams,
isApplicable: (data: Model) => ConfalPyramids.isApplicable(data),
obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<ConfalPyramidsProps>) => {
const p = { ...PD.getDefaultValues(ConfalPyramidsParams), ...props };
return ConfalPyramids.fromCif(ctx, data, p);
}
});
type StepsSummaryTable = Table<typeof ConfalPyramids.Schema.ndb_struct_ntc_step_summary>;
function createPyramidsFromCif(model: Model,
steps: Table<typeof ConfalPyramids.Schema.ndb_struct_ntc_step>,
stepsSummary: StepsSummaryTable): CPT.PyramidsData {
const pyramids = new Array<CPT.Pyramid>();
const names = new Map<string, number>();
const locations = new Array<CPT.Location>();
let hasMultipleModels = false;
const {
id, PDB_model_number, name,
auth_asym_id_1, auth_seq_id_1, label_comp_id_1, label_alt_id_1, PDB_ins_code_1,
auth_asym_id_2, auth_seq_id_2, label_comp_id_2, label_alt_id_2, PDB_ins_code_2,
_rowCount } = steps;
if (_rowCount !== stepsSummary._rowCount) throw new Error('Inconsistent mmCIF data');
for (let i = 0; i < _rowCount; i++) {
const model_num = PDB_model_number.value(i);
if (model_num !== model.modelNum) {
hasMultipleModels = true;
continue; // We are only interested in data for the current model
}
const { _NtC, _confal_score } = getNtCAndConfalScore(id.value(i), i, stepsSummary);
const pyramid = {
PDB_model_number: model_num,
name: name.value(i),
auth_asym_id_1: auth_asym_id_1.value(i),
auth_seq_id_1: auth_seq_id_1.value(i),
label_comp_id_1: label_comp_id_1.value(i),
label_alt_id_1: label_alt_id_1.value(i),
PDB_ins_code_1: PDB_ins_code_1.value(i),
auth_asym_id_2: auth_asym_id_2.value(i),
auth_seq_id_2: auth_seq_id_2.value(i),
label_comp_id_2: label_comp_id_2.value(i),
label_alt_id_2: label_alt_id_2.value(i),
PDB_ins_code_2: PDB_ins_code_2.value(i),
confal_score: _confal_score,
NtC: _NtC
};
pyramids.push(pyramid);
names.set(pyramid.name, pyramids.length - 1);
locations.push(CPT.Location(pyramid, false));
locations.push(CPT.Location(pyramid, true));
}
return { pyramids, names, locations, hasMultipleModels };
}
function getNtCAndConfalScore(id: number, i: number, stepsSummary: StepsSummaryTable) {
const { step_id, confal_score, assigned_NtC } = stepsSummary;
// Assume that step_ids in ntc_step_summary are in the same order as steps in ntc_step
for (let j = i; j < stepsSummary._rowCount; j++) {
if (id === step_id.value(j)) return { _NtC: assigned_NtC.value(j), _confal_score: confal_score.value(j) };
}
// Safety net for cases where the previous assumption is not met
for (let j = 0; j < i; j++) {
if (id === step_id.value(j)) return { _NtC: assigned_NtC.value(j), _confal_score: confal_score.value(j) };
}
throw new Error('Inconsistent mmCIF data');
}

View File

@@ -0,0 +1,186 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { ConfalPyramids, ConfalPyramidsProvider } from './property';
import { ConfalPyramidsUtil } from './util';
import { ConfalPyramidsTypes as CPT } from './types';
import { Interval } from '../../../mol-data/int';
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 { PrimitiveBuilder } from '../../../mol-geo/primitive/primitive';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { EmptyLoci, Loci } from '../../../mol-model/loci';
import { Structure, StructureProperties, Unit } from '../../../mol-model/structure';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder, UnitsRepresentation } from '../../../mol-repr/structure/representation';
import { StructureGroup, UnitsMeshParams, UnitsMeshVisual, UnitsVisual } from '../../../mol-repr/structure/units-visual';
import { VisualUpdateState } from '../../../mol-repr/util';
import { VisualContext } from '../../../mol-repr/visual';
import { getAltResidueLociFromId } from '../../../mol-repr/structure/visual/util/common';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Theme, ThemeRegistryContext } from '../../../mol-theme/theme';
import { NullLocation } from '../../../mol-model/location';
const t = Mat4.identity();
const w = Vec3.zero();
const mp = Vec3.zero();
function calcMidpoint(mp: Vec3, v: Vec3, w: Vec3) {
Vec3.sub(mp, v, w);
Vec3.scale(mp, mp, 0.5);
Vec3.add(mp, mp, w);
}
function shiftVertex(vec: Vec3, ref: Vec3, scale: number) {
Vec3.sub(w, vec, ref);
Vec3.scale(w, w, scale);
Vec3.add(vec, vec, w);
}
const ConfalPyramidsMeshParams = {
...UnitsMeshParams
};
type ConfalPyramidsMeshParams = typeof ConfalPyramidsMeshParams;
function createConfalPyramidsIterator(structureGroup: StructureGroup): LocationIterator {
const { structure, group } = structureGroup;
const instanceCount = group.units.length;
const prop = ConfalPyramidsProvider.get(structure.model).value;
if (prop === undefined || prop.data === undefined) {
return LocationIterator(0, 1, 1, () => NullLocation);
}
const { locations } = prop.data;
const getLocation = (groupIndex: number, instanceIndex: number) => {
if (locations.length <= groupIndex) return NullLocation;
return locations[groupIndex];
};
return LocationIterator(locations.length, instanceCount, 1, getLocation);
}
function createConfalPyramidsMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<ConfalPyramidsMeshParams>, mesh?: Mesh) {
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
const prop = ConfalPyramidsProvider.get(structure.model).value;
if (prop === undefined || prop.data === undefined) return Mesh.createEmpty(mesh);
const { pyramids } = prop.data;
if (pyramids.length === 0) return Mesh.createEmpty(mesh);
const mb = MeshBuilder.createState(512, 512, mesh);
const handler = (pyramid: CPT.Pyramid, first: ConfalPyramidsUtil.FirstResidueAtoms, second: ConfalPyramidsUtil.SecondResidueAtoms, firsLocIndex: number, secondLocIndex: number) => {
if (firsLocIndex === -1 || secondLocIndex === -1)
throw new Error('Invalid location index');
const scale = (pyramid.confal_score - 20.0) / 100.0;
const O3 = first.O3.pos;
const OP1 = second.OP1.pos; const OP2 = second.OP2.pos; const O5 = second.O5.pos; const P = second.P.pos;
shiftVertex(O3, P, scale);
shiftVertex(OP1, P, scale);
shiftVertex(OP2, P, scale);
shiftVertex(O5, P, scale);
calcMidpoint(mp, O3, O5);
mb.currentGroup = firsLocIndex;
let pb = PrimitiveBuilder(3);
/* Upper part (for first residue in step) */
pb.add(O3, OP1, OP2);
pb.add(O3, mp, OP1);
pb.add(O3, OP2, mp);
MeshBuilder.addPrimitive(mb, t, pb.getPrimitive());
/* Lower part (for second residue in step */
mb.currentGroup = secondLocIndex;
pb = PrimitiveBuilder(3);
pb.add(mp, O5, OP1);
pb.add(mp, OP2, O5);
pb.add(O5, OP2, OP1);
MeshBuilder.addPrimitive(mb, t, pb.getPrimitive());
};
const walker = new ConfalPyramidsUtil.UnitWalker(structure, unit, handler);
walker.walk();
return MeshBuilder.getMesh(mb);
}
function getConfalPyramidLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
const { groupId, objectId, instanceId } = pickingId;
if (objectId !== id) return EmptyLoci;
const { structure } = structureGroup;
const unit = structureGroup.group.units[instanceId];
if (!Unit.isAtomic(unit)) return EmptyLoci;
const prop = ConfalPyramidsProvider.get(structure.model).value;
if (prop === undefined || prop.data === undefined) return EmptyLoci;
const { locations } = prop.data;
if (locations.length <= groupId) return EmptyLoci;
const altId = StructureProperties.atom.label_alt_id(CPT.toElementLocation(locations[groupId]));
const rI = unit.residueIndex[locations[groupId].element.element];
return getAltResidueLociFromId(structure, unit, rI, altId);
}
function eachConfalPyramid(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
return false; // TODO: Implement me
}
function ConfalPyramidsVisual(materialId: number): UnitsVisual<ConfalPyramidsMeshParams> {
return UnitsMeshVisual<ConfalPyramidsMeshParams>({
defaultProps: PD.getDefaultValues(ConfalPyramidsMeshParams),
createGeometry: createConfalPyramidsMesh,
createLocationIterator: createConfalPyramidsIterator,
getLoci: getConfalPyramidLoci,
eachLocation: eachConfalPyramid,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<ConfalPyramidsMeshParams>, currentProps: PD.Values<ConfalPyramidsMeshParams>) => {
}
}, materialId);
}
const ConfalPyramidsVisuals = {
'confal-pyramids-symbol': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, UnitsMeshParams>) => UnitsRepresentation('Confal Pyramids Symbol Mesh', ctx, getParams, ConfalPyramidsVisual),
};
export const ConfalPyramidsParams = {
...UnitsMeshParams
};
export type ConfalPyramidsParams = typeof ConfalPyramidsParams;
export function getConfalPyramidsParams(ctx: ThemeRegistryContext, structure: Structure) {
return PD.clone(ConfalPyramidsParams);
}
export type ConfalPyramidsRepresentation = StructureRepresentation<ConfalPyramidsParams>;
export function ConfalPyramidsRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, ConfalPyramidsParams>): ConfalPyramidsRepresentation {
const repr = Representation.createMulti('Confal Pyramids', ctx, getParams, StructureRepresentationStateBuilder, ConfalPyramidsVisuals as unknown as Representation.Def<Structure, ConfalPyramidsParams>);
return repr;
}
export const ConfalPyramidsRepresentationProvider = StructureRepresentationProvider({
name: 'confal-pyramids',
label: 'Confal Pyramids',
description: 'Displays schematic depiction of conformer classes and confal values',
factory: ConfalPyramidsRepresentation,
getParams: getConfalPyramidsParams,
defaultValues: PD.getDefaultValues(ConfalPyramidsParams),
defaultColorTheme: { name: 'confal-pyramids' },
defaultSizeTheme: { name: 'uniform' },
isApplicable: (structure: Structure) => structure.models.some(m => ConfalPyramids.isApplicable(m)),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, structure: Structure) => ConfalPyramidsProvider.attach(ctx, structure.model, void 0, true),
detach: (data) => ConfalPyramidsProvider.ref(data.model, false),
}
});

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { DataLocation } from '../../../mol-model/location';
import { ElementIndex, Structure, StructureElement, Unit } from '../../../mol-model/structure';
export namespace ConfalPyramidsTypes {
export type Pyramid = {
PDB_model_number: number,
name: string,
auth_asym_id_1: string,
auth_seq_id_1: number,
label_comp_id_1: string,
label_alt_id_1: string,
PDB_ins_code_1: string,
auth_asym_id_2: string,
auth_seq_id_2: number,
label_comp_id_2: string,
label_alt_id_2: string,
PDB_ins_code_2: string,
confal_score: number,
NtC: string
}
export interface PyramidsData {
pyramids: Array<Pyramid>,
names: Map<string, number>,
locations: Array<Location>,
hasMultipleModels: boolean
}
export interface LocationData {
readonly pyramid: Pyramid
readonly isLower: boolean;
}
export interface Element {
structure: Structure;
unit: Unit.Atomic;
element: ElementIndex;
}
export interface Location extends DataLocation<LocationData, Element> {}
export function Location(pyramid: Pyramid, isLower: boolean, structure?: Structure, unit?: Unit.Atomic, element?: ElementIndex) {
return DataLocation('pyramid', { pyramid, isLower }, { structure: structure as any, unit: unit as any, element: element as any });
}
export function isLocation(x: any): x is Location {
return !!x && x.kind === 'data-location' && x.tag === 'pyramid';
}
export function toElementLocation(location: Location) {
return StructureElement.Location.create(location.element.structure, location.element.unit, location.element.element);
}
}

View File

@@ -0,0 +1,299 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
import { ConfalPyramidsProvider } from './property';
import { ConfalPyramidsTypes as CPT } from './types';
import { OrderedSet, Segmentation } from '../../../mol-data/int';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { ChainIndex, ElementIndex, ResidueIndex, Structure, StructureElement, StructureProperties, Unit } from '../../../mol-model/structure';
export namespace ConfalPyramidsUtil {
type Residue = Segmentation.Segment<ResidueIndex>;
export type AtomInfo = {
pos: Vec3,
index: ElementIndex,
fakeAltId: string,
};
export type FirstResidueAtoms = {
O3: AtomInfo,
};
export type SecondResidueAtoms = {
OP1: AtomInfo,
OP2: AtomInfo,
O5: AtomInfo,
P: AtomInfo,
};
type ResidueInfo = {
PDB_model_num: number,
asym_id: string,
auth_asym_id: string,
seq_id: number,
auth_seq_id: number,
comp_id: string,
alt_id: string,
ins_code: string,
};
export type Handler = (pyramid: CPT.Pyramid, first: FirstResidueAtoms, second: SecondResidueAtoms, firstLocIndex: number, secondLocIndex: number) => void;
function residueInfoFromLocation(loc: StructureElement.Location): ResidueInfo {
return {
PDB_model_num: StructureProperties.unit.model_num(loc),
asym_id: StructureProperties.chain.label_asym_id(loc),
auth_asym_id: StructureProperties.chain.auth_asym_id(loc),
seq_id: StructureProperties.residue.label_seq_id(loc),
auth_seq_id: StructureProperties.residue.auth_seq_id(loc),
comp_id: StructureProperties.atom.label_comp_id(loc),
alt_id: StructureProperties.atom.label_alt_id(loc),
ins_code: StructureProperties.residue.pdbx_PDB_ins_code(loc)
};
}
export function hasMultipleModels(unit: Unit.Atomic): boolean {
const prop = ConfalPyramidsProvider.get(unit.model).value;
if (prop === undefined || prop.data === undefined) throw new Error('No custom properties data');
return prop.data.hasMultipleModels;
}
function getPossibleAltIdsIndices(eIFirst: ElementIndex, eILast: ElementIndex, structure: Structure, unit: Unit.Atomic): string[] {
const loc = StructureElement.Location.create(structure, unit, -1 as ElementIndex);
const uIFirst = OrderedSet.indexOf(unit.elements, eIFirst);
const uILast = OrderedSet.indexOf(unit.elements, eILast);
const possibleAltIds: string[] = [];
for (let uI = uIFirst; uI <= uILast; uI++) {
loc.element = unit.elements[uI];
const altId = StructureProperties.atom.label_alt_id(loc);
if (altId !== '' && !possibleAltIds.includes(altId)) possibleAltIds.push(altId);
}
return possibleAltIds;
}
function getPossibleAltIdsResidue(residue: Residue, structure: Structure, unit: Unit.Atomic): string[] {
return getPossibleAltIdsIndices(unit.elements[residue.start], unit.elements[residue.end - 1], structure, unit);
}
class Utility {
protected getPyramidByName(name: string): { pyramid: CPT.Pyramid | undefined, index: number } {
const index = this.data.names.get(name);
if (index === undefined) return { pyramid: undefined, index: -1 };
return { pyramid: this.data.pyramids[index], index };
}
protected stepToName(entry_id: string, modelNum: number, locFirst: StructureElement.Location, locSecond: StructureElement.Location, fakeAltId_1: string, fakeAltId_2: string) {
const first = residueInfoFromLocation(locFirst);
const second = residueInfoFromLocation(locSecond);
const model_id = this.hasMultipleModels ? `-m${modelNum}` : '';
const alt_id_1 = fakeAltId_1 !== '' ? `.${fakeAltId_1}` : (first.alt_id.length ? `.${first.alt_id}` : '');
const alt_id_2 = fakeAltId_2 !== '' ? `.${fakeAltId_2}` : (second.alt_id.length ? `.${second.alt_id}` : '');
const ins_code_1 = first.ins_code.length ? `.${first.ins_code}` : '';
const ins_code_2 = second.ins_code.length ? `.${second.ins_code}` : '';
return `${entry_id}${model_id}_${first.auth_asym_id}_${first.comp_id}${alt_id_1}_${first.auth_seq_id}${ins_code_1}_${second.comp_id}${alt_id_2}_${second.auth_seq_id}${ins_code_2}`;
}
constructor(unit: Unit.Atomic) {
const prop = ConfalPyramidsProvider.get(unit.model).value;
if (prop === undefined || prop.data === undefined) throw new Error('No custom properties data');
this.data = prop.data;
this.hasMultipleModels = hasMultipleModels(unit);
this.entryId = unit.model.entryId.toLowerCase();
this.modelNum = unit.model.modelNum;
}
protected readonly data: CPT.PyramidsData
protected readonly hasMultipleModels: boolean;
protected readonly entryId: string;
protected readonly modelNum: number;
}
export class UnitWalker extends Utility {
private getAtomIndices(names: string[], residue: Residue): ElementIndex[] {
let rI = residue.start;
const rILast = residue.end - 1;
const indices: ElementIndex[] = [];
for (; rI !== rILast; rI++) {
const eI = this.unit.elements[rI];
const loc = StructureElement.Location.create(this.structure, this.unit, eI);
const thisName = StructureProperties.atom.label_atom_id(loc);
if (names.includes(thisName)) indices.push(eI);
}
if (indices.length === 0)
throw new Error(`Element ${name} not found on residue ${residue.index}`);
return indices;
}
private getAtomPositions(indices: ElementIndex[]): Vec3[] {
const pos = this.unit.conformation.invariantPosition;
const positions: Vec3[] = [];
for (const eI of indices) {
const v = Vec3.zero();
pos(eI, v);
positions.push(v);
}
return positions;
}
private handleStep(firstAtoms: FirstResidueAtoms[], secondAtoms: SecondResidueAtoms[]) {
const modelNum = this.hasMultipleModels ? this.modelNum : -1;
let ok = false;
const firstLoc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex);
const secondLoc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex);
for (let i = 0; i < firstAtoms.length; i++) {
const first = firstAtoms[i];
for (let j = 0; j < secondAtoms.length; j++) {
const second = secondAtoms[j];
firstLoc.element = first.O3.index;
secondLoc.element = second.OP1.index;
const name = this.stepToName(this.entryId, modelNum, firstLoc, secondLoc, first.O3.fakeAltId, second.OP1.fakeAltId);
const { pyramid, index } = this.getPyramidByName(name);
if (pyramid !== undefined) {
const setLoc = (loc: CPT.Location, eI: ElementIndex) => {
loc.element.structure = this.structure;
loc.element.unit = this.unit;
loc.element.element = eI;
};
const locIndex = index * 2;
setLoc(this.data.locations[locIndex], firstLoc.element);
setLoc(this.data.locations[locIndex + 1], secondLoc.element);
this.handler(pyramid, first, second, locIndex, locIndex + 1);
ok = true;
}
}
}
if (!ok) throw new Error('Bogus step');
}
private processFirstResidue(residue: Residue, possibleAltIds: string[]) {
const indO3 = this.getAtomIndices(['O3\'', 'O3*'], residue);
const posO3 = this.getAtomPositions(indO3);
const altPos: FirstResidueAtoms[] = [
{ O3: { pos: posO3[0], index: indO3[0], fakeAltId: '' } }
];
for (let i = 1; i < indO3.length; i++) {
altPos.push({ O3: { pos: posO3[i], index: indO3[i], fakeAltId: '' } });
}
if (altPos.length === 1 && possibleAltIds.length > 1) {
/* We have some alternate positions on the residue but O3 does not have any - fake them */
altPos[0].O3.fakeAltId = possibleAltIds[0];
for (let i = 1; i < possibleAltIds.length; i++)
altPos.push({ O3: { pos: posO3[0], index: indO3[0], fakeAltId: possibleAltIds[i] } });
}
return altPos;
}
private processSecondResidue(residue: Residue, possibleAltIds: string[]) {
const indOP1 = this.getAtomIndices(['OP1'], residue);
const indOP2 = this.getAtomIndices(['OP2'], residue);
const indO5 = this.getAtomIndices(['O5\'', 'O5*'], residue);
const indP = this.getAtomIndices(['P'], residue);
const posOP1 = this.getAtomPositions(indOP1);
const posOP2 = this.getAtomPositions(indOP2);
const posO5 = this.getAtomPositions(indO5);
const posP = this.getAtomPositions(indP);
const infoOP1: AtomInfo[] = [];
/* We use OP1 as "pivotal" atom. There is no specific reason
* to pick OP1, it is as good a choice as any other atom
*/
if (indOP1.length === 1 && possibleAltIds.length > 1) {
/* No altIds on OP1, fake them */
for (const altId of possibleAltIds)
infoOP1.push({ pos: posOP1[0], index: indOP1[0], fakeAltId: altId });
} else {
for (let i = 0; i < indOP1.length; i++)
infoOP1.push({ pos: posOP1[i], index: indOP1[i], fakeAltId: '' });
}
const mkInfo = (i: number, indices: ElementIndex[], positions: Vec3[], altId: string) => {
if (i >= indices.length) {
const last = indices.length - 1;
return { pos: positions[last], index: indices[last], fakeAltId: altId };
}
return { pos: positions[i], index: indices[i], fakeAltId: altId };
};
const altPos: SecondResidueAtoms[] = [];
for (let i = 0; i < infoOP1.length; i++) {
const altId = infoOP1[i].fakeAltId;
const OP2 = mkInfo(i, indOP2, posOP2, altId);
const O5 = mkInfo(i, indO5, posO5, altId);
const P = mkInfo(i, indP, posP, altId);
altPos.push({ OP1: infoOP1[i], OP2, O5, P });
}
return altPos;
}
private step(residue: Residue): { firstAtoms: FirstResidueAtoms[], secondAtoms: SecondResidueAtoms[] } {
const firstPossibleAltIds = getPossibleAltIdsResidue(residue, this.structure, this.unit);
const firstAtoms = this.processFirstResidue(residue, firstPossibleAltIds);
residue = this.residueIt.move();
const secondPossibleAltIds = getPossibleAltIdsResidue(residue, this.structure, this.unit);
const secondAtoms = this.processSecondResidue(residue, secondPossibleAltIds);
return { firstAtoms, secondAtoms };
}
walk() {
while (this.chainIt.hasNext) {
this.residueIt.setSegment(this.chainIt.move());
let residue = this.residueIt.move();
while (this.residueIt.hasNext) {
try {
const { firstAtoms, secondAtoms } = this.step(residue);
this.handleStep(firstAtoms, secondAtoms);
} catch (error) {
/* Skip and move along */
residue = this.residueIt.move();
}
}
}
}
constructor(private structure: Structure, private unit: Unit.Atomic, private handler: Handler) {
super(unit);
this.chainIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, unit.elements);
this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
}
private chainIt: Segmentation.SegmentIterator<ChainIndex>;
private residueIt: Segmentation.SegmentIterator<ResidueIndex>;
}
}

View File

@@ -0,0 +1,8 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Michal Malý <michal.maly@ibt.cas.cz>
* @author Jiří Černý <jiri.cerny@ibt.cas.cz>
*/
export { DnatcoConfalPyramids } from './confal-pyramids/behavior';

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { decodeMsgPack } from '../../mol-io/common/msgpack/decode';
import { PluginContext } from '../../mol-plugin/context';
import { Task } from '../../mol-task';
import { inflate } from '../../mol-util/zip/zip';
export interface G3dHeader {
magic: 'G3D',
version: number,
genome: string,
name: string,
offsets: { [resolution: string]: { offset: number, size: number } },
resolutions: number[]
}
export type G3dDataBlock = {
header: G3dHeader,
resolution: number,
data: {
[haplotype: string]: {
[ch: string]: {
start: number[]
x: number[],
y: number[],
z: number[],
}
}
}
}
const HEADER_SIZE = 64000;
export async function getG3dHeader(ctx: PluginContext, urlOrData: string | Uint8Array): Promise<G3dHeader> {
const data: Uint8Array = await getRawData(ctx, urlOrData, { offset: 0, size: HEADER_SIZE });
let last = data.length - 1;
for (; last >= 0; last--) {
if (data[last] !== 0) break;
}
const header = decodeMsgPack(data.slice(0, last + 1));
return header;
}
export async function getG3dDataBlock(ctx: PluginContext, header: G3dHeader, urlOrData: string | Uint8Array, resolution: number): Promise<G3dDataBlock> {
if (!header.offsets[resolution]) throw new Error(`Resolution ${resolution} not available.`);
const data = await getRawData(ctx, urlOrData, header.offsets[resolution]);
const unzipped = await ctx.runTask(Task.create('Unzip', ctx => inflate(ctx, data)));
return {
header,
resolution,
data: decodeMsgPack(unzipped)
};
}
async function getRawData(ctx: PluginContext, urlOrData: string | Uint8Array, range: { offset: number, size: number }) {
if (typeof urlOrData === 'string') {
return await ctx.runTask(ctx.fetch({ url: urlOrData, headers: [['Range', `bytes=${range.offset}-${range.offset + range.size - 1}`]], type: 'binary' }));
} else {
return urlOrData.slice(range.offset, range.offset + range.size);
}
}

View File

@@ -0,0 +1,173 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Trajectory } from '../../mol-model/structure';
import { TrajectoryFormatCategory, TrajectoryFormatProvider } from '../../mol-plugin-state/formats/trajectory';
import { PluginStateObject as SO, PluginStateTransform } from '../../mol-plugin-state/objects';
import { PluginBehavior } from '../../mol-plugin/behavior';
import { PluginContext } from '../../mol-plugin/context';
import { DefaultQueryRuntimeTable } from '../../mol-script/runtime/query/base';
import { StateAction, StateObjectRef } from '../../mol-state';
import { Task } from '../../mol-task';
import { ParamDefinition } from '../../mol-util/param-definition';
import { G3dHeader, getG3dDataBlock, getG3dHeader } from './data';
import { g3dHaplotypeQuery, G3dLabelProvider, trajectoryFromG3D, G3dSymbols, G3dInfoDataProperty } from './model';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
import { stringToWords } from '../../mol-util/string';
import { objectForEach } from '../../mol-util/object';
export const G3dProvider: TrajectoryFormatProvider = {
label: 'G3D',
description: 'G3D',
category: TrajectoryFormatCategory,
binaryExtensions: ['g3d'],
parse: async (plugin, data) => {
const trajectory = await plugin.state.data.build()
.to(data)
.apply(G3DHeaderFromFile, {}, { state: { isGhost: true } })
.apply(G3DTrajectory)
.commit();
return { trajectory };
},
visuals: defaultStructure
};
async function defaultStructure(plugin: PluginContext, data: { trajectory: StateObjectRef<SO.Molecule.Trajectory> }) {
const builder = plugin.builders.structure;
const model = await builder.createModel(data.trajectory);
if (!model) return;
const structure = await builder.createStructure(model);
const info = G3dInfoDataProperty.get(model.data!);
if (!info) return;
const components = plugin.build().to(structure);
const repr = createStructureRepresentationParams(plugin, void 0, {
type: 'cartoon',
color: 'polymer-index',
size: 'uniform',
sizeParams: { value: 0.25 }
});
for (const h of info.haplotypes) {
components
.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: g3dHaplotypeQuery(h), label: stringToWords(h) })
.apply(StateTransforms.Representation.StructureRepresentation3D, repr);
}
await components.commit();
}
export class G3dHeaderObject extends SO.Create<{
header: G3dHeader,
urlOrData: Uint8Array | string,
cache: { [resolution: number]: Trajectory | undefined }
}>({ name: 'G3D Header', typeClass: 'Data' }) { }
export type G3DHeaderFromFile = typeof G3DHeaderFromFile
export const G3DHeaderFromFile = PluginStateTransform.BuiltIn({
name: 'g3d-header-from-file',
display: { name: 'G3D Header', description: 'Parse G3D Header' },
from: SO.Data.Binary,
to: G3dHeaderObject
})({
apply({ a }, plugin: PluginContext) {
return Task.create('Parse G3D', async () => {
const header = await getG3dHeader(plugin, a.data);
return new G3dHeaderObject({ header, urlOrData: a.data, cache: { } }, { label: header.name, description: header.genome });
});
}
});
export type G3DHeaderFromUrl = typeof G3DHeaderFromUrl
export const G3DHeaderFromUrl = PluginStateTransform.BuiltIn({
name: 'g3d-header-from-url',
display: { name: 'G3D Header', description: 'Parse G3D Header' },
params: { url: ParamDefinition.Text('') },
from: SO.Root,
to: G3dHeaderObject
})({
apply({ params }, plugin: PluginContext) {
return Task.create('Parse G3D', async () => {
const header = await getG3dHeader(plugin, params.url);
return new G3dHeaderObject({ header, urlOrData: params.url, cache: { } }, { label: header.name, description: header.genome });
});
}
});
export type G3DTrajectory = typeof G3DHeaderFromUrl
export const G3DTrajectory = PluginStateTransform.BuiltIn({
name: 'g3d-trajecotry',
display: { name: 'G3D Trajectory', description: 'Create G3D Trajectory' },
params: a => {
if (!a) return { resolution: ParamDefinition.Numeric(200000) };
const rs = a.data.header.resolutions;
return {
resolution: ParamDefinition.Select(rs[rs.length - 1], rs.map(r => [r, '' + r] as const))
};
},
from: G3dHeaderObject,
to: SO.Molecule.Trajectory
})({
apply({ a, params }, plugin: PluginContext) {
return Task.create('G3D Trajectory', async ctx => {
if (a.data.cache[params.resolution]) {
return new SO.Molecule.Trajectory(a.data.cache[params.resolution]!, { label: a.label, description: a.description });
}
const data = await getG3dDataBlock(plugin, a.data.header, a.data.urlOrData, params.resolution);
const traj = await trajectoryFromG3D(data).runInContext(ctx);
a.data.cache[params.resolution] = traj;
return new SO.Molecule.Trajectory(traj, { label: a.label, description: a.description });
});
}
});
export const LoadG3D = StateAction.build({
display: { name: 'Load Genome 3D (G3D)', description: 'Load G3D file from the specified URL.' },
from: SO.Root,
params: { url: ParamDefinition.Text('') }
})(({ params, state }, ctx: PluginContext) => Task.create('Genome3D', taskCtx => {
return state.transaction(async () => {
if (params.url.trim().length === 0) {
throw new Error('Specify URL');
}
ctx.behaviors.layout.leftPanelTabName.next('data');
const trajectory = await state.build().toRoot()
.apply(G3DHeaderFromUrl, { url: params.url })
.apply(G3DTrajectory)
.commit();
await defaultStructure(ctx, { trajectory });
}).runInContext(taskCtx);
}));
export const G3DFormat = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
name: 'g3d',
category: 'misc',
display: {
name: 'G3D',
description: 'G3D Format Support'
},
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
register() {
this.ctx.state.data.actions.add(LoadG3D);
objectForEach(G3dSymbols, s => DefaultQueryRuntimeTable.addSymbol(s));
this.ctx.managers.lociLabels.addProvider(G3dLabelProvider);
}
unregister() {
this.ctx.state.data.actions.remove(LoadG3D);
objectForEach(G3dSymbols, s => DefaultQueryRuntimeTable.removeSymbol(s));
this.ctx.managers.lociLabels.removeProvider(G3dLabelProvider);
}
}
});

245
src/extensions/g3d/model.ts Normal file
View File

@@ -0,0 +1,245 @@
/**
* Copyright (c) 2020 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 { OrderedSet } from '../../mol-data/int';
import { Vec3 } from '../../mol-math/linear-algebra';
import { createModels } from '../../mol-model-formats/structure/basic/parser';
import { BasicSchema, createBasic } from '../../mol-model-formats/structure/basic/schema';
import { EntityBuilder } from '../../mol-model-formats/structure/common/entity';
import { Loci } from '../../mol-model/loci';
import { Trajectory, Unit } from '../../mol-model/structure';
import { MoleculeType } from '../../mol-model/structure/model/types';
import { LociLabelProvider } from '../../mol-plugin-state/manager/loci-label';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { CustomPropSymbol } from '../../mol-script/language/symbol';
import { Type } from '../../mol-script/language/type';
import { QuerySymbolRuntime } from '../../mol-script/runtime/query/base';
import { RuntimeContext, Task } from '../../mol-task';
import { objectForEach } from '../../mol-util/object';
import { G3dDataBlock } from './data';
import { FormatPropertyProvider } from '../../mol-model-formats/structure/common/property';
interface NormalizedData {
entity_id: string[],
chromosome: string[],
seq_id_begin: Int32Array,
seq_id_end: Int32Array,
start: Int32Array,
x: Float32Array,
y: Float32Array,
z: Float32Array,
r: Float32Array,
haplotype: string[]
}
function getColumns(block: G3dDataBlock) {
const { data } = block;
let size = 0;
objectForEach(data, h => objectForEach(h, g => size += g.start.length));
const normalized: NormalizedData = {
entity_id: new Array(size),
chromosome: new Array(size),
seq_id_begin: new Int32Array(size),
seq_id_end: new Int32Array(size),
start: new Int32Array(size),
x: new Float32Array(size),
y: new Float32Array(size),
z: new Float32Array(size),
r: new Float32Array(size),
haplotype: new Array(size)
};
const p = [Vec3(), Vec3(), Vec3()];
let o = 0;
objectForEach(data, (hs, h) => {
objectForEach(hs, (chs, ch) => {
const entity_id = `${ch}-${h}`;
const l = chs.start.length;
if (l === 0) return;
let x = chs.x[0];
let y = chs.y[0];
let z = chs.z[0];
Vec3.set(p[0], x, y, z);
Vec3.set(p[2], x, y, z);
for (let i = 0; i < l; i++) {
normalized.entity_id[o] = entity_id;
normalized.chromosome[o] = ch;
normalized.start[o] = chs.start[i];
normalized.seq_id_begin[o] = o;
normalized.seq_id_end[o] = o;
x = chs.x[i];
y = chs.y[i];
z = chs.z[i];
Vec3.set(p[1], x, y, z);
if (i + 1 < l) Vec3.set(p[2], chs.x[i + 1], chs.y[i + 1], chs.z[i + 1]);
else Vec3.set(p[2], x, y, z);
normalized.x[o] = x;
normalized.y[o] = y;
normalized.z[o] = z;
normalized.r[o] = 2 / 3 * Math.min(Vec3.distance(p[0], p[1]), Vec3.distance(p[1], p[2]));
normalized.haplotype[o] = h;
const _p = p[0];
p[0] = p[1];
p[1] = _p;
o++;
}
if (l === 1) {
normalized.r[o - 1] = 1;
}
});
});
return normalized;
}
async function getTraj(ctx: RuntimeContext, data: G3dDataBlock) {
const normalized = getColumns(data);
const rowCount = normalized.seq_id_begin.length;
const entityIds = new Array<string>(rowCount);
const entityBuilder = new EntityBuilder();
const eName = { customName: '' };
for (let i = 0; i < rowCount; ++i) {
const e = normalized.entity_id[i];
eName.customName = e;
const entityId = entityBuilder.getEntityId(e, MoleculeType.DNA, e, eName);
entityIds[i] = entityId;
}
const ihm_sphere_obj_site = Table.ofPartialColumns(BasicSchema.ihm_sphere_obj_site, {
id: Column.range(0, rowCount),
entity_id: Column.ofStringArray(entityIds),
seq_id_begin: Column.ofIntArray(normalized.seq_id_begin),
seq_id_end: Column.ofIntArray(normalized.seq_id_end),
asym_id: Column.ofStringArray(normalized.chromosome),
Cartn_x: Column.ofFloatArray(normalized.x),
Cartn_y: Column.ofFloatArray(normalized.y),
Cartn_z: Column.ofFloatArray(normalized.z),
object_radius: Column.ofFloatArray(normalized.r),
rmsf: Column.ofConst(0, rowCount, Column.Schema.float),
model_id: Column.ofConst(1, rowCount, Column.Schema.int),
}, rowCount);
const basic = createBasic({
entity: entityBuilder.getEntityTable(),
ihm_model_list: Table.ofPartialColumns(BasicSchema.ihm_model_list, {
model_id: Column.ofIntArray([1]),
model_name: Column.ofStringArray(['G3D Model']),
}, 1),
ihm_sphere_obj_site
});
const models = await createModels(basic, { kind: 'g3d', name: 'G3D', data }, ctx);
G3dInfoDataProperty.set(models.representative, {
haplotypes: Object.keys(data.data),
haplotype: normalized.haplotype,
resolution: data.resolution,
start: normalized.start,
chroms: normalized.chromosome,
});
return models;
}
export function trajectoryFromG3D(data: G3dDataBlock): Task<Trajectory> {
return Task.create('Parse G3D', async ctx => {
return getTraj(ctx, data);
});
}
export const G3dSymbols = {
haplotype: QuerySymbolRuntime.Dynamic(CustomPropSymbol('g3d', 'haplotype', Type.Str),
ctx => {
if (Unit.isAtomic(ctx.element.unit)) return '';
const info = (G3dInfoDataProperty as any).get(ctx.element.unit.model);
if (!info) return '';
const seqId = ctx.element.unit.model.coarseHierarchy.spheres.seq_id_begin.value(ctx.element.element);
return info.haplotype[seqId] || '';
}
),
chromosome: QuerySymbolRuntime.Dynamic(CustomPropSymbol('g3d', 'chromosome', Type.Str),
ctx => {
if (Unit.isAtomic(ctx.element.unit)) return '';
const { asym_id } = ctx.element.unit.model.coarseHierarchy.spheres;
return asym_id.value(ctx.element.element) || '';
}
),
region: QuerySymbolRuntime.Dynamic(CustomPropSymbol('g3d', 'region', Type.Num),
ctx => {
if (Unit.isAtomic(ctx.element.unit)) return '';
const info = (G3dInfoDataProperty as any).get(ctx.element.unit.model);
if (!info) return 0;
const seqId = ctx.element.unit.model.coarseHierarchy.spheres.seq_id_begin.value(ctx.element.element);
return info.start[seqId] || 0;
}
)
};
export const G3dInfoDataProperty = FormatPropertyProvider.create<G3dInfoData>({ name: 'g3d_info' });
export function g3dHaplotypeQuery(haplotype: string) {
return MS.struct.generator.atomGroups({
'chain-test': MS.core.rel.eq([G3dSymbols.haplotype.symbol(), haplotype]),
});
}
export function g3dChromosomeQuery(chr: string) {
return MS.struct.generator.atomGroups({
'chain-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp('objectPrimitive'), 'sphere']),
MS.core.rel.eq([G3dSymbols.chromosome.symbol(), chr])
])
});
}
export function g3dRegionQuery(chr: string, start: number, end: number) {
return MS.struct.generator.atomGroups({
'chain-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp('objectPrimitive'), 'sphere']),
MS.core.rel.eq([G3dSymbols.chromosome.symbol(), chr])
]),
'residue-test': MS.core.rel.inRange([G3dSymbols.region.symbol(), start, end])
});
}
export interface G3dInfoData {
haplotypes: string[],
haplotype: string[],
start: Int32Array,
resolution: number,
chroms: string[]
};
export const G3dLabelProvider: LociLabelProvider = {
label: (e: Loci): string | undefined => {
if (e.kind !== 'element-loci' || Loci.isEmpty(e)) return;
const first = e.elements[0];
if (e.elements.length !== 1 || Unit.isAtomic(first.unit)) return;
const info = G3dInfoDataProperty.get(first.unit.model);
if (!info) return;
const eI = first.unit.elements[OrderedSet.getAt(first.indices, 0)];
const seqId = first.unit.model.coarseHierarchy.spheres.seq_id_begin.value(eI);
return `<b>Start:</b> ${info.start[seqId]} <small>| resolution ${info.resolution}<small>`;
}
};

View File

@@ -0,0 +1,144 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { debounceTime } from 'rxjs/operators';
import { PluginStateAnimation } from '../../mol-plugin-state/animation/model';
import { PluginComponent } from '../../mol-plugin-state/component';
import { PluginContext } from '../../mol-plugin/context';
import { Task } from '../../mol-task';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { encodeMp4Animation } from './encoder';
export interface Mp4AnimationInfo {
width: number,
height: number
}
export const Mp4AnimationParams = {
quantization: PD.Numeric(18, { min: 10, max: 51 }, { description: 'Lower is better, but slower.' })
};
export class Mp4Controls extends PluginComponent {
private currentNames = new Set<string>();
private animations: PluginStateAnimation[] = [];
readonly behaviors = {
animations: this.ev.behavior<PD.Params>({ }),
current: this.ev.behavior<{ anim: PluginStateAnimation, params: PD.Params, values: any } | undefined>(void 0),
canApply: this.ev.behavior<PluginStateAnimation.CanApply>({ canApply: false }),
info: this.ev.behavior<Mp4AnimationInfo>({ width: 0, height: 0 }),
params: this.ev.behavior<PD.Values<typeof Mp4AnimationParams>>(PD.getDefaultValues(Mp4AnimationParams))
}
setCurrent(name?: string) {
const anim = this.animations.find(a => a.name === name);
if (!anim) {
this.behaviors.current.next(anim);
return;
}
const params = anim.params(this.plugin) as PD.Params;
const values = PD.getDefaultValues(params);
this.behaviors.current.next({ anim, params, values });
this.behaviors.canApply.next(anim.canApply?.(this.plugin) ?? { canApply: true });
}
setCurrentParams(values: any) {
this.behaviors.current.next({ ...this.behaviors.current.value!, values });
}
get current() {
return this.behaviors.current.value;
}
render() {
const task = Task.create('Export Animation', async ctx => {
try {
const resolution = this.plugin.helpers.viewportScreenshot?.getSizeAndViewport()!;
const anim = this.current!;
const movie = await encodeMp4Animation(this.plugin, ctx, {
animation: {
definition: anim.anim,
params: anim.values,
},
...resolution,
quantizationParameter: this.behaviors.params.value.quantization,
pass: this.plugin.helpers.viewportScreenshot?.imagePass!,
});
const filename = anim.anim.display.name.toLowerCase().replace(/\s/g, '-').replace(/[^a-z0-9_\-]/g, '');
return { movie, filename: `${this.plugin.helpers.viewportScreenshot?.getFilename('')}_${filename}.mp4` };
} catch (e) {
this.plugin.log.error('' + e);
throw e;
}
});
return this.plugin.runTask(task, { useOverlay: true });
}
private get manager() {
return this.plugin.managers.animation;
}
private syncInfo() {
const helper = this.plugin.helpers.viewportScreenshot;
const size = helper?.getSizeAndViewport();
if (!size) return;
this.behaviors.info.next({ width: size.viewport.width, height: size.viewport.height });
}
private sync() {
const animations = this.manager.animations.filter(a => a.isExportable);
const hasAll = animations.every(a => this.currentNames.has(a.name));
if (hasAll && this.currentNames.size === animations.length) {
return;
}
const params = {
current: PD.Select(animations[0]?.name,
animations.map(a => [a.name, a.display.name] as [string, string]),
{ label: 'Animation' })
};
const current = this.behaviors.current.value;
const hasCurrent = !!animations.find(a => a.name === current?.anim.name);
this.animations = animations;
if (!hasCurrent) {
this.setCurrent(animations[0]?.name);
}
this.behaviors.animations.next(params);
}
private init() {
this.subscribe(this.plugin.managers.animation.events.updated.pipe(debounceTime(16)), () => {
this.sync();
});
this.subscribe(this.plugin.canvas3d?.resized!, () => this.syncInfo());
this.subscribe(this.plugin.helpers.viewportScreenshot?.events.previewed!, () => this.syncInfo());
this.subscribe(this.plugin.behaviors.state.isBusy, b => {
const anim = this.current;
if (!b && anim) {
this.behaviors.canApply.next(anim.anim.canApply?.(this.plugin) ?? { canApply: true });
}
});
this.sync();
this.syncInfo();
}
constructor(private plugin: PluginContext) {
super();
this.init();
}
}

View File

@@ -0,0 +1,105 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as HME from 'h264-mp4-encoder';
import { Viewport } from '../../mol-canvas3d/camera/util';
import { ImagePass } from '../../mol-canvas3d/passes/image';
import { PluginStateAnimation } from '../../mol-plugin-state/animation/model';
import { PluginContext } from '../../mol-plugin/context';
import { RuntimeContext } from '../../mol-task';
import { Color } from '../../mol-util/color';
export interface Mp4EncoderParams<A extends PluginStateAnimation = PluginStateAnimation> {
pass: ImagePass,
customBackground?: Color,
animation: PluginStateAnimation.Instance<A>,
width: number,
height: number,
viewport: Viewport,
/** default is 30 */
fps?: number,
/** Number from 10 (best quality, slowest) to 51 (worst, fastest) */
quantizationParameter?: number
}
export async function encodeMp4Animation<A extends PluginStateAnimation>(plugin: PluginContext, ctx: RuntimeContext, params: Mp4EncoderParams<A>) {
await ctx.update({ message: 'Initializing...', isIndeterminate: true });
validateViewport(params);
const durationMs = PluginStateAnimation.getDuration(plugin, params.animation);
if (durationMs === void 0) {
throw new Error('The animation does not have the duration specified.');
}
const encoder = await HME.createH264MP4Encoder();
const { width, height } = params;
let vw = params.viewport.width, vh = params.viewport.height;
// dimensions must be a multiple of 2
if (vw % 2 !== 0) vw -= 1;
if (vh % 2 !== 0) vh -= 1;
const normalizedViewport: Viewport = { ...params.viewport, width: vw, height: vh };
encoder.width = vw;
encoder.height = vh;
if (params.quantizationParameter) encoder.quantizationParameter = params.quantizationParameter;
if (params.fps) encoder.frameRate = params.fps;
encoder.initialize();
const loop = plugin.animationLoop;
const canvasProps = plugin.canvas3d?.props;
const wasAnimating = loop.isAnimating;
let stoppedAnimation = true, finalized = false;
try {
loop.stop();
loop.resetTime(0);
if (params.customBackground !== void 0) {
plugin.canvas3d?.setProps({ renderer: { backgroundColor: params.customBackground }, transparentBackground: false }, true);
}
const fps = encoder.frameRate;
const N = Math.ceil(durationMs / 1000 * fps);
const dt = durationMs / N;
await ctx.update({ message: 'Rendering...', isIndeterminate: false, current: 0, max: N + 1 });
await plugin.managers.animation.play(params.animation.definition, params.animation.params);
stoppedAnimation = false;
for (let i = 0; i <= N; i++) {
await loop.tick(i * dt, { isSynchronous: true, manualDraw: true });
const image = params.pass.getImageData(width, height, normalizedViewport);
encoder.addFrameRgba(image.data);
if (ctx.shouldUpdate) {
await ctx.update({ current: i + 1 });
}
}
await ctx.update({ message: 'Applying finishing touches...', isIndeterminate: true });
await plugin.managers.animation.stop();
stoppedAnimation = true;
encoder.finalize();
finalized = true;
return encoder.FS.readFile(encoder.outputFilename);
} finally {
if (finalized) encoder.delete();
if (params.customBackground !== void 0) {
plugin.canvas3d?.setProps({ renderer: { backgroundColor: canvasProps?.renderer!.backgroundColor }, transparentBackground: canvasProps?.transparentBackground });
}
if (!stoppedAnimation) await plugin.managers.animation.stop();
if (wasAnimating) loop.start();
}
}
function validateViewport(params: Mp4EncoderParams) {
if (params.viewport.x + params.viewport.width > params.width || params.viewport.y + params.viewport.height > params.height) {
throw new Error('Viewport exceeds the canvas dimensions.');
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
import { Mp4EncoderUI } from './ui';
export const Mp4Export = PluginBehavior.create<{ }>({
name: 'extension-mp4-export',
category: 'misc',
display: {
name: 'MP4 Animation Export'
},
ctor: class extends PluginBehavior.Handler<{ }> {
register(): void {
this.ctx.customStructureControls.set('mp4-export', Mp4EncoderUI as any);
}
update() {
return false;
}
unregister() {
this.ctx.customStructureControls.delete('mp4-export');
}
},
params: () => ({ })
});

View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @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';
import { Button } from '../../mol-plugin-ui/controls/common';
import { CameraOutlinedSvg, GetAppSvg, Icon, SubscriptionsOutlinedSvg } from '../../mol-plugin-ui/controls/icons';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { download } from '../../mol-util/download';
import { Mp4AnimationParams, Mp4Controls } from './controls';
interface State {
busy?: boolean,
data?: { movie: Uint8Array, filename: string };
}
export class Mp4EncoderUI extends CollapsableControls<{}, State> {
private _controls: Mp4Controls | undefined;
get controls() {
return this._controls || (this._controls = new Mp4Controls(this.plugin));
}
protected defaultState(): State & CollapsableState {
return {
header: 'Export Animation',
isCollapsed: true,
brand: { accent: 'cyan', svg: SubscriptionsOutlinedSvg }
};
}
private downloadControls() {
return <>
<div className='msp-control-offset msp-help-text'>
<div className='msp-help-description' style={{ textAlign: 'center' }}>
Rendering successful!
</div>
</div>
<Button icon={GetAppSvg} onClick={this.save} style={{ marginTop: 1 }}>Save Animation</Button>
<Button onClick={() => this.setState({ data: void 0 })} style={{ marginTop: 6 }}>Clear</Button>
</>;
}
protected renderControls(): JSX.Element | null {
if (this.state.data) {
return this.downloadControls();
}
const ctrl = this.controls;
const current = ctrl.behaviors.current.value;
const info = ctrl.behaviors.info.value;
const canApply = ctrl.behaviors.canApply.value;
return <>
<ParameterControls
params={ctrl.behaviors.animations.value}
values={{ current: current?.anim.name }}
onChangeValues={xs => ctrl.setCurrent(xs.current)}
isDisabled={this.state.busy}
/>
{current && <ParameterControls
params={current.params}
values={current.values}
onChangeValues={xs => ctrl.setCurrentParams(xs)}
isDisabled={this.state.busy}
/>}
<div className='msp-control-offset msp-help-text'>
<div className='msp-help-description' style={{ textAlign: 'center' }}>
Resolution: {info.width}x{info.height}<br />
Adjust in viewport using <Icon svg={CameraOutlinedSvg} inline />
</div>
</div>
<ParameterControls
params={Mp4AnimationParams}
values={ctrl.behaviors.params.value}
onChangeValues={xs => ctrl.behaviors.params.next(xs)}
isDisabled={this.state.busy}
/>
<Button onClick={this.generate} style={{ marginTop: 1 }}
disabled={this.state.busy || !canApply.canApply}
commit={canApply.canApply ? 'on' : 'off'}>
{canApply.canApply ? 'Render' : canApply.reason ?? 'Invalid params/state'}
</Button>
</>;
}
componentDidMount() {
const merged = merge(
this.controls.behaviors.animations,
this.controls.behaviors.current,
this.controls.behaviors.canApply,
this.controls.behaviors.info,
this.controls.behaviors.params
);
this.subscribe(merged.pipe(debounceTime(10)), () => {
if (!this.state.isCollapsed) this.forceUpdate();
});
}
componentWillUnmount() {
this._controls?.dispose();
this._controls = void 0;
}
save = () => {
download(new Blob([this.state.data!.movie]), this.state.data!.filename);
}
generate = async () => {
try {
this.setState({ busy: true });
const data = await this.controls.render();
this.setState({ busy: false, data });
} catch {
this.setState({ busy: false });
}
}
}

View File

@@ -6,7 +6,7 @@
import { StructureQualityReport, StructureQualityReportProvider } from './prop';
import { Location } from '../../../mol-model/location';
import { StructureElement } from '../../../mol-model/structure';
import { Bond, StructureElement } from '../../../mol-model/structure';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ThemeDataContext } from '../../../mol-theme/theme';
import { Color } from '../../../mol-util/color';
@@ -46,11 +46,16 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
if (ctx.structure && !ctx.structure.isEmpty && ctx.structure.models[0].customProperties.has(StructureQualityReportProvider.descriptor)) {
const getIssues = StructureQualityReport.getIssues;
const l = StructureElement.Location.create(ctx.structure);
if (props.type.name === 'issue-count') {
color = (location: Location) => {
if (StructureElement.Location.is(location)) {
return ValidationColors[Math.min(3, getIssues(location).length) + 1];
} else if (Bond.isLocation(location)) {
l.unit = location.aUnit;
l.element = location.aUnit.elements[location.aIndex];
return ValidationColors[Math.min(3, getIssues(l).length) + 1];
}
return ValidationColors[0];
};
@@ -59,6 +64,10 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
color = (location: Location) => {
if (StructureElement.Location.is(location) && getIssues(location).indexOf(issue) >= 0) {
return ValidationColors[4];
} else if (Bond.isLocation(location)) {
l.unit = location.aUnit;
l.element = location.aUnit.elements[location.aIndex];
return ValidationColors[Math.min(3, getIssues(l).length) + 1];
}
return ValidationColors[0];
};

View File

@@ -13,7 +13,7 @@ import { Model, ResidueIndex, Unit, IndexedCustomProperty } from '../../../mol-m
import { residueIdFields } from '../../../mol-model/structure/export/categories/atom_site';
import { StructureElement, CifExportContext, Structure } from '../../../mol-model/structure/structure';
import { CustomPropSymbol } from '../../../mol-script/language/symbol';
import Type from '../../../mol-script/language/type';
import { Type } from '../../../mol-script/language/type';
import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { arraySetAdd } from '../../../mol-util/array';

View File

@@ -181,8 +181,8 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
}
const assemblySymmetry = await tryCreateAssemblySymmetry(plugin, structureCell);
const globalThemeName = assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
const preset = await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName }, plugin);
const colorTheme = assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
const preset = await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
return { components: preset.components, representations: { ...preset.representations, assemblySymmetry } };
}

View File

@@ -9,7 +9,7 @@ import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { AssemblySymmetryProvider, AssemblySymmetry } from './prop';
import { Color } from '../../../mol-util/color';
import { Unit, StructureElement, StructureProperties } from '../../../mol-model/structure';
import { Unit, StructureElement, StructureProperties, Bond } from '../../../mol-model/structure';
import { Location } from '../../../mol-model/location';
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
import { getPalette, getPaletteParams } from '../../../mol-util/color/palette';
@@ -50,6 +50,8 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
const clusters = assemblySymmetry?.value?.clusters;
if (clusters?.length && ctx.structure) {
const l = StructureElement.Location.create(ctx.structure);
const clusterByMember = new Map<string, number>();
for (let i = 0, il = clusters.length; i < il; ++i) {
const { members } = clusters[i]!;
@@ -67,12 +69,20 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
legend = palette.legend;
const _emptyList: any[] = [];
const getColor = (location: StructureElement.Location) => {
const { assembly } = location.unit.conformation.operator;
const asymId = getAsymId(location.unit)(location);
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly?.operList || _emptyList));
return cluster !== undefined ? palette.color(cluster) : DefaultColor;
};
color = (location: Location): Color => {
if (StructureElement.Location.is(location)) {
const { assembly } = location.unit.conformation.operator;
const asymId = getAsymId(location.unit)(location);
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly?.operList || _emptyList));
return cluster !== undefined ? palette.color(cluster) : DefaultColor;
return getColor(location);
} else if (Bond.isLocation(location)) {
l.unit = location.aUnit;
l.element = location.aUnit.elements[location.aIndex];
return getColor(l);
}
return DefaultColor;
};

View File

@@ -5,7 +5,7 @@
*/
import { AssemblySymmetryQuery, AssemblySymmetryQueryVariables } from '../graphql/types';
import query from '../graphql/symmetry.gql';
import { symmetry_gql } from '../graphql/symmetry.gql';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Structure, Model, StructureSelection, QueryContext } from '../../../mol-model/structure';
@@ -66,7 +66,7 @@ export namespace AssemblySymmetry {
assembly_id: structure.units[0].conformation.operator.assembly?.id || '',
entry_id: structure.units[0].model.entryId
};
const result = await client.request(ctx.runtime, query, variables);
const result = await client.request(ctx.runtime, symmetry_gql, variables);
let value: AssemblySymmetryDataValue = [];
if (!result.data.assembly?.rcsb_struct_symmetry) {

View File

@@ -1,4 +1,4 @@
export default /* GraphQL */ `
export const symmetry_gql = /* GraphQL */ `
query AssemblySymmetry($assembly_id: String!, $entry_id: String!) {
assembly(assembly_id: $assembly_id, entry_id: $entry_id) {
rcsb_struct_symmetry {

File diff suppressed because it is too large Load Diff

View File

@@ -309,15 +309,15 @@ export const ValidationReportGeometryQualityPreset = StructureRepresentationPres
params: () => StructureRepresentationPresetProvider.CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const model = structureCell?.obj?.data.model;
if (!structureCell || !model) return {};
const structure = structureCell?.obj?.data;
if (!structureCell || !structure) return {};
await plugin.runTask(Task.create('Validation Report', async runtime => {
await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, structure.models[0]);
}));
const colorTheme = GeometryQualityColorThemeProvider.name as any;
const { components, representations } = await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin);
const { components, representations } = await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
const clashes = await plugin.builders.structure.tryCreateComponentFromExpression(structureCell, hasClash.expression, 'clashes', { label: 'Clashes' });
@@ -329,6 +329,7 @@ export const ValidationReportGeometryQualityPreset = StructureRepresentationPres
}
await update.commit({ revertOnError: true });
return { components: { ...components, clashes }, representations: { ...representations, clashesBallAndStick, clashesRepr } };
}
});
@@ -345,15 +346,15 @@ export const ValidationReportDensityFitPreset = StructureRepresentationPresetPro
params: () => StructureRepresentationPresetProvider.CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const model = structureCell?.obj?.data.model;
if (!structureCell || !model) return {};
const structure = structureCell?.obj?.data;
if (!structureCell || !structure) return {};
await plugin.runTask(Task.create('Validation Report', async runtime => {
await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, structure.models[0]);
}));
const colorTheme = DensityFitColorThemeProvider.name as any;
return await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin);
return await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
}
});
@@ -369,14 +370,14 @@ export const ValidationReportRandomCoilIndexPreset = StructureRepresentationPres
params: () => StructureRepresentationPresetProvider.CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const model = structureCell?.obj?.data.model;
if (!structureCell || !model) return {};
const structure = structureCell?.obj?.data;
if (!structureCell || !structure) return {};
await plugin.runTask(Task.create('Validation Report', async runtime => {
await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, structure.models[0]);
}));
const colorTheme = RandomCoilIndexColorThemeProvider.name as any;
return await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin);
return await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
}
});

View File

@@ -8,7 +8,7 @@ import { ThemeDataContext } from '../../../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { Color, ColorScale } from '../../../../mol-util/color';
import { StructureElement, Model } from '../../../../mol-model/structure';
import { StructureElement, Model, ElementIndex, Bond } from '../../../../mol-model/structure';
import { Location } from '../../../../mol-model/location';
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
import { ValidationReportProvider, ValidationReport } from '../prop';
@@ -37,13 +37,19 @@ export function DensityFitColorTheme(ctx: ThemeDataContext, props: {}): ColorThe
if (validationReport?.value && model) {
const { rsrz, rscc } = validationReport.value;
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
const getColor = (element: ElementIndex) => {
const rsrzValue = rsrz.get(residueIndex[element]);
if (rsrzValue !== undefined) return scaleRsrz.color(rsrzValue);
const rsccValue = rscc.get(residueIndex[element]);
if (rsccValue !== undefined) return scaleRscc.color(rsccValue);
return DefaultColor;
};
color = (location: Location): Color => {
if (StructureElement.Location.is(location) && location.unit.model === model) {
const rsrzValue = rsrz.get(residueIndex[location.element]);
if (rsrzValue !== undefined) return scaleRsrz.color(rsrzValue);
const rsccValue = rscc.get(residueIndex[location.element]);
if (rsccValue !== undefined) return scaleRscc.color(rsccValue);
return DefaultColor;
return getColor(location.element);
} else if (Bond.isLocation(location) && location.aUnit.model === model) {
return getColor(location.aUnit.elements[location.aIndex]);
}
return DefaultColor;
};

View File

@@ -8,7 +8,7 @@ import { ThemeDataContext } from '../../../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { Color } from '../../../../mol-util/color';
import { StructureElement } from '../../../../mol-model/structure';
import { Bond, ElementIndex, StructureElement } from '../../../../mol-model/structure';
import { Location } from '../../../../mol-model/location';
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
import { ValidationReportProvider, ValidationReport } from '../prop';
@@ -32,7 +32,7 @@ const ColorLegend = TableLegend([
]);
export function getGeometricQualityColorThemeParams(ctx: ThemeDataContext) {
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]).value;
const validationReport = !!ctx.structure && ctx.structure.models.length > 0 && ValidationReportProvider.get(ctx.structure.models[0]).value;
const options: [string, string][] = [];
if (validationReport) {
const kinds = new Set<string>();
@@ -48,7 +48,7 @@ export type GeometricQualityColorThemeParams = ReturnType<typeof getGeometricQua
export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Values<GeometricQualityColorThemeParams>): ColorTheme<GeometricQualityColorThemeParams> {
let color: LocationColor = () => DefaultColor;
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]);
const validationReport = !!ctx.structure && ctx.structure.models.length > 0 ? ValidationReportProvider.get(ctx.structure.models[0]) : void 0;
const contextHash = validationReport?.version;
const value = validationReport?.value;
@@ -59,31 +59,35 @@ export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Value
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
const { polymerType } = model.atomicHierarchy.derived.residue;
const ignore = new Set(props.ignore);
const getColor = (element: ElementIndex) => {
const rI = residueIndex[element];
const value = geometryIssues.get(rI);
if (value === undefined) return DefaultColor;
let count = SetUtils.differenceSize(value, ignore);
if (count > 0 && polymerType[rI] === PolymerType.NA) {
count = 0;
if (!ignore.has('clash') && clashes.getVertexEdgeCount(element) > 0) count += 1;
if (!ignore.has('mog-bond-outlier') && bondOutliers.index.has(element)) count += 1;
if (!ignore.has('mog-angle-outlier') && angleOutliers.index.has(element)) count += 1;
}
switch (count) {
case undefined: return DefaultColor;
case 0: return NoIssuesColor;
case 1: return OneIssueColor;
case 2: return TwoIssuesColor;
default: return ThreeOrMoreIssuesColor;
}
};
color = (location: Location): Color => {
if (StructureElement.Location.is(location) && location.unit.model === model) {
const { element } = location;
const rI = residueIndex[element];
const value = geometryIssues.get(rI);
if (value === undefined) return DefaultColor;
let count = SetUtils.differenceSize(value, ignore);
if (count > 0 && polymerType[rI] === PolymerType.NA) {
count = 0;
if (!ignore.has('clash') && clashes.getVertexEdgeCount(element) > 0) count += 1;
if (!ignore.has('mog-bond-outlier') && bondOutliers.index.has(element)) count += 1;
if (!ignore.has('mog-angle-outlier') && angleOutliers.index.has(element)) count += 1;
}
switch (count) {
case undefined: return DefaultColor;
case 0: return NoIssuesColor;
case 1: return OneIssueColor;
case 2: return TwoIssuesColor;
default: return ThreeOrMoreIssuesColor;
}
return getColor(location.element);
} else if (Bond.isLocation(location) && location.aUnit.model === model) {
return getColor(location.aUnit.elements[location.aIndex]);
}
return DefaultColor;
};

View File

@@ -8,7 +8,7 @@ import { ThemeDataContext } from '../../../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { Color, ColorScale } from '../../../../mol-util/color';
import { StructureElement, Model } from '../../../../mol-model/structure';
import { StructureElement, Model, ElementIndex, Bond } from '../../../../mol-model/structure';
import { Location } from '../../../../mol-model/location';
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
import { ValidationReportProvider, ValidationReport } from '../prop';
@@ -31,10 +31,16 @@ export function RandomCoilIndexColorTheme(ctx: ThemeDataContext, props: {}): Col
if (rci && model) {
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
const getColor = (element: ElementIndex) => {
const value = rci.get(residueIndex[element]);
return value === undefined ? DefaultColor : scale.color(value);
};
color = (location: Location): Color => {
if (StructureElement.Location.is(location) && location.unit.model === model) {
const value = rci.get(residueIndex[location.element]);
return value === undefined ? DefaultColor : scale.color(value);
return getColor(location.element);
} else if (Bond.isLocation(location) && location.aUnit.model === model) {
return getColor(location.aUnit.elements[location.aIndex]);
}
return DefaultColor;
};

View File

@@ -19,7 +19,7 @@ import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
import { CustomPropSymbol } from '../../../mol-script/language/symbol';
import Type from '../../../mol-script/language/type';
import { Type } from '../../../mol-script/language/type';
import { Asset } from '../../../mol-util/assets';
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
@@ -187,7 +187,7 @@ type InterUnitClashesProps = {
}
export type IntraUnitClashes = IntAdjacencyGraph<UnitIndex, IntraUnitClashesProps>
export type InterUnitClashes = InterUnitGraph<Unit.Atomic, UnitIndex, InterUnitClashesProps>
export type InterUnitClashes = InterUnitGraph<number, UnitIndex, InterUnitClashesProps>
export interface Clashes {
readonly interUnit: InterUnitClashes
@@ -195,7 +195,7 @@ export interface Clashes {
}
function createInterUnitClashes(structure: Structure, clashes: ValidationReport['clashes']) {
const builder = new InterUnitGraph.Builder<Unit.Atomic, UnitIndex, InterUnitClashesProps>();
const builder = new InterUnitGraph.Builder<number, UnitIndex, InterUnitClashesProps>();
const { a, b, edgeProps: { id, magnitude, distance } } = clashes;
const pA = Vec3(), pB = Vec3();
@@ -204,7 +204,7 @@ function createInterUnitClashes(structure: Structure, clashes: ValidationReport[
const elementsA = unitA.elements;
const elementsB = unitB.elements;
builder.startUnitPair(unitA as Unit.Atomic, unitB as Unit.Atomic);
builder.startUnitPair(unitA.id, unitB.id);
for (let i = 0, il = clashes.edgeCount * 2; i < il; ++i) {
// TODO create lookup

View File

@@ -15,7 +15,7 @@ import { Interval } from '../../../mol-data/int';
import { RepresentationContext, RepresentationParamsGetter, Representation } from '../../../mol-repr/representation';
import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
import { VisualContext } from '../../../mol-repr/visual';
import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from '../../../mol-repr/structure/visual/util/link';
import { createLinkCylinderMesh, LinkCylinderParams, LinkStyle } from '../../../mol-repr/structure/visual/util/link';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../../../mol-repr/structure/units-visual';
import { VisualUpdateState } from '../../../mol-repr/util';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
@@ -50,7 +50,7 @@ function createIntraUnitClashCylinderMesh(ctx: VisualContext, unit: Unit, struct
pos(elements[a[edgeIndex]], posA);
pos(elements[b[edgeIndex]], posB);
},
style: (edgeIndex: number) => LinkCylinderStyle.Disk,
style: (edgeIndex: number) => LinkStyle.Disk,
radius: (edgeIndex: number) => magnitude[edgeIndex] * sizeFactor,
};
@@ -143,7 +143,7 @@ function createIntraClashIterator(structureGroup: StructureGroup): LocationItera
location.element = unit.elements[a[groupIndex]];
return location;
};
return LocationIterator(groupCount, instanceCount, getLocation);
return LocationIterator(groupCount, instanceCount, 1, getLocation);
}
//
@@ -159,11 +159,12 @@ function createInterUnitClashCylinderMesh(ctx: VisualContext, structure: Structu
linkCount: edgeCount,
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
const b = edges[edgeIndex];
const uA = b.unitA, uB = b.unitB;
const uA = structure.unitMap.get(b.unitA);
const uB = structure.unitMap.get(b.unitB);
uA.conformation.position(uA.elements[b.indexA], posA);
uB.conformation.position(uB.elements[b.indexB], posB);
},
style: (edgeIndex: number) => LinkCylinderStyle.Disk,
style: (edgeIndex: number) => LinkStyle.Disk,
radius: (edgeIndex: number) => edges[edgeIndex].props.magnitude * sizeFactor
};
@@ -197,11 +198,13 @@ export function InterUnitClashVisual(materialId: number): ComplexVisual<InterUni
}, materialId);
}
function getInterClashBoundingSphere(clashes: InterUnitClashes, elements: number[], boundingSphere: Sphere3D) {
function getInterClashBoundingSphere(structure: Structure, clashes: InterUnitClashes, elements: number[], boundingSphere: Sphere3D) {
return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
const c = clashes.edges[elements[i]];
c.unitA.conformation.position(c.unitA.elements[c.indexA], pA);
c.unitB.conformation.position(c.unitB.elements[c.indexB], pB);
const uA = structure.unitMap.get(c.unitA);
const uB = structure.unitMap.get(c.unitB);
uA.conformation.position(uA.elements[c.indexA], pA);
uB.conformation.position(uB.elements[c.indexB], pB);
}, boundingSphere);
}
@@ -209,18 +212,20 @@ function getInterClashLabel(structure: Structure, clashes: InterUnitClashes, ele
const idx = elements[0];
if (idx === undefined) return '';
const c = clashes.edges[idx];
const uA = structure.unitMap.get(c.unitA);
const uB = structure.unitMap.get(c.unitB);
const mag = c.props.magnitude.toFixed(2);
const dist = c.props.distance.toFixed(2);
return [
`Clash id: ${c.props.id} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`,
bondLabel(Bond.Location(structure, c.unitA, c.indexA, structure, c.unitB, c.indexB))
bondLabel(Bond.Location(structure, uA, c.indexA, structure, uB, c.indexB))
].join('</br>');
}
function InterClashLoci(structure: Structure, clashes: InterUnitClashes, elements: number[]) {
return DataLoci('inter-clashes', clashes, elements,
(boundingSphere: Sphere3D) => getInterClashBoundingSphere(clashes, elements, boundingSphere),
(boundingSphere: Sphere3D) => getInterClashBoundingSphere(structure, clashes, elements, boundingSphere),
() => getInterClashLabel(structure, clashes, elements));
}
@@ -246,11 +251,11 @@ function createInterClashIterator(structure: Structure): LocationIterator {
const location = StructureElement.Location.create(structure);
const getLocation = (groupIndex: number) => {
const clash = clashes.edges[groupIndex];
location.unit = clash.unitA;
location.element = clash.unitA.elements[clash.indexA];
location.unit = structure.unitMap.get(clash.unitA);
location.element = location.unit.elements[clash.indexA];
return location;
};
return LocationIterator(groupCount, instanceCount, getLocation, true);
return LocationIterator(groupCount, instanceCount, 1, getLocation, true);
}
//

View File

@@ -10,22 +10,37 @@ import { Viewport, cameraProject, cameraUnproject } from './camera/util';
import { CameraTransitionManager } from './camera/transition';
import { BehaviorSubject } from 'rxjs';
export { Camera };
export { ICamera, Camera };
class Camera {
interface ICamera {
readonly viewport: Viewport,
readonly view: Mat4,
readonly projection: Mat4,
readonly projectionView: Mat4,
readonly inverseProjectionView: Mat4,
readonly state: Readonly<Camera.Snapshot>,
readonly viewOffset: Camera.ViewOffset,
readonly far: number,
readonly near: number,
readonly fogFar: number,
readonly fogNear: number,
}
class Camera implements ICamera {
readonly view: Mat4 = Mat4.identity();
readonly projection: Mat4 = Mat4.identity();
readonly projectionView: Mat4 = Mat4.identity();
readonly inverseProjectionView: Mat4 = Mat4.identity();
private pixelScale: number
get pixelRatio () {
const dpr = (typeof window !== 'undefined') ? window.devicePixelRatio : 1;
return dpr * this.pixelScale;
}
readonly viewport: Viewport;
readonly state: Readonly<Camera.Snapshot> = Camera.createDefaultSnapshot();
readonly viewOffset: Camera.ViewOffset = {
enabled: false,
fullWidth: 1, fullHeight: 1,
offsetX: 0, offsetY: 0,
width: 1, height: 1
}
readonly viewOffset = Camera.ViewOffset();
near = 1
far = 10000
@@ -52,6 +67,10 @@ class Camera {
update() {
const snapshot = this.state as Camera.Snapshot;
if (snapshot.radiusMax === 0) {
return false;
}
const height = 2 * Math.tan(snapshot.fov / 2) * Vec3.distance(snapshot.position, snapshot.target);
this.zoom = this.viewport.height / height;
@@ -86,12 +105,7 @@ class Camera {
}
getTargetDistance(radius: number) {
const r = Math.max(radius, 0.01);
const { fov } = this.state;
const { width, height } = this.viewport;
const aspect = width / height;
const aspectFactor = (height < width ? 1 : aspect);
return Math.abs((r / aspectFactor) / Math.sin(fov / 2));
return Camera.targetDistance(radius, this.state.fov, this.viewport.width, this.viewport.height);
}
getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
@@ -126,8 +140,9 @@ class Camera {
return cameraUnproject(out, point, this.viewport, this.inverseProjectionView);
}
constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) {
constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(0, 0, 128, 128), props: Partial<{ pixelScale: number }> = {}) {
this.viewport = viewport;
this.pixelScale = props.pixelScale || 1;
Camera.copySnapshot(this.state, state);
}
}
@@ -150,6 +165,15 @@ namespace Camera {
height: number
}
export function ViewOffset(): ViewOffset {
return {
enabled: false,
fullWidth: 1, fullHeight: 1,
offsetX: 0, offsetY: 0,
width: 1, height: 1
};
}
export function setViewOffset(out: ViewOffset, fullWidth: number, fullHeight: number, offsetX: number, offsetY: number, width: number, height: number) {
out.fullWidth = fullWidth;
out.fullHeight = fullHeight;
@@ -159,6 +183,23 @@ namespace Camera {
out.height = height;
}
export function copyViewOffset(out: ViewOffset, view: ViewOffset) {
out.enabled = view.enabled;
out.fullWidth = view.fullWidth;
out.fullHeight = view.fullHeight;
out.offsetX = view.offsetX;
out.offsetY = view.offsetY;
out.width = view.width;
out.height = view.height;
}
export function targetDistance(radius: number, fov: number, width: number, height: number) {
const r = Math.max(radius, 0.01);
const aspect = width / height;
const aspectFactor = (height < width ? 1 : aspect);
return Math.abs((r / aspectFactor) / Math.sin(fov / 2));
}
export function createDefaultSnapshot(): Snapshot {
return {
mode: 'perspective',
@@ -168,8 +209,8 @@ namespace Camera {
up: Vec3.create(0, 1, 0),
target: Vec3.create(0, 0, 0),
radius: 10,
radiusMax: 10,
radius: 0,
radiusMax: 0,
fog: 50,
clipFar: true
};
@@ -211,10 +252,10 @@ namespace Camera {
function updateOrtho(camera: Camera) {
const { viewport, zoom, near, far, viewOffset } = camera;
const fullLeft = -(viewport.width - viewport.x) / 2;
const fullRight = (viewport.width - viewport.x) / 2;
const fullTop = (viewport.height - viewport.y) / 2;
const fullBottom = -(viewport.height - viewport.y) / 2;
const fullLeft = -viewport.width / 2;
const fullRight = viewport.width / 2;
const fullTop = viewport.height / 2;
const fullBottom = -viewport.height / 2;
const dx = (fullRight - fullLeft) / (2 * zoom);
const dy = (fullTop - fullBottom) / (2 * zoom);
@@ -278,12 +319,12 @@ function updateClip(camera: Camera) {
let far = cameraDistance + normalizedFar;
const fogNearFactor = -(50 - fog) / 50;
let fogNear = cameraDistance - (normalizedFar * fogNearFactor);
let fogFar = far;
const fogNear = cameraDistance - (normalizedFar * fogNearFactor);
const fogFar = far;
if (mode === 'perspective') {
// set at least to 5 to avoid slow sphere impostor rendering
near = Math.max(5, near);
near = Math.max(Math.min(radiusMax, 5), near);
far = Math.max(5, far);
} else {
near = Math.max(0, near);
@@ -296,7 +337,7 @@ function updateClip(camera: Camera) {
}
camera.near = near;
camera.far = far;
camera.far = 2 * far; // avoid precision issues distingushing far objects from background
camera.fogNear = fogNear;
camera.fogFar = fogFar;
}

View File

@@ -0,0 +1,141 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
* Adapted from three.js, The MIT License, Copyright © 2010-2020 three.js authors
*/
import { Mat4 } from '../../mol-math/linear-algebra';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Camera, ICamera } from '../camera';
import { Viewport } from './util';
export const StereoCameraParams = {
eyeSeparation: PD.Numeric(0.064, { min: 0.01, max: 0.5, step: 0.001 }),
focus: PD.Numeric(10, { min: 1, max: 100, step: 0.01 }),
};
export const DefaultStereoCameraProps = PD.getDefaultValues(StereoCameraParams);
export type StereoCameraProps = PD.Values<typeof StereoCameraParams>
export { StereoCamera };
class StereoCamera {
readonly left: ICamera = new EyeCamera();
readonly right: ICamera = new EyeCamera();
get viewport() {
return this.parent.viewport;
}
get viewOffset() {
return this.parent.viewOffset;
}
private props: StereoCameraProps
constructor(private parent: Camera, props: Partial<StereoCameraProps> = {}) {
this.props = { ...DefaultStereoCameraProps, ...props };
}
setProps(props: Partial<StereoCameraProps>) {
Object.assign(this.props, props);
}
update() {
this.parent.update();
update(this.parent, this.props, this.left as EyeCamera, this.right as EyeCamera);
}
}
namespace StereoCamera {
export function is(camera: Camera | StereoCamera): camera is StereoCamera {
return 'left' in camera && 'right' in camera;
}
}
class EyeCamera implements ICamera {
viewport = Viewport.create(0, 0, 0, 0);
view = Mat4();
projection = Mat4();
projectionView = Mat4();
inverseProjectionView = Mat4();
state: Readonly<Camera.Snapshot> = Camera.createDefaultSnapshot();
viewOffset: Readonly<Camera.ViewOffset> = Camera.ViewOffset();
far: number = 0;
near: number = 0;
fogFar: number = 0;
fogNear: number = 0;
}
const eyeLeft = Mat4.identity(), eyeRight = Mat4.identity();
function update(camera: Camera, props: StereoCameraProps, left: EyeCamera, right: EyeCamera) {
// Copy the states
Viewport.copy(left.viewport, camera.viewport);
Mat4.copy(left.view, camera.view);
Mat4.copy(left.projection, camera.projection);
Camera.copySnapshot(left.state, camera.state);
Camera.copyViewOffset(left.viewOffset, camera.viewOffset);
left.far = camera.far;
left.near = camera.near;
left.fogFar = camera.fogFar;
left.fogNear = camera.fogNear;
Viewport.copy(right.viewport, camera.viewport);
Mat4.copy(right.view, camera.view);
Mat4.copy(right.projection, camera.projection);
Camera.copySnapshot(right.state, camera.state);
Camera.copyViewOffset(right.viewOffset, camera.viewOffset);
right.far = camera.far;
right.near = camera.near;
right.fogFar = camera.fogFar;
right.fogNear = camera.fogNear;
// update the view offsets
const w = Math.floor(camera.viewport.width / 2);
const aspect = w / camera.viewport.height;
left.viewport.width = w;
right.viewport.x += w;
right.viewport.width -= w;
// update the projection and view matrices
const eyeSepHalf = props.eyeSeparation / 2;
const eyeSepOnProjection = eyeSepHalf * camera.near / props.focus;
const ymax = camera.near * Math.tan(camera.state.fov * 0.5);
let xmin, xmax;
// translate xOffset
eyeLeft[12] = -eyeSepHalf;
eyeRight[12] = eyeSepHalf;
// for left eye
xmin = -ymax * aspect + eyeSepOnProjection;
xmax = ymax * aspect + eyeSepOnProjection;
left.projection[0] = 2 * camera.near / (xmax - xmin);
left.projection[8] = (xmax + xmin) / (xmax - xmin);
Mat4.mul(left.view, left.view, eyeLeft);
Mat4.mul(left.projectionView, left.projection, left.view);
Mat4.invert(left.inverseProjectionView, left.projectionView);
// for right eye
xmin = -ymax * aspect - eyeSepOnProjection;
xmax = ymax * aspect - eyeSepOnProjection;
right.projection[0] = 2 * camera.near / (xmax - xmin);
right.projection[8] = (xmax + xmin) / (xmax - xmin);
Mat4.mul(right.view, right.view, eyeRight);
Mat4.mul(right.projectionView, right.projection, right.view);
Mat4.invert(right.inverseProjectionView, right.projectionView);
}

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 Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -7,45 +7,51 @@
import { BehaviorSubject, Subscription } from 'rxjs';
import { now } from '../mol-util/now';
import { Vec3 } from '../mol-math/linear-algebra';
import InputObserver, { ModifiersKeys, ButtonsType } from '../mol-util/input/input-observer';
import Renderer, { RendererStats, RendererParams } from '../mol-gl/renderer';
import { Vec3, Vec2 } from '../mol-math/linear-algebra';
import { InputObserver, ModifiersKeys, ButtonsType } from '../mol-util/input/input-observer';
import { Renderer, RendererStats, RendererParams } from '../mol-gl/renderer';
import { GraphicsRenderObject } from '../mol-gl/render-object';
import { TrackballControls, TrackballControlsParams } from './controls/trackball';
import { Viewport } from './camera/util';
import { createContext, WebGLContext, getGLContext } from '../mol-gl/webgl/context';
import { Representation } from '../mol-repr/representation';
import Scene from '../mol-gl/scene';
import { GraphicsRenderVariant } from '../mol-gl/webgl/render-item';
import { Scene } from '../mol-gl/scene';
import { PickingId } from '../mol-geo/geometry/picking';
import { MarkerAction } from '../mol-util/marker-action';
import { Loci, EmptyLoci, isEmptyLoci } from '../mol-model/loci';
import { Camera } from './camera';
import { ParamDefinition as PD } from '../mol-util/param-definition';
import { BoundingSphereHelper, DebugHelperParams } from './helper/bounding-sphere-helper';
import { DebugHelperParams } from './helper/bounding-sphere-helper';
import { SetUtils } from '../mol-util/set';
import { Canvas3dInteractionHelper } from './helper/interaction-events';
import { PostprocessingParams, PostprocessingPass } from './passes/postprocessing';
import { MultiSampleParams, MultiSamplePass } from './passes/multi-sample';
import { PixelData } from '../mol-util/image';
import { readTexture } from '../mol-gl/compute/util';
import { DrawPass } from './passes/draw';
import { PickPass } from './passes/pick';
import { PostprocessingParams } from './passes/postprocessing';
import { MultiSampleHelper, MultiSampleParams, MultiSamplePass } from './passes/multi-sample';
import { PickData } from './passes/pick';
import { PickHelper } from './passes/pick';
import { ImagePass, ImageProps } from './passes/image';
import { Sphere3D } from '../mol-math/geometry';
import { isDebugMode } from '../mol-util/debug';
import { CameraHelperParams } from './helper/camera-helper';
import { produce } from 'immer';
import { HandleHelper, HandleHelperParams } from './helper/handle-helper';
import { HandleHelperParams } from './helper/handle-helper';
import { StereoCamera, StereoCameraParams } from './camera/stereo';
import { Helper } from './helper/helper';
import { Passes } from './passes/passes';
import { shallowEqual } from '../mol-util';
export const Canvas3DParams = {
camera: PD.Group({
mode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']] as const, { label: 'Camera' }),
helper: PD.Group(CameraHelperParams, { isFlat: true })
mode: PD.Select('perspective', PD.arrayToOptions(['perspective', 'orthographic'] as const), { label: 'Camera' }),
helper: PD.Group(CameraHelperParams, { isFlat: true }),
stereo: PD.MappedStatic('off', {
on: PD.Group(StereoCameraParams),
off: PD.Group({})
}, { cycle: true, hideIf: p => p?.mode !== 'perspective' }),
manualReset: PD.Boolean(false, { isHidden: true }),
}, { pivot: 'mode' }),
cameraFog: PD.MappedStatic('on', {
on: PD.Group({
intensity: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
intensity: PD.Numeric(15, { min: 1, max: 100, step: 1 }),
}),
off: PD.Group({})
}, { cycle: true, description: 'Show fog in the distance' }),
@@ -53,6 +59,15 @@ export const Canvas3DParams = {
radius: PD.Numeric(100, { min: 0, max: 99, step: 1 }, { label: 'Clipping', description: 'How much of the scene to show.' }),
far: PD.Boolean(true, { description: 'Hide scene in the distance' }),
}, { pivot: 'radius' }),
viewport: PD.MappedStatic('canvas', {
canvas: PD.Group({}),
custom: PD.Group({
x: PD.Numeric(0),
y: PD.Numeric(0),
width: PD.Numeric(128),
height: PD.Numeric(128)
})
}),
cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }),
transparentBackground: PD.Boolean(false),
@@ -66,70 +81,57 @@ export const Canvas3DParams = {
};
export const DefaultCanvas3DParams = PD.getDefaultValues(Canvas3DParams);
export type Canvas3DProps = PD.Values<typeof Canvas3DParams>
export { Canvas3D };
interface Canvas3D {
readonly webgl: WebGLContext,
add(repr: Representation.Any): void
remove(repr: Representation.Any): void
/**
* This function must be called if animate() is not set up so that add/remove actions take place.
*/
commit(isSynchronous?: boolean): void
update(repr?: Representation.Any, keepBoundingSphere?: boolean): void
clear(): void
syncVisibility(): void
requestDraw(force?: boolean): void
animate(): void
identify(x: number, y: number): PickingId | undefined
mark(loci: Representation.Loci, action: MarkerAction): void
getLoci(pickingId: PickingId): Representation.Loci
readonly didDraw: BehaviorSubject<now.Timestamp>
readonly reprCount: BehaviorSubject<number>
handleResize(): void
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
requestCameraReset(options?: { durationMs?: number, snapshot?: Partial<Camera.Snapshot> }): void
readonly camera: Camera
readonly boundingSphere: Readonly<Sphere3D>
getPixelData(variant: GraphicsRenderVariant): PixelData
setProps(props: Partial<Canvas3DProps> | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void)): void
getImagePass(props: Partial<ImageProps>): ImagePass
/** Returns a copy of the current Canvas3D instance props */
readonly props: Readonly<Canvas3DProps>
readonly input: InputObserver
readonly stats: RendererStats
readonly interaction: Canvas3dInteractionHelper['events']
dispose(): void
export type PartialCanvas3DProps = {
[K in keyof Canvas3DProps]?: Canvas3DProps[K] extends { name: string, params: any } ? Canvas3DProps[K] : Partial<Canvas3DProps[K]>
}
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()));
export { Canvas3DContext };
namespace Canvas3D {
export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
/** Can be used to create multiple Canvas3D objects */
interface Canvas3DContext {
readonly canvas: HTMLCanvasElement
readonly webgl: WebGLContext
readonly input: InputObserver
readonly passes: Passes
readonly attribs: Readonly<Canvas3DContext.Attribs>
readonly contextLost: BehaviorSubject<now.Timestamp>
readonly contextRestored: BehaviorSubject<now.Timestamp>
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => void
}
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}) {
namespace Canvas3DContext {
const DefaultAttribs = {
/** true by default to avoid issues with Safari (Jan 2021) */
antialias: true,
/** true to support multiple Canvas3D objects with a single context */
preserveDrawingBuffer: true,
pixelScale: 1,
pickScale: 0.25,
enableWboit: true
};
export type Attribs = typeof DefaultAttribs
export function fromCanvas(canvas: HTMLCanvasElement, attribs: Partial<Attribs> = {}): Canvas3DContext {
const a = { ...DefaultAttribs, ...attribs };
const { antialias, preserveDrawingBuffer, pixelScale } = a;
const gl = getGLContext(canvas, {
alpha: true,
antialias: true,
depth: true,
preserveDrawingBuffer: true,
premultipliedAlpha: false,
antialias,
preserveDrawingBuffer,
alpha: true, // the renderer requires an alpha channel
depth: true, // the renderer requires a depth buffer
premultipliedAlpha: true, // the renderer outputs PMA
});
if (gl === null) throw new Error('Could not create a WebGL rendering context');
const input = InputObserver.fromElement(canvas);
const webgl = createContext(gl);
const input = InputObserver.fromElement(canvas, { pixelScale });
const webgl = createContext(gl, { pixelScale });
const passes = new Passes(webgl, attribs);
if (isDebugMode) {
const loseContextExt = gl.getExtension('WEBGL_lose_context');
if (loseContextExt) {
// Hold down shift+ctrl+alt and press any mouse button to call `loseContext`.
// After 1 second `restoreContext` will be called.
canvas.addEventListener('mousedown', e => {
if (webgl.isContextLost) return;
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return;
@@ -148,35 +150,130 @@ namespace Canvas3D {
// https://www.khronos.org/webgl/wiki/HandlingContextLost
canvas.addEventListener('webglcontextlost', e => {
const contextLost = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const handleWebglContextLost = (e: Event) => {
webgl.setContextLost();
e.preventDefault();
if (isDebugMode) console.log('context lost');
}, false);
contextLost.next(now());
};
canvas.addEventListener('webglcontextrestored', () => {
const handlewWebglContextRestored = () => {
if (!webgl.isContextLost) return;
webgl.handleContextRestored();
webgl.handleContextRestored(() => {
passes.draw.reset();
});
if (isDebugMode) console.log('context restored');
}, false);
};
return Canvas3D.create(webgl, input, props);
canvas.addEventListener('webglcontextlost', handleWebglContextLost, false);
canvas.addEventListener('webglcontextrestored', handlewWebglContextRestored, false);
return {
canvas,
webgl,
input,
passes,
attribs: a,
contextLost,
contextRestored: webgl.contextRestored,
dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => {
input.dispose();
canvas.removeEventListener('webglcontextlost', handleWebglContextLost, false);
canvas.removeEventListener('webglcontextrestored', handlewWebglContextRestored, false);
webgl.destroy(options);
}
};
}
}
export function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}): Canvas3D {
const p = { ...DefaultCanvas3DParams, ...props };
export { Canvas3D };
interface Canvas3D {
readonly webgl: WebGLContext,
add(repr: Representation.Any): void
remove(repr: Representation.Any): void
/**
* This function must be called if animate() is not set up so that add/remove actions take place.
*/
commit(isSynchronous?: boolean): void
/**
* Funcion for external "animation" control
* Calls commit.
*/
tick(t: now.Timestamp, options?: { isSynchronous?: boolean, manualDraw?: boolean }): void
update(repr?: Representation.Any, keepBoundingSphere?: boolean): void
clear(): void
syncVisibility(): void
requestDraw(force?: boolean): void
/** Reset the timers, used by "animate" */
resetTime(t: number): void
animate(): void
pause(): void
identify(x: number, y: number): PickData | undefined
mark(loci: Representation.Loci, action: MarkerAction): void
getLoci(pickingId: PickingId | undefined): Representation.Loci
notifyDidDraw: boolean,
readonly didDraw: BehaviorSubject<now.Timestamp>
readonly commited: BehaviorSubject<now.Timestamp>
readonly reprCount: BehaviorSubject<number>
readonly resized: BehaviorSubject<any>
handleResize(): void
/** 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
readonly camera: Camera
readonly boundingSphere: Readonly<Sphere3D>
setProps(props: PartialCanvas3DProps | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void), doNotRequestDraw?: boolean /* = false */): void
getImagePass(props: Partial<ImageProps>): ImagePass
/** Returns a copy of the current Canvas3D instance props */
readonly props: Readonly<Canvas3DProps>
readonly input: InputObserver
readonly stats: RendererStats
readonly interaction: Canvas3dInteractionHelper['events']
dispose(): void
}
const requestAnimationFrame = typeof window !== 'undefined'
? window.requestAnimationFrame
: (f: (time: number) => void) => setImmediate(() => f(Date.now())) as unknown as number;
const cancelAnimationFrame = typeof window !== 'undefined'
? window.cancelAnimationFrame
: (handle: number) => clearImmediate(handle as unknown as NodeJS.Immediate);
namespace Canvas3D {
export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
export interface DragEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, pageStart: Vec2, pageEnd: Vec2 }
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 }
export function create({ webgl, input, passes, attribs }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D {
const p: Canvas3DProps = { ...DefaultCanvas3DParams, ...props };
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>();
const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>();
const reprCount = new BehaviorSubject(0);
const startTime = now();
let startTime = now();
const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const commited = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
const { gl, contextRestored } = webgl;
let width = gl.drawingBufferWidth;
let height = gl.drawingBufferHeight;
let x = 0;
let y = 0;
let width = 128;
let height = 128;
updateViewport();
const scene = Scene.create(webgl);
@@ -185,40 +282,41 @@ namespace Canvas3D {
mode: p.camera.mode,
fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0,
clipFar: p.cameraClipping.far
});
}, { x, y, width, height }, { pixelScale: attribs.pixelScale });
const stereoCamera = new StereoCamera(camera, p.camera.stereo.params);
const controls = TrackballControls.create(input, camera, p.trackball);
const renderer = Renderer.create(webgl, p.renderer);
const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
const handleHelper = new HandleHelper(webgl, p.handle);
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
const helper = new Helper(webgl, scene, p);
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, handleHelper, {
cameraHelper: p.camera.helper
});
const pickPass = new PickPass(webgl, renderer, scene, camera, handleHelper, 0.5);
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing);
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample);
const pickHelper = new PickHelper(webgl, renderer, scene, helper, passes.pick, { x, y, width, height });
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera);
const multiSampleHelper = new MultiSampleHelper(passes.multiSample);
let drawPending = false;
let cameraResetRequested = false;
let nextCameraResetDuration: number | undefined = void 0;
let nextCameraResetSnapshot: Partial<Camera.Snapshot> | undefined = void 0;
let resizeRequested = false;
function getLoci(pickingId: PickingId) {
let notifyDidDraw = true;
function getLoci(pickingId: PickingId | undefined) {
let loci: Loci = EmptyLoci;
let repr: Representation.Any = Representation.Empty;
loci = handleHelper.getLoci(pickingId);
reprRenderObjects.forEach((_, _repr) => {
const _loci = _repr.getLoci(pickingId);
if (!isEmptyLoci(_loci)) {
if (!isEmptyLoci(loci)) {
console.warn('found another loci, this should not happen');
if (pickingId) {
loci = helper.handle.getLoci(pickingId);
reprRenderObjects.forEach((_, _repr) => {
const _loci = _repr.getLoci(pickingId);
if (!isEmptyLoci(_loci)) {
if (!isEmptyLoci(loci)) {
console.warn('found another loci, this should not happen');
}
loci = _loci;
repr = _repr;
}
loci = _loci;
repr = _repr;
}
});
});
}
return { loci, repr };
}
@@ -228,36 +326,50 @@ namespace Canvas3D {
if (repr) {
changed = repr.mark(loci, action);
} else {
changed = handleHelper.mark(loci, action);
changed = helper.handle.mark(loci, action);
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
}
if (changed) {
scene.update(void 0, true);
handleHelper.scene.update(void 0, true);
const prevPickDirty = pickPass.pickDirty;
helper.handle.scene.update(void 0, true);
const prevPickDirty = pickHelper.dirty;
draw(true);
pickPass.pickDirty = prevPickDirty; // marking does not change picking buffers
pickHelper.dirty = prevPickDirty; // marking does not change picking buffers
}
}
function render(force: boolean) {
if (webgl.isContextLost) return false;
let resized = false;
if (resizeRequested) {
handleResize(false);
resizeRequested = false;
resized = true;
}
if (x > gl.drawingBufferWidth || x + width < 0 ||
y > gl.drawingBufferHeight || y + height < 0
) return false;
let didRender = false;
controls.update(currentTime);
Viewport.set(camera.viewport, 0, 0, width, height);
const cameraChanged = camera.update();
multiSample.update(force || cameraChanged, currentTime);
const multiSampleChanged = multiSampleHelper.update(force || cameraChanged, p.multiSample);
if (force || cameraChanged || multiSample.enabled) {
renderer.setViewport(0, 0, width, height);
if (multiSample.enabled) {
multiSample.render(true, p.transparentBackground);
} else {
drawPass.render(!postprocessing.enabled, p.transparentBackground);
if (postprocessing.enabled) postprocessing.render(true);
if (resized || force || cameraChanged || multiSampleChanged) {
let cam: Camera | StereoCamera = camera;
if (p.camera.stereo.name === 'on') {
stereoCamera.update();
cam = stereoCamera;
}
pickPass.pickDirty = true;
if (MultiSamplePass.isEnabled(p.multiSample)) {
multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p);
} else {
passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing);
}
pickHelper.dirty = true;
didRender = true;
}
@@ -265,10 +377,11 @@ namespace Canvas3D {
}
let forceNextDraw = false;
let forceDrawAfterAllCommited = false;
let currentTime = 0;
function draw(force?: boolean) {
if (render(!!force || forceNextDraw)) {
if (render(!!force || forceNextDraw) && notifyDidDraw) {
didDraw.next(now() - startTime as now.Timestamp);
}
forceNextDraw = false;
@@ -281,26 +394,60 @@ namespace Canvas3D {
forceNextDraw = !!force;
}
function animate() {
currentTime = now();
commit();
let animationFrameHandle = 0;
function tick(t: now.Timestamp, options?: { isSynchronous?: boolean, manualDraw?: boolean }) {
currentTime = t;
commit(options?.isSynchronous);
camera.transition.tick(currentTime);
if (options?.manualDraw) {
return;
}
draw(false);
if (!camera.transition.inTransition && !webgl.isContextLost) {
interactionHelper.tick(currentTime);
}
requestAnimationFrame(animate);
}
function identify(x: number, y: number): PickingId | undefined {
return webgl.isContextLost ? undefined : pickPass.identify(x, y);
function _animate() {
tick(now());
animationFrameHandle = requestAnimationFrame(_animate);
}
function resetTime(t: now.Timestamp) {
startTime = t;
controls.start(t);
}
function animate() {
controls.start(now());
if (animationFrameHandle === 0) _animate();
}
function pause() {
cancelAnimationFrame(animationFrameHandle);
animationFrameHandle = 0;
}
function identify(x: number, y: number): PickData | undefined {
const cam = p.camera.stereo.name === 'on' ? stereoCamera : camera;
return webgl.isContextLost ? undefined : pickHelper.identify(x, y, cam);
}
function commit(isSynchronous: boolean = false) {
const allCommited = commitScene(isSynchronous);
// Only reset the camera after the full scene has been commited.
if (allCommited) resolveCameraReset();
if (allCommited) {
resolveCameraReset();
if (forceDrawAfterAllCommited) {
if (helper.debug.isEnabled) helper.debug.update();
draw(true);
forceDrawAfterAllCommited = false;
}
commited.next(now());
}
}
function resolveCameraReset() {
@@ -311,7 +458,7 @@ namespace Canvas3D {
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration;
const focus = camera.getFocus(center, radius);
const snapshot = nextCameraResetSnapshot ? { ...focus, ...nextCameraResetSnapshot } : focus;
camera.setState(snapshot, duration);
camera.setState({ ...snapshot, radiusMax: scene.boundingSphere.radius }, duration);
}
nextCameraResetDuration = void 0;
@@ -338,7 +485,8 @@ namespace Canvas3D {
const b = r.values.boundingSphere.ref.value;
if (!b.radius) continue;
if (!Sphere3D.includes(oldBoundingSphereVisible, b)) return true;
const cameraDist = Vec3.distance(cameraSphere.center, b.center);
if ((cameraDist > cameraSphere.radius || cameraDist > b.radius || b.radius > camera.state.radiusMax) && !Sphere3D.includes(oldBoundingSphereVisible, b)) return true;
if (Sphere3D.overlaps(cameraSphere, b)) cameraSphereOverlapsNone = false;
}
@@ -354,8 +502,8 @@ namespace Canvas3D {
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
if (debugHelper.isEnabled) debugHelper.update();
if (reprCount.value === 0 || shouldResetCamera()) {
if (helper.debug.isEnabled) helper.debug.update();
if (!p.camera.manualReset && (reprCount.value === 0 || shouldResetCamera())) {
cameraResetRequested = true;
}
if (oldBoundingSphereVisible.radius === 0) nextCameraResetDuration = 0;
@@ -372,7 +520,16 @@ namespace Canvas3D {
drawCount: r.values.drawCount.ref.value,
instanceCount: r.values.instanceCount.ref.value,
materialId: r.materialId,
renderItemId: r.id,
})));
console.log(webgl.stats);
const { texture, attribute, elements } = webgl.resources.getByteCounts();
console.log({
texture: `${(texture / 1024 / 1024).toFixed(3)} MiB`,
attribute: `${(attribute / 1024 / 1024).toFixed(3)} MiB`,
elements: `${(elements / 1024 / 1024).toFixed(3)} MiB`,
});
}
function add(repr: Representation.Any) {
@@ -393,6 +550,7 @@ namespace Canvas3D {
reprRenderObjects.set(repr, newRO);
scene.update(repr.renderObjects, false);
forceDrawAfterAllCommited = true;
if (isDebugMode) consoleStats();
}
@@ -403,7 +561,7 @@ namespace Canvas3D {
if (renderObjects) {
renderObjects.forEach(o => scene.remove(o));
reprRenderObjects.delete(repr);
scene.update(repr.renderObjects, false, true);
forceDrawAfterAllCommited = true;
if (isDebugMode) consoleStats();
}
}
@@ -432,7 +590,9 @@ namespace Canvas3D {
return {
camera: {
mode: camera.state.mode,
helper: { ...drawPass.props.cameraHelper }
helper: { ...helper.camera.props },
stereo: { ...p.camera.stereo },
manualReset: !!p.camera.manualReset
},
cameraFog: camera.state.fog > 0
? { name: 'on' as const, params: { intensity: camera.state.fog } }
@@ -440,23 +600,36 @@ namespace Canvas3D {
cameraClipping: { far: camera.state.clipFar, radius },
cameraResetDurationMs: p.cameraResetDurationMs,
transparentBackground: p.transparentBackground,
viewport: p.viewport,
postprocessing: { ...postprocessing.props },
multiSample: { ...multiSample.props },
postprocessing: { ...p.postprocessing },
multiSample: { ...p.multiSample },
renderer: { ...renderer.props },
trackball: { ...controls.props },
debug: { ...debugHelper.props },
handle: { ...handleHelper.props },
debug: { ...helper.debug.props },
handle: { ...helper.handle.props },
};
}
handleResize();
const contextRestoredSub = contextRestored.subscribe(() => {
pickPass.pickDirty = true;
pickHelper.dirty = true;
draw(true);
// Unclear why, but in Chrome with wboit enabled the first `draw` only clears
// the drawingBuffer. Note that in Firefox the drawingBuffer is preserved after
// context loss so it is unclear if it behaves the same.
draw(true);
});
const resized = new BehaviorSubject<any>(0);
function handleResize(draw = true) {
passes.updateSize();
updateViewport();
syncViewport();
if (draw) requestDraw(true);
resized.next(+new Date());
}
return {
webgl,
@@ -470,13 +643,14 @@ namespace Canvas3D {
} else {
scene.update(void 0, !!keepSphere);
}
forceDrawAfterAllCommited = true;
},
clear: () => {
reprUpdatedSubscriptions.forEach(v => v.unsubscribe());
reprUpdatedSubscriptions.clear();
reprRenderObjects.clear();
scene.clear();
debugHelper.clear();
helper.debug.clear();
requestDraw(true);
reprCount.next(reprRenderObjects.size);
},
@@ -487,18 +661,24 @@ namespace Canvas3D {
}
if (scene.syncVisibility()) {
if (debugHelper.isEnabled) debugHelper.update();
if (helper.debug.isEnabled) helper.debug.update();
}
requestDraw(true);
},
// draw,
requestDraw,
tick,
animate,
resetTime,
pause,
identify,
mark,
getLoci,
handleResize,
requestResize: () => {
resizeRequested = true;
},
requestCameraReset: options => {
nextCameraResetDuration = options?.durationMs;
nextCameraResetSnapshot = options?.snapshot;
@@ -506,19 +686,14 @@ namespace Canvas3D {
},
camera,
boundingSphere: scene.boundingSphere,
getPixelData: (variant: GraphicsRenderVariant) => {
switch (variant) {
case 'color': return webgl.getDrawingBufferPixelData();
case 'pickObject': return pickPass.objectPickTarget.getPixelData();
case 'pickInstance': return pickPass.instancePickTarget.getPixelData();
case 'pickGroup': return pickPass.groupPickTarget.getPixelData();
case 'depth': return readTexture(webgl, drawPass.depthTexture) as PixelData;
}
},
get notifyDidDraw() { return notifyDidDraw; },
set notifyDidDraw(v: boolean) { notifyDidDraw = v; },
didDraw,
commited,
reprCount,
setProps: (properties) => {
const props: Partial<Canvas3DProps> = typeof properties === 'function'
resized,
setProps: (properties, doNotRequestDraw = false) => {
const props: PartialCanvas3DProps = typeof properties === 'function'
? produce(getProps(), properties)
: properties;
@@ -526,7 +701,7 @@ namespace Canvas3D {
if (props.camera && props.camera.mode !== undefined && props.camera.mode !== camera.state.mode) {
cameraState.mode = props.camera.mode;
}
if (props.cameraFog !== undefined) {
if (props.cameraFog !== undefined && props.cameraFog.params) {
const newFog = props.cameraFog.name === 'on' ? props.cameraFog.params.intensity : 0;
if (newFog !== camera.state.fog) cameraState.fog = newFog;
}
@@ -544,47 +719,43 @@ namespace Canvas3D {
}
if (Object.keys(cameraState).length > 0) camera.setState(cameraState);
if (props.camera?.helper) drawPass.setProps({ cameraHelper: props.camera.helper });
if (props.camera?.helper) helper.camera.setProps(props.camera.helper);
if (props.camera?.manualReset !== undefined) p.camera.manualReset = props.camera.manualReset;
if (props.camera?.stereo !== undefined) Object.assign(p.camera.stereo, props.camera.stereo);
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs;
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground;
if (props.viewport !== undefined) {
const doNotUpdate = p.viewport === props.viewport ||
(p.viewport.name === props.viewport.name && shallowEqual(p.viewport.params, props.viewport.params));
if (props.postprocessing) postprocessing.setProps(props.postprocessing);
if (props.multiSample) multiSample.setProps(props.multiSample);
if (!doNotUpdate) {
p.viewport = props.viewport;
updateViewport();
syncViewport();
}
}
if (props.postprocessing) Object.assign(p.postprocessing, props.postprocessing);
if (props.multiSample) Object.assign(p.multiSample, props.multiSample);
if (props.renderer) renderer.setProps(props.renderer);
if (props.trackball) controls.setProps(props.trackball);
if (props.debug) debugHelper.setProps(props.debug);
if (props.handle) handleHelper.setProps(props.handle);
if (props.debug) helper.debug.setProps(props.debug);
if (props.handle) helper.handle.setProps(props.handle);
requestDraw(true);
if (cameraState.mode === 'orthographic') {
p.camera.stereo.name = 'off';
}
if (!doNotRequestDraw) {
requestDraw(true);
}
},
getImagePass: (props: Partial<ImageProps> = {}) => {
return new ImagePass(webgl, renderer, scene, camera, debugHelper, handleHelper, props);
return new ImagePass(webgl, renderer, scene, camera, helper, passes.draw.wboitEnabled, props);
},
get props() {
const radius = scene.boundingSphere.radius > 0
? 100 - Math.round((camera.transition.target.radius / scene.boundingSphere.radius) * 100)
: 0;
return {
camera: {
mode: camera.state.mode,
helper: { ...drawPass.props.cameraHelper }
},
cameraFog: camera.state.fog > 0
? { name: 'on' as const, params: { intensity: camera.state.fog } }
: { name: 'off' as const, params: {} },
cameraClipping: { far: camera.state.clipFar, radius },
cameraResetDurationMs: p.cameraResetDurationMs,
transparentBackground: p.transparentBackground,
postprocessing: { ...postprocessing.props },
multiSample: { ...multiSample.props },
renderer: { ...renderer.props },
trackball: { ...controls.props },
debug: { ...debugHelper.props },
handle: { ...handleHelper.props },
};
return getProps();
},
get input() {
return input;
@@ -599,28 +770,32 @@ namespace Canvas3D {
contextRestoredSub.unsubscribe();
scene.clear();
debugHelper.clear();
input.dispose();
helper.debug.clear();
controls.dispose();
renderer.dispose();
interactionHelper.dispose();
}
};
function handleResize() {
width = gl.drawingBufferWidth;
height = gl.drawingBufferHeight;
function updateViewport() {
if (p.viewport.name === 'canvas') {
x = 0;
y = 0;
width = gl.drawingBufferWidth;
height = gl.drawingBufferHeight;
} else {
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;
}
}
renderer.setViewport(0, 0, width, height);
Viewport.set(camera.viewport, 0, 0, width, height);
Viewport.set(controls.viewport, 0, 0, width, height);
drawPass.setSize(width, height);
pickPass.setSize(width, height);
postprocessing.setSize(width, height);
multiSample.setSize(width, height);
requestDraw(true);
function syncViewport() {
pickHelper.setViewport(x, y, width, height);
renderer.setViewport(x, y, width, height);
Viewport.set(camera.viewport, x, y, width, height);
Viewport.set(controls.viewport, x, y, width, height);
}
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { Camera } from '../camera';
import { Viewport } from '../camera/util';
export namespace ObjectControls {
function mouseOnScreen(out: Vec2, page: Vec2, viewport: Viewport) {
return Vec2.set(
out,
(page[0] - viewport.x) / viewport.width,
(page[1] - viewport.y) / viewport.height
);
}
const panMouseChange = Vec2();
const panObjUp = Vec3();
const panOffset = Vec3();
const eye = Vec3();
const panStart = Vec2();
const panEnd = Vec2();
const target = Vec3();
/**
* Get vector for movement in camera projection plane:
* `pageStart` and `pageEnd` are 2d window coordinates;
* `ref` defines the plane depth, if not given `camera.target` is used
*/
export function panDirection(out: Vec3, pageStart: Vec2, pageEnd: Vec2, camera: Camera, ref?: Vec3) {
mouseOnScreen(panStart, pageStart, camera.viewport);
mouseOnScreen(panEnd, pageEnd, camera.viewport);
Vec2.sub(panMouseChange, Vec2.copy(panMouseChange, panEnd), panStart);
Vec3.sub(eye, camera.position, camera.target);
if (!ref || camera.state.mode === 'orthographic') Vec3.copy(target, camera.target);
else Vec3.projectPointOnVector(target, ref, eye, camera.position);
const dist = Vec3.distance(camera.position, target);
const height = 2 * Math.tan(camera.state.fov / 2) * dist;
const zoom = camera.viewport.height / height;
panMouseChange[0] *= (1 / zoom) * camera.viewport.width * camera.pixelRatio;
panMouseChange[1] *= (1 / zoom) * camera.viewport.height * camera.pixelRatio;
Vec3.cross(panOffset, Vec3.copy(panOffset, eye), camera.up);
Vec3.setMagnitude(panOffset, panOffset, panMouseChange[0]);
Vec3.setMagnitude(panObjUp, camera.up, panMouseChange[1]);
Vec3.add(panOffset, panOffset, panObjUp);
return Vec3.negate(out, panOffset);
}
}

View File

@@ -10,7 +10,7 @@
import { Quat, Vec2, Vec3, EPSILON } from '../../mol-math/linear-algebra';
import { Viewport } from '../camera/util';
import InputObserver, { DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys } from '../../mol-util/input/input-observer';
import { InputObserver, DragInput, WheelInput, PinchInput, ButtonsType, ModifiersKeys } from '../../mol-util/input/input-observer';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Camera } from '../camera';
import { absMax } from '../../mol-math/misc';
@@ -36,9 +36,9 @@ export const DefaultTrackballBindings = {
export const TrackballControlsParams = {
noScroll: PD.Boolean(true, { isHidden: true }),
rotateSpeed: PD.Numeric(3.0, { min: 0.1, max: 10, step: 0.1 }),
zoomSpeed: PD.Numeric(6.0, { min: 0.1, max: 10, step: 0.1 }),
panSpeed: PD.Numeric(0.8, { min: 0.1, max: 5, step: 0.1 }),
rotateSpeed: PD.Numeric(5.0, { min: 1, max: 10, step: 1 }),
zoomSpeed: PD.Numeric(7.0, { min: 1, max: 15, step: 1 }),
panSpeed: PD.Numeric(1.0, { min: 0.1, max: 5, step: 0.1 }),
spin: PD.Boolean(false, { description: 'Spin the 3D scene around the x-axis in view space' }),
spinSpeed: PD.Numeric(1, { min: -20, max: 20, step: 1 }),
@@ -60,6 +60,7 @@ interface TrackballControls {
readonly props: Readonly<TrackballControlsProps>
setProps: (props: Partial<TrackballControlsProps>) => void
start: (t: number) => void
update: (t: number) => void
reset: () => void
dispose: () => void
@@ -68,7 +69,7 @@ namespace TrackballControls {
export function create(input: InputObserver, camera: Camera, props: Partial<TrackballControlsProps> = {}): TrackballControls {
const p = { ...PD.getDefaultValues(TrackballControlsParams), ...props };
const viewport = Viewport();
const viewport = Viewport.clone(camera.viewport);
let disposed = false;
@@ -137,7 +138,8 @@ namespace TrackballControls {
const dy = _rotCurr[1] - _rotPrev[1];
Vec3.set(rotMoveDir, dx, dy, 0);
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed;
const aspectRatio = input.width / input.height;
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed * input.pixelRatio * aspectRatio;
if (angle) {
Vec3.sub(_eye, camera.position, camera.target);
@@ -227,7 +229,9 @@ namespace TrackballControls {
Vec2.sub(panMouseChange, Vec2.copy(panMouseChange, _panEnd), _panStart);
if (Vec2.squaredMagnitude(panMouseChange)) {
Vec2.scale(panMouseChange, panMouseChange, Vec3.magnitude(_eye) * p.panSpeed);
const factor = input.pixelRatio * p.panSpeed;
panMouseChange[0] *= (1 / camera.zoom) * camera.viewport.width * factor;
panMouseChange[1] *= (1 / camera.zoom) * camera.viewport.height * factor;
Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), camera.up);
Vec3.setMagnitude(panOffset, panOffset, panMouseChange[0]);
@@ -269,11 +273,22 @@ namespace TrackballControls {
}
}
function outsideViewport(x: number, y: number) {
x *= input.pixelRatio;
y *= input.pixelRatio;
return (
x > viewport.x + viewport.width ||
input.height - y > viewport.y + viewport.height ||
x < viewport.x ||
input.height - y < viewport.y
);
}
let lastUpdated = -1;
/** Update the object's position, direction and up vectors */
function update(t: number) {
if (lastUpdated === t) return;
if (p.spin) spin(t - lastUpdated);
if (p.spin && lastUpdated > 0) spin(t - lastUpdated);
Vec3.sub(_eye, camera.position, camera.target);
@@ -305,7 +320,12 @@ namespace TrackballControls {
// listeners
function onDrag({ pageX, pageY, buttons, modifiers, isStart }: DragInput) {
function onDrag({ x, y, pageX, pageY, buttons, modifiers, isStart }: DragInput) {
const isOutside = outsideViewport(x, y);
if (isStart && isOutside) return;
if (!isStart && !_isInteracting) return;
_isInteracting = true;
const dragRotate = Binding.match(p.bindings.dragRotate, buttons, modifiers);
@@ -356,7 +376,9 @@ namespace TrackballControls {
_isInteracting = false;
}
function onWheel({ dx, dy, dz, buttons, modifiers }: WheelInput) {
function onWheel({ x, y, dx, dy, dz, buttons, modifiers }: WheelInput) {
if (outsideViewport(x, y)) return;
const delta = absMax(dx, dy, dz);
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
_zoomEnd[1] += delta * 0.0001;
@@ -385,13 +407,17 @@ namespace TrackballControls {
const _spinSpeed = Vec2.create(0.005, 0);
function spin(deltaT: number) {
if (p.spinSpeed === 0) return;
const frameSpeed = (p.spinSpeed || 0) / 1000;
_spinSpeed[0] = 60 * Math.min(Math.abs(deltaT), 1000 / 8) / 1000 * frameSpeed;
if (!_isInteracting) Vec2.add(_rotCurr, _rotPrev, _spinSpeed);
}
// force an update at start
update(0);
function start(t: number) {
lastUpdated = -1;
update(t);
}
return {
viewport,
@@ -401,6 +427,7 @@ namespace TrackballControls {
Object.assign(p, props);
},
start,
update,
reset,
dispose

View File

@@ -9,7 +9,7 @@ import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import Scene from '../../mol-gl/scene';
import { Scene } from '../../mol-gl/scene';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { Sphere3D } from '../../mol-math/geometry';
import { Color } from '../../mol-util/color';
@@ -67,6 +67,7 @@ export class BoundingSphereHelper {
uInstanceCount: ro.values.uInstanceCount,
instanceCount: ro.values.instanceCount,
aInstance: ro.values.aInstance,
hasReflection: ro.values.hasReflection,
});
if (newInstanceData) this.instancesData.set(ro, newInstanceData);
});
@@ -131,7 +132,7 @@ function updateBoundingSphereData(scene: Scene, boundingSphere: Sphere3D, data:
const mesh = createBoundingSphereMesh(boundingSphere, data && data.mesh);
const renderObject = data ? data.renderObject : createBoundingSphereRenderObject(mesh, color, materialId, transform);
if (data) {
ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(mesh));
ValueCell.updateIfChanged(renderObject.values.drawCount, Geometry.getDrawCount(mesh));
} else {
scene.add(renderObject);
}
@@ -159,5 +160,5 @@ const instanceMaterialId = getNextMaterialId();
function createBoundingSphereRenderObject(mesh: Mesh, color: Color, materialId: number, transform?: TransformData) {
const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform);
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, opaque: false, writeDepth: false }, materialId);
return createRenderObject('mesh', values, { disposed: false, visible: true, alphaFactor: 1, pickable: false, colorOnly: false, opaque: false, writeDepth: false, noClip: false }, materialId);
}

View File

@@ -5,8 +5,8 @@
*/
import { WebGLContext } from '../../mol-gl/webgl/context';
import Scene from '../../mol-gl/scene';
import { Camera } from '../camera';
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';
@@ -15,7 +15,6 @@ import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { ColorNames } from '../../mol-util/color/names';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { Viewport } from '../camera/util';
import { ValueCell } from '../../mol-util';
import { Sphere3D } from '../../mol-math/geometry';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import produce from 'immer';
@@ -71,6 +70,7 @@ export class CameraHelper {
this.scene.clear();
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio };
this.renderObject = createAxesRenderObject(params);
this.renderObject.state.noClip = true;
this.scene.add(this.renderObject);
this.scene.commit();
@@ -87,43 +87,47 @@ export class CameraHelper {
return this.props.axes.name === 'on';
}
update(camera: Camera) {
update(camera: ICamera) {
if (!this.renderObject) return;
updateCamera(this.camera, camera.viewport);
const m = this.renderObject.values.aTransform.ref.value as unknown as Mat4;
Mat4.extractRotation(m, camera.view);
updateCamera(this.camera, camera.viewport, camera.viewOffset);
Mat4.extractRotation(this.scene.view, camera.view);
const r = this.renderObject.values.boundingSphere.ref.value.radius;
Mat4.setTranslation(m, Vec3.create(
Mat4.setTranslation(this.scene.view, Vec3.create(
-camera.viewport.width / 2 + r,
-camera.viewport.height / 2 + r,
0
));
ValueCell.update(this.renderObject.values.aTransform, this.renderObject.values.aTransform.ref.value);
this.scene.update([this.renderObject], true);
}
}
function updateCamera(camera: Camera, viewport: Viewport) {
function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.ViewOffset) {
const { near, far } = camera;
const fullLeft = -(viewport.width - viewport.x) / 2;
const fullRight = (viewport.width - viewport.x) / 2;
const fullTop = (viewport.height - viewport.y) / 2;
const fullBottom = -(viewport.height - viewport.y) / 2;
const fullLeft = -viewport.width / 2;
const fullRight = viewport.width / 2;
const fullTop = viewport.height / 2;
const fullBottom = -viewport.height / 2;
const dx = (fullRight - fullLeft) / 2;
const dy = (fullTop - fullBottom) / 2;
const cx = (fullRight + fullLeft) / 2;
const cy = (fullTop + fullBottom) / 2;
const left = cx - dx;
const right = cx + dx;
const top = cy + dy;
const bottom = cy - dy;
let left = cx - dx;
let right = cx + dx;
let top = cy + dy;
let bottom = cy - dy;
if (viewOffset.enabled) {
const scaleW = (fullRight - fullLeft) / viewOffset.width;
const scaleH = (fullTop - fullBottom) / viewOffset.height;
left += scaleW * viewOffset.offsetX;
right = left + scaleW * viewOffset.width;
top -= scaleH * viewOffset.offsetY;
bottom = top - scaleH * viewOffset.height;
}
Mat4.ortho(camera.projection, left, right, top, bottom, near, far);
}

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