mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 21:34:23 +08:00
Compare commits
97 Commits
v2.0.0-dev
...
v2.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
614cffda96 | ||
|
|
2e0379d202 | ||
|
|
b5cfdcd2a3 | ||
|
|
c00de6fde0 | ||
|
|
da3a8e56f3 | ||
|
|
103d6fe775 | ||
|
|
5df55e6bf7 | ||
|
|
3b285086d4 | ||
|
|
91793bc3cc | ||
|
|
fa3828e820 | ||
|
|
31ba8212da | ||
|
|
fe27d8e134 | ||
|
|
83dcdfdc4b | ||
|
|
f9aaabc1f7 | ||
|
|
034370b44c | ||
|
|
b87666df3e | ||
|
|
c98c3228fe | ||
|
|
9419980dfc | ||
|
|
42d60420e5 | ||
|
|
5b1df333a7 | ||
|
|
0bb376706d | ||
|
|
eca7da2c72 | ||
|
|
b0bdb3ddb6 | ||
|
|
3180d7c305 | ||
|
|
2faa821c50 | ||
|
|
7f355ae501 | ||
|
|
7f79ff9ff2 | ||
|
|
02de871c59 | ||
|
|
00cb783d4c | ||
|
|
c925919ee5 | ||
|
|
324820890a | ||
|
|
2687b29d4d | ||
|
|
7084aaee1a | ||
|
|
520a2f7850 | ||
|
|
9264987817 | ||
|
|
b736ed3ea4 | ||
|
|
166d660fa7 | ||
|
|
b8249cde4d | ||
|
|
f12f5eca90 | ||
|
|
cd3798b46f | ||
|
|
0240e54737 | ||
|
|
6a735d902e | ||
|
|
57a942ecb5 | ||
|
|
f67605a398 | ||
|
|
aaafa1d5ad | ||
|
|
a1d9a77653 | ||
|
|
f2f1181af3 | ||
|
|
864befc48a | ||
|
|
73f6793bd8 | ||
|
|
87ee9d88f2 | ||
|
|
b1e245e913 | ||
|
|
78c0471f39 | ||
|
|
c57b9b9214 | ||
|
|
34f33c5bbb | ||
|
|
57da2a7ebb | ||
|
|
d45d5c0e55 | ||
|
|
42ed425e65 | ||
|
|
f752ee5094 | ||
|
|
044c796942 | ||
|
|
0aabbcfaab | ||
|
|
24274cc53b | ||
|
|
870cef2fd4 | ||
|
|
bf7b1f5bfd | ||
|
|
9c9a0312db | ||
|
|
724fa2a7cd | ||
|
|
19b36e5942 | ||
|
|
b0dd9ab026 | ||
|
|
b77f1d4dee | ||
|
|
3770fd7706 | ||
|
|
e3175c3ed1 | ||
|
|
7c5dd5b15b | ||
|
|
0872e11669 | ||
|
|
a66da4defc | ||
|
|
d4ba13a2f2 | ||
|
|
3b25e037aa | ||
|
|
189fad3d84 | ||
|
|
c3c22ee3bc | ||
|
|
8a3222005c | ||
|
|
a17da36410 | ||
|
|
80323d8122 | ||
|
|
cbd6aa0b6b | ||
|
|
3831bd9941 | ||
|
|
3d3e2c3a86 | ||
|
|
acf13fa46f | ||
|
|
bc5d796653 | ||
|
|
82dd0496c2 | ||
|
|
056742ac74 | ||
|
|
29d4cfbcca | ||
|
|
376449f7c8 | ||
|
|
bc37fad007 | ||
|
|
2e561a8de7 | ||
|
|
e6c8c69d0c | ||
|
|
d121a11e28 | ||
|
|
5484a2a72c | ||
|
|
d527609b6d | ||
|
|
e628f580a7 | ||
|
|
b662179b4d |
@@ -1 +1 @@
|
||||
tsconfig.commonjs.buildinfo
|
||||
tsconfig.commonjs.tsbuildinfo
|
||||
17
README.md
17
README.md
@@ -5,15 +5,12 @@
|
||||
|
||||
# Mol*
|
||||
|
||||
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that will serve as a basis for the next-generation data delivery and analysis tools for macromolecular structure data. This is a collaboration between PDBe and RCSB PDB teams and the development will be open-source and available to anyone who wants to use it for developing visualization tools for macromolecular structure data available from [PDB](https://www.wwpdb.org/) and other institutions.
|
||||
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that serves as a basis for the next-generation data delivery and analysis tools for (not only) macromolecular structure data. Mol* development was jointly initiated by PDBe and RCSB PDB to combine and build on the strengths of [LiteMol](https://litemol.org) (developed by PDBe) and [NGL](https://nglviewer.org) (developed by RCSB PDB) viewers.
|
||||
|
||||
This particular project is the implementation of this technology (still under development).
|
||||
|
||||
*If you are looking for the "MOLeculAR structure annoTator", that package is now available on NPM as [MolArt](https://www.npmjs.com/package/molart).*
|
||||
## Project Structure Overview
|
||||
|
||||
## Project Overview
|
||||
|
||||
The core of Mol* currently consists of these modules (see under `src/`):
|
||||
The core of Mol* consists of these modules (see under `src/`):
|
||||
|
||||
- `mol-task` Computation abstraction with progress tracking and cancellation support.
|
||||
- `mol-data` Collections (integer-based sets, interface to columns/tables, etc.)
|
||||
@@ -29,7 +26,6 @@ The core of Mol* currently consists of these modules (see under `src/`):
|
||||
- `mol-gl` A wrapper around WebGL.
|
||||
- `mol-canvas3d` A low-level 3d view component. Uses `mol-geo` to generate geometries.
|
||||
- `mol-state` State representation tree with state saving and automatic updates.
|
||||
- `mol-app` Components for building UIs.
|
||||
- `mol-plugin` Allow to define modular Mol* plugin instances utilizing `mol-state` and `mol-canvas3d`.
|
||||
- `mol-plugin-state` State transformations, builders, and managers.
|
||||
- `mol-plugin-ui` React-based user interface for the Mol* plugin. Some components of the UI are usable outside the main plugin and can be integrated into 3rd party solutions.
|
||||
@@ -41,7 +37,7 @@ Moreover, the project contains the implementation of `servers`, including
|
||||
- `servers/volume` A tool for accessing volumetric experimental data related to molecular structures.
|
||||
- `servers/plugin-state` A basic server to store Mol* Plugin states.
|
||||
|
||||
The project also contains performance tests (`perf-tests`), `examples`, and basic proof of concept `cli` apps (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
|
||||
The project also contains performance tests (`perf-tests`), `examples`, and `cli` apps (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
|
||||
|
||||
## Previous Work
|
||||
This project builds on experience from previous solutions:
|
||||
@@ -169,12 +165,9 @@ To get syntax highlighting for shader and graphql files add the following to Vis
|
||||
## Contributing
|
||||
Just open an issue or make a pull request. All contributions are welcome.
|
||||
|
||||
## Roadmap
|
||||
Continually develop this prototype project. As individual modules become stable, make them into standalone libraries.
|
||||
|
||||
## Funding
|
||||
Funding sources include but are not limited to:
|
||||
* [RCSB PDB](https://www.rcsb.org) funding by a grant [DBI-1338415; PI: SK Burley] from the NSF, the NIH, and the US DoE
|
||||
* [PDBe, EMBL-EBI](https://pdbe.org)
|
||||
* [CEITEC](https://www.ceitec.eu/)
|
||||
* [EntosAI](https://www.entos.ai) (``alpha-orbitals`` extension)
|
||||
* [EntosAI](https://www.entos.ai)
|
||||
|
||||
@@ -26,3 +26,5 @@
|
||||
* Protein (1BRR, 5Z6Y)
|
||||
* DNA (5D3G)
|
||||
* Multiple models with different sets of ligands or missing ligands (1J6T, 1VRC, 2ICY, 1O2F)
|
||||
* Long linear sugar chain (4HG6)
|
||||
* Anisotropic B-factors/Ellipsoids (1EJG)
|
||||
|
||||
6220
package-lock.json
generated
6220
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.0.0-dev.2",
|
||||
"version": "2.0.1",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -115,7 +115,7 @@
|
||||
"simple-git": "^2.25.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"ts-jest": "^26.4.4",
|
||||
"typescript": "^4.1.2",
|
||||
"typescript": "^4.2.3",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-version-file-plugin": "^0.4.0"
|
||||
@@ -137,14 +137,14 @@
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^8.0.0",
|
||||
"immer": "^8.0.1",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.1",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"rxjs": "^6.6.3",
|
||||
"rxjs": "^6.6.6",
|
||||
"swagger-ui-dist": "^3.37.2",
|
||||
"tslib": "^2.0.3",
|
||||
"tslib": "^2.1.0",
|
||||
"util.promisify": "^1.0.1",
|
||||
"xhr2": "^0.2.0"
|
||||
}
|
||||
|
||||
@@ -5,31 +5,32 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import '../../mol-util/polyfill';
|
||||
import { createPlugin } from '../../mol-plugin';
|
||||
import { DefaultPluginSpec } from '../../mol-plugin/spec';
|
||||
import './index.html';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { PluginStateTransform, PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Task } from '../../mol-task';
|
||||
import { StateObject } from '../../mol-state';
|
||||
import { ViewportComponent, StructurePreset, ShowButtons } from './viewport';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
import { createPlugin } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginBehaviors } from '../../mol-plugin/behavior';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { StateObject } from '../../mol-state';
|
||||
import { Task } from '../../mol-task';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import './index.html';
|
||||
import { ShowButtons, StructurePreset, ViewportComponent } from './viewport';
|
||||
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setProductionMode, setDebugMode } from '../../mol-util/debug';
|
||||
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
|
||||
export { Viewer as DockingViewer };
|
||||
|
||||
const DefaultViewerOptions = {
|
||||
extensions: ObjectKeys({}),
|
||||
@@ -53,7 +54,7 @@ const DefaultViewerOptions = {
|
||||
};
|
||||
|
||||
class Viewer {
|
||||
plugin: PluginContext
|
||||
plugin: PluginUIContext
|
||||
|
||||
constructor(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
|
||||
const o = { ...DefaultViewerOptions, ...{
|
||||
@@ -70,10 +71,10 @@ class Viewer {
|
||||
viewportShowSelectionMode: false,
|
||||
viewportShowAnimation: false,
|
||||
} };
|
||||
const defaultSpec = DefaultPluginSpec();
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const spec: PluginSpec = {
|
||||
actions: [...defaultSpec.actions],
|
||||
const spec: PluginUISpec = {
|
||||
actions: defaultSpec.actions,
|
||||
behaviors: [
|
||||
PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci, { mark: false }),
|
||||
PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
|
||||
@@ -83,7 +84,7 @@ class Viewer {
|
||||
PluginSpec.Behavior(PluginBehaviors.CustomProps.Interactions),
|
||||
PluginSpec.Behavior(PluginBehaviors.CustomProps.SecondaryStructure),
|
||||
],
|
||||
animations: [...defaultSpec.animations || []],
|
||||
animations: defaultSpec.animations,
|
||||
customParamEditors: defaultSpec.customParamEditors,
|
||||
layout: {
|
||||
initial: {
|
||||
@@ -91,15 +92,15 @@ class Viewer {
|
||||
showControls: o.layoutShowControls,
|
||||
controlsDisplay: o.layoutControlsDisplay,
|
||||
},
|
||||
controls: {
|
||||
...defaultSpec.layout && defaultSpec.layout.controls,
|
||||
top: o.layoutShowSequence ? undefined : 'none',
|
||||
bottom: o.layoutShowLog ? undefined : 'none',
|
||||
left: o.layoutShowLeftPanel ? undefined : 'none',
|
||||
}
|
||||
},
|
||||
components: {
|
||||
...defaultSpec.components,
|
||||
controls: {
|
||||
...defaultSpec.components?.controls,
|
||||
top: o.layoutShowSequence ? undefined : 'none',
|
||||
bottom: o.layoutShowLog ? undefined : 'none',
|
||||
left: o.layoutShowLeftPanel ? undefined : 'none',
|
||||
},
|
||||
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
|
||||
viewport: {
|
||||
view: ViewportComponent
|
||||
@@ -210,4 +211,3 @@ const MergeStructures = PluginStateTransform.BuiltIn({
|
||||
});
|
||||
|
||||
(window as any).DockingViewer = Viewer;
|
||||
export { Viewer as DockingViewer };
|
||||
@@ -4,24 +4,23 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { PluginUIComponent } from '../../mol-plugin-ui/base';
|
||||
import { Viewport, ViewportControls } from '../../mol-plugin-ui/viewport';
|
||||
import { BackgroundTaskProgress } from '../../mol-plugin-ui/task';
|
||||
import { LociLabels } from '../../mol-plugin-ui/controls';
|
||||
import { Toasts } from '../../mol-plugin-ui/toast';
|
||||
import { Button } from '../../mol-plugin-ui/controls/common';
|
||||
import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
import { StructureSelectionQueries, StructureSelectionQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { InteractionsRepresentationProvider } from '../../mol-model-props/computed/representations/interactions';
|
||||
import { InteractionTypeColorThemeProvider } from '../../mol-model-props/computed/themes/interaction-type';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { presetStaticComponent, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StructureSelectionQueries, StructureSelectionQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { StructureRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { PluginUIComponent } from '../../mol-plugin-ui/base';
|
||||
import { LociLabels } from '../../mol-plugin-ui/controls';
|
||||
import { Button } from '../../mol-plugin-ui/controls/common';
|
||||
import { BackgroundTaskProgress } from '../../mol-plugin-ui/task';
|
||||
import { Toasts } from '../../mol-plugin-ui/toast';
|
||||
import { Viewport, ViewportControls } from '../../mol-plugin-ui/viewport';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
import { Color } from '../../mol-util/color';
|
||||
|
||||
function shinyStyle(plugin: PluginContext) {
|
||||
return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
|
||||
|
||||
@@ -48,18 +48,14 @@
|
||||
var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
|
||||
if (debugMode) molstar.setDebugMode(debugMode, debugMode);
|
||||
|
||||
var disableAntialiasing = getParam('disable-antialiasing', '[^&]+').trim() === '1';
|
||||
var pixelScale = parseFloat(getParam('pixel-scale', '[^&]+').trim() || '1');
|
||||
var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1';
|
||||
var hideControls = getParam('hide-controls', '[^&]+').trim() === '1';
|
||||
var collapseLeftPanel = getParam('collapse-left-panel', '[^&]+').trim() === '1';
|
||||
var pdbProvider = getParam('pdb-provider', '[^&]+').trim().toLowerCase();
|
||||
var emdbProvider = getParam('emdb-provider', '[^&]+').trim().toLowerCase();
|
||||
var viewer = new molstar.Viewer('app', {
|
||||
disableAntialiasing: disableAntialiasing,
|
||||
pixelScale: pixelScale,
|
||||
enableWboit: !disableWboit,
|
||||
layoutShowControls: !hideControls,
|
||||
viewportShowExpand: false,
|
||||
collapseLeftPanel: collapseLeftPanel,
|
||||
pdbProvider: pdbProvider || 'pdbe',
|
||||
emdbProvider: emdbProvider || 'pdbe',
|
||||
});
|
||||
|
||||
@@ -5,43 +5,43 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import '../../mol-util/polyfill';
|
||||
import { createPlugin } from '../../mol-plugin';
|
||||
import { DefaultPluginSpec } from '../../mol-plugin/spec';
|
||||
import './index.html';
|
||||
import './embedded.html';
|
||||
import './favicon.ico';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { CellPack } from '../../extensions/cellpack';
|
||||
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
|
||||
import { CellPack } from '../../extensions/cellpack';
|
||||
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
|
||||
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
|
||||
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
|
||||
import { StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { StateObjectSelector } from '../../mol-state';
|
||||
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { createPlugin } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { StateObjectSelector } from '../../mol-state';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import '../../mol-util/polyfill';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import './embedded.html';
|
||||
import './favicon.ico';
|
||||
import './index.html';
|
||||
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setProductionMode, setDebugMode } from '../../mol-util/debug';
|
||||
export { setDebugMode, setProductionMode } from '../../mol-util/debug';
|
||||
|
||||
const CustomFormats = [
|
||||
['g3d', G3dProvider] as const
|
||||
@@ -68,6 +68,7 @@ const DefaultViewerOptions = {
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: true,
|
||||
layoutShowLeftPanel: true,
|
||||
collapseLeftPanel: false,
|
||||
disableAntialiasing: false,
|
||||
pixelScale: 1,
|
||||
enableWboit: true,
|
||||
@@ -86,14 +87,14 @@ const DefaultViewerOptions = {
|
||||
type ViewerOptions = typeof DefaultViewerOptions;
|
||||
|
||||
export class Viewer {
|
||||
plugin: PluginContext
|
||||
plugin: PluginUIContext
|
||||
|
||||
constructor(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
|
||||
const o = { ...DefaultViewerOptions, ...options };
|
||||
const defaultSpec = DefaultPluginSpec();
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
|
||||
const spec: PluginSpec = {
|
||||
actions: [...defaultSpec.actions],
|
||||
const spec: PluginUISpec = {
|
||||
actions: defaultSpec.actions,
|
||||
behaviors: [
|
||||
...defaultSpec.behaviors,
|
||||
...o.extensions.map(e => Extensions[e]),
|
||||
@@ -106,16 +107,22 @@ export class Viewer {
|
||||
isExpanded: o.layoutIsExpanded,
|
||||
showControls: o.layoutShowControls,
|
||||
controlsDisplay: o.layoutControlsDisplay,
|
||||
regionState: {
|
||||
bottom: 'full',
|
||||
left: o.collapseLeftPanel ? 'collapsed' : 'full',
|
||||
right: 'full',
|
||||
top: 'full',
|
||||
}
|
||||
},
|
||||
controls: {
|
||||
...defaultSpec.layout && defaultSpec.layout.controls,
|
||||
top: o.layoutShowSequence ? undefined : 'none',
|
||||
bottom: o.layoutShowLog ? undefined : 'none',
|
||||
left: o.layoutShowLeftPanel ? undefined : 'none',
|
||||
}
|
||||
},
|
||||
components: {
|
||||
...defaultSpec.components,
|
||||
controls: {
|
||||
...defaultSpec.components?.controls,
|
||||
top: o.layoutShowSequence ? undefined : 'none',
|
||||
bottom: o.layoutShowLog ? undefined : 'none',
|
||||
left: o.layoutShowLeftPanel ? undefined : 'none',
|
||||
},
|
||||
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
|
||||
},
|
||||
config: [
|
||||
|
||||
@@ -18,7 +18,6 @@ _.StateTransforms.Data.Download.id;
|
||||
|
||||
// Empty plugin context
|
||||
const ctx = new PluginContext({
|
||||
actions: [],
|
||||
behaviors: []
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { AlphaOrbitalsExample } from '.';
|
||||
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
|
||||
|
||||
@@ -22,7 +22,23 @@
|
||||
left: 8px;
|
||||
top: 8px;
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
#sponsor {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
bottom: 8px;
|
||||
font-family: "Helvetica Neue", "Segoe UI", Helvetica, "Source Sans Pro", Arial, sans-serif;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
#sponsor svg {
|
||||
fill: #128EA4;
|
||||
width: 100px;
|
||||
}
|
||||
#sponsor a {
|
||||
text-decoration: none;
|
||||
color: #128EA4;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
@@ -30,6 +46,14 @@
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id='controls'></div>
|
||||
<div id='sponsor'>
|
||||
<a href='https://www.entos.ai/envision' target="_blank" rel="noopener">
|
||||
<svg class="makeStyles-root-46" viewBox="0 0 190 36" xmlns="http://www.w3.org/2000/svg"><path d="M32.2591 28.6707C32.2591 32.3914 29.2421 35.407 25.5214 35.407C22.0752 35.407 19.2338 32.8206 18.8325 29.4831V29.4775C18.8143 29.3312 18.8018 29.1835 18.7934 29.0344C18.7934 29.0316 18.7921 29.0274 18.7921 29.0246V29.0177C18.7865 28.902 18.7837 28.7864 18.7837 28.6707C18.7837 26.2557 20.0532 24.1389 21.9609 22.9503C21.9623 22.9489 21.9651 22.9489 21.9665 22.9475C22.0933 22.8666 22.2243 22.7914 22.3581 22.7203C22.3581 22.7203 22.3595 22.7203 22.3595 22.7189C23.3029 22.2173 24.3787 21.933 25.5214 21.933C29.2421 21.933 32.2591 24.9486 32.2591 28.6707Z"></path><path d="M25.5214 14.0692C29.2421 14.0692 32.2591 11.0522 32.2591 7.33146C32.2591 3.61074 29.2421 0.59375 25.5214 0.59375C22.0529 0.59375 19.1962 3.21637 18.8255 6.58592C18.8185 6.67092 18.8116 6.75454 18.8018 6.83815C18.7893 7.00119 18.7837 7.16563 18.7837 7.33146C18.7837 9.73669 20.0434 11.8465 21.94 13.038C22.0891 13.116 22.2355 13.201 22.3776 13.2916C22.3783 13.2923 22.379 13.2926 22.3797 13.293C22.3804 13.2933 22.3811 13.2937 22.3818 13.2944C23.3196 13.7891 24.3871 14.0692 25.5214 14.0692Z"></path><path d="M19.3645 12.4113C20.2926 12.4113 21.1694 12.638 21.94 13.038C20.0434 11.8465 18.7837 9.73669 18.7837 7.33146C18.7837 7.16563 18.7893 7.00119 18.8018 6.83815C18.4688 9.76455 16.1385 12.0866 13.2065 12.4044C13.8545 13.1193 14.3785 13.9484 14.745 14.857C15.7497 13.3798 17.4443 12.4113 19.3645 12.4113Z"></path><path d="M14.7312 21.1249V21.1236C14.1279 20.2331 13.7767 19.1587 13.7767 18.0007V17.9728C13.7767 15.3084 12.2285 13.0035 9.9835 11.911C9.98141 11.9103 9.97967 11.9096 9.97793 11.9089C9.97619 11.9082 9.97444 11.9075 9.97235 11.9068C9.96817 11.904 9.96538 11.9026 9.9612 11.9012C9.95981 11.9012 9.95981 11.8998 9.95981 11.8998C9.9417 11.8915 9.92394 11.8831 9.90618 11.8747C9.8884 11.8664 9.87063 11.858 9.85251 11.8497C9.82046 11.8343 9.78701 11.819 9.75357 11.8051L9.74521 11.8009C8.91745 11.4372 8.0019 11.2351 7.03898 11.2351C3.31826 11.2351 0.30127 14.2521 0.30127 17.9728C0.30127 21.6935 3.31826 24.7105 7.03898 24.7105C7.98797 24.7105 8.89098 24.514 9.71037 24.1601C9.71246 24.1594 9.7142 24.1583 9.71594 24.1573C9.71768 24.1562 9.71943 24.1552 9.72152 24.1545C9.8107 24.1169 9.8985 24.0765 9.9849 24.0333L9.98629 24.0319C10.7625 23.6919 11.6181 23.5037 12.5197 23.5037C12.7524 23.5037 12.9824 23.5163 13.2081 23.54C13.2082 23.5399 13.2081 23.54 13.2081 23.54C15.0168 23.7365 16.5971 24.695 17.6185 26.0885C17.9195 25.1688 18.3752 24.3201 18.9563 23.5732C17.1964 23.4464 15.6635 22.5058 14.7312 21.1249Z"></path><g clip-path="url(#clip0)"><path d="M106.391 18.0021C106.391 11.3724 101.039 6 94.4389 6H88.4585C81.8581 6 76.5061 11.3724 76.5061 18.0021V30.0042H81.2845V18.0021C81.2845 14.0268 84.4941 10.8008 88.4585 10.8008H94.4347C98.395 10.8008 101.609 14.0226 101.609 18.0021V30.0042H106.391V18.0021Z"></path><path d="M149.432 6H142.258C135.653 6 130.301 11.3724 130.301 18.0021C130.301 24.6319 135.653 30.0042 142.258 30.0042H149.432C156.036 30.0042 161.388 24.6319 161.388 18.0021C161.388 11.3724 156.032 6 149.432 6ZM149.432 25.1992H142.258C138.297 25.1992 135.084 21.9774 135.084 17.9979C135.084 14.0183 138.293 10.7966 142.258 10.7966H149.432C153.392 10.7966 156.606 14.0183 156.606 17.9979C156.606 21.9774 153.392 25.1992 149.432 25.1992Z"></path><path d="M74.1151 25.1992H58.5736C55.4526 25.1992 52.804 23.1924 51.8171 20.3983H74.1151V17.9979C74.1151 17.1808 74.1868 16.3807 74.3175 15.5975H51.8171C52.804 12.8033 55.4526 10.7966 58.5736 10.7966H76.0383C77.1475 8.87458 78.6911 7.22773 80.5299 6H58.5736C51.969 6 46.6169 11.3724 46.6169 18.0021C46.6169 24.6276 51.969 30 58.5736 30H74.1151V25.1992Z"></path><path d="M120.74 6H115.958H102.369C104.212 7.22773 105.751 8.87458 106.861 10.8008H115.958V30H120.74V10.8008H129.838C130.947 8.87458 132.486 7.22773 134.329 6H120.74Z"></path><path d="M182.906 15.6017H169.756C168.436 15.6017 167.365 14.5264 167.365 13.2013C167.365 11.8762 168.436 10.8008 169.756 10.8008H188.882V6H169.756C165.796 6 162.582 9.22173 162.582 13.2013C162.582 17.1808 165.791 20.4025 169.756 20.4025H182.906C184.226 20.4025 185.297 21.4779 185.297 22.803C185.297 24.1281 184.226 25.2034 182.906 25.2034H161.852C160.743 27.1297 159.199 28.7765 157.361 30.0042H182.906C186.866 30.0042 190.08 26.7825 190.08 22.803C190.08 18.8234 186.866 15.6017 182.906 15.6017Z"></path></g><defs><clipPath id="clip0"><rect width="190" height="24" fill="white" transform="translate(0 6)"></rect></clipPath></defs></svg>
|
||||
<div>
|
||||
Entos Envision
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<script>
|
||||
AlphaOrbitalsExample.init('app')
|
||||
</script>
|
||||
|
||||
@@ -4,25 +4,25 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { debounceTime, skip } from 'rxjs/operators';
|
||||
import { AlphaOrbital, Basis } from '../../extensions/alpha-orbitals/data-model';
|
||||
import { SphericalBasisOrder } from '../../extensions/alpha-orbitals/spherical-functions';
|
||||
import { BasisAndOrbitals, CreateOrbitalDensityVolume, CreateOrbitalRepresentation3D, CreateOrbitalVolume, StaticBasisAndOrbitals } from '../../extensions/alpha-orbitals/transforms';
|
||||
import { createPluginAsync } from '../../mol-plugin';
|
||||
import { DefaultPluginSpec } from '../../mol-plugin/spec';
|
||||
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { createPluginAsync } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateObjectSelector, StateTransformer } from '../../mol-state';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
import { mountControls } from './controls';
|
||||
import { DemoMoleculeSDF, DemoOrbitals } from './example-data';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { debounceTime, skip } from 'rxjs/operators';
|
||||
import './index.html';
|
||||
import { Basis, AlphaOrbital } from '../../extensions/alpha-orbitals/data-model';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
interface DemoInput {
|
||||
@@ -50,18 +50,26 @@ type Selectors = {
|
||||
}
|
||||
|
||||
export class AlphaOrbitalsExample {
|
||||
plugin: PluginContext;
|
||||
plugin: PluginUIContext;
|
||||
|
||||
async init(target: string | HTMLElement) {
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
this.plugin = await createPluginAsync(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec(),
|
||||
...defaultSpec,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
},
|
||||
components: {
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' },
|
||||
},
|
||||
canvas3d: {
|
||||
camera: {
|
||||
helper: { axes: { name: 'off', params: { } } }
|
||||
}
|
||||
},
|
||||
config: [
|
||||
[PluginConfig.Viewport.ShowExpand, false],
|
||||
[PluginConfig.Viewport.ShowControls, false],
|
||||
@@ -166,7 +174,8 @@ export class AlphaOrbitalsExample {
|
||||
kind,
|
||||
relativeIsovalue: this.state.value.isoValue,
|
||||
pickable: false,
|
||||
xrayShaded: true
|
||||
xrayShaded: true,
|
||||
tryUseGpu: false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
import { PluginUIComponent } from '../../mol-plugin-ui/base';
|
||||
import * as React from 'react';
|
||||
|
||||
export class CustomToastMessage extends PluginUIComponent {
|
||||
render() {
|
||||
|
||||
@@ -4,39 +4,36 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { EmptyLoci } from '../../mol-model/loci';
|
||||
import { StructureSelection } from '../../mol-model/structure';
|
||||
import { createPlugin } from '../../mol-plugin';
|
||||
import { DefaultPluginSpec } from '../../mol-plugin/spec';
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { createPlugin } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { StripedResidues } from './coloring';
|
||||
import { CustomToastMessage } from './controls';
|
||||
import './index.html';
|
||||
import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData } from './superposition';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }
|
||||
|
||||
class BasicWrapper {
|
||||
plugin: PluginContext;
|
||||
plugin: PluginUIContext;
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec(),
|
||||
...DefaultPluginUISpec(),
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: {
|
||||
// left: 'none'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
@@ -93,7 +90,7 @@ class BasicWrapper {
|
||||
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'forward' } } }); },
|
||||
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'backward' } } }); },
|
||||
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'palindrome', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: { direction: 'forward' } } }); },
|
||||
stop: () => this.plugin.managers.animation.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,18 @@
|
||||
}
|
||||
#app {
|
||||
position: absolute;
|
||||
left: 160px;
|
||||
top: 100px;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
border: 1px solid #ccc;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: absolute;
|
||||
width: 150px;
|
||||
top: 100px;
|
||||
left: 780px;
|
||||
bottom: 100px;
|
||||
right: 50px;
|
||||
z-index: 10;
|
||||
font-family: sans-serif;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
#controls > button {
|
||||
@@ -46,13 +46,13 @@
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
LightingDemo.init('app')
|
||||
LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' })
|
||||
LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3)
|
||||
|
||||
addHeader('Example PDB IDs');
|
||||
addControl('1M07', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' }));
|
||||
addControl('6HY0', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6HY0.cif', assemblyId: '1' }));
|
||||
addControl('6QVK', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6QVK.cif', assemblyId: '1' }));
|
||||
addControl('1RB8', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1RB8.cif', assemblyId: '1' }));
|
||||
addControl('4KTC', () => LightingDemo.load({ url: 'https://models.rcsb.org/4KTC.bcif', assemblyId: '1' }, 5, 1.3));
|
||||
addControl('5FJ5', () => LightingDemo.load({ url: 'https://models.rcsb.org/5FJ5.bcif', assemblyId: '1' }, 8, 1.8));
|
||||
addControl('1UPN', () => LightingDemo.load({ url: 'https://models.rcsb.org/1UPN.bcif', assemblyId: '1' }, 7, 1.6));
|
||||
addControl('1RB8', () => LightingDemo.load({ url: 'https://models.rcsb.org/1RB8.bcif', assemblyId: '1' }, 6, 1.3));
|
||||
|
||||
addSeparator()
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
*/
|
||||
|
||||
import { Canvas3DProps } from '../../mol-canvas3d/canvas3d';
|
||||
import { createPlugin } from '../../mol-plugin';
|
||||
import { DefaultPluginSpec } from '../../mol-plugin/spec';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { createPlugin } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import './index.html';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import './index.html';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }
|
||||
@@ -25,12 +25,11 @@ const Canvas3DPresets = {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { samples: 64, radius: 8, bias: 1.0, blurKernelSize: 13 } },
|
||||
outline: { name: 'on', params: { scale: 1, threshold: 0.33 } }
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
|
||||
outline: { name: 'on', params: { scale: 1, threshold: 0.1 } }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 1,
|
||||
lightIntensity: 0,
|
||||
style: { name: 'flat', params: {} }
|
||||
}
|
||||
},
|
||||
occlusion: <Preset> {
|
||||
@@ -38,12 +37,11 @@ const Canvas3DPresets = {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { samples: 64, radius: 8, bias: 1.0, blurKernelSize: 13 } },
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
|
||||
outline: { name: 'off', params: { } }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
lightIntensity: 0.6,
|
||||
style: { name: 'matte', params: {} }
|
||||
}
|
||||
},
|
||||
standard: <Preset> {
|
||||
@@ -55,25 +53,31 @@ const Canvas3DPresets = {
|
||||
outline: { name: 'off', params: { } }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
lightIntensity: 0.6,
|
||||
style: { name: 'matte', params: {} }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
type Canvas3DPreset = keyof typeof Canvas3DPresets
|
||||
|
||||
class LightingDemo {
|
||||
plugin: PluginContext;
|
||||
plugin: PluginUIContext;
|
||||
|
||||
private radius = 5;
|
||||
private bias = 1.1;
|
||||
private preset: Canvas3DPreset = 'illustrative';
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec(),
|
||||
...DefaultPluginUISpec(),
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
},
|
||||
components: {
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
|
||||
}
|
||||
});
|
||||
@@ -83,6 +87,10 @@ class LightingDemo {
|
||||
|
||||
setPreset(preset: Canvas3DPreset) {
|
||||
const props = Canvas3DPresets[preset];
|
||||
if (props.postprocessing.occlusion?.name === 'on') {
|
||||
props.postprocessing.occlusion.params.radius = this.radius;
|
||||
props.postprocessing.occlusion.params.bias = this.bias;
|
||||
}
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
|
||||
...props,
|
||||
multiSample: {
|
||||
@@ -100,7 +108,7 @@ class LightingDemo {
|
||||
}});
|
||||
}
|
||||
|
||||
async load({ url, format = 'mmcif', isBinary = false, assemblyId = '' }: LoadParams) {
|
||||
async load({ url, format = 'mmcif', isBinary = true, assemblyId = '' }: LoadParams, radius: number, bias: number) {
|
||||
await this.plugin.clear();
|
||||
|
||||
const data = await this.plugin.builders.data.download({ url: Asset.Url(url), isBinary }, { state: { isGhost: true } });
|
||||
@@ -112,7 +120,11 @@ class LightingDemo {
|
||||
if (polymer) await this.plugin.builders.structure.representation.addRepresentation(polymer, { type: 'spacefill', color: 'illustrative' });
|
||||
|
||||
const ligand = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'ligand');
|
||||
if (ligand) await this.plugin.builders.structure.representation.addRepresentation(ligand, { type: 'ball-and-stick' });
|
||||
if (ligand) await this.plugin.builders.structure.representation.addRepresentation(ligand, { type: 'ball-and-stick', color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } });
|
||||
|
||||
this.radius = radius;
|
||||
this.bias = bias;
|
||||
this.setPreset(this.preset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Canvas3DProps, DefaultCanvas3DParams } from '../../mol-canvas3d/canvas3d';
|
||||
import { createPlugin } from '../../mol-plugin';
|
||||
import { DefaultPluginSpec } from '../../mol-plugin/spec';
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in/model-index';
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
import { PluginStateObject, PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { createPlugin } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { CreateVolumeStreamingInfo, InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
|
||||
@@ -41,13 +41,13 @@ class MolStarProteopediaWrapper {
|
||||
modelInfo: this._ev<ModelInfo>()
|
||||
};
|
||||
|
||||
plugin: PluginContext;
|
||||
plugin: PluginUIContext;
|
||||
|
||||
init(target: string | HTMLElement, options?: {
|
||||
customColorList?: number[]
|
||||
}) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec(),
|
||||
...DefaultPluginUISpec(),
|
||||
animations: [
|
||||
AnimateModelIndex
|
||||
],
|
||||
@@ -282,7 +282,7 @@ class MolStarProteopediaWrapper {
|
||||
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'forward' } } }); },
|
||||
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'once', params: { direction: 'backward' } } }); },
|
||||
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'palindrome', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { duration: { name: 'computed', params: { targetFps: this.animateModelIndexTargetFps() } }, mode: { name: 'loop', params: { direction: 'forward' } } }); },
|
||||
stop: () => this.plugin.managers.animation.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { PluginUIContext } from '../../../mol-plugin-ui/context';
|
||||
import { PluginContextContainer } from '../../../mol-plugin-ui/plugin';
|
||||
import { TransformUpdaterControl } from '../../../mol-plugin-ui/state/update-transform';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateElements } from '../helpers';
|
||||
|
||||
export function volumeStreamingControls(plugin: PluginContext, parent: Element) {
|
||||
export function volumeStreamingControls(plugin: PluginUIContext, parent: Element) {
|
||||
ReactDOM.render(<PluginContextContainer plugin={plugin}>
|
||||
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
|
||||
</PluginContextContainer>, parent);
|
||||
|
||||
@@ -172,7 +172,8 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
color: PD.Color(ColorNames.blue),
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
xrayShaded: PD.Boolean(false),
|
||||
pickable: PD.Boolean(true)
|
||||
pickable: PD.Boolean(true),
|
||||
tryUseGpu: PD.Boolean(true)
|
||||
}
|
||||
})({
|
||||
canAutoUpdate() {
|
||||
@@ -190,7 +191,7 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, params));
|
||||
await repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
repr.setState({ pickable: srcParams.pickable });
|
||||
return new PluginStateObject.Volume.Representation3D({ repr, source: a }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
|
||||
return new PluginStateObject.Volume.Representation3D({ repr, sourceData: a.data }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
|
||||
});
|
||||
},
|
||||
update({ a, b, newParams: srcParams }, plugin: PluginContext) {
|
||||
@@ -200,6 +201,7 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams.type.params };
|
||||
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, newParams));
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
b.data.repr.setState({ pickable: srcParams.pickable });
|
||||
b.description = VolumeRepresentation3DHelpers.getDescription(props);
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
@@ -229,7 +231,7 @@ function volumeParams(plugin: PluginContext, volume: PluginStateObject.Volume.Da
|
||||
colorParams: { value: params.color }
|
||||
} : {
|
||||
type: 'isosurface',
|
||||
typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded },
|
||||
typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded, tryUseGpu: params.tryUseGpu },
|
||||
color: 'uniform',
|
||||
colorParams: { value: params.color }
|
||||
});
|
||||
|
||||
@@ -121,7 +121,7 @@ const MembraneOrientation3D = PluginStateTransform.BuiltIn({
|
||||
await MembraneOrientationProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
|
||||
const repr = MembraneOrientationRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => MembraneOrientationParams);
|
||||
await repr.createOrUpdate(params, a.data).runInContext(ctx);
|
||||
return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: 'Membrane Orientation' });
|
||||
return new PluginStateObject.Shape.Representation3D({ repr, sourceData: a.data }, { label: 'Membrane Orientation' });
|
||||
});
|
||||
},
|
||||
update({ a, b, newParams }, plugin: PluginContext) {
|
||||
@@ -129,6 +129,7 @@ const MembraneOrientation3D = PluginStateTransform.BuiltIn({
|
||||
await MembraneOrientationProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -414,7 +414,7 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
|
||||
const structure = new Structure(units);
|
||||
const structure = Structure.create(units);
|
||||
for( let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { merge } from 'rxjs';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
|
||||
|
||||
@@ -124,7 +124,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
|
||||
const repr = AssemblySymmetryRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => AssemblySymmetryParams);
|
||||
await repr.createOrUpdate(params, a.data).runInContext(ctx);
|
||||
const { type, kind, symbol } = assemblySymmetry;
|
||||
return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: kind, description: `${type} (${symbol})` });
|
||||
return new PluginStateObject.Shape.Representation3D({ repr, sourceData: a.data }, { label: kind, description: `${type} (${symbol})` });
|
||||
});
|
||||
},
|
||||
update({ a, b, newParams }, plugin: PluginContext) {
|
||||
@@ -138,6 +138,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
const { type, kind, symbol } = assemblySymmetry;
|
||||
b.label = kind;
|
||||
b.description = `${type} (${symbol})`;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { CollapsableState, CollapsableControls } from '../../../mol-plugin-ui/base';
|
||||
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
|
||||
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry } from './behavior';
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra';
|
||||
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
|
||||
import { CameraTransitionManager } from './camera/transition';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Scene } from '../mol-gl/scene';
|
||||
|
||||
export { ICamera, Camera };
|
||||
|
||||
@@ -126,6 +127,23 @@ class Camera implements ICamera {
|
||||
return state;
|
||||
}
|
||||
|
||||
getInvariantFocus(target: Vec3, radius: number, up: Vec3, dir: Vec3): Partial<Camera.Snapshot> {
|
||||
const r = Math.max(radius, 0.01);
|
||||
const targetDistance = this.getTargetDistance(r);
|
||||
|
||||
Vec3.copy(this.deltaDirection, dir);
|
||||
Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance);
|
||||
Vec3.sub(this.newPosition, target, this.deltaDirection);
|
||||
|
||||
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
|
||||
state.target = Vec3.clone(target);
|
||||
state.radius = r;
|
||||
state.position = Vec3.clone(this.newPosition);
|
||||
Vec3.copy(state.up, up);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
focus(target: Vec3, radius: number, durationMs?: number, up?: Vec3, dir?: Vec3) {
|
||||
if (radius > 0) {
|
||||
this.setState(this.getFocus(target, radius, up, dir), durationMs);
|
||||
@@ -150,6 +168,8 @@ class Camera implements ICamera {
|
||||
namespace Camera {
|
||||
export type Mode = 'perspective' | 'orthographic'
|
||||
|
||||
export type SnapshotProvider = Partial<Snapshot> | ((scene: Scene, camera: Camera) => Partial<Snapshot>)
|
||||
|
||||
/**
|
||||
* Sets an offseted view in a larger frustum. This is useful for
|
||||
* - multi-window or multi-monitor/multi-machine setups
|
||||
|
||||
@@ -229,7 +229,7 @@ interface Canvas3D {
|
||||
/** performs handleResize on the next animation frame */
|
||||
requestResize(): void
|
||||
/** Focuses camera on scene's bounding sphere, centered and zoomed. */
|
||||
requestCameraReset(options?: { durationMs?: number, snapshot?: Partial<Camera.Snapshot> }): void
|
||||
requestCameraReset(options?: { durationMs?: number, snapshot?: Camera.SnapshotProvider }): void
|
||||
readonly camera: Camera
|
||||
readonly boundingSphere: Readonly<Sphere3D>
|
||||
setProps(props: PartialCanvas3DProps | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void), doNotRequestDraw?: boolean /* = false */): void
|
||||
@@ -296,7 +296,7 @@ namespace Canvas3D {
|
||||
let drawPending = false;
|
||||
let cameraResetRequested = false;
|
||||
let nextCameraResetDuration: number | undefined = void 0;
|
||||
let nextCameraResetSnapshot: Partial<Camera.Snapshot> | undefined = void 0;
|
||||
let nextCameraResetSnapshot: Camera.SnapshotProvider | undefined = void 0;
|
||||
let resizeRequested = false;
|
||||
|
||||
let notifyDidDraw = true;
|
||||
@@ -305,7 +305,11 @@ namespace Canvas3D {
|
||||
let loci: Loci = EmptyLoci;
|
||||
let repr: Representation.Any = Representation.Empty;
|
||||
if (pickingId) {
|
||||
const cameraHelperLoci = helper.camera.getLoci(pickingId);
|
||||
if (cameraHelperLoci !== EmptyLoci) return { loci: cameraHelperLoci, repr };
|
||||
|
||||
loci = helper.handle.getLoci(pickingId);
|
||||
|
||||
reprRenderObjects.forEach((_, _repr) => {
|
||||
const _loci = _repr.getLoci(pickingId);
|
||||
if (!isEmptyLoci(_loci)) {
|
||||
@@ -327,11 +331,13 @@ namespace Canvas3D {
|
||||
changed = repr.mark(loci, action);
|
||||
} else {
|
||||
changed = helper.handle.mark(loci, action);
|
||||
changed = helper.camera.mark(loci, action) || changed;
|
||||
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
|
||||
}
|
||||
if (changed) {
|
||||
scene.update(void 0, true);
|
||||
helper.handle.scene.update(void 0, true);
|
||||
helper.camera.scene.update(void 0, true);
|
||||
const prevPickDirty = pickHelper.dirty;
|
||||
draw(true);
|
||||
pickHelper.dirty = prevPickDirty; // marking does not change picking buffers
|
||||
@@ -453,11 +459,21 @@ namespace Canvas3D {
|
||||
function resolveCameraReset() {
|
||||
if (!cameraResetRequested) return;
|
||||
|
||||
const { center, radius } = scene.boundingSphereVisible;
|
||||
const boundingSphere = scene.boundingSphereVisible;
|
||||
const { center, radius } = boundingSphere;
|
||||
|
||||
const autoAdjustControls = controls.props.autoAdjustMinMaxDistance;
|
||||
if (autoAdjustControls.name === 'on') {
|
||||
const minDistance = autoAdjustControls.params.minDistanceFactor * radius + autoAdjustControls.params.minDistancePadding;
|
||||
const maxDistance = Math.max(autoAdjustControls.params.maxDistanceFactor * radius, autoAdjustControls.params.maxDistanceMin);
|
||||
controls.setProps({ minDistance, maxDistance });
|
||||
}
|
||||
|
||||
if (radius > 0) {
|
||||
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration;
|
||||
const focus = camera.getFocus(center, radius);
|
||||
const snapshot = nextCameraResetSnapshot ? { ...focus, ...nextCameraResetSnapshot } : focus;
|
||||
const next = typeof nextCameraResetSnapshot === 'function' ? nextCameraResetSnapshot(scene, camera) : nextCameraResetSnapshot;
|
||||
const snapshot = next ? { ...focus, ...next } : focus;
|
||||
camera.setState({ ...snapshot, radiusMax: scene.boundingSphere.radius }, duration);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,21 @@ export const TrackballControlsParams = {
|
||||
minDistance: PD.Numeric(0.01, {}, { isHidden: true }),
|
||||
maxDistance: PD.Numeric(1e150, {}, { isHidden: true }),
|
||||
|
||||
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true })
|
||||
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true }),
|
||||
|
||||
/**
|
||||
* minDistance = minDistanceFactor * boundingSphere.radius + minDistancePadding
|
||||
* maxDistance = max(maxDistanceFactor * boundingSphere.radius, maxDistanceMin)
|
||||
*/
|
||||
autoAdjustMinMaxDistance: PD.MappedStatic('on', {
|
||||
off: PD.EmptyGroup(),
|
||||
on: PD.Group({
|
||||
minDistanceFactor: PD.Numeric(0),
|
||||
minDistancePadding: PD.Numeric(5),
|
||||
maxDistanceFactor: PD.Numeric(10),
|
||||
maxDistanceMin: PD.Numeric(20)
|
||||
})
|
||||
}, { isHidden: true })
|
||||
};
|
||||
export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams>
|
||||
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Scene } from '../../mol-gl/scene';
|
||||
import { Camera, ICamera } from '../camera';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { Viewport } from '../camera/util';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import produce from 'immer';
|
||||
import { Interval } from '../../mol-data/int/interval';
|
||||
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
import { Scene } from '../../mol-gl/scene';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { DataLoci, EmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { Visual } from '../../mol-repr/visual';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Camera, ICamera } from '../camera';
|
||||
import { Viewport } from '../camera/util';
|
||||
|
||||
// TODO add scale line/grid
|
||||
|
||||
const AxesParams = {
|
||||
...Mesh.Params,
|
||||
alpha: { ...Mesh.Params.alpha, defaultValue: 0.33 },
|
||||
alpha: { ...Mesh.Params.alpha, defaultValue: 0.51 },
|
||||
ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
|
||||
colorX: PD.Color(ColorNames.red, { isEssential: true }),
|
||||
colorY: PD.Color(ColorNames.green, { isEssential: true }),
|
||||
@@ -87,6 +92,32 @@ export class CameraHelper {
|
||||
return this.props.axes.name === 'on';
|
||||
}
|
||||
|
||||
getLoci(pickingId: PickingId) {
|
||||
const { objectId, groupId, instanceId } = pickingId;
|
||||
if (!this.renderObject || objectId !== this.renderObject.id || groupId === CameraHelperAxis.None) return EmptyLoci;
|
||||
return CameraAxesLoci(this, groupId, instanceId);
|
||||
}
|
||||
|
||||
private eachGroup = (loci: Loci, apply: (interval: Interval) => boolean): boolean => {
|
||||
if (!this.renderObject) return false;
|
||||
if (!isCameraAxesLoci(loci)) return false;
|
||||
let changed = false;
|
||||
const groupCount = this.renderObject.values.uGroupCount.ref.value;
|
||||
const { elements } = loci;
|
||||
for (const { groupId, instanceId } of elements) {
|
||||
const idx = instanceId * groupCount + groupId;
|
||||
if (apply(Interval.ofSingleton(idx))) changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
mark(loci: Loci, action: MarkerAction) {
|
||||
if (!MarkerActions.is(MarkerActions.Highlighting, action)) return false;
|
||||
if (!isCameraAxesLoci(loci)) return false;
|
||||
if (loci.data !== this) return false;
|
||||
return Visual.mark(this.renderObject, loci, action, this.eachGroup);
|
||||
}
|
||||
|
||||
update(camera: ICamera) {
|
||||
if (!this.renderObject) return;
|
||||
|
||||
@@ -102,6 +133,38 @@ export class CameraHelper {
|
||||
}
|
||||
}
|
||||
|
||||
export const enum CameraHelperAxis {
|
||||
None = 0,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
XY,
|
||||
XZ,
|
||||
YZ
|
||||
}
|
||||
|
||||
function getAxisLabel(axis: number) {
|
||||
switch (axis) {
|
||||
case CameraHelperAxis.X: return 'X Axis';
|
||||
case CameraHelperAxis.Y: return 'Y Axis';
|
||||
case CameraHelperAxis.Z: return 'Z Axis';
|
||||
case CameraHelperAxis.XY: return 'XY Plane';
|
||||
case CameraHelperAxis.XZ: return 'XZ Plane';
|
||||
case CameraHelperAxis.YZ: return 'YZ Plane';
|
||||
default: return 'Axes';
|
||||
}
|
||||
}
|
||||
|
||||
function CameraAxesLoci(cameraHelper: CameraHelper, groupId: number, instanceId: number) {
|
||||
return DataLoci('camera-axes', cameraHelper, [{ groupId, instanceId }],
|
||||
void 0 /** bounding sphere */,
|
||||
() => getAxisLabel(groupId));
|
||||
}
|
||||
export type CameraAxesLoci = ReturnType<typeof CameraAxesLoci>
|
||||
export function isCameraAxesLoci(x: Loci): x is CameraAxesLoci {
|
||||
return x.kind === 'data-loci' && x.tag === 'camera-axes';
|
||||
}
|
||||
|
||||
function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.ViewOffset) {
|
||||
const { near, far } = camera;
|
||||
|
||||
@@ -134,27 +197,52 @@ function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.Vie
|
||||
|
||||
function createAxesMesh(scale: number, mesh?: Mesh) {
|
||||
const state = MeshBuilder.createState(512, 256, mesh);
|
||||
const radius = 0.05 * scale;
|
||||
const radius = 0.075 * scale;
|
||||
const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
|
||||
const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
|
||||
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
|
||||
|
||||
state.currentGroup = 0;
|
||||
state.currentGroup = CameraHelperAxis.None;
|
||||
addSphere(state, Vec3.origin, radius, 2);
|
||||
|
||||
state.currentGroup = 1;
|
||||
state.currentGroup = CameraHelperAxis.X;
|
||||
addSphere(state, x, radius, 2);
|
||||
addCylinder(state, Vec3.origin, x, 1, cylinderProps);
|
||||
|
||||
state.currentGroup = 2;
|
||||
state.currentGroup = CameraHelperAxis.Y;
|
||||
addSphere(state, y, radius, 2);
|
||||
addCylinder(state, Vec3.origin, y, 1, cylinderProps);
|
||||
|
||||
state.currentGroup = 3;
|
||||
state.currentGroup = CameraHelperAxis.Z;
|
||||
addSphere(state, z, radius, 2);
|
||||
addCylinder(state, Vec3.origin, z, 1, cylinderProps);
|
||||
|
||||
Vec3.scale(x, x, 0.5);
|
||||
Vec3.scale(y, y, 0.5);
|
||||
Vec3.scale(z, z, 0.5);
|
||||
|
||||
state.currentGroup = CameraHelperAxis.XY;
|
||||
MeshBuilder.addTriangle(state, Vec3.origin, x, y);
|
||||
MeshBuilder.addTriangle(state, Vec3.origin, y, x);
|
||||
const xy = Vec3.add(Vec3(), x, y);
|
||||
MeshBuilder.addTriangle(state, xy, x, y);
|
||||
MeshBuilder.addTriangle(state, xy, y, x);
|
||||
|
||||
state.currentGroup = CameraHelperAxis.XZ;
|
||||
MeshBuilder.addTriangle(state, Vec3.origin, x, z);
|
||||
MeshBuilder.addTriangle(state, Vec3.origin, z, x);
|
||||
const xz = Vec3.add(Vec3(), x, z);
|
||||
MeshBuilder.addTriangle(state, xz, x, z);
|
||||
MeshBuilder.addTriangle(state, xz, z, x);
|
||||
|
||||
state.currentGroup = CameraHelperAxis.YZ;
|
||||
MeshBuilder.addTriangle(state, Vec3.origin, y, z);
|
||||
MeshBuilder.addTriangle(state, Vec3.origin, z, y);
|
||||
const yz = Vec3.add(Vec3(), y, z);
|
||||
MeshBuilder.addTriangle(state, yz, y, z);
|
||||
MeshBuilder.addTriangle(state, yz, z, y);
|
||||
|
||||
return MeshBuilder.getMesh(state);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
@@ -100,7 +100,7 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, width: number, height: number, enableWboit: boolean) {
|
||||
const { extensions, resources } = webgl;
|
||||
const { extensions, resources, isWebGL2 } = webgl;
|
||||
|
||||
this.drawTarget = createNullRenderTarget(webgl.gl);
|
||||
|
||||
@@ -113,8 +113,8 @@ export class DrawPass {
|
||||
this.depthTargetPrimitives = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
|
||||
this.depthTargetVolumes = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
|
||||
|
||||
this.depthTexturePrimitives = this.depthTargetPrimitives ? this.depthTargetPrimitives.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest');
|
||||
this.depthTextureVolumes = this.depthTargetVolumes ? this.depthTargetVolumes.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest');
|
||||
this.depthTexturePrimitives = this.depthTargetPrimitives ? this.depthTargetPrimitives.texture : resources.texture('image-depth', 'depth', isWebGL2 ? 'float' : 'ushort', 'nearest');
|
||||
this.depthTextureVolumes = this.depthTargetVolumes ? this.depthTargetVolumes.texture : resources.texture('image-depth', 'depth', isWebGL2 ? 'float' : 'ushort', 'nearest');
|
||||
if (!this.packedDepth) {
|
||||
this.depthTexturePrimitives.define(width, height);
|
||||
this.depthTextureVolumes.define(width, height);
|
||||
|
||||
@@ -66,14 +66,20 @@ export class PickPass {
|
||||
private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant) {
|
||||
const depth = this.drawPass.depthTexturePrimitives;
|
||||
renderer.clear(false);
|
||||
|
||||
renderer.update(camera);
|
||||
renderer.renderPick(scene.primitives, camera, variant, null);
|
||||
renderer.renderPick(scene.volumes, camera, variant, depth);
|
||||
renderer.renderPick(helper.handle.scene, camera, variant, null);
|
||||
|
||||
if (helper.camera.isEnabled) {
|
||||
helper.camera.update(camera);
|
||||
renderer.update(helper.camera.camera);
|
||||
renderer.renderPick(helper.camera.scene, helper.camera.camera, variant, null);
|
||||
}
|
||||
}
|
||||
|
||||
render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper) {
|
||||
renderer.update(camera);
|
||||
|
||||
this.objectPickTarget.bind();
|
||||
this.renderVariant(renderer, camera, scene, helper, 'pickObject');
|
||||
|
||||
|
||||
@@ -12,13 +12,13 @@ export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height:
|
||||
}
|
||||
|
||||
/** Resize canvas to container element taking `devicePixelRatio` into account */
|
||||
export function resizeCanvas (canvas: HTMLCanvasElement, container: Element, scale = 1) {
|
||||
export function resizeCanvas (canvas: HTMLCanvasElement, container: HTMLElement, scale = 1) {
|
||||
let width = window.innerWidth;
|
||||
let height = window.innerHeight;
|
||||
if (container !== document.body) {
|
||||
let bounds = container.getBoundingClientRect();
|
||||
width = bounds.right - bounds.left;
|
||||
height = bounds.bottom - bounds.top;
|
||||
// fixes issue #molstar/molstar#147, offsetWidth/offsetHeight is correct size when css transform:scale is used
|
||||
width = container.offsetWidth;
|
||||
height = container.offsetHeight;
|
||||
}
|
||||
setCanvasSize(canvas, width, height, scale);
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ export namespace Spheres {
|
||||
|
||||
const counts = { drawCount: spheres.sphereCount * 2 * 3, vertexCount: spheres.sphereCount * 4, groupCount, instanceCount };
|
||||
|
||||
const padding = getMaxSize(size) * props.sizeFactor;
|
||||
const padding = spheres.boundingSphere.radius ? getMaxSize(size) * props.sizeFactor : 0;
|
||||
const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), spheres.boundingSphere, padding);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
@@ -222,7 +222,9 @@ export namespace Spheres {
|
||||
}
|
||||
|
||||
function updateBoundingSphere(values: SpheresValues, spheres: Spheres) {
|
||||
const padding = getMaxSize(values) * values.uSizeFactor.ref.value;
|
||||
const padding = spheres.boundingSphere.radius
|
||||
? getMaxSize(values) * values.uSizeFactor.ref.value
|
||||
: 0;
|
||||
const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), spheres.boundingSphere, padding);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -36,11 +36,36 @@ export interface TextureMesh {
|
||||
readonly vertexTexture: ValueCell<Texture>,
|
||||
readonly groupTexture: ValueCell<Texture>,
|
||||
readonly normalTexture: ValueCell<Texture>,
|
||||
readonly doubleBuffer: TextureMesh.DoubleBuffer
|
||||
|
||||
readonly boundingSphere: Sphere3D
|
||||
}
|
||||
|
||||
export namespace TextureMesh {
|
||||
export class DoubleBuffer {
|
||||
private index = 0;
|
||||
private textures: ({ vertex: Texture, group: Texture, normal: Texture } | undefined)[] = []
|
||||
|
||||
get() {
|
||||
return this.textures[this.index];
|
||||
}
|
||||
|
||||
set(vertex: Texture, group: Texture, normal: Texture) {
|
||||
this.textures[this.index] = Object.assign(this.textures[this.index] || {}, {
|
||||
vertex, group, normal
|
||||
});
|
||||
this.index = (this.index + 1) % 2;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (const buffer of this.textures) {
|
||||
buffer!.vertex.destroy();
|
||||
buffer!.group.destroy();
|
||||
buffer!.normal.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function create(vertexCount: number, groupCount: number, vertexTexture: Texture, groupTexture: Texture, normalTexture: Texture, boundingSphere: Sphere3D, textureMesh?: TextureMesh): TextureMesh {
|
||||
const width = vertexTexture.getWidth();
|
||||
const height = vertexTexture.getHeight();
|
||||
@@ -49,7 +74,9 @@ export namespace TextureMesh {
|
||||
textureMesh.groupCount = groupCount;
|
||||
ValueCell.update(textureMesh.geoTextureDim, Vec2.set(textureMesh.geoTextureDim.ref.value, width, height));
|
||||
ValueCell.update(textureMesh.vertexTexture, vertexTexture);
|
||||
ValueCell.update(textureMesh.groupTexture, groupTexture);
|
||||
ValueCell.update(textureMesh.normalTexture, normalTexture);
|
||||
textureMesh.doubleBuffer.set(vertexTexture, groupTexture, normalTexture);
|
||||
Sphere3D.copy(textureMesh.boundingSphere, boundingSphere);
|
||||
return textureMesh;
|
||||
} else {
|
||||
@@ -61,6 +88,7 @@ export namespace TextureMesh {
|
||||
vertexTexture: ValueCell.create(vertexTexture),
|
||||
groupTexture: ValueCell.create(groupTexture),
|
||||
normalTexture: ValueCell.create(normalTexture),
|
||||
doubleBuffer: new DoubleBuffer(),
|
||||
boundingSphere: Sphere3D.clone(boundingSphere),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import { quad_vert } from '../../../mol-gl/shader/quad.vert';
|
||||
import { isosurface_frag } from '../../../mol-gl/shader/marching-cubes/isosurface.frag';
|
||||
import { calcActiveVoxels } from './active-voxels';
|
||||
import { isWebGL2 } from '../../webgl/compat';
|
||||
import { Scheduler } from '../../../mol-task';
|
||||
|
||||
const IsosurfaceSchema = {
|
||||
...QuadSchema,
|
||||
@@ -33,6 +32,7 @@ const IsosurfaceSchema = {
|
||||
uSize: UniformSpec('f'),
|
||||
uLevels: UniformSpec('f'),
|
||||
uCount: UniformSpec('f'),
|
||||
uInvert: UniformSpec('b'),
|
||||
|
||||
uGridDim: UniformSpec('v3'),
|
||||
uGridTexDim: UniformSpec('v3'),
|
||||
@@ -45,7 +45,7 @@ type IsosurfaceValues = Values<typeof IsosurfaceSchema>
|
||||
|
||||
const IsosurfaceName = 'isosurface';
|
||||
|
||||
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, packedGroup: boolean): ComputeRenderable<IsosurfaceValues> {
|
||||
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean): ComputeRenderable<IsosurfaceValues> {
|
||||
if (ctx.namedComputeRenderables[IsosurfaceName]) {
|
||||
const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues;
|
||||
|
||||
@@ -57,6 +57,7 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
|
||||
ValueCell.updateIfChanged(v.uSize, Math.pow(2, levels));
|
||||
ValueCell.updateIfChanged(v.uLevels, levels);
|
||||
ValueCell.updateIfChanged(v.uCount, count);
|
||||
ValueCell.updateIfChanged(v.uInvert, invert);
|
||||
|
||||
ValueCell.update(v.uGridDim, gridDim);
|
||||
ValueCell.update(v.uGridTexDim, gridTexDim);
|
||||
@@ -67,12 +68,12 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
|
||||
|
||||
ctx.namedComputeRenderables[IsosurfaceName].update();
|
||||
} else {
|
||||
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, packedGroup);
|
||||
ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup);
|
||||
}
|
||||
return ctx.namedComputeRenderables[IsosurfaceName];
|
||||
}
|
||||
|
||||
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, packedGroup: boolean) {
|
||||
function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean) {
|
||||
// console.log('uSize', Math.pow(2, levels))
|
||||
const values: IsosurfaceValues = {
|
||||
...QuadValues,
|
||||
@@ -86,6 +87,7 @@ function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Text
|
||||
uSize: ValueCell.create(Math.pow(2, levels)),
|
||||
uLevels: ValueCell.create(levels),
|
||||
uCount: ValueCell.create(count),
|
||||
uInvert: ValueCell.create(invert),
|
||||
|
||||
uGridDim: ValueCell.create(gridDim),
|
||||
uGridTexDim: ValueCell.create(gridTexDim),
|
||||
@@ -113,7 +115,7 @@ function setRenderingDefaults(ctx: WebGLContext) {
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
const { gl, resources, extensions } = ctx;
|
||||
const { pyramidTex, height, levels, scale, count } = histogramPyramid;
|
||||
const width = pyramidTex.getWidth();
|
||||
@@ -168,7 +170,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
groupTexture.attachFramebuffer(framebuffer, 1);
|
||||
normalTexture.attachFramebuffer(framebuffer, 2);
|
||||
|
||||
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, packedGroup);
|
||||
const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup);
|
||||
ctx.state.currentRenderItemId = -1;
|
||||
|
||||
const { drawBuffers } = ctx.extensions;
|
||||
@@ -193,29 +195,28 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
|
||||
//
|
||||
|
||||
function delay() {
|
||||
return new Promise(r => Scheduler.setImmediate(r));
|
||||
}
|
||||
|
||||
export async function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
/**
|
||||
* GPU isosurface extraction
|
||||
*
|
||||
* Algorithm from "High‐speed Marching Cubes using HistoPyramids"
|
||||
* by C Dyken, G Ziegler, C Theobalt, HP Seidel
|
||||
* https://doi.org/10.1111/j.1467-8659.2008.01182.x
|
||||
*
|
||||
* Implementation based on http://www.miaumiau.cat/2016/10/stream-compaction-in-webgl/
|
||||
*/
|
||||
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
// console.time('calcActiveVoxels');
|
||||
const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
|
||||
// apply advanced magic to solve incomplete buffer rendering issue
|
||||
await delay();
|
||||
// ctx.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('calcActiveVoxels');
|
||||
|
||||
// console.time('createHistogramPyramid');
|
||||
const compacted = createHistogramPyramid(ctx, activeVoxelsTex, gridTexScale, gridTexDim);
|
||||
// apply advanced magic to solve incomplete buffer rendering issue
|
||||
await delay();
|
||||
|
||||
// ctx.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('createHistogramPyramid');
|
||||
|
||||
// console.time('createIsosurfaceBuffers');
|
||||
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, packedGroup, vertexTexture, groupTexture, normalTexture);
|
||||
// apply advanced magic to solve incomplete buffer rendering issue
|
||||
await delay();
|
||||
|
||||
const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, vertexTexture, groupTexture, normalTexture);
|
||||
// ctx.waitForGpuCommandsCompleteSync();
|
||||
// console.timeEnd('createIsosurfaceBuffers');
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues)
|
||||
const textureValues: TextureValues = {};
|
||||
const uniformValues: UniformValues = {};
|
||||
const materialUniformValues: UniformValues = {};
|
||||
const bufferedUniformValues: UniformValues = {};
|
||||
Object.keys(schema).forEach(k => {
|
||||
const spec = schema[k];
|
||||
if (spec.type === 'attribute') attributeValues[k] = values[k];
|
||||
@@ -45,11 +46,12 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues)
|
||||
if (spec.type === 'texture' && values[k] !== undefined) textureValues[k] = values[k];
|
||||
// check if k exists in values to exclude global uniforms
|
||||
if (spec.type === 'uniform' && values[k] !== undefined) {
|
||||
if (spec.isMaterial) materialUniformValues[k] = values[k];
|
||||
if (spec.variant === 'material') materialUniformValues[k] = values[k];
|
||||
else if (spec.variant === 'buffered') bufferedUniformValues[k] = values[k];
|
||||
else uniformValues[k] = values[k];
|
||||
}
|
||||
});
|
||||
return { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues };
|
||||
return { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues, bufferedUniformValues };
|
||||
}
|
||||
|
||||
export type Versions<T extends RenderableValues> = { [k in keyof T]: number }
|
||||
@@ -68,9 +70,9 @@ export function AttributeSpec<K extends AttributeKind>(kind: K, itemSize: Attrib
|
||||
return { type: 'attribute', kind, itemSize, divisor };
|
||||
}
|
||||
|
||||
export type UniformSpec<K extends UniformKind> = { type: 'uniform', kind: K, isMaterial: boolean }
|
||||
export function UniformSpec<K extends UniformKind>(kind: K, isMaterial = false): UniformSpec<K> {
|
||||
return { type: 'uniform', kind, isMaterial };
|
||||
export type UniformSpec<K extends UniformKind> = { type: 'uniform', kind: K, variant?: 'material' | 'buffered' }
|
||||
export function UniformSpec<K extends UniformKind>(kind: K, variant?: 'material' | 'buffered'): UniformSpec<K> {
|
||||
return { type: 'uniform', kind, variant };
|
||||
}
|
||||
|
||||
export type TextureSpec<K extends TextureKind> = { type: 'texture', kind: K, format: TextureFormat, dataType: TextureType, filter: TextureFilter }
|
||||
@@ -180,7 +182,7 @@ export type InternalValues = Values<InternalSchema>
|
||||
|
||||
export const ColorSchema = {
|
||||
// aColor: AttributeSpec('float32', 3, 0), // TODO
|
||||
uColor: UniformSpec('v3', true),
|
||||
uColor: UniformSpec('v3', 'material'),
|
||||
uColorTexDim: UniformSpec('v2'),
|
||||
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
|
||||
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
|
||||
@@ -190,7 +192,7 @@ export type ColorValues = Values<ColorSchema>
|
||||
|
||||
export const SizeSchema = {
|
||||
// aSize: AttributeSpec('float32', 1, 0), // TODO
|
||||
uSize: UniformSpec('f', true),
|
||||
uSize: UniformSpec('f', 'material'),
|
||||
uSizeTexDim: UniformSpec('v2'),
|
||||
tSize: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
|
||||
dSizeType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance']),
|
||||
@@ -251,7 +253,7 @@ export const BaseSchema = {
|
||||
/**
|
||||
* final alpha, calculated as `values.alpha * state.alpha`
|
||||
*/
|
||||
uAlpha: UniformSpec('f', true),
|
||||
uAlpha: UniformSpec('f', 'material'),
|
||||
uVertexCount: UniformSpec('i'),
|
||||
uInstanceCount: UniformSpec('i'),
|
||||
uGroupCount: UniformSpec('i'),
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ValueCell } from '../../mol-util';
|
||||
|
||||
export const TextureMeshSchema = {
|
||||
...BaseSchema,
|
||||
uGeoTexDim: UniformSpec('v2'),
|
||||
uGeoTexDim: UniformSpec('v2', 'buffered'),
|
||||
tPosition: TextureSpec('texture', 'rgb', 'float', 'nearest'),
|
||||
tGroup: TextureSpec('texture', 'alpha', 'float', 'nearest'),
|
||||
tNormal: TextureSpec('texture', 'rgb', 'float', 'nearest'),
|
||||
|
||||
@@ -18,6 +18,7 @@ uniform float uIsoValue;
|
||||
uniform float uLevels;
|
||||
uniform float uSize;
|
||||
uniform float uCount;
|
||||
uniform bool uInvert;
|
||||
|
||||
uniform vec3 uGridDim;
|
||||
uniform vec3 uGridTexDim;
|
||||
@@ -163,6 +164,13 @@ void main(void) {
|
||||
// current vertex for the up to 15 MC cases
|
||||
int currentVertex = vI - idot4(m, starts);
|
||||
|
||||
// ensure winding-order is the same for negative and positive iso-levels
|
||||
if (uInvert) {
|
||||
int v = imod(currentVertex + 1, 3);
|
||||
if (v == 1) currentVertex += 2;
|
||||
else if (v == 0) currentVertex -= 2;
|
||||
}
|
||||
|
||||
// get index into triIndices table
|
||||
int mcIndex = 16 * int(edgeIndex) + currentVertex;
|
||||
vec4 mcData = texture2D(tTriIndices, vec2(imod(mcIndex, 64), mcIndex / 64) / 64.);
|
||||
@@ -273,11 +281,18 @@ void main(void) {
|
||||
voxelPadded(b1 - c3).a - voxelPadded(b1 + c3).a,
|
||||
voxelPadded(b1 - c4).a - voxelPadded(b1 + c4).a
|
||||
));
|
||||
mat3 normalMatrix = transpose3(inverse3(mat3(uGridTransform)));
|
||||
gl_FragData[2].xyz = normalMatrix * -vec3(
|
||||
gl_FragData[2].xyz = -vec3(
|
||||
n0.x + t * (n0.x - n1.x),
|
||||
n0.y + t * (n0.y - n1.y),
|
||||
n0.z + t * (n0.z - n1.z)
|
||||
);
|
||||
|
||||
// ensure normal-direction is the same for negative and positive iso-levels
|
||||
if (uInvert) {
|
||||
gl_FragData[2].xyz *= -1.0;
|
||||
}
|
||||
|
||||
// apply normal matrix
|
||||
gl_FragData[2].xyz *= transpose3(inverse3(mat3(uGridTransform)));
|
||||
}
|
||||
`;
|
||||
@@ -17,6 +17,8 @@ import { checkFramebufferStatus } from './framebuffer';
|
||||
import { isDebugMode } from '../../mol-util/debug';
|
||||
import { VertexArray } from './vertex-array';
|
||||
import { fillSerial } from '../../mol-util/array';
|
||||
import { deepClone } from '../../mol-util/object';
|
||||
import { cloneUniformValues } from './uniform';
|
||||
|
||||
const getNextRenderItemId = idFactory();
|
||||
|
||||
@@ -123,10 +125,12 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
|
||||
(schema as any).aVertex = AttributeSpec('float32', 1, 0);
|
||||
}
|
||||
|
||||
const { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues } = splitValues(schema, values);
|
||||
const { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues, bufferedUniformValues } = splitValues(schema, values);
|
||||
|
||||
const uniformValueEntries = Object.entries(uniformValues);
|
||||
const materialUniformValueEntries = Object.entries(materialUniformValues);
|
||||
const backBufferUniformValueEntries = Object.entries(bufferedUniformValues);
|
||||
const frontBufferUniformValueEntries = Object.entries(cloneUniformValues(bufferedUniformValues));
|
||||
const defineValueEntries = Object.entries(defineValues);
|
||||
|
||||
const versions = getValueVersions(values);
|
||||
@@ -192,6 +196,7 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
|
||||
currentProgramId = program.id;
|
||||
}
|
||||
program.setUniforms(uniformValueEntries);
|
||||
program.setUniforms(frontBufferUniformValueEntries);
|
||||
if (sharedTexturesList && sharedTexturesList.length > 0) {
|
||||
program.bindTextures(sharedTexturesList, 0);
|
||||
program.bindTextures(textures, sharedTexturesList.length);
|
||||
@@ -318,11 +323,20 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
|
||||
if (schema[k].kind !== 'texture') {
|
||||
// console.log('texture version changed, uploading image', k);
|
||||
texture.load(value.ref.value as TextureImage<any> | TextureVolume<any>);
|
||||
versions[k] = value.ref.version;
|
||||
valueChanges.textures = true;
|
||||
} else {
|
||||
textures[i][1] = value.ref.value as Texture;
|
||||
}
|
||||
versions[k] = value.ref.version;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, il = backBufferUniformValueEntries.length; i < il; ++i) {
|
||||
const [k, uniform] = backBufferUniformValueEntries[i];
|
||||
if (uniform.ref.version !== versions[k]) {
|
||||
// console.log('back-buffer uniform version changed, updating front-buffer', k);
|
||||
ValueCell.update(frontBufferUniformValueEntries[i][1], deepClone(uniform.ref.value));
|
||||
versions[k] = uniform.ref.version;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { idFactory } from '../../mol-util/id-factory';
|
||||
import { createNullTexture, Texture, TextureFilter } from './texture';
|
||||
import { createNullFramebuffer, Framebuffer } from './framebuffer';
|
||||
import { WebGLResources } from './resources';
|
||||
import { GLRenderingContext } from './compat';
|
||||
import { GLRenderingContext, isWebGL2 } from './compat';
|
||||
|
||||
const getNextRenderTargetId = idFactory();
|
||||
|
||||
@@ -35,9 +35,11 @@ export function createRenderTarget(gl: GLRenderingContext, resources: WebGLResou
|
||||
? resources.texture('image-float32', 'rgba', 'float', filter)
|
||||
: resources.texture('image-uint8', 'rgba', 'ubyte', filter);
|
||||
// make a depth renderbuffer of the same size as the targetTexture
|
||||
const depthRenderbuffer = depth
|
||||
? resources.renderbuffer('depth16', 'depth', _width, _height)
|
||||
: null;
|
||||
const depthRenderbuffer = !depth
|
||||
? null
|
||||
: isWebGL2(gl)
|
||||
? resources.renderbuffer('depth32f', 'depth', _width, _height)
|
||||
: resources.renderbuffer('depth16', 'depth', _width, _height);
|
||||
|
||||
function init() {
|
||||
targetTexture.define(_width, _height);
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { idFactory } from '../../mol-util/id-factory';
|
||||
import { GLRenderingContext } from './compat';
|
||||
import { GLRenderingContext, isWebGL2 } from './compat';
|
||||
import { Framebuffer, checkFramebufferStatus } from './framebuffer';
|
||||
import { isDebugMode } from '../../mol-util/debug';
|
||||
|
||||
const getNextRenderbufferId = idFactory();
|
||||
|
||||
export type RenderbufferFormat = 'depth16' | 'stencil8' | 'rgba4' | 'depth-stencil'
|
||||
export type RenderbufferFormat = 'depth16' | 'stencil8' | 'rgba4' | 'depth-stencil' | 'depth32f'
|
||||
export type RenderbufferAttachment = 'depth' | 'stencil' | 'depth-stencil' | 'color0'
|
||||
|
||||
export function getFormat(gl: GLRenderingContext, format: RenderbufferFormat) {
|
||||
@@ -20,6 +20,9 @@ export function getFormat(gl: GLRenderingContext, format: RenderbufferFormat) {
|
||||
case 'stencil8': return gl.STENCIL_INDEX8;
|
||||
case 'rgba4': return gl.RGBA4;
|
||||
case 'depth-stencil': return gl.DEPTH_STENCIL;
|
||||
case 'depth32f':
|
||||
if (isWebGL2(gl)) return gl.DEPTH_COMPONENT32F;
|
||||
else throw new Error('WebGL2 needed for `depth32f` renderbuffer format');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,10 @@ export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat,
|
||||
case 'int': return gl.RGBA32I;
|
||||
}
|
||||
case 'depth':
|
||||
return gl.DEPTH_COMPONENT16;
|
||||
switch (type) {
|
||||
case 'ushort': return gl.DEPTH_COMPONENT16;
|
||||
case 'float': return gl.DEPTH_COMPONENT32F;
|
||||
}
|
||||
}
|
||||
}
|
||||
return getFormat(gl, format, type);
|
||||
@@ -229,7 +232,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
(kind.endsWith('float16') && _type !== 'fp16') ||
|
||||
(kind.endsWith('uint8') && _type !== 'ubyte') ||
|
||||
(kind.endsWith('int32') && _type !== 'int') ||
|
||||
(kind.endsWith('depth') && _type !== 'ushort')
|
||||
(kind.endsWith('depth') && _type !== 'ushort' && _type !== 'float')
|
||||
) {
|
||||
throw new Error(`texture kind '${kind}' and type '${_type}' are incompatible`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -9,6 +9,7 @@ import { ValueCell } from '../../mol-util';
|
||||
import { GLRenderingContext } from './compat';
|
||||
import { RenderableSchema } from '../../mol-gl/renderable/schema';
|
||||
import { ValueOf } from '../../mol-util/type-helpers';
|
||||
import { deepClone } from '../../mol-util/object';
|
||||
|
||||
export type UniformKindValue = {
|
||||
'b': boolean; 'b[]': boolean[]
|
||||
@@ -105,4 +106,12 @@ export function isUniformValueScalar(kind: UniformKind): boolean {
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function cloneUniformValues(uniformValues: UniformValues): UniformValues {
|
||||
const clonedValues: UniformValues = {};
|
||||
Object.keys(uniformValues).forEach(k => {
|
||||
clonedValues[k] = ValueCell.create(deepClone(uniformValues[k].ref.value));
|
||||
});
|
||||
return clonedValues;
|
||||
}
|
||||
@@ -112,7 +112,22 @@ Phosphate ion
|
||||
> <SYNONYMS>
|
||||
Orthophosphate; Phosphate
|
||||
|
||||
$$$$`;
|
||||
$$$$
|
||||
|
||||
Comp 2
|
||||
|
||||
5 4 0 0 0 0 999 V2000
|
||||
0.0000 0.8250 0.0000 O 0 5 0 0 0 0 0 0 0 0 0 0
|
||||
-0.8250 0.0000 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.0000 -0.8250 0.0000 O 0 5 0 0 0 0 0 0 0 0 0 0
|
||||
0.0000 0.0000 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.8250 0.0000 0.0000 O 0 5 0 0 0 0 0 0 0 0 0 0
|
||||
4 1 1 0 0 0 0
|
||||
4 2 2 0 0 0 0
|
||||
4 3 1 0 0 0 0
|
||||
4 5 1 0 0 0 0
|
||||
M CHG 3 1 -1 3 -1 5 -1
|
||||
M END`;
|
||||
|
||||
describe('sdf reader', () => {
|
||||
it('basic', async () => {
|
||||
@@ -120,14 +135,20 @@ describe('sdf reader', () => {
|
||||
if (parsed.isError) {
|
||||
throw new Error(parsed.message);
|
||||
}
|
||||
const compound = parsed.result.compounds[0];
|
||||
const { molFile, dataItems } = compound;
|
||||
const compound1 = parsed.result.compounds[0];
|
||||
const compound2 = parsed.result.compounds[1];
|
||||
const { molFile, dataItems } = compound1;
|
||||
const { atoms, bonds } = molFile;
|
||||
|
||||
expect(parsed.result.compounds.length).toBe(2);
|
||||
|
||||
// number of structures
|
||||
expect(atoms.count).toBe(5);
|
||||
expect(bonds.count).toBe(4);
|
||||
|
||||
expect(compound2.molFile.atoms.count).toBe(5);
|
||||
expect(compound2.molFile.bonds.count).toBe(4);
|
||||
|
||||
expect(atoms.x.value(0)).toBeCloseTo(0, 0.001);
|
||||
expect(atoms.y.value(0)).toBeCloseTo(0.8250, 0.0001);
|
||||
expect(atoms.z.value(0)).toBeCloseTo(0, 0.0001);
|
||||
|
||||
@@ -91,12 +91,21 @@ namespace Tokenizer {
|
||||
return eatLine(state);
|
||||
}
|
||||
|
||||
/** Advance the state by the given number of lines and return line as string. */
|
||||
/** Advance the state and return line as string. */
|
||||
export function readLine(state: Tokenizer): string {
|
||||
markLine(state);
|
||||
return getTokenString(state);
|
||||
}
|
||||
|
||||
/** Advance the state and return trimmed line as string. */
|
||||
export function readLineTrim(state: Tokenizer): string {
|
||||
markLine(state);
|
||||
const position = state.position;
|
||||
trim(state, state.tokenStart, state.tokenEnd);
|
||||
state.position = position;
|
||||
return getTokenString(state);
|
||||
}
|
||||
|
||||
function readLinesChunk(state: Tokenizer, count: number, tokens: Tokens) {
|
||||
let read = 0;
|
||||
for (let i = 0; i < count; i++) {
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface SdfFile {
|
||||
}[]
|
||||
}
|
||||
|
||||
const delimiter = '$$$$';
|
||||
function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, data: Column<string> } {
|
||||
const dataHeader = TokenBuilder.create(tokenizer.data, 32);
|
||||
const data = TokenBuilder.create(tokenizer.data, 32);
|
||||
@@ -29,6 +30,7 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
|
||||
let sawHeaderToken = false;
|
||||
while (tokenizer.position < tokenizer.length) {
|
||||
const line = Tokenizer.readLine(tokenizer);
|
||||
if (line.startsWith(delimiter)) break;
|
||||
if (!!line) {
|
||||
if (line.startsWith('> <')) {
|
||||
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 3, tokenizer.tokenEnd - 1);
|
||||
@@ -49,9 +51,7 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
|
||||
};
|
||||
}
|
||||
|
||||
function handleMolFile(data: string) {
|
||||
const tokenizer = Tokenizer(data);
|
||||
|
||||
function handleMolFile(tokenizer: Tokenizer) {
|
||||
const title = Tokenizer.readLine(tokenizer).trim();
|
||||
const program = Tokenizer.readLine(tokenizer).trim();
|
||||
const comment = Tokenizer.readLine(tokenizer).trim();
|
||||
@@ -60,6 +60,15 @@ function handleMolFile(data: string) {
|
||||
|
||||
const atomCount = +counts.substr(0, 3), bondCount = +counts.substr(3, 3);
|
||||
|
||||
if (Number.isNaN(atomCount) || Number.isNaN(bondCount)) {
|
||||
// try to skip to next molecule
|
||||
while (tokenizer.position < tokenizer.length) {
|
||||
const line = Tokenizer.readLine(tokenizer);
|
||||
if (line.startsWith(delimiter)) break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const atoms = handleAtoms(tokenizer, atomCount);
|
||||
const bonds = handleBonds(tokenizer, bondCount);
|
||||
const dataItems = handleDataItems(tokenizer);
|
||||
@@ -70,10 +79,16 @@ function handleMolFile(data: string) {
|
||||
};
|
||||
}
|
||||
|
||||
const delimiter = '$$$$';
|
||||
function parseInternal(data: string): Result<SdfFile> {
|
||||
const result: SdfFile = { compounds: data.split(delimiter).map(d => handleMolFile(d)) };
|
||||
return Result.success(result);
|
||||
const tokenizer = Tokenizer(data);
|
||||
|
||||
const compounds: SdfFile['compounds'] = [];
|
||||
while (tokenizer.position < tokenizer.length) {
|
||||
const c = handleMolFile(tokenizer);
|
||||
if (c) compounds.push(c);
|
||||
}
|
||||
|
||||
return Result.success({ compounds });
|
||||
}
|
||||
|
||||
export function parseSdf(data: string) {
|
||||
|
||||
71
src/mol-io/reader/xyz/parser.ts
Normal file
71
src/mol-io/reader/xyz/parser.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Column } from '../../../mol-data/db';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { Tokenizer } from '../common/text/tokenizer';
|
||||
import { ReaderResult as Result } from '../result';
|
||||
|
||||
export interface XyzFile {
|
||||
readonly molecules: {
|
||||
readonly comment: string,
|
||||
readonly count: number,
|
||||
readonly x: Column<number>,
|
||||
readonly y: Column<number>,
|
||||
readonly z: Column<number>,
|
||||
readonly type_symbol: Column<string>
|
||||
}[],
|
||||
}
|
||||
|
||||
function handleMolecule(tokenizer: Tokenizer): XyzFile['molecules'][number] {
|
||||
let count = tokenizer.position >= tokenizer.data.length - 1 ? 0 : +Tokenizer.readLine(tokenizer);
|
||||
if (isNaN(count)) count = 0;
|
||||
|
||||
const comment = Tokenizer.readLine(tokenizer);
|
||||
|
||||
const x = new Float64Array(count);
|
||||
const y = new Float64Array(count);
|
||||
const z = new Float64Array(count);
|
||||
const type_symbol = new Array<string>(count);
|
||||
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const line = Tokenizer.readLineTrim(tokenizer);
|
||||
const fields = line.split(/\s+/g);
|
||||
type_symbol[i] = fields[0];
|
||||
x[i] = +fields[1];
|
||||
y[i] = +fields[2];
|
||||
z[i] = +fields[3];
|
||||
}
|
||||
|
||||
return {
|
||||
count,
|
||||
comment,
|
||||
x: Column.ofFloatArray(x),
|
||||
y: Column.ofFloatArray(y),
|
||||
z: Column.ofFloatArray(z),
|
||||
type_symbol: Column.ofStringArray(type_symbol)
|
||||
};
|
||||
}
|
||||
|
||||
function parseInternal(data: string): Result<XyzFile> {
|
||||
const tokenizer = Tokenizer(data);
|
||||
|
||||
const molecules: XyzFile['molecules'] = [];
|
||||
while (true) {
|
||||
const mol = handleMolecule(tokenizer);
|
||||
if (mol.count === 0) break;
|
||||
molecules.push(mol);
|
||||
}
|
||||
|
||||
const result: XyzFile = { molecules };
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
export function parseXyz(data: string) {
|
||||
return Task.create<Result<XyzFile>>('Parse Mol', async () => {
|
||||
return parseInternal(data);
|
||||
});
|
||||
}
|
||||
@@ -33,14 +33,14 @@ export const GaussianDensitySchema = {
|
||||
uCurrentSlice: UniformSpec('f'),
|
||||
uCurrentX: UniformSpec('f'),
|
||||
uCurrentY: UniformSpec('f'),
|
||||
uBboxMin: UniformSpec('v3', true),
|
||||
uBboxSize: UniformSpec('v3', true),
|
||||
uGridDim: UniformSpec('v3', true),
|
||||
uGridTexDim: UniformSpec('v3', true),
|
||||
uGridTexScale: UniformSpec('v2', true),
|
||||
uAlpha: UniformSpec('f', true),
|
||||
uResolution: UniformSpec('f', true),
|
||||
uRadiusFactorInv: UniformSpec('f', true),
|
||||
uBboxMin: UniformSpec('v3', 'material'),
|
||||
uBboxSize: UniformSpec('v3', 'material'),
|
||||
uGridDim: UniformSpec('v3', 'material'),
|
||||
uGridTexDim: UniformSpec('v3', 'material'),
|
||||
uGridTexScale: UniformSpec('v2', 'material'),
|
||||
uAlpha: UniformSpec('f', 'material'),
|
||||
uResolution: UniformSpec('f', 'material'),
|
||||
uRadiusFactorInv: UniformSpec('f', 'material'),
|
||||
tMinDistanceTex: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
|
||||
dGridTexType: DefineSpec('string', ['2d', '3d']),
|
||||
|
||||
@@ -111,33 +111,41 @@ namespace Spacegroup {
|
||||
|
||||
const _translationRef = Vec3();
|
||||
const _translationRefSymop = Vec3();
|
||||
const _translationRefOffset = Vec3();
|
||||
const _translationSymop = Vec3();
|
||||
export function setOperatorMatrixRef(spacegroup: Spacegroup, index: number, i: number, j: number, k: number, ref: Vec3, target: Mat4) {
|
||||
Vec3.set(_ijkVec, i, j, k);
|
||||
Vec3.floor(_translationRef, ref);
|
||||
|
||||
Mat4.copy(target, spacegroup.operators[index]);
|
||||
|
||||
Vec3.floor(_translationRefSymop, Vec3.transformMat4(_translationRefSymop, ref, target));
|
||||
|
||||
Mat4.getTranslation(_translationSymop, target);
|
||||
Vec3.sub(_translationSymop, _translationSymop, _translationRefSymop);
|
||||
Vec3.add(_translationSymop, _translationSymop, _translationRef);
|
||||
Vec3.add(_translationSymop, _translationSymop, _ijkVec);
|
||||
|
||||
Mat4.setTranslation(target, _translationSymop);
|
||||
Mat4.mul(target, spacegroup.cell.fromFractional, target);
|
||||
Mat4.mul(target, target, spacegroup.cell.toFractional);
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Symmetry operator for transformation around the given
|
||||
* reference point `ref` in fractional coordinates
|
||||
*/
|
||||
export function getSymmetryOperatorRef(spacegroup: Spacegroup, spgrOp: number, i: number, j: number, k: number, ref: Vec3) {
|
||||
const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
|
||||
return SymmetryOperator.create(`${spgrOp + 1}_${5 + i}${5 + j}${5 + k}`, operator, { hkl: Vec3.create(i, j, k), spgrOp });
|
||||
|
||||
const operator = Mat4.zero();
|
||||
|
||||
Vec3.set(_ijkVec, i, j, k);
|
||||
Vec3.floor(_translationRef, ref);
|
||||
|
||||
Mat4.copy(operator, spacegroup.operators[spgrOp]);
|
||||
|
||||
Vec3.floor(_translationRefSymop, Vec3.transformMat4(_translationRefSymop, ref, operator));
|
||||
|
||||
Mat4.getTranslation(_translationSymop, operator);
|
||||
Vec3.sub(_translationSymop, _translationSymop, _translationRefSymop);
|
||||
Vec3.add(_translationSymop, _translationSymop, _translationRef);
|
||||
Vec3.add(_translationSymop, _translationSymop, _ijkVec);
|
||||
|
||||
Mat4.setTranslation(operator, _translationSymop);
|
||||
Mat4.mul(operator, spacegroup.cell.fromFractional, operator);
|
||||
Mat4.mul(operator, operator, spacegroup.cell.toFractional);
|
||||
|
||||
Vec3.sub(_translationRefOffset, _translationRefSymop, _translationRef);
|
||||
|
||||
const _i = i - _translationRefOffset[0];
|
||||
const _j = j - _translationRefOffset[1];
|
||||
const _k = k - _translationRefOffset[2];
|
||||
|
||||
// const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
|
||||
return SymmetryOperator.create(`${spgrOp + 1}_${5 + _i}${5 + _j}${5 + _k}`, operator, { hkl: Vec3.create(_i, _j, _k), spgrOp });
|
||||
}
|
||||
|
||||
function getOperatorMatrix(ids: number[]) {
|
||||
|
||||
@@ -590,6 +590,9 @@ namespace Vec3 {
|
||||
export const unitX: ReadonlyVec3 = create(1, 0, 0);
|
||||
export const unitY: ReadonlyVec3 = create(0, 1, 0);
|
||||
export const unitZ: ReadonlyVec3 = create(0, 0, 1);
|
||||
export const negUnitX: ReadonlyVec3 = create(-1, 0, 0);
|
||||
export const negUnitY: ReadonlyVec3 = create(0, -1, 0);
|
||||
export const negUnitZ: ReadonlyVec3 = create(0, 0, -1);
|
||||
}
|
||||
|
||||
export { Vec3 };
|
||||
@@ -74,7 +74,7 @@ function createHierarchyData(atom_site: AtomSite, sourceIndex: Column<number>, o
|
||||
let cI = 0;
|
||||
let seqId = 0;
|
||||
for (let i = 0, il = seqIds.length; i < il; ++i) {
|
||||
if (residueOffsets[i] > chainOffsets[cI + 1]) {
|
||||
if (residueOffsets[i] >= chainOffsets[cI + 1]) {
|
||||
cI += 1;
|
||||
seqId = 0;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ const StandardComponents = (function() {
|
||||
{ id: 'ASP', name: 'ASPARTIC ACID', type: 'L-peptide linking' },
|
||||
{ id: 'GLU', name: 'GLUTAMIC ACID', type: 'L-peptide linking' },
|
||||
{ id: 'THR', name: 'THREONINE', type: 'L-peptide linking' },
|
||||
{ id: 'PRO', name: 'PROLINE', type: 'L-peptide linking' },
|
||||
{ id: 'SEC', name: 'SELENOCYSTEINE', type: 'L-peptide linking' },
|
||||
{ id: 'PYL', name: 'PYRROLYSINE', type: 'L-peptide linking' },
|
||||
|
||||
|
||||
108
src/mol-model-formats/structure/xyz.ts
Normal file
108
src/mol-model-formats/structure/xyz.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { XyzFile } from '../../mol-io/reader/xyz/parser';
|
||||
import { Trajectory } from '../../mol-model/structure';
|
||||
import { MoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { RuntimeContext, Task } from '../../mol-task';
|
||||
import { ModelFormat } from '../format';
|
||||
import { createModels } from './basic/parser';
|
||||
import { BasicSchema, createBasic } from './basic/schema';
|
||||
import { ComponentBuilder } from './common/component';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
|
||||
function getModels(mol: XyzFile, ctx: RuntimeContext) {
|
||||
const { molecules } = mol;
|
||||
|
||||
let count = 0;
|
||||
for (const m of molecules) count += m.count;
|
||||
|
||||
const type_symbols = new Array<string>(count);
|
||||
const id = new Int32Array(count);
|
||||
const x = new Float32Array(count);
|
||||
const y = new Float32Array(count);
|
||||
const z = new Float32Array(count);
|
||||
const model_num = new Int32Array(count);
|
||||
|
||||
let offset = 0;
|
||||
for (let i = 0; i < molecules.length; i++) {
|
||||
const m = molecules[i];
|
||||
for (let j = 0; j < m.count; j++) {
|
||||
type_symbols[offset] = m.type_symbol.value(j);
|
||||
x[offset] = m.x.value(j);
|
||||
y[offset] = m.y.value(j);
|
||||
z[offset] = m.z.value(j);
|
||||
id[offset] = j;
|
||||
model_num[offset] = i;
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
const MOL = Column.ofConst('MOL', count, Column.Schema.str);
|
||||
const A = Column.ofConst('A', count, Column.Schema.str);
|
||||
const seq_id = Column.ofConst(1, count, Column.Schema.int);
|
||||
|
||||
const type_symbol = Column.ofStringArray(type_symbols);
|
||||
|
||||
const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
|
||||
auth_asym_id: A,
|
||||
auth_atom_id: type_symbol,
|
||||
auth_comp_id: MOL,
|
||||
auth_seq_id: seq_id,
|
||||
Cartn_x: Column.ofFloatArray(x),
|
||||
Cartn_y: Column.ofFloatArray(y),
|
||||
Cartn_z: Column.ofFloatArray(z),
|
||||
id: Column.ofIntArray(id),
|
||||
|
||||
label_asym_id: A,
|
||||
label_atom_id: type_symbol,
|
||||
label_comp_id: MOL,
|
||||
label_seq_id: seq_id,
|
||||
label_entity_id: Column.ofConst('1', count, Column.Schema.str),
|
||||
|
||||
occupancy: Column.ofConst(1, count, Column.Schema.float),
|
||||
type_symbol,
|
||||
|
||||
pdbx_PDB_model_num: Column.ofIntArray(model_num),
|
||||
}, count);
|
||||
|
||||
const entityBuilder = new EntityBuilder();
|
||||
entityBuilder.setNames([['MOL', 'Unknown Entity']]);
|
||||
entityBuilder.getEntityId('MOL', MoleculeType.Unknown, 'A');
|
||||
|
||||
const componentBuilder = new ComponentBuilder(seq_id, type_symbol);
|
||||
componentBuilder.setNames([['MOL', 'Unknown Molecule']]);
|
||||
componentBuilder.add('MOL', 0);
|
||||
|
||||
const basics = createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
chem_comp: componentBuilder.getChemCompTable(),
|
||||
atom_site
|
||||
});
|
||||
|
||||
return createModels(basics, XyzFormat.create(mol), ctx);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export { XyzFormat };
|
||||
|
||||
type XyzFormat = ModelFormat<XyzFile>
|
||||
|
||||
namespace XyzFormat {
|
||||
export function is(x?: ModelFormat): x is XyzFormat {
|
||||
return x?.kind === 'xyz';
|
||||
}
|
||||
|
||||
export function create(mol: XyzFile): XyzFormat {
|
||||
return { kind: 'xyz', name: 'xyz', data: mol };
|
||||
}
|
||||
}
|
||||
|
||||
export function trajectoryFromXyz(mol: XyzFile): Task<Trajectory> {
|
||||
return Task.create('Parse XYZ', ctx => getModels(mol, ctx));
|
||||
}
|
||||
@@ -61,7 +61,7 @@ namespace CustomElementProperty {
|
||||
type: builder.type || 'dynamic',
|
||||
defaultParams: {},
|
||||
getParams: (data: Model) => ({}),
|
||||
isApplicable: (data: Model) => !!builder.isApplicable?.(data),
|
||||
isApplicable: (data: Model) => !builder.isApplicable || !!builder.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Model) => {
|
||||
return await builder.getData(data, ctx);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export const InteractionsProvider: CustomStructureProperty.Provider<Interactions
|
||||
type: 'local',
|
||||
defaultParams: InteractionsParams,
|
||||
getParams: (data: Structure) => InteractionsParams,
|
||||
isApplicable: (data: Structure) => true,
|
||||
isApplicable: (data: Structure) => !data.isCoarseGrained,
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<InteractionsProps>) => {
|
||||
const p = { ...PD.getDefaultValues(InteractionsParams), ...props };
|
||||
return { value: await computeInteractions(ctx, data, p) };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -15,7 +15,7 @@ import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../../../mo
|
||||
import { VisualUpdateState } from '../../../mol-repr/util';
|
||||
import { PickingId } from '../../../mol-geo/geometry/picking';
|
||||
import { EmptyLoci, Loci } from '../../../mol-model/loci';
|
||||
import { Interval, OrderedSet } from '../../../mol-data/int';
|
||||
import { Interval, OrderedSet, SortedArray } from '../../../mol-data/int';
|
||||
import { Interactions } from '../interactions/interactions';
|
||||
import { InteractionsProvider } from '../interactions';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
@@ -35,6 +35,8 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
|
||||
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh);
|
||||
|
||||
const { child } = structure;
|
||||
|
||||
const builderProps = {
|
||||
linkCount: edgeCount,
|
||||
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
|
||||
@@ -63,12 +65,28 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
|
||||
const sizeB = theme.size.size(l);
|
||||
return Math.min(sizeA, sizeB) * sizeFactor;
|
||||
},
|
||||
ignore: (edgeIndex: number) => edges[edgeIndex].props.flag === InteractionFlag.Filtered
|
||||
ignore: (edgeIndex: number) => {
|
||||
if (edges[edgeIndex].props.flag === InteractionFlag.Filtered) return true;
|
||||
|
||||
if (child) {
|
||||
const b = edges[edgeIndex];
|
||||
const childUnitA = child.unitMap.get(b.unitA);
|
||||
if (!childUnitA) return true;
|
||||
|
||||
const unitA = structure.unitMap.get(b.unitA);
|
||||
const fA = unitsFeatures.get(b.unitA);
|
||||
// TODO: check all members
|
||||
const eA = unitA.elements[fA.members[fA.offsets[b.indexA]]];
|
||||
if (!SortedArray.has(childUnitA.elements, eA)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * sizeFactor);
|
||||
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * sizeFactor);
|
||||
m.setBoundingSphere(sphere);
|
||||
|
||||
return m;
|
||||
@@ -80,6 +98,7 @@ export const InteractionsInterUnitParams = {
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
|
||||
dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
|
||||
includeParent: PD.Boolean(false),
|
||||
};
|
||||
export type InteractionsInterUnitParams = typeof InteractionsInterUnitParams
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -7,7 +7,7 @@
|
||||
import { Unit, Structure, StructureElement } from '../../../mol-model/structure';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { Loci, EmptyLoci } from '../../../mol-model/loci';
|
||||
import { Interval, OrderedSet } from '../../../mol-data/int';
|
||||
import { Interval, OrderedSet, SortedArray } from '../../../mol-data/int';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { PickingId } from '../../../mol-geo/geometry/picking';
|
||||
@@ -25,6 +25,10 @@ import { Sphere3D } from '../../../mol-math/geometry';
|
||||
async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<InteractionsIntraUnitParams>, mesh?: Mesh) {
|
||||
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
|
||||
|
||||
const { child } = structure;
|
||||
const childUnit = child?.unitMap.get(unit.id);
|
||||
if (child && !childUnit) return Mesh.createEmpty(mesh);
|
||||
|
||||
const location = StructureElement.Location.create(structure, unit);
|
||||
|
||||
const interactions = InteractionsProvider.get(structure).value!;
|
||||
@@ -51,12 +55,16 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
|
||||
const sizeB = theme.size.size(location);
|
||||
return Math.min(sizeA, sizeB) * sizeFactor;
|
||||
},
|
||||
ignore: (edgeIndex: number) => flag[edgeIndex] === InteractionFlag.Filtered
|
||||
ignore: (edgeIndex: number) => (
|
||||
flag[edgeIndex] === InteractionFlag.Filtered ||
|
||||
// TODO: check all members
|
||||
(!!childUnit && !SortedArray.has(childUnit.elements, unit.elements[members[offsets[a[edgeIndex]]]]))
|
||||
)
|
||||
};
|
||||
|
||||
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * sizeFactor);
|
||||
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * sizeFactor);
|
||||
m.setBoundingSphere(sphere);
|
||||
|
||||
return m;
|
||||
@@ -68,6 +76,7 @@ export const InteractionsIntraUnitParams = {
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
|
||||
dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
|
||||
includeParent: PD.Boolean(false),
|
||||
};
|
||||
export type InteractionsIntraUnitParams = typeof InteractionsIntraUnitParams
|
||||
|
||||
@@ -147,7 +156,7 @@ function eachInteraction(loci: Loci, structureGroup: StructureGroup, apply: (int
|
||||
const { offset } = contacts;
|
||||
const { offsets: fOffsets, indices: fIndices } = features.elementsIndex;
|
||||
|
||||
// TODO when isMarking, all elements of contact features need to be in the loci
|
||||
// TODO: when isMarking, all elements of contact features need to be in the loci
|
||||
for (const e of loci.elements) {
|
||||
const unitIdx = group.unitIndexMap.get(e.unit.id);
|
||||
if (unitIdx !== undefined) continue;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -46,9 +46,15 @@ export const InteractionsRepresentationProvider = StructureRepresentationProvide
|
||||
defaultValues: PD.getDefaultValues(InteractionsParams),
|
||||
defaultColorTheme: { name: 'interaction-type' },
|
||||
defaultSizeTheme: { name: 'uniform' },
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0,
|
||||
isApplicable: (structure: Structure) => structure.elementCount > 0 && InteractionsProvider.isApplicable(structure),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => InteractionsProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (data) => InteractionsProvider.ref(data, false)
|
||||
},
|
||||
getData: (structure: Structure, props: PD.Values<InteractionsParams>) => {
|
||||
return props.includeParent ? structure.asParent() : structure;
|
||||
},
|
||||
mustRecreate: (oldProps: PD.Values<InteractionsParams>, newProps: PD.Values<InteractionsParams>) => {
|
||||
return oldProps.includeParent !== newProps.includeParent;
|
||||
}
|
||||
});
|
||||
@@ -69,7 +69,7 @@ async function computeDssp(structure: Structure, props: DSSPComputationProps): P
|
||||
const map = new Map<number, SecondaryStructure>();
|
||||
for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
|
||||
const u = structure.unitSymmetryGroups[i].units[0];
|
||||
if (Unit.isAtomic(u)) {
|
||||
if (Unit.isAtomic(u) && !Model.isCoarseGrained(u.model)) {
|
||||
const secondaryStructure = await computeUnitDSSP(u, props);
|
||||
map.set(u.invariantId, secondaryStructure);
|
||||
}
|
||||
|
||||
@@ -37,9 +37,10 @@ export interface DataLoci<T = unknown, E = unknown> {
|
||||
readonly kind: 'data-loci',
|
||||
readonly tag: string
|
||||
readonly data: T,
|
||||
readonly elements: ReadonlyArray<E>
|
||||
readonly elements: ReadonlyArray<E>,
|
||||
|
||||
getBoundingSphere(boundingSphere: Sphere3D): Sphere3D
|
||||
/** if undefined, won't zoom */
|
||||
getBoundingSphere?(boundingSphere: Sphere3D): Sphere3D
|
||||
getLabel(): string
|
||||
}
|
||||
export function isDataLoci(x?: Loci): x is DataLoci {
|
||||
@@ -159,7 +160,7 @@ namespace Loci {
|
||||
} else if (loci.kind === 'group-loci') {
|
||||
return ShapeGroup.getBoundingSphere(loci, boundingSphere);
|
||||
} else if (loci.kind === 'data-loci') {
|
||||
return loci.getBoundingSphere(boundingSphere);
|
||||
return loci.getBoundingSphere?.(boundingSphere);
|
||||
} else if (loci.kind === 'volume-loci') {
|
||||
return Volume.getBoundingSphere(loci.volume, boundingSphere);
|
||||
} else if (loci.kind === 'isosurface-loci') {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -29,6 +29,7 @@ import { CustomModelProperty } from '../../../mol-model-props/common/custom-mode
|
||||
import { Trajectory, ArrayTrajectory } from '../trajectory';
|
||||
import { Unit } from '../structure';
|
||||
import { SortedArray } from '../../../mol-data/int/sorted-array';
|
||||
import { PolymerType } from './types';
|
||||
|
||||
/**
|
||||
* Interface to the "source data" of the molecule.
|
||||
@@ -224,6 +225,39 @@ export namespace Model {
|
||||
}
|
||||
};
|
||||
|
||||
const CoarseGrainedProp = '__CoarseGrained__';
|
||||
/**
|
||||
* Has typical coarse grained atom names (BB, SC1) or less than three times as many
|
||||
* atoms as polymer residues (C-alpha only models).
|
||||
*/
|
||||
export function isCoarseGrained(model: Model): boolean {
|
||||
if (model._staticPropertyData[CoarseGrainedProp] !== undefined) return model._staticPropertyData[CoarseGrainedProp];
|
||||
|
||||
let polymerResidueCount = 0;
|
||||
const { polymerType } = model.atomicHierarchy.derived.residue;
|
||||
for (let i = 0; i < polymerType.length; ++i) {
|
||||
if (polymerType[i] !== PolymerType.NA) polymerResidueCount += 1;
|
||||
}
|
||||
|
||||
// check for coarse grained atom names
|
||||
let hasBB = false, hasSC1 = false;
|
||||
const { label_atom_id, _rowCount: atomCount } = model.atomicHierarchy.atoms;
|
||||
for (let i = 0; i < atomCount; ++i) {
|
||||
const atomName = label_atom_id.value(i);
|
||||
if (!hasBB && atomName === 'BB') hasBB = true;
|
||||
if (!hasSC1 && atomName === 'SC1') hasSC1 = true;
|
||||
if (hasBB && hasSC1) break;
|
||||
}
|
||||
|
||||
const coarseGrained = (hasBB && hasSC1) || (
|
||||
polymerResidueCount && atomCount
|
||||
? atomCount / polymerResidueCount < 3
|
||||
: false
|
||||
);
|
||||
model._staticPropertyData[CoarseGrainedProp] = coarseGrained;
|
||||
return coarseGrained;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export function hasCarbohydrate(model: Model): boolean {
|
||||
|
||||
107
src/mol-model/structure/model/properties/utils/residue-set.ts
Normal file
107
src/mol-model/structure/model/properties/utils/residue-set.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { StructureElement } from '../../../structure/element';
|
||||
import { StructureProperties } from '../../../structure/properties';
|
||||
|
||||
export interface ResidueSetEntry {
|
||||
label_asym_id: string,
|
||||
label_comp_id: string,
|
||||
label_seq_id: number,
|
||||
label_alt_id: string,
|
||||
ins_code: string,
|
||||
// 1_555 by default
|
||||
operator_name?: string
|
||||
}
|
||||
|
||||
export class ResidueSet {
|
||||
private index = new Map<string, Map<number, ResidueSetEntry[]>>();
|
||||
private checkOperator: boolean = false;
|
||||
|
||||
add(entry: ResidueSetEntry) {
|
||||
let root = this.index.get(entry.label_asym_id);
|
||||
if (!root) {
|
||||
root = new Map();
|
||||
this.index.set(entry.label_asym_id, root);
|
||||
}
|
||||
|
||||
let entries = root.get(entry.label_seq_id);
|
||||
if (!entries) {
|
||||
entries = [];
|
||||
root.set(entry.label_seq_id, entries);
|
||||
}
|
||||
|
||||
const exists = this._find(entry, entries);
|
||||
if (!exists) {
|
||||
entries.push(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
hasLabelAsymId(asym_id: string) {
|
||||
return this.index.has(asym_id);
|
||||
}
|
||||
|
||||
has(loc: StructureElement.Location) {
|
||||
const asym_id = _asym_id(loc);
|
||||
if (!this.index.has(asym_id)) return;
|
||||
const root = this.index.get(asym_id)!;
|
||||
const seq_id = _seq_id(loc);
|
||||
if (!root.has(seq_id)) return;
|
||||
const entries = root.get(seq_id)!;
|
||||
|
||||
const comp_id = _comp_id(loc);
|
||||
const alt_id = _alt_id(loc);
|
||||
const ins_code = _ins_code(loc);
|
||||
const op_name = _op_name(loc) ?? '1_555';
|
||||
|
||||
for (const e of entries) {
|
||||
if (e.label_comp_id !== comp_id || e.label_alt_id !== alt_id || e.ins_code !== ins_code) continue;
|
||||
if (this.checkOperator && (e.operator_name ?? '1_555') !== op_name) continue;
|
||||
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
static getLabel(entry: ResidueSetEntry, checkOperator = false) {
|
||||
return `${entry.label_asym_id} ${entry.label_comp_id} ${entry.label_seq_id}:${entry.ins_code}:${entry.label_alt_id}${checkOperator ? ' ' + (entry.operator_name ?? '1_555') : ''}`;
|
||||
}
|
||||
|
||||
static getEntryFromLocation(loc: StructureElement.Location): ResidueSetEntry {
|
||||
return {
|
||||
label_asym_id: _asym_id(loc),
|
||||
label_comp_id: _comp_id(loc),
|
||||
label_seq_id: _seq_id(loc),
|
||||
label_alt_id: _alt_id(loc),
|
||||
ins_code: _ins_code(loc),
|
||||
operator_name: _op_name(loc) ?? '1_555'
|
||||
};
|
||||
}
|
||||
|
||||
private _find(entry: ResidueSetEntry, xs: ResidueSetEntry[]) {
|
||||
for (const e of xs) {
|
||||
if (e.label_comp_id !== entry.label_comp_id || e.label_alt_id !== entry.label_alt_id || e.ins_code !== entry.ins_code) continue;
|
||||
if (this.checkOperator && (e.operator_name ?? '1_555') !== (entry.operator_name ?? '1_555')) continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constructor(options?: { checkOperator?: boolean }) {
|
||||
this.checkOperator = options?.checkOperator ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
const _asym_id = StructureProperties.chain.label_asym_id;
|
||||
const _seq_id = StructureProperties.residue.label_seq_id;
|
||||
const _comp_id = StructureProperties.atom.label_comp_id;
|
||||
const _alt_id = StructureProperties.atom.label_alt_id;
|
||||
const _ins_code = StructureProperties.residue.pdbx_PDB_ins_code;
|
||||
const _op_name = StructureProperties.unit.operator_name;
|
||||
@@ -43,7 +43,7 @@ export function atomicSequence(): StructureQuery {
|
||||
|
||||
units.push(unit);
|
||||
}
|
||||
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
|
||||
return StructureSelection.Singletons(inputStructure, Structure.create(units, { parent: inputStructure }));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export function water(): StructureQuery {
|
||||
if (P.entity.type(l) !== 'water') continue;
|
||||
units.push(unit);
|
||||
}
|
||||
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
|
||||
return StructureSelection.Singletons(inputStructure, Structure.create(units, { parent: inputStructure }));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export function atomicHet(): StructureQuery {
|
||||
|
||||
units.push(unit);
|
||||
}
|
||||
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
|
||||
return StructureSelection.Singletons(inputStructure, Structure.create(units, { parent: inputStructure }));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ export function spheres(): StructureQuery {
|
||||
if (unit.kind !== Unit.Kind.Spheres) continue;
|
||||
units.push(unit);
|
||||
}
|
||||
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
|
||||
return StructureSelection.Singletons(inputStructure, Structure.create(units, { parent: inputStructure }));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,14 @@ import { StructureSelection } from '../selection';
|
||||
import { UniqueStructuresBuilder } from '../utils/builders';
|
||||
import { StructureUniqueSubsetBuilder } from '../../structure/util/unique-subset-builder';
|
||||
import { QueryContext, QueryFn } from '../context';
|
||||
import { structureIntersect, structureSubtract } from '../utils/structure-set';
|
||||
import { structureIntersect, structureSubtract, structureUnion } from '../utils/structure-set';
|
||||
import { UniqueArray } from '../../../../mol-data/generic';
|
||||
import { StructureSubsetBuilder } from '../../structure/util/subset-builder';
|
||||
import { StructureElement } from '../../structure/element';
|
||||
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { ResidueSet, ResidueSetEntry } from '../../model/properties/utils/residue-set';
|
||||
import { StructureProperties } from '../../structure/properties';
|
||||
import { arraySetAdd } from '../../../../mol-util/array';
|
||||
|
||||
function getWholeResidues(ctx: QueryContext, source: Structure, structure: Structure) {
|
||||
const builder = source.subsetBuilder(true);
|
||||
@@ -306,10 +310,11 @@ export interface IncludeConnectedParams {
|
||||
query: StructureQuery,
|
||||
bondTest?: QueryFn<boolean>,
|
||||
layerCount: number,
|
||||
wholeResidues: boolean
|
||||
wholeResidues: boolean,
|
||||
fixedPoint: boolean
|
||||
}
|
||||
|
||||
export function includeConnected({ query, layerCount, wholeResidues, bondTest }: IncludeConnectedParams): StructureQuery {
|
||||
export function includeConnected({ query, layerCount, wholeResidues, bondTest, fixedPoint }: IncludeConnectedParams): StructureQuery {
|
||||
const lc = Math.max(layerCount, 0);
|
||||
return function query_includeConnected(ctx) {
|
||||
const builder = StructureSelection.UniqueBuilder(ctx.inputStructure);
|
||||
@@ -318,8 +323,17 @@ export function includeConnected({ query, layerCount, wholeResidues, bondTest }:
|
||||
ctx.atomicBond.setTestFn(bondTest);
|
||||
StructureSelection.forEach(src, (s, sI) => {
|
||||
let incl = s;
|
||||
for (let i = 0; i < lc; i++) {
|
||||
incl = includeConnectedStep(ctx, wholeResidues, incl);
|
||||
|
||||
if (fixedPoint) {
|
||||
while (true) {
|
||||
const prevCount = incl.elementCount;
|
||||
incl = includeConnectedStep(ctx, wholeResidues, incl);
|
||||
if (incl.elementCount === prevCount) break;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < lc; i++) {
|
||||
incl = includeConnectedStep(ctx, wholeResidues, incl);
|
||||
}
|
||||
}
|
||||
builder.add(incl);
|
||||
if (sI % 10 === 0) ctx.throwIfTimedOut();
|
||||
@@ -425,4 +439,252 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
|
||||
return builder.getStructure();
|
||||
}
|
||||
|
||||
export interface SurroundingLigandsParams {
|
||||
query: StructureQuery,
|
||||
radius: number,
|
||||
includeWater: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes expanded surrounding ligands based on radius from the source, struct_conn entries & pdbx_molecule entries.
|
||||
*/
|
||||
export function surroundingLigands({ query, radius, includeWater }: SurroundingLigandsParams): StructureQuery {
|
||||
return function query_surroundingLigands(ctx) {
|
||||
|
||||
const inner = StructureSelection.unionStructure(query(ctx));
|
||||
const surroundings = getWholeResidues(ctx, ctx.inputStructure, getIncludeSurroundings(ctx, ctx.inputStructure, inner, { radius }));
|
||||
|
||||
const prd = getPrdAsymIdx(ctx.inputStructure);
|
||||
const graph = getStructConnInfo(ctx.inputStructure);
|
||||
|
||||
const l = StructureElement.Location.create(surroundings);
|
||||
|
||||
const includedPrdChains = new Map<string, string[]>();
|
||||
|
||||
const componentResidues = new ResidueSet({ checkOperator: true });
|
||||
|
||||
for (const unit of surroundings.units) {
|
||||
if (unit.kind !== Unit.Kind.Atomic) continue;
|
||||
|
||||
l.unit = unit;
|
||||
|
||||
const { elements } = unit;
|
||||
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
|
||||
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
|
||||
|
||||
while (chainsIt.hasNext) {
|
||||
const chainSegment = chainsIt.move();
|
||||
l.element = elements[chainSegment.start];
|
||||
|
||||
const asym_id = StructureProperties.chain.label_asym_id(l);
|
||||
const op_name = StructureProperties.unit.operator_name(l);
|
||||
|
||||
// check for PRD molecules
|
||||
if (prd.has(asym_id)) {
|
||||
if (includedPrdChains.has(asym_id)) {
|
||||
arraySetAdd(includedPrdChains.get(asym_id)!, op_name);
|
||||
} else {
|
||||
includedPrdChains.set(asym_id, [op_name]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const entityType = StructureProperties.entity.type(l);
|
||||
|
||||
// test entity and chain
|
||||
if (entityType === 'water' || entityType === 'polymer') continue;
|
||||
|
||||
residuesIt.setSegment(chainSegment);
|
||||
while (residuesIt.hasNext) {
|
||||
const residueSegment = residuesIt.move();
|
||||
l.element = elements[residueSegment.start];
|
||||
graph.addComponent(ResidueSet.getEntryFromLocation(l), componentResidues);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.throwIfTimedOut();
|
||||
}
|
||||
|
||||
// assemble the core structure
|
||||
|
||||
const builder = ctx.inputStructure.subsetBuilder(true);
|
||||
for (const unit of ctx.inputStructure.units) {
|
||||
if (unit.kind !== Unit.Kind.Atomic) continue;
|
||||
|
||||
l.unit = unit;
|
||||
const { elements } = unit;
|
||||
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
|
||||
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
|
||||
|
||||
builder.beginUnit(unit.id);
|
||||
|
||||
while (chainsIt.hasNext) {
|
||||
const chainSegment = chainsIt.move();
|
||||
l.element = elements[chainSegment.start];
|
||||
|
||||
const asym_id = StructureProperties.chain.label_asym_id(l);
|
||||
const op_name = StructureProperties.unit.operator_name(l);
|
||||
|
||||
if (includedPrdChains.has(asym_id) && includedPrdChains.get(asym_id)!.indexOf(op_name) >= 0) {
|
||||
builder.addElementRange(elements, chainSegment.start, chainSegment.end);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!componentResidues.hasLabelAsymId(asym_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
residuesIt.setSegment(chainSegment);
|
||||
while (residuesIt.hasNext) {
|
||||
const residueSegment = residuesIt.move();
|
||||
l.element = elements[residueSegment.start];
|
||||
|
||||
if (!componentResidues.has(l)) continue;
|
||||
builder.addElementRange(elements, residueSegment.start, residueSegment.end);
|
||||
}
|
||||
}
|
||||
builder.commitUnit();
|
||||
|
||||
ctx.throwIfTimedOut();
|
||||
}
|
||||
|
||||
const components = structureUnion(ctx.inputStructure, [builder.getStructure(), inner]);
|
||||
|
||||
// add water
|
||||
if (includeWater) {
|
||||
const finalBuilder = new StructureUniqueSubsetBuilder(ctx.inputStructure);
|
||||
const lookup = ctx.inputStructure.lookup3d;
|
||||
for (const unit of components.units) {
|
||||
const { x, y, z } = unit.conformation;
|
||||
const elements = unit.elements;
|
||||
for (let i = 0, _i = elements.length; i < _i; i++) {
|
||||
const e = elements[i];
|
||||
lookup.findIntoBuilderIf(x(e), y(e), z(e), radius, finalBuilder, testIsWater);
|
||||
finalBuilder.addToUnit(unit.id, e);
|
||||
}
|
||||
|
||||
ctx.throwIfTimedOut();
|
||||
}
|
||||
|
||||
return StructureSelection.Sequence(ctx.inputStructure, [finalBuilder.getStructure()]);
|
||||
} else {
|
||||
return StructureSelection.Sequence(ctx.inputStructure, [components]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const _entity_type = StructureProperties.entity.type;
|
||||
function testIsWater(l: StructureElement.Location) {
|
||||
return _entity_type(l) === 'water';
|
||||
}
|
||||
|
||||
function getPrdAsymIdx(structure: Structure) {
|
||||
const model = structure.models[0];
|
||||
const ids = new Set<string>();
|
||||
if (!MmcifFormat.is(model.sourceData)) return ids;
|
||||
const { _rowCount, asym_id } = model.sourceData.data.db.pdbx_molecule;
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
ids.add(asym_id.value(i));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
function getStructConnInfo(structure: Structure) {
|
||||
const model = structure.models[0];
|
||||
const graph = new StructConnGraph();
|
||||
|
||||
if (!MmcifFormat.is(model.sourceData)) return graph;
|
||||
|
||||
const struct_conn = model.sourceData.data.db.struct_conn;
|
||||
const { conn_type_id } = struct_conn;
|
||||
const { ptnr1_label_asym_id, ptnr1_label_comp_id, ptnr1_label_seq_id, ptnr1_symmetry, pdbx_ptnr1_label_alt_id, pdbx_ptnr1_PDB_ins_code } = struct_conn;
|
||||
const { ptnr2_label_asym_id, ptnr2_label_comp_id, ptnr2_label_seq_id, ptnr2_symmetry, pdbx_ptnr2_label_alt_id, pdbx_ptnr2_PDB_ins_code } = struct_conn;
|
||||
|
||||
for (let i = 0; i < struct_conn._rowCount; i++) {
|
||||
const bondType = conn_type_id.value(i);
|
||||
if (bondType !== 'covale' && bondType !== 'metalc') continue;
|
||||
|
||||
const a: ResidueSetEntry = {
|
||||
label_asym_id: ptnr1_label_asym_id.value(i),
|
||||
label_comp_id: ptnr1_label_comp_id.value(i),
|
||||
label_seq_id: ptnr1_label_seq_id.value(i),
|
||||
label_alt_id: pdbx_ptnr1_label_alt_id.value(i),
|
||||
ins_code: pdbx_ptnr1_PDB_ins_code.value(i),
|
||||
operator_name: ptnr1_symmetry.value(i) ?? '1_555'
|
||||
};
|
||||
|
||||
const b: ResidueSetEntry = {
|
||||
label_asym_id: ptnr2_label_asym_id.value(i),
|
||||
label_comp_id: ptnr2_label_comp_id.value(i),
|
||||
label_seq_id: ptnr2_label_seq_id.value(i),
|
||||
label_alt_id: pdbx_ptnr2_label_alt_id.value(i),
|
||||
ins_code: pdbx_ptnr2_PDB_ins_code.value(i),
|
||||
operator_name: ptnr2_symmetry.value(i) ?? '1_555'
|
||||
};
|
||||
|
||||
graph.addEdge(a, b);
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
class StructConnGraph {
|
||||
vertices = new Map<string, ResidueSetEntry>();
|
||||
edges = new Map<string, string[]>();
|
||||
|
||||
private addVertex(e: ResidueSetEntry, label: string) {
|
||||
if (this.vertices.has(label)) return;
|
||||
this.vertices.set(label, e);
|
||||
this.edges.set(label, []);
|
||||
}
|
||||
|
||||
addEdge(a: ResidueSetEntry, b: ResidueSetEntry) {
|
||||
const al = ResidueSet.getLabel(a);
|
||||
const bl = ResidueSet.getLabel(b);
|
||||
this.addVertex(a, al);
|
||||
this.addVertex(b, bl);
|
||||
arraySetAdd(this.edges.get(al)!, bl);
|
||||
arraySetAdd(this.edges.get(bl)!, al);
|
||||
}
|
||||
|
||||
addComponent(start: ResidueSetEntry, set: ResidueSet) {
|
||||
const startLabel = ResidueSet.getLabel(start);
|
||||
|
||||
if (!this.vertices.has(startLabel)) {
|
||||
set.add(start);
|
||||
return;
|
||||
}
|
||||
|
||||
const visited = new Set<string>();
|
||||
const added = new Set<string>();
|
||||
const stack = [startLabel];
|
||||
|
||||
added.add(startLabel);
|
||||
set.add(start);
|
||||
|
||||
while (stack.length > 0) {
|
||||
const a = stack.pop()!;
|
||||
visited.add(a);
|
||||
|
||||
const u = this.vertices.get(a)!;
|
||||
|
||||
for (const b of this.edges.get(a)!) {
|
||||
if (visited.has(b)) continue;
|
||||
stack.push(b);
|
||||
|
||||
if (added.has(b)) continue;
|
||||
added.add(b);
|
||||
|
||||
const v = this.vertices.get(b)!;
|
||||
if (u.operator_name === v.operator_name) {
|
||||
set.add({ ...v, operator_name: start.operator_name });
|
||||
} else {
|
||||
set.add(v);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: unionBy (skip this one?), cluster
|
||||
@@ -23,7 +23,7 @@ export function checkStructureMaxRadiusDistance(ctx: QueryContext, a: Structure,
|
||||
}
|
||||
|
||||
namespace MinMaxDist {
|
||||
const enum Result {
|
||||
export const enum Result {
|
||||
BelowMin,
|
||||
WithinMax,
|
||||
Miss
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as StructureElement from './element/index';
|
||||
import * as StructureElement from './element/element';
|
||||
|
||||
export { StructureElement };
|
||||
@@ -5,50 +5,8 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { SortedArray } from '../../../../mol-data/int';
|
||||
import { ElementIndex, ResidueIndex, ChainIndex } from '../../model';
|
||||
import { Unit } from '../unit';
|
||||
import { Location } from './location';
|
||||
import { StructureProperties } from '../properties';
|
||||
|
||||
// TODO: when nominal types are available, make this indexed by UnitIndex
|
||||
export type Set = SortedArray<ElementIndex>
|
||||
|
||||
/** Index into Unit.elements */
|
||||
export type UnitIndex = { readonly '@type': 'unit-element-index' } & number
|
||||
|
||||
export interface Property<T> { (location: Location): T }
|
||||
export interface Predicate extends Property<boolean> { }
|
||||
|
||||
export function property<T>(p: Property<T>) { return p; }
|
||||
|
||||
function _wrongUnitKind(kind: string) { throw new Error(`Property only available for ${kind} models.`); }
|
||||
export function atomicProperty<T>(p: (location: Location<Unit.Atomic>) => T) {
|
||||
return property(l => Unit.isAtomic(l.unit) ? p(l as Location<Unit.Atomic>) : _wrongUnitKind('atomic'));
|
||||
}
|
||||
|
||||
export function coarseProperty<T>(p: (location: Location<Unit.Spheres | Unit.Gaussians>) => T) {
|
||||
return property(l => Unit.isCoarse(l.unit) ? p(l as Location<Unit.Spheres | Unit.Gaussians>) : _wrongUnitKind('coarse'));
|
||||
}
|
||||
|
||||
export function residueIndex(e: Location) {
|
||||
if (Unit.isAtomic(e.unit)) {
|
||||
return e.unit.residueIndex[e.element];
|
||||
} else {
|
||||
// TODO: throw error instead?
|
||||
return -1 as ResidueIndex;
|
||||
}
|
||||
}
|
||||
|
||||
export function chainIndex(e: Location) {
|
||||
if (Unit.isAtomic(e.unit)) {
|
||||
return e.unit.chainIndex[e.element];
|
||||
} else {
|
||||
// TODO: throw error instead?
|
||||
return -1 as ChainIndex;
|
||||
}
|
||||
}
|
||||
|
||||
export function entityIndex(l: Location) {
|
||||
return StructureProperties.entity.key(l);
|
||||
}
|
||||
export * from './location';
|
||||
export * from './loci';
|
||||
export * from './bundle';
|
||||
export * from './stats';
|
||||
export * from './util';
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
export * from './location';
|
||||
export * from './loci';
|
||||
export * from './bundle';
|
||||
export * from './stats';
|
||||
export * from './element';
|
||||
@@ -23,6 +23,9 @@ import { StructureProperties } from '../properties';
|
||||
import { BoundaryHelper } from '../../../../mol-math/geometry/boundary-helper';
|
||||
import { Boundary } from '../../../../mol-math/geometry/boundary';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const osSize = OrderedSet.size;
|
||||
|
||||
/** Represents multiple structure element index locations */
|
||||
export interface Loci {
|
||||
readonly kind: 'element-loci',
|
||||
@@ -71,7 +74,7 @@ export namespace Loci {
|
||||
|
||||
export function size(loci: Loci) {
|
||||
let s = 0;
|
||||
for (const u of loci.elements) s += OrderedSet.size(u.indices);
|
||||
for (const u of loci.elements) s += osSize(u.indices);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
54
src/mol-model/structure/structure/element/util.ts
Normal file
54
src/mol-model/structure/structure/element/util.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { SortedArray } from '../../../../mol-data/int';
|
||||
import { ElementIndex, ResidueIndex, ChainIndex } from '../../model';
|
||||
import { Unit } from '../unit';
|
||||
import { Location } from './location';
|
||||
import { StructureProperties } from '../properties';
|
||||
|
||||
// TODO: when nominal types are available, make this indexed by UnitIndex
|
||||
export type Set = SortedArray<ElementIndex>
|
||||
|
||||
/** Index into Unit.elements */
|
||||
export type UnitIndex = { readonly '@type': 'unit-element-index' } & number
|
||||
|
||||
export interface Property<T> { (location: Location): T }
|
||||
export interface Predicate extends Property<boolean> { }
|
||||
|
||||
export function property<T>(p: Property<T>) { return p; }
|
||||
|
||||
function _wrongUnitKind(kind: string) { throw new Error(`Property only available for ${kind} models.`); }
|
||||
export function atomicProperty<T>(p: (location: Location<Unit.Atomic>) => T) {
|
||||
return property(l => Unit.isAtomic(l.unit) ? p(l as Location<Unit.Atomic>) : _wrongUnitKind('atomic'));
|
||||
}
|
||||
|
||||
export function coarseProperty<T>(p: (location: Location<Unit.Spheres | Unit.Gaussians>) => T) {
|
||||
return property(l => Unit.isCoarse(l.unit) ? p(l as Location<Unit.Spheres | Unit.Gaussians>) : _wrongUnitKind('coarse'));
|
||||
}
|
||||
|
||||
export function residueIndex(e: Location) {
|
||||
if (Unit.isAtomic(e.unit)) {
|
||||
return e.unit.residueIndex[e.element];
|
||||
} else {
|
||||
// TODO: throw error instead?
|
||||
return -1 as ResidueIndex;
|
||||
}
|
||||
}
|
||||
|
||||
export function chainIndex(e: Location) {
|
||||
if (Unit.isAtomic(e.unit)) {
|
||||
return e.unit.chainIndex[e.element];
|
||||
} else {
|
||||
// TODO: throw error instead?
|
||||
return -1 as ChainIndex;
|
||||
}
|
||||
}
|
||||
|
||||
export function entityIndex(l: Location) {
|
||||
return StructureProperties.entity.key(l);
|
||||
}
|
||||
@@ -98,12 +98,12 @@ const residue = {
|
||||
microheterogeneityCompIds: p(microheterogeneityCompIds),
|
||||
secondary_structure_type: p(l => {
|
||||
if (!Unit.isAtomic(l.unit)) notAtomic();
|
||||
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.id);
|
||||
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
|
||||
return secStruc?.type[l.unit.residueIndex[l.element]] ?? SecondaryStructureType.Flag.NA;
|
||||
}),
|
||||
secondary_structure_key: p(l => {
|
||||
if (!Unit.isAtomic(l.unit)) notAtomic();
|
||||
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.id);
|
||||
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
|
||||
return secStruc?.key[l.unit.residueIndex[l.element]] ?? -1;
|
||||
}),
|
||||
chem_comp_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.type),
|
||||
|
||||
@@ -23,7 +23,7 @@ import { Carbohydrates } from './carbohydrates/data';
|
||||
import { computeCarbohydrates } from './carbohydrates/compute';
|
||||
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
|
||||
import { idFactory } from '../../../mol-util/id-factory';
|
||||
import { Box3D, GridLookup3D } from '../../../mol-math/geometry';
|
||||
import { GridLookup3D } from '../../../mol-math/geometry';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { CustomProperties } from '../../custom-property';
|
||||
import { AtomicHierarchy } from '../model/properties/atomic';
|
||||
@@ -35,90 +35,73 @@ import { Trajectory } from '../trajectory';
|
||||
import { RuntimeContext, Task } from '../../../mol-task';
|
||||
import { computeStructureBoundary } from './util/boundary';
|
||||
|
||||
/** Internal structure state */
|
||||
type State = {
|
||||
parent?: Structure,
|
||||
boundary?: Boundary,
|
||||
lookup3d?: StructureLookup3D,
|
||||
interUnitBonds?: InterUnitBonds,
|
||||
unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
|
||||
unitSymmetryGroupsIndexMap?: IntMap<number>,
|
||||
unitsSortedByVolume?: ReadonlyArray<Unit>;
|
||||
carbohydrates?: Carbohydrates,
|
||||
models?: ReadonlyArray<Model>,
|
||||
model?: Model,
|
||||
masterModel?: Model,
|
||||
representativeModel?: Model,
|
||||
uniqueResidueNames?: Set<string>,
|
||||
uniqueElementSymbols?: Set<ElementSymbol>,
|
||||
entityIndices?: ReadonlyArray<EntityIndex>,
|
||||
uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
|
||||
serialMapping?: SerialMapping,
|
||||
hashCode: number,
|
||||
transformHash: number,
|
||||
elementCount: number,
|
||||
bondCount: number,
|
||||
uniqueElementCount: number,
|
||||
atomicResidueCount: number,
|
||||
polymerResidueCount: number,
|
||||
polymerGapCount: number,
|
||||
polymerUnitCount: number,
|
||||
coordinateSystem: SymmetryOperator,
|
||||
label: string,
|
||||
propertyData?: any,
|
||||
customProps?: CustomProperties
|
||||
}
|
||||
|
||||
class Structure {
|
||||
/** Maps unit.id to unit */
|
||||
readonly unitMap: IntMap<Unit>;
|
||||
/** Maps unit.id to index of unit in units array */
|
||||
readonly unitIndexMap: IntMap<number>;
|
||||
/** Array of all units in the structure, sorted by unit.id */
|
||||
readonly units: ReadonlyArray<Unit>;
|
||||
|
||||
private _props: {
|
||||
parent?: Structure,
|
||||
boundary?: Boundary,
|
||||
lookup3d?: StructureLookup3D,
|
||||
interUnitBonds?: InterUnitBonds,
|
||||
unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
|
||||
unitSymmetryGroupsIndexMap?: IntMap<number>,
|
||||
unitsSortedByVolume?: ReadonlyArray<Unit>;
|
||||
carbohydrates?: Carbohydrates,
|
||||
models?: ReadonlyArray<Model>,
|
||||
model?: Model,
|
||||
masterModel?: Model,
|
||||
representativeModel?: Model,
|
||||
uniqueResidueNames?: Set<string>,
|
||||
uniqueElementSymbols?: Set<ElementSymbol>,
|
||||
entityIndices?: ReadonlyArray<EntityIndex>,
|
||||
uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
|
||||
serialMapping?: SerialMapping,
|
||||
hashCode: number,
|
||||
/** Hash based on all unit.id values in the structure, reflecting the units transformation */
|
||||
transformHash: number,
|
||||
elementCount: number,
|
||||
bondCount: number,
|
||||
uniqueElementCount: number,
|
||||
atomicResidueCount: number,
|
||||
polymerResidueCount: number,
|
||||
polymerUnitCount: number,
|
||||
coordinateSystem: SymmetryOperator,
|
||||
label: string,
|
||||
propertyData?: any,
|
||||
customProps?: CustomProperties
|
||||
} = {
|
||||
hashCode: -1,
|
||||
transformHash: -1,
|
||||
elementCount: -1,
|
||||
bondCount: -1,
|
||||
uniqueElementCount: -1,
|
||||
atomicResidueCount: -1,
|
||||
polymerResidueCount: -1,
|
||||
polymerUnitCount: -1,
|
||||
coordinateSystem: SymmetryOperator.Default,
|
||||
label: ''
|
||||
};
|
||||
|
||||
subsetBuilder(isSorted: boolean) {
|
||||
return new StructureSubsetBuilder(this, isSorted);
|
||||
}
|
||||
|
||||
/** Count of all elements in the structure, i.e. the sum of the elements in the units */
|
||||
get elementCount() {
|
||||
return this._props.elementCount;
|
||||
return this.state.elementCount;
|
||||
}
|
||||
|
||||
/** Count of all bonds (intra- and inter-unit) in the structure */
|
||||
get bondCount() {
|
||||
if (this._props.bondCount === -1) {
|
||||
this._props.bondCount = this.interUnitBonds.edgeCount + Bond.getIntraUnitBondCount(this);
|
||||
if (this.state.bondCount === -1) {
|
||||
this.state.bondCount = this.interUnitBonds.edgeCount + Bond.getIntraUnitBondCount(this);
|
||||
}
|
||||
return this._props.bondCount;
|
||||
return this.state.bondCount;
|
||||
}
|
||||
|
||||
get hasCustomProperties() {
|
||||
return !!this._props.customProps && this._props.customProps.all.length > 0;
|
||||
return !!this.state.customProps && this.state.customProps.all.length > 0;
|
||||
}
|
||||
|
||||
get customPropertyDescriptors() {
|
||||
if (!this._props.customProps) this._props.customProps = new CustomProperties();
|
||||
return this._props.customProps;
|
||||
if (!this.state.customProps) this.state.customProps = new CustomProperties();
|
||||
return this.state.customProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Property data unique to this instance of the structure.
|
||||
*/
|
||||
get currentPropertyData() {
|
||||
if (!this._props.propertyData) this._props.propertyData = Object.create(null);
|
||||
return this._props.propertyData;
|
||||
if (!this.state.propertyData) this.state.propertyData = Object.create(null);
|
||||
return this.state.propertyData;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,41 +113,47 @@ class Structure {
|
||||
|
||||
/** Count of all polymer residues in the structure */
|
||||
get polymerResidueCount() {
|
||||
if (this._props.polymerResidueCount === -1) {
|
||||
this._props.polymerResidueCount = getPolymerResidueCount(this);
|
||||
if (this.state.polymerResidueCount === -1) {
|
||||
this.state.polymerResidueCount = getPolymerResidueCount(this);
|
||||
}
|
||||
return this._props.polymerResidueCount;
|
||||
return this.state.polymerResidueCount;
|
||||
}
|
||||
|
||||
/** Count of all polymer gaps in the structure */
|
||||
get polymerGapCount() {
|
||||
if (this.state.polymerGapCount === -1) {
|
||||
this.state.polymerGapCount = getPolymerGapCount(this);
|
||||
}
|
||||
return this.state.polymerGapCount;
|
||||
}
|
||||
|
||||
get polymerUnitCount() {
|
||||
if (this._props.polymerUnitCount === -1) {
|
||||
this._props.polymerUnitCount = getPolymerUnitCount(this);
|
||||
if (this.state.polymerUnitCount === -1) {
|
||||
this.state.polymerUnitCount = getPolymerUnitCount(this);
|
||||
}
|
||||
return this._props.polymerUnitCount;
|
||||
return this.state.polymerUnitCount;
|
||||
}
|
||||
|
||||
get uniqueElementCount() {
|
||||
if (this._props.uniqueElementCount === -1) {
|
||||
this._props.uniqueElementCount = getUniqueElementCount(this);
|
||||
if (this.state.uniqueElementCount === -1) {
|
||||
this.state.uniqueElementCount = getUniqueElementCount(this);
|
||||
}
|
||||
return this._props.uniqueElementCount;
|
||||
return this.state.uniqueElementCount;
|
||||
}
|
||||
|
||||
get atomicResidueCount() {
|
||||
if (this._props.atomicResidueCount === -1) {
|
||||
this._props.atomicResidueCount = getAtomicResidueCount(this);
|
||||
if (this.state.atomicResidueCount === -1) {
|
||||
this.state.atomicResidueCount = getAtomicResidueCount(this);
|
||||
}
|
||||
return this._props.atomicResidueCount;
|
||||
return this.state.atomicResidueCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coarse-grained structure, defined as containing less than
|
||||
* twice as many elements as polymer residues
|
||||
* True if any model the structure is based on is coarse grained.
|
||||
* @see Model.isCoarseGrained
|
||||
*/
|
||||
get isCoarseGrained() {
|
||||
const ec = this.elementCount;
|
||||
const prc = this.polymerResidueCount;
|
||||
return prc && ec ? ec / prc < 2 : false;
|
||||
return this.models.some(m => Model.isCoarseGrained(m));
|
||||
}
|
||||
|
||||
get isEmpty() {
|
||||
@@ -172,15 +161,15 @@ class Structure {
|
||||
}
|
||||
|
||||
get hashCode() {
|
||||
if (this._props.hashCode !== -1) return this._props.hashCode;
|
||||
if (this.state.hashCode !== -1) return this.state.hashCode;
|
||||
return this.computeHash();
|
||||
}
|
||||
|
||||
/** Hash based on all unit.id values in the structure, reflecting the units transformation */
|
||||
get transformHash() {
|
||||
if (this._props.transformHash !== -1) return this._props.transformHash;
|
||||
this._props.transformHash = hashFnv32a(this.units.map(u => u.id));
|
||||
return this._props.transformHash;
|
||||
if (this.state.transformHash !== -1) return this.state.transformHash;
|
||||
this.state.transformHash = hashFnv32a(this.units.map(u => u.id));
|
||||
return this.state.transformHash;
|
||||
}
|
||||
|
||||
private computeHash() {
|
||||
@@ -193,7 +182,7 @@ class Structure {
|
||||
hash = (31 * hash + this.elementCount) | 0;
|
||||
hash = hash1(hash);
|
||||
if (hash === -1) hash = 0;
|
||||
this._props.hashCode = hash;
|
||||
this.state.hashCode = hash;
|
||||
return hash;
|
||||
}
|
||||
|
||||
@@ -204,12 +193,12 @@ class Structure {
|
||||
|
||||
/** The parent or itself in case this is the root */
|
||||
get root() {
|
||||
return this._props.parent || this;
|
||||
return this.state.parent || this;
|
||||
}
|
||||
|
||||
/** The root/top-most parent or `undefined` in case this is the root */
|
||||
get parent() {
|
||||
return this._props.parent;
|
||||
return this.state.parent;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,86 +209,79 @@ class Structure {
|
||||
* by the consumer.
|
||||
*/
|
||||
get coordinateSystem(): SymmetryOperator {
|
||||
return this._props.coordinateSystem;
|
||||
return this.state.coordinateSystem;
|
||||
}
|
||||
|
||||
get label() {
|
||||
return this._props.label;
|
||||
return this.state.label;
|
||||
}
|
||||
|
||||
get boundary() {
|
||||
if (this._props.boundary) return this._props.boundary;
|
||||
this._props.boundary = computeStructureBoundary(this);
|
||||
return this._props.boundary;
|
||||
if (this.state.boundary) return this.state.boundary;
|
||||
this.state.boundary = computeStructureBoundary(this);
|
||||
return this.state.boundary;
|
||||
}
|
||||
|
||||
get lookup3d() {
|
||||
if (this._props.lookup3d) return this._props.lookup3d;
|
||||
this._props.lookup3d = new StructureLookup3D(this);
|
||||
return this._props.lookup3d;
|
||||
if (this.state.lookup3d) return this.state.lookup3d;
|
||||
this.state.lookup3d = new StructureLookup3D(this);
|
||||
return this.state.lookup3d;
|
||||
}
|
||||
|
||||
get interUnitBonds() {
|
||||
if (this._props.interUnitBonds) return this._props.interUnitBonds;
|
||||
this._props.interUnitBonds = computeInterUnitBonds(this);
|
||||
return this._props.interUnitBonds;
|
||||
if (this.state.interUnitBonds) return this.state.interUnitBonds;
|
||||
this.state.interUnitBonds = computeInterUnitBonds(this);
|
||||
return this.state.interUnitBonds;
|
||||
}
|
||||
|
||||
get unitSymmetryGroups(): ReadonlyArray<Unit.SymmetryGroup> {
|
||||
if (this._props.unitSymmetryGroups) return this._props.unitSymmetryGroups;
|
||||
this._props.unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this);
|
||||
return this._props.unitSymmetryGroups;
|
||||
if (this.state.unitSymmetryGroups) return this.state.unitSymmetryGroups;
|
||||
this.state.unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this);
|
||||
return this.state.unitSymmetryGroups;
|
||||
}
|
||||
|
||||
/** Maps unit.id to index of SymmetryGroup in unitSymmetryGroups array */
|
||||
get unitSymmetryGroupsIndexMap(): IntMap<number> {
|
||||
if (this._props.unitSymmetryGroupsIndexMap) return this._props.unitSymmetryGroupsIndexMap;
|
||||
this._props.unitSymmetryGroupsIndexMap = Unit.SymmetryGroup.getUnitSymmetryGroupsIndexMap(this.unitSymmetryGroups);
|
||||
return this._props.unitSymmetryGroupsIndexMap;
|
||||
}
|
||||
|
||||
/** Array of all units in the structure, sorted by their boundary volume */
|
||||
get unitsSortedByVolume(): ReadonlyArray<Unit> {
|
||||
if (this._props.unitsSortedByVolume) return this._props.unitsSortedByVolume;
|
||||
this._props.unitsSortedByVolume = getUnitsSortedByVolume(this);
|
||||
return this._props.unitsSortedByVolume;
|
||||
if (this.state.unitSymmetryGroupsIndexMap) return this.state.unitSymmetryGroupsIndexMap;
|
||||
this.state.unitSymmetryGroupsIndexMap = Unit.SymmetryGroup.getUnitSymmetryGroupsIndexMap(this.unitSymmetryGroups);
|
||||
return this.state.unitSymmetryGroupsIndexMap;
|
||||
}
|
||||
|
||||
get carbohydrates(): Carbohydrates {
|
||||
if (this._props.carbohydrates) return this._props.carbohydrates;
|
||||
this._props.carbohydrates = computeCarbohydrates(this);
|
||||
return this._props.carbohydrates;
|
||||
if (this.state.carbohydrates) return this.state.carbohydrates;
|
||||
this.state.carbohydrates = computeCarbohydrates(this);
|
||||
return this.state.carbohydrates;
|
||||
}
|
||||
|
||||
get models(): ReadonlyArray<Model> {
|
||||
if (this._props.models) return this._props.models;
|
||||
this._props.models = getModels(this);
|
||||
return this._props.models;
|
||||
if (this.state.models) return this.state.models;
|
||||
this.state.models = getModels(this);
|
||||
return this.state.models;
|
||||
}
|
||||
|
||||
get uniqueResidueNames() {
|
||||
return this._props.uniqueResidueNames
|
||||
|| (this._props.uniqueResidueNames = getUniqueResidueNames(this));
|
||||
return this.state.uniqueResidueNames
|
||||
|| (this.state.uniqueResidueNames = getUniqueResidueNames(this));
|
||||
}
|
||||
|
||||
get uniqueElementSymbols() {
|
||||
return this._props.uniqueElementSymbols
|
||||
|| (this._props.uniqueElementSymbols = getUniqueElementSymbols(this));
|
||||
return this.state.uniqueElementSymbols
|
||||
|| (this.state.uniqueElementSymbols = getUniqueElementSymbols(this));
|
||||
}
|
||||
|
||||
get entityIndices() {
|
||||
return this._props.entityIndices
|
||||
|| (this._props.entityIndices = getEntityIndices(this));
|
||||
return this.state.entityIndices
|
||||
|| (this.state.entityIndices = getEntityIndices(this));
|
||||
}
|
||||
|
||||
get uniqueAtomicResidueIndices() {
|
||||
return this._props.uniqueAtomicResidueIndices
|
||||
|| (this._props.uniqueAtomicResidueIndices = getUniqueAtomicResidueIndices(this));
|
||||
return this.state.uniqueAtomicResidueIndices
|
||||
|| (this.state.uniqueAtomicResidueIndices = getUniqueAtomicResidueIndices(this));
|
||||
}
|
||||
|
||||
/** Contains only atomic units */
|
||||
get isAtomic() {
|
||||
for (const u of this.units) if (Unit.isAtomic(u)) return false;
|
||||
for (const u of this.units) if (!Unit.isAtomic(u)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -311,7 +293,7 @@ class Structure {
|
||||
|
||||
/** Contains only coarse units */
|
||||
get isCoarse() {
|
||||
for (const u of this.units) if (Unit.isCoarse(u)) return false;
|
||||
for (const u of this.units) if (!Unit.isCoarse(u)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -329,7 +311,7 @@ class Structure {
|
||||
* to address elements in a structure.
|
||||
*/
|
||||
get serialMapping() {
|
||||
return this._props.serialMapping || (this._props.serialMapping = getSerialMapping(this));
|
||||
return this.state.serialMapping || (this.state.serialMapping = getSerialMapping(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,25 +319,25 @@ class Structure {
|
||||
* Otherwise throw an exception.
|
||||
*/
|
||||
get model(): Model {
|
||||
if (this._props.model) return this._props.model;
|
||||
if (this._props.representativeModel) return this._props.representativeModel;
|
||||
if (this._props.masterModel) return this._props.masterModel;
|
||||
if (this.state.model) return this.state.model;
|
||||
if (this.state.representativeModel) return this.state.representativeModel;
|
||||
if (this.state.masterModel) return this.state.masterModel;
|
||||
const models = this.models;
|
||||
if (models.length > 1) {
|
||||
throw new Error('The structure is based on multiple models and has neither a master- nor a representative-model.');
|
||||
}
|
||||
this._props.model = models[0];
|
||||
return this._props.model;
|
||||
this.state.model = models[0];
|
||||
return this.state.model;
|
||||
}
|
||||
|
||||
/** The master-model, other models can have bonds to it */
|
||||
get masterModel(): Model | undefined {
|
||||
return this._props.masterModel;
|
||||
return this.state.masterModel;
|
||||
}
|
||||
|
||||
/** A representative model, e.g. the first model of a trajectory */
|
||||
get representativeModel(): Model | undefined {
|
||||
return this._props.representativeModel;
|
||||
return this.state.representativeModel;
|
||||
}
|
||||
|
||||
hasElement(e: StructureElement.Location) {
|
||||
@@ -379,51 +361,39 @@ class Structure {
|
||||
}
|
||||
return Structure.create(units, {
|
||||
label: this.label,
|
||||
interUnitBonds: this._props.interUnitBonds,
|
||||
interUnitBonds: this.state.interUnitBonds,
|
||||
});
|
||||
}
|
||||
|
||||
private initUnits(units: ArrayLike<Unit>) {
|
||||
const unitMap = IntMap.Mutable<Unit>();
|
||||
const unitIndexMap = IntMap.Mutable<number>();
|
||||
let elementCount = 0;
|
||||
let isSorted = true;
|
||||
let lastId = units.length > 0 ? units[0].id : 0;
|
||||
for (let i = 0, _i = units.length; i < _i; i++) {
|
||||
const u = units[i];
|
||||
unitMap.set(u.id, u);
|
||||
elementCount += u.elements.length;
|
||||
if (u.id < lastId) isSorted = false;
|
||||
lastId = u.id;
|
||||
}
|
||||
if (!isSorted) sort(units, 0, units.length, cmpUnits, arraySwap);
|
||||
for (let i = 0, _i = units.length; i < _i; i++) {
|
||||
unitIndexMap.set(units[i].id, i);
|
||||
}
|
||||
this._props.elementCount = elementCount;
|
||||
return { unitMap, unitIndexMap };
|
||||
private _child: Structure | undefined;
|
||||
private _target: Structure | undefined;
|
||||
|
||||
/**
|
||||
* For `structure` with `parent` this returns a proxy that
|
||||
* targets `parent` and has `structure` attached as a child.
|
||||
*/
|
||||
asParent(): Structure {
|
||||
return this.parent ? new Structure(this.parent.units, this.parent.unitMap, this.parent.unitIndexMap, this.parent.state, { child: this, target: this.parent }) : this;
|
||||
}
|
||||
|
||||
constructor(units: ArrayLike<Unit>, props: Structure.Props = {}) {
|
||||
const { unitMap, unitIndexMap } = this.initUnits(units);
|
||||
this.unitMap = unitMap;
|
||||
this.unitIndexMap = unitIndexMap;
|
||||
this.units = units as ReadonlyArray<Unit>;
|
||||
get child(): Structure | undefined {
|
||||
return this._child;
|
||||
}
|
||||
|
||||
if (props.parent) this._props.parent = props.parent.parent || props.parent;
|
||||
if (props.interUnitBonds) this._props.interUnitBonds = props.interUnitBonds;
|
||||
/** Get the proxy target. Usefull for equality checks. */
|
||||
get target(): Structure {
|
||||
return this._target ?? this;
|
||||
}
|
||||
|
||||
if (props.coordinateSystem) this._props.coordinateSystem = props.coordinateSystem;
|
||||
else if (props.parent) this._props.coordinateSystem = props.parent.coordinateSystem;
|
||||
|
||||
if (props.label) this._props.label = props.label;
|
||||
else if (props.parent) this._props.label = props.parent.label;
|
||||
|
||||
if (props.masterModel) this._props.masterModel = props.masterModel;
|
||||
else if (props.parent) this._props.masterModel = props.parent.masterModel;
|
||||
|
||||
if (props.representativeModel) this._props.representativeModel = props.representativeModel;
|
||||
else if (props.parent) this._props.representativeModel = props.parent.representativeModel;
|
||||
/**
|
||||
* @param units Array of all units in the structure, sorted by unit.id
|
||||
* @param unitMap Maps unit.id to index of unit in units array
|
||||
* @param unitIndexMap Array of all units in the structure, sorted by unit.id
|
||||
*/
|
||||
constructor(readonly units: ReadonlyArray<Unit>, readonly unitMap: IntMap<Unit>, readonly unitIndexMap: IntMap<number>, private readonly state: State, asParent?: { child: Structure, target: Structure }) {
|
||||
// always assign to ensure object shape
|
||||
this._child = asParent?.child;
|
||||
this._target = asParent?.target;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,24 +401,6 @@ function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) {
|
||||
return units[i].id - units[j].id;
|
||||
|
||||
}
|
||||
function cmpUnitGroupVolume(units: ArrayLike<[index: number, volume: number]>, i: number, j: number) {
|
||||
const d = units[i][1] - units[j][1];
|
||||
if (d === 0) return units[i][0] - units[j][0];
|
||||
return d;
|
||||
}
|
||||
|
||||
function getUnitsSortedByVolume(structure: Structure) {
|
||||
const { unitSymmetryGroups } = structure;
|
||||
const groups = unitSymmetryGroups.map((g, i) => [i, Box3D.volume(g.units[0].lookup3d.boundary.box)] as [number, number]);
|
||||
sort(groups, 0, groups.length, cmpUnitGroupVolume, arraySwap);
|
||||
const ret: Unit[] = [];
|
||||
for (const [i] of groups) {
|
||||
for (const u of unitSymmetryGroups[i].units) {
|
||||
ret.push(u);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getModels(s: Structure) {
|
||||
const { units } = s;
|
||||
@@ -570,6 +522,15 @@ function getPolymerResidueCount(structure: Structure): number {
|
||||
return polymerResidueCount;
|
||||
}
|
||||
|
||||
function getPolymerGapCount(structure: Structure): number {
|
||||
const { units } = structure;
|
||||
let polymerGapCount = 0;
|
||||
for (let i = 0, _i = units.length; i < _i; i++) {
|
||||
polymerGapCount += units[i].gapElements.length / 2;
|
||||
}
|
||||
return polymerGapCount;
|
||||
}
|
||||
|
||||
function getPolymerUnitCount(structure: Structure): number {
|
||||
const { units } = structure;
|
||||
let polymerUnitCount = 0;
|
||||
@@ -634,7 +595,7 @@ function getSerialMapping(structure: Structure): SerialMapping {
|
||||
}
|
||||
|
||||
namespace Structure {
|
||||
export const Empty = new Structure([]);
|
||||
export const Empty = create([]);
|
||||
|
||||
export interface Props {
|
||||
parent?: Structure
|
||||
@@ -647,7 +608,7 @@ namespace Structure {
|
||||
representativeModel?: Model
|
||||
}
|
||||
|
||||
/** Serial index of an element in the structure accross all units */
|
||||
/** Serial index of an element in the structure across all units */
|
||||
export type SerialIndex = { readonly '@type': 'serial-index' } & number
|
||||
|
||||
/** Represents a single structure */
|
||||
@@ -688,8 +649,57 @@ namespace Structure {
|
||||
return Loci(structure);
|
||||
}
|
||||
|
||||
export function create(units: ReadonlyArray<Unit>, props?: Props): Structure {
|
||||
return new Structure(units, props);
|
||||
export function create(units: ReadonlyArray<Unit>, props: Props = {}): Structure {
|
||||
// init units
|
||||
const unitMap = IntMap.Mutable<Unit>();
|
||||
const unitIndexMap = IntMap.Mutable<number>();
|
||||
let elementCount = 0;
|
||||
let isSorted = true;
|
||||
let lastId = units.length > 0 ? units[0].id : 0;
|
||||
for (let i = 0, _i = units.length; i < _i; i++) {
|
||||
const u = units[i];
|
||||
unitMap.set(u.id, u);
|
||||
elementCount += u.elements.length;
|
||||
if (u.id < lastId) isSorted = false;
|
||||
lastId = u.id;
|
||||
}
|
||||
if (!isSorted) sort(units, 0, units.length, cmpUnits, arraySwap);
|
||||
for (let i = 0, _i = units.length; i < _i; i++) {
|
||||
unitIndexMap.set(units[i].id, i);
|
||||
}
|
||||
|
||||
// initial state
|
||||
const state: State = {
|
||||
hashCode: -1,
|
||||
transformHash: -1,
|
||||
elementCount,
|
||||
bondCount: -1,
|
||||
uniqueElementCount: -1,
|
||||
atomicResidueCount: -1,
|
||||
polymerResidueCount: -1,
|
||||
polymerGapCount: -1,
|
||||
polymerUnitCount: -1,
|
||||
coordinateSystem: SymmetryOperator.Default,
|
||||
label: ''
|
||||
};
|
||||
|
||||
// handle props
|
||||
if (props.parent) state.parent = props.parent.parent || props.parent;
|
||||
if (props.interUnitBonds) state.interUnitBonds = props.interUnitBonds;
|
||||
|
||||
if (props.coordinateSystem) state.coordinateSystem = props.coordinateSystem;
|
||||
else if (props.parent) state.coordinateSystem = props.parent.coordinateSystem;
|
||||
|
||||
if (props.label) state.label = props.label;
|
||||
else if (props.parent) state.label = props.parent.label;
|
||||
|
||||
if (props.masterModel) state.masterModel = props.masterModel;
|
||||
else if (props.parent) state.masterModel = props.parent.masterModel;
|
||||
|
||||
if (props.representativeModel) state.representativeModel = props.representativeModel;
|
||||
else if (props.parent) state.representativeModel = props.parent.representativeModel;
|
||||
|
||||
return new Structure(units, unitMap, unitIndexMap, state);
|
||||
}
|
||||
|
||||
export async function ofTrajectory(trajectory: Trajectory, ctx: RuntimeContext): Promise<Structure> {
|
||||
@@ -895,7 +905,7 @@ namespace Structure {
|
||||
|
||||
const cs = s.coordinateSystem;
|
||||
const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs), cs);
|
||||
return new Structure(units, { parent: s, coordinateSystem: newCS });
|
||||
return create(units, { parent: s, coordinateSystem: newCS });
|
||||
}
|
||||
|
||||
export class StructureBuilder {
|
||||
|
||||
@@ -153,7 +153,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
? thresholdAB
|
||||
: beI < 0
|
||||
? thresholdA
|
||||
: (thresholdA + getElementThreshold(beI)) / 2; // not sure if avg or min but max is too big
|
||||
: (thresholdA + getElementThreshold(beI)) / 1.95; // not sure if avg or min but max is too big
|
||||
|
||||
if (dist <= pairingThreshold) {
|
||||
const atomIdB = label_atom_idB.value(bI);
|
||||
@@ -176,7 +176,7 @@ export interface InterBondComputationProps extends BondComputationProps {
|
||||
function findBonds(structure: Structure, props: InterBondComputationProps) {
|
||||
const builder = new InterUnitGraph.Builder<number, StructureElement.UnitIndex, InterUnitEdgeProps>();
|
||||
|
||||
if (props.noCompute) {
|
||||
if (props.noCompute || structure.isCoarseGrained) {
|
||||
// TODO add function that only adds bonds defined in structConn and avoids using
|
||||
// structure.lookup and unit.lookup (expensive for large structure and not
|
||||
// needed for archival files or files with an MD topology)
|
||||
|
||||
@@ -19,6 +19,7 @@ import { StructConn } from '../../../../../mol-model-formats/structure/property/
|
||||
import { Vec3 } from '../../../../../mol-math/linear-algebra';
|
||||
import { ElementIndex } from '../../../model/indexing';
|
||||
import { equalEps } from '../../../../../mol-math/linear-algebra/3d/common';
|
||||
import { Model } from '../../../model/model';
|
||||
|
||||
function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], atomCount: number, canRemap: boolean): IntraUnitBonds {
|
||||
const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB);
|
||||
@@ -211,7 +212,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
? thresholdAB
|
||||
: beI < 0
|
||||
? thresholdA
|
||||
: (thresholdA + getElementThreshold(beI)) / 2; // not sure if avg or min but max is too big
|
||||
: (thresholdA + getElementThreshold(beI)) / 1.95; // not sure if avg or min but max is too big
|
||||
|
||||
if (dist <= pairingThreshold) {
|
||||
atomA[atomA.length] = _aI;
|
||||
@@ -233,7 +234,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
|
||||
function computeIntraUnitBonds(unit: Unit.Atomic, props?: Partial<BondComputationProps>) {
|
||||
const p = { ...DefaultBondComputationProps, ...props };
|
||||
if (p.noCompute) {
|
||||
if (p.noCompute || Model.isCoarseGrained(unit.model)) {
|
||||
// TODO add function that only adds bonds defined in structConn of chemCompBond
|
||||
// and avoid using unit.lookup
|
||||
return IntraUnitBonds.Empty;
|
||||
|
||||
@@ -94,6 +94,36 @@ export class StructureLookup3D {
|
||||
}
|
||||
}
|
||||
|
||||
findIntoBuilderIf(x: number, y: number, z: number, radius: number, builder: StructureUniqueSubsetBuilder, test: (l: StructureElement.Location) => boolean) {
|
||||
const { units } = this.structure;
|
||||
const closeUnits = this.unitLookup.find(x, y, z, radius);
|
||||
if (closeUnits.count === 0) return;
|
||||
|
||||
const loc = StructureElement.Location.create(this.structure);
|
||||
|
||||
for (let t = 0, _t = closeUnits.count; t < _t; t++) {
|
||||
const unit = units[closeUnits.indices[t]];
|
||||
Vec3.set(this.pivot, x, y, z);
|
||||
if (!unit.conformation.operator.isIdentity) {
|
||||
Vec3.transformMat4(this.pivot, this.pivot, unit.conformation.operator.inverse);
|
||||
}
|
||||
const unitLookup = unit.lookup3d;
|
||||
const groupResult = unitLookup.find(this.pivot[0], this.pivot[1], this.pivot[2], radius);
|
||||
if (groupResult.count === 0) continue;
|
||||
|
||||
const elements = unit.elements;
|
||||
loc.unit = unit;
|
||||
builder.beginUnit(unit.id);
|
||||
for (let j = 0, _j = groupResult.count; j < _j; j++) {
|
||||
loc.element = elements[groupResult.indices[j]];
|
||||
if (test(loc)) {
|
||||
builder.addElement(loc.element);
|
||||
}
|
||||
}
|
||||
builder.commitUnit();
|
||||
}
|
||||
}
|
||||
|
||||
findIntoBuilderWithRadius(x: number, y: number, z: number, pivotR: number, maxRadius: number, radius: number, eRadius: StructureElement.Property<number>, builder: StructureUniqueSubsetBuilder) {
|
||||
const { units } = this.structure;
|
||||
const closeUnits = this.unitLookup.find(x, y, z, radius);
|
||||
|
||||
@@ -18,7 +18,7 @@ export const AnimateModelIndex = PluginStateAnimation.create({
|
||||
params: () => ({
|
||||
mode: PD.MappedStatic('loop', {
|
||||
palindrome: PD.Group({ }),
|
||||
loop: PD.Group({ }),
|
||||
loop: PD.Group({ direction: PD.Select('forward', [['forward', 'Forward'], ['backward', 'Backward']]) }),
|
||||
once: PD.Group({ direction: PD.Select('forward', [['forward', 'Forward'], ['backward', 'Backward']]) }, { isFlat: true })
|
||||
}, { options: [['palindrome', 'Palindrome'], ['loop', 'Loop'], ['once', 'Once']] }),
|
||||
duration: PD.MappedStatic('fixed', {
|
||||
@@ -125,8 +125,11 @@ export const AnimateModelIndex = PluginStateAnimation.create({
|
||||
: Math.ceil(1000 * traj.data.frameCount / params.duration.params.targetFps);
|
||||
|
||||
let phase: number = (t.current % durationInMs) / durationInMs;
|
||||
|
||||
if (params.mode.name === 'palindrome') {
|
||||
if (params.mode.name === 'loop') {
|
||||
if (params.mode.params.direction === 'backward') {
|
||||
phase = 1 - phase;
|
||||
}
|
||||
} if (params.mode.name === 'palindrome') {
|
||||
phase = 2 * phase;
|
||||
if (phase > 1) phase = 2 - phase;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import { ChainIdColorThemeProvider } from '../../../mol-theme/color/chain-id';
|
||||
import { OperatorNameColorThemeProvider } from '../../../mol-theme/color/operator-name';
|
||||
import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair';
|
||||
import { StructConn } from '../../../mol-model-formats/structure/property/bonds/struct_conn';
|
||||
import { StructureRepresentationRegistry } from '../../../mol-repr/structure/registry';
|
||||
import { assertUnreachable } from '../../../mol-util/type-helpers';
|
||||
|
||||
export interface StructureRepresentationPresetProvider<P = any, S extends _Result = _Result> extends PresetProvider<PluginStateObject.Molecule.Structure, P, S> { }
|
||||
export function StructureRepresentationPresetProvider<P, S extends _Result>(repr: StructureRepresentationPresetProvider<P, S>) { return repr; }
|
||||
@@ -110,6 +112,8 @@ const auto = StructureRepresentationPresetProvider({
|
||||
const thresholds = plugin.config.get(PluginConfig.Structure.SizeThresholds) || Structure.DefaultSizeThresholds;
|
||||
const size = Structure.getSize(structure, thresholds);
|
||||
|
||||
const gapFraction = structure.polymerResidueCount / structure.polymerGapCount;
|
||||
|
||||
switch (size) {
|
||||
case Structure.Size.Gigantic:
|
||||
case Structure.Size.Huge:
|
||||
@@ -117,10 +121,14 @@ const auto = StructureRepresentationPresetProvider({
|
||||
case Structure.Size.Large:
|
||||
return polymerCartoon.apply(ref, params, plugin);
|
||||
case Structure.Size.Medium:
|
||||
return polymerAndLigand.apply(ref, params, plugin);
|
||||
if (gapFraction > 3) {
|
||||
return polymerAndLigand.apply(ref, params, plugin);
|
||||
} // else fall through
|
||||
case Structure.Size.Small:
|
||||
// `showCarbohydrateSymbol: true` is nice e.g. for PDB 1aga
|
||||
// `showCarbohydrateSymbol: true` is nice, e.g., for PDB 1aga
|
||||
return atomicDetail.apply(ref, { ...params, showCarbohydrateSymbol: true }, plugin);
|
||||
default:
|
||||
assertUnreachable(size);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -230,7 +238,7 @@ const coarseSurface = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-coarse-surface',
|
||||
display: {
|
||||
name: 'Coarse Surface', group: BuiltInPresetGroupName,
|
||||
description: 'Shows polymers as coarse Gaussian Surface.'
|
||||
description: 'Shows polymers and lipids as coarse Gaussian Surface.'
|
||||
},
|
||||
params: () => CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
@@ -238,7 +246,8 @@ const coarseSurface = StructureRepresentationPresetProvider({
|
||||
if (!structureCell) return {};
|
||||
|
||||
const components = {
|
||||
polymer: await presetStaticComponent(plugin, structureCell, 'polymer')
|
||||
polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
|
||||
lipid: await presetStaticComponent(plugin, structureCell, 'lipid'),
|
||||
};
|
||||
|
||||
const structure = structureCell.obj!.data;
|
||||
@@ -246,7 +255,7 @@ const coarseSurface = StructureRepresentationPresetProvider({
|
||||
const gaussianProps = Object.create(null);
|
||||
if (size === Structure.Size.Gigantic) {
|
||||
Object.assign(gaussianProps, {
|
||||
traceOnly: true,
|
||||
traceOnly: !structure.isCoarseGrained,
|
||||
radiusOffset: 2,
|
||||
smoothness: 1,
|
||||
visuals: ['structure-gaussian-surface-mesh']
|
||||
@@ -266,7 +275,8 @@ const coarseSurface = StructureRepresentationPresetProvider({
|
||||
const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
|
||||
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'polymer' })
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'polymer' }),
|
||||
lipid: builder.buildRepresentation(update, components.lipid, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'lipid' })
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
@@ -337,9 +347,15 @@ const atomicDetail = StructureRepresentationPresetProvider({
|
||||
const m = structure.models[0];
|
||||
const bondsGiven = !!IndexPairBonds.Provider.get(m) || StructConn.isExhaustive(m);
|
||||
|
||||
const atomicType = lowResidueElementRatio && !bondsGiven
|
||||
? 'spacefill' : highElementCount
|
||||
? 'line' : 'ball-and-stick';
|
||||
let atomicType: StructureRepresentationRegistry.BuiltIn = 'ball-and-stick';
|
||||
if (structure.isCoarseGrained) {
|
||||
// TODO make configurable?
|
||||
atomicType = structure.elementCount > 1_000_000 ? 'point' : 'spacefill';
|
||||
} else if (lowResidueElementRatio && !bondsGiven) {
|
||||
atomicType = 'spacefill';
|
||||
} else if (highElementCount) {
|
||||
atomicType = 'line';
|
||||
}
|
||||
const showCarbohydrateSymbol = params.showCarbohydrateSymbol && !highElementCount && !lowResidueElementRatio;
|
||||
|
||||
if (showCarbohydrateSymbol) {
|
||||
|
||||
@@ -103,6 +103,15 @@ export const PdbqtProvider: TrajectoryFormatProvider = {
|
||||
visuals: defaultVisuals
|
||||
};
|
||||
|
||||
export const XyzProvider: TrajectoryFormatProvider = {
|
||||
label: 'XYZ',
|
||||
description: 'XYZ',
|
||||
category: TrajectoryFormatCategory,
|
||||
stringExtensions: ['xyz'],
|
||||
parse: directTrajectory(StateTransforms.Model.TrajectoryFromXYZ),
|
||||
visuals: defaultVisuals
|
||||
};
|
||||
|
||||
export const GroProvider: TrajectoryFormatProvider = {
|
||||
label: 'GRO',
|
||||
description: 'GRO',
|
||||
@@ -114,14 +123,23 @@ export const GroProvider: TrajectoryFormatProvider = {
|
||||
};
|
||||
|
||||
export const MolProvider: TrajectoryFormatProvider = {
|
||||
label: 'MOL/SDF',
|
||||
description: 'MOL/SDF',
|
||||
label: 'MOL',
|
||||
description: 'MOL',
|
||||
category: TrajectoryFormatCategory,
|
||||
stringExtensions: ['mol', 'sdf', 'sd'],
|
||||
stringExtensions: ['mol'],
|
||||
parse: directTrajectory(StateTransforms.Model.TrajectoryFromMOL),
|
||||
visuals: defaultVisuals
|
||||
};
|
||||
|
||||
export const SdfProvider: TrajectoryFormatProvider = {
|
||||
label: 'SDF',
|
||||
description: 'SDF',
|
||||
category: TrajectoryFormatCategory,
|
||||
stringExtensions: ['sdf', 'sd'],
|
||||
parse: directTrajectory(StateTransforms.Model.TrajectoryFromSDF),
|
||||
visuals: defaultVisuals
|
||||
};
|
||||
|
||||
export const Mol2Provider: TrajectoryFormatProvider = {
|
||||
label: 'MOL2',
|
||||
description: 'MOL2',
|
||||
@@ -137,7 +155,9 @@ export const BuiltInTrajectoryFormats = [
|
||||
['pdb', PdbProvider] as const,
|
||||
['pdbqt', PdbqtProvider] as const,
|
||||
['gro', GroProvider] as const,
|
||||
['xyz', XyzProvider] as const,
|
||||
['mol', MolProvider] as const,
|
||||
['sdf', SdfProvider] as const,
|
||||
['mol2', Mol2Provider] as const,
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function setStructureClipping(plugin: PluginContext, components: St
|
||||
await eachRepr(plugin, components, async (update, repr, clippingCell) => {
|
||||
if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return;
|
||||
|
||||
const structure = repr.obj!.data.source.data;
|
||||
const structure = repr.obj!.data.sourceData;
|
||||
// always use the root structure to get the loci so the clipping
|
||||
// stays applicable as long as the root structure does not change
|
||||
const loci = await lociGetter(structure.root);
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function setStructureOverpaint(plugin: PluginContext, components: S
|
||||
await eachRepr(plugin, components, async (update, repr, overpaintCell) => {
|
||||
if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return;
|
||||
|
||||
const structure = repr.obj!.data.source.data;
|
||||
const structure = repr.obj!.data.sourceData;
|
||||
// always use the root structure to get the loci so the overpaint
|
||||
// stays applicable as long as the root structure does not change
|
||||
const loci = await lociGetter(structure.root);
|
||||
|
||||
@@ -425,6 +425,18 @@ const surroundings = StructureSelectionQuery('Surrounding Residues (5 \u212B) of
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const surroundingLigands = StructureSelectionQuery('Surrounding Ligands (5 \u212B) of Selection', MS.struct.modifier.union([
|
||||
MS.struct.modifier.surroundingLigands({
|
||||
0: MS.internal.generator.current(),
|
||||
radius: 5,
|
||||
'include-water': true
|
||||
})
|
||||
]), {
|
||||
description: 'Select ligand components within 5 \u212B of the current selection.',
|
||||
category: StructureSelectionCategory.Manipulate,
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const complement = StructureSelectionQuery('Inverse / Complement of Selection', MS.struct.modifier.union([
|
||||
MS.struct.modifier.exceptBy({
|
||||
0: MS.struct.generator.all(),
|
||||
@@ -446,6 +458,16 @@ const covalentlyBonded = StructureSelectionQuery('Residues Covalently Bonded to
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const covalentlyBondedComponent = StructureSelectionQuery('Covalently Bonded Component', MS.struct.modifier.union([
|
||||
MS.struct.modifier.includeConnected({
|
||||
0: MS.internal.generator.current(), 'fixed-point': true
|
||||
})
|
||||
]), {
|
||||
description: 'Select covalently bonded component based on current selection.',
|
||||
category: StructureSelectionCategory.Manipulate,
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const covalentlyOrMetallicBonded = StructureSelectionQuery('Residues with Cov. or Metallic Bond to Selection', MS.struct.modifier.union([
|
||||
MS.struct.modifier.includeConnected({
|
||||
0: MS.internal.generator.current(),
|
||||
@@ -635,9 +657,11 @@ export const StructureSelectionQueries = {
|
||||
ring,
|
||||
aromaticRing,
|
||||
surroundings,
|
||||
surroundingLigands,
|
||||
complement,
|
||||
covalentlyBonded,
|
||||
covalentlyOrMetallicBonded,
|
||||
covalentlyBondedComponent,
|
||||
wholeResidues,
|
||||
};
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function setStructureTransparency(plugin: PluginContext, components
|
||||
await eachRepr(plugin, components, async (update, repr, transparencyCell) => {
|
||||
if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return;
|
||||
|
||||
const structure = repr.obj!.data.source.data;
|
||||
const structure = repr.obj!.data.sourceData;
|
||||
// always use the root structure to get the loci so the transparency
|
||||
// stays applicable as long as the root structure does not change
|
||||
const loci = await lociGetter(structure.root);
|
||||
|
||||
@@ -164,7 +164,10 @@ export class StructureFocusManager extends StatefulPluginComponent<StructureFocu
|
||||
|
||||
plugin.state.data.events.object.updated.subscribe(({ oldData, obj, action }) => {
|
||||
if (!PluginStateObject.Molecule.Structure.is(obj)) return;
|
||||
// structure NOT changed, keep everything as is; fixes #123
|
||||
if (oldData === obj.data) return;
|
||||
|
||||
// structure changed (e.g. coordinates), try to remap and re-focus
|
||||
if (action === 'in-place') {
|
||||
const current = this.state.current;
|
||||
const structure = obj.data as Structure;
|
||||
|
||||
@@ -41,8 +41,8 @@ export namespace PluginStateObject {
|
||||
return !!o && o.type.typeClass === 'Behavior';
|
||||
}
|
||||
|
||||
export interface Representation3DData<T extends Representation.Any, S extends StateObject = StateObject> { repr: T, source: S }
|
||||
export function CreateRepresentation3D<T extends Representation.Any, S extends StateObject = StateObject>(type: { name: string }) {
|
||||
export interface Representation3DData<T extends Representation.Any, S = any> { repr: T, sourceData: S }
|
||||
export function CreateRepresentation3D<T extends Representation.Any, S = any>(type: { name: string }) {
|
||||
return Create<Representation3DData<T, S>>({ ...type, typeClass: 'Representation3D' });
|
||||
}
|
||||
|
||||
@@ -102,10 +102,10 @@ export namespace PluginStateObject {
|
||||
export class Structure extends Create<_Structure>({ name: 'Structure', typeClass: 'Object' }) { }
|
||||
|
||||
export namespace Structure {
|
||||
export class Representation3D extends CreateRepresentation3D<StructureRepresentation<any> | ShapeRepresentation<any, any, any>, Structure>({ name: 'Structure 3D' }) { }
|
||||
export class Representation3D extends CreateRepresentation3D<StructureRepresentation<any>, _Structure>({ name: 'Structure 3D' }) { }
|
||||
|
||||
export interface Representation3DStateData {
|
||||
source: Representation3D,
|
||||
repr: StructureRepresentation<any>,
|
||||
/** used to restore state when the obj is removed */
|
||||
initialState: Partial<StructureRepresentationState>,
|
||||
state: Partial<StructureRepresentationState>,
|
||||
@@ -120,12 +120,12 @@ export namespace PluginStateObject {
|
||||
|
||||
export namespace Volume {
|
||||
export class Data extends Create<_Volume>({ name: 'Volume', typeClass: 'Object' }) { }
|
||||
export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>>({ name: 'Volume 3D' }) { }
|
||||
export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>, _Volume>({ name: 'Volume 3D' }) { }
|
||||
}
|
||||
|
||||
export namespace Shape {
|
||||
export class Provider extends Create<ShapeProvider<any, any, any>>({ name: 'Shape Provider', typeClass: 'Object' }) { }
|
||||
export class Representation3D extends CreateRepresentation3D<ShapeRepresentation<any, any, any>>({ name: 'Shape 3D' }) { }
|
||||
export class Representation3D extends CreateRepresentation3D<ShapeRepresentation<any, any, any>, unknown>({ name: 'Shape 3D' }) { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,15 @@ import * as Misc from './transforms/misc';
|
||||
import * as Model from './transforms/model';
|
||||
import * as Volume from './transforms/volume';
|
||||
import * as Representation from './transforms/representation';
|
||||
import * as Shape from './transforms/shape';
|
||||
|
||||
export const StateTransforms = {
|
||||
Data,
|
||||
Misc,
|
||||
Model,
|
||||
Volume,
|
||||
Representation
|
||||
Representation,
|
||||
Shape
|
||||
};
|
||||
|
||||
export type StateTransforms = typeof StateTransforms
|
||||
@@ -37,6 +37,9 @@ import { parseMol2 } from '../../mol-io/reader/mol2/parser';
|
||||
import { trajectoryFromMol2 } from '../../mol-model-formats/structure/mol2';
|
||||
import { parseXtc } from '../../mol-io/reader/xtc/parser';
|
||||
import { coordinatesFromXtc } from '../../mol-model-formats/structure/xtc';
|
||||
import { parseXyz } from '../../mol-io/reader/xyz/parser';
|
||||
import { trajectoryFromXyz } from '../../mol-model-formats/structure/xyz';
|
||||
import { parseSdf } from '../../mol-io/reader/sdf/parser';
|
||||
|
||||
export { CoordinatesFromDcd };
|
||||
export { CoordinatesFromXtc };
|
||||
@@ -46,7 +49,9 @@ export { TrajectoryFromBlob };
|
||||
export { TrajectoryFromMmCif };
|
||||
export { TrajectoryFromPDB };
|
||||
export { TrajectoryFromGRO };
|
||||
export { TrajectoryFromXYZ };
|
||||
export { TrajectoryFromMOL };
|
||||
export { TrajectoryFromSDF };
|
||||
export { TrajectoryFromMOL2 };
|
||||
export { TrajectoryFromCube };
|
||||
export { TrajectoryFromCifCore };
|
||||
@@ -253,6 +258,24 @@ const TrajectoryFromGRO = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
});
|
||||
|
||||
type TrajectoryFromXYZ = typeof TrajectoryFromXYZ
|
||||
const TrajectoryFromXYZ = PluginStateTransform.BuiltIn({
|
||||
name: 'trajectory-from-xyz',
|
||||
display: { name: 'Parse XYZ', description: 'Parse XYZ string and create trajectory.' },
|
||||
from: [SO.Data.String],
|
||||
to: SO.Molecule.Trajectory
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse XYZ', async ctx => {
|
||||
const parsed = await parseXyz(a.data).runInContext(ctx);
|
||||
if (parsed.isError) throw new Error(parsed.message);
|
||||
const models = await trajectoryFromXyz(parsed.result).runInContext(ctx);
|
||||
const props = trajectoryProps(models);
|
||||
return new SO.Molecule.Trajectory(models, props);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
type TrajectoryFromMOL = typeof TrajectoryFromMOL
|
||||
const TrajectoryFromMOL = PluginStateTransform.BuiltIn({
|
||||
name: 'trajectory-from-mol',
|
||||
@@ -271,6 +294,36 @@ const TrajectoryFromMOL = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
});
|
||||
|
||||
type TrajectoryFromSDF = typeof TrajectoryFromSDF
|
||||
const TrajectoryFromSDF = PluginStateTransform.BuiltIn({
|
||||
name: 'trajectory-from-sdf',
|
||||
display: { name: 'Parse SDF', description: 'Parse SDF string and create trajectory.' },
|
||||
from: [SO.Data.String],
|
||||
to: SO.Molecule.Trajectory
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse SDF', async ctx => {
|
||||
const parsed = await parseSdf(a.data).runInContext(ctx);
|
||||
if (parsed.isError) throw new Error(parsed.message);
|
||||
|
||||
const models: Model[] = [];
|
||||
|
||||
for (const { molFile } of parsed.result.compounds) {
|
||||
const traj = await trajectoryFromMol(molFile).runInContext(ctx);
|
||||
for (let i = 0; i < traj.frameCount; i++) {
|
||||
models.push(await Task.resolveInContext(traj.getFrameAtIndex(i), ctx));
|
||||
}
|
||||
}
|
||||
|
||||
const traj = new ArrayTrajectory(models);
|
||||
|
||||
const props = trajectoryProps(traj);
|
||||
return new SO.Molecule.Trajectory(traj, props);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
type TrajectoryFromMOL2 = typeof TrajectoryFromMOL
|
||||
const TrajectoryFromMOL2 = PluginStateTransform.BuiltIn({
|
||||
name: 'trajectory-from-mol2',
|
||||
|
||||
@@ -36,6 +36,10 @@ import { DihedralParams, DihedralRepresentation } from '../../mol-repr/shape/loc
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
import { Clipping } from '../../mol-theme/clipping';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { getBoxMesh } from './shape';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
|
||||
export { StructureRepresentation3D };
|
||||
export { ExplodeStructureRepresentation3D };
|
||||
@@ -126,14 +130,15 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
return Task.create('Structure Representation', async ctx => {
|
||||
const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset };
|
||||
const provider = plugin.representation.structure.registry.get(params.type.name);
|
||||
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
|
||||
const data = provider.getData?.(a.data, params.type.params) || a.data;
|
||||
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, data);
|
||||
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, provider.getParams);
|
||||
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, params);
|
||||
repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: a.data }, params));
|
||||
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: data }, params);
|
||||
repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: data }, params));
|
||||
|
||||
const props = params.type.params || {};
|
||||
await repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
return new SO.Molecule.Structure.Representation3D({ repr, source: a }, { label: provider.label });
|
||||
await repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
return new SO.Molecule.Structure.Representation3D({ repr, sourceData: a.data }, { label: provider.label });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams, cache }, plugin: PluginContext) {
|
||||
@@ -141,26 +146,28 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
if (newParams.type.name !== oldParams.type.name) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const provider = plugin.representation.structure.registry.get(newParams.type.name);
|
||||
if (provider.mustRecreate?.(oldParams.type.params, newParams.type.params)) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const data = provider.getData?.(a.data, newParams.type.params) || a.data;
|
||||
const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset };
|
||||
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
|
||||
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, data);
|
||||
|
||||
// TODO: if themes had a .needsUpdate method the following block could
|
||||
// be optimized and only executed conditionally
|
||||
// dispose isn't called on update so we need to handle it manually
|
||||
Theme.releaseDependencies(plugin.representation.structure.themes, { structure: b.data.source.data }, oldParams);
|
||||
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, newParams);
|
||||
b.data.repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: a.data }, newParams));
|
||||
Theme.releaseDependencies(plugin.representation.structure.themes, { structure: b.data.sourceData }, oldParams);
|
||||
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: data }, newParams);
|
||||
b.data.repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: data }, newParams));
|
||||
|
||||
const props = { ...b.data.repr.props, ...newParams.type.params };
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
dispose({ b, params }, plugin: PluginContext) {
|
||||
if (!b || !params) return;
|
||||
|
||||
const structure = b.data.source.data;
|
||||
const structure = b.data.sourceData;
|
||||
const provider = plugin.representation.structure.registry.get(params.type.name);
|
||||
if (provider.ensureCustomProperties) provider.ensureCustomProperties.detach(structure);
|
||||
Theme.releaseDependencies(plugin.representation.structure.themes, { structure }, params);
|
||||
@@ -191,26 +198,26 @@ const UnwindStructureAssemblyRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const unitTransforms = new StructureUnitTransforms(structure);
|
||||
unwindStructureAssembly(structure, unitTransforms, params.t);
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { unitTransforms },
|
||||
initialState: { unitTransforms: new StructureUnitTransforms(structure) },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Unwind T = ${params.t.toFixed(2)}` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
|
||||
const unitTransforms = b.data.state.unitTransforms!;
|
||||
unwindStructureAssembly(structure, unitTransforms, newParams.t);
|
||||
b.label = `Unwind T = ${newParams.t.toFixed(2)}`;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
});
|
||||
@@ -228,26 +235,26 @@ const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const unitTransforms = new StructureUnitTransforms(structure.root);
|
||||
explodeStructure(structure, unitTransforms, params.t);
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { unitTransforms },
|
||||
initialState: { unitTransforms: new StructureUnitTransforms(structure.root) },
|
||||
info: structure.root,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Explode T = ${params.t.toFixed(2)}` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
if (b.data.info !== structure.root) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
|
||||
const unitTransforms = b.data.state.unitTransforms!;
|
||||
explodeStructure(structure.root, unitTransforms, newParams.t);
|
||||
b.label = `Explode T = ${newParams.t.toFixed(2)}`;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
});
|
||||
@@ -276,28 +283,28 @@ const OverpaintStructureRepresentation3DFromScript = PluginStateTransform.BuiltI
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const overpaint = Overpaint.ofScript(params.layers, structure);
|
||||
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { overpaint },
|
||||
initialState: { overpaint: Overpaint.Empty },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Overpaint (${overpaint.layers.length} Layers)` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const oldStructure = b.data.info as Structure;
|
||||
const newStructure = a.data.source.data;
|
||||
const newStructure = a.data.sourceData;
|
||||
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldOverpaint = b.data.state.overpaint!;
|
||||
const newOverpaint = Overpaint.ofScript(newParams.layers, newStructure);
|
||||
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
b.data.state.overpaint = newOverpaint;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
b.label = `Overpaint (${newOverpaint.layers.length} Layers)`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
@@ -328,28 +335,28 @@ const OverpaintStructureRepresentation3DFromBundle = PluginStateTransform.BuiltI
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const overpaint = Overpaint.ofBundle(params.layers, structure);
|
||||
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { overpaint },
|
||||
initialState: { overpaint: Overpaint.Empty },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Overpaint (${overpaint.layers.length} Layers)` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const oldStructure = b.data.info as Structure;
|
||||
const newStructure = a.data.source.data;
|
||||
const newStructure = a.data.sourceData;
|
||||
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldOverpaint = b.data.state.overpaint!;
|
||||
const newOverpaint = Overpaint.ofBundle(newParams.layers, newStructure);
|
||||
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
b.data.state.overpaint = newOverpaint;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
b.label = `Overpaint (${newOverpaint.layers.length} Layers)`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
@@ -377,27 +384,27 @@ const TransparencyStructureRepresentation3DFromScript = PluginStateTransform.Bui
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const transparency = Transparency.ofScript(params.layers, structure);
|
||||
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { transparency },
|
||||
initialState: { transparency: Transparency.Empty },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Transparency (${transparency.layers.length} Layers)` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldTransparency = b.data.state.transparency!;
|
||||
const newTransparency = Transparency.ofScript(newParams.layers, structure);
|
||||
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
b.data.state.transparency = newTransparency;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
b.label = `Transparency (${newTransparency.layers.length} Layers)`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
@@ -426,27 +433,27 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const transparency = Transparency.ofBundle(params.layers, structure);
|
||||
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { transparency },
|
||||
initialState: { transparency: Transparency.Empty },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Transparency (${transparency.layers.length} Layers)` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldTransparency = b.data.state.transparency!;
|
||||
const newTransparency = Transparency.ofBundle(newParams.layers, structure);
|
||||
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
b.data.state.transparency = newTransparency;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
b.label = `Transparency (${newTransparency.layers.length} Layers)`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
@@ -474,27 +481,27 @@ const ClippingStructureRepresentation3DFromScript = PluginStateTransform.BuiltIn
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const clipping = Clipping.ofScript(params.layers, structure);
|
||||
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { clipping },
|
||||
initialState: { clipping: Clipping.Empty },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Clipping (${clipping.layers.length} Layers)` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldClipping = b.data.state.clipping!;
|
||||
const newClipping = Clipping.ofScript(newParams.layers, structure);
|
||||
if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
b.data.state.clipping = newClipping;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
b.label = `Clipping (${newClipping.layers.length} Layers)`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
@@ -523,27 +530,27 @@ const ClippingStructureRepresentation3DFromBundle = PluginStateTransform.BuiltIn
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const clipping = Clipping.ofBundle(params.layers, structure);
|
||||
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { clipping },
|
||||
initialState: { clipping: Clipping.Empty },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Clipping (${clipping.layers.length} Layers)` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldClipping = b.data.state.clipping!;
|
||||
const newClipping = Clipping.ofBundle(newParams.layers, structure);
|
||||
if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
b.data.state.clipping = newClipping;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
b.label = `Clipping (${newClipping.layers.length} Layers)`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
@@ -646,7 +653,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
|
||||
const props = params.type.params || {};
|
||||
await repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
return new SO.Volume.Representation3D({ repr, source: a }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
|
||||
return new SO.Volume.Representation3D({ repr, sourceData: a.data }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
@@ -659,6 +666,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams.type.params };
|
||||
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, newParams));
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
b.description = VolumeRepresentation3DHelpers.getDescription(props);
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
@@ -686,13 +694,14 @@ const ShapeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...PD.getDefaultValues(a.data.params), ...params };
|
||||
const repr = ShapeRepresentation(a.data.getShape, a.data.geometryUtils);
|
||||
await repr.createOrUpdate(props, a.data.data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: a.data.label });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: a.data }, { label: a.data.label });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
return Task.create('Shape Representation', async ctx => {
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
await b.data.repr.createOrUpdate(props, a.data.data).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
}
|
||||
@@ -720,7 +729,7 @@ const ModelUnitcell3D = PluginStateTransform.BuiltIn({
|
||||
const data = getUnitcellData(a.data, symmetry, params);
|
||||
const repr = UnitcellRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => UnitcellParams);
|
||||
await repr.createOrUpdate(params, data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Unit Cell`, description: symmetry.spacegroup.name });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Unit Cell`, description: symmetry.spacegroup.name });
|
||||
});
|
||||
},
|
||||
update({ a, b, newParams }) {
|
||||
@@ -730,7 +739,42 @@ const ModelUnitcell3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
const data = getUnitcellData(a.data, symmetry, props);
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
b.data.sourceData = data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export { StructureBoundingBox3D };
|
||||
type StructureBoundingBox3D = typeof StructureBoundingBox3D
|
||||
const StructureBoundingBox3D = PluginStateTransform.BuiltIn({
|
||||
name: 'structure-bounding-box-3d',
|
||||
display: 'Bounding Box',
|
||||
from: SO.Molecule.Structure,
|
||||
to: SO.Shape.Representation3D,
|
||||
params: {
|
||||
radius: PD.Numeric(0.05, { min: 0.01, max: 4, step: 0.01 }, { isEssential: true }),
|
||||
color: PD.Color(ColorNames.red, { isEssential: true }),
|
||||
...Mesh.Params,
|
||||
}
|
||||
})({
|
||||
canAutoUpdate() {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Bounding Box', async ctx => {
|
||||
const repr = ShapeRepresentation((_, data: { box: Box3D, radius: number, color: Color }, __, shape) => {
|
||||
const mesh = getBoxMesh(data.box, data.radius, shape?.geometry);
|
||||
return Shape.create('Bouding Box', data, mesh, () => data.color, () => 1, () => 'Bounding Box');
|
||||
}, Mesh.Utils);
|
||||
await repr.createOrUpdate(params, { box: a.data.boundary.box, radius: params.radius, color: params.color }).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: a.data }, { label: `Bounding Box` });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
return Task.create('Bounding Box', async ctx => {
|
||||
await b.data.repr.createOrUpdate(newParams, { box: a.data.boundary.box, radius: newParams.radius, color: newParams.color }).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
}
|
||||
@@ -755,7 +799,7 @@ const StructureSelectionsDistance3D = PluginStateTransform.BuiltIn({
|
||||
const data = getDistanceDataFromStructureSelections(a.data);
|
||||
const repr = DistanceRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => DistanceParams);
|
||||
await repr.createOrUpdate(params, data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Distance` });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Distance` });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
@@ -763,7 +807,7 @@ const StructureSelectionsDistance3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
const data = getDistanceDataFromStructureSelections(a.data);
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
b.data.sourceData = data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
@@ -788,7 +832,7 @@ const StructureSelectionsAngle3D = PluginStateTransform.BuiltIn({
|
||||
const data = getAngleDataFromStructureSelections(a.data);
|
||||
const repr = AngleRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => AngleParams);
|
||||
await repr.createOrUpdate(params, data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Angle` });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Angle` });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
@@ -796,7 +840,7 @@ const StructureSelectionsAngle3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
const data = getAngleDataFromStructureSelections(a.data);
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
b.data.sourceData = data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
@@ -821,7 +865,7 @@ const StructureSelectionsDihedral3D = PluginStateTransform.BuiltIn({
|
||||
const data = getDihedralDataFromStructureSelections(a.data);
|
||||
const repr = DihedralRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => DihedralParams);
|
||||
await repr.createOrUpdate(params, data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Dihedral` });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Dihedral` });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
@@ -829,7 +873,7 @@ const StructureSelectionsDihedral3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
const data = getDihedralDataFromStructureSelections(a.data);
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
b.data.sourceData = data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
@@ -854,7 +898,7 @@ const StructureSelectionsLabel3D = PluginStateTransform.BuiltIn({
|
||||
const data = getLabelDataFromStructureSelections(a.data);
|
||||
const repr = LabelRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => LabelParams);
|
||||
await repr.createOrUpdate(params, data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Label` });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Label` });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
@@ -862,7 +906,7 @@ const StructureSelectionsLabel3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
const data = getLabelDataFromStructureSelections(a.data);
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
b.data.sourceData = data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
@@ -887,7 +931,7 @@ const StructureSelectionsOrientation3D = PluginStateTransform.BuiltIn({
|
||||
const data = getOrientationDataFromStructureSelections(a.data);
|
||||
const repr = OrientationRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => OrientationParams);
|
||||
await repr.createOrUpdate(params, data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Orientation` });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Orientation` });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
@@ -895,7 +939,7 @@ const StructureSelectionsOrientation3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
const data = getOrientationDataFromStructureSelections(a.data);
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
b.data.sourceData = data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
|
||||
69
src/mol-plugin-state/transforms/shape.ts
Normal file
69
src/mol-plugin-state/transforms/shape.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { BoxCage } from '../../mol-geo/primitive/box';
|
||||
import { Box3D, Sphere3D } from '../../mol-math/geometry';
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../objects';
|
||||
|
||||
export { BoxShape3D };
|
||||
type BoxShape3D = typeof BoxShape3D
|
||||
const BoxShape3D = PluginStateTransform.BuiltIn({
|
||||
name: 'box-shape-3d',
|
||||
display: 'Box Shape',
|
||||
from: SO.Root,
|
||||
to: SO.Shape.Provider,
|
||||
params: {
|
||||
bottomLeft: PD.Vec3(Vec3()),
|
||||
topRight: PD.Vec3(Vec3.create(1, 1, 1)),
|
||||
radius: PD.Numeric(0.15, { min: 0.01, max: 4, step: 0.01 }),
|
||||
color: PD.Color(ColorNames.red)
|
||||
}
|
||||
})({
|
||||
canAutoUpdate() {
|
||||
return true;
|
||||
},
|
||||
apply({ params }) {
|
||||
return Task.create('Shape Representation', async ctx => {
|
||||
return new SO.Shape.Provider({
|
||||
label: 'Box',
|
||||
data: params,
|
||||
params: Mesh.Params,
|
||||
getShape: (_, data: typeof params) => {
|
||||
const mesh = getBoxMesh(Box3D.create(params.bottomLeft, params.topRight), params.radius);
|
||||
return Shape.create('Box', data, mesh, () => data.color, () => 1, () => 'Box');
|
||||
},
|
||||
geometryUtils: Mesh.Utils
|
||||
}, { label: 'Box' });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export function getBoxMesh(box: Box3D, radius: number, oldMesh?: Mesh) {
|
||||
const diag = Vec3.sub(Vec3(), box.max, box.min);
|
||||
const translateUnit = Mat4.fromTranslation(Mat4(), Vec3.create(0.5, 0.5, 0.5));
|
||||
const scale = Mat4.fromScaling(Mat4(), diag);
|
||||
const translate = Mat4.fromTranslation(Mat4(), box.min);
|
||||
const transform = Mat4.mul3(Mat4(), translate, scale, translateUnit);
|
||||
|
||||
// TODO: optimize?
|
||||
const state = MeshBuilder.createState(256, 128, oldMesh);
|
||||
state.currentGroup = 1;
|
||||
MeshBuilder.addCage(state, transform, BoxCage(), radius, 2, 20);
|
||||
const mesh = MeshBuilder.getMesh(state);
|
||||
|
||||
const center = Vec3.scaleAndAdd(Vec3(), box.min, diag, 0.5);
|
||||
const sphereRadius = Vec3.distance(box.min, center);
|
||||
mesh.setBoundingSphere(Sphere3D.create(center, sphereRadius));
|
||||
|
||||
return mesh;
|
||||
}
|
||||
@@ -7,15 +7,15 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { PluginContext } from '../mol-plugin/context';
|
||||
import { PluginUIContext } from './context';
|
||||
import { Button, ColorAccent } from './controls/common';
|
||||
import { Icon, ArrowRightSvg, ArrowDropDownSvg } from './controls/icons';
|
||||
|
||||
export const PluginReactContext = React.createContext(void 0 as any as PluginContext);
|
||||
export const PluginReactContext = React.createContext(void 0 as any as PluginUIContext);
|
||||
|
||||
export abstract class PluginUIComponent<P = {}, S = {}, SS = {}> extends React.Component<P, S, SS> {
|
||||
static contextType = PluginReactContext;
|
||||
readonly plugin: PluginContext;
|
||||
readonly plugin: PluginUIContext;
|
||||
|
||||
private subs: Subscription[] | undefined = void 0;
|
||||
|
||||
@@ -33,7 +33,7 @@ export abstract class PluginUIComponent<P = {}, S = {}, SS = {}> extends React.C
|
||||
protected init?(): void;
|
||||
|
||||
constructor(props: P, context?: any) {
|
||||
super(props, context);
|
||||
super(props);
|
||||
this.plugin = context;
|
||||
if (this.init) this.init();
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export abstract class PluginUIComponent<P = {}, S = {}, SS = {}> extends React.C
|
||||
|
||||
export abstract class PurePluginUIComponent<P = {}, S = {}, SS = {}> extends React.PureComponent<P, S, SS> {
|
||||
static contextType = PluginReactContext;
|
||||
readonly plugin: PluginContext;
|
||||
readonly plugin: PluginUIContext;
|
||||
|
||||
private subs: Subscription[] | undefined = void 0;
|
||||
|
||||
|
||||
33
src/mol-plugin-ui/context.ts
Normal file
33
src/mol-plugin-ui/context.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
|
||||
import { PluginContext } from '../mol-plugin/context';
|
||||
import { PluginUISpec } from './spec';
|
||||
import { StateTransformParameters } from './state/common';
|
||||
|
||||
export class PluginUIContext extends PluginContext {
|
||||
readonly customParamEditors = new Map<string, StateTransformParameters.Class>();
|
||||
|
||||
private initCustomParamEditors() {
|
||||
if (!this.spec.customParamEditors) return;
|
||||
|
||||
for (const [t, e] of this.spec.customParamEditors) {
|
||||
this.customParamEditors.set(t.id, e);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(options?: { doNotForceWebGLContextLoss?: boolean }) {
|
||||
super.dispose(options);
|
||||
this.layout.dispose();
|
||||
}
|
||||
|
||||
constructor(public spec: PluginUISpec) {
|
||||
super(spec);
|
||||
|
||||
this.initCustomParamEditors();
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorListName, ColorListOptions, ColorListOptionsScale, ColorListOptionsSet, getColorListFromName } from '../../mol-util/color/lists';
|
||||
import { Legend as LegendData } from '../../mol-util/legend';
|
||||
@@ -26,6 +25,7 @@ import { LineGraphComponent } from './line-graph/line-graph-component';
|
||||
import { Slider, Slider2 } from './slider';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { ColorListEntry } from '../../mol-util/color/color';
|
||||
import { PluginUIContext } from '../context';
|
||||
|
||||
export type ParameterControlsCategoryFilter = string | null | (string | null)[]
|
||||
|
||||
@@ -104,7 +104,7 @@ export class ParameterControls<P extends PD.Params> extends React.PureComponent<
|
||||
}
|
||||
}
|
||||
|
||||
export class ParameterMappingControl<S, T> extends PluginUIComponent<{ mapping: ParamMapping<S, T, PluginContext> }> {
|
||||
export class ParameterMappingControl<S, T> extends PluginUIComponent<{ mapping: ParamMapping<S, T, PluginUIContext> }> {
|
||||
setSettings = (p: { param: PD.Base<any>, name: string, value: any }, old: any) => {
|
||||
const values = { ...old, [p.name]: p.value };
|
||||
const t = this.props.mapping.update(values, this.plugin);
|
||||
@@ -240,8 +240,8 @@ function renderSimple(options: { props: ParamProps<any>, state: { showHelp: bool
|
||||
const help = props.param.help
|
||||
? props.param.help(props.value)
|
||||
: { description: props.param.description, legend: props.param.legend };
|
||||
const desc = props.param.description;
|
||||
const hasHelp = help.description || help.legend;
|
||||
const desc = label + (hasHelp ? '. Click for help.' : '');
|
||||
return <>
|
||||
<ControlRow
|
||||
className={className}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { StateTransformParameters } from '../state/common';
|
||||
import * as React from 'react';
|
||||
import { VolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/behavior';
|
||||
import { ExpandableControlRow, IconButton } from '../controls/common';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
@@ -7,20 +7,20 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Plugin } from '../mol-plugin-ui/plugin';
|
||||
import { PluginContext } from './context';
|
||||
import { DefaultPluginSpec, PluginSpec } from './spec';
|
||||
import { Plugin } from './plugin';
|
||||
import { PluginUIContext } from './context';
|
||||
import { DefaultPluginUISpec, PluginUISpec } from './spec';
|
||||
|
||||
export function createPlugin(target: HTMLElement, spec?: PluginSpec): PluginContext {
|
||||
const ctx = new PluginContext(spec || DefaultPluginSpec());
|
||||
export function createPlugin(target: HTMLElement, spec?: PluginUISpec): PluginUIContext {
|
||||
const ctx = new PluginUIContext(spec || DefaultPluginUISpec());
|
||||
ctx.init();
|
||||
ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/** Returns the instance of the plugin after all behaviors have been initialized */
|
||||
export async function createPluginAsync(target: HTMLElement, spec?: PluginSpec) {
|
||||
const ctx = new PluginContext(spec || DefaultPluginSpec());
|
||||
export async function createPluginAsync(target: HTMLElement, spec?: PluginUISpec) {
|
||||
const ctx = new PluginUIContext(spec || DefaultPluginUISpec());
|
||||
const init = ctx.init();
|
||||
ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target);
|
||||
await init;
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
import { List } from 'immutable';
|
||||
import * as React from 'react';
|
||||
import { PluginContext } from '../mol-plugin/context';
|
||||
import { formatTime } from '../mol-util';
|
||||
import { LogEntry } from '../mol-util/log-entry';
|
||||
import { PluginReactContext, PluginUIComponent } from './base';
|
||||
@@ -18,8 +17,9 @@ import { BackgroundTaskProgress, OverlayTaskProgress } from './task';
|
||||
import { Toasts } from './toast';
|
||||
import { Viewport, ViewportControls } from './viewport';
|
||||
import { PluginCommands } from '../mol-plugin/commands';
|
||||
import { PluginUIContext } from './context';
|
||||
|
||||
export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
|
||||
export class Plugin extends React.Component<{ plugin: PluginUIContext }, {}> {
|
||||
region(kind: 'left' | 'right' | 'bottom' | 'main', element: JSX.Element) {
|
||||
return <div className={`msp-layout-region msp-layout-${kind}`}>
|
||||
<div className='msp-layout-static'>
|
||||
@@ -35,7 +35,7 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
export class PluginContextContainer extends React.Component<{ plugin: PluginContext }> {
|
||||
export class PluginContextContainer extends React.Component<{ plugin: PluginUIContext }> {
|
||||
render() {
|
||||
return <PluginReactContext.Provider value={this.props.plugin}>
|
||||
<div className='msp-plugin'>
|
||||
@@ -62,7 +62,7 @@ class Layout extends PluginUIComponent {
|
||||
|
||||
get layoutVisibilityClassName() {
|
||||
const layout = this.plugin.layout.state;
|
||||
const controls = (this.plugin.spec.layout && this.plugin.spec.layout.controls) || {};
|
||||
const controls = this.plugin.spec.components?.controls ?? {};
|
||||
|
||||
const classList: string[] = [];
|
||||
if (controls.top === 'none' || !layout.showControls || layout.regionState.top === 'hidden') {
|
||||
@@ -127,7 +127,7 @@ class Layout extends PluginUIComponent {
|
||||
|
||||
render() {
|
||||
const layout = this.plugin.layout.state;
|
||||
const controls = this.plugin.spec.layout?.controls || {};
|
||||
const controls = this.plugin.spec.components?.controls || {};
|
||||
const viewport = this.plugin.spec.components?.viewport?.view || DefaultViewport;
|
||||
|
||||
return <div className='msp-plugin' onDrop={this.onDrop} onDragOver={this.onDragOver}>
|
||||
@@ -149,7 +149,6 @@ export class ControlsWrapper extends PluginUIComponent {
|
||||
render() {
|
||||
const StructureTools = this.plugin.spec.components?.structureTools || DefaultStructureTools;
|
||||
return <div className='msp-scrollable-container'>
|
||||
{/* <CurrentObject /> */}
|
||||
<StructureTools />
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -383,10 +383,10 @@ export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceView
|
||||
|
||||
if (values.mode === 'single') return elem;
|
||||
|
||||
return <>
|
||||
return <React.Fragment key={i}>
|
||||
<div className='msp-sequence-chain-label'>{s.label}</div>
|
||||
{elem}
|
||||
</>;
|
||||
</React.Fragment>;
|
||||
})}
|
||||
</NonEmptySequenceWrapper>
|
||||
</div>;
|
||||
|
||||
@@ -122,12 +122,11 @@ function applyMarkerAtomic(e: StructureElement.Loci.Element, action: MarkerActio
|
||||
const { index: residueIndex } = model.atomicHierarchy.residueAtomSegments;
|
||||
const { label_seq_id } = model.atomicHierarchy.residues;
|
||||
|
||||
let changed = false;
|
||||
OrderedSet.forEachSegment(e.indices, i => residueIndex[elements[i]], rI => {
|
||||
const seqId = label_seq_id.value(rI);
|
||||
changed = applyMarkerActionAtPosition(markerArray, index(seqId), action) || changed;
|
||||
applyMarkerActionAtPosition(markerArray, index(seqId), action);
|
||||
});
|
||||
return changed;
|
||||
return true;
|
||||
}
|
||||
|
||||
function applyMarkerCoarse(e: StructureElement.Loci.Element, action: MarkerAction, markerArray: Uint8Array, index: (seqId: number) => number) {
|
||||
@@ -135,12 +134,11 @@ function applyMarkerCoarse(e: StructureElement.Loci.Element, action: MarkerActio
|
||||
const begin = Unit.isSpheres(e.unit) ? model.coarseHierarchy.spheres.seq_id_begin : model.coarseHierarchy.gaussians.seq_id_begin;
|
||||
const end = Unit.isSpheres(e.unit) ? model.coarseHierarchy.spheres.seq_id_end : model.coarseHierarchy.gaussians.seq_id_end;
|
||||
|
||||
let changed = false;
|
||||
OrderedSet.forEach(e.indices, i => {
|
||||
const eI = elements[i];
|
||||
for (let s = index(begin.value(eI)), e = index(end.value(eI)); s <= e; s++) {
|
||||
changed = applyMarkerActionAtPosition(markerArray, s, action) || changed;
|
||||
applyMarkerActionAtPosition(markerArray, s, action);
|
||||
}
|
||||
});
|
||||
return changed;
|
||||
return true;
|
||||
}
|
||||
45
src/mol-plugin-ui/spec.ts
Normal file
45
src/mol-plugin-ui/spec.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
|
||||
import { StateTransformParameters } from '../mol-plugin-ui/state/common';
|
||||
import { CreateVolumeStreamingBehavior } from '../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { DefaultPluginSpec, PluginSpec } from '../mol-plugin/spec';
|
||||
import { StateAction, StateTransformer } from '../mol-state';
|
||||
import { VolumeStreamingCustomControls } from './custom/volume';
|
||||
|
||||
export { PluginUISpec };
|
||||
|
||||
interface PluginUISpec extends PluginSpec {
|
||||
customParamEditors?: [StateAction | StateTransformer, StateTransformParameters.Class][],
|
||||
components?: {
|
||||
controls?: PluginUISpec.LayoutControls
|
||||
remoteState?: 'none' | 'default',
|
||||
structureTools?: React.ComponentClass,
|
||||
viewport?: {
|
||||
view?: React.ComponentClass,
|
||||
controls?: React.ComponentClass
|
||||
},
|
||||
hideTaskOverlay?: boolean
|
||||
},
|
||||
}
|
||||
|
||||
namespace PluginUISpec {
|
||||
export interface LayoutControls {
|
||||
top?: React.ComponentClass | 'none',
|
||||
left?: React.ComponentClass | 'none',
|
||||
right?: React.ComponentClass | 'none',
|
||||
bottom?: React.ComponentClass | 'none'
|
||||
}
|
||||
}
|
||||
|
||||
export const DefaultPluginUISpec = (): PluginUISpec => ({
|
||||
...DefaultPluginSpec(),
|
||||
customParamEditors: [
|
||||
[CreateVolumeStreamingBehavior, VolumeStreamingCustomControls]
|
||||
],
|
||||
});
|
||||
@@ -4,7 +4,6 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { State } from '../../mol-state';
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { Icon, CodeSvg } from '../controls/icons';
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { ParameterControls, ParamOnChange } from '../controls/parameters';
|
||||
import { Button } from '../controls/common';
|
||||
|
||||
@@ -8,7 +8,6 @@ import { State, StateTransform, StateTransformer } from '../../mol-state';
|
||||
import { memoizeLatest } from '../../mol-util/memoize';
|
||||
import { StateTransformParameters, TransformControlBase } from './common';
|
||||
import { Observable } from 'rxjs';
|
||||
import * as React from 'react';
|
||||
import { PluginUIComponent } from '../base';
|
||||
|
||||
export { UpdateTransformControl, TransformUpdaterControl };
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user