Compare commits

...

217 Commits

Author SHA1 Message Date
Alexander Rose
4a7413a8d9 0.6.2 2020-04-06 12:43:22 -07:00
Alexander Rose
593b34bfe3 added binding to add loci to current focus 2020-04-06 12:40:24 -07:00
Alexander Rose
691b1721b2 tweaked select mode icon 2020-04-06 11:27:09 -07:00
David Sehnal
b8133b4300 Canvas3dInteractionHelper.maxFps = 30
- makes the interaction feel more responsive
2020-04-06 18:42:10 +02:00
David Sehnal
0c459b98db mol-plugin: added PluginContext.state.updateBehavior 2020-04-06 17:33:22 +02:00
David Sehnal
060725642f mol-plugin: state tree UI fix + remove global current object 2020-04-06 16:45:28 +02:00
David Sehnal
eaf28bf016 fix PDBeStructureQualityReport.autoAttach 2020-04-06 16:23:11 +02:00
David Sehnal
ed6084c40b mol-plugin-ui: optimize state tree rendering 2020-04-06 16:18:23 +02:00
David Sehnal
e56063b065 mol-plugin: selection mode 2020-04-06 15:08:00 +02:00
Alexander Rose
f8ddfb1638 Merge branch 'master' of https://github.com/molstar/molstar 2020-04-05 20:59:49 -07:00
Alexander Rose
900f2e1f76 wip, selection viewport controls
- refactored selection controls as components
- initial css, buggy
2020-04-05 20:59:17 -07:00
David Sehnal
00c9e05a65 mol-plugin-ui: use componentDidUpdated instead of getDerivedStateFromProps where appropriate 2020-04-06 03:20:54 +02:00
Alexander Rose
ecaab51315 support update of props.initiallyCollapsed in CollapsableControls 2020-04-05 17:17:48 -07:00
Alexander Rose
2a4d45714c added npm script and webpack config for viewer debugging
- generates sourcemaps that work with vscode/chrome debugging
2020-04-05 14:18:17 -07:00
Alexander Rose
6e13ef5cbc use webpack.DefinePlugin.runtimeValue for __VERSION__ 2020-04-03 18:54:04 -07:00
Alexander Rose
f48e2ab238 0.6.1 2020-04-03 17:52:16 -07:00
Alexander Rose
0e34d976c0 use version from package.json for plugin 2020-04-03 17:50:50 -07:00
Alexander Rose
2d6cd4c6da package updates 2020-04-03 17:42:59 -07:00
Alexander Rose
4a4b3ef5b4 fix whitespace linting issues 2020-04-03 17:29:59 -07:00
Alexander Rose
67e6167b55 removed orientation from measurements for now
- too limited and confusing
2020-04-03 17:26:32 -07:00
Alexander Rose
79b33bfbd8 selection and focus tweaks
- support highlight for current selection UI
2020-04-03 17:25:36 -07:00
Alexander Rose
f5fc96ee3b more label tweaks
- show entity in locilabel-provider for single-entity loci
- better category handling in focus ui
2020-04-03 16:49:41 -07:00
Alexander Rose
691571ec39 better handling of labels from multiple structures
- intruduced group hash
2020-04-03 15:48:05 -07:00
Alexander Rose
f2b20a646e cellpack tweaks 2020-04-03 12:37:35 -07:00
Alexander Rose
bef9ff86d2 remove console.log 2020-04-03 12:20:07 -07:00
Alexander Rose
ff3da5f2db refactored cellpack loader
- properly handle coloring
- support hiv_lipids loading
2020-04-03 12:09:35 -07:00
Alexander Rose
129727d5d1 calculateTransformBoundingSphere: only use extrema if there are not too many transforms 2020-04-03 12:06:51 -07:00
Alexander Rose
6e26b4d5f9 fix SequenceWrapper type determination
- failed in case of Partitioned chains
2020-04-03 12:06:08 -07:00
Alexander Rose
371bb11ce8 0.6.0 2020-04-02 21:11:10 -07:00
Alexander Rose
556196ae3f wip, cellpack model loader 2020-04-02 21:09:36 -07:00
Alexander Rose
f5667411d7 check if carbs are applicable without calculating them 2020-04-02 20:56:36 -07:00
Alexander Rose
774f419a53 add traceOnly support to element-sphere visual 2020-04-02 20:55:59 -07:00
Alexander Rose
9fa5d40306 improved labels in measurement representations 2020-04-02 18:48:38 -07:00
Alexander Rose
ba89d5ec1e simple PD.Mat4 2020-04-02 17:47:32 -07:00
Alexander Rose
b7fa577d9b improved validation-report and assembly-symmetry presets 2020-04-02 17:21:05 -07:00
David Sehnal
949425d14d mol-plugin-ui: fixed crash caused by invalid state during updates 2020-04-03 00:40:48 +02:00
Alexander Rose
2cb0d63750 added more descriptions and titles
- added description prop to action-item
- use for presets and selection queries
2020-04-02 13:16:14 -07:00
Alexander Rose
038aa47578 update assembly-symmetry UI when repr is added 2020-04-02 12:01:42 -07:00
Alexander Rose
c833598c26 improved computing of covalent bonds with hydrogen
- added more X-H pair thresholds
- removed maxCovalentHydrogenBondingLength param
2020-04-02 11:20:24 -07:00
Alexander Rose
489c52361a measurement label tweaks 2020-04-02 10:18:01 -07:00
David Sehnal
9edf41a2f2 model-server: requestTimeout config option 2020-04-02 16:08:05 +02:00
David Sehnal
5bcca99f60 mol-plugin: support request body in Download(Blob) transforms 2020-04-02 14:33:29 +02:00
David Sehnal
1b0a310fc7 mol-state & volume streaming updates
- error cells are no longer considered valid update roots
- volume streaming now better handles initialization errors
2020-04-02 14:27:55 +02:00
David Sehnal
e3e7fa3040 model-server: support tar.gz output in local mode 2020-04-02 13:52:42 +02:00
David Sehnal
5297dd6f11 model-server: query many limit 2020-04-02 12:49:39 +02:00
David Sehnal
c557e93255 model-server: do not apply content-encoing: gzip to tar.gz resposes 2020-04-02 03:03:52 +02:00
Alexander Rose
ad5f0c987a 0.6.0-dev.11 2020-04-01 17:32:01 -07:00
Alexander Rose
30aa6d035d tweaked volume streaming labels 2020-04-01 17:29:15 -07:00
Alexander Rose
b198289fc4 Merge branch 'master' of https://github.com/molstar/molstar 2020-04-01 17:25:25 -07:00
Alexander Rose
51e45085f1 package updates 2020-04-01 17:22:47 -07:00
Alexander Rose
97ebb2dccc fixed labels for MultiChain units 2020-04-01 17:07:53 -07:00
David Sehnal
29d28bd3f4 UI tweaks 2020-04-02 01:40:23 +02:00
David Sehnal
09c5c040f2 Merge branch 'master' of https://github.com/molstar/molstar 2020-04-02 01:26:39 +02:00
David Sehnal
6e60a2f9dc model-server: query-many result as tar.gz 2020-04-02 01:26:30 +02:00
Alexander Rose
5d36108113 added title tooltip to CollapsableControls 2020-04-01 16:16:37 -07:00
Alexander Rose
f4a423ebe5 show mssage when enabling volume streaming fails 2020-04-01 16:13:21 -07:00
Alexander Rose
9942fbe549 added EmdbHeaderServer plugin config item 2020-04-01 14:46:56 -07:00
Alexander Rose
dbde7521e4 improved volume streaming type determination 2020-04-01 14:34:38 -07:00
David Sehnal
6eee3e8368 model-server: refactored result writer 2020-04-01 22:25:25 +02:00
Alexander Rose
cc0ccd7830 made cameraHelper part of drawPass, screenshot improvements 2020-04-01 12:41:33 -07:00
Alexander Rose
7ad25249a9 removed async renderObject creation in cameraHelper 2020-04-01 12:40:29 -07:00
Alexander Rose
687c7d54ff Shape: extracted helper methods 2020-04-01 12:39:16 -07:00
Alexander Rose
d6fff1ffdf Merge pull request #27 from ValentaTomas/ValentaTomas-readme-typo-fix
Fix README typos
2020-04-01 10:54:42 -07:00
David Sehnal
20a8b8892e colored icons for structure tools 2020-04-01 18:50:37 +02:00
David Sehnal
5c09ecc98d StateTree facelift 2020-04-01 18:04:22 +02:00
ValentaTomas
19539a2c9d Fix README typos 2020-04-01 14:54:53 +02:00
David Sehnal
5c093c7f22 mol-canvas-3d: camera transition tweak 2020-04-01 14:30:51 +02:00
Alexander Rose
c43ed90607 improve volume streaming method determination
- take content_type of related entry into account
2020-03-31 22:26:05 -07:00
Alexander Rose
a85242d9c5 improved focus entry computation
- handle branched
- handle Unit.Trait.MultiChain
2020-03-31 18:43:54 -07:00
Alexander Rose
722d2a9c6c include selected values in sequence panel select titles 2020-03-31 17:31:56 -07:00
Alexander Rose
f7d65ff52c avoid breaking long residue names in sequence panel 2020-03-31 16:52:24 -07:00
Alexander Rose
7bee2be12d added traceOnly param to gaussian-surface
- avoids creating trace structure selection with slow labeling, stats and bundle params
2020-03-31 15:31:41 -07:00
Alexander Rose
326649d4f5 fix broken getLoci in Representation.createMulti 2020-03-31 15:28:56 -07:00
David Sehnal
6aba5df55d Merge branch 'master' of https://github.com/molstar/molstar 2020-03-31 21:08:34 +02:00
David Sehnal
57bbcf9425 fixed issue that prevented delayed isBusy and enabled it again
- lesson learned: do not raise events inside property getters
2020-03-31 21:08:15 +02:00
Alexander Rose
08ba34d607 0.6.0-dev.10 2020-03-31 11:15:57 -07:00
David Sehnal
4c122227a7 Merge branch 'master' of https://github.com/molstar/molstar 2020-03-31 20:10:31 +02:00
David Sehnal
e14afb4dad reverted delayed isBusy
- made CSS/code tweaks in the process
2020-03-31 20:10:22 +02:00
Alexander Rose
472def49f6 0.6.0-dev.9 2020-03-31 10:17:54 -07:00
Alexander Rose
90549893e3 added pdbx_entity_instance_feature to mmcif schema 2020-03-31 09:49:18 -07:00
Alexander Rose
73a8e45202 added missing pdbx_branch_scheme fields to mmcif schema 2020-03-31 09:26:29 -07:00
David Sehnal
35df55cb4f CSS & structure type auto-apply tweaks 2020-03-31 17:19:03 +02:00
David Sehnal
c2028d20a8 mol-plugin: do not disable UI for fast updates 2020-03-31 16:35:45 +02:00
David Sehnal
eaffdc6a98 mol-plugin-ui: do not auto-apply symmetry structure types 2020-03-31 15:52:56 +02:00
David Sehnal
0b1e6100a9 mol-canvas-3d: fix camera transition when adding new representations 2020-03-31 15:25:00 +02:00
David Sehnal
2168905c11 StructureHierarchyManager.updateCurrent fix 2020-03-31 14:57:49 +02:00
David Sehnal
f955e6a299 mol-plugin-state: changing structure "type" recreates the subtree
* different assemblies can have different default components
* i.e. assembly 1 doesn't have carbs and asm 2 does
2020-03-31 14:55:59 +02:00
David Sehnal
98f3981e12 Fix CSS issues 2020-03-31 14:12:45 +02:00
Alexander Rose
82f94d20ea wip, cellpack file input 2020-03-31 02:04:18 -07:00
Alexander Rose
fbc6d47117 wip, cellpack loader update 2020-03-31 01:19:07 -07:00
Alexander Rose
6a2e4cf813 handle orphan models and structures in StructureHierarchyManager 2020-03-31 01:18:41 -07:00
Alexander Rose
4aabef7683 package updates 2020-03-31 00:48:40 -07:00
Alexander Rose
74ae91ee1b support initiallyCollapsed in CustomStructureControls 2020-03-31 00:24:17 -07:00
Alexander Rose
8b62766474 add data-color to ButtonProps (for color swatches) 2020-03-30 23:03:20 -07:00
Alexander Rose
2995504916 made StructureMeasurementsControls seperate panel again 2020-03-30 22:56:01 -07:00
Alexander Rose
63f6848d26 0.6.0-dev.8 2020-03-30 22:35:58 -07:00
Alexander Rose
988e429693 Merge branch 'master' of https://github.com/molstar/molstar 2020-03-30 19:27:35 -07:00
David Sehnal
ba1c6ef046 CameraTransitionManager.apply reverse change 2020-03-31 03:49:20 +02:00
David Sehnal
7ceff92a4e fix structure focus camera animation
+ more UI code tweaks
2020-03-31 03:45:48 +02:00
Alexander Rose
1f968b2836 Merge branch 'master' of https://github.com/molstar/molstar 2020-03-30 18:37:14 -07:00
Alexander Rose
d97d7e3b14 AssemblySymmetryControls fixes 2020-03-30 18:37:03 -07:00
Alexander Rose
5c4c4811e4 added .props() to CustomProperty.Provider 2020-03-30 18:35:30 -07:00
David Sehnal
f2a6e63a20 mol-plugin-ui: general refactoring & code improvements 2020-03-31 02:35:58 +02:00
David Sehnal
1aace4a26f mol-plugin-ui: added Button common ctrl 2020-03-31 01:43:04 +02:00
David Sehnal
b1c140d23e IconButton flex prop 2020-03-31 00:42:54 +02:00
David Sehnal
ee16212c31 Merge branch 'master' of https://github.com/molstar/molstar 2020-03-31 00:14:29 +02:00
David Sehnal
f6d232b1c5 mol-plugin-ui: removed unused CSS 2020-03-31 00:14:13 +02:00
Alexander Rose
c8a002933e take PluginContext.spec into account for SimpleSettingsMapping.layout 2020-03-30 14:55:28 -07:00
Alexander Rose
4757ca9913 fix clashes variable in validationReportPreset 2020-03-30 14:30:00 -07:00
Alexander Rose
5264c75e37 Merge branch 'master' of https://github.com/molstar/molstar 2020-03-30 12:29:23 -07:00
Alexander Rose
032bf44863 package updates (npm audit fix) 2020-03-30 12:02:28 -07:00
David Sehnal
2f4f5e43f3 StructureFocusManager.behaviors.current
replaces events.changed
2020-03-30 20:57:39 +02:00
Alexander Rose
7b1edcadf6 fous fixes and improvements 2020-03-30 11:53:55 -07:00
Alexander Rose
f0f74d182d updated schemas 2020-03-30 11:36:37 -07:00
Alexander Rose
59142adbbc updated packages 2020-03-30 11:26:09 -07:00
Alexander Rose
2fda8c5db1 tweaked assembly symmetry ui 2020-03-30 11:21:07 -07:00
Alexander Rose
7b5efa3e42 LociLabelManager: fold and count identical labels 2020-03-30 10:05:14 -07:00
Alexander Rose
d784d202bd volume streaming: ensure current focus is shown upon registering 2020-03-30 09:52:48 -07:00
Alexander Rose
8833474a43 structure size and quality threshold improvements 2020-03-30 09:51:04 -07:00
David Sehnal
57cbb2f8b6 StructureSourceControls: Fix crash when selecting "Symmetry (assembly)" 2020-03-28 18:01:56 +01:00
David Sehnal
b6112a914f StateTransformer.dispose
- use in StructureRepresentation3D to release custom props
2020-03-28 17:45:16 +01:00
David Sehnal
2008f8538c Various tweaks and fixes
- allow to hide expand button in Viewport
- principal axes fix for single element
- fixes to preset syncing
- CSS fixes
- picking level grouping
2020-03-28 16:46:47 +01:00
David Sehnal
09fba43a1c mol-plugin-state: representation presets "sync" instead of recreate 2020-03-28 14:00:01 +01:00
David Sehnal
b76c3613f9 Focus/Selection UI tweaks 2020-03-28 12:38:39 +01:00
Alexander Rose
cf4ddcb587 label tweaks 2020-03-27 23:42:03 -07:00
Alexander Rose
696106f48b check distance for IntraUnitClashes 2020-03-27 21:54:30 -07:00
Alexander Rose
fb286cd9cf take pixelRatio into account for camera-helper 2020-03-27 21:44:02 -07:00
Alexander Rose
5121bd700e changed assembly-symmetry preset to StructureRepresentationPresetProvider 2020-03-27 21:25:18 -07:00
Alexander Rose
6173520ad0 add validation-report preset 2020-03-27 20:09:17 -07:00
Alexander Rose
a7189232dd wip, StructureFocus controls and manager 2020-03-27 20:00:02 -07:00
Alexander Rose
4a96b45b04 added StructureSelectionQueryRegistry
- register hasClashes, isAccessible, isBuried from their custom props
2020-03-27 19:51:19 -07:00
David Sehnal
20ac549dd6 Plugin UI tweaks
- component focus ensures visibility
- moved measurements to selection section
2020-03-27 18:46:27 +01:00
David Sehnal
38be00c0b7 fix StateTreeNode display bug 2020-03-27 17:10:44 +01:00
David Sehnal
0a9bdc8cf6 model-server: query-many (wip)
+ some UI changes
2020-03-27 16:01:29 +01:00
David Sehnal
0b4318280a cosmetic UI changes 2020-03-27 12:29:51 +01:00
Alexander Rose
b1da60e1c0 wip, focus manager, focus repr refactoring 2020-03-26 18:32:21 -07:00
David Sehnal
2cc5987f9e cosmetic UI changes 2020-03-26 15:52:23 +01:00
David Sehnal
745415a1d8 rename first-model trajectory preset to default 2020-03-26 14:09:50 +01:00
David Sehnal
c80658f368 hide reprLabel in StructureComponentGroup 2020-03-26 13:15:27 +01:00
David Sehnal
033675a417 mol-plugin-ui: GenericEntry.showOnFocus option 2020-03-26 13:09:09 +01:00
David Sehnal
3dd57e9dc8 PD.ColorList refactoring 2020-03-26 12:49:31 +01:00
Alexander Rose
1e3daa6c98 fixed color-theme param getters for palette.scale.list.predefined 2020-03-25 19:44:57 -07:00
Alexander Rose
8855f51cfc Merge branch 'master' of https://github.com/molstar/molstar 2020-03-25 19:33:13 -07:00
Alexander Rose
50d8debb2b refactored assembly-symmetry
- provide a single symmetry-index, story data in AssemblySymmetryDataProvider
2020-03-25 19:32:30 -07:00
Alexander Rose
e682eb78b0 various ui tweaks 2020-03-25 19:31:39 -07:00
Alexander Rose
bd2bbf3e2d plugin: added customStructureControls renamed genericRepresentationControls 2020-03-25 19:30:46 -07:00
Alexander Rose
f38f040aea updated DefaultCellPackBaseUrl 2020-03-25 19:25:02 -07:00
David Sehnal
f912b2d802 proteopedia wrapper bugfix 2020-03-25 21:20:51 +01:00
David Sehnal
0ad03e6a0b custom interpolation list for color palette 2020-03-25 20:46:14 +01:00
Alexander Rose
160d8c529e Merge branch 'master' of https://github.com/molstar/molstar 2020-03-25 10:42:43 -07:00
David Sehnal
28edfd810c mol-model: molstar_atom_site_operator_mapping parsing 2020-03-25 18:29:10 +01:00
Alexander Rose
7b931cfb66 wip, assembly-symmetry preset 2020-03-25 10:28:55 -07:00
David Sehnal
5575c61577 mmcif export updates- rename chains with asm/symm/ncs operators- molstar_atom_site_operator_mapping category 2020-03-25 17:32:15 +01:00
Alexander Rose
8f93dce105 tweaked Structure.hasHighSymmetry 2020-03-25 08:48:26 -07:00
David Sehnal
0eb3d7226a SymmetryOperator.create refactor, added SymmetryOperator.suffix 2020-03-25 15:56:20 +01:00
Alexander Rose
a9533b666c added TrajectoryHierarchyBuilder 2020-03-24 19:23:13 -07:00
David Sehnal
6d67b4db56 update camera axes default params to make it less distracting 2020-03-24 11:46:38 +01:00
David Sehnal
1b5eff6454 ui improvements
- state tree update/apply transform
- focus label, customize radius
- StructureComponentGroup repr label
2020-03-24 11:41:30 +01:00
Alexander Rose
83fb28cc9d wip, generic representation entry 2020-03-23 19:31:07 -07:00
Alexander Rose
c39ffc0b0e fix Infinity in projection matrix
- ensure near and far are not identical
2020-03-23 19:30:18 -07:00
Alexander Rose
23892cfbdd fixed genericTarget in hierarchy-state tagMap 2020-03-23 19:05:45 -07:00
Alexander Rose
3bdabc444d camera helper param tweaks 2020-03-23 15:14:16 -07:00
Alexander Rose
c233be4467 set isoValue param as essential 2020-03-23 15:08:54 -07:00
Alexander Rose
8468f33803 Merge branch 'master' of https://github.com/molstar/molstar 2020-03-23 14:56:55 -07:00
Alexander Rose
0e77369fdb added camera helper showing orientation axes 2020-03-23 14:55:54 -07:00
Alexander Rose
9fcc8e7977 param handling tweaks 2020-03-23 12:16:57 -07:00
David Sehnal
538371ced8 Remove StructureBuilderTags.Component
+ remove some unused code
2020-03-23 19:11:00 +01:00
Alexander Rose
380887bd22 improved trackball.focusCamera
- radius change is now relative to current radius and not fixed
2020-03-23 11:03:35 -07:00
Alexander Rose
17b4b1cb86 added visible bounding sphere to debug helper 2020-03-23 11:01:33 -07:00
Alexander Rose
3e7c358c07 improved unitcell bounding sphere (added extrema) 2020-03-23 11:00:51 -07:00
Alexander Rose
05b592a173 removed unused runTask canvas3d argument 2020-03-23 11:00:12 -07:00
Alexander Rose
e1d0515fae unified font and svg icons 2020-03-23 10:48:02 -07:00
David Sehnal
d4d3b9645e treat "current interaction" as a "structure component" 2020-03-23 18:14:01 +01:00
David Sehnal
d12d99dcfa remove unused StructureBuilderTags 2020-03-23 17:57:28 +01:00
David Sehnal
174324d21c added 'reds-darker' color list 2020-03-23 15:24:49 +01:00
David Sehnal
26156a5982 ProteopediaWrapper: snapshots 2020-03-23 15:18:40 +01:00
David Sehnal
af5ddf6950 StructureHierarchy now relies less on tags 2020-03-23 15:03:38 +01:00
David Sehnal
2ef35e5fb9 CustomElementProperty label fix 2020-03-23 13:43:52 +01:00
David Sehnal
513bfeaae7 StructureHierarchyManager sync on demand
+ PluginComponent updates
2020-03-23 11:01:37 +01:00
David Sehnal
7311e6f484 mol-plugin-state: StructureCoordinateSystem decorator (wip)
+ SymmetryOperator.assembly now optional
2020-03-22 22:08:37 +01:00
David Sehnal
90ecedcae8 mol-state: StateTransformer.Definition.isDecorator
- moved from StateTransform.isDecorator
- when using StateBuilder.apply:
  * decorators are always "inserted"
  * if a node has a decorator, the transformer is applied to the decorator instead (recursive)
