mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 14:04:36 +08:00
Compare commits
50 Commits
v2.0.0-dev
...
v2.0.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
acf13fa46f |
15
README.md
15
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,9 +165,6 @@ To get syntax highlighting for shader and graphql files add the following to Vis
|
||||
## Contributing
|
||||
Just open an issue or make a pull request. All contributions are welcome.
|
||||
|
||||
## Roadmap
|
||||
Continually develop this prototype project. As individual modules become stable, make them into standalone libraries.
|
||||
|
||||
## Funding
|
||||
Funding sources include but are not limited to:
|
||||
* [RCSB PDB](https://www.rcsb.org) funding by a grant [DBI-1338415; PI: SK Burley] from the NSF, the NIH, and the US DoE
|
||||
|
||||
6222
package-lock.json
generated
6222
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.0.0-dev.6",
|
||||
"version": "2.0.0-dev.11",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
|
||||
@@ -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 };
|
||||
@@ -49,14 +49,13 @@
|
||||
if (debugMode) molstar.setDebugMode(debugMode, debugMode);
|
||||
|
||||
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,25 +4,25 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { debounceTime, skip } from 'rxjs/operators';
|
||||
import { AlphaOrbital, Basis } from '../../extensions/alpha-orbitals/data-model';
|
||||
import { SphericalBasisOrder } from '../../extensions/alpha-orbitals/spherical-functions';
|
||||
import { BasisAndOrbitals, CreateOrbitalDensityVolume, CreateOrbitalRepresentation3D, CreateOrbitalVolume, StaticBasisAndOrbitals } from '../../extensions/alpha-orbitals/transforms';
|
||||
import { createPluginAsync } from '../../mol-plugin';
|
||||
import { DefaultPluginSpec } from '../../mol-plugin/spec';
|
||||
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { createPluginAsync } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateObjectSelector, StateTransformer } from '../../mol-state';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
import { mountControls } from './controls';
|
||||
import { DemoMoleculeSDF, DemoOrbitals } from './example-data';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { debounceTime, skip } from 'rxjs/operators';
|
||||
import './index.html';
|
||||
import { Basis, AlphaOrbital } from '../../extensions/alpha-orbitals/data-model';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
interface DemoInput {
|
||||
@@ -50,10 +50,10 @@ type Selectors = {
|
||||
}
|
||||
|
||||
export class AlphaOrbitalsExample {
|
||||
plugin: PluginContext;
|
||||
plugin: PluginUIContext;
|
||||
|
||||
async init(target: string | HTMLElement) {
|
||||
const defaultSpec = DefaultPluginSpec();
|
||||
const defaultSpec = DefaultPluginUISpec();
|
||||
this.plugin = await createPluginAsync(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...defaultSpec,
|
||||
layout: {
|
||||
@@ -61,15 +61,13 @@ export class AlphaOrbitalsExample {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' },
|
||||
},
|
||||
components: {
|
||||
viewport: {
|
||||
canvas3d: {
|
||||
camera: {
|
||||
helper: { axes: { name: 'off', params: { } } }
|
||||
}
|
||||
}
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' },
|
||||
},
|
||||
canvas3d: {
|
||||
camera: {
|
||||
helper: { axes: { name: 'off', params: { } } }
|
||||
}
|
||||
},
|
||||
config: [
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
*/
|
||||
|
||||
import { Canvas3DProps } from '../../mol-canvas3d/canvas3d';
|
||||
import { createPlugin } from '../../mol-plugin';
|
||||
import { DefaultPluginSpec } from '../../mol-plugin/spec';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { createPlugin } from '../../mol-plugin-ui';
|
||||
import { PluginUIContext } from '../../mol-plugin-ui/context';
|
||||
import { DefaultPluginUISpec } from '../../mol-plugin-ui/spec';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import './index.html';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import './index.html';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }
|
||||
@@ -62,7 +62,7 @@ const Canvas3DPresets = {
|
||||
type Canvas3DPreset = keyof typeof Canvas3DPresets
|
||||
|
||||
class LightingDemo {
|
||||
plugin: PluginContext;
|
||||
plugin: PluginUIContext;
|
||||
|
||||
private radius = 5;
|
||||
private bias = 1.1;
|
||||
@@ -70,12 +70,14 @@ class LightingDemo {
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec(),
|
||||
...DefaultPluginUISpec(),
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
},
|
||||
components: {
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
],
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -192,6 +192,15 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
|
||||
|
||||
//
|
||||
|
||||
/**
|
||||
* 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, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
// console.time('calcActiveVoxels');
|
||||
const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -32,7 +32,7 @@ function handleMolecule(tokenizer: Tokenizer): XyzFile['molecules'][number] {
|
||||
const type_symbol = new Array<string>(count);
|
||||
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const line = Tokenizer.readLine(tokenizer);
|
||||
const line = Tokenizer.readLineTrim(tokenizer);
|
||||
const fields = line.split(/\s+/g);
|
||||
type_symbol[i] = fields[0];
|
||||
x[i] = +fields[1];
|
||||
|
||||
@@ -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 };
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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') {
|
||||
|
||||
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;
|
||||
@@ -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);
|
||||
@@ -435,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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
@@ -50,7 +50,6 @@ class Structure {
|
||||
interUnitBonds?: InterUnitBonds,
|
||||
unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
|
||||
unitSymmetryGroupsIndexMap?: IntMap<number>,
|
||||
unitsSortedByVolume?: ReadonlyArray<Unit>;
|
||||
carbohydrates?: Carbohydrates,
|
||||
models?: ReadonlyArray<Model>,
|
||||
model?: Model,
|
||||
@@ -69,6 +68,7 @@ class Structure {
|
||||
uniqueElementCount: number,
|
||||
atomicResidueCount: number,
|
||||
polymerResidueCount: number,
|
||||
polymerGapCount: number,
|
||||
polymerUnitCount: number,
|
||||
coordinateSystem: SymmetryOperator,
|
||||
label: string,
|
||||
@@ -82,6 +82,7 @@ class Structure {
|
||||
uniqueElementCount: -1,
|
||||
atomicResidueCount: -1,
|
||||
polymerResidueCount: -1,
|
||||
polymerGapCount: -1,
|
||||
polymerUnitCount: -1,
|
||||
coordinateSystem: SymmetryOperator.Default,
|
||||
label: ''
|
||||
@@ -136,6 +137,14 @@ class Structure {
|
||||
return this._props.polymerResidueCount;
|
||||
}
|
||||
|
||||
/** Count of all polymer gaps in the structure */
|
||||
get polymerGapCount() {
|
||||
if (this._props.polymerGapCount === -1) {
|
||||
this._props.polymerGapCount = getPolymerGapCount(this);
|
||||
}
|
||||
return this._props.polymerGapCount;
|
||||
}
|
||||
|
||||
get polymerUnitCount() {
|
||||
if (this._props.polymerUnitCount === -1) {
|
||||
this._props.polymerUnitCount = getPolymerUnitCount(this);
|
||||
@@ -256,13 +265,6 @@ class Structure {
|
||||
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;
|
||||
}
|
||||
|
||||
get carbohydrates(): Carbohydrates {
|
||||
if (this._props.carbohydrates) return this._props.carbohydrates;
|
||||
this._props.carbohydrates = computeCarbohydrates(this);
|
||||
@@ -429,24 +431,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;
|
||||
@@ -568,6 +552,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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -23,6 +23,7 @@ import { OperatorNameColorThemeProvider } from '../../../mol-theme/color/operato
|
||||
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; }
|
||||
@@ -111,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:
|
||||
@@ -118,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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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(),
|
||||
@@ -645,6 +657,7 @@ export const StructureSelectionQueries = {
|
||||
ring,
|
||||
aromaticRing,
|
||||
surroundings,
|
||||
surroundingLigands,
|
||||
complement,
|
||||
covalentlyBonded,
|
||||
covalentlyOrMetallicBonded,
|
||||
|
||||
@@ -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
|
||||
@@ -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 };
|
||||
@@ -736,6 +740,41 @@ const ModelUnitcell3D = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
});
|
||||
|
||||
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, source: a }, { 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.source = a;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export { StructureSelectionsDistance3D };
|
||||
type StructureSelectionsDistance3D = typeof StructureSelectionsDistance3D
|
||||
const StructureSelectionsDistance3D = PluginStateTransform.BuiltIn({
|
||||
|
||||
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);
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
],
|
||||
});
|
||||
@@ -195,12 +195,27 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
|
||||
get presetActions() {
|
||||
const actions: ActionMenu.Item[] = [];
|
||||
const { trajectories } = this.plugin.managers.structure.hierarchy.selection;
|
||||
if (trajectories.length !== 1) return actions;
|
||||
if (trajectories.length === 0) return actions;
|
||||
|
||||
let providers = this.plugin.builders.structure.hierarchy.getPresets(trajectories[0].cell.obj);
|
||||
|
||||
if (trajectories.length > 1) {
|
||||
const providerSet = new Set(providers);
|
||||
for (let i = 1; i < trajectories.length; i++) {
|
||||
const providers = this.plugin.builders.structure.hierarchy.getPresets(trajectories[i].cell.obj);
|
||||
const current = new Set(providers);
|
||||
|
||||
for (const p of providers) {
|
||||
if (!current.has(p)) providerSet.delete(p);
|
||||
}
|
||||
}
|
||||
providers = providers.filter(p => providerSet.has(p));
|
||||
}
|
||||
|
||||
const providers = this.plugin.builders.structure.hierarchy.getPresets(trajectories[0].cell.obj);
|
||||
for (const p of providers) {
|
||||
actions.push(ActionMenu.Item(p.display.name, p, { description: p.display.description }));
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ import { produce } from 'immer';
|
||||
import * as React from 'react';
|
||||
import { Canvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateTransform } from '../../mol-state';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ParamMapping } from '../../mol-util/param-mapping';
|
||||
import { Mutable } from '../../mol-util/type-helpers';
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { PluginUIContext } from '../context';
|
||||
import { ParameterMappingControl } from '../controls/parameters';
|
||||
import { ViewportHelpContent } from './help';
|
||||
|
||||
@@ -71,9 +71,9 @@ const SimpleSettingsParams = {
|
||||
|
||||
type SimpleSettingsParams = typeof SimpleSettingsParams
|
||||
const SimpleSettingsMapping = ParamMapping({
|
||||
params: (ctx: PluginContext) => {
|
||||
params: (ctx: PluginUIContext) => {
|
||||
const params = PD.clone(SimpleSettingsParams);
|
||||
const controls = ctx.spec.layout?.controls;
|
||||
const controls = ctx.spec.components?.controls;
|
||||
if (controls) {
|
||||
const options: [LayoutOptions, string][] = [];
|
||||
if (controls.top !== 'none') options.push(['sequence', LayoutOptions.sequence]);
|
||||
@@ -83,8 +83,8 @@ const SimpleSettingsMapping = ParamMapping({
|
||||
}
|
||||
return params;
|
||||
},
|
||||
target(ctx: PluginContext) {
|
||||
const c = ctx.spec.layout?.controls;
|
||||
target(ctx: PluginUIContext) {
|
||||
const c = ctx.spec.components?.controls;
|
||||
const r = ctx.layout.state.regionState;
|
||||
const layout: SimpleSettingsParams['layout']['defaultValue'] = [];
|
||||
if (r.top !== 'hidden' && (!c || c.top !== 'none')) layout.push('sequence');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -11,6 +11,8 @@ import { PluginBehavior } from '../behavior';
|
||||
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
|
||||
import { Binding } from '../../../mol-util/binding';
|
||||
import { PluginCommands } from '../../commands';
|
||||
import { CameraHelperAxis, isCameraAxesLoci } from '../../../mol-canvas3d/helper/camera-helper';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
|
||||
const B = ButtonsType;
|
||||
const M = ModifiersKeys;
|
||||
@@ -62,4 +64,67 @@ export const FocusLoci = PluginBehavior.create<FocusLociProps>({
|
||||
},
|
||||
params: () => FocusLociParams,
|
||||
display: { name: 'Camera Focus Loci on Canvas' }
|
||||
});
|
||||
|
||||
export const CameraAxisHelper = PluginBehavior.create<{}>({
|
||||
name: 'camera-axis-helper',
|
||||
category: 'interaction',
|
||||
ctor: class extends PluginBehavior.Handler<{}> {
|
||||
register(): void {
|
||||
|
||||
let lastPlane = CameraHelperAxis.None;
|
||||
let state = 0;
|
||||
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current }) => {
|
||||
if (!this.ctx.canvas3d || !isCameraAxesLoci(current.loci)) return;
|
||||
|
||||
const axis = current.loci.elements[0].groupId;
|
||||
if (axis === CameraHelperAxis.None) {
|
||||
lastPlane = CameraHelperAxis.None;
|
||||
state = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const { camera } = this.ctx.canvas3d;
|
||||
let dir: Vec3, up: Vec3;
|
||||
|
||||
if (axis >= CameraHelperAxis.X && axis <= CameraHelperAxis.Z) {
|
||||
lastPlane = CameraHelperAxis.None;
|
||||
state = 0;
|
||||
|
||||
const d = Vec3.sub(Vec3(), camera.target, camera.position);
|
||||
const c = Vec3.cross(Vec3(), d, camera.up);
|
||||
|
||||
up = Vec3();
|
||||
up[axis - 1] = 1;
|
||||
dir = Vec3.cross(Vec3(), up, c);
|
||||
if (Vec3.magnitude(dir) === 0) dir = d;
|
||||
} else {
|
||||
if (lastPlane === axis) {
|
||||
state = (state + 1) % 2;
|
||||
} else {
|
||||
lastPlane = axis;
|
||||
state = 0;
|
||||
}
|
||||
|
||||
if (axis === CameraHelperAxis.XY) {
|
||||
up = state ? Vec3.unitX : Vec3.unitY;
|
||||
dir = Vec3.negUnitZ;
|
||||
} else if (axis === CameraHelperAxis.XZ) {
|
||||
up = state ? Vec3.unitX : Vec3.unitZ;
|
||||
dir = Vec3.negUnitY;
|
||||
} else {
|
||||
up = state ? Vec3.unitY : Vec3.unitZ;
|
||||
dir = Vec3.negUnitX;
|
||||
}
|
||||
}
|
||||
|
||||
this.ctx.canvas3d.requestCameraReset({
|
||||
snapshot: (scene, camera) => camera.getInvariantFocus(scene.boundingSphereVisible.center, scene.boundingSphereVisible.radius, up, dir)
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
params: () => ({}),
|
||||
display: { name: 'Camera Axis Helper' }
|
||||
});
|
||||
@@ -64,8 +64,8 @@ export namespace VolumeStreaming {
|
||||
};
|
||||
}
|
||||
|
||||
export type EntryParamDefinition = typeof createEntryParams extends (...args: any[]) => (infer T) ? T : never
|
||||
export type EntryParams = EntryParamDefinition extends PD.Params ? PD.Values<EntryParamDefinition> : {}
|
||||
export type EntryParamDefinition = ReturnType<typeof createEntryParams>
|
||||
export type EntryParams = PD.Values<EntryParamDefinition>
|
||||
|
||||
export function createEntryParams(options: { entryData?: VolumeServerInfo.EntryData, defaultView?: ViewTypes, structure?: Structure, channelParams?: DefaultChannelParams }) {
|
||||
const { entryData, defaultView, structure, channelParams = { } } = options;
|
||||
@@ -86,7 +86,7 @@ export namespace VolumeStreaming {
|
||||
bottomLeft: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
|
||||
topRight: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }),
|
||||
}, { description: 'Box around focused element.', isFlat: true }),
|
||||
'cell': PD.Group({}),
|
||||
'cell': PD.Group<{}>({}),
|
||||
// Show selection-box if available and cell otherwise.
|
||||
'auto': PD.Group({
|
||||
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
|
||||
@@ -115,8 +115,8 @@ export namespace VolumeStreaming {
|
||||
|
||||
export type ViewTypes = 'off' | 'box' | 'selection-box' | 'cell' | 'auto'
|
||||
|
||||
export type ParamDefinition = typeof createParams extends (...args: any[]) => (infer T) ? T : never
|
||||
export type Params = ParamDefinition extends PD.Params ? PD.Values<ParamDefinition> : {}
|
||||
export type ParamDefinition = ReturnType<typeof createParams>
|
||||
export type Params = PD.Values<ParamDefinition>
|
||||
|
||||
type ChannelsInfo = { [name in ChannelType]?: { isoValue: Volume.IsoValue, color: Color, wireframe: boolean, opacity: number } }
|
||||
type ChannelsData = { [name in 'EM' | '2FO-FC' | 'FO-FC']?: Volume }
|
||||
|
||||
@@ -8,44 +8,51 @@
|
||||
import produce, { setAutoFreeze } from 'immer';
|
||||
import { List } from 'immutable';
|
||||
import { merge, Subscription } from 'rxjs';
|
||||
import { filter, take } from 'rxjs/operators';
|
||||
import { Canvas3D, Canvas3DContext, DefaultCanvas3DParams } from '../mol-canvas3d/canvas3d';
|
||||
import { resizeCanvas } from '../mol-canvas3d/util';
|
||||
import { Vec2 } from '../mol-math/linear-algebra';
|
||||
import { CustomProperty } from '../mol-model-props/common/custom-property';
|
||||
import { Model, Structure } from '../mol-model/structure';
|
||||
import { DataBuilder } from '../mol-plugin-state/builder/data';
|
||||
import { StructureBuilder } from '../mol-plugin-state/builder/structure';
|
||||
import { DataFormatRegistry } from '../mol-plugin-state/formats/registry';
|
||||
import { StructureSelectionQueryRegistry } from '../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { PluginAnimationManager } from '../mol-plugin-state/manager/animation';
|
||||
import { CameraManager } from '../mol-plugin-state/manager/camera';
|
||||
import { InteractivityManager } from '../mol-plugin-state/manager/interactivity';
|
||||
import { LociLabel, LociLabelManager } from '../mol-plugin-state/manager/loci-label';
|
||||
import { PluginStateSnapshotManager } from '../mol-plugin-state/manager/snapshots';
|
||||
import { StructureComponentManager } from '../mol-plugin-state/manager/structure/component';
|
||||
import { StructureFocusManager } from '../mol-plugin-state/manager/structure/focus';
|
||||
import { StructureHierarchyManager } from '../mol-plugin-state/manager/structure/hierarchy';
|
||||
import { StructureHierarchyRef } from '../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
import { StructureMeasurementManager } from '../mol-plugin-state/manager/structure/measurement';
|
||||
import { StructureSelectionManager } from '../mol-plugin-state/manager/structure/selection';
|
||||
import { PluginUIComponent } from '../mol-plugin-ui/base';
|
||||
import { StateTransformParameters } from '../mol-plugin-ui/state/common';
|
||||
import { VolumeHierarchyManager } from '../mol-plugin-state/manager/volume/hierarchy';
|
||||
import { LeftPanelTabName, PluginLayout } from './layout';
|
||||
import { Representation } from '../mol-repr/representation';
|
||||
import { StructureRepresentationRegistry } from '../mol-repr/structure/registry';
|
||||
import { VolumeRepresentationRegistry } from '../mol-repr/volume/registry';
|
||||
import { StateTransform } from '../mol-state';
|
||||
import { Task, RuntimeContext } from '../mol-task';
|
||||
import { RuntimeContext, Task } from '../mol-task';
|
||||
import { ColorTheme } from '../mol-theme/color';
|
||||
import { SizeTheme } from '../mol-theme/size';
|
||||
import { ThemeRegistryContext } from '../mol-theme/theme';
|
||||
import { AssetManager } from '../mol-util/assets';
|
||||
import { Color } from '../mol-util/color';
|
||||
import { ajaxGet } from '../mol-util/data-source';
|
||||
import { isDebugMode, isProductionMode } from '../mol-util/debug';
|
||||
import { ModifiersKeys } from '../mol-util/input/input-observer';
|
||||
import { LogEntry } from '../mol-util/log-entry';
|
||||
import { objectForEach } from '../mol-util/object';
|
||||
import { RxEventHelper } from '../mol-util/rx-event-helper';
|
||||
import { PluginAnimationLoop } from './animation-loop';
|
||||
import { BuiltInPluginBehaviors } from './behavior';
|
||||
import { PluginBehavior } from './behavior/behavior';
|
||||
import { PluginCommandManager } from './command';
|
||||
import { PluginCommands } from './commands';
|
||||
import { PluginConfig, PluginConfigManager } from './config';
|
||||
import { LeftPanelTabName, PluginLayout } from './layout';
|
||||
import { PluginSpec } from './spec';
|
||||
import { PluginState } from './state';
|
||||
import { SubstructureParentHelper } from './util/substructure-parent-helper';
|
||||
@@ -53,15 +60,6 @@ import { TaskManager } from './util/task-manager';
|
||||
import { PluginToastManager } from './util/toast';
|
||||
import { ViewportScreenshotHelper } from './util/viewport-screenshot';
|
||||
import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version';
|
||||
import { AssetManager } from '../mol-util/assets';
|
||||
import { PluginStateSnapshotManager } from '../mol-plugin-state/manager/snapshots';
|
||||
import { PluginAnimationManager } from '../mol-plugin-state/manager/animation';
|
||||
import { objectForEach } from '../mol-util/object';
|
||||
import { VolumeHierarchyManager } from '../mol-plugin-state/manager/volume/hierarchy';
|
||||
import { filter, take } from 'rxjs/operators';
|
||||
import { Vec2 } from '../mol-math/linear-algebra';
|
||||
import { PluginAnimationLoop } from './animation-loop';
|
||||
import { resizeCanvas } from '../mol-canvas3d/util';
|
||||
|
||||
export class PluginContext {
|
||||
runTask = <T>(task: Task<T>, params?: { useOverlay?: boolean }) => this.managers.task.run(task, params);
|
||||
@@ -71,7 +69,7 @@ export class PluginContext {
|
||||
return object;
|
||||
}
|
||||
|
||||
private subs: Subscription[] = [];
|
||||
protected subs: Subscription[] = [];
|
||||
|
||||
private disposed = false;
|
||||
private ev = RxEventHelper.create();
|
||||
@@ -109,8 +107,8 @@ export class PluginContext {
|
||||
|
||||
readonly canvas3dContext: Canvas3DContext | undefined;
|
||||
readonly canvas3d: Canvas3D | undefined;
|
||||
readonly animationLoop = new PluginAnimationLoop(this);
|
||||
readonly layout = new PluginLayout(this);
|
||||
readonly animationLoop = new PluginAnimationLoop(this);
|
||||
|
||||
readonly representation = {
|
||||
structure: {
|
||||
@@ -176,9 +174,8 @@ export class PluginContext {
|
||||
|
||||
readonly customModelProperties = new CustomProperty.Registry<Model>();
|
||||
readonly customStructureProperties = new CustomProperty.Registry<Structure>();
|
||||
readonly customParamEditors = new Map<string, StateTransformParameters.Class>();
|
||||
|
||||
readonly customStructureControls = new Map<string, { new(): PluginUIComponent<any, any, any> }>();
|
||||
readonly customStructureControls = new Map<string, { new(): any /* constructible react components with <action.customControl /> */ }>();
|
||||
readonly genericRepresentationControls = new Map<string, (selection: StructureHierarchyManager['selection']) => [StructureHierarchyRef[], string]>();
|
||||
|
||||
/**
|
||||
@@ -200,7 +197,7 @@ export class PluginContext {
|
||||
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, enableWboit });
|
||||
(this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
|
||||
this.canvas3dInit.next(true);
|
||||
let props = this.spec.components?.viewport?.canvas3d;
|
||||
let props = this.spec.canvas3d;
|
||||
|
||||
const backgroundColor = Color(0xFCFBF9);
|
||||
if (!props) {
|
||||
@@ -294,7 +291,6 @@ export class PluginContext {
|
||||
this.ev.dispose();
|
||||
this.state.dispose();
|
||||
this.managers.task.dispose();
|
||||
this.layout.dispose();
|
||||
this.helpers.substructureParent.dispose();
|
||||
|
||||
objectForEach(this.managers, m => (m as any)?.dispose?.());
|
||||
@@ -385,12 +381,6 @@ export class PluginContext {
|
||||
}
|
||||
}
|
||||
|
||||
private initDataActions() {
|
||||
for (const a of this.spec.actions) {
|
||||
this.state.data.actions.add(a.action);
|
||||
}
|
||||
}
|
||||
|
||||
private initAnimations() {
|
||||
if (!this.spec.animations) return;
|
||||
for (const anim of this.spec.animations) {
|
||||
@@ -398,11 +388,10 @@ export class PluginContext {
|
||||
}
|
||||
}
|
||||
|
||||
private initCustomParamEditors() {
|
||||
if (!this.spec.customParamEditors) return;
|
||||
|
||||
for (const [t, e] of this.spec.customParamEditors) {
|
||||
this.customParamEditors.set(t.id, e);
|
||||
private initDataActions() {
|
||||
if (!this.spec.actions) return;
|
||||
for (const a of this.spec.actions) {
|
||||
this.state.data.actions.add(a.action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,9 +406,8 @@ export class PluginContext {
|
||||
(this.managers.lociLabels as LociLabelManager) = new LociLabelManager(this);
|
||||
(this.builders.structure as StructureBuilder) = new StructureBuilder(this);
|
||||
|
||||
this.initDataActions();
|
||||
this.initAnimations();
|
||||
this.initCustomParamEditors();
|
||||
this.initDataActions();
|
||||
|
||||
await this.initBehaviors();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
import { StatefulPluginComponent } from '../mol-plugin-state/component';
|
||||
import { PluginContext } from './context';
|
||||
import { PluginCommands } from './commands';
|
||||
import { PluginContext } from './context';
|
||||
|
||||
const regionStateOptions = [
|
||||
['full', 'Full'],
|
||||
|
||||
@@ -1,50 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateTransformer, StateAction } from '../mol-state';
|
||||
import { StateTransformParameters } from '../mol-plugin-ui/state/common';
|
||||
import { PluginLayoutStateProps } from './layout';
|
||||
import { PluginStateAnimation } from '../mol-plugin-state/animation/model';
|
||||
import { PluginConfigItem } from './config';
|
||||
import { PartialCanvas3DProps } from '../mol-canvas3d/canvas3d';
|
||||
import { DataFormatProvider } from '../mol-plugin-state/formats/provider';
|
||||
import { StateActions } from '../mol-plugin-state/actions';
|
||||
import { StateTransforms } from '../mol-plugin-state/transforms';
|
||||
import { VolumeStreamingCustomControls } from '../mol-plugin-ui/custom/volume';
|
||||
import { PluginBehaviors } from './behavior';
|
||||
import { StructureFocusRepresentation } from './behavior/dynamic/selection/structure-focus-representation';
|
||||
import { BoxifyVolumeStreaming, CreateVolumeStreamingBehavior, InitVolumeStreaming } from './behavior/dynamic/volume-streaming/transformers';
|
||||
import { AssignColorVolume } from '../mol-plugin-state/actions/volume';
|
||||
import { AnimateModelIndex } from '../mol-plugin-state/animation/built-in/model-index';
|
||||
import { AnimateAssemblyUnwind } from '../mol-plugin-state/animation/built-in/assembly-unwind';
|
||||
import { AnimateCameraSpin } from '../mol-plugin-state/animation/built-in/camera-spin';
|
||||
import { AnimateModelIndex } from '../mol-plugin-state/animation/built-in/model-index';
|
||||
import { AnimateStateSnapshots } from '../mol-plugin-state/animation/built-in/state-snapshots';
|
||||
import { PluginStateAnimation } from '../mol-plugin-state/animation/model';
|
||||
import { DataFormatProvider } from '../mol-plugin-state/formats/provider';
|
||||
import { StateAction, StateTransformer } from '../mol-state';
|
||||
import { PluginBehaviors } from './behavior';
|
||||
import { StructureFocusRepresentation } from './behavior/dynamic/selection/structure-focus-representation';
|
||||
import { PluginConfigItem } from './config';
|
||||
import { PluginLayoutStateProps } from './layout';
|
||||
import { StateActions } from '../mol-plugin-state/actions';
|
||||
import { AssignColorVolume } from '../mol-plugin-state/actions/volume';
|
||||
import { StateTransforms } from '../mol-plugin-state/transforms';
|
||||
import { BoxifyVolumeStreaming, CreateVolumeStreamingBehavior, InitVolumeStreaming } from '../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
|
||||
export { PluginSpec };
|
||||
|
||||
interface PluginSpec {
|
||||
actions: PluginSpec.Action[],
|
||||
actions?: PluginSpec.Action[],
|
||||
behaviors: PluginSpec.Behavior[],
|
||||
animations?: PluginStateAnimation[],
|
||||
customParamEditors?: [StateAction | StateTransformer, StateTransformParameters.Class][],
|
||||
customFormats?: [string, DataFormatProvider][]
|
||||
customFormats?: [string, DataFormatProvider][],
|
||||
canvas3d?: PartialCanvas3DProps,
|
||||
layout?: {
|
||||
initial?: Partial<PluginLayoutStateProps>,
|
||||
controls?: PluginSpec.LayoutControls
|
||||
},
|
||||
components?: {
|
||||
remoteState?: 'none' | 'default',
|
||||
structureTools?: React.ComponentClass,
|
||||
viewport?: {
|
||||
view?: React.ComponentClass,
|
||||
controls?: React.ComponentClass,
|
||||
canvas3d?: PartialCanvas3DProps
|
||||
},
|
||||
hideTaskOverlay?: boolean
|
||||
},
|
||||
config?: [PluginConfigItem, unknown][]
|
||||
}
|
||||
@@ -52,11 +39,12 @@ interface PluginSpec {
|
||||
namespace PluginSpec {
|
||||
export interface Action {
|
||||
action: StateAction | StateTransformer,
|
||||
customControl?: StateTransformParameters.Class,
|
||||
/* constructible react component with <action.customControl /> */
|
||||
customControl?: any,
|
||||
autoUpdate?: boolean
|
||||
}
|
||||
|
||||
export function Action(action: StateAction | StateTransformer, params?: { customControl?: StateTransformParameters.Class, autoUpdate?: boolean }): Action {
|
||||
export function Action(action: StateAction | StateTransformer, params?: { customControl?: any /* constructible react component with <action.customControl /> */, autoUpdate?: boolean }): Action {
|
||||
return { action, customControl: params && params.customControl, autoUpdate: params && params.autoUpdate };
|
||||
}
|
||||
|
||||
@@ -68,13 +56,6 @@ namespace PluginSpec {
|
||||
export function Behavior<T extends StateTransformer>(transformer: T, defaultParams: Partial<StateTransformer.Params<T>> = {}): Behavior {
|
||||
return { transformer, defaultParams };
|
||||
}
|
||||
|
||||
export interface LayoutControls {
|
||||
top?: React.ComponentClass | 'none',
|
||||
left?: React.ComponentClass | 'none',
|
||||
right?: React.ComponentClass | 'none',
|
||||
bottom?: React.ComponentClass | 'none'
|
||||
}
|
||||
}
|
||||
|
||||
export const DefaultPluginSpec = (): PluginSpec => ({
|
||||
@@ -112,6 +93,7 @@ export const DefaultPluginSpec = (): PluginSpec => ({
|
||||
PluginSpec.Action(StateTransforms.Representation.StructureSelectionsLabel3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.StructureSelectionsOrientation3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.ModelUnitcell3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.StructureBoundingBox3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.ExplodeStructureRepresentation3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.OverpaintStructureRepresentation3DFromScript),
|
||||
@@ -130,6 +112,7 @@ export const DefaultPluginSpec = (): PluginSpec => ({
|
||||
PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
|
||||
PluginSpec.Behavior(PluginBehaviors.Representation.FocusLoci),
|
||||
PluginSpec.Behavior(PluginBehaviors.Camera.FocusLoci),
|
||||
PluginSpec.Behavior(PluginBehaviors.Camera.CameraAxisHelper),
|
||||
PluginSpec.Behavior(StructureFocusRepresentation),
|
||||
|
||||
PluginSpec.Behavior(PluginBehaviors.CustomProps.StructureInfo),
|
||||
@@ -139,9 +122,6 @@ export const DefaultPluginSpec = (): PluginSpec => ({
|
||||
PluginSpec.Behavior(PluginBehaviors.CustomProps.ValenceModel),
|
||||
PluginSpec.Behavior(PluginBehaviors.CustomProps.CrossLinkRestraint),
|
||||
],
|
||||
customParamEditors: [
|
||||
[CreateVolumeStreamingBehavior, VolumeStreamingCustomControls]
|
||||
],
|
||||
animations: [
|
||||
AnimateModelIndex,
|
||||
AnimateCameraSpin,
|
||||
|
||||
@@ -14,9 +14,9 @@ import { PluginCommands } from '../commands';
|
||||
export interface PluginToast {
|
||||
title: string,
|
||||
/**
|
||||
* The message can be either a string, html string, or an arbitrary React component.
|
||||
* The message can be either a string, html string, or an arbitrary React (Function) component.
|
||||
*/
|
||||
message: string | React.ComponentClass,
|
||||
message: string | Function,
|
||||
/**
|
||||
* Only one message with a given key can be shown.
|
||||
*/
|
||||
@@ -103,7 +103,7 @@ export namespace PluginToastManager {
|
||||
serialNumber: number,
|
||||
key?: string,
|
||||
title: string,
|
||||
message: string | React.ComponentClass,
|
||||
message: string | Function,
|
||||
hide: () => void,
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
@@ -155,6 +155,12 @@ const modifier = {
|
||||
'as-whole-residues': Argument(Type.Bool, { isOptional: true })
|
||||
}), Types.ElementSelectionQuery, 'For each atom set in the selection, include all surrouding atoms/residues that are within the specified radius.'),
|
||||
|
||||
surroundingLigands: symbol(Arguments.Dictionary({
|
||||
0: Argument(Types.ElementSelectionQuery),
|
||||
radius: Argument(Type.Num),
|
||||
'include-water': Argument(Type.Bool, { isOptional: true, defaultValue: true })
|
||||
}), Types.ElementSelectionQuery, 'Find all ligands components around the source query.'),
|
||||
|
||||
includeConnected: symbol(Arguments.Dictionary({
|
||||
0: Argument(Types.ElementSelectionQuery),
|
||||
'bond-test': Argument(Type.Bool, { isOptional: true, defaultValue: 'true for covalent bonds' as any }),
|
||||
|
||||
@@ -258,6 +258,13 @@ const symbols = [
|
||||
elementRadius: xs['atom-radius']
|
||||
})(ctx);
|
||||
}),
|
||||
D(MolScript.structureQuery.modifier.surroundingLigands, function structureQuery_modifier_includeSurroundingLigands(ctx, xs) {
|
||||
return Queries.modifiers.surroundingLigands({
|
||||
query: xs[0] as any,
|
||||
radius: xs['radius'](ctx),
|
||||
includeWater: !!(xs['include-water'] && xs['include-water'](ctx)),
|
||||
})(ctx);
|
||||
}),
|
||||
D(MolScript.structureQuery.modifier.wholeResidues, function structureQuery_modifier_wholeResidues(ctx, xs) { return Queries.modifiers.wholeResidues(xs[0] as any)(ctx); }),
|
||||
D(MolScript.structureQuery.modifier.union, function structureQuery_modifier_union(ctx, xs) { return Queries.modifiers.union(xs[0] as any)(ctx); }),
|
||||
D(MolScript.structureQuery.modifier.expandProperty, function structureQuery_modifier_expandProperty(ctx, xs) { return Queries.modifiers.expandProperty(xs[0] as any, xs['property'])(ctx); }),
|
||||
|
||||
@@ -138,6 +138,7 @@ export const SymbolTable = [
|
||||
Alias(MolScript.structureQuery.modifier.union, 'sel.atom.union'),
|
||||
Alias(MolScript.structureQuery.modifier.cluster, 'sel.atom.cluster'),
|
||||
Alias(MolScript.structureQuery.modifier.includeSurroundings, 'sel.atom.include-surroundings'),
|
||||
Alias(MolScript.structureQuery.modifier.surroundingLigands, 'sel.atom.surrounding-ligands'),
|
||||
Alias(MolScript.structureQuery.modifier.includeConnected, 'sel.atom.include-connected'),
|
||||
Alias(MolScript.structureQuery.modifier.expandProperty, 'sel.atom.expand-property'),
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
/**
|
||||
* 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 { OrderedSet, Interval } from '../mol-data/int';
|
||||
import { BitFlags } from './bit-flags';
|
||||
import { assertUnreachable } from './type-helpers';
|
||||
|
||||
export enum MarkerAction {
|
||||
None = 0x0,
|
||||
@@ -37,50 +38,85 @@ export namespace MarkerActions {
|
||||
}
|
||||
|
||||
export function applyMarkerActionAtPosition(array: Uint8Array, i: number, action: MarkerAction) {
|
||||
let v = array[i];
|
||||
switch (action) {
|
||||
case MarkerAction.Highlight:
|
||||
if (v % 2 === 0) {
|
||||
array[i] = v + 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case MarkerAction.RemoveHighlight:
|
||||
if (v % 2 !== 0) {
|
||||
array[i] = v - 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case MarkerAction.Select:
|
||||
if (v < 2) {
|
||||
array[i] = v + 2;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case MarkerAction.Deselect:
|
||||
array[i] = v % 2;
|
||||
return array[i] !== v;
|
||||
case MarkerAction.Toggle:
|
||||
if (v >= 2) array[i] = v - 2;
|
||||
else array[i] = v + 2;
|
||||
return true;
|
||||
case MarkerAction.Clear:
|
||||
array[i] = 0;
|
||||
return v !== 0;
|
||||
case MarkerAction.Highlight: array[i] |= 1; break;
|
||||
case MarkerAction.RemoveHighlight: array[i] &= ~1; break;
|
||||
case MarkerAction.Select: array[i] |= 2; break;
|
||||
case MarkerAction.Deselect: array[i] &= ~2; break;
|
||||
case MarkerAction.Toggle: array[i] ^= 2; break;
|
||||
case MarkerAction.Clear: array[i] = 0; break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function applyMarkerAction(array: Uint8Array, set: OrderedSet, action: MarkerAction) {
|
||||
let changed = false;
|
||||
if (action === MarkerAction.None) return false;
|
||||
|
||||
if (Interval.is(set)) {
|
||||
for (let i = Interval.start(set), _i = Interval.end(set); i < _i; i++) {
|
||||
changed = applyMarkerActionAtPosition(array, i, action) || changed;
|
||||
const start = Interval.start(set);
|
||||
const end = Interval.end(set);
|
||||
const view = new Uint32Array(array.buffer, 0, array.buffer.byteLength >> 2);
|
||||
|
||||
const viewStart = (start + 3) >> 2;
|
||||
const viewEnd = viewStart + ((end - 4 * viewStart) >> 2);
|
||||
|
||||
const frontStart = start;
|
||||
const frontEnd = Math.min(4 * viewStart, end);
|
||||
const backStart = Math.max(start, 4 * viewEnd);
|
||||
const backEnd = end;
|
||||
|
||||
switch (action) {
|
||||
case MarkerAction.Highlight:
|
||||
for (let i = viewStart; i < viewEnd; ++i) view[i] |= 0x01010101;
|
||||
break;
|
||||
case MarkerAction.RemoveHighlight:
|
||||
for (let i = viewStart; i < viewEnd; ++i) view[i] &= ~0x01010101;
|
||||
break;
|
||||
case MarkerAction.Select:
|
||||
for (let i = viewStart; i < viewEnd; ++i) view[i] |= 0x02020202;
|
||||
break;
|
||||
case MarkerAction.Deselect:
|
||||
for (let i = viewStart; i < viewEnd; ++i) view[i] &= ~0x02020202;
|
||||
break;
|
||||
case MarkerAction.Toggle:
|
||||
for (let i = viewStart; i < viewEnd; ++i) view[i] ^= 0x02020202;
|
||||
break;
|
||||
case MarkerAction.Clear:
|
||||
for (let i = viewStart; i < viewEnd; ++i) view[i] = 0;
|
||||
break;
|
||||
default:
|
||||
assertUnreachable(action);
|
||||
}
|
||||
|
||||
for (let i = frontStart; i < frontEnd; ++i) {
|
||||
applyMarkerActionAtPosition(array, i, action);
|
||||
}
|
||||
|
||||
for (let i = backStart; i < backEnd; ++i) {
|
||||
applyMarkerActionAtPosition(array, i, action);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0, _i = set.length; i < _i; i++) {
|
||||
changed = applyMarkerActionAtPosition(array, set[i], action) || changed;
|
||||
switch (action) {
|
||||
case MarkerAction.Highlight:
|
||||
for (let i = 0, il = set.length; i < il; ++i) array[set[i]] |= 1;
|
||||
break;
|
||||
case MarkerAction.RemoveHighlight:
|
||||
for (let i = 0, il = set.length; i < il; ++i) array[set[i]] &= ~1;
|
||||
break;
|
||||
case MarkerAction.Select:
|
||||
for (let i = 0, il = set.length; i < il; ++i) array[set[i]] |= 2;
|
||||
break;
|
||||
case MarkerAction.Deselect:
|
||||
for (let i = 0, il = set.length; i < il; ++i) array[set[i]] &= ~2;
|
||||
break;
|
||||
case MarkerAction.Toggle:
|
||||
for (let i = 0, il = set.length; i < il; ++i) array[set[i]] ^= 2;
|
||||
break;
|
||||
case MarkerAction.Clear:
|
||||
for (let i = 0, il = set.length; i < il; ++i) array[set[i]] = 0;
|
||||
break;
|
||||
default:
|
||||
assertUnreachable(action);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -25,4 +25,8 @@ export type NonNullableArray<T extends any[] | ReadonlyArray<any>> = T extends a
|
||||
export function ObjectKeys<T extends object>(o: T) {
|
||||
return Object.keys(o) as (keyof T)[];
|
||||
}
|
||||
export interface FiniteArray<T, L extends number = number> extends ReadonlyArray<T> { length: L };
|
||||
export interface FiniteArray<T, L extends number = number> extends ReadonlyArray<T> { length: L };
|
||||
|
||||
export function assertUnreachable(x: never): never {
|
||||
throw new Error('unreachable');
|
||||
}
|
||||
@@ -53,6 +53,7 @@ export const indexTemplate = `<!DOCTYPE html>
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
syntaxHighlight: { activated: false, theme: 'agate' },
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl,
|
||||
HidePlugin
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# 0.9.7
|
||||
* add Surrounding Ligands query
|
||||
|
||||
# 0.9.6
|
||||
* optional download parameter
|
||||
|
||||
|
||||
@@ -136,6 +136,13 @@ const AssemblyNameParam: QueryParamInfo = {
|
||||
description: 'Assembly name. If none is provided, crystal symmetry (where available) or deposited model is used.'
|
||||
};
|
||||
|
||||
const OmitWaterParam: QueryParamInfo = {
|
||||
name: 'omit_water',
|
||||
type: QueryParamType.Boolean,
|
||||
required: false,
|
||||
defaultValue: false
|
||||
};
|
||||
|
||||
function Q<Params = any>(definition: Partial<QueryDefinition<Params>>) {
|
||||
return definition;
|
||||
}
|
||||
@@ -223,7 +230,28 @@ const QueryMap = {
|
||||
jsonParams: [ AtomSiteTestJsonParam, RadiusParam ],
|
||||
restParams: [ ...AtomSiteTestRestParams, RadiusParam ],
|
||||
filter: QuerySchemas.interaction
|
||||
})
|
||||
}),
|
||||
'surroundingLigands': Q<{ atom_site: AtomSiteSchema, radius: number, assembly_name: string, omit_water: boolean }>({
|
||||
niceName: 'Surrounding Ligands',
|
||||
description: 'Identifies (complete) ligands within the given radius from the source atom set. Takes crystal symmetry into account.',
|
||||
query(p) {
|
||||
const tests = getAtomsTests(p.atom_site);
|
||||
const center = Queries.combinators.merge(tests.map(test => Queries.generators.atoms({
|
||||
...test,
|
||||
entityTest: test.entityTest
|
||||
? ctx => test.entityTest!(ctx) && ctx.element.unit.conformation.operator.isIdentity
|
||||
: ctx => ctx.element.unit.conformation.operator.isIdentity
|
||||
})));
|
||||
return Queries.modifiers.surroundingLigands({ query: center, radius: p.radius !== void 0 ? p.radius : 5, includeWater: !p.omit_water });
|
||||
},
|
||||
structureTransform(p, s) {
|
||||
if (p.assembly_name) return StructureSymmetry.buildAssembly(s, '' + p.assembly_name).run();
|
||||
return StructureSymmetry.builderSymmetryMates(s, p.radius !== void 0 ? p.radius : 5).run();
|
||||
},
|
||||
jsonParams: [ AtomSiteTestJsonParam, RadiusParam, OmitWaterParam, AssemblyNameParam ],
|
||||
restParams: [ ...AtomSiteTestRestParams, RadiusParam, OmitWaterParam, AssemblyNameParam ],
|
||||
filter: QuerySchemas.interaction
|
||||
}),
|
||||
};
|
||||
|
||||
export type QueryName = keyof typeof QueryMap
|
||||
|
||||
@@ -248,6 +248,7 @@ async function resolveJobEntry(entry: JobEntry, structure: StructureWrapper, enc
|
||||
|
||||
if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter(entry.queryDefinition.filter);
|
||||
if (result.length > 0) encode_mmCIF_categories(encoder, result, { copyAllCategories: entry.copyAllCategories });
|
||||
else ConsoleLogger.logId(entry.job.id, 'Warning', `Empty result for Query ${entry.key}/${entry.queryDefinition.name}`);
|
||||
if (entry.transform && !Mat4.isIdentity(entry.transform)) GlobalModelTransformInfo.writeMmCif(encoder, entry.transform);
|
||||
if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter();
|
||||
perf.end('encode');
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
export const VERSION = '0.9.6';
|
||||
export const VERSION = '0.9.7';
|
||||
@@ -75,6 +75,7 @@ function remove(id: string) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (e.isSticky) return;
|
||||
try {
|
||||
for (let j = i + 1; j < index.length; j++) {
|
||||
index[j - 1] = index[j];
|
||||
@@ -89,15 +90,15 @@ function remove(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
let index = readIndex();
|
||||
for (const e of index) {
|
||||
try {
|
||||
fs.unlinkSync(path.join(Config.working_folder, e.id + '.json'));
|
||||
} catch { }
|
||||
}
|
||||
writeIndex([]);
|
||||
}
|
||||
// function clear() {
|
||||
// let index = readIndex();
|
||||
// for (const e of index) {
|
||||
// try {
|
||||
// fs.unlinkSync(path.join(Config.working_folder, e.id + '.json'));
|
||||
// } catch { }
|
||||
// }
|
||||
// writeIndex([]);
|
||||
// }
|
||||
|
||||
function mapPath(path: string) {
|
||||
if (!Config.api_prefix) return path;
|
||||
@@ -128,11 +129,11 @@ app.get(mapPath(`/get/:id`), (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.get(mapPath(`/clear`), (req, res) => {
|
||||
clear();
|
||||
res.status(200);
|
||||
res.end();
|
||||
});
|
||||
// app.get(mapPath(`/clear`), (req, res) => {
|
||||
// clear();
|
||||
// res.status(200);
|
||||
// res.end();
|
||||
// });
|
||||
|
||||
app.get(mapPath(`/remove/:id`), (req, res) => {
|
||||
remove((req.params.id as string || '').toLowerCase());
|
||||
|
||||
Reference in New Issue
Block a user