2020-03-22 15:09:33 +01:00
David Sehnal
352a20ac48 camera manager focus multiple loci/spheres
+ show Unitcell control for multiple structures
+ misc fixes
2020-03-21 13:07:35 +01:00
David Sehnal
b1d8c5f6ea mol-plugin-ui: left panel tweak 2020-03-21 12:16:45 +01:00
Alexander Rose
6497be9e52 0.6.0-dev.7 2020-03-20 19:14:45 -07:00
Alexander Rose
40a8cee8e5 improved scene bounding sphere (reset) handling 2020-03-20 19:13:26 -07:00
Alexander Rose
112cfcac90 0.6.0-dev.6 2020-03-20 14:21:38 -07:00
David Sehnal
8016fcbd54 mol-plugin-state: use 'deposited' as default value for structure if no assembly is present 2020-03-20 21:37:23 +01:00
David Sehnal
6de97f1d03 Merge branch 'master' of https://github.com/molstar/molstar 2020-03-20 21:22:54 +01:00
David Sehnal
204075bbe0 updateStructureComponent bugfix 2020-03-20 21:22:40 +01:00
David Sehnal
eb448bce37 fix volume streaming with custom props enabled 2020-03-20 20:59:23 +01:00
Alexander Rose
3d09b5cb67 use shallow cloning in PD.merge 2020-03-20 12:53:28 -07:00
Alexander Rose
35d06040f7 update debugHelper when visibility changes 2020-03-20 12:22:07 -07:00
Alexander Rose
0868e81944 improved download density action
- combined pdb providers
2020-03-20 12:08:44 -07:00
Alexander Rose
7efbeb7d0f improved download structure action
- updated pdb-dev url
- added archival pdbe
- combined pdb providers
2020-03-20 11:12:52 -07:00
David Sehnal
815f61b550 StructureHierarchyManagerState: auto-select newly added refs 2020-03-20 16:17:47 +01:00
David Sehnal
c3a90ab499 mol-canvas3d: shouldResetCamera check 2020-03-20 13:35:20 +01:00
David Sehnal
321126afa2 show current selection desc in StructureComponentControls header 2020-03-20 11:52:34 +01:00
Alexander Rose
9ad4a9c3c9 recreate vao to fix attempted binding of deleted attributes 2020-03-19 17:29:04 -07:00
Alexander Rose
b12f7c7ce4 add .name property to ShaderCode 2020-03-19 17:26:43 -07:00
Alexander Rose
6d7d3c0794 unitcell label tweaks 2020-03-19 14:24:32 -07:00
Alexander Rose
92b988a8d5 added PD.merge, always use props from structure.root for custom properties 2020-03-19 14:07:48 -07:00
Alexander Rose
18952ee2bd deepClone fix 2020-03-19 14:03:39 -07:00
David Sehnal
bff07888f9 StructureBuilder.tryCreateComponent* refactoring 2020-03-19 20:32:50 +01:00
David Sehnal
8e6b0b220a do not auto-attach interactions in component manager 2020-03-19 20:12:29 +01:00
David Sehnal
74acb0d078 mol-model-state: fix custom props transform bug 2020-03-19 19:14:11 +01:00
Alexander Rose
d4727eea02 removed console.log statement 2020-03-19 10:07:48 -07:00
Alexander Rose
76d14eba0c fix broken units-representation visual state update 2020-03-19 10:01:57 -07:00
David Sehnal
90d05d9260 fix bug in structure repr interaction 2020-03-19 17:43:11 +01:00
David Sehnal
23b24bbb6c custom props are now included by default
+ structure parent helper now takes decorators into account
+ ui & api tweaks
2020-03-19 15:33:37 +01:00
David Sehnal
91bc6f07c5 mol-plugin-state: added TrajectoryHierarchy presets
refactored representation presets
2020-03-19 12:56:47 +01:00
David Sehnal
9b2181667d mol-plugin-state: do not create empty unit cells 2020-03-19 10:59:35 +01:00
Alexander Rose
40347e3e46 0.6.0-dev.5 2020-03-18 21:16:44 -07:00
Alexander Rose
ed277f6e16 Merge remote-tracking branch 'origin/master' into objects 2020-03-18 21:12:52 -07:00
David Sehnal
af1fb7041e removed apps/model-server-query 2020-03-18 18:28:19 +01:00
David Sehnal
e2eb1bf223 model-server: config tweak 2020-03-18 13:49:54 +01:00
David Sehnal
5a64a6f1a3 mol-model: fixed a bug in secondary structure export 2020-03-18 13:45:43 +01:00
David Sehnal
88aa9303d7 model-server: fixed data_source bug, allow fetching source data over http(s) 2020-03-18 13:20:08 +01:00
374 changed files with 11708 additions and 18470 deletions

View File

@@ -5,7 +5,7 @@
# Mol*
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that will serve as basis for the next-generation data delivery and analysis tools for macromolecular structure data. This is a collaboration between PDBe and RCSB PDB teams and the development will be open source and available to anyone who wants to use it for developing visualisation tools for macromolecular structure data available from [PDB](https://www.wwpdb.org/) and other institutions.
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that will serve as a basis for the next-generation data delivery and analysis tools for macromolecular structure data. This is a collaboration between PDBe and RCSB PDB teams and the development will be open-source and available to anyone who wants to use it for developing visualization tools for macromolecular structure data available from [PDB](https://www.wwpdb.org/) and other institutions.
This particular project is the implementation of this technology (still under development).
@@ -16,26 +16,26 @@ This particular project is the implementation of this technology (still under de
The core of Mol* currently consists of these modules (see under `src/`):
- `mol-task` Computation abstraction with progress tracking and cancellation support.
- `mol-data` Collections (integer based sets, interface to columns/tables, etc.)
- `mol-data` Collections (integer-based sets, interface to columns/tables, etc.)
- `mol-math` Math related (loosely) algorithms and data structures.
- `mol-io` Parsing library. Each format is parsed into an interface that corresponds to the data stored by it. Support for common coordinate, experimental/map, and annotation data formats.
- `mol-model` Data structures and algorithms (such as querying) for representing molecular data (including coordinate, experimental/map, and annotation data).
- `mol-model-formats` Data format parsers for `mol-model`.
- `mol-model-props` Common "custom properties".
- `mol-script` A scriting language for creating representations/scenes and querying (includes the [MolQL query language](https://molql.github.io)).
- `mol-script` A scripting language for creating representations/scenes and querying (includes the [MolQL query language](https://molql.github.io)).
- `mol-geo` Creating (molecular) geometries.
- `mol-theme` Theming for structure, volume and shape representations.
- `mol-repr` Molecular representations for structures, volumes and shapes.
- `mol-gl` A wrapper around WebGL.
- `mol-canvas3d` A low level 3d view component. Uses `mol-geo` to generate geometries.
- `mol-canvas3d` A low-level 3d view component. Uses `mol-geo` to generate geometries.
- `mol-state` State representation tree with state saving and automatic updates.
- `mol-app` Components for builduing UIs.
- `mol-app` Components for building UIs.
- `mol-plugin` Allow to define modular Mol* plugin instances utilizing `mol-state` and `mol-canvas3d`.
- `mol-plugin-state` State transformations, builders, and managers.
- `mol-plugin-ui` React based user interface for the Mol* plugin. Some components of the UI are usable outside the main plugin and can be integrated to 3rd party solutions.
- `mol-plugin-ui` React-based user interface for the Mol* plugin. Some components of the UI are usable outside the main plugin and can be integrated into 3rd party solutions.
- `mol-util` Useful things that do not fit elsewhere.
Moreover, the project contains the imlementation of `servers`, including
Moreover, the project contains the implementation of `servers`, including
- `servers/model` A tool for accessing coordinate and annotation data of molecular structures.
- `servers/volume` A tool for accessing volumetric experimental data related to molecular structures.
@@ -121,16 +121,16 @@ To see all available commands, use ``node build/model-server/preprocess -h``.
## Development
### Intallation
### Installation
If node complains about a missine acorn peer dependency, run the following commands
If node complains about a missing acorn peer dependency, run the following commands
npm update acorn --depth 20
npm dedupe
### Editor
To get syntax highlighting for shader and graphql files add the following to Visual Code's settings files and make sure relevant extanesions are installed in the editor.
To get syntax highlighting for shader and graphql files add the following to Visual Code's settings files and make sure relevant extensions are installed in the editor.
"files.associations": {
"*.glsl.ts": "glsl",
@@ -142,7 +142,7 @@ To get syntax highlighting for shader and graphql files add the following to Vis
## Publish
### Prerelease
npm version prerelease # asumes the current version ends with '-dev.X'
npm version prerelease # assumes the current version ends with '-dev.X'
npm publish --tag next
### Release
@@ -164,4 +164,4 @@ Continually develop this prototype project. As individual modules become stable,
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/)
* [CEITEC](https://www.ceitec.eu/)

4303
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.6.0-dev.4",
"version": "0.6.2",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -19,10 +19,12 @@
"build-webpack": "webpack --mode production",
"watch": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack\"",
"watch-viewer": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer\"",
"watch-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-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/ --watch",
"watch-webpack": "webpack -w --mode development --display errors-only --info-verbosity verbose",
"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/model/server.js",
"model-server-watch": "nodemon --watch lib lib/servers/model/server.js",
@@ -82,8 +84,8 @@
"@graphql-codegen/typescript-graphql-request": "^1.13.1",
"@graphql-codegen/typescript-operations": "^1.13.1",
"@types/cors": "^2.8.6",
"@typescript-eslint/eslint-plugin": "^2.24.0",
"@typescript-eslint/parser": "^2.24.0",
"@typescript-eslint/eslint-plugin": "^2.26.0",
"@typescript-eslint/parser": "^2.26.0",
"benchmark": "^2.1.4",
"circular-dependency-plugin": "^5.2.0",
"concurrently": "^5.1.0",
@@ -92,9 +94,9 @@
"eslint": "^6.8.0",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.0.0",
"fs-extra": "^8.1.0",
"fs-extra": "^9.0.0",
"http-server": "^0.12.1",
"jest": "^25.1.0",
"jest": "^25.2.7",
"jest-raw-loader": "^1.0.1",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.13.1",
@@ -104,21 +106,21 @@
"sass-loader": "^8.0.2",
"simple-git": "^1.132.0",
"style-loader": "^1.1.3",
"ts-jest": "^25.2.1",
"ts-jest": "^25.3.1",
"typescript": "^3.8.3",
"webpack": "^4.42.0",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11"
},
"dependencies": {
"@types/argparse": "^1.0.38",
"@types/benchmark": "^1.0.31",
"@types/compression": "1.7.0",
"@types/express": "^4.17.3",
"@types/jest": "^25.1.4",
"@types/node": "^13.9.2",
"@types/express": "^4.17.4",
"@types/jest": "^25.2.1",
"@types/node": "^13.11.0",
"@types/node-fetch": "^2.5.5",
"@types/react": "^16.9.23",
"@types/react-dom": "^16.9.5",
"@types/react": "^16.9.32",
"@types/react-dom": "^16.9.6",
"@types/swagger-ui-dist": "3.0.5",
"argparse": "^1.0.10",
"body-parser": "^1.19.0",
@@ -126,12 +128,12 @@
"cors": "^2.8.5",
"express": "^4.17.1",
"graphql": "^14.6.0",
"immer": "^6.0.2",
"immer": "^6.0.3",
"immutable": "^3.8.2",
"node-fetch": "^2.6.0",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"rxjs": "^6.5.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"rxjs": "^6.5.5",
"swagger-ui-dist": "^3.25.0",
"tslib": "^1.11.1",
"util.promisify": "^1.0.1",

View File

@@ -58,12 +58,14 @@ export namespace StateHelper {
export function identityTransform(b: StateBuilder.To<PSO.Molecule.Structure>, m: Mat4) {
return b.apply(StateTransforms.Model.TransformStructureConformation,
{ axis: Vec3.create(1, 0, 0), angle: 0, translation: Vec3.zero() },
{ transform: { name: 'components', params: { axis: Vec3.create(1, 0, 0), angle: 0, translation: Vec3.zero() } } },
{ tags: 'transform' });
}
export function transform(b: StateBuilder.To<PSO.Molecule.Structure>, matrix: Mat4) {
return b.apply(StateTransforms.Model.TransformStructureConformationByMatrix, { matrix }, { tags: 'transform' });
return b.apply(StateTransforms.Model.TransformStructureConformation, {
transform: { name: 'matrix', params: matrix }
}, { tags: 'transform' });
}
export function assemble(b: StateBuilder.To<PSO.Molecule.Model>, id?: string) {

View File

@@ -144,7 +144,7 @@ async function createBonds() {
const comp_id: string[] = []
const atom_id_1: string[] = []
const atom_id_2: string[] = []
const value_order: typeof mmCIF_chemCompBond_schema['value_order']['T'][] = []
const value_order: typeof mmCIF_chemCompBond_schema['value_order']['T'][] = []
const pdbx_aromatic_flag: typeof mmCIF_chemCompBond_schema['pdbx_aromatic_flag']['T'][] = []
const pdbx_stereo_config: typeof mmCIF_chemCompBond_schema['pdbx_stereo_config']['T'][] = []
const molstar_protonation_variant: string[] = []

View File

@@ -1,12 +0,0 @@
<!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* ModelServer Query Builder</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="./index.js"></script>
</body>
</html>

View File

@@ -1,134 +0,0 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import * as Rx from 'rxjs'
import { QueryDefinition, QueryList } from '../../servers/model/server/api'
import './index.html'
interface State {
query: Rx.BehaviorSubject<QueryDefinition>,
id: Rx.BehaviorSubject<string>,
params: Rx.BehaviorSubject<any>,
isBinary: Rx.BehaviorSubject<boolean>,
models: Rx.BehaviorSubject<number[]>,
url: Rx.Subject<string>
}
class Root extends React.Component<{ state: State }, { }> {
render() {
return <div>
<div>
Query: <QuerySelect state={this.props.state} />
</div>
<div>
ID: <input type='text' onChange={t => this.props.state.id.next(t.currentTarget.value)} />
</div>
<div>
Params:<br/>
<QueryParams state={this.props.state} />
</div>
<div>
Model numbers (empty for all): <ModelNums state={this.props.state} />
</div>
<div>
<input type='checkbox' onChange={t => this.props.state.isBinary.next(!!t.currentTarget.checked)} /> Binary
</div>
<div>
Query string:
<QueryUrl state={this.props.state} />
</div>
</div>
}
}
class QuerySelect extends React.Component<{ state: State }> {
render() {
return <select onChange={s => this.props.state.query.next(QueryList[+s.currentTarget.value].definition)}>
{ QueryList.map((q, i) => <option value={i} key={i} selected={i === 1}>{q.definition.niceName}</option>) }
</select>
}
}
class QueryParams extends React.Component<{ state: State }, { prms: string }> {
state = { prms: '' };
parseParams(str: string) {
this.setState({ prms: str });
try {
const params = JSON.parse(str);
this.props.state.params.next(params);
} catch {
this.props.state.params.next({});
}
}
componentDidMount() {
this.props.state.query.subscribe(q => this.setState({ prms: formatParams(q) }))
}
render() {
return <textarea style={{height: '300px'}} value={this.state.prms} cols={80} onChange={t => this.parseParams(t.currentTarget.value)} />;
}
}
class QueryUrl extends React.Component<{ state: State }, { queryString: string }> {
state = { queryString: '' };
componentDidMount() {
this.props.state.url.subscribe(url => this.setState({ queryString: url }))
}
render() {
return <input type='text' value={this.state.queryString} style={{ width: '800px' }} />
}
}
class ModelNums extends React.Component<{ state: State }> {
render() {
return <input type='text' defaultValue='1' style={{ width: '300px' }} onChange={t =>
this.props.state.models.next(t.currentTarget.value.split(',')
.map(v => v.trim())
.filter(v => !!v)
.map(v => +v)
)} />
}
}
const state: State = {
query: new Rx.BehaviorSubject(QueryList[1].definition),
id: new Rx.BehaviorSubject('1cbs'),
params: new Rx.BehaviorSubject({ }),
isBinary: new Rx.BehaviorSubject<boolean>(false),
models: new Rx.BehaviorSubject<number[]>([]),
url: new Rx.Subject()
}
function formatParams(def: QueryDefinition) {
const prms = Object.create(null);
for (const p of def.jsonParams) {
prms[p.name] = p.exampleValues ? p.exampleValues[0] : void 0;
}
return JSON.stringify(prms, void 0, 2);
}
function formatUrl() {
const json = JSON.stringify({
name: state.query.value.name,
id: state.id.value,
modelNums: state.models.value.length ? state.models.value : void 0,
binary: state.isBinary.value,
params: state.params.value
});
state.url.next(encodeURIComponent(json));
}
Rx.merge(state.query, state.id, state.params, state.isBinary, state.models).subscribe(s => formatUrl());
ReactDOM.render(<Root state={state} />, document.getElementById('app'));

View File

@@ -19,8 +19,9 @@ function paramInfo(param: PD.Any, offset: number): string {
case 'conditioned': return getParams(param.conditionParams, offset);
case 'multi-select': return `Array of ${oToS(param.options)}`;
case 'color': return 'Color as 0xrrggbb';
case 'color-list': return `One of ${oToS(param.options)}`;
case 'color-list': return `A list of colors as 0xrrggbb`;
case 'vec3': return `3D vector [x, y, z]`;
case 'mat4': return `4x4 transformation matrix`;
case 'file': return `JavaScript File Handle`;
case 'file-list': return `JavaScript FileList Handle`;
case 'select': return `One of ${oToS(param.options)}`;
@@ -39,7 +40,7 @@ function paramInfo(param: PD.Any, offset: number): string {
}
}
function oToS(options: readonly (readonly [string, string] | readonly [string, string, string])[]) {
function oToS(options: readonly (readonly [string, string] | readonly [string, string, string | undefined])[]) {
return options.map(o => `'${o[0]}'`).join(', ');
}

View File

@@ -0,0 +1,96 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ThemeDataContext } from '../../../../mol-theme/theme'
import { ParamDefinition as PD } from '../../../../mol-util/param-definition'
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 { Location } from '../../../../mol-model/location';
import { CellPackInfoProvider } from './property'
import { distinctColors } from '../../../../mol-util/color/distinct'
import { Hcl } from '../../../../mol-util/color/spaces/hcl'
const DefaultColor = Color(0xCCCCCC)
const Description = 'Gives every model in a CellPack packing a unique color similar to other models in the packing.'
export const CellPackColorThemeParams = {}
export type CellPackColorThemeParams = typeof CellPackColorThemeParams
export function getCellPackColorThemeParams(ctx: ThemeDataContext) {
return CellPackColorThemeParams // TODO return copy
}
export function CellPackColorTheme(ctx: ThemeDataContext, props: PD.Values<CellPackColorThemeParams>): ColorTheme<CellPackColorThemeParams> {
let color: LocationColor
let legend: ScaleLegend | TableLegend | undefined
const info = ctx.structure && CellPackInfoProvider.get(ctx.structure).value
if (ctx.structure && info) {
const colors = distinctColors(info.packingsCount)
const hcl = Hcl.fromColor(Hcl(), colors[info.packingIndex])
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number]
const { models } = ctx.structure.root
let size = 0;
for (const m of models) size = Math.max(size, m.trajectoryInfo.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}`,
}
}})
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))
}
color = (location: Location): Color => {
if (StructureElement.Location.is(location)) {
return modelColor.get(location.unit.model.trajectoryInfo.index)!
} else if (Bond.isLocation(location)) {
return modelColor.get(location.aUnit.model.trajectoryInfo.index)!
}
return DefaultColor
}
} else {
color = () => DefaultColor
}
return {
factory: CellPackColorTheme,
granularity: 'instance',
color,
props,
description: Description,
legend
}
}
export const CellPackColorThemeProvider: ColorTheme.Provider<CellPackColorThemeParams, 'cellpack'> = {
name: 'cellpack',
label: 'CellPack',
category: ColorTheme.Category.Chain,
factory: CellPackColorTheme,
getParams: getCellPackColorThemeParams,
defaultValues: PD.getDefaultValues(CellPackColorThemeParams),
isApplicable: (ctx: ThemeDataContext) => {
return (
!!ctx.structure && ctx.structure.elementCount > 0 &&
ctx.structure.models[0].trajectoryInfo.size > 1 &&
!!CellPackInfoProvider.get(ctx.structure).value
)
}
}

View File

@@ -4,32 +4,31 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { StateAction } from '../../../../mol-state';
import { StateAction, StateBuilder, StateTransformer, State } from '../../../../mol-state';
import { PluginContext } from '../../../../mol-plugin/context';
import { PluginStateObject as PSO } from '../../../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { Ingredient, CellPacking, Cell } from './data';
import { Ingredient, CellPacking } from './data';
import { getFromPdb, getFromCellPackDB } from './util';
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } 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';
import { SymmetryOperator } from '../../../../mol-math/geometry';
import { Task } from '../../../../mol-task';
import { Task, RuntimeContext } from '../../../../mol-task';
import { StateTransforms } from '../../../../mol-plugin-state/transforms';
import { distinctColors } from '../../../../mol-util/color/distinct';
import { ModelIndexColorThemeProvider } from '../../../../mol-theme/color/model-index';
import { Hcl } from '../../../../mol-util/color/spaces/hcl';
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl } from './state';
import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
import { getMatFromResamplePoints } from './curve';
import { compile } from '../../../../mol-script/runtime/query/compiler';
import { UniformColorThemeProvider } from '../../../../mol-theme/color/uniform';
import { CifCategory, CifField } from '../../../../mol-io/reader/cif';
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
import { Column } from '../../../../mol-data/db';
import { createModels } from '../../../../mol-model-formats/structure/basic/parser';
import { createStructureRepresentationParams } from '../../../../mol-plugin-state/helpers/structure-representation-params';
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
import { AjaxTask } from '../../../../mol-util/data-source';
import { CellPackInfoProvider } from './property';
import { CellPackColorThemeProvider } from './color';
function getCellPackModelUrl(fileName: string, baseUrl: string) {
return `${baseUrl}/results/${fileName}`
@@ -111,7 +110,7 @@ function getAssembly(transforms: Mat4[], structure: Structure) {
for (let i = 0, il = transforms.length; i < il; ++i) {
const id = `${i + 1}`
const op = SymmetryOperator.create(id, transforms[i], { id, operList: [ id ] })
const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [ id ] } })
for (const unit of units) {
builder.addWithOperator(unit, op)
}
@@ -270,200 +269,134 @@ export function createStructureFromCellPack(packing: CellPacking, baseUrl: strin
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId)
}
offsetInvariantId += maxInvariantId
offsetInvariantId += maxInvariantId + 1
}
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`)
const s = builder.getStructure()
for( let i = 0, il = s.models.length; i < il; ++i) {
const { trajectoryInfo } = s.models[i]
trajectoryInfo.size = il
trajectoryInfo.index = i
}
return s
})
}
const RepresentationOptions = PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'ellipsoid'] as const)
type RepresentationName = (typeof RepresentationOptions)[0][0]
export const LoadCellPackModel = StateAction.build({
display: { name: 'Load CellPack Model' },
params: {
id: PD.Select('influenza_model1.json', [
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
['influenza_model1.json', 'influenza_model1'],
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
['curveTest', 'Curve Test'],
] as const),
baseUrl: PD.Text(DefaultCellPackBaseUrl),
preset: PD.Group({
traceOnly: PD.Boolean(false),
representation: PD.Select('spacefill', RepresentationOptions)
}, { isExpanded: true })
},
from: PSO.Root
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
const url = getCellPackModelUrl(params.id, params.baseUrl)
const root = state.build().toRoot();
let cellPackBuilder: any
if (params.id === 'curveTest') {
const url = `${params.baseUrl}/extras/rna_allpoints.json`
const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
const { points } = await (new Response(data)).json() as { points: number[] }
const curve0: Vec3[] = []
for (let j = 0, jl = Math.min(points.length, 3 * 100); j < jl; j += 3) {
curve0.push(Vec3.fromArray(Vec3(), points, j))
}
const cell: Cell = {
recipe: { setupfile: '', paths: [], version: '', name: 'Curve Test' },
compartments: {
'CurveCompartment': {
interior: {
ingredients: {
'CurveIngredient': {
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
results: [],
name: 'RNA',
nbCurve: 1,
curve0
}
}
}
}
}
}
cellPackBuilder = root
.apply(StateTransforms.Data.ImportJson, { data: cell }, { state: { isGhost: true } })
.apply(ParseCellPack)
} else {
cellPackBuilder = root
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.id }, { state: { isGhost: true } })
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
.apply(ParseCellPack)
}
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(taskCtx)
const { packings } = cellPackObject.data
const tree = state.build().to(cellPackBuilder.ref);
const isHiv = (
params.id === 'BloodHIV1.0_mixed_fixed_nc1.cpr' ||
params.id === 'HIV-1_0.1.6-8_mixed_radii_pdb.cpr'
)
if (isHiv) {
for (let i = 0, il = packings.length; i < il; ++i) {
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
const url = `${params.baseUrl}/extras/rna_allpoints.json`
const data = await ctx.fetch({ url, type: 'string' }).runInContext(taskCtx);
const { points } = await (new Response(data)).json() as { points: number[] }
const curve0: Vec3[] = []
for (let j = 0, jl = points.length; j < jl; j += 3) {
curve0.push(Vec3.fromArray(Vec3(), points, j))
}
packings[i].ingredients['RNA'] = {
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
results: [],
name: 'RNA',
nbCurve: 1,
curve0
}
}
}
}
const colors = distinctColors(packings.length)
async function handleHivRna(ctx: { runtime: RuntimeContext, fetch: AjaxTask }, packings: CellPacking[], baseUrl: string) {
for (let i = 0, il = packings.length; i < il; ++i) {
const hcl = Hcl.fromColor(Hcl(), colors[i])
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number]
const p = { packing: i, baseUrl: params.baseUrl }
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
const url = `${baseUrl}/extras/rna_allpoints.json`
const data = await ctx.fetch({ url, type: 'string' }).runInContext(ctx.runtime);
const { points } = await (new Response(data)).json() as { points: number[] }
let cellpackTree = tree.apply(StructureFromCellpack, p)
if (params.preset.traceOnly) {
const expression = MS.struct.generator.atomGroups({
'atom-test': MS.core.logic.or([
MS.core.rel.eq([MS.ammp('label_atom_id'), 'CA']),
MS.core.rel.eq([MS.ammp('label_atom_id'), 'P'])
])
})
cellpackTree = cellpackTree.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression }, { state: { isGhost: true } }) as any
}
cellpackTree
.apply(StateTransforms.Representation.StructureRepresentation3D,
createStructureRepresentationParams(ctx, Structure.Empty, {
...getReprParams(ctx, params.preset),
...getColorParams(hue)
})
)
}
if (isHiv) {
const url = `${params.baseUrl}/membranes/hiv_lipids.bcif`
tree.apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } })
.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.StructureFromModel, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Misc.CreateGroup, { label: 'HIV1_envelope_Membrane' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
createStructureRepresentationParams(ctx, Structure.Empty, {
...getReprParams(ctx, params.preset),
color: UniformColorThemeProvider
})
)
}
console.time('cellpack')
await state.updateTree(tree).runInContext(taskCtx);
console.timeEnd('cellpack')
}));
function getReprParams(ctx: PluginContext, params: { representation: RepresentationName, traceOnly: boolean }) {
const { representation, traceOnly } = params
switch (representation) {
case 'spacefill':
return traceOnly
? {
type: ctx.representation.structure.registry.get('spacefill'),
typeParams: { sizeFactor: 2, ignoreHydrogens: true }
} : {
type: ctx.representation.structure.registry.get('spacefill'),
typeParams: { ignoreHydrogens: true }
}
case 'gaussian-surface':
return {
type: ctx.representation.structure.registry.get('gaussian-surface'),
typeParams: {
quality: 'custom', resolution: 10, radiusOffset: 2,
alpha: 1.0, flatShaded: false, doubleSided: false,
ignoreHydrogens: true
}
const curve0: Vec3[] = []
for (let j = 0, jl = points.length; j < jl; j += 3) {
curve0.push(Vec3.fromArray(Vec3(), points, j))
}
case 'point':
return { type: ctx.representation.structure.registry.get('point') }
case 'ellipsoid':
return { type: ctx.representation.structure.registry.get('orientation') }
packings[i].ingredients['RNA'] = {
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
results: [],
name: 'RNA',
nbCurve: 1,
curve0
}
}
}
}
function getColorParams(hue: [number, number]): any {
return {
color: ModelIndexColorThemeProvider,
colorParams: {
palette: {
name: 'generate',
params: {
hue, chroma: [30, 80], luminance: [15, 85],
clusteringStepCount: 50, minSampleCount: 800,
maxCount: 75
}
}
}
async function loadHivMembrane(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
const url = `${params.baseUrl}/membranes/hiv_lipids.bcif`
const membraneBuilder = state.build().toRoot()
.apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } })
.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
.apply(StateTransforms.Model.StructureFromModel)
await state.updateTree(membraneBuilder).runInContext(runtime)
const membraneParams = {
representation: params.preset.representation,
}
}
const membrane = state.build().to(membraneBuilder.ref)
await plugin.updateDataState(membrane, { revertOnError: true });
await CellpackMembranePreset.apply(membrane.selector, membraneParams, plugin)
}
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>
if (params.source.name === 'id') {
const url = getCellPackModelUrl(params.source.params, params.baseUrl)
cellPackJson = state.build().toRoot()
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } })
} else {
const file = params.source.params
cellPackJson = state.build().toRoot()
.apply(StateTransforms.Data.ReadFile, { file, isBinary: false, label: file.name }, { state: { isGhost: true } })
}
const cellPackBuilder = cellPackJson
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
.apply(ParseCellPack)
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime)
const { packings } = cellPackObject.data
await handleHivRna({ runtime, fetch: plugin.fetch }, packings, params.baseUrl)
for (let i = 0, il = packings.length; i < il; ++i) {
const p = { packing: i, baseUrl: params.baseUrl }
const packing = state.build().to(cellPackBuilder.ref).apply(StructureFromCellpack, p)
await plugin.updateDataState(packing, { revertOnError: true });
const structure = packing.selector.obj?.data
if (structure) {
await CellPackInfoProvider.attach({ fetch: plugin.fetch, runtime }, structure, {
info: { packingsCount: packings.length, packingIndex: i }
})
}
const packingParams = {
traceOnly: params.preset.traceOnly,
representation: params.preset.representation,
}
await CellpackPackingPreset.apply(packing.selector, packingParams, plugin)
}
}
const LoadCellPackModelParams = {
source: PD.MappedStatic('id', {
'id': PD.Select('influenza_model1.json', [
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
['hiv_lipids.bcif', 'hiv_lipids'],
['influenza_model1.json', 'influenza_model1'],
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
] as const),
'file': PD.File({ accept: 'id' }),
}, { options: [['id', 'Id'], ['file', 'File']] }),
baseUrl: PD.Text(DefaultCellPackBaseUrl),
preset: PD.Group({
traceOnly: PD.Boolean(false),
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'ellipsoid']))
}, { isExpanded: true })
}
type LoadCellPackModelParams = PD.Values<typeof LoadCellPackModelParams>
export const LoadCellPackModel = StateAction.build({
display: { name: 'Load CellPack', description: 'Open or download a model' },
params: LoadCellPackModelParams,
from: PSO.Root
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
if (!ctx.representation.structure.themes.colorThemeRegistry.has(CellPackColorThemeProvider)) {
ctx.representation.structure.themes.colorThemeRegistry.add(CellPackColorThemeProvider)
}
if (params.source.name === 'id' && params.source.params === 'hiv_lipids.bcif') {
await loadHivMembrane(ctx, taskCtx, state, params)
} else {
await loadPackings(ctx, taskCtx, state, params)
}
}));

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { StateObjectRef } from '../../../../mol-state';
import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../../../mol-plugin-state/builder/structure/representation-preset';
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
import { ColorNames } from '../../../../mol-util/color/names';
import { CellPackColorThemeProvider } from './color';
export const CellpackPackingPresetParams = {
traceOnly: PD.Boolean(true),
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'])),
}
export type CellpackPackingPresetParams = PD.ValuesFor<typeof CellpackPackingPresetParams>
export const CellpackPackingPreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-cellpack-packing',
display: { name: 'CellPack Packing' },
params: () => CellpackPackingPresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const reprProps = {
ignoreHydrogens: true,
traceOnly: params.traceOnly
};
const components = {
polymer: await presetStaticComponent(plugin, structureCell, 'polymer')
};
if (params.representation === 'gaussian-surface') {
Object.assign(reprProps, {
quality: 'custom', resolution: 10, radiusOffset: 2, doubleSided: false
})
} else if (params.representation === 'spacefill' && params.traceOnly) {
Object.assign(reprProps, { sizeFactor: 2 })
}
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
const color = CellPackColorThemeProvider.name
const representations = {
polymer: builder.buildRepresentation<any>(update, components.polymer, { type: params.representation, typeParams: { ...typeParams, ...reprProps }, color }, { tag: 'polymer' })
};
await plugin.updateDataState(update, { revertOnError: true });
return { components, representations };
}
});
//
export const CellpackMembranePresetParams = {
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'])),
}
export type CellpackMembranePresetParams = PD.ValuesFor<typeof CellpackMembranePresetParams>
export const CellpackMembranePreset = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-cellpack-membrane',
display: { name: 'CellPack Membrane' },
params: () => CellpackMembranePresetParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const reprProps = {
ignoreHydrogens: true,
};
const components = {
membrane: await presetStaticComponent(plugin, structureCell, 'all', { label: 'Membrane' })
};
if (params.representation === 'gaussian-surface') {
Object.assign(reprProps, {
quality: 'custom', resolution: 10, radiusOffset: 2, doubleSided: false
})
}
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
const representations = {
membrane: builder.buildRepresentation(update, components.membrane, { type: 'gaussian-surface', typeParams: { ...typeParams, ...reprProps }, color: 'uniform', colorParams: { value: ColorNames.lightgrey } }, { tag: 'all' })
};
await plugin.updateDataState(update, { revertOnError: true });
return { components, representations };
}
});

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { CustomStructureProperty } from '../../../../mol-model-props/common/custom-structure-property'
import { Structure, CustomPropertyDescriptor } from '../../../../mol-model/structure'
import { CustomProperty } from '../../../../mol-model-props/common/custom-property'
import { ParamDefinition as PD } from '../../../../mol-util/param-definition'
export type CellPackInfoValue = {
packingsCount: number
packingIndex: number
}
const CellPackInfoParams = {
info: PD.Value<CellPackInfoValue>({ packingsCount: 1, packingIndex: 0 }, { isHidden: true })
}
type CellPackInfoParams = PD.Values<typeof CellPackInfoParams>
export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellPackInfoParams, CellPackInfoValue> = CustomStructureProperty.createProvider({
label: 'CellPack Info',
descriptor: CustomPropertyDescriptor({ name: 'cellpack-info' }),
type: 'root',
defaultParams: CellPackInfoParams,
getParams: (data: Structure) => CellPackInfoParams,
isApplicable: (data: Structure) => true,
obtain: async (ctx: CustomProperty.Context, data: Structure, props: CellPackInfoParams) => {
return { ...CellPackInfoParams.info.defaultValue, ...props.info }
}
})

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -10,8 +10,7 @@ import { Task } from '../../../../mol-task';
import { CellPack as _CellPack, Cell, CellPacking } from './data';
import { createStructureFromCellPack } from './model';
// export const DefaultCellPackBaseUrl = 'https://cdn.jsdelivr.net/gh/mesoscope/cellPACK_data@master/cellPACK_database_1.1.0/'
export const DefaultCellPackBaseUrl = 'https://mgldev.scripps.edu/projects/autoPACK/web/cellpackproject/'
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/'
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }

View File

@@ -15,6 +15,7 @@ import { PluginSpec } from '../../mol-plugin/spec';
import { LoadCellPackModel } from './extensions/cellpack/model';
import { StructureFromCellpack } from './extensions/cellpack/state';
import { DownloadStructure } from '../../mol-plugin-state/actions/structure';
import { PluginConfig } from '../../mol-plugin/config';
require('mol-plugin-ui/skin/light.scss')
function getParam(name: string, regex: string): string {
@@ -46,6 +47,7 @@ function init() {
},
config: DefaultPluginSpec.config
};
spec.config?.set(PluginConfig.Viewport.ShowExpand, false);
const plugin = createPlugin(document.getElementById('app')!, spec);
trySetSnapshot(plugin);
tryLoadFromUrl(plugin);
@@ -88,7 +90,6 @@ async function tryLoadFromUrl(ctx: PluginContext) {
format: format as any,
isBinary,
options: params.source.params.options,
structure: params.source.params.structure,
}
}
}));

View File

@@ -172,16 +172,17 @@
addHeader('State');
var snapshot;
addControl('Create Snapshot', () => {
addControl('Set Snapshot', () => {
snapshot = PluginWrapper.snapshot.get();
// could use JSON.stringify(snapshot) and upload the data
// console.log(JSON.stringify(snapshot, null, 2));
});
addControl('Apply Snapshot', () => {
addControl('Restore Snapshot', () => {
if (!snapshot) return;
PluginWrapper.snapshot.set(snapshot);
// or download snapshot using fetch or ajax or whatever
// or PluginWrapper.snapshot.download(url);
});
addControl('Download Snapshot', () => {
snapshot = PluginWrapper.snapshot.download();
});
////////////////////////////////////////////////////////

View File

@@ -26,13 +26,12 @@ import { ColorNames } from '../../mol-util/color/names';
import { InitVolumeStreaming, CreateVolumeStreamingInfo } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
import { DefaultCanvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
// import { Vec3 } from 'mol-math/linear-algebra';
// import { ParamDefinition } from 'mol-util/param-definition';
// import { Text } from 'mol-geo/geometry/text/text';
import { download } from '../../mol-util/download';
import { getFormattedTime } from '../../mol-util/date';
require('../../mol-plugin-ui/skin/light.scss')
class MolStarProteopediaWrapper {
static VERSION_MAJOR = 4;
static VERSION_MAJOR = 5;
static VERSION_MINOR = 0;
private _ev = RxEventHelper.create();
@@ -97,7 +96,6 @@ class MolStarProteopediaWrapper {
}
const s = model
.apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [EvolutionaryConservation.propertyProvider.descriptor.name], properties: {} }, { ref: StateElements.ModelProps, state: { isGhost: false } })
.apply(StateTransforms.Model.StructureFromModel, props, { ref: StateElements.Assembly });
s.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: StateElements.Sequence });
@@ -427,10 +425,16 @@ class MolStarProteopediaWrapper {
set: (snapshot: PluginState.Snapshot) => {
return this.plugin.state.setSnapshot(snapshot);
},
download: async (url: string) => {
download: () => {
const json = JSON.stringify(this.plugin.state.getSnapshot(), null, 2);
const blob = new Blob([json], {type : 'application/json;charset=utf-8'});
download(blob, `mol-star_state_${(name || getFormattedTime())}.json`)
},
fetch: async (url: string) => {
try {
const snapshot = await this.plugin.runTask(this.plugin.fetch({ url, type: 'json' }));
await this.plugin.state.setSnapshot(snapshot);
// TODO: is this OK to test for snapshots from server?
await this.plugin.state.setSnapshot(snapshot?.data?.entries?.[0]?.snapshot || snapshot);
} catch (e) {
console.log(e);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -47,8 +47,8 @@ class Camera {
private prevProjection = Mat4.identity();
private prevView = Mat4.identity();
private deltaDirection = Vec3.zero();
private newPosition = Vec3.zero();
private deltaDirection = Vec3();
private newPosition = Vec3();
update() {
const snapshot = this.state as Camera.Snapshot;
@@ -85,13 +85,18 @@ class Camera {
return Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
}
getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
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)
const targetDistance = Math.abs((r / aspectFactor) / Math.sin(fov / 2))
return Math.abs((r / aspectFactor) / Math.sin(fov / 2))
}
getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
const r = Math.max(radius, 0.01)
const targetDistance = this.getTargetDistance(r)
Vec3.sub(this.deltaDirection, this.target, this.position)
if (dir) Vec3.matchDirection(this.deltaDirection, dir, this.deltaDirection)
@@ -125,7 +130,6 @@ class Camera {
this.viewport = viewport;
Camera.copySnapshot(this.state, state);
}
}
namespace Camera {
@@ -286,6 +290,11 @@ function updateClip(camera: Camera) {
far = Math.max(0, far)
}
if (near === far) {
// make sure near and far are not identical to avoid Infinity in the projection matrix
far = near + 0.01
}
camera.near = near;
camera.far = far;
camera.fogNear = fogNear;

View File

@@ -25,23 +25,32 @@ class CameraTransitionManager {
get target(): Readonly<Camera.Snapshot> { return this._target }
apply(to: Partial<Camera.Snapshot>, durationMs: number = 0, transition?: CameraTransitionManager.TransitionFunc) {
Camera.copySnapshot(this._source, this.camera.state);
Camera.copySnapshot(this._target, this.camera.state);
if (!this.inTransition || durationMs > 0) {
Camera.copySnapshot(this._source, this.camera.state);
}
if (!this.inTransition) {
Camera.copySnapshot(this._target, this.camera.state);
}
Camera.copySnapshot(this._target, to);
if (this._target.radius > this._target.radiusMax) {
this._target.radius = this._target.radiusMax
}
if (durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
if (!this.inTransition && durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
this.finish(this._target);
return;
}
this.inTransition = true;
this.func = transition || CameraTransitionManager.defaultTransition;
this.start = this.t;
this.durationMs = durationMs;
if (!this.inTransition || durationMs > 0) {
this.start = this.t;
this.durationMs = durationMs;
}
}
tick(t: number) {

View File

@@ -31,13 +31,16 @@ import { PixelData } from '../mol-util/image';
import { readTexture } from '../mol-gl/compute/util';
import { DrawPass } from './passes/draw';
import { PickPass } from './passes/pick';
import { Task } from '../mol-task';
import { ImagePass, ImageProps } from './passes/image';
import { Sphere3D } from '../mol-math/geometry';
import { isDebugMode } from '../mol-util/debug';
import { CameraHelperParams } from './helper/camera-helper';
export const Canvas3DParams = {
cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']] as const),
camera: PD.Group({
mode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']] as const, { label: 'Camera' }),
helper: PD.Group(CameraHelperParams, { isFlat: true })
}, { pivot: 'mode' }),
cameraFog: PD.MappedStatic('on', {
on: PD.Group({
intensity: PD.Numeric(50, { min: 1, max: 100, step: 1 }),
@@ -93,7 +96,7 @@ interface Canvas3D {
downloadScreenshot(): void
getPixelData(variant: GraphicsRenderVariant): PixelData
setProps(props: Partial<Canvas3DProps>): void
getImagePass(): ImagePass
getImagePass(props: Partial<ImageProps>): ImagePass
/** Returns a copy of the current Canvas3D instance props */
readonly props: Readonly<Canvas3DProps>
@@ -105,13 +108,12 @@ interface Canvas3D {
}
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()))
const DefaultRunTask = (task: Task<unknown>) => task.run()
namespace Canvas3D {
export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask) {
export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}) {
const gl = getGLContext(canvas, {
alpha: true,
antialias: true,
@@ -156,10 +158,10 @@ namespace Canvas3D {
if (isDebugMode) console.log('context restored')
}, false)
return Canvas3D.create(webgl, input, props, runTask)
return Canvas3D.create(webgl, input, props)
}
export function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}, runTask = DefaultRunTask): Canvas3D {
export function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}): Canvas3D {
const p = { ...DefaultCanvas3DParams, ...props }
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>()
@@ -178,7 +180,7 @@ namespace Canvas3D {
const camera = new Camera({
position: Vec3.create(0, 0, 100),
mode: p.cameraMode,
mode: p.camera.mode,
fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0,
clipFar: p.cameraClipping.far
})
@@ -188,7 +190,9 @@ namespace Canvas3D {
const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper)
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, {
cameraHelper: p.camera.helper
})
const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5)
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing)
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample)
@@ -300,7 +304,8 @@ namespace Canvas3D {
function resolveCameraReset() {
if (!cameraResetRequested) return;
const { center, radius } = scene.boundingSphere;
const { center, radius } = scene.boundingSphereVisible;
if (radius > 0) {
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration
const focus = camera.getFocus(center, radius);
@@ -313,16 +318,50 @@ namespace Canvas3D {
cameraResetRequested = false;
}
const oldBoundingSphereVisible = Sphere3D();
const cameraSphere = Sphere3D();
function shouldResetCamera() {
if (camera.state.radiusMax === 0) return true;
if (camera.transition.inTransition || nextCameraResetSnapshot) return false;
let cameraSphereOverlapsNone = true
Sphere3D.set(cameraSphere, camera.state.target, camera.state.radius)
// check if any renderable has moved outside of the old bounding sphere
// and if no renderable is overlapping with the camera sphere
for (const r of scene.renderables) {
if (!r.state.visible) continue;
const b = r.values.boundingSphere.ref.value;
if (!b.radius) continue;
if (!Sphere3D.includes(oldBoundingSphereVisible, b)) return true;
if (Sphere3D.overlaps(cameraSphere, b)) cameraSphereOverlapsNone = false;
}
return cameraSphereOverlapsNone;
}
const sceneCommitTimeoutMs = 250;
function commitScene(isSynchronous: boolean) {
if (!scene.needsCommit) return true;
// snapshot the current bounding sphere of visible objects
Sphere3D.copy(oldBoundingSphereVisible, scene.boundingSphereVisible);
if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
if (debugHelper.isEnabled) debugHelper.update();
if (reprCount.value === 0 || camera.state.radiusMax === 0) cameraResetRequested = true;
camera.setState({ radiusMax: scene.boundingSphere.radius })
if (reprCount.value === 0 || shouldResetCamera()) {
cameraResetRequested = true;
}
if (oldBoundingSphereVisible.radius === 0) nextCameraResetDuration = 0;
camera.setState({ radiusMax: scene.boundingSphere.radius }, 0)
reprCount.next(reprRenderObjects.size);
return true;
}
@@ -399,8 +438,13 @@ namespace Canvas3D {
reprCount.next(reprRenderObjects.size)
},
syncVisibility: () => {
if (camera.state.radiusMax === 0) {
cameraResetRequested = true
nextCameraResetDuration = 0
}
if (scene.syncVisibility()) {
camera.setState({ radiusMax: scene.boundingSphere.radius })
if (debugHelper.isEnabled) debugHelper.update()
}
},
@@ -435,8 +479,8 @@ namespace Canvas3D {
reprCount,
setProps: (props: Partial<Canvas3DProps>) => {
const cameraState: Partial<Camera.Snapshot> = Object.create(null)
if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
cameraState.mode = props.cameraMode
if (props.camera && props.camera.mode !== undefined && props.camera.mode !== camera.state.mode) {
cameraState.mode = props.camera.mode
}
if (props.cameraFog !== undefined) {
const newFog = props.cameraFog.name === 'on' ? props.cameraFog.params.intensity : 0
@@ -456,6 +500,7 @@ namespace Canvas3D {
}
if (Object.keys(cameraState).length > 0) camera.setState(cameraState)
if (props.camera?.helper) drawPass.setProps({ cameraHelper: props.camera.helper })
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground
@@ -477,7 +522,10 @@ namespace Canvas3D {
: 0
return {
cameraMode: camera.state.mode,
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: {} },

View File

@@ -208,7 +208,7 @@ namespace TrackballControls {
function focusCamera() {
const factor = (_focusEnd[1] - _focusStart[1]) * p.zoomSpeed
if (factor !== 0.0) {
const radius = Math.max(1, camera.state.radius + 10 * factor)
const radius = Math.max(1, camera.state.radius + camera.state.radius * factor)
camera.setState({ radius })
}
@@ -248,10 +248,14 @@ namespace TrackballControls {
}
}
/** Ensure the distance between object and target is within the min/max distance */
/**
* Ensure the distance between object and target is within the min/max distance
* and not too large compared to `camera.state.radiusMax`
*/
function checkDistances() {
if (Vec3.squaredMagnitude(_eye) > p.maxDistance * p.maxDistance) {
Vec3.setMagnitude(_eye, _eye, p.maxDistance)
const maxDistance = Math.min(Math.max(camera.state.radiusMax * 1000, 0.01), p.maxDistance)
if (Vec3.squaredMagnitude(_eye) > maxDistance * maxDistance) {
Vec3.setMagnitude(_eye, _eye, maxDistance)
Vec3.add(camera.position, camera.target, _eye)
Vec2.copy(_zoomStart, _zoomEnd)
Vec2.copy(_focusStart, _focusEnd)

View File

@@ -20,9 +20,10 @@ import { ValueCell } from '../../mol-util';
import { Geometry } from '../../mol-geo/geometry/geometry';
export const DebugHelperParams = {
sceneBoundingSpheres: PD.Boolean(false, { description: 'Show scene bounding spheres.' }),
objectBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of render objects.' }),
instanceBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of instances.' }),
sceneBoundingSpheres: PD.Boolean(false, { description: 'Show full scene bounding spheres.' }),
visibleSceneBoundingSpheres: PD.Boolean(false, { description: 'Show visible scene bounding spheres.' }),
objectBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of visible render objects.' }),
instanceBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of visible instances.' }),
}
export type DebugHelperParams = typeof DebugHelperParams
export type DebugHelperProps = PD.Values<DebugHelperParams>
@@ -37,6 +38,7 @@ export class BoundingSphereHelper {
private objectsData = new Map<GraphicsRenderObject, BoundingSphereData>()
private instancesData = new Map<GraphicsRenderObject, BoundingSphereData>()
private sceneData: BoundingSphereData | undefined
private visibleSceneData: BoundingSphereData | undefined
constructor(ctx: WebGLContext, parent: Scene, props: Partial<DebugHelperProps>) {
this.scene = Scene.create(ctx)
@@ -45,9 +47,12 @@ export class BoundingSphereHelper {
}
update() {
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.grey, sceneMaterialId)
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.lightgrey, sceneMaterialId)
if (newSceneData) this.sceneData = newSceneData
const newVisibleSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphereVisible, this.visibleSceneData, ColorNames.black, visibleSceneMaterialId)
if (newVisibleSceneData) this.visibleSceneData = newVisibleSceneData
this.parent.forEach((r, ro) => {
const objectData = this.objectsData.get(ro)
const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato, objectMaterialId)
@@ -88,6 +93,10 @@ export class BoundingSphereHelper {
this.sceneData.renderObject.state.visible = this._props.sceneBoundingSpheres
}
if (this.visibleSceneData) {
this.visibleSceneData.renderObject.state.visible = this._props.visibleSceneBoundingSpheres
}
this.parent.forEach((_, ro) => {
const objectData = this.objectsData.get(ro)
if (objectData) objectData.renderObject.state.visible = ro.state.visible && this._props.objectBoundingSpheres
@@ -104,7 +113,10 @@ export class BoundingSphereHelper {
}
get isEnabled() {
return this._props.sceneBoundingSpheres || this._props.objectBoundingSpheres || this._props.instanceBoundingSpheres
return (
this._props.sceneBoundingSpheres || this._props.visibleSceneBoundingSpheres ||
this._props.objectBoundingSpheres || this._props.instanceBoundingSpheres
)
}
get props() { return this._props as Readonly<DebugHelperProps> }
@@ -141,6 +153,7 @@ function createBoundingSphereMesh(boundingSphere: Sphere3D, mesh?: Mesh) {
}
const sceneMaterialId = getNextMaterialId()
const visibleSceneMaterialId = getNextMaterialId()
const objectMaterialId = getNextMaterialId()
const instanceMaterialId = getNextMaterialId()

View File

@@ -0,0 +1,175 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { WebGLContext } from '../../mol-gl/webgl/context';
import Scene from '../../mol-gl/scene';
import { Camera } from '../camera';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { GraphicsRenderObject } from '../../mol-gl/render-object';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { ColorNames } from '../../mol-util/color/names';
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
import { Viewport } from '../camera/util';
import { ValueCell } from '../../mol-util';
import { Sphere3D } from '../../mol-math/geometry';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import produce from 'immer';
import { Shape } from '../../mol-model/shape';
// TODO add scale line/grid
const AxesParams = {
...Mesh.Params,
alpha: { ...Mesh.Params.alpha, defaultValue: 0.33 },
ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
colorX: PD.Color(ColorNames.red, { isEssential: true }),
colorY: PD.Color(ColorNames.green, { isEssential: true }),
colorZ: PD.Color(ColorNames.blue, { isEssential: true }),
scale: PD.Numeric(0.33, { min: 0.1, max: 2, step: 0.1 }, { isEssential: true }),
}
type AxesParams = typeof AxesParams
type AxesProps = PD.Values<AxesParams>
export const CameraHelperParams = {
axes: PD.MappedStatic('on', {
on: PD.Group(AxesParams),
off: PD.Group({})
}, { cycle: true, description: 'Show camera orientation axes' }),
}
export type CameraHelperParams = typeof CameraHelperParams
export type CameraHelperProps = PD.Values<CameraHelperParams>
export class CameraHelper {
scene: Scene
camera: Camera
props: CameraHelperProps = {
axes: { name: 'off', params: {} }
}
private renderObject: GraphicsRenderObject | undefined
constructor(private webgl: WebGLContext, props: Partial<CameraHelperProps> = {}) {
this.scene = Scene.create(webgl)
this.camera = new Camera()
Vec3.set(this.camera.up, 0, 1, 0)
Vec3.set(this.camera.target, 0, 0, 0)
this.setProps(props)
}
setProps(props: Partial<CameraHelperProps>) {
this.props = produce(this.props, p => {
if (props.axes !== undefined) {
p.axes.name = props.axes.name
if (props.axes.name === 'on') {
this.scene.clear()
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio }
this.renderObject = createAxesRenderObject(params)
this.scene.add(this.renderObject)
this.scene.commit()
Vec3.set(this.camera.position, 0, 0, params.scale * 200)
Mat4.lookAt(this.camera.view, this.camera.position, this.camera.target, this.camera.up)
p.axes.params = { ...props.axes.params }
}
}
})
}
get isEnabled() {
return this.props.axes.name === 'on'
}
update(camera: Camera) {
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)
const r = this.renderObject.values.boundingSphere.ref.value.radius
Mat4.setTranslation(m, 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) {
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 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
Mat4.ortho(camera.projection, left, right, top, bottom, near, far)
}
function createAxesMesh(scale: number, mesh?: Mesh) {
const state = MeshBuilder.createState(512, 256, mesh)
const radius = 0.05 * scale
const x = Vec3.scale(Vec3(), Vec3.unitX, scale)
const y = Vec3.scale(Vec3(), Vec3.unitY, scale)
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale)
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 }
state.currentGroup = 0
addSphere(state, Vec3.origin, radius, 2)
state.currentGroup = 1
addSphere(state, x, radius, 2)
addCylinder(state, Vec3.origin, x, 1, cylinderProps)
state.currentGroup = 2
addSphere(state, y, radius, 2)
addCylinder(state, Vec3.origin, y, 1, cylinderProps)
state.currentGroup = 3
addSphere(state, z, radius, 2)
addCylinder(state, Vec3.origin, z, 1, cylinderProps)
return MeshBuilder.getMesh(state)
}
function getAxesShape(props: AxesProps, shape?: Shape<Mesh>) {
const scale = 100 * props.scale
const mesh = createAxesMesh(scale, shape?.geometry)
mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4))
const getColor = (groupId: number) => {
switch (groupId) {
case 1: return props.colorX
case 2: return props.colorY
case 3: return props.colorZ
default: return ColorNames.grey
}
}
return Shape.create('axes', {}, mesh, getColor, () => 1, () => '')
}
function createAxesRenderObject(props: AxesProps) {
const shape = getAxesShape(props)
return Shape.createRenderObject(shape, props)
}

View File

@@ -111,7 +111,7 @@ export class Canvas3dInteractionHelper {
this.ev.dispose();
}
constructor(private canvasIdentify: Canvas3D['identify'], private getLoci: Canvas3D['getLoci'], input: InputObserver, private maxFps: number = 15) {
constructor(private canvasIdentify: Canvas3D['identify'], private getLoci: Canvas3D['getLoci'], input: InputObserver, private maxFps: number = 30) {
input.move.subscribe(({x, y, inside, buttons, button, modifiers }) => {
if (!inside) return;
this.move(x, y, buttons, button, modifiers);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -11,15 +11,25 @@ import Scene from '../../mol-gl/scene';
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
import { Texture } from '../../mol-gl/webgl/texture';
import { Camera } from '../camera';
import { CameraHelper, CameraHelperParams } from '../helper/camera-helper';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
export const DrawPassParams = {
cameraHelper: PD.Group(CameraHelperParams)
}
export const DefaultDrawPassProps = PD.getDefaultValues(DrawPassParams);
export type DrawPassProps = PD.Values<typeof DrawPassParams>
export class DrawPass {
colorTarget: RenderTarget
depthTexture: Texture
packedDepth: boolean
cameraHelper: CameraHelper
private depthTarget: RenderTarget | null
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper) {
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, props: Partial<DrawPassProps> = {}) {
const { gl, extensions, resources } = webgl
const width = gl.drawingBufferWidth
const height = gl.drawingBufferHeight
@@ -31,6 +41,9 @@ export class DrawPass {
this.depthTexture.define(width, height)
this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth')
}
const p = { ...DefaultDrawPassProps, ...props }
this.cameraHelper = new CameraHelper(webgl, p.cameraHelper);
}
setSize(width: number, height: number) {
@@ -42,8 +55,18 @@ export class DrawPass {
}
}
setProps(props: Partial<DrawPassProps>) {
if (props.cameraHelper) this.cameraHelper.setProps(props.cameraHelper)
}
get props(): DrawPassProps {
return {
cameraHelper: { ...this.cameraHelper.props }
}
}
render(toDrawingBuffer: boolean, transparentBackground: boolean) {
const { webgl, renderer, scene, camera, debugHelper, colorTarget, depthTarget } = this
const { webgl, renderer, colorTarget, depthTarget } = this
if (toDrawingBuffer) {
webgl.unbindFramebuffer()
} else {
@@ -55,21 +78,26 @@ export class DrawPass {
}
renderer.setViewport(0, 0, colorTarget.getWidth(), colorTarget.getHeight())
renderer.render(scene, camera, 'color', true, transparentBackground)
if (debugHelper.isEnabled) {
debugHelper.syncVisibility()
renderer.render(debugHelper.scene, camera, 'color', false, transparentBackground)
}
this.renderInternal('color', transparentBackground)
// do a depth pass if not rendering to drawing buffer and
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
if (!toDrawingBuffer && depthTarget) {
depthTarget.bind()
renderer.render(scene, camera, 'depth', true, transparentBackground)
if (debugHelper.isEnabled) {
debugHelper.syncVisibility()
renderer.render(debugHelper.scene, camera, 'depth', false, transparentBackground)
}
this.renderInternal('depth', transparentBackground)
}
}
private renderInternal(variant: 'color' | 'depth', transparentBackground: boolean) {
const { renderer, scene, camera, debugHelper, cameraHelper } = this
renderer.render(scene, camera, variant, true, transparentBackground)
if (debugHelper.isEnabled) {
debugHelper.syncVisibility()
renderer.render(debugHelper.scene, camera, variant, false, transparentBackground)
}
if (cameraHelper.isEnabled) {
cameraHelper.update(camera)
renderer.render(cameraHelper.scene, cameraHelper.camera, variant, false, transparentBackground)
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -10,7 +10,7 @@ import Renderer from '../../mol-gl/renderer';
import Scene from '../../mol-gl/scene';
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { DrawPass } from './draw'
import { DrawPass, DrawPassParams } from './draw'
import { PostprocessingPass, PostprocessingParams } from './postprocessing'
import { MultiSamplePass, MultiSampleParams } from './multi-sample'
import { Camera } from '../camera';
@@ -20,6 +20,7 @@ export const ImageParams = {
transparentBackground: PD.Boolean(false),
multiSample: PD.Group(MultiSampleParams),
postprocessing: PD.Group(PostprocessingParams),
drawPass: PD.Group(DrawPassParams),
}
export type ImageProps = PD.Values<typeof ImageParams>
@@ -44,7 +45,7 @@ export class ImagePass {
this._transparentBackground = p.transparentBackground
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper)
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, p.drawPass)
this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing)
this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample)
@@ -66,6 +67,16 @@ export class ImagePass {
if (props.transparentBackground !== undefined) this._transparentBackground = props.transparentBackground
if (props.postprocessing) this.postprocessing.setProps(props.postprocessing)
if (props.multiSample) this.multiSample.setProps(props.multiSample)
if (props.drawPass) this.drawPass.setProps(props.drawPass)
}
get props(): ImageProps {
return {
transparentBackground: this._transparentBackground,
postprocessing: this.postprocessing.props,
multiSample: this.multiSample.props,
drawPass: this.drawPass.props
}
}
render() {

View File

@@ -40,7 +40,7 @@ function getComposeRenderable(ctx: WebGLContext, colorTexture: Texture): Compose
}
const schema = { ...ComposeSchema }
const shaderCode = ShaderCode(quad_vert, compose_frag)
const shaderCode = ShaderCode('compose', quad_vert, compose_frag)
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
return createComputeRenderable(renderItem, values)

View File

@@ -96,7 +96,7 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
}
const schema = { ...PostprocessingSchema }
const shaderCode = ShaderCode(quad_vert, postprocessing_frag)
const shaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag)
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
return createComputeRenderable(renderItem, values)

View File

@@ -4,29 +4,28 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ValueCell } from '../../../mol-util'
import { Sphere3D, Box3D } from '../../../mol-math/geometry'
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { DirectVolumeValues } from '../../../mol-gl/renderable/direct-volume';
import { Vec3, Mat4, Vec2 } from '../../../mol-math/linear-algebra';
import { Box } from '../../primitive/box';
import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function';
import { Texture } from '../../../mol-gl/webgl/texture';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { TransformData } from '../transform-data';
import { createColors } from '../color-data';
import { createMarkers } from '../marker-data';
import { GeometryUtils } from '../geometry';
import { transformPositionArray } from '../../../mol-geo/util';
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
import { Theme } from '../../../mol-theme/theme';
import { RenderableState } from '../../../mol-gl/renderable';
import { ColorListOptions, ColorListName } from '../../../mol-util/color/lists';
import { Color } from '../../../mol-util/color';
import { BaseGeometry } from '../base';
import { createEmptyOverpaint } from '../overpaint-data';
import { createEmptyTransparency } from '../transparency-data';
import { hashFnv32a } from '../../../mol-data/util';
import { transformPositionArray } from '../../../mol-geo/util';
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
import { RenderableState } from '../../../mol-gl/renderable';
import { DirectVolumeValues } from '../../../mol-gl/renderable/direct-volume';
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
import { Texture } from '../../../mol-gl/webgl/texture';
import { Box3D, Sphere3D } from '../../../mol-math/geometry';
import { Mat4, Vec2, Vec3 } from '../../../mol-math/linear-algebra';
import { Theme } from '../../../mol-theme/theme';
import { ValueCell } from '../../../mol-util';
import { Color } from '../../../mol-util/color';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Box } from '../../primitive/box';
import { BaseGeometry } from '../base';
import { createColors } from '../color-data';
import { GeometryUtils } from '../geometry';
import { createMarkers } from '../marker-data';
import { createEmptyOverpaint } from '../overpaint-data';
import { TransformData } from '../transform-data';
import { createEmptyTransparency } from '../transparency-data';
import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function';
const VolumeBox = Box()
const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][]
@@ -117,7 +116,7 @@ export namespace DirectVolume {
Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
]),
list: PD.ColorList<ColorListName>('red-yellow-blue', ColorListOptions),
list: PD.ColorList('red-yellow-blue'),
}
export type Params = typeof Params
@@ -148,7 +147,7 @@ export namespace DirectVolume {
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount)
const controlPoints = getControlPointsFromVec2Array(props.controlPoints)
const transferTex = createTransferFunctionTexture(controlPoints, props.list)
const transferTex = createTransferFunctionTexture(controlPoints, props.list.colors)
const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value)) * 2 * 5
@@ -193,7 +192,7 @@ export namespace DirectVolume {
ValueCell.updateIfChanged(values.dRenderMode, props.renderMode)
const controlPoints = getControlPointsFromVec2Array(props.controlPoints)
createTransferFunctionTexture(controlPoints, props.list, values.tTransferTex)
createTransferFunctionTexture(controlPoints, props.list.colors, values.tTransferTex)
}
function updateBoundingSphere(values: DirectVolumeValues, directVolume: DirectVolume) {

View File

@@ -41,7 +41,7 @@ function getHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: T
}
const schema = { ...HistopyramidReductionSchema }
const shaderCode = ShaderCode(quad_vert, reduction_frag)
const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag)
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
HistopyramidReductionRenderable = createComputeRenderable(renderItem, values);

View File

@@ -34,7 +34,7 @@ function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
}
const schema = { ...HistopyramidSumSchema }
const shaderCode = ShaderCode(quad_vert, sum_frag)
const shaderCode = ShaderCode('sum', quad_vert, sum_frag)
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
HistopyramidSumRenderable = createComputeRenderable(renderItem, values)

View File

@@ -46,7 +46,7 @@ function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridD
}
const schema = { ...ActiveVoxelsSchema }
const shaderCode = ShaderCode(quad_vert, active_voxels_frag)
const shaderCode = ShaderCode('active-voxels', quad_vert, active_voxels_frag)
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
return createComputeRenderable(renderItem, values);

View File

@@ -62,7 +62,7 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
}
const schema = { ...IsosurfaceSchema }
const shaderCode = ShaderCode(quad_vert, isosurface_frag, { drawBuffers: true })
const shaderCode = ShaderCode('isosurface', quad_vert, isosurface_frag, { drawBuffers: true })
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
return createComputeRenderable(renderItem, values);

View File

@@ -95,12 +95,12 @@ export function calculateInvariantBoundingSphere(position: Float32Array, positio
boundaryHelper.reset()
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
Vec3.fromArray(v, position, i)
boundaryHelper.includeStep(v)
boundaryHelper.includePosition(v)
}
boundaryHelper.finishedIncludeStep()
for (let i = 0, _i = positionCount * 3; i < _i; i += step) {
Vec3.fromArray(v, position, i)
boundaryHelper.radiusStep(v)
boundaryHelper.radiusPosition(v)
}
const sphere = boundaryHelper.getSphere()
@@ -122,29 +122,30 @@ export function calculateTransformBoundingSphere(invariantBoundingSphere: Sphere
const { center, radius, extrema } = invariantBoundingSphere
if (extrema) {
// only use extrema if there are not too many transforms
if (extrema && transformCount < 50) {
for (let i = 0, _i = transformCount; i < _i; ++i) {
for (const e of extrema) {
Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16)
boundaryHelper.includeStep(v)
boundaryHelper.includePosition(v)
}
}
boundaryHelper.finishedIncludeStep()
for (let i = 0, _i = transformCount; i < _i; ++i) {
for (const e of extrema) {
Vec3.transformMat4Offset(v, e, transform, 0, 0, i * 16)
boundaryHelper.radiusStep(v)
boundaryHelper.radiusPosition(v)
}
}
} else {
for (let i = 0, _i = transformCount; i < _i; ++i) {
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
boundaryHelper.includeSphereStep(v, radius)
boundaryHelper.includePositionRadius(v, radius)
}
boundaryHelper.finishedIncludeStep()
for (let i = 0, _i = transformCount; i < _i; ++i) {
Vec3.transformMat4Offset(v, center, transform, 0, 0, i * 16)
boundaryHelper.radiusSphereStep(v, radius)
boundaryHelper.radiusPositionRadius(v, radius)
}
}

View File

@@ -16,7 +16,6 @@ import { ValueCell } from '../mol-util';
import { RenderableValues, GlobalUniformValues, BaseValues } from './renderable/schema';
import { GraphicsRenderVariant } from './webgl/render-item';
import { ParamDefinition as PD } from '../mol-util/param-definition';
import { deepClone } from '../mol-util/object';
export interface RendererStats {
programCount: number
@@ -110,7 +109,7 @@ function getStyle(props: RendererProps['style']) {
namespace Renderer {
export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
const { gl, state, stats } = ctx
const p = deepClone({ ...PD.getDefaultValues(RendererParams), ...props })
const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props)
const style = getStyle(p.style)
const viewport = Viewport()

View File

@@ -19,37 +19,25 @@ import { hash1 } from '../mol-data/util';
const boundaryHelper = new BoundaryHelper('98')
function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D): Sphere3D {
function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D, onlyVisible: boolean): Sphere3D {
boundaryHelper.reset();
for (let i = 0, il = renderables.length; i < il; ++i) {
if (!renderables[i].state.visible) continue;
if (onlyVisible && !renderables[i].state.visible) continue;
const boundingSphere = renderables[i].values.boundingSphere.ref.value
if (!boundingSphere.radius) continue;
if (Sphere3D.hasExtrema(boundingSphere)) {
for (const e of boundingSphere.extrema) {
boundaryHelper.includeStep(e)
}
} else {
boundaryHelper.includeSphereStep(boundingSphere.center, boundingSphere.radius);
}
boundaryHelper.includeSphere(boundingSphere);
}
boundaryHelper.finishedIncludeStep();
for (let i = 0, il = renderables.length; i < il; ++i) {
if (!renderables[i].state.visible) continue;
if (onlyVisible && !renderables[i].state.visible) continue;
const boundingSphere = renderables[i].values.boundingSphere.ref.value
if (!boundingSphere.radius) continue;
if (Sphere3D.hasExtrema(boundingSphere)) {
for (const e of boundingSphere.extrema) {
boundaryHelper.radiusStep(e)
}
} else {
boundaryHelper.radiusSphereStep(boundingSphere.center, boundingSphere.radius);
}
boundaryHelper.radiusSphere(boundingSphere);
}
return boundaryHelper.getSphere(boundingSphere);
@@ -74,6 +62,7 @@ interface Scene extends Object3D {
readonly count: number
readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>>
readonly boundingSphere: Sphere3D
readonly boundingSphereVisible: Sphere3D
/** Returns `true` if some visibility has changed, `false` otherwise. */
syncVisibility: () => boolean
@@ -92,8 +81,10 @@ namespace Scene {
const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>()
const renderables: Renderable<RenderableValues & BaseValues>[] = []
const boundingSphere = Sphere3D()
const boundingSphereVisible = Sphere3D()
let boundingSphereDirty = true
let boundingSphereVisibleDirty = true
const object3d = Object3D.create()
@@ -103,6 +94,7 @@ namespace Scene {
renderables.push(renderable)
renderableMap.set(o, renderable)
boundingSphereDirty = true
boundingSphereVisibleDirty = true
return renderable;
} else {
console.warn(`RenderObject with id '${o.id}' already present`)
@@ -117,6 +109,7 @@ namespace Scene {
arraySetRemove(renderables, renderable);
renderableMap.delete(o)
boundingSphereDirty = true
boundingSphereVisibleDirty = true
}
}
@@ -161,7 +154,7 @@ namespace Scene {
function syncVisibility() {
const newVisibleHash = computeVisibleHash()
if (newVisibleHash !== visibleHash) {
boundingSphereDirty = true
boundingSphereVisibleDirty = true
return true
} else {
return false
@@ -190,6 +183,7 @@ namespace Scene {
}
if (!keepBoundingSphere) {
boundingSphereDirty = true
boundingSphereVisibleDirty = true
} else {
syncVisibility()
}
@@ -208,6 +202,7 @@ namespace Scene {
renderables.length = 0
renderableMap.clear()
boundingSphereDirty = true
boundingSphereVisibleDirty = true
},
forEach: (callbackFn: (value: Renderable<any>, key: GraphicsRenderObject) => void) => {
renderableMap.forEach(callbackFn)
@@ -218,11 +213,18 @@ namespace Scene {
renderables,
get boundingSphere() {
if (boundingSphereDirty) {
calculateBoundingSphere(renderables, boundingSphere)
calculateBoundingSphere(renderables, boundingSphere, false)
boundingSphereDirty = false
visibleHash = computeVisibleHash()
}
return boundingSphere
},
get boundingSphereVisible() {
if (boundingSphereVisibleDirty) {
calculateBoundingSphere(renderables, boundingSphereVisible, true)
boundingSphereVisibleDirty = false
visibleHash = computeVisibleHash()
}
return boundingSphereVisible
}
}
}

View File

@@ -24,6 +24,7 @@ export interface ShaderExtensions {
export interface ShaderCode {
readonly id: number
readonly name: string
readonly vert: string
readonly frag: string
readonly extensions: ShaderExtensions
@@ -97,33 +98,33 @@ function addIncludes(text: string) {
.replace(reMultipleLinebreaks, '\n')
}
export function ShaderCode(vert: string, frag: string, extensions: ShaderExtensions = {}): ShaderCode {
return { id: shaderCodeId(), vert: addIncludes(vert), frag: addIncludes(frag), extensions }
export function ShaderCode(name: string, vert: string, frag: string, extensions: ShaderExtensions = {}): ShaderCode {
return { id: shaderCodeId(), name, vert: addIncludes(vert), frag: addIncludes(frag), extensions }
}
import points_vert from './shader/points.vert'
import points_frag from './shader/points.frag'
export const PointsShaderCode = ShaderCode(points_vert, points_frag)
export const PointsShaderCode = ShaderCode('points', points_vert, points_frag)
import spheres_vert from './shader/spheres.vert'
import spheres_frag from './shader/spheres.frag'
export const SpheresShaderCode = ShaderCode(spheres_vert, spheres_frag, { fragDepth: true })
export const SpheresShaderCode = ShaderCode('spheres', spheres_vert, spheres_frag, { fragDepth: true })
import text_vert from './shader/text.vert'
import text_frag from './shader/text.frag'
export const TextShaderCode = ShaderCode(text_vert, text_frag, { standardDerivatives: true })
export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: true })
import lines_vert from './shader/lines.vert'
import lines_frag from './shader/lines.frag'
export const LinesShaderCode = ShaderCode(lines_vert, lines_frag)
export const LinesShaderCode = ShaderCode('lines', lines_vert, lines_frag)
import mesh_vert from './shader/mesh.vert'
import mesh_frag from './shader/mesh.frag'
export const MeshShaderCode = ShaderCode(mesh_vert, mesh_frag, { standardDerivatives: true })
export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { standardDerivatives: true })
import direct_volume_vert from './shader/direct-volume.vert'
import direct_volume_frag from './shader/direct-volume.frag'
export const DirectVolumeShaderCode = ShaderCode(direct_volume_vert, direct_volume_frag, { fragDepth: true })
export const DirectVolumeShaderCode = ShaderCode('direct-volume', direct_volume_vert, direct_volume_frag, { fragDepth: true })
//
@@ -233,6 +234,7 @@ export function addShaderDefines(gl: GLRenderingContext, extensions: WebGLExtens
const frag = isWebGL2(gl) ? transformGlsl300Frag(shaders.frag) : shaders.frag
return {
id: shaderCodeId(),
name: shaders.name,
vert: `${vertPrefix}${header}${shaders.vert}`,
frag: `${fragPrefix}${header}${frag}`,
extensions: shaders.extensions

View File

@@ -38,10 +38,12 @@ function getLocations(gl: GLRenderingContext, program: WebGLProgram, schema: Ren
const spec = schema[k]
if (spec.type === 'attribute') {
const loc = gl.getAttribLocation(program, k)
// unused attributes will result in a `-1` location which is usually fine
// if (loc === -1) console.info(`Could not get attribute location for '${k}'`)
locations[k] = loc
} else if (spec.type === 'uniform' || spec.type === 'texture') {
const loc = gl.getUniformLocation(program, k)
// unused uniforms will result in a `null` location which is usually fine
// if (loc === null) console.info(`Could not get uniform location for '${k}'`)
locations[k] = loc as number
}
@@ -146,8 +148,8 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
const vertShader = getShader('vert', shaderCode.vert)
const fragShader = getShader('frag', shaderCode.frag)
let locations: Locations // = getLocations(gl, program, schema)
let uniformSetters: UniformSetters // = getUniformSetters(schema)
let locations: Locations
let uniformSetters: UniformSetters
function init() {
vertShader.attach(program)
@@ -186,9 +188,9 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
}
}
},
bindAttributes: (attribueBuffers: AttributeBuffers) => {
for (let i = 0, il = attribueBuffers.length; i < il; ++i) {
const [k, buffer] = attribueBuffers[i]
bindAttributes: (attributeBuffers: AttributeBuffers) => {
for (let i = 0, il = attributeBuffers.length; i < il; ++i) {
const [k, buffer] = attributeBuffers[i]
const l = locations[k]
if (l !== -1) buffer.bind(l)
}

View File

@@ -198,9 +198,6 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
try {
checkError(ctx.gl)
} catch (e) {
// console.log('shaderCode', shaderCode)
// console.log('schema', schema)
// console.log('attributeBuffers', attributeBuffers)
throw new Error(`Error rendering item id ${id}: '${e}'`)
}
}
@@ -246,10 +243,10 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
const value = attributeValues[k]
if (value.ref.version !== versions[k]) {
if (buffer.length >= value.ref.value.length) {
// console.log('attribute array large enough to update', k, value.ref.id, value.ref.version)
// console.log('attribute array large enough to update', buffer.id, k, value.ref.id, value.ref.version)
buffer.updateData(value.ref.value)
} else {
// console.log('attribute array to small, need to create new attribute', k, value.ref.id, value.ref.version)
// console.log('attribute array too small, need to create new attribute', buffer.id, k, value.ref.id, value.ref.version)
buffer.destroy()
const { itemSize, divisor } = schema[k] as AttributeSpec<AttributeKind>
attributeBuffers[i][1] = resources.attribute(value.ref.value, itemSize, divisor)
@@ -276,7 +273,8 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
// console.log('program/defines or buffers changed, update vaos')
Object.keys(renderVariantDefines).forEach(k => {
const vertexArray = vertexArrays[k]
if (vertexArray) vertexArray.update()
if (vertexArray) vertexArray.destroy()
vertexArrays[k] = vertexArrayObject ? resources.vertexArray(programs[k], attributeBuffers, elementsBuffer) : null
})
}

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
*
* @author molstar/ciftools package
*/
@@ -129,7 +129,7 @@ export const BIRD_Schema = {
/**
* Defines the polymer characteristic of the entity.
*/
type: str,
type: Aliased<'polymer' | 'polymer-like' | 'non-polymer' | 'branched'>(str),
/**
* Additional details about this entity.
*/
@@ -423,7 +423,7 @@ export const BIRD_Schema = {
/**
* The monomer type for the sequence.
*/
type: str,
type: Aliased<'peptide-like' | 'saccharide'>(str),
/**
* A flag to indicate a non-ribosomal entity.
*/
@@ -487,7 +487,7 @@ export const BIRD_Schema = {
/**
* An identifier for the wwPDB site creating or modifying the molecule.
*/
processing_site: Aliased<'RCSB' | 'PDBe' | 'PDBJ' | 'BMRB'>(str),
processing_site: Aliased<'RCSB' | 'PDBe' | 'PDBJ' | 'BMRB' | 'PDBC'>(str),
/**
* The action associated with this audit record.
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
*
* @author molstar/ciftools package
*/
@@ -175,7 +175,7 @@ export const CCD_Schema = {
* This data item identifies the deposition site that processed
* this chemical component defintion.
*/
pdbx_processing_site: Aliased<'PDBE' | 'EBI' | 'PDBJ' | 'RCSB'>(str),
pdbx_processing_site: Aliased<'PDBE' | 'EBI' | 'PDBJ' | 'PDBC' | 'RCSB'>(str),
},
/**
* Data items in the CHEM_COMP_ATOM category record details about

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CifCore' schema file. Dictionary versions: CifCore 3.0.11.
* Code-generated 'CifCore' schema file. Dictionary versions: CifCore 3.0.13.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.323, IHM 1.08, CARB draft.
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
*
* @author molstar/ciftools package
*/
@@ -185,7 +185,7 @@ export const mmCIF_Schema = {
/**
* The fraction of the atom type present at this site.
* The sum of the occupancies of all the atom types at this site
* may not significantly exceed 1.0 unless it is a dummy site.
* may not exceed 1.0 unless it is a dummy site.
*/
occupancy: float,
/**
@@ -1774,11 +1774,11 @@ export const mmCIF_Schema = {
/**
* The site where the file was deposited.
*/
deposit_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BMRB' | 'BNL'>(str),
deposit_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BMRB' | 'BNL' | 'PDBC'>(str),
/**
* The site where the file was deposited.
*/
process_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BNL'>(str),
process_site: Aliased<'NDB' | 'RCSB' | 'PDBE' | 'PDBJ' | 'BNL' | 'PDBC'>(str),
/**
* Code for status of chemical shift data file.
*/
@@ -2192,7 +2192,7 @@ export const mmCIF_Schema = {
/**
* Defines the polymer characteristic of the entity.
*/
type: str,
type: Aliased<'polymer' | 'polymer-like' | 'non-polymer' | 'branched'>(str),
/**
* Additional details about this entity.
*/
@@ -2575,6 +2575,48 @@ export const mmCIF_Schema = {
*/
ordinal: int,
},
/**
* Data items in the pdbx_entity_instance_feature category records
* special features of selected entity instances.
*/
pdbx_entity_instance_feature: {
/**
* Special structural details about this entity instance.
*/
details: str,
/**
* A feature type associated with entity instance.
*/
feature_type: Aliased<'SUBJECT OF INVESTIGATION' | 'NO FUNCTIONAL ROLE' | 'OTHER'>(str),
/**
* Author instance identifier (formerly PDB Chain ID)
*/
auth_asym_id: str,
/**
* Instance identifier for this entity.
*/
asym_id: str,
/**
* Author provided residue number.
*/
auth_seq_num: str,
/**
* Position in the sequence.
*/
seq_num: int,
/**
* Chemical component identifier
*/
comp_id: str,
/**
* The author provided chemical component identifier
*/
auth_comp_id: str,
/**
* An ordinal index for this category
*/
ordinal: int,
},
/**
* Data items in the IHM_STARTING_MODEL_DETAILS category records the
* details about structural models used as starting inputs in
@@ -4585,6 +4627,21 @@ export const mmCIF_Schema = {
* PDBX_ENTITY_BRANCH_LIST category.
*/
num: int,
/**
* This data item is a pointer to _atom_site.auth_asym_id in the
* ATOM_SITE category.
*/
pdb_asym_id: str,
/**
* This data item is a pointer to _atom_site.auth_seq_id in the
* ATOM_SITE category.
*/
pdb_seq_num: str,
/**
* This data item is a pointer to _atom_site.auth_comp_id in the
* ATOM_SITE category.
*/
pdb_mon_id: str,
/**
* This data item is a pointer to _atom_site.pdbx_auth_asym_id in the
* ATOM_SITE category.

View File

@@ -6,7 +6,7 @@
*/
import TextEncoder from './cif/encoder/text'
import BinaryEncoder, { EncodingProvider } from './cif/encoder/binary'
import BinaryEncoder, { BinaryEncodingProvider } from './cif/encoder/binary'
import * as _Encoder from './cif/encoder'
import { ArrayEncoding, ArrayEncoder } from '../common/binary-cif';
import { CifFrame } from '../reader/cif';
@@ -20,7 +20,7 @@ export namespace CifWriter {
export interface EncoderParams {
binary?: boolean,
encoderName?: string,
binaryEncodingPovider?: EncodingProvider,
binaryEncodingPovider?: BinaryEncodingProvider,
binaryAutoClassifyEncoding?: boolean
}
@@ -29,8 +29,8 @@ export namespace CifWriter {
return binary ? new BinaryEncoder(encoderName, params ? params.binaryEncodingPovider : void 0, params ? !!params.binaryAutoClassifyEncoding : false) : new TextEncoder();
}
export function fields<K = number, D = any>() {
return Field.build<K, D>();
export function fields<K = number, D = any, N extends string = string>() {
return Field.build<K, D, N>();
}
import E = Encoding
@@ -44,7 +44,7 @@ export namespace CifWriter {
return { fields, source: [source] };
}
export function createEncodingProviderFromCifFrame(frame: CifFrame): EncodingProvider {
export function createEncodingProviderFromCifFrame(frame: CifFrame): BinaryEncodingProvider {
return {
get(c, f) {
const cat = frame.categories[c];
@@ -55,7 +55,7 @@ export namespace CifWriter {
}
};
export function createEncodingProviderFromJsonConfig(hints: EncodingStrategyHint[]): EncodingProvider {
export function createEncodingProviderFromJsonConfig(hints: EncodingStrategyHint[]): BinaryEncodingProvider {
return {
get(c, f) {
for (let i = 0; i < hints.length; i++) {

View File

@@ -10,6 +10,7 @@ import { Column, Table, Database, DatabaseCollection } from '../../../mol-data/d
import { Tensor } from '../../../mol-math/linear-algebra'
import EncoderBase from '../encoder'
import { ArrayEncoder, ArrayEncoding } from '../../common/binary-cif';
import { BinaryEncodingProvider } from './encoder/binary';
// TODO: support for "coordinate fields", make "coordinate precision" a parameter of the encoder
// TODO: automatically detect "precision" of floating point arrays.
@@ -72,25 +73,32 @@ export namespace Field {
return int(name, (e, d, i) => i + 1, { typedArray: Int32Array, encoder: ArrayEncoding.by(ArrayEncoding.delta).and(ArrayEncoding.runLength).and(ArrayEncoding.integerPacking) })
}
export class Builder<K = number, D = any> {
export class Builder<K = number, D = any, N extends string = string> {
private fields: Field<K, D>[] = [];
index(name: string) {
index(name: N) {
this.fields.push(Field.index(name));
return this;
}
str(name: string, value: (k: K, d: D, index: number) => string, params?: ParamsBase<K, D>) {
str(name: N, value: (k: K, d: D, index: number) => string, params?: ParamsBase<K, D>) {
this.fields.push(Field.str(name, value, params));
return this;
}
int(name: string, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }) {
int(name: N, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }) {
this.fields.push(Field.int(name, value, params));
return this;
}
float(name: string, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }) {
vec(name: N, values: ((k: K, d: D, index: number) => number)[], params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor }) {
for (let i = 0; i < values.length; i++) {
this.fields.push(Field.int(`${name}[${i + 1}]`, values[i], params));
}
return this;
}
float(name: N, value: (k: K, d: D, index: number) => number, params?: ParamsBase<K, D> & { typedArray?: ArrayEncoding.TypedArrayCtor, digitCount?: number }) {
this.fields.push(Field.float(name, value, params));
return this;
}
@@ -103,8 +111,8 @@ export namespace Field {
getFields() { return this.fields; }
}
export function build<K = number, D = any>() {
return new Builder<K, D>();
export function build<K = number, D = any, N extends string = string>() {
return new Builder<K, D, N>();
}
}
@@ -215,14 +223,21 @@ export namespace Category {
export interface Encoder<T = string | Uint8Array> extends EncoderBase {
setFilter(filter?: Category.Filter): void,
isCategoryIncluded(name: string): boolean,
setFormatter(formatter?: Category.Formatter): void,
startDataBlock(header: string): void,
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx): void,
getData(): T
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx, options?: Encoder.WriteCategoryOptions): void,
getData(): T,
binaryEncodingProvider: BinaryEncodingProvider | undefined;
}
export namespace Encoder {
export interface WriteCategoryOptions {
ignoreFilter?: boolean
}
export function writeDatabase(encoder: Encoder, name: string, database: Database<Database.Schema>) {
encoder.startDataBlock(name);
for (const table of database._tableNames) {

View File

@@ -17,7 +17,7 @@ import { getIncludedFields, getCategoryInstanceData, CategoryInstanceData } from
import { classifyIntArray, classifyFloatArray } from '../../../common/binary-cif/classifier';
import { ArrayCtor } from '../../../../mol-util/type-helpers';
export interface EncodingProvider {
export interface BinaryEncodingProvider {
get(category: string, field: string): ArrayEncoder | undefined;
}
@@ -28,10 +28,16 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
private filter: Category.Filter = Category.DefaultFilter;
private formatter: Category.Formatter = Category.DefaultFormatter;
binaryEncodingProvider: BinaryEncodingProvider | undefined = void 0;
setFilter(filter?: Category.Filter) {
this.filter = filter || Category.DefaultFilter;
}
isCategoryIncluded(name: string) {
return this.filter.includeCategory(name);
}
setFormatter(formatter?: Category.Formatter) {
this.formatter = formatter || Category.DefaultFormatter;
}
@@ -43,7 +49,7 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
});
}
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx) {
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx, options?: Encoder.WriteCategoryOptions) {
if (!this.data) {
throw new Error('The writer contents have already been encoded, no more writing.');
}
@@ -52,7 +58,7 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
throw new Error('No data block created.');
}
if (!this.filter.includeCategory(category.name)) return;
if (!options?.ignoreFilter && !this.filter.includeCategory(category.name)) return;
const { instance, rowCount, source } = getCategoryInstanceData(category, context);
if (!rowCount) return;
@@ -64,7 +70,7 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
if (!this.filter.includeField(category.name, f.name)) continue;
const format = this.formatter.getFormat(category.name, f.name);
cat.columns.push(encodeField(category.name, f, source, rowCount, format, this.encodingProvider, this.autoClassify));
cat.columns.push(encodeField(category.name, f, source, rowCount, format, this.binaryEncodingProvider, this.autoClassify));
}
// no columns included.
if (!cat.columns.length) return;
@@ -88,7 +94,12 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
return this.encodedData;
}
constructor(encoder: string, private encodingProvider: EncodingProvider | undefined, private autoClassify: boolean) {
getSize() {
return this.encodedData.length;
}
constructor(encoder: string, encodingProvider: BinaryEncodingProvider | undefined, private autoClassify: boolean) {
this.binaryEncodingProvider = encodingProvider;
this.data = {
encoder,
version: VERSION,
@@ -110,7 +121,7 @@ function getDefaultEncoder(type: Field.Type): ArrayEncoder {
return ArrayEncoder.by(E.byteArray);
}
function tryGetEncoder(categoryName: string, field: Field, format: Field.Format | undefined, provider: EncodingProvider | undefined) {
function tryGetEncoder(categoryName: string, field: Field, format: Field.Format | undefined, provider: BinaryEncodingProvider | undefined) {
if (format && format.encoder) {
return format.encoder;
} else if (field.defaultFormat && field.defaultFormat.encoder) {
@@ -129,7 +140,7 @@ function classify(type: Field.Type, data: ArrayLike<any>) {
}
function encodeField(categoryName: string, field: Field, data: CategoryInstanceData['source'], totalCount: number,
format: Field.Format | undefined, encoderProvider: EncodingProvider | undefined, autoClassify: boolean): EncodedColumn {
format: Field.Format | undefined, encoderProvider: BinaryEncodingProvider | undefined, autoClassify: boolean): EncodedColumn {
const { array, allPresent, mask } = getFieldData(field, getArrayCtor(field, format), totalCount, data);

View File

@@ -19,10 +19,16 @@ export default class TextEncoder implements Encoder<string> {
private filter: Category.Filter = Category.DefaultFilter;
private formatter: Category.Formatter = Category.DefaultFormatter;
binaryEncodingProvider = void 0;
setFilter(filter?: Category.Filter) {
this.filter = filter || Category.DefaultFilter;
}
isCategoryIncluded(name: string) {
return this.filter.includeCategory(name);
}
setFormatter(formatter?: Category.Formatter) {
this.formatter = formatter || Category.DefaultFormatter;
}
@@ -32,7 +38,7 @@ export default class TextEncoder implements Encoder<string> {
StringBuilder.write(this.builder, `data_${(header || '').replace(/[ \n\t]/g, '').toUpperCase()}\n#\n`);
}
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx) {
writeCategory<Ctx>(category: Category<Ctx>, context?: Ctx, options?: Encoder.WriteCategoryOptions) {
if (this.encoded) {
throw new Error('The writer contents have already been encoded, no more writing.');
}
@@ -41,7 +47,7 @@ export default class TextEncoder implements Encoder<string> {
throw new Error('No data block created.');
}
if (!this.filter.includeCategory(category.name)) return;
if (!options?.ignoreFilter && !this.filter.includeCategory(category.name)) return;
const { instance, rowCount, source } = getCategoryInstanceData(category, context);
if (!rowCount) return;
@@ -63,6 +69,10 @@ export default class TextEncoder implements Encoder<string> {
}
}
getSize() {
return StringBuilder.getSize(this.builder);
}
getData() {
return StringBuilder.getString(this.builder);
}

View File

@@ -8,7 +8,8 @@ import Writer from './writer'
interface Encoder {
encode(): void,
writeTo(writer: Writer): void
writeTo(writer: Writer): void,
getSize(): number
}
export default Encoder

View File

@@ -45,13 +45,23 @@ export class BoundaryHelper {
}
}
includeStep(p: Vec3) {
includeSphere(s: Sphere3D) {
if (Sphere3D.hasExtrema(s)) {
for (const e of s.extrema) {
this.includePosition(e);
}
} else {
this.includePositionRadius(s.center, s.radius);
}
}
includePosition(p: Vec3) {
for (let i = 0, il = this.dir.length; i < il; ++i) {
this.computeExtrema(i, p)
}
}
includeSphereStep(center: Vec3, radius: number) {
includePositionRadius(center: Vec3, radius: number) {
for (let i = 0, il = this.dir.length; i < il; ++i) {
this.computeSphereExtrema(i, center, radius)
}
@@ -64,11 +74,21 @@ export class BoundaryHelper {
this.centroidHelper.finishedIncludeStep();
}
radiusStep(p: Vec3) {
radiusSphere(s: Sphere3D) {
if (Sphere3D.hasExtrema(s)) {
for (const e of s.extrema) {
this.radiusPosition(e)
}
} else {
this.radiusPositionRadius(s.center, s.radius);
}
}
radiusPosition(p: Vec3) {
this.centroidHelper.radiusStep(p);
}
radiusSphereStep(center: Vec3, radius: number) {
radiusPositionRadius(center: Vec3, radius: number) {
this.centroidHelper.radiusSphereStep(center, radius);
}

View File

@@ -28,13 +28,13 @@ export function getBoundary(data: PositionData): Boundary {
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
const i = OrderedSet.getAt(indices, t);
Vec3.set(p, x[i], y[i], z[i]);
boundaryHelper.includeSphereStep(p, (radius && radius[i]) || 0);
boundaryHelper.includePositionRadius(p, (radius && radius[i]) || 0);
}
boundaryHelper.finishedIncludeStep();
for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
const i = OrderedSet.getAt(indices, t);
Vec3.set(p, x[i], y[i], z[i]);
boundaryHelper.radiusSphereStep(p, (radius && radius[i]) || 0);
boundaryHelper.radiusPositionRadius(p, (radius && radius[i]) || 0);
}
const sphere = boundaryHelper.getSphere()

View File

@@ -46,7 +46,7 @@ export const GaussianDensitySchema = {
}
export const GaussianDensityShaderCode = ShaderCode(
gaussian_density_vert, gaussian_density_frag,
'gaussian-density', gaussian_density_vert, gaussian_density_frag,
{ standardDerivatives: false, fragDepth: false }
)

View File

@@ -28,7 +28,7 @@ namespace Sphere3D {
}
export function create(center: Vec3, radius: number): Sphere3D { return { center, radius }; }
export function zero(): Sphere3D { return { center: Vec3.zero(), radius: 0 }; }
export function zero(): Sphere3D { return { center: Vec3(), radius: 0 }; }
export function clone(a: Sphere3D): Sphere3D {
const out = create(Vec3.clone(a.center), a.radius)
@@ -36,6 +36,12 @@ namespace Sphere3D {
return out;
}
export function set(out: Sphere3D, center: Vec3, radius: number) {
Vec3.copy(out.center, center)
out.radius = radius
return out
}
export function copy(out: Sphere3D, a: Sphere3D) {
Vec3.copy(out.center, a.center)
out.radius = a.radius
@@ -128,6 +134,12 @@ namespace Sphere3D {
export function expandBySphere(out: Sphere3D, sphere: Sphere3D, by: Sphere3D) {
Vec3.copy(out.center, sphere.center)
out.radius = Math.max(sphere.radius, Vec3.distance(sphere.center, by.center) + by.radius)
if (hasExtrema(sphere) && hasExtrema(by)) {
setExtrema(out, [
...sphere.extrema.map(e => Vec3.clone(e)),
...by.extrema.map(e => Vec3.clone(e))
])
}
return out
}
@@ -163,6 +175,30 @@ namespace Sphere3D {
return (Math.abs(ar - br) <= EPSILON * Math.max(1.0, Math.abs(ar), Math.abs(br)) &&
Vec3.equals(a.center, b.center));
}
/**
* Check if `a` includes `b`, use `extrema` of `b` when available
*/
export function includes(a: Sphere3D, b: Sphere3D) {
if (hasExtrema(b)) {
for (const e of b.extrema) {
if (Vec3.distance(a.center, e) > a.radius) return false
}
return true
} else {
return Vec3.distance(a.center, b.center) + b.radius <= a.radius
}
}
/** Check if `a` and `b` are overlapping */
export function overlaps(a: Sphere3D, b: Sphere3D) {
return Vec3.distance(a.center, b.center) <= a.radius + b.radius
}
/** Get the signed distance of `a` and `b` */
export function distance(a: Sphere3D, b: Sphere3D) {
return Vec3.distance(a.center, b.center) - a.radius + b.radius
}
}
export { Sphere3D }

View File

@@ -35,7 +35,8 @@ namespace SpacegroupCell {
export const Zero: SpacegroupCell = create('P 1', Vec3.create(1, 1, 1), Vec3.create(Math.PI / 2, Math.PI / 2, Math.PI / 2));
/** True if 'P 1' with cellsize [1, 1, 1] */
export function isZero(cell: SpacegroupCell) {
export function isZero(cell?: SpacegroupCell) {
if (!cell) return true;
return cell.index === 0 && cell.size[0] === 1 && cell.size[1] === 1 && cell.size[1] === 1;
}
@@ -103,9 +104,9 @@ namespace Spacegroup {
);
}
export function getSymmetryOperator(spacegroup: Spacegroup, index: number, i: number, j: number, k: number): SymmetryOperator {
const operator = setOperatorMatrix(spacegroup, index, i, j, k, Mat4.zero());
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k), index);
export function getSymmetryOperator(spacegroup: Spacegroup, spgrOp: number, i: number, j: number, k: number): SymmetryOperator {
const operator = setOperatorMatrix(spacegroup, spgrOp, i, j, k, Mat4.zero());
return SymmetryOperator.create(`${spgrOp + 1}_${5 + i}${5 + j}${5 + k}`, operator, { hkl: Vec3.create(i, j, k), spgrOp });
}
const _translationRef = Vec3()
@@ -134,9 +135,9 @@ namespace Spacegroup {
* Get Symmetry operator for transformation around the given
* reference point `ref` in fractional coordinates
*/
export function getSymmetryOperatorRef(spacegroup: Spacegroup, index: number, i: number, j: number, k: number, ref: Vec3) {
const operator = setOperatorMatrixRef(spacegroup, index, i, j, k, ref, Mat4.zero());
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, { id: '', operList: [] }, '', Vec3.create(i, j, k), index);
export function getSymmetryOperatorRef(spacegroup: Spacegroup, spgrOp: number, i: number, j: number, k: number, ref: Vec3) {
const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
return SymmetryOperator.create(`${spgrOp + 1}_${5 + i}${5 + j}${5 + k}`, operator, { hkl: Vec3.create(i, j, k), spgrOp });
}
function getOperatorMatrix(ids: number[]) {

View File

@@ -11,12 +11,14 @@ import { defaults } from '../../mol-util';
interface SymmetryOperator {
readonly name: string,
readonly assembly: {
readonly assembly?: {
/** pointer to `pdbx_struct_assembly.id` or empty string */
readonly id: string
readonly id: string,
/** pointers to `pdbx_struct_oper_list.id` or empty list */
readonly operList: string[]
}
readonly operList: string[],
/** (arbitrary) unique id of the operator to be used in suffix */
readonly operId: number
},
/** pointer to `struct_ncs_oper.id` or empty string */
readonly ncsId: string,
@@ -29,22 +31,52 @@ interface SymmetryOperator {
// cache the inverse of the transform
readonly inverse: Mat4,
// optimize the identity case
readonly isIdentity: boolean
readonly isIdentity: boolean,
/**
* Suffix based on operator type.
* - Assembly: _assembly.operId
* - Crytal: -op_ijk
* - ncs: _ncsId
*/
readonly suffix: string
}
namespace SymmetryOperator {
export const DefaultName = '1_555'
export const Default: SymmetryOperator = create(DefaultName, Mat4.identity(), { id: '', operList: [] });
export const Default: SymmetryOperator = create(DefaultName, Mat4.identity());
export const RotationTranslationEpsilon = 0.005;
export function create(name: string, matrix: Mat4, assembly: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3, spgrOp?: number): SymmetryOperator {
export type CreateInfo = { assembly?: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3, spgrOp?: number }
export function create(name: string, matrix: Mat4, info?: CreateInfo): SymmetryOperator {
let { assembly, ncsId, hkl, spgrOp } = info || { };
const _hkl = hkl ? Vec3.clone(hkl) : Vec3.zero();
spgrOp = defaults(spgrOp, -1)
ncsId = ncsId || ''
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId };
spgrOp = defaults(spgrOp, -1);
ncsId = ncsId || '';
const suffix = getSuffix(info);
if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId, suffix };
if (!Mat4.isRotationAndTranslation(matrix, RotationTranslationEpsilon)) throw new Error(`Symmetry operator (${name}) must be a composition of rotation and translation.`);
return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, spgrOp, ncsId };
return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, spgrOp, ncsId, suffix };
}
function getSuffix(info?: CreateInfo) {
if (!info) return '';
if (info.assembly) {
return `_${info.assembly.operId}`;
}
if (typeof info.spgrOp !== 'undefined' && typeof info.hkl !== 'undefined' && info.spgrOp !== -1) {
const [i, j, k] = info.hkl;
return `-${info.spgrOp + 1}_${5 + i}${5 + j}${5 + k}`
}
if (info.ncsId) {
return `_${info.ncsId}`;
}
return '';
}
export function checkIfRotationAndTranslation(rot: Mat3, offset: Vec3) {
@@ -66,7 +98,7 @@ namespace SymmetryOperator {
}
}
Mat4.setTranslation(t, offset);
return create(name, t, { id: '', operList: [] }, ncsId);
return create(name, t, { ncsId });
}
const _q1 = Quat.identity(), _q2 = Quat.zero(), _q3 = Quat.zero(), _axis = Vec3.zero();
@@ -114,7 +146,7 @@ namespace SymmetryOperator {
*/
export function compose(first: SymmetryOperator, second: SymmetryOperator) {
const matrix = Mat4.mul(Mat4.zero(), second.matrix, first.matrix);
return create(second.name, matrix, second.assembly, second.ncsId, second.hkl, second.spgrOp);
return create(second.name, matrix, second);
}
export interface CoordinateMapper<T extends number> { (index: T, slot: Vec3): Vec3 }

View File

@@ -254,6 +254,31 @@ namespace Mat4 {
return out;
}
export function extractRotation(out: Mat4, mat: Mat4) {
const scaleX = 1 / Math.sqrt(mat[0] * mat[0] + mat[1] * mat[1] + mat[2] * mat[2]);
const scaleY = 1 / Math.sqrt(mat[4] * mat[4] + mat[5] * mat[5] + mat[6] * mat[6]);
const scaleZ = 1 / Math.sqrt(mat[8] * mat[8] + mat[9] * mat[9] + mat[10] * mat[10]);
out[0] = mat[0] * scaleX;
out[1] = mat[1] * scaleX;
out[2] = mat[2] * scaleX;
out[3] = 0;
out[4] = mat[4] * scaleY;
out[5] = mat[5] * scaleY;
out[6] = mat[6] * scaleY;
out[7] = 0;
out[8] = mat[8] * scaleZ;
out[9] = mat[9] * scaleZ;
out[10] = mat[10] * scaleZ;
out[11] = 0;
out[12] = 0;
out[13] = 0;
out[14] = 0;
out[15] = 1;
return out;
}
export function transpose(out: Mat4, a: Mat4) {
// If we are transposing ourselves we can skip a few steps but have to cache some values
if (out === a) {

View File

@@ -546,6 +546,8 @@ namespace Vec3 {
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)}]`;
}
export const origin: ReadonlyVec3 = Vec3.create(0, 0, 0)
export const unit: ReadonlyVec3 = Vec3.create(1, 1, 1)
export const negUnit: ReadonlyVec3 = Vec3.create(-1, -1, -1)

View File

@@ -26,7 +26,7 @@ namespace PrincipalAxes {
export function calculateMomentsAxes(positions: NumberArray): Axes3D {
if (positions.length === 3) {
return Axes3D.create(Vec3.fromArray(Vec3(), positions, 0), Vec3(), Vec3(), Vec3())
return Axes3D.create(Vec3.fromArray(Vec3(), positions, 0), Vec3.create(1, 0, 0), Vec3.create(0, 1, 0), Vec3.create(0, 1, 0))
}
const points = Matrix.fromArray(positions, 3, positions.length / 3)

View File

@@ -8,7 +8,7 @@
import { Column, Table } from '../../../mol-data/db';
import { Interval, Segmentation } from '../../../mol-data/int';
import UUID from '../../../mol-util/uuid';
import { ElementIndex } from '../../../mol-model/structure';
import { ElementIndex, ChainIndex } from '../../../mol-model/structure';
import { Model } from '../../../mol-model/structure/model/model';
import { AtomicConformation, AtomicData, AtomicHierarchy, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../../../mol-model/structure/model/properties/atomic';
import { getAtomicIndex } from '../../../mol-model/structure/model/properties/utils/atomic-index';
@@ -16,6 +16,12 @@ import { ElementSymbol } from '../../../mol-model/structure/model/types';
import { Entities } from '../../../mol-model/structure/model/properties/common';
import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived';
import { AtomSite } from './schema';
import { ModelFormat } from '../format';
import { SymmetryOperator } from '../../../mol-math/geometry';
import { MmcifFormat } from '../mmcif';
import { AtomSiteOperatorMappingSchema } from '../../../mol-model/structure/export/categories/atom_site_operator_mapping';
import { toDatabase } from '../../../mol-io/reader/cif/schema';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
function findHierarchyOffsets(atom_site: AtomSite) {
if (atom_site._rowCount === 0) return { residues: [], chains: [] };
@@ -95,14 +101,70 @@ function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
&& Table.areEqual(a.atoms, b.atoms)
}
function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {
function createChainOperatorMappingAndSubstituteNames(hierarchy: AtomicData, format: ModelFormat) {
const mapping = new Map<ChainIndex, SymmetryOperator>();
if (!MmcifFormat.is(format)) return mapping;
const { molstar_atom_site_operator_mapping: entries } = toDatabase(AtomSiteOperatorMappingSchema, format.data.frame);
if (entries._rowCount === 0) return mapping;
const labelMap = new Map<string, { name: string, operator: SymmetryOperator }>();
const authMap = new Map<string, string>();
for (let i = 0; i < entries._rowCount; i++) {
const assembly: SymmetryOperator['assembly'] = entries.assembly_operator_id.valueKind(i) === Column.ValueKind.Present
? { id: entries.assembly_id.value(i), operList: [], operId: entries.assembly_operator_id.value(i) }
: void 0;
const operator = SymmetryOperator.create(entries.operator_name.value(i), Mat4.identity(), {
assembly,
spgrOp: entries.symmetry_operator_index.valueKind(i) === Column.ValueKind.Present ? entries.symmetry_operator_index.value(i) : void 0,
hkl: Vec3.ofArray(entries.symmetry_hkl.value(i)),
ncsId: entries.ncs_id.value(i)
});
const suffix = entries.suffix.value(i);
const label = entries.label_asym_id.value(i);
labelMap.set(`${label}${suffix}`, { name: label, operator });
const auth = entries.auth_asym_id.value(i);
authMap.set(`${auth}${suffix}`, auth);
}
const { label_asym_id, auth_asym_id } = hierarchy.chains;
const mappedLabel: string[] = new Array(label_asym_id.rowCount);
const mappedAuth: string[] = new Array(label_asym_id.rowCount);
for (let i = 0 as ChainIndex; i < label_asym_id.rowCount; i++) {
const label = label_asym_id.value(i), auth = auth_asym_id.value(i);
if (!labelMap.has(label)) {
mappedLabel[i] = label;
mappedAuth[i] = auth;
continue;
}
const { name, operator } = labelMap.get(label)!;
mapping.set(i, operator);
mappedLabel[i] = name;
mappedAuth[i] = authMap.get(auth) || auth;
}
hierarchy.chains.label_asym_id = Column.ofArray({ array: mappedLabel, valueKind: hierarchy.chains.label_asym_id.valueKind, schema: hierarchy.chains.label_asym_id.schema });
hierarchy.chains.auth_asym_id = Column.ofArray({ array: mappedAuth, valueKind: hierarchy.chains.auth_asym_id.valueKind, schema: hierarchy.chains.auth_asym_id.schema });
return mapping;
}
function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], format: ModelFormat, previous?: Model) {
const hierarchyOffsets = findHierarchyOffsets(atom_site);
const hierarchyData = createHierarchyData(atom_site, sourceIndex, hierarchyOffsets);
const chainOperatorMapping = createChainOperatorMappingAndSubstituteNames(hierarchyData, format);
if (previous && isHierarchyDataEqual(previous.atomicHierarchy, hierarchyData)) {
return {
sameAsPrevious: true,
hierarchy: previous.atomicHierarchy,
chainOperatorMapping
};
}
@@ -114,11 +176,11 @@ function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, en
const index = getAtomicIndex(hierarchyData, entities, hierarchySegments);
const derived = getAtomicDerivedData(hierarchyData, index, chemicalComponentMap);
const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, index, derived };
return { sameAsPrevious: false, hierarchy };
return { sameAsPrevious: false, hierarchy, chainOperatorMapping };
}
export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], previous?: Model) {
const { sameAsPrevious, hierarchy } = getAtomicHierarchy(atom_site, sourceIndex, entities, chemicalComponentMap, previous)
export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, chemicalComponentMap: Model['properties']['chemicalComponentMap'], format: ModelFormat, previous?: Model) {
const { sameAsPrevious, hierarchy, chainOperatorMapping } = getAtomicHierarchy(atom_site, sourceIndex, entities, chemicalComponentMap, format, previous)
const conformation = getConformation(atom_site)
return { sameAsPrevious, hierarchy, conformation };
return { sameAsPrevious, hierarchy, conformation, chainOperatorMapping };
}

View File

@@ -39,7 +39,7 @@ export async function createModels(data: BasicData, format: ModelFormat, ctx: Ru
/** Standard atomic model */
function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex: Column<number>, entities: Entities, properties: Model['properties'], format: ModelFormat, previous?: Model): Model {
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, properties.chemicalComponentMap, previous);
const atomic = getAtomicHierarchyAndConformation(atom_site, sourceIndex, entities, properties.chemicalComponentMap, format, previous);
const modelNum = atom_site.pdbx_PDB_model_num.value(0)
if (previous && atomic.sameAsPrevious) {
return {
@@ -75,6 +75,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
atomicHierarchy: atomic.hierarchy,
atomicConformation: atomic.conformation,
atomicRanges,
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
coarseHierarchy: coarse.hierarchy,
coarseConformation: coarse.conformation,
properties,
@@ -86,7 +87,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
/** Integrative model with atomic/coarse parts */
function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Model['properties'], format: ModelFormat): Model {
const atomic = getAtomicHierarchyAndConformation(ihm.atom_site, ihm.atom_site_sourceIndex, ihm.entities, properties.chemicalComponentMap);
const atomic = getAtomicHierarchyAndConformation(ihm.atom_site, ihm.atom_site_sourceIndex, ihm.entities, properties.chemicalComponentMap, format);
const coarse = getCoarse(ihm, properties);
const sequence = getSequence(data, ihm.entities, atomic.hierarchy, coarse.hierarchy)
const atomicRanges = getAtomicRanges(atomic.hierarchy, ihm.entities, atomic.conformation, sequence)
@@ -113,6 +114,7 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
atomicHierarchy: atomic.hierarchy,
atomicConformation: atomic.conformation,
atomicRanges,
atomicChainOperatorMappinng: atomic.chainOperatorMapping,
coarseHierarchy: coarse.hierarchy,
coarseConformation: coarse.conformation,
properties,

View File

@@ -117,7 +117,7 @@ function getAssemblyOperators(matrices: Matrices, operatorNames: string[][], sta
Mat4.mul(m, m, matrices.get(op[i])!);
}
index++
operators[operators.length] = SymmetryOperator.create(`ASM_${index}`, m, { id: assemblyId, operList: op });
operators[operators.length] = SymmetryOperator.create(`ASM_${index}`, m, { assembly: { id: assemblyId, operId: index, operList: op } });
}
return operators;

View File

@@ -107,23 +107,25 @@ namespace CustomElementProperty {
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !!modelProperty.get(ctx.structure.models[0]).value,
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? modelProperty.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (_, data: ThemeDataContext) => data.structure && data.structure.models[0].customProperties.reference(modelProperty.descriptor, false)
detach: (data: ThemeDataContext) => data.structure && data.structure.models[0].customProperties.reference(modelProperty.descriptor, false)
}
}
}
function createLabelProvider<T>(modelProperty: CustomModelProperty.Provider<{}, Value<T>>, getLabel: (p: T) => string | undefined) {
return function(loci: Loci): string | undefined {
if (loci.kind === 'element-loci') {
const e = loci.elements[0];
if (!e) return
const data = modelProperty.get(e.unit.model).value
const element = e.unit.elements[OrderedSet.start(e.indices)]
const value = data?.get(element)
if (value === undefined) return
return getLabel(value);
function createLabelProvider<T>(modelProperty: CustomModelProperty.Provider<{}, Value<T>>, getLabel: (p: T) => string | undefined): LociLabelProvider {
return {
label: (loci: Loci) => {
if (loci.kind === 'element-loci') {
const e = loci.elements[0];
if (!e || !e.unit.model.customProperties.hasReference(modelProperty.descriptor)) return
const data = modelProperty.get(e.unit.model).value
const element = e.unit.elements[OrderedSet.start(e.indices)]
const value = data?.get(element)
if (value === undefined) return
return getLabel(value);
}
return
}
return
}
}
}

View File

@@ -54,7 +54,7 @@ namespace CustomModelProperty {
attach: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<Params>> = {}, addRef) => {
if (addRef) data.customProperties.reference(builder.descriptor, true);
const property = get(data)
const p = { ...property.props, ...props }
const p = PD.merge(builder.defaultParams, property.props, props)
if (property.data.value && PD.areEqual(builder.defaultParams, property.props, p)) return
const value = await builder.obtain(ctx, data, p)
data.customProperties.add(builder.descriptor);
@@ -64,12 +64,13 @@ namespace CustomModelProperty {
get: (data: Model) => get(data)?.data,
set: (data: Model, props: Partial<PD.Values<Params>> = {}) => {
const property = get(data)
const p = { ...property.props, ...props }
const p = PD.merge(builder.defaultParams, property.props, props)
if (!PD.areEqual(builder.defaultParams, property.props, p)) {
// this invalidates property.value
set(data, p, undefined)
}
}
},
props: (data: Model) => get(data).props,
}
}
}

View File

@@ -35,6 +35,7 @@ namespace CustomProperty {
readonly ref: (data: Data, add: boolean) => void
readonly get: (data: Data) => ValueBox<Value | undefined>
readonly set: (data: Data, props: PD.Values<Params>, value?: Value) => void
readonly props: (data: Data) => PD.Values<Params>
}
export class Registry<Data> {

View File

@@ -54,8 +54,9 @@ namespace CustomStructureProperty {
attach: async (ctx: CustomProperty.Context, data: Structure, props: Partial<PD.Values<Params>> = {}, addRef) => {
if (addRef) data.customPropertyDescriptors.reference(builder.descriptor, true);
if (builder.type === 'root') data = data.root
const rootProps = get(data.root).props
const property = get(data)
const p = { ...property.props, ...props }
const p = PD.merge(builder.defaultParams, rootProps, props)
if (property.data.value && PD.areEqual(builder.defaultParams, property.props, p)) return
const value = await builder.obtain(ctx, data, p)
data.customPropertyDescriptors.add(builder.descriptor);
@@ -66,12 +67,13 @@ namespace CustomStructureProperty {
set: (data: Structure, props: Partial<PD.Values<Params>> = {}, value?: Value) => {
if (builder.type === 'root') data = data.root
const property = get(data)
const p = { ...property.props, ...props }
const p = PD.merge(builder.defaultParams, property.props, props)
if (!PD.areEqual(builder.defaultParams, property.props, p)) {
// this invalidates property.value
set(data, p, value)
}
}
},
props: (data: Structure) => get(data).props,
}
}
}

View File

@@ -48,6 +48,6 @@ export const InteractionsRepresentationProvider = StructureRepresentationProvide
isApplicable: (structure: Structure) => structure.elementCount > 0,
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, structure: Structure) => InteractionsProvider.attach(ctx, structure, void 0, true),
detach: (_, data) => InteractionsProvider.ref(data, false)
detach: (data) => InteractionsProvider.ref(data, false)
}
})

View File

@@ -5,7 +5,6 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ColorListName, ColorListOptionsScale } from '../../../mol-util/color/lists'
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
import { Color, ColorScale } from '../../../mol-util/color'
import { ThemeDataContext } from '../../../mol-theme/theme'
@@ -20,7 +19,7 @@ const DefaultColor = Color(0xFAFAFA)
const Description = 'Assigns a color based on the relative accessible surface area of a residue.'
export const AccessibleSurfaceAreaColorThemeParams = {
list: PD.ColorList<ColorListName>('rainbow', ColorListOptionsScale)
list: PD.ColorList('rainbow', { presetKind: 'scale' })
}
export type AccessibleSurfaceAreaColorThemeParams = typeof AccessibleSurfaceAreaColorThemeParams
export function getAccessibleSurfaceAreaColorThemeParams(ctx: ThemeDataContext) {
@@ -30,7 +29,7 @@ export function AccessibleSurfaceAreaColorTheme(ctx: ThemeDataContext, props: PD
let color: LocationColor
const scale = ColorScale.create({
listOrName: props.list,
listOrName: props.list.colors,
minLabel: 'buried',
maxLabel: 'exposed',
domain: [0.0, 1.0]
@@ -74,6 +73,6 @@ export const AccessibleSurfaceAreaColorThemeProvider: ColorTheme.Provider<Access
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AccessibleSurfaceAreaProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(AccessibleSurfaceAreaProvider.descriptor, false)
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(AccessibleSurfaceAreaProvider.descriptor, false)
}
}

View File

@@ -116,6 +116,6 @@ export const InteractionTypeColorThemeProvider: ColorTheme.Provider<InteractionT
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? InteractionsProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(InteractionsProvider.descriptor, false)
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(InteractionsProvider.descriptor, false)
}
}

View File

@@ -8,7 +8,6 @@ import { Color, ColorScale } from '../../../mol-util/color';
import { Location } from '../../../mol-model/location';
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
import { ThemeDataContext } from '../../../mol-theme/theme';
import { ColorListName, ColorListOptionsScale } from '../../../mol-util/color/lists';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { CustomProperty } from '../../common/custom-property';
import { CrossLinkRestraintProvider, CrossLinkRestraint } from './property';
@@ -18,7 +17,7 @@ const Description = 'Colors cross-links by the deviation of the observed distanc
export const CrossLinkColorThemeParams = {
domain: PD.Interval([0.5, 1.5], { step: 0.01 }),
list: PD.ColorList<ColorListName>('red-grey', ColorListOptionsScale),
list: PD.ColorList('red-grey', { presetKind: 'scale' }),
}
export type CrossLinkColorThemeParams = typeof CrossLinkColorThemeParams
export function getCrossLinkColorThemeParams(ctx: ThemeDataContext) {
@@ -34,7 +33,7 @@ export function CrossLinkColorTheme(ctx: ThemeDataContext, props: PD.Values<Cros
if (crossLinkRestraints) {
scale = ColorScale.create({
domain: props.domain,
listOrName: props.list
listOrName: props.list.colors
})
const scaleColor = scale.color
@@ -71,6 +70,6 @@ export const CrossLinkColorThemeProvider: ColorTheme.Provider<CrossLinkColorThem
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && CrossLinkRestraint.isApplicable(ctx.structure),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? CrossLinkRestraintProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(CrossLinkRestraintProvider.descriptor, false)
}
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(CrossLinkRestraintProvider.descriptor, false)
}
}

View File

@@ -144,6 +144,6 @@ export const CrossLinkRestraintRepresentationProvider = StructureRepresentationP
isApplicable: (structure: Structure) => CrossLinkRestraint.isApplicable(structure),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, structure: Structure) => CrossLinkRestraintProvider.attach(ctx, structure, void 0, true),
detach: (_, data) => CrossLinkRestraintProvider.ref(data, false)
detach: (data) => CrossLinkRestraintProvider.ref(data, false)
}
})

View File

@@ -105,6 +105,6 @@ export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Param
isApplicable: (ctx: ThemeDataContext) => StructureQualityReport.isApplicable(ctx.structure?.models[0]),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(StructureQualityReportProvider.descriptor, false)
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(StructureQualityReportProvider.descriptor, false)
}
}

View File

@@ -8,7 +8,7 @@ import { AssemblySymmetryQuery, AssemblySymmetryQueryVariables } from './graphql
import query from './graphql/symmetry.gql';
import { ParamDefinition as PD } from '../../mol-util/param-definition'
import { CustomPropertyDescriptor, Structure } from '../../mol-model/structure';
import { CustomPropertyDescriptor, Structure, Model } from '../../mol-model/structure';
import { Database as _Database, Column } from '../../mol-data/db'
import { GraphQLClient } from '../../mol-util/graphql-client';
import { CustomProperty } from '../common/custom-property';
@@ -26,6 +26,18 @@ const BiologicalAssemblyNames = new Set([
'software_defined_assembly'
])
export function isBiologicalAssembly(structure: Structure): boolean {
if (!MmcifFormat.is(structure.models[0].sourceData)) return false
const mmcif = structure.models[0].sourceData.data.db
if (!mmcif.pdbx_struct_assembly.details.isDefined) return false
const id = structure.units[0].conformation.operator.assembly?.id || ''
if (id === '' || id === 'deposited') return true
const indices = Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id)
if (indices.length !== 1) return false
const details = mmcif.pdbx_struct_assembly.details.value(indices[0])
return BiologicalAssemblyNames.has(details)
}
export namespace AssemblySymmetry {
export enum Tag {
Cluster = 'rcsb-assembly-symmetry-cluster',
@@ -35,27 +47,19 @@ export namespace AssemblySymmetry {
export const DefaultServerUrl = 'https://data-beta.rcsb.org/graphql'
export function isApplicable(structure?: Structure): boolean {
// check if structure is from pdb entry
if (!structure || structure.models.length !== 1 || !MmcifFormat.is(structure.models[0].sourceData) || (!structure.models[0].sourceData.data.db.database_2.database_id.isDefined &&
structure.models[0].entryId.length !== 4)) return false
// check if assembly is 'biological'
const mmcif = structure.models[0].sourceData.data.db
if (!mmcif.pdbx_struct_assembly.details.isDefined) return false
const id = structure.units[0].conformation.operator.assembly.id
if (id === '' || id === 'deposited') return true
const indices = Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id)
if (indices.length !== 1) return false
const details = mmcif.pdbx_struct_assembly.details.value(indices[0])
return BiologicalAssemblyNames.has(details)
return (
!!structure && structure.models.length === 1 &&
Model.isFromPdbArchive(structure.models[0]) &&
isBiologicalAssembly(structure)
)
}
export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryProps): Promise<AssemblySymmetryValue> {
export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<AssemblySymmetryDataValue> {
if (!isApplicable(structure)) return []
const client = new GraphQLClient(props.serverUrl, ctx.fetch)
const variables: AssemblySymmetryQueryVariables = {
assembly_id: structure.units[0].conformation.operator.assembly.id || 'deposited',
assembly_id: structure.units[0].conformation.operator.assembly?.id || 'deposited',
entry_id: structure.units[0].model.entryId
}
const result = await client.request<AssemblySymmetryQuery>(ctx.runtime, query, variables)
@@ -64,23 +68,23 @@ export namespace AssemblySymmetry {
console.error('expected `rcsb_struct_symmetry` field')
return []
}
return result.assembly.rcsb_struct_symmetry as AssemblySymmetryValue
return result.assembly.rcsb_struct_symmetry as AssemblySymmetryDataValue
}
export type RotationAxes = ReadonlyArray<{ order: number, start: ReadonlyVec3, end: ReadonlyVec3 }>
export function isRotationAxes(x: AssemblySymmetryValue[0]['rotation_axes']): x is RotationAxes {
export function isRotationAxes(x: AssemblySymmetryValue['rotation_axes']): x is RotationAxes {
return !!x && x.length > 0
}
}
export function getSymmetrySelectParam(structure?: Structure) {
const param = PD.Select<number>(-1, [[-1, 'No Symmetries']])
const param = PD.Select<number>(0, [[0, 'First Symmetry']])
if (structure) {
const assemblySymmetry = AssemblySymmetryProvider.get(structure).value
if (assemblySymmetry) {
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value
if (assemblySymmetryData) {
const options: [number, string][] = []
for (let i = 0, il = assemblySymmetry.length; i < il; ++i) {
const { symbol, kind } = assemblySymmetry[i]
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
const { symbol, kind } = assemblySymmetryData[i]
if (symbol !== 'C1') {
options.push([ i, `${i + 1}: ${symbol} ${kind}` ])
}
@@ -94,13 +98,46 @@ export function getSymmetrySelectParam(structure?: Structure) {
return param
}
export const AssemblySymmetryParams = {
//
export const AssemblySymmetryDataParams = {
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl, { description: 'GraphQL endpoint URL' })
}
export type AssemblySymmetryDataParams = typeof AssemblySymmetryDataParams
export type AssemblySymmetryDataProps = PD.Values<AssemblySymmetryDataParams>
export type AssemblySymmetryDataValue = NonNullableArray<NonNullable<NonNullable<AssemblySymmetryQuery['assembly']>['rcsb_struct_symmetry']>>
export const AssemblySymmetryDataProvider: CustomStructureProperty.Provider<AssemblySymmetryDataParams, AssemblySymmetryDataValue> = CustomStructureProperty.createProvider({
label: 'Assembly Symmetry Data',
descriptor: CustomPropertyDescriptor({
name: 'rcsb_struct_symmetry_data',
// TODO `cifExport` and `symbol`
}),
type: 'root',
defaultParams: AssemblySymmetryDataParams,
getParams: (data: Structure) => AssemblySymmetryDataParams,
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryDataProps>) => {
const p = { ...PD.getDefaultValues(AssemblySymmetryDataParams), ...props }
return await AssemblySymmetry.fetch(ctx, data, p)
}
})
//
function getAssemblySymmetryParams(data?: Structure) {
return {
... AssemblySymmetryDataParams,
symmetryIndex: getSymmetrySelectParam(data)
}
}
export const AssemblySymmetryParams = getAssemblySymmetryParams()
export type AssemblySymmetryParams = typeof AssemblySymmetryParams
export type AssemblySymmetryProps = PD.Values<AssemblySymmetryParams>
export type AssemblySymmetryValue = NonNullableArray<NonNullable<NonNullable<AssemblySymmetryQuery['assembly']>['rcsb_struct_symmetry']>>
export type AssemblySymmetryValue = AssemblySymmetryDataValue[0]
export const AssemblySymmetryProvider: CustomStructureProperty.Provider<AssemblySymmetryParams, AssemblySymmetryValue> = CustomStructureProperty.createProvider({
label: 'Assembly Symmetry',
@@ -110,10 +147,14 @@ export const AssemblySymmetryProvider: CustomStructureProperty.Provider<Assembly
}),
type: 'root',
defaultParams: AssemblySymmetryParams,
getParams: (data: Structure) => AssemblySymmetryParams,
getParams: getAssemblySymmetryParams,
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryProps>) => {
const p = { ...PD.getDefaultValues(AssemblySymmetryParams), ...props }
return await AssemblySymmetry.fetch(ctx, data, p)
const p = { ...PD.getDefaultValues(getAssemblySymmetryParams(data)), ...props }
await AssemblySymmetryDataProvider.attach(ctx, data, p)
const assemblySymmetryData = AssemblySymmetryDataProvider.get(data).value
const assemblySymmetry = assemblySymmetryData?.[p.symmetryIndex]
if (!assemblySymmetry) new Error(`No assembly symmetry found for index ${p.symmetryIndex}`)
return assemblySymmetry
}
})

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { AssemblySymmetryValue, getSymmetrySelectParam, AssemblySymmetryProvider, AssemblySymmetry } from '../assembly-symmetry';
import { AssemblySymmetryValue, AssemblySymmetryProvider, AssemblySymmetry } from '../assembly-symmetry';
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
import { addCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
@@ -51,7 +51,6 @@ function axesColorHelp(value: { name: string, params: {} }) {
const SharedParams = {
...Mesh.Params,
scale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
symmetryIndex: getSymmetrySelectParam(),
}
const AxesParams = {
@@ -87,7 +86,7 @@ export type AssemblySymmetryProps = PD.Values<AssemblySymmetryParams>
//
function getAssemblyName(s: Structure) {
const { id } = s.units[0].conformation.operator.assembly
const id = s.units[0].conformation.operator.assembly?.id || ''
return isInteger(id) ? `Assembly ${id}` : id
}
@@ -113,9 +112,9 @@ const getOrderPrimitive = memoize1((order: number): Primitive | undefined => {
})
function getAxesMesh(data: AssemblySymmetryValue, props: PD.Values<AxesParams>, mesh?: Mesh) {
const { symmetryIndex, scale } = props
const { scale } = props
const { rotation_axes } = data[symmetryIndex]
const { rotation_axes } = data
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
const { start, end } = rotation_axes[0]
@@ -158,7 +157,7 @@ function getAxesShape(ctx: RuntimeContext, data: Structure, props: AssemblySymme
const geo = getAxesMesh(assemblySymmetry, props, shape && shape.geometry);
const getColor = (groupId: number) => {
if (props.axesColor.name === 'byOrder') {
const { rotation_axes } = assemblySymmetry[props.symmetryIndex]
const { rotation_axes } = assemblySymmetry
const order = rotation_axes![groupId]?.order
if (order === 2) return OrderColors[2]
else if (order === 3) return OrderColors[3]
@@ -168,7 +167,7 @@ function getAxesShape(ctx: RuntimeContext, data: Structure, props: AssemblySymme
}
}
const getLabel = (groupId: number) => {
const { type, symbol, kind, rotation_axes } = assemblySymmetry[props.symmetryIndex]
const { type, symbol, kind, rotation_axes } = assemblySymmetry
const order = rotation_axes![groupId]?.order
return [
`<small>${data.model.entryId}</small>`,
@@ -279,9 +278,9 @@ function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.Rota
function getCageMesh(data: Structure, props: PD.Values<CageParams>, mesh?: Mesh) {
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!
const { symmetryIndex, scale } = props
const { scale } = props
const { rotation_axes, symbol } = assemblySymmetry[symmetryIndex]
const { rotation_axes, symbol } = assemblySymmetry
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
const cage = getSymbolCage(symbol)
@@ -308,7 +307,7 @@ function getCageShape(ctx: RuntimeContext, data: Structure, props: AssemblySymme
return props.cageColor
}
const getLabel = (groupId: number) => {
const { type, symbol, kind } = assemblySymmetry[props.symmetryIndex]
const { type, symbol, kind } = assemblySymmetry
data.model.entryId
return [
`<small>${data.model.entryId}</small>`,

View File

@@ -289,6 +289,6 @@ export const ClashesRepresentationProvider = StructureRepresentationProvider({
isApplicable: (structure: Structure) => structure.elementCount > 0,
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, structure: Structure) => ClashesProvider.attach(ctx, structure, void 0, true),
detach: (_, data) => ClashesProvider.ref(data, false)
detach: (data) => ClashesProvider.ref(data, false)
}
})

View File

@@ -7,7 +7,7 @@
import { ThemeDataContext } from '../../../mol-theme/theme';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
import { AssemblySymmetryProvider, AssemblySymmetry, getSymmetrySelectParam } from '../assembly-symmetry';
import { AssemblySymmetryProvider, AssemblySymmetry } from '../assembly-symmetry';
import { Color } from '../../../mol-util/color';
import { Unit, StructureElement, StructureProperties } from '../../../mol-model/structure';
import { Location } from '../../../mol-model/location';
@@ -32,13 +32,11 @@ function clusterMemberKey(asymId: string, operList: string[]) {
}
export const AssemblySymmetryClusterColorThemeParams = {
...getPaletteParams({ scaleList: 'red-yellow-blue' }),
symmetryIndex: getSymmetrySelectParam(),
...getPaletteParams({ colorList: 'red-yellow-blue' }),
}
export type AssemblySymmetryClusterColorThemeParams = typeof AssemblySymmetryClusterColorThemeParams
export function getAssemblySymmetryClusterColorThemeParams(ctx: ThemeDataContext) {
const params = PD.clone(AssemblySymmetryClusterColorThemeParams)
params.symmetryIndex = getSymmetrySelectParam(ctx.structure)
return params
}
@@ -46,11 +44,10 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
let color: LocationColor = () => DefaultColor
let legend: ScaleLegend | TableLegend | undefined
const { symmetryIndex } = props
const assemblySymmetry = ctx.structure && AssemblySymmetryProvider.get(ctx.structure)
const contextHash = assemblySymmetry?.version
const clusters = assemblySymmetry?.value?.[symmetryIndex]?.clusters
const clusters = assemblySymmetry?.value?.clusters
if (clusters?.length && ctx.structure) {
const clusterByMember = new Map<string, number>()
@@ -69,11 +66,12 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
const palette = getPalette(clusters.length, props)
legend = palette.legend
const _emptyList: any[] = [];
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))
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly?.operList || _emptyList))
return cluster !== undefined ? palette.color(cluster) : DefaultColor
}
return DefaultColor
@@ -101,6 +99,6 @@ export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<Asse
isApplicable: (ctx: ThemeDataContext) => AssemblySymmetry.isApplicable(ctx.structure),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
detach: (_, data) => data.structure && data.structure.customPropertyDescriptors.reference(AssemblySymmetryProvider.descriptor, false)
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(AssemblySymmetryProvider.descriptor, false)
}
}

View File

@@ -70,6 +70,6 @@ export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationRep
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
}
}

View File

@@ -110,6 +110,6 @@ export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQua
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
}
}

View File

@@ -61,6 +61,6 @@ export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}, Validati
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
detach: (_, data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
}
}

View File

@@ -202,8 +202,7 @@ function createInterUnitClashes(structure: Structure, clashes: ValidationReport[
const builder = new InterUnitGraph.Builder<Unit.Atomic, UnitIndex, InterUnitClashesProps>()
const { a, b, edgeProps: { id, magnitude, distance } } = clashes
const pA = Vec3()
const pB = Vec3()
const pA = Vec3(), pB = Vec3()
Structure.eachUnitPair(structure, (unitA: Unit, unitB: Unit) => {
const elementsA = unitA.elements
@@ -248,6 +247,8 @@ function createIntraUnitClashes(unit: Unit.Atomic, clashes: ValidationReport['cl
const magnitudes: number[] = []
const distances: number[] = []
const pA = Vec3(), pB = Vec3()
const { elements } = unit
const { a, b, edgeCount, edgeProps } = clashes
@@ -257,11 +258,17 @@ function createIntraUnitClashes(unit: Unit.Atomic, clashes: ValidationReport['cl
let indexB = SortedArray.indexOf(elements, b[i])
if (indexA !== -1 && indexB !== -1) {
aIndices.push(indexA as UnitIndex)
bIndices.push(indexB as UnitIndex)
ids.push(edgeProps.id[i])
magnitudes.push(edgeProps.magnitude[i])
distances.push(edgeProps.distance[i])
unit.conformation.position(a[i], pA)
unit.conformation.position(b[i], pB)
// check actual distance to avoid clashes between unrelated chain instances
if (equalEps(edgeProps.distance[i], Vec3.distance(pA, pB), 0.1)) {
aIndices.push(indexA as UnitIndex)
bIndices.push(indexB as UnitIndex)
ids.push(edgeProps.id[i])
magnitudes.push(edgeProps.magnitude[i])
distances.push(edgeProps.distance[i])
}
}
}
@@ -279,7 +286,6 @@ function createIntraUnitClashes(unit: Unit.Atomic, clashes: ValidationReport['cl
}
function createClashes(structure: Structure, clashes: ValidationReport['clashes']): Clashes {
const intraUnit = IntMap.Mutable<IntraUnitClashes>()
for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {

View File

@@ -71,9 +71,9 @@ namespace Loci {
export function getBundleBoundingSphere(bundle: Bundle<any>): Sphere3D {
const spheres = bundle.loci.map(l => getBoundingSphere(l)).filter(s => !!s) as Sphere3D[]
boundaryHelper.reset();
for (const s of spheres) boundaryHelper.includeSphereStep(s.center, s.radius);
for (const s of spheres) boundaryHelper.includePositionRadius(s.center, s.radius);
boundaryHelper.finishedIncludeStep();
for (const s of spheres) boundaryHelper.radiusSphereStep(s.center, s.radius);
for (const s of spheres) boundaryHelper.radiusPositionRadius(s.center, s.radius);
return boundaryHelper.getSphere();
}
@@ -192,21 +192,6 @@ namespace Loci {
? StructureElement.Loci.extendToWholeChains(loci)
: loci
},
'elementInstances': (loci: Loci) => {
return StructureElement.Loci.is(loci)
? StructureElement.Loci.extendToAllInstances(loci)
: loci
},
'residueInstances': (loci: Loci) => {
return StructureElement.Loci.is(loci)
? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeResidues(loci, true))
: loci
},
'chainInstances': (loci: Loci) => {
return StructureElement.Loci.is(loci)
? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeChains(loci))
: loci
},
'entity': (loci: Loci) => {
return StructureElement.Loci.is(loci)
? StructureElement.Loci.extendToWholeEntities(loci)
@@ -224,9 +209,25 @@ namespace Loci {
? Shape.Loci(loci.shape)
: loci
},
'elementInstances': (loci: Loci) => {
return StructureElement.Loci.is(loci)
? StructureElement.Loci.extendToAllInstances(loci)
: loci
},
'residueInstances': (loci: Loci) => {
return StructureElement.Loci.is(loci)
? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeResidues(loci, true))
: loci
},
'chainInstances': (loci: Loci) => {
return StructureElement.Loci.is(loci)
? StructureElement.Loci.extendToAllInstances(StructureElement.Loci.extendToWholeChains(loci))
: loci
},
}
export type Granularity = keyof typeof Granularity
export const GranularityOptions = ParamDefinition.objectToOptions(Granularity, k => {
if (k.indexOf('Instances') > 0) return [stringToWords(k), 'With Symmetry'];
switch (k) {
case 'element': return'Atom/Coarse Element'
case 'structure': return'Structure/Shape'

View File

@@ -12,6 +12,13 @@ import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { Sphere3D } from '../../mol-math/geometry';
import { CentroidHelper } from '../../mol-math/geometry/centroid-helper';
import { GroupMapping } from '../../mol-geo/util';
import { ShapeGroupSizeTheme } from '../../mol-theme/size/shape-group';
import { ShapeGroupColorTheme } from '../../mol-theme/color/shape-group';
import { Theme } from '../../mol-theme/theme';
import { TransformData, createTransform as _createTransform } from '../../mol-geo/geometry/transform-data';
import { createRenderObject as _createRenderObject, getNextMaterialId } from '../../mol-gl/render-object';
import { ParamDefinition as PD } from '../../mol-util/param-definition'
import { LocationIterator } from '../../mol-geo/util/location-iterator';
export interface Shape<G extends Geometry = Geometry> {
/** A uuid to identify a shape object */
@@ -49,6 +56,46 @@ export namespace Shape {
}
}
export function getTheme(shape: Shape): Theme {
return {
color: ShapeGroupColorTheme({ shape }, {}),
size: ShapeGroupSizeTheme({ shape }, {})
}
}
export function groupIterator(shape: Shape): LocationIterator {
const instanceCount = shape.transforms.length
const location = ShapeGroup.Location(shape)
const getLocation = (groupIndex: number, instanceIndex: number) => {
location.group = groupIndex
location.instance = instanceIndex
return location
}
return LocationIterator(shape.groupCount, instanceCount, getLocation)
}
export function createTransform(transforms: Mat4[], transformData?: TransformData) {
const transformArray = transformData && transformData.aTransform.ref.value.length >= transforms.length * 16 ? transformData.aTransform.ref.value : new Float32Array(transforms.length * 16)
for (let i = 0, il = transforms.length; i < il; ++i) {
Mat4.toArray(transforms[i], transformArray, i * 16)
}
return _createTransform(transformArray, transforms.length, transformData)
}
export function createRenderObject<G extends Geometry>(shape: Shape<G>, props: PD.Values<Geometry.Params<G>>) {
props
const theme = Shape.getTheme(shape)
const utils = Geometry.getUtils(shape.geometry)
const materialId = getNextMaterialId()
const locationIt = groupIterator(shape)
const transform = Shape.createTransform(shape.transforms)
const values = utils.createValues(shape.geometry, transform, locationIt, theme, props)
const state = utils.createRenderableState(props)
return _createRenderObject(shape.geometry.kind, values, state, materialId)
}
export interface Loci { readonly kind: 'shape-loci', readonly shape: Shape }
export function Loci(shape: Shape): Loci { return { kind: 'shape-loci', shape } }
export function isLoci(x: any): x is Loci { return !!x && x.kind === 'shape-loci' }

View File

@@ -69,6 +69,6 @@ class CustomProperties {
}
has(desc: CustomPropertyDescriptor<any>): boolean {
return this._refs.has(desc);
return this._set.has(desc);
}
}

View File

@@ -12,6 +12,22 @@ import CifField = CifWriter.Field
import CifCategory = CifWriter.Category
import E = CifWriter.Encodings
const _label_asym_id = P.chain.label_asym_id;
function atom_site_label_asym_id(e: StructureElement.Location) {
const l = _label_asym_id(e);
const suffix = e.unit.conformation.operator.suffix;
if (!suffix) return l;
return l + suffix;
}
const _auth_asym_id = P.chain.auth_asym_id;
function atom_site_auth_asym_id(e: StructureElement.Location) {
const l = _auth_asym_id(e);
const suffix = e.unit.conformation.operator.suffix;
if (!suffix) return l;
return l + suffix;
}
const atom_site_fields = CifWriter.fields<StructureElement.Location, Structure>()
.str('group_PDB', P.residue.group_PDB)
.index('id')
@@ -29,14 +45,14 @@ const atom_site_fields = CifWriter.fields<StructureElement.Location, Structure>(
.str('label_alt_id', P.atom.label_alt_id)
.str('pdbx_PDB_ins_code', P.residue.pdbx_PDB_ins_code)
.str('label_asym_id', P.chain.label_asym_id)
.str('label_asym_id', atom_site_label_asym_id)
.str('label_entity_id', P.chain.label_entity_id)
.float('Cartn_x', P.atom.x, { digitCount: 3, encoder: E.fixedPoint3 })
.float('Cartn_y', P.atom.y, { digitCount: 3, encoder: E.fixedPoint3 })
.float('Cartn_z', P.atom.z, { digitCount: 3, encoder: E.fixedPoint3 })
.float('occupancy', P.atom.occupancy, { digitCount: 2, encoder: E.fixedPoint2 })
.int('pdbx_formal_charge', P.atom.pdbx_formal_charge, {
.int('pdbx_formal_charge', P.atom.pdbx_formal_charge, {
encoder: E.deltaRLE,
valueKind: (k, d) => k.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.valueKind(k.element)
})
@@ -44,12 +60,12 @@ const atom_site_fields = CifWriter.fields<StructureElement.Location, Structure>(
.str('auth_atom_id', P.atom.auth_atom_id)
.str('auth_comp_id', P.residue.auth_comp_id)
.int('auth_seq_id', P.residue.auth_seq_id, { encoder: E.deltaRLE })
.str('auth_asym_id', P.chain.auth_asym_id)
.str('auth_asym_id', atom_site_auth_asym_id)
.int('pdbx_PDB_model_num', P.unit.model_num, { encoder: E.deltaRLE })
.str('operator_name', P.unit.operator_name, {
shouldInclude: structure => structure.units.some(u => !u.conformation.operator.isIdentity)
})
// .str('operator_name', P.unit.operator_name, {
// shouldInclude: structure => structure.units.some(u => !u.conformation.operator.isIdentity)
// })
.getFields();
export const _atom_site: CifCategory<CifExportContext> = {

View File

@@ -0,0 +1,104 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { SymmetryOperator } from '../../../../mol-math/geometry';
import { CifExportContext } from '../mmcif';
import { StructureElement, StructureProperties as P } from '../../structure';
import Unit from '../../structure/unit';
import { Segmentation } from '../../../../mol-data/int';
import { CifWriter } from '../../../../mol-io/writer/cif';
import { Column } from '../../../../mol-data/db';
export function atom_site_operator_mapping(encoder: CifWriter.Encoder, ctx: CifExportContext) {
const entries = getEntries(ctx);
if (entries.length === 0) return;
encoder.writeCategory(Category, entries, { ignoreFilter: true });
}
export const AtomSiteOperatorMappingSchema = {
molstar_atom_site_operator_mapping: {
label_asym_id: Column.Schema.Str(),
auth_asym_id: Column.Schema.Str(),
operator_name: Column.Schema.Str(),
suffix: Column.Schema.Str(),
// assembly
assembly_id: Column.Schema.Str(),
assembly_operator_id: Column.Schema.Int(),
// symmetry
symmetry_operator_index: Column.Schema.Int(),
symmetry_hkl: Column.Schema.Vector(3),
// NCS
ncs_id: Column.Schema.Str(),
}
}
const asmValueKind = (i: number, xs: Entry[]) => typeof xs[i].operator.assembly === 'undefined' ? Column.ValueKind.NotPresent : Column.ValueKind.Present;
const symmetryValueKind = (i: number, xs: Entry[]) => xs[i].operator.spgrOp === -1 ? Column.ValueKind.NotPresent : Column.ValueKind.Present;
const Fields = CifWriter.fields<number, Entry[], keyof (typeof AtomSiteOperatorMappingSchema)['molstar_atom_site_operator_mapping']>()
.str('label_asym_id', (i, xs) => xs[i].label_asym_id)
.str('auth_asym_id', (i, xs) => xs[i].auth_asym_id)
.str('operator_name', (i, xs) => xs[i].operator.name)
.str('suffix', (i, xs) => xs[i].operator.suffix)
// assembly
// TODO: include oper list as well?
.str('assembly_id', (i, xs) => xs[i].operator.assembly?.id || '', { valueKind: asmValueKind })
.int('assembly_operator_id', (i, xs) => xs[i].operator.assembly?.operId || 0, { valueKind: asmValueKind })
// symmetry
.int('symmetry_operator_index', (i, xs) => xs[i].operator.spgrOp, { valueKind: symmetryValueKind })
.vec('symmetry_hkl', [(i, xs) => xs[i].operator.hkl[0], (i, xs) => xs[i].operator.hkl[1], (i, xs) => xs[i].operator.hkl[2]], { valueKind: symmetryValueKind })
// NCS
.str('ncs_id', (i, xs) => xs[i].operator.ncsId || '', { valueKind: (i, xs) => !xs[i].operator.ncsId ? Column.ValueKind.NotPresent : Column.ValueKind.Present })
.getFields()
const Category: CifWriter.Category<Entry[]> = {
name: 'molstar_atom_site_operator_mapping',
instance(entries: Entry[]) {
return { fields: Fields, source: [{ data: entries, rowCount: entries.length }] };
}
}
interface Entry {
label_asym_id: string,
auth_asym_id: string,
operator: SymmetryOperator
}
function getEntries(ctx: CifExportContext) {
const existing = new Set<string>();
const entries: Entry[] = [];
for (const s of ctx.structures) {
const l = StructureElement.Location.create(s);
for (const unit of s.units) {
const operator = unit.conformation.operator;
if (!operator.suffix || unit.kind !== Unit.Kind.Atomic) continue;
l.unit = unit;
const { elements } = unit;
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
while (chainsIt.hasNext) {
const chainSegment = chainsIt.move();
l.element = elements[chainSegment.start];
const label_asym_id = P.chain.label_asym_id(l);
const key = `${label_asym_id}${operator.suffix}`;
if (existing.has(key)) continue;
existing.add(key);
const auth_asym_id = P.chain.label_asym_id(l);
entries.push({ label_asym_id, auth_asym_id, operator });
}
}
}
return entries;
}

View File

@@ -51,7 +51,7 @@ const struct_conf_fields: CifField[] = [
CifField.str<number, SSElement<SecondaryStructure.Helix>[]>('details', (i, data) => data[i].element.details || '', {
valueKind: (i, d) => !!d[i].element.details ? Column.ValueKind.Present : Column.ValueKind.Unknown
}),
CifField.int<number, SSElement<SecondaryStructure.Helix>[]>('pdbx_PDB_helix_class', (i, data) => data[i].length)
CifField.int<number, SSElement<SecondaryStructure.Helix>[]>('pdbx_PDB_helix_length', (i, data) => data[i].length)
];
const struct_sheet_range_fields: CifField[] = [

View File

@@ -16,6 +16,7 @@ import { Model } from '../model';
import { getUniqueEntityIndicesFromStructures, copy_mmCif_category } from './categories/utils';
import { _struct_asym, _entity_poly, _entity_poly_seq } from './categories/sequence';
import { CustomPropertyDescriptor } from '../common/custom-property';
import { atom_site_operator_mapping } from './categories/atom_site_operator_mapping';
export interface CifExportContext {
structures: Structure[],
@@ -136,6 +137,10 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures:
encoder.writeCategory(cat, ctx);
}
if ((!_params.skipCategoryNames || !_params.skipCategoryNames.has('atom_site')) && encoder.isCategoryIncluded('atom_site')) {
atom_site_operator_mapping(encoder, ctx);
}
for (const customProp of models[0].customProperties.all) {
encodeCustomProp(customProp, ctx, encoder, _params);
}

View File

@@ -22,6 +22,8 @@ import { Task } from '../../../mol-task';
import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair';
import { createModels } from '../../../mol-model-formats/structure/basic/parser';
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
import { ChainIndex } from './indexing';
import { SymmetryOperator } from '../../../mol-math/geometry';
/**
* Interface to the "source data" of the molecule.
@@ -60,6 +62,7 @@ export interface Model extends Readonly<{
atomicHierarchy: AtomicHierarchy,
atomicConformation: AtomicConformation,
atomicRanges: AtomicRanges,
atomicChainOperatorMappinng: Map<ChainIndex, SymmetryOperator>,
properties: {
/** map that holds details about unobserved or zero occurrence residues */
@@ -182,6 +185,16 @@ export namespace Model {
return false
}
export function isFromEm(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return false
const { db } = model.sourceData.data
for (let i = 0; i < db.exptl.method.rowCount; i++) {
const v = db.exptl.method.value(i).toUpperCase()
if (v.indexOf('MICROSCOPY') >= 0) return true
}
return false
}
export function isFromNmr(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return false
const { db } = model.sourceData.data
@@ -194,21 +207,29 @@ export namespace Model {
export function hasXrayMap(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return false
// Check exprimental method to exclude models solved with
// 'ELECTRON CRYSTALLOGRAPHY' which also have structure factors
if (!isFromXray(model)) return false
const { db } = model.sourceData.data
return db.pdbx_database_status.status_code_sf.value(0) === 'REL'
const { status_code_sf } = db.pdbx_database_status
return status_code_sf.isDefined && status_code_sf.value(0) === 'REL'
}
/**
* Also checks for `content_type` of 'associated EM volume' to exclude cases
* like 6TEK which are solved with 'X-RAY DIFFRACTION' but have an related
* EMDB entry of type 'other EM volume'.
*/
export function hasEmMap(model: Model) {
if (!MmcifFormat.is(model.sourceData)) return false
const { db } = model.sourceData.data
let hasEmMap = false
const { db_name, content_type } = db.pdbx_database_related
for (let i = 0, il = db.pdbx_database_related._rowCount; i < il; ++i) {
if (db.pdbx_database_related.db_name.value(i).toUpperCase() === 'EMDB') {
hasEmMap = true
break
if (db_name.value(i).toUpperCase() === 'EMDB' && content_type.value(i) === 'associated EM volume') {
return true
}
}
return hasEmMap
return false
}
export function hasDensityMap(model: Model) {

View File

@@ -11,6 +11,7 @@ import { Model } from '../../model'
import { Spacegroup } from '../../../../mol-math/geometry';
import { Vec3 } from '../../../../mol-math/linear-algebra';
import { ModelSymmetry } from '../../../../mol-model-formats/structure/property/symmetry';
import { radToDeg } from '../../../../mol-math/misc';
/** Determine an atom set and a list of operators that should be applied to that set */
export interface OperatorGroup {
@@ -67,6 +68,25 @@ namespace Symmetry {
const symmetry = ModelSymmetry.Provider.get(model)
return symmetry ? arrayFind(symmetry.assemblies, a => a.id.toLowerCase() === _id) : undefined;
}
export function getUnitcellLabel(symmetry: Symmetry) {
const { cell, name, num } = symmetry.spacegroup
const { size, anglesInRadians } = cell
const a = size[0].toFixed(2)
const b = size[1].toFixed(2)
const c = size[2].toFixed(2)
const alpha = radToDeg(anglesInRadians[0]).toFixed(2)
const beta = radToDeg(anglesInRadians[1]).toFixed(2)
const gamma = radToDeg(anglesInRadians[2]).toFixed(2)
const label: string[] = []
// name
label.push(`Unitcell <b>${name}</b> #${num}`)
// sizes
label.push(`${a}\u00D7${b}\u00D7${c} \u212B`)
// angles
label.push(`\u03b1=${alpha}\u00B0 \u03b2=${beta}\u00B0 \u03b3=${gamma}\u00B0`)
return label.join(' | ')
}
}
export { Symmetry }

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-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>
@@ -21,9 +21,9 @@ interface Location<U = Unit> {
}
namespace Location {
export function create<U extends Unit>(structure: Structure | undefined, unit?: U, element?: ElementIndex): Location<U> {
export function create<U extends Unit>(structure?: Structure, unit?: U, element?: ElementIndex): Location<U> {
return {
kind: 'element-location',
kind: 'element-location',
structure: structure as any,
unit: unit as any,
element: element || (0 as ElementIndex)

View File

@@ -485,7 +485,7 @@ export namespace Loci {
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
const eI = elements[OrderedSet.getAt(indices, i)];
pos(eI, tempPosBoundary);
boundaryHelper.includeSphereStep(tempPosBoundary, r(eI));
boundaryHelper.includePositionRadius(tempPosBoundary, r(eI));
}
}
boundaryHelper.finishedIncludeStep();
@@ -496,7 +496,7 @@ export namespace Loci {
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
const eI = elements[OrderedSet.getAt(indices, i)];
pos(eI, tempPosBoundary);
boundaryHelper.radiusSphereStep(tempPosBoundary, r(eI));
boundaryHelper.radiusPositionRadius(tempPosBoundary, r(eI));
}
}

View File

@@ -71,9 +71,13 @@ export namespace Stats {
Location.set(stats.firstUnitLoc, structure, unit, elements[OrderedSet.start(indices)])
}
} else if (size === 1) {
stats.elementCount += 1
if (stats.elementCount === 1) {
Location.set(stats.firstElementLoc, structure, unit, elements[OrderedSet.start(indices)])
if (Unit.Traits.is(unit.traits, Unit.Trait.MultiChain)) {
return
} else {
stats.elementCount += 1
if (stats.elementCount === 1) {
Location.set(stats.firstElementLoc, structure, unit, elements[OrderedSet.start(indices)])
}
}
} else {
if (Unit.isAtomic(unit)) {

View File

@@ -10,6 +10,7 @@ import Unit from './unit'
import { VdwRadius } from '../model/properties/atomic';
import { SecondaryStructureType } from '../model/types';
import { SecondaryStructureProvider } from '../../../mol-model-props/computed/secondary-structure';
import { SymmetryOperator } from '../../../mol-math/geometry';
function p<T>(p: StructureElement.Property<T>) { return p; }
@@ -167,6 +168,7 @@ const entity = {
pdbx_ec: p(l => l.unit.model.entities.data.pdbx_ec.value(eK(l)))
}
const _emptyList: any[] = [];
const unit = {
id: p(l => l.unit.id),
chainGroupId: p(l => l.unit.chainGroupId),
@@ -180,8 +182,8 @@ const unit = {
spgrOp: p(l => l.unit.conformation.operator.spgrOp),
model_num: p(l => l.unit.model.modelNum),
pdbx_struct_assembly_id: p(l => l.unit.conformation.operator.assembly.id),
pdbx_struct_oper_list_ids: p(l => l.unit.conformation.operator.assembly.operList),
pdbx_struct_assembly_id: p(l => l.unit.conformation.operator.assembly?.id || SymmetryOperator.DefaultName),
pdbx_struct_oper_list_ids: p(l => l.unit.conformation.operator.assembly?.operList || _emptyList),
struct_ncs_oper_id: p(l => l.unit.conformation.operator.ncsId),
}

View File

@@ -205,7 +205,13 @@ class Structure {
}
get coordinateSystem() {
return this._props.coordinateSystem;
// TODO: do not use SymmetryOperator for this?
// TODO: figure out a good way to compose this
return this.parent?.coordinateSystem || this._props.coordinateSystem;
}
set coordinateSystem(op: SymmetryOperator) {
this._props.coordinateSystem = op;
}
get label() {
@@ -635,11 +641,13 @@ namespace Structure {
*/
export function ofModel(model: Model): Structure {
const chains = model.atomicHierarchy.chainAtomSegments;
const { index } = model.atomicHierarchy
const { auth_asym_id } = model.atomicHierarchy.chains
const { index } = model.atomicHierarchy;
const { auth_asym_id } = model.atomicHierarchy.chains;
const { atomicChainOperatorMappinng } = model;
const builder = new StructureBuilder({ label: model.label });
for (let c = 0 as ChainIndex; c < chains.count; c++) {
const operator = atomicChainOperatorMappinng.get(c) || SymmetryOperator.Default;
const start = chains.offsets[c];
// set to true for chains that consist of "single atom residues",
@@ -655,11 +663,15 @@ namespace Structure {
singleAtomResidues = true
const e1 = index.getEntityFromChain(c);
const e2 = index.getEntityFromChain(c + 1 as ChainIndex);
if (e1 !== e2) break
if (e1 !== e2) break;
const a1 = auth_asym_id.value(c);
const a2 = auth_asym_id.value(c + 1);
if (a1 !== a2) break
if (a1 !== a2) break;
const op1 = atomicChainOperatorMappinng.get(c);
const op2 = atomicChainOperatorMappinng.get(c + 1 as ChainIndex);
if (op1 !== op2) break;
multiChain = true
c++;
@@ -668,12 +680,12 @@ namespace Structure {
const elements = SortedArray.ofBounds(start as ElementIndex, chains.offsets[c + 1] as ElementIndex);
if (singleAtomResidues) {
partitionAtomicUnitByAtom(model, elements, builder, multiChain);
partitionAtomicUnitByAtom(model, elements, builder, multiChain, operator);
} else if (elements.length > 200000 || isWaterChain(model, c)) {
// split up very large chains e.g. lipid bilayers, micelles or water with explicit H
partitionAtomicUnitByResidue(model, elements, builder, multiChain);
partitionAtomicUnitByResidue(model, elements, builder, multiChain, operator);
} else {
builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements, multiChain ? Unit.Trait.MultiChain : Unit.Trait.None);
builder.addUnit(Unit.Kind.Atomic, model, operator, elements, multiChain ? Unit.Trait.MultiChain : Unit.Trait.None);
}
}
@@ -695,7 +707,7 @@ namespace Structure {
return model.entities.data.type.value(e) === 'water';
}
function partitionAtomicUnitByAtom(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean) {
function partitionAtomicUnitByAtom(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean, operator: SymmetryOperator) {
const { x, y, z } = model.atomicConformation;
const position = { x, y, z, indices }
const lookup = GridLookup3D(position, getBoundary(position), 8192);
@@ -710,13 +722,13 @@ namespace Structure {
for (let j = 0, _j = count[i]; j < _j; j++) {
set[j] = indices[array[start + j]];
}
builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(set), traits);
builder.addUnit(Unit.Kind.Atomic, model, operator, SortedArray.ofSortedArray(set), traits);
}
builder.endChainGroup();
}
// keeps atoms of residues together
function partitionAtomicUnitByResidue(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean) {
function partitionAtomicUnitByResidue(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean, operator: SymmetryOperator) {
const { residueAtomSegments } = model.atomicHierarchy
const startIndices: number[] = []
@@ -749,7 +761,7 @@ namespace Structure {
set[set.length] = l;
}
}
builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(new Int32Array(set)), traits);
builder.addUnit(Unit.Kind.Atomic, model, operator, SortedArray.ofSortedArray(new Int32Array(set)), traits);
}
builder.endChainGroup();
}
@@ -769,12 +781,12 @@ namespace Structure {
const units: Unit[] = [];
for (const u of s.units) {
const old = u.conformation.operator;
const op = SymmetryOperator.create(old.name, transform, old.assembly, old.ncsId, old.hkl);
const op = SymmetryOperator.create(old.name, transform, old);
units.push(u.applyOperator(u.id, op));
}
const cs = s.coordinateSystem;
const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs.assembly, cs.ncsId, cs.hkl), cs);
const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs), cs);
return new Structure(units, { parent: s, coordinateSystem: newCS });
}
@@ -1002,14 +1014,17 @@ namespace Structure {
//
const DefaultSizeThresholds = {
export const DefaultSizeThresholds = {
smallResidueCount: 10,
mediumResidueCount: 1500,
largeResidueCount: 12000,
mediumResidueCount: 3000,
/** large ribosomes like 4UG0 should still be `large` */
largeResidueCount: 20000,
highSymmetryUnitCount: 10,
fiberResidueCount: 15
fiberResidueCount: 15,
residueCountFactor: 1
}
type SizeThresholds = typeof DefaultSizeThresholds
export type SizeThresholds = typeof DefaultSizeThresholds
function getPolymerSymmetryGroups(structure: Structure) {
return structure.unitSymmetryGroups.filter(ug => ug.units[0].polymerElements.length > 0)
@@ -1030,7 +1045,7 @@ namespace Structure {
function hasHighSymmetry(structure: Structure, thresholds: SizeThresholds) {
const polymerSymmetryGroups = getPolymerSymmetryGroups(structure)
return (
polymerSymmetryGroups.length > 1 &&
polymerSymmetryGroups.length >= 1 &&
polymerSymmetryGroups[0].units.length > thresholds.highSymmetryUnitCount
)
}
@@ -1038,8 +1053,8 @@ namespace Structure {
export enum Size { Small, Medium, Large, Huge, Gigantic }
export function getSize(structure: Structure, thresholds: Partial<SizeThresholds> = {}): Size {
const t = { ...DefaultSizeThresholds, thresholds }
if (structure.polymerResidueCount >= t.largeResidueCount) {
const t = { ...DefaultSizeThresholds, ...thresholds }
if (structure.polymerResidueCount >= t.largeResidueCount * t.residueCountFactor) {
if (hasHighSymmetry(structure, t)) {
return Size.Huge
} else {
@@ -1047,9 +1062,9 @@ namespace Structure {
}
} else if (isFiberLike(structure, t)) {
return Size.Small
} else if (structure.polymerResidueCount < t.smallResidueCount) {
} else if (structure.polymerResidueCount < t.smallResidueCount * t.residueCountFactor) {
return Size.Small
} else if (structure.polymerResidueCount < t.mediumResidueCount) {
} else if (structure.polymerResidueCount < t.mediumResidueCount * t.residueCountFactor) {
return Size.Medium
} else {
return Size.Large

View File

@@ -26,7 +26,7 @@ namespace StructureSymmetry {
const assembly = Symmetry.findAssembly(models[0], asmName);
if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`);
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] })
const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { assembly: { id: assembly.id, operId: 0, operList: [] } })
const assembler = Structure.Builder({ coordinateSystem, label: structure.label });
const queryCtx = new QueryContext(structure);
@@ -57,7 +57,7 @@ namespace StructureSymmetry {
if (models.length !== 1) throw new Error('Can only build symmetry assemblies from structures based on 1 model.');
const modelCenter = Vec3()
const assembler = Structure.Builder({ label: structure.label });
const assembler = Structure.Builder({ label: structure.label, representativeModel: models[0] });
const queryCtx = new QueryContext(structure);
@@ -150,7 +150,12 @@ function getOperatorsForIndex(symmetry: Symmetry, index: number, i: number, j: n
for (let u = 0, ul = ncsOperators.length; u < ul; ++u) {
const ncsOp = ncsOperators![u]
const matrix = Mat4.mul(Mat4(), symOp.matrix, ncsOp.matrix)
const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, symOp.assembly, ncsOp.ncsId, symOp.hkl, symOp.spgrOp);
const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, {
assembly: symOp.assembly,
ncsId: ncsOp.ncsId,
hkl: symOp.hkl,
spgrOp: symOp.spgrOp
});
operators.push(operator)
}
} else {

View File

@@ -19,7 +19,7 @@ export * from './bonds/inter-compute'
namespace Bond {
export interface Location<U extends Unit = Unit> {
readonly kind: 'bond-location',
aStructure: Structure,
aUnit: U,
/** Index into aUnit.elements */

View File

@@ -1,41 +1,17 @@
/**
* Copyright (c) 2017-2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-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 { ElementSymbol } from '../../../model/types';
export interface BondComputationProps {
/**
* Experimental covalent hydrogen bond lengths
*
* C-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rCH)
* - Average 1.091 (+/- 0.017)
* - Min 0.931
* - Max 1.140
*
* N-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rNH)
* - Average 1.009 (+/- 0.043)
* - Min 0.836
* - Max 1.090
*
* O-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rOH)
* - Average 0.967 (+/- 0.022)
* - Min 0.912
* - Max 1.033
*
* S-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rSH)
* - Average 1.345 (+/- 0.020)
* - Min 1.322
* - Max 1.400
*/
maxCovalentHydrogenBondingLength: number,
forceCompute: boolean
noCompute: boolean
}
export const DefaultBondComputationProps: BondComputationProps = {
maxCovalentHydrogenBondingLength: 1.45,
forceCompute: false,
noCompute: false
}
@@ -43,11 +19,48 @@ export const DefaultBondComputationProps: BondComputationProps = {
// H,D,T are all mapped to H
const __ElementIndex: { [e: string]: number | undefined } = { 'H': 0, 'h': 0, 'D': 0, 'd': 0, 'T': 0, 't': 0, 'He': 2, 'HE': 2, 'he': 2, 'Li': 3, 'LI': 3, 'li': 3, 'Be': 4, 'BE': 4, 'be': 4, 'B': 5, 'b': 5, 'C': 6, 'c': 6, 'N': 7, 'n': 7, 'O': 8, 'o': 8, 'F': 9, 'f': 9, 'Ne': 10, 'NE': 10, 'ne': 10, 'Na': 11, 'NA': 11, 'na': 11, 'Mg': 12, 'MG': 12, 'mg': 12, 'Al': 13, 'AL': 13, 'al': 13, 'Si': 14, 'SI': 14, 'si': 14, 'P': 15, 'p': 15, 'S': 16, 's': 16, 'Cl': 17, 'CL': 17, 'cl': 17, 'Ar': 18, 'AR': 18, 'ar': 18, 'K': 19, 'k': 19, 'Ca': 20, 'CA': 20, 'ca': 20, 'Sc': 21, 'SC': 21, 'sc': 21, 'Ti': 22, 'TI': 22, 'ti': 22, 'V': 23, 'v': 23, 'Cr': 24, 'CR': 24, 'cr': 24, 'Mn': 25, 'MN': 25, 'mn': 25, 'Fe': 26, 'FE': 26, 'fe': 26, 'Co': 27, 'CO': 27, 'co': 27, 'Ni': 28, 'NI': 28, 'ni': 28, 'Cu': 29, 'CU': 29, 'cu': 29, 'Zn': 30, 'ZN': 30, 'zn': 30, 'Ga': 31, 'GA': 31, 'ga': 31, 'Ge': 32, 'GE': 32, 'ge': 32, 'As': 33, 'AS': 33, 'as': 33, 'Se': 34, 'SE': 34, 'se': 34, 'Br': 35, 'BR': 35, 'br': 35, 'Kr': 36, 'KR': 36, 'kr': 36, 'Rb': 37, 'RB': 37, 'rb': 37, 'Sr': 38, 'SR': 38, 'sr': 38, 'Y': 39, 'y': 39, 'Zr': 40, 'ZR': 40, 'zr': 40, 'Nb': 41, 'NB': 41, 'nb': 41, 'Mo': 42, 'MO': 42, 'mo': 42, 'Tc': 43, 'TC': 43, 'tc': 43, 'Ru': 44, 'RU': 44, 'ru': 44, 'Rh': 45, 'RH': 45, 'rh': 45, 'Pd': 46, 'PD': 46, 'pd': 46, 'Ag': 47, 'AG': 47, 'ag': 47, 'Cd': 48, 'CD': 48, 'cd': 48, 'In': 49, 'IN': 49, 'in': 49, 'Sn': 50, 'SN': 50, 'sn': 50, 'Sb': 51, 'SB': 51, 'sb': 51, 'Te': 52, 'TE': 52, 'te': 52, 'I': 53, 'i': 53, 'Xe': 54, 'XE': 54, 'xe': 54, 'Cs': 55, 'CS': 55, 'cs': 55, 'Ba': 56, 'BA': 56, 'ba': 56, 'La': 57, 'LA': 57, 'la': 57, 'Ce': 58, 'CE': 58, 'ce': 58, 'Pr': 59, 'PR': 59, 'pr': 59, 'Nd': 60, 'ND': 60, 'nd': 60, 'Pm': 61, 'PM': 61, 'pm': 61, 'Sm': 62, 'SM': 62, 'sm': 62, 'Eu': 63, 'EU': 63, 'eu': 63, 'Gd': 64, 'GD': 64, 'gd': 64, 'Tb': 65, 'TB': 65, 'tb': 65, 'Dy': 66, 'DY': 66, 'dy': 66, 'Ho': 67, 'HO': 67, 'ho': 67, 'Er': 68, 'ER': 68, 'er': 68, 'Tm': 69, 'TM': 69, 'tm': 69, 'Yb': 70, 'YB': 70, 'yb': 70, 'Lu': 71, 'LU': 71, 'lu': 71, 'Hf': 72, 'HF': 72, 'hf': 72, 'Ta': 73, 'TA': 73, 'ta': 73, 'W': 74, 'w': 74, 'Re': 75, 'RE': 75, 're': 75, 'Os': 76, 'OS': 76, 'os': 76, 'Ir': 77, 'IR': 77, 'ir': 77, 'Pt': 78, 'PT': 78, 'pt': 78, 'Au': 79, 'AU': 79, 'au': 79, 'Hg': 80, 'HG': 80, 'hg': 80, 'Tl': 81, 'TL': 81, 'tl': 81, 'Pb': 82, 'PB': 82, 'pb': 82, 'Bi': 83, 'BI': 83, 'bi': 83, 'Po': 84, 'PO': 84, 'po': 84, 'At': 85, 'AT': 85, 'at': 85, 'Rn': 86, 'RN': 86, 'rn': 86, 'Fr': 87, 'FR': 87, 'fr': 87, 'Ra': 88, 'RA': 88, 'ra': 88, 'Ac': 89, 'AC': 89, 'ac': 89, 'Th': 90, 'TH': 90, 'th': 90, 'Pa': 91, 'PA': 91, 'pa': 91, 'U': 92, 'u': 92, 'Np': 93, 'NP': 93, 'np': 93, 'Pu': 94, 'PU': 94, 'pu': 94, 'Am': 95, 'AM': 95, 'am': 95, 'Cm': 96, 'CM': 96, 'cm': 96, 'Bk': 97, 'BK': 97, 'bk': 97, 'Cf': 98, 'CF': 98, 'cf': 98, 'Es': 99, 'ES': 99, 'es': 99, 'Fm': 100, 'FM': 100, 'fm': 100, 'Md': 101, 'MD': 101, 'md': 101, 'No': 102, 'NO': 102, 'no': 102, 'Lr': 103, 'LR': 103, 'lr': 103, 'Rf': 104, 'RF': 104, 'rf': 104, 'Db': 105, 'DB': 105, 'db': 105, 'Sg': 106, 'SG': 106, 'sg': 106, 'Bh': 107, 'BH': 107, 'bh': 107, 'Hs': 108, 'HS': 108, 'hs': 108, 'Mt': 109, 'MT': 109, 'mt': 109 };
// increased P (15) threshold from 1.9 to 2.0 (e.g. for G16 in 1O08)
// Increased P (15) threshold from 1.9 to 2.0 (e.g. for G16 in 1O08)
const __ElementBondThresholds: { [e: number]: number | undefined } = { 0: 1.42, 1: 1.42, 3: 2.7, 4: 2.7, 6: 1.75, 7: 1.6, 8: 1.52, 11: 2.7, 12: 2.7, 13: 2.7, 14: 1.9, 15: 2.0, 16: 1.9, 17: 1.8, 19: 2.7, 20: 2.7, 21: 2.7, 22: 2.7, 23: 2.7, 24: 2.7, 25: 2.7, 26: 2.7, 27: 2.7, 28: 2.7, 29: 2.7, 30: 2.7, 31: 2.7, 33: 2.68, 37: 2.7, 38: 2.7, 39: 2.7, 40: 2.7, 41: 2.7, 42: 2.7, 43: 2.7, 44: 2.7, 45: 2.7, 46: 2.7, 47: 2.7, 48: 2.7, 49: 2.7, 50: 2.7, 55: 2.7, 56: 2.7, 57: 2.7, 58: 2.7, 59: 2.7, 60: 2.7, 61: 2.7, 62: 2.7, 63: 2.7, 64: 2.7, 65: 2.7, 66: 2.7, 67: 2.7, 68: 2.7, 69: 2.7, 70: 2.7, 71: 2.7, 72: 2.7, 73: 2.7, 74: 2.7, 75: 2.7, 76: 2.7, 77: 2.7, 78: 2.7, 79: 2.7, 80: 2.7, 81: 2.7, 82: 2.7, 83: 2.7, 87: 2.7, 88: 2.7, 89: 2.7, 90: 2.7, 91: 2.7, 92: 2.7, 93: 2.7, 94: 2.7, 95: 2.7, 96: 2.7, 97: 2.7, 98: 2.7, 99: 2.7, 100: 2.7, 101: 2.7, 102: 2.7, 103: 2.7, 104: 2.7, 105: 2.7, 106: 2.7, 107: 2.7, 108: 2.7, 109: 2.88 };
// increased N-N (112) threshold from 1.55 to 1.6 (e.g. for 0QH in 1BMA)
const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.3, 35: 1.3, 44: 1.05, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.6, 113: 1.59, 114: 1.36, 129: 1.45, 144: 1.6, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 };
/**
* Increased N-N (112) threshold from 1.55 to 1.6 (e.g. for 0QH in 1BMA)
*
* More experimentally observed bond length here (https://cccbdb.nist.gov/expbondlengths1x.asp)
*
* H-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rHH)
* - 0.741
*
* Changed C-H (27) to 1.2
* C-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rCH)
* - Average 1.091 (+/- 0.017)
* - Min 0.931
* - Max 1.140
*
* Changed N-H (35) to 1.15
* N-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rNH)
* - Average 1.009 (+/- 0.043)
* - Min 0.836
* - Max 1.090
*
* Changed O-H (44) to 1.1
* O-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rOH)
* - Average 0.967 (+/- 0.022)
* - Min 0.912
* - Max 1.033
*
* Added P-H (135) as 1.47
* P-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rPH)
* - Average 1.423 ((+/- 0.007)
* - Min 0.912
* - Max 1.033
*
* Added S-H (152) as 1.45
* S-H (https://cccbdb.nist.gov/expbondlengths2x.asp?descript=rSH)
* - Average 1.345 (+/- 0.020)
* - Min 1.322
* - Max 1.400
*/
const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.2, 35: 1.15, 44: 1.1, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.6, 113: 1.59, 114: 1.36, 129: 1.45, 135: 1.47, 144: 1.6, 152: 1.45, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 };
const __DefaultBondingRadius = 2.001;

View File

@@ -125,25 +125,15 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
}
const beI = getElementIdx(type_symbolB.value(bI)!);
const isMetal = metalA || MetalsSet.has(beI);
const isHb = isHydrogen(beI);
if (isHa && isHb) continue;
const isMetal = (metalA || MetalsSet.has(beI)) && !(isHa || isHb);
const dist = Math.sqrt(squaredDistances[ni]);
if (dist === 0) continue;
if (isHa || isHb) {
if (dist < props.maxCovalentHydrogenBondingLength) {
// covalent bonds involving a hydrogen are always of order 1
builder.add(_aI, _bI, {
order: 1,
flag: BondType.Flag.Covalent | BondType.Flag.Computed
});
}
continue;
}
const thresholdAB = getElementPairThreshold(aeI, beI);
const pairingThreshold = thresholdAB > 0
? thresholdAB

View File

@@ -127,7 +127,11 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
if (altA && altB && altA !== altB) continue;
const beI = getElementIdx(type_symbol.value(bI)!);
const isMetal = metalA || MetalsSet.has(beI);
const isHb = isHydrogen(beI);
if (isHa && isHb) continue;
const isMetal = (metalA || MetalsSet.has(beI)) && !(isHa || isHb);
const rbI = residueIndex[bI];
// handle "component dictionary" bonds.
@@ -147,22 +151,9 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
continue;
}
const isHb = isHydrogen(beI);
if (isHa && isHb) continue;
const dist = Math.sqrt(squaredDistances[ni]);
if (dist === 0) continue;
if (isHa || isHb) {
if (dist < props.maxCovalentHydrogenBondingLength) {
atomA[atomA.length] = _aI;
atomB[atomB.length] = _bI;
order[order.length] = 1; // covalent bonds involving hydrogen are always of order 1
flags[flags.length] = BondType.Flag.Covalent | BondType.Flag.Computed;
}
continue;
}
const thresholdAB = getElementPairThreshold(aeI, beI);
const pairingThreshold = thresholdAB > 0
? thresholdAB

View File

@@ -157,11 +157,11 @@ namespace UnitRings {
/** Creates a mapping ResidueIndex -> list or rings that are on that residue and have one of the specified fingerprints. */
export function byFingerprintAndResidue(rings: UnitRings, fingerprints: ReadonlyArray<UnitRing.Fingerprint>) {
const map = new Map<ResidueIndex, Index[]>();
for (let fI = 0, _fI = fingerprints.length; fI < _fI; fI++) {
const fp = fingerprints[fI];
addSingleResidueRings(rings, fp, map);
}
}
return map;
}
}

View File

@@ -116,7 +116,7 @@ function processResidue(state: State, start: number, end: number) {
arraySetAdd(altLocs, altLoc);
}
arraySetRemove(altLocs, '');
if (altLocs.length === 0) {
resetState(state);
for (let i = 0; i < state.count; i++) {

View File

@@ -34,14 +34,14 @@ export function computeStructureBoundary(s: Structure): Boundary {
Vec3.min(min, min, invariantBoundary.box.min);
Vec3.max(max, max, invariantBoundary.box.max);
boundaryHelper.includeSphereStep(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
boundaryHelper.includePositionRadius(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
} else {
Box3D.transform(tmpBox, invariantBoundary.box, o.matrix);
Vec3.min(min, min, tmpBox.min);
Vec3.max(max, max, tmpBox.max);
Sphere3D.transform(tmpSphere, invariantBoundary.sphere, o.matrix);
boundaryHelper.includeSphereStep(tmpSphere.center, tmpSphere.radius);
boundaryHelper.includePositionRadius(tmpSphere.center, tmpSphere.radius);
}
}
@@ -53,10 +53,10 @@ export function computeStructureBoundary(s: Structure): Boundary {
const o = u.conformation.operator;
if (o.isIdentity) {
boundaryHelper.radiusSphereStep(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
boundaryHelper.radiusPositionRadius(invariantBoundary.sphere.center, invariantBoundary.sphere.radius);
} else {
Sphere3D.transform(tmpSphere, invariantBoundary.sphere, o.matrix);
boundaryHelper.radiusSphereStep(tmpSphere.center, tmpSphere.radius);
boundaryHelper.radiusPositionRadius(tmpSphere.center, tmpSphere.radius);
}
}

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