Compare commits

...

74 Commits

Author SHA1 Message Date
dsehnal
614cffda96 2.0.1 2021-03-23 15:44:24 +01:00
dsehnal
2e0379d202 npm ignore 2021-03-23 15:42:52 +01:00
dsehnal
b5cfdcd2a3 2.0.0 2021-03-23 15:31:18 +01:00
dsehnal
c00de6fde0 Merge branch 'master' of https://github.com/molstar/molstar 2021-03-23 11:16:26 +01:00
Alexander Rose
da3a8e56f3 handle negative isovalues in gpu mc 2021-03-22 22:06:49 -07:00
dsehnal
103d6fe775 alpha orbitals tryUseGpu param 2021-03-22 20:18:48 +01:00
dsehnal
5df55e6bf7 SDF delimiter bugfix + multi-molecule SDF support in mol-plugin 2021-03-22 17:21:47 +01:00
dsehnal
3b285086d4 rename files called "macro" due to Jest not being able to process them 2021-03-22 16:53:48 +01:00
dsehnal
91793bc3cc 2.0.0-dev.13 2021-03-22 13:29:42 +01:00
dsehnal
fa3828e820 add model-server-query transform support 2021-03-22 12:51:03 +01:00
Alexander Rose
31ba8212da 2.0.0-dev.12 2021-03-21 16:47:53 -07:00
Alexander Rose
fe27d8e134 Merge pull request #150 from molstar/stubs2
basic support for bond stubs
2021-03-21 16:40:03 -07:00
Alexander Rose
83dcdfdc4b Merge commit '2faa821c50a6dfce700eb8072a61d01d937c18e5' into stubs2 2021-03-21 16:36:33 -07:00
Alexander Rose
f9aaabc1f7 fix interactions bounding sphere 2021-03-21 16:29:56 -07:00
Alexander Rose
034370b44c add includeParent support to interactions 2021-03-21 16:25:03 -07:00
Alexander Rose
b87666df3e don't pad empty bounding spheres 2021-03-21 16:24:25 -07:00
Alexander Rose
c98c3228fe fix structure.asParent 2021-03-21 16:23:57 -07:00
Alexander Rose
9419980dfc make structure state private (like before) 2021-03-21 12:39:21 -07:00
Alexander Rose
42d60420e5 added Structure.asParent
- refactored structure state handling
- removed Structure.WithChild
2021-03-21 12:10:24 -07:00
dsehnal
5b1df333a7 tsconfig jsx param 2021-03-21 16:18:01 +01:00
Alexander Rose
0bb376706d fix ellipsoid repr and support includeParent
- switch off adjustCylinderLength
- handle structure with child
2021-03-20 23:58:48 -07:00
Alexander Rose
eca7da2c72 add adjustCylinderLength param
- so it can be switched off
2021-03-20 23:54:50 -07:00
Alexander Rose
b0bdb3ddb6 tweak param help 2021-03-20 23:52:23 -07:00
Alexander Rose
3180d7c305 basic support for bond stubs
- line and ball & stick repr
- stubs support in link visual helper
- getData and mustRecreate methods for structure repr provider
- Structure.WithChild helper (needs Proxy support)
2021-03-20 18:05:58 -07:00
dsehnal
2faa821c50 2.0.0-dev.11 2021-03-19 17:29:29 +01:00
David Sehnal
7f355ae501 Merge pull request #141 from molstar/surrounding-ligands
Surrounding Ligands query
2021-03-19 17:16:25 +01:00
dsehnal
7f79ff9ff2 StructureSourceControls: show hierarchy preset is >1 trajectory is selected 2021-03-18 15:29:48 +01:00
dsehnal
02de871c59 StructureBoundingBox3D transform 2021-03-18 15:18:15 +01:00
dsehnal
00cb783d4c BoxShape3D transform 2021-03-18 14:15:04 +01:00
David Sehnal
c925919ee5 Merge pull request #148 from TomasKulhanek/master
FIX issue #147 CSS transform:scale cause molstar canvas to have incorrect size
2021-03-17 10:50:44 +01:00
dsehnal
324820890a Fix createModelProperty.isApplicable 2021-03-17 10:35:29 +01:00
Tomas Kulhanek
2687b29d4d FIX molstar/molstar#147 offsetWidth/offsetHeight is correct size of element when css transform:scale is used 2021-03-17 07:46:41 +00:00
dsehnal
7084aaee1a adjust text 2021-03-16 23:02:14 +01:00
dsehnal
520a2f7850 model-server: empty result console output 2021-03-16 22:47:34 +01:00
Alexander Rose
9264987817 camera helper tweaks
- add highlighting
- improved axes alignment
2021-03-15 23:16:19 -07:00
dsehnal
b736ed3ea4 readme tweaks 2021-03-15 21:35:17 +01:00
dsehnal
166d660fa7 2.0.0-dev.10 2021-03-15 20:20:21 +01:00
dsehnal
b8249cde4d interactive camera axis helper 2021-03-15 20:16:07 +01:00
dsehnal
f12f5eca90 Merge branch 'master' into surrounding-ligands 2021-03-15 16:45:53 +01:00
dsehnal
cd3798b46f disable SwaggerUI response syntax highlight 2021-03-15 16:44:36 +01:00
dsehnal
0240e54737 TrackballControlsParams.autoAdjustControls 2021-03-15 14:13:11 +01:00
dsehnal
6a735d902e fix XYZ parser bug 2021-03-15 13:31:44 +01:00
dsehnal
57a942ecb5 requestCameraReset SnapshotProvide
- allow to customize the snapshop based on the current scane/boundingbox/camera state
2021-03-15 12:47:45 +01:00
dsehnal
f67605a398 applyMarkerAction fix 2 2021-03-14 18:46:04 +01:00
dsehnal
aaafa1d5ad model-server: surroundingLigands query 2021-03-14 15:09:37 +01:00
dsehnal
a1d9a77653 surroundingLigands query 2021-03-14 15:00:13 +01:00
dsehnal
f2f1181af3 Merge branch 'master' into surrounding-ligands 2021-03-14 13:16:22 +01:00
dsehnal
864befc48a applyMarkerAction fix 2021-03-14 13:09:53 +01:00
dsehnal
73f6793bd8 surroundingLigands query wip 2021-03-14 12:22:01 +01:00
dsehnal
87ee9d88f2 ResidueSet helper (wip) 2021-03-14 11:21:12 +01:00
dsehnal
b1e245e913 add UndirectedGraph 2021-03-14 10:19:28 +01:00
Alexander Rose
78c0471f39 remove unused Structure.unitsSortedByVolume 2021-03-13 22:35:24 -08:00
Alexander Rose
c57b9b9214 improve preset for many polymer gaps
- show all atom instead
- for medium sized structures
- fixes #57
2021-03-13 22:27:51 -08:00
Alexander Rose
34f33c5bbb fix apply marker type error 2021-03-13 22:24:48 -08:00
Alexander Rose
57da2a7ebb optimized applyMarkerAction
- extract switch statement out of loop
- use int32 view to handle 4 byte together
- don't check for change (essentially done at a higher level anyway)
2021-03-13 12:22:53 -08:00
Alexander Rose
d45d5c0e55 add assertUnreachable helper
- to type check if, e.g. if/switch statements are exhaustive
- TODO use...
2021-03-13 12:20:00 -08:00
dsehnal
42ed425e65 fix secondary_structure_type 2021-03-13 19:58:50 +01:00
dsehnal
f752ee5094 plugin-state server: remove /clear and can't remotely remove sticky entries 2021-03-13 17:56:18 +01:00
dsehnal
044c796942 Fix getSymmetryOperatorRef indexing 2021-03-13 17:53:42 +01:00
dsehnal
0aabbcfaab add back CreateVolumeStreamingBehavior custom controls 2021-03-13 16:40:48 +01:00
Alexander Rose
24274cc53b 2.0.0-dev.9 2021-03-09 22:50:34 -08:00
Alexander Rose
870cef2fd4 add collapse-left-panel to viewer query params 2021-03-09 22:46:53 -08:00
Alexander Rose
bf7b1f5bfd move StateActions to PluginSpec 2021-03-09 22:46:13 -08:00
Alexander Rose
9c9a0312db gpu mc attribution 2021-03-09 22:42:52 -08:00
dsehnal
724fa2a7cd package lock 2021-03-08 18:32:27 +01:00
Alexander Rose
19b36e5942 2.0.0-dev.8 2021-03-07 14:13:51 -08:00
Alexander Rose
b0dd9ab026 Merge pull request #135 from molstar/split-plugin-context
Move part of PluginContext to mol-plugin-ui
2021-03-07 13:41:17 -08:00
Alexander Rose
b77f1d4dee move initDataActions to PluginContext 2021-03-07 13:39:30 -08:00
dsehnal
3770fd7706 move actions back to PluginSpec 2021-03-07 13:36:50 +01:00
dsehnal
e3175c3ed1 Merge branch 'master' into split-plugin-context 2021-03-07 13:24:17 +01:00
dsehnal
7c5dd5b15b fix build caused by some typing edge case 2021-03-07 11:41:56 +01:00
Alexander Rose
0872e11669 2.0.0-dev.7 2021-03-07 01:29:34 -08:00
Alexander Rose
a66da4defc fix missing vars 2021-03-07 01:27:16 -08:00
dsehnal
acf13fa46f split plugin context (wip) 2021-03-05 00:33:00 +01:00
122 changed files with 4998 additions and 4100 deletions

View File

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

View File

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

View File

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

6222
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "2.0.0-dev.6",
"version": "2.0.1",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {

View File

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

View File

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

View File

@@ -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',
});

View File

@@ -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: [

View File

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

View File

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

View File

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

View File

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

View File

@@ -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: {

View File

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

View File

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

View File

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

View File

@@ -172,7 +172,8 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
color: PD.Color(ColorNames.blue),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
xrayShaded: PD.Boolean(false),
pickable: PD.Boolean(true)
pickable: PD.Boolean(true),
tryUseGpu: PD.Boolean(true)
}
})({
canAutoUpdate() {
@@ -190,7 +191,7 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, params));
await repr.createOrUpdate(props, a.data).runInContext(ctx);
repr.setState({ pickable: srcParams.pickable });
return new PluginStateObject.Volume.Representation3D({ repr, source: a }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
return new PluginStateObject.Volume.Representation3D({ repr, sourceData: a.data }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
});
},
update({ a, b, newParams: srcParams }, plugin: PluginContext) {
@@ -200,6 +201,7 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
const props = { ...b.data.repr.props, ...newParams.type.params };
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, newParams));
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
b.data.sourceData = a.data;
b.data.repr.setState({ pickable: srcParams.pickable });
b.description = VolumeRepresentation3DHelpers.getDescription(props);
return StateTransformer.UpdateResult.Updated;
@@ -229,7 +231,7 @@ function volumeParams(plugin: PluginContext, volume: PluginStateObject.Volume.Da
colorParams: { value: params.color }
} : {
type: 'isosurface',
typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded },
typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded, tryUseGpu: params.tryUseGpu },
color: 'uniform',
colorParams: { value: params.color }
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra';
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
import { CameraTransitionManager } from './camera/transition';
import { BehaviorSubject } from 'rxjs';
import { Scene } from '../mol-gl/scene';
export { ICamera, Camera };
@@ -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

View File

@@ -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);
}

View File

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

View File

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

View File

@@ -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');

View File

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

View File

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

View File

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

View File

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

View File

@@ -112,7 +112,22 @@ Phosphate ion
> <SYNONYMS>
Orthophosphate; Phosphate
$$$$`;
$$$$
Comp 2
5 4 0 0 0 0 999 V2000
0.0000 0.8250 0.0000 O 0 5 0 0 0 0 0 0 0 0 0 0
-0.8250 0.0000 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 -0.8250 0.0000 O 0 5 0 0 0 0 0 0 0 0 0 0
0.0000 0.0000 0.0000 P 0 0 0 0 0 0 0 0 0 0 0 0
0.8250 0.0000 0.0000 O 0 5 0 0 0 0 0 0 0 0 0 0
4 1 1 0 0 0 0
4 2 2 0 0 0 0
4 3 1 0 0 0 0
4 5 1 0 0 0 0
M CHG 3 1 -1 3 -1 5 -1
M END`;
describe('sdf reader', () => {
it('basic', async () => {
@@ -120,14 +135,20 @@ describe('sdf reader', () => {
if (parsed.isError) {
throw new Error(parsed.message);
}
const compound = parsed.result.compounds[0];
const { molFile, dataItems } = compound;
const compound1 = parsed.result.compounds[0];
const compound2 = parsed.result.compounds[1];
const { molFile, dataItems } = compound1;
const { atoms, bonds } = molFile;
expect(parsed.result.compounds.length).toBe(2);
// number of structures
expect(atoms.count).toBe(5);
expect(bonds.count).toBe(4);
expect(compound2.molFile.atoms.count).toBe(5);
expect(compound2.molFile.bonds.count).toBe(4);
expect(atoms.x.value(0)).toBeCloseTo(0, 0.001);
expect(atoms.y.value(0)).toBeCloseTo(0.8250, 0.0001);
expect(atoms.z.value(0)).toBeCloseTo(0, 0.0001);

View File

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

View File

@@ -22,6 +22,7 @@ export interface SdfFile {
}[]
}
const delimiter = '$$$$';
function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, data: Column<string> } {
const dataHeader = TokenBuilder.create(tokenizer.data, 32);
const data = TokenBuilder.create(tokenizer.data, 32);
@@ -29,6 +30,7 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
let sawHeaderToken = false;
while (tokenizer.position < tokenizer.length) {
const line = Tokenizer.readLine(tokenizer);
if (line.startsWith(delimiter)) break;
if (!!line) {
if (line.startsWith('> <')) {
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 3, tokenizer.tokenEnd - 1);
@@ -49,9 +51,7 @@ function handleDataItems(tokenizer: Tokenizer): { dataHeader: Column<string>, da
};
}
function handleMolFile(data: string) {
const tokenizer = Tokenizer(data);
function handleMolFile(tokenizer: Tokenizer) {
const title = Tokenizer.readLine(tokenizer).trim();
const program = Tokenizer.readLine(tokenizer).trim();
const comment = Tokenizer.readLine(tokenizer).trim();
@@ -60,6 +60,15 @@ function handleMolFile(data: string) {
const atomCount = +counts.substr(0, 3), bondCount = +counts.substr(3, 3);
if (Number.isNaN(atomCount) || Number.isNaN(bondCount)) {
// try to skip to next molecule
while (tokenizer.position < tokenizer.length) {
const line = Tokenizer.readLine(tokenizer);
if (line.startsWith(delimiter)) break;
}
return;
}
const atoms = handleAtoms(tokenizer, atomCount);
const bonds = handleBonds(tokenizer, bondCount);
const dataItems = handleDataItems(tokenizer);
@@ -70,10 +79,16 @@ function handleMolFile(data: string) {
};
}
const delimiter = '$$$$';
function parseInternal(data: string): Result<SdfFile> {
const result: SdfFile = { compounds: data.split(delimiter).map(d => handleMolFile(d)) };
return Result.success(result);
const tokenizer = Tokenizer(data);
const compounds: SdfFile['compounds'] = [];
while (tokenizer.position < tokenizer.length) {
const c = handleMolFile(tokenizer);
if (c) compounds.push(c);
}
return Result.success({ compounds });
}
export function parseSdf(data: string) {

View File

@@ -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];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,10 +11,14 @@ import { StructureSelection } from '../selection';
import { UniqueStructuresBuilder } from '../utils/builders';
import { StructureUniqueSubsetBuilder } from '../../structure/util/unique-subset-builder';
import { QueryContext, QueryFn } from '../context';
import { structureIntersect, structureSubtract } from '../utils/structure-set';
import { structureIntersect, structureSubtract, structureUnion } from '../utils/structure-set';
import { UniqueArray } from '../../../../mol-data/generic';
import { StructureSubsetBuilder } from '../../structure/util/subset-builder';
import { StructureElement } from '../../structure/element';
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
import { ResidueSet, ResidueSetEntry } from '../../model/properties/utils/residue-set';
import { StructureProperties } from '../../structure/properties';
import { arraySetAdd } from '../../../../mol-util/array';
function getWholeResidues(ctx: QueryContext, source: Structure, structure: Structure) {
const builder = source.subsetBuilder(true);
@@ -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

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,7 @@ import { Carbohydrates } from './carbohydrates/data';
import { computeCarbohydrates } from './carbohydrates/compute';
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
import { idFactory } from '../../../mol-util/id-factory';
import { Box3D, GridLookup3D } from '../../../mol-math/geometry';
import { GridLookup3D } from '../../../mol-math/geometry';
import { UUID } from '../../../mol-util';
import { CustomProperties } from '../../custom-property';
import { AtomicHierarchy } from '../model/properties/atomic';
@@ -35,90 +35,73 @@ import { Trajectory } from '../trajectory';
import { RuntimeContext, Task } from '../../../mol-task';
import { computeStructureBoundary } from './util/boundary';
/** Internal structure state */
type State = {
parent?: Structure,
boundary?: Boundary,
lookup3d?: StructureLookup3D,
interUnitBonds?: InterUnitBonds,
unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
unitSymmetryGroupsIndexMap?: IntMap<number>,
unitsSortedByVolume?: ReadonlyArray<Unit>;
carbohydrates?: Carbohydrates,
models?: ReadonlyArray<Model>,
model?: Model,
masterModel?: Model,
representativeModel?: Model,
uniqueResidueNames?: Set<string>,
uniqueElementSymbols?: Set<ElementSymbol>,
entityIndices?: ReadonlyArray<EntityIndex>,
uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
serialMapping?: SerialMapping,
hashCode: number,
transformHash: number,
elementCount: number,
bondCount: number,
uniqueElementCount: number,
atomicResidueCount: number,
polymerResidueCount: number,
polymerGapCount: number,
polymerUnitCount: number,
coordinateSystem: SymmetryOperator,
label: string,
propertyData?: any,
customProps?: CustomProperties
}
class Structure {
/** Maps unit.id to unit */
readonly unitMap: IntMap<Unit>;
/** Maps unit.id to index of unit in units array */
readonly unitIndexMap: IntMap<number>;
/** Array of all units in the structure, sorted by unit.id */
readonly units: ReadonlyArray<Unit>;
private _props: {
parent?: Structure,
boundary?: Boundary,
lookup3d?: StructureLookup3D,
interUnitBonds?: InterUnitBonds,
unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
unitSymmetryGroupsIndexMap?: IntMap<number>,
unitsSortedByVolume?: ReadonlyArray<Unit>;
carbohydrates?: Carbohydrates,
models?: ReadonlyArray<Model>,
model?: Model,
masterModel?: Model,
representativeModel?: Model,
uniqueResidueNames?: Set<string>,
uniqueElementSymbols?: Set<ElementSymbol>,
entityIndices?: ReadonlyArray<EntityIndex>,
uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
serialMapping?: SerialMapping,
hashCode: number,
/** Hash based on all unit.id values in the structure, reflecting the units transformation */
transformHash: number,
elementCount: number,
bondCount: number,
uniqueElementCount: number,
atomicResidueCount: number,
polymerResidueCount: number,
polymerUnitCount: number,
coordinateSystem: SymmetryOperator,
label: string,
propertyData?: any,
customProps?: CustomProperties
} = {
hashCode: -1,
transformHash: -1,
elementCount: -1,
bondCount: -1,
uniqueElementCount: -1,
atomicResidueCount: -1,
polymerResidueCount: -1,
polymerUnitCount: -1,
coordinateSystem: SymmetryOperator.Default,
label: ''
};
subsetBuilder(isSorted: boolean) {
return new StructureSubsetBuilder(this, isSorted);
}
/** Count of all elements in the structure, i.e. the sum of the elements in the units */
get elementCount() {
return this._props.elementCount;
return this.state.elementCount;
}
/** Count of all bonds (intra- and inter-unit) in the structure */
get bondCount() {
if (this._props.bondCount === -1) {
this._props.bondCount = this.interUnitBonds.edgeCount + Bond.getIntraUnitBondCount(this);
if (this.state.bondCount === -1) {
this.state.bondCount = this.interUnitBonds.edgeCount + Bond.getIntraUnitBondCount(this);
}
return this._props.bondCount;
return this.state.bondCount;
}
get hasCustomProperties() {
return !!this._props.customProps && this._props.customProps.all.length > 0;
return !!this.state.customProps && this.state.customProps.all.length > 0;
}
get customPropertyDescriptors() {
if (!this._props.customProps) this._props.customProps = new CustomProperties();
return this._props.customProps;
if (!this.state.customProps) this.state.customProps = new CustomProperties();
return this.state.customProps;
}
/**
* Property data unique to this instance of the structure.
*/
get currentPropertyData() {
if (!this._props.propertyData) this._props.propertyData = Object.create(null);
return this._props.propertyData;
if (!this.state.propertyData) this.state.propertyData = Object.create(null);
return this.state.propertyData;
}
/**
@@ -130,31 +113,39 @@ class Structure {
/** Count of all polymer residues in the structure */
get polymerResidueCount() {
if (this._props.polymerResidueCount === -1) {
this._props.polymerResidueCount = getPolymerResidueCount(this);
if (this.state.polymerResidueCount === -1) {
this.state.polymerResidueCount = getPolymerResidueCount(this);
}
return this._props.polymerResidueCount;
return this.state.polymerResidueCount;
}
/** Count of all polymer gaps in the structure */
get polymerGapCount() {
if (this.state.polymerGapCount === -1) {
this.state.polymerGapCount = getPolymerGapCount(this);
}
return this.state.polymerGapCount;
}
get polymerUnitCount() {
if (this._props.polymerUnitCount === -1) {
this._props.polymerUnitCount = getPolymerUnitCount(this);
if (this.state.polymerUnitCount === -1) {
this.state.polymerUnitCount = getPolymerUnitCount(this);
}
return this._props.polymerUnitCount;
return this.state.polymerUnitCount;
}
get uniqueElementCount() {
if (this._props.uniqueElementCount === -1) {
this._props.uniqueElementCount = getUniqueElementCount(this);
if (this.state.uniqueElementCount === -1) {
this.state.uniqueElementCount = getUniqueElementCount(this);
}
return this._props.uniqueElementCount;
return this.state.uniqueElementCount;
}
get atomicResidueCount() {
if (this._props.atomicResidueCount === -1) {
this._props.atomicResidueCount = getAtomicResidueCount(this);
if (this.state.atomicResidueCount === -1) {
this.state.atomicResidueCount = getAtomicResidueCount(this);
}
return this._props.atomicResidueCount;
return this.state.atomicResidueCount;
}
/**
@@ -170,15 +161,15 @@ class Structure {
}
get hashCode() {
if (this._props.hashCode !== -1) return this._props.hashCode;
if (this.state.hashCode !== -1) return this.state.hashCode;
return this.computeHash();
}
/** Hash based on all unit.id values in the structure, reflecting the units transformation */
get transformHash() {
if (this._props.transformHash !== -1) return this._props.transformHash;
this._props.transformHash = hashFnv32a(this.units.map(u => u.id));
return this._props.transformHash;
if (this.state.transformHash !== -1) return this.state.transformHash;
this.state.transformHash = hashFnv32a(this.units.map(u => u.id));
return this.state.transformHash;
}
private computeHash() {
@@ -191,7 +182,7 @@ class Structure {
hash = (31 * hash + this.elementCount) | 0;
hash = hash1(hash);
if (hash === -1) hash = 0;
this._props.hashCode = hash;
this.state.hashCode = hash;
return hash;
}
@@ -202,12 +193,12 @@ class Structure {
/** The parent or itself in case this is the root */
get root() {
return this._props.parent || this;
return this.state.parent || this;
}
/** The root/top-most parent or `undefined` in case this is the root */
get parent() {
return this._props.parent;
return this.state.parent;
}
/**
@@ -218,81 +209,74 @@ class Structure {
* by the consumer.
*/
get coordinateSystem(): SymmetryOperator {
return this._props.coordinateSystem;
return this.state.coordinateSystem;
}
get label() {
return this._props.label;
return this.state.label;
}
get boundary() {
if (this._props.boundary) return this._props.boundary;
this._props.boundary = computeStructureBoundary(this);
return this._props.boundary;
if (this.state.boundary) return this.state.boundary;
this.state.boundary = computeStructureBoundary(this);
return this.state.boundary;
}
get lookup3d() {
if (this._props.lookup3d) return this._props.lookup3d;
this._props.lookup3d = new StructureLookup3D(this);
return this._props.lookup3d;
if (this.state.lookup3d) return this.state.lookup3d;
this.state.lookup3d = new StructureLookup3D(this);
return this.state.lookup3d;
}
get interUnitBonds() {
if (this._props.interUnitBonds) return this._props.interUnitBonds;
this._props.interUnitBonds = computeInterUnitBonds(this);
return this._props.interUnitBonds;
if (this.state.interUnitBonds) return this.state.interUnitBonds;
this.state.interUnitBonds = computeInterUnitBonds(this);
return this.state.interUnitBonds;
}
get unitSymmetryGroups(): ReadonlyArray<Unit.SymmetryGroup> {
if (this._props.unitSymmetryGroups) return this._props.unitSymmetryGroups;
this._props.unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this);
return this._props.unitSymmetryGroups;
if (this.state.unitSymmetryGroups) return this.state.unitSymmetryGroups;
this.state.unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this);
return this.state.unitSymmetryGroups;
}
/** Maps unit.id to index of SymmetryGroup in unitSymmetryGroups array */
get unitSymmetryGroupsIndexMap(): IntMap<number> {
if (this._props.unitSymmetryGroupsIndexMap) return this._props.unitSymmetryGroupsIndexMap;
this._props.unitSymmetryGroupsIndexMap = Unit.SymmetryGroup.getUnitSymmetryGroupsIndexMap(this.unitSymmetryGroups);
return this._props.unitSymmetryGroupsIndexMap;
}
/** Array of all units in the structure, sorted by their boundary volume */
get unitsSortedByVolume(): ReadonlyArray<Unit> {
if (this._props.unitsSortedByVolume) return this._props.unitsSortedByVolume;
this._props.unitsSortedByVolume = getUnitsSortedByVolume(this);
return this._props.unitsSortedByVolume;
if (this.state.unitSymmetryGroupsIndexMap) return this.state.unitSymmetryGroupsIndexMap;
this.state.unitSymmetryGroupsIndexMap = Unit.SymmetryGroup.getUnitSymmetryGroupsIndexMap(this.unitSymmetryGroups);
return this.state.unitSymmetryGroupsIndexMap;
}
get carbohydrates(): Carbohydrates {
if (this._props.carbohydrates) return this._props.carbohydrates;
this._props.carbohydrates = computeCarbohydrates(this);
return this._props.carbohydrates;
if (this.state.carbohydrates) return this.state.carbohydrates;
this.state.carbohydrates = computeCarbohydrates(this);
return this.state.carbohydrates;
}
get models(): ReadonlyArray<Model> {
if (this._props.models) return this._props.models;
this._props.models = getModels(this);
return this._props.models;
if (this.state.models) return this.state.models;
this.state.models = getModels(this);
return this.state.models;
}
get uniqueResidueNames() {
return this._props.uniqueResidueNames
|| (this._props.uniqueResidueNames = getUniqueResidueNames(this));
return this.state.uniqueResidueNames
|| (this.state.uniqueResidueNames = getUniqueResidueNames(this));
}
get uniqueElementSymbols() {
return this._props.uniqueElementSymbols
|| (this._props.uniqueElementSymbols = getUniqueElementSymbols(this));
return this.state.uniqueElementSymbols
|| (this.state.uniqueElementSymbols = getUniqueElementSymbols(this));
}
get entityIndices() {
return this._props.entityIndices
|| (this._props.entityIndices = getEntityIndices(this));
return this.state.entityIndices
|| (this.state.entityIndices = getEntityIndices(this));
}
get uniqueAtomicResidueIndices() {
return this._props.uniqueAtomicResidueIndices
|| (this._props.uniqueAtomicResidueIndices = getUniqueAtomicResidueIndices(this));
return this.state.uniqueAtomicResidueIndices
|| (this.state.uniqueAtomicResidueIndices = getUniqueAtomicResidueIndices(this));
}
/** Contains only atomic units */
@@ -327,7 +311,7 @@ class Structure {
* to address elements in a structure.
*/
get serialMapping() {
return this._props.serialMapping || (this._props.serialMapping = getSerialMapping(this));
return this.state.serialMapping || (this.state.serialMapping = getSerialMapping(this));
}
/**
@@ -335,25 +319,25 @@ class Structure {
* Otherwise throw an exception.
*/
get model(): Model {
if (this._props.model) return this._props.model;
if (this._props.representativeModel) return this._props.representativeModel;
if (this._props.masterModel) return this._props.masterModel;
if (this.state.model) return this.state.model;
if (this.state.representativeModel) return this.state.representativeModel;
if (this.state.masterModel) return this.state.masterModel;
const models = this.models;
if (models.length > 1) {
throw new Error('The structure is based on multiple models and has neither a master- nor a representative-model.');
}
this._props.model = models[0];
return this._props.model;
this.state.model = models[0];
return this.state.model;
}
/** The master-model, other models can have bonds to it */
get masterModel(): Model | undefined {
return this._props.masterModel;
return this.state.masterModel;
}
/** A representative model, e.g. the first model of a trajectory */
get representativeModel(): Model | undefined {
return this._props.representativeModel;
return this.state.representativeModel;
}
hasElement(e: StructureElement.Location) {
@@ -377,51 +361,39 @@ class Structure {
}
return Structure.create(units, {
label: this.label,
interUnitBonds: this._props.interUnitBonds,
interUnitBonds: this.state.interUnitBonds,
});
}
private initUnits(units: ArrayLike<Unit>) {
const unitMap = IntMap.Mutable<Unit>();
const unitIndexMap = IntMap.Mutable<number>();
let elementCount = 0;
let isSorted = true;
let lastId = units.length > 0 ? units[0].id : 0;
for (let i = 0, _i = units.length; i < _i; i++) {
const u = units[i];
unitMap.set(u.id, u);
elementCount += u.elements.length;
if (u.id < lastId) isSorted = false;
lastId = u.id;
}
if (!isSorted) sort(units, 0, units.length, cmpUnits, arraySwap);
for (let i = 0, _i = units.length; i < _i; i++) {
unitIndexMap.set(units[i].id, i);
}
this._props.elementCount = elementCount;
return { unitMap, unitIndexMap };
private _child: Structure | undefined;
private _target: Structure | undefined;
/**
* For `structure` with `parent` this returns a proxy that
* targets `parent` and has `structure` attached as a child.
*/
asParent(): Structure {
return this.parent ? new Structure(this.parent.units, this.parent.unitMap, this.parent.unitIndexMap, this.parent.state, { child: this, target: this.parent }) : this;
}
constructor(units: ArrayLike<Unit>, props: Structure.Props = {}) {
const { unitMap, unitIndexMap } = this.initUnits(units);
this.unitMap = unitMap;
this.unitIndexMap = unitIndexMap;
this.units = units as ReadonlyArray<Unit>;
get child(): Structure | undefined {
return this._child;
}
if (props.parent) this._props.parent = props.parent.parent || props.parent;
if (props.interUnitBonds) this._props.interUnitBonds = props.interUnitBonds;
/** Get the proxy target. Usefull for equality checks. */
get target(): Structure {
return this._target ?? this;
}
if (props.coordinateSystem) this._props.coordinateSystem = props.coordinateSystem;
else if (props.parent) this._props.coordinateSystem = props.parent.coordinateSystem;
if (props.label) this._props.label = props.label;
else if (props.parent) this._props.label = props.parent.label;
if (props.masterModel) this._props.masterModel = props.masterModel;
else if (props.parent) this._props.masterModel = props.parent.masterModel;
if (props.representativeModel) this._props.representativeModel = props.representativeModel;
else if (props.parent) this._props.representativeModel = props.parent.representativeModel;
/**
* @param units Array of all units in the structure, sorted by unit.id
* @param unitMap Maps unit.id to index of unit in units array
* @param unitIndexMap Array of all units in the structure, sorted by unit.id
*/
constructor(readonly units: ReadonlyArray<Unit>, readonly unitMap: IntMap<Unit>, readonly unitIndexMap: IntMap<number>, private readonly state: State, asParent?: { child: Structure, target: Structure }) {
// always assign to ensure object shape
this._child = asParent?.child;
this._target = asParent?.target;
}
}
@@ -429,24 +401,6 @@ function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) {
return units[i].id - units[j].id;
}
function cmpUnitGroupVolume(units: ArrayLike<[index: number, volume: number]>, i: number, j: number) {
const d = units[i][1] - units[j][1];
if (d === 0) return units[i][0] - units[j][0];
return d;
}
function getUnitsSortedByVolume(structure: Structure) {
const { unitSymmetryGroups } = structure;
const groups = unitSymmetryGroups.map((g, i) => [i, Box3D.volume(g.units[0].lookup3d.boundary.box)] as [number, number]);
sort(groups, 0, groups.length, cmpUnitGroupVolume, arraySwap);
const ret: Unit[] = [];
for (const [i] of groups) {
for (const u of unitSymmetryGroups[i].units) {
ret.push(u);
}
}
return ret;
}
function getModels(s: Structure) {
const { units } = s;
@@ -568,6 +522,15 @@ function getPolymerResidueCount(structure: Structure): number {
return polymerResidueCount;
}
function getPolymerGapCount(structure: Structure): number {
const { units } = structure;
let polymerGapCount = 0;
for (let i = 0, _i = units.length; i < _i; i++) {
polymerGapCount += units[i].gapElements.length / 2;
}
return polymerGapCount;
}
function getPolymerUnitCount(structure: Structure): number {
const { units } = structure;
let polymerUnitCount = 0;
@@ -632,7 +595,7 @@ function getSerialMapping(structure: Structure): SerialMapping {
}
namespace Structure {
export const Empty = new Structure([]);
export const Empty = create([]);
export interface Props {
parent?: Structure
@@ -645,7 +608,7 @@ namespace Structure {
representativeModel?: Model
}
/** Serial index of an element in the structure accross all units */
/** Serial index of an element in the structure across all units */
export type SerialIndex = { readonly '@type': 'serial-index' } & number
/** Represents a single structure */
@@ -686,8 +649,57 @@ namespace Structure {
return Loci(structure);
}
export function create(units: ReadonlyArray<Unit>, props?: Props): Structure {
return new Structure(units, props);
export function create(units: ReadonlyArray<Unit>, props: Props = {}): Structure {
// init units
const unitMap = IntMap.Mutable<Unit>();
const unitIndexMap = IntMap.Mutable<number>();
let elementCount = 0;
let isSorted = true;
let lastId = units.length > 0 ? units[0].id : 0;
for (let i = 0, _i = units.length; i < _i; i++) {
const u = units[i];
unitMap.set(u.id, u);
elementCount += u.elements.length;
if (u.id < lastId) isSorted = false;
lastId = u.id;
}
if (!isSorted) sort(units, 0, units.length, cmpUnits, arraySwap);
for (let i = 0, _i = units.length; i < _i; i++) {
unitIndexMap.set(units[i].id, i);
}
// initial state
const state: State = {
hashCode: -1,
transformHash: -1,
elementCount,
bondCount: -1,
uniqueElementCount: -1,
atomicResidueCount: -1,
polymerResidueCount: -1,
polymerGapCount: -1,
polymerUnitCount: -1,
coordinateSystem: SymmetryOperator.Default,
label: ''
};
// handle props
if (props.parent) state.parent = props.parent.parent || props.parent;
if (props.interUnitBonds) state.interUnitBonds = props.interUnitBonds;
if (props.coordinateSystem) state.coordinateSystem = props.coordinateSystem;
else if (props.parent) state.coordinateSystem = props.parent.coordinateSystem;
if (props.label) state.label = props.label;
else if (props.parent) state.label = props.parent.label;
if (props.masterModel) state.masterModel = props.masterModel;
else if (props.parent) state.masterModel = props.parent.masterModel;
if (props.representativeModel) state.representativeModel = props.representativeModel;
else if (props.parent) state.representativeModel = props.parent.representativeModel;
return new Structure(units, unitMap, unitIndexMap, state);
}
export async function ofTrajectory(trajectory: Trajectory, ctx: RuntimeContext): Promise<Structure> {
@@ -893,7 +905,7 @@ namespace Structure {
const cs = s.coordinateSystem;
const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs), cs);
return new Structure(units, { parent: s, coordinateSystem: newCS });
return create(units, { parent: s, coordinateSystem: newCS });
}
export class StructureBuilder {

View File

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

View File

@@ -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);
}
}
});

View File

@@ -123,14 +123,23 @@ export const GroProvider: TrajectoryFormatProvider = {
};
export const MolProvider: TrajectoryFormatProvider = {
label: 'MOL/SDF',
description: 'MOL/SDF',
label: 'MOL',
description: 'MOL',
category: TrajectoryFormatCategory,
stringExtensions: ['mol', 'sdf', 'sd'],
stringExtensions: ['mol'],
parse: directTrajectory(StateTransforms.Model.TrajectoryFromMOL),
visuals: defaultVisuals
};
export const SdfProvider: TrajectoryFormatProvider = {
label: 'SDF',
description: 'SDF',
category: TrajectoryFormatCategory,
stringExtensions: ['sdf', 'sd'],
parse: directTrajectory(StateTransforms.Model.TrajectoryFromSDF),
visuals: defaultVisuals
};
export const Mol2Provider: TrajectoryFormatProvider = {
label: 'MOL2',
description: 'MOL2',
@@ -148,6 +157,7 @@ export const BuiltInTrajectoryFormats = [
['gro', GroProvider] as const,
['xyz', XyzProvider] as const,
['mol', MolProvider] as const,
['sdf', SdfProvider] as const,
['mol2', Mol2Provider] as const,
] as const;

View File

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

View File

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

View File

@@ -425,6 +425,18 @@ const surroundings = StructureSelectionQuery('Surrounding Residues (5 \u212B) of
referencesCurrent: true
});
const surroundingLigands = StructureSelectionQuery('Surrounding Ligands (5 \u212B) of Selection', MS.struct.modifier.union([
MS.struct.modifier.surroundingLigands({
0: MS.internal.generator.current(),
radius: 5,
'include-water': true
})
]), {
description: 'Select ligand components within 5 \u212B of the current selection.',
category: StructureSelectionCategory.Manipulate,
referencesCurrent: true
});
const complement = StructureSelectionQuery('Inverse / Complement of Selection', MS.struct.modifier.union([
MS.struct.modifier.exceptBy({
0: MS.struct.generator.all(),
@@ -645,6 +657,7 @@ export const StructureSelectionQueries = {
ring,
aromaticRing,
surroundings,
surroundingLigands,
complement,
covalentlyBonded,
covalentlyOrMetallicBonded,

View File

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

View File

@@ -41,8 +41,8 @@ export namespace PluginStateObject {
return !!o && o.type.typeClass === 'Behavior';
}
export interface Representation3DData<T extends Representation.Any, S extends StateObject = StateObject> { repr: T, source: S }
export function CreateRepresentation3D<T extends Representation.Any, S extends StateObject = StateObject>(type: { name: string }) {
export interface Representation3DData<T extends Representation.Any, S = any> { repr: T, sourceData: S }
export function CreateRepresentation3D<T extends Representation.Any, S = any>(type: { name: string }) {
return Create<Representation3DData<T, S>>({ ...type, typeClass: 'Representation3D' });
}
@@ -102,10 +102,10 @@ export namespace PluginStateObject {
export class Structure extends Create<_Structure>({ name: 'Structure', typeClass: 'Object' }) { }
export namespace Structure {
export class Representation3D extends CreateRepresentation3D<StructureRepresentation<any> | ShapeRepresentation<any, any, any>, Structure>({ name: 'Structure 3D' }) { }
export class Representation3D extends CreateRepresentation3D<StructureRepresentation<any>, _Structure>({ name: 'Structure 3D' }) { }
export interface Representation3DStateData {
source: Representation3D,
repr: StructureRepresentation<any>,
/** used to restore state when the obj is removed */
initialState: Partial<StructureRepresentationState>,
state: Partial<StructureRepresentationState>,
@@ -120,12 +120,12 @@ export namespace PluginStateObject {
export namespace Volume {
export class Data extends Create<_Volume>({ name: 'Volume', typeClass: 'Object' }) { }
export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>>({ name: 'Volume 3D' }) { }
export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>, _Volume>({ name: 'Volume 3D' }) { }
}
export namespace Shape {
export class Provider extends Create<ShapeProvider<any, any, any>>({ name: 'Shape Provider', typeClass: 'Object' }) { }
export class Representation3D extends CreateRepresentation3D<ShapeRepresentation<any, any, any>>({ name: 'Shape 3D' }) { }
export class Representation3D extends CreateRepresentation3D<ShapeRepresentation<any, any, any>, unknown>({ name: 'Shape 3D' }) { }
}
}

View File

@@ -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

View File

@@ -39,6 +39,7 @@ import { parseXtc } from '../../mol-io/reader/xtc/parser';
import { coordinatesFromXtc } from '../../mol-model-formats/structure/xtc';
import { parseXyz } from '../../mol-io/reader/xyz/parser';
import { trajectoryFromXyz } from '../../mol-model-formats/structure/xyz';
import { parseSdf } from '../../mol-io/reader/sdf/parser';
export { CoordinatesFromDcd };
export { CoordinatesFromXtc };
@@ -50,6 +51,7 @@ export { TrajectoryFromPDB };
export { TrajectoryFromGRO };
export { TrajectoryFromXYZ };
export { TrajectoryFromMOL };
export { TrajectoryFromSDF };
export { TrajectoryFromMOL2 };
export { TrajectoryFromCube };
export { TrajectoryFromCifCore };
@@ -292,6 +294,36 @@ const TrajectoryFromMOL = PluginStateTransform.BuiltIn({
}
});
type TrajectoryFromSDF = typeof TrajectoryFromSDF
const TrajectoryFromSDF = PluginStateTransform.BuiltIn({
name: 'trajectory-from-sdf',
display: { name: 'Parse SDF', description: 'Parse SDF string and create trajectory.' },
from: [SO.Data.String],
to: SO.Molecule.Trajectory
})({
apply({ a }) {
return Task.create('Parse SDF', async ctx => {
const parsed = await parseSdf(a.data).runInContext(ctx);
if (parsed.isError) throw new Error(parsed.message);
const models: Model[] = [];
for (const { molFile } of parsed.result.compounds) {
const traj = await trajectoryFromMol(molFile).runInContext(ctx);
for (let i = 0; i < traj.frameCount; i++) {
models.push(await Task.resolveInContext(traj.getFrameAtIndex(i), ctx));
}
}
const traj = new ArrayTrajectory(models);
const props = trajectoryProps(traj);
return new SO.Molecule.Trajectory(traj, props);
});
}
});
type TrajectoryFromMOL2 = typeof TrajectoryFromMOL
const TrajectoryFromMOL2 = PluginStateTransform.BuiltIn({
name: 'trajectory-from-mol2',

View File

@@ -36,6 +36,10 @@ import { DihedralParams, DihedralRepresentation } from '../../mol-repr/shape/loc
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
import { Clipping } from '../../mol-theme/clipping';
import { ObjectKeys } from '../../mol-util/type-helpers';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { getBoxMesh } from './shape';
import { Shape } from '../../mol-model/shape';
import { Box3D } from '../../mol-math/geometry';
export { StructureRepresentation3D };
export { ExplodeStructureRepresentation3D };
@@ -126,14 +130,15 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
return Task.create('Structure Representation', async ctx => {
const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset };
const provider = plugin.representation.structure.registry.get(params.type.name);
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
const data = provider.getData?.(a.data, params.type.params) || a.data;
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, data);
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, provider.getParams);
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, params);
repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: a.data }, params));
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: data }, params);
repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: data }, params));
const props = params.type.params || {};
await repr.createOrUpdate(props, a.data).runInContext(ctx);
return new SO.Molecule.Structure.Representation3D({ repr, source: a }, { label: provider.label });
await repr.createOrUpdate(props, data).runInContext(ctx);
return new SO.Molecule.Structure.Representation3D({ repr, sourceData: a.data }, { label: provider.label });
});
},
update({ a, b, oldParams, newParams, cache }, plugin: PluginContext) {
@@ -141,26 +146,28 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
if (newParams.type.name !== oldParams.type.name) return StateTransformer.UpdateResult.Recreate;
const provider = plugin.representation.structure.registry.get(newParams.type.name);
if (provider.mustRecreate?.(oldParams.type.params, newParams.type.params)) return StateTransformer.UpdateResult.Recreate;
const data = provider.getData?.(a.data, newParams.type.params) || a.data;
const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset };
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, data);
// TODO: if themes had a .needsUpdate method the following block could
// be optimized and only executed conditionally
// dispose isn't called on update so we need to handle it manually
Theme.releaseDependencies(plugin.representation.structure.themes, { structure: b.data.source.data }, oldParams);
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, newParams);
b.data.repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: a.data }, newParams));
Theme.releaseDependencies(plugin.representation.structure.themes, { structure: b.data.sourceData }, oldParams);
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: data }, newParams);
b.data.repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: data }, newParams));
const props = { ...b.data.repr.props, ...newParams.type.params };
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
b.data.source = a;
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
b.data.sourceData = a.data;
return StateTransformer.UpdateResult.Updated;
});
},
dispose({ b, params }, plugin: PluginContext) {
if (!b || !params) return;
const structure = b.data.source.data;
const structure = b.data.sourceData;
const provider = plugin.representation.structure.registry.get(params.type.name);
if (provider.ensureCustomProperties) provider.ensureCustomProperties.detach(structure);
Theme.releaseDependencies(plugin.representation.structure.themes, { structure }, params);
@@ -191,26 +198,26 @@ const UnwindStructureAssemblyRepresentation3D = PluginStateTransform.BuiltIn({
return true;
},
apply({ a, params }) {
const structure = a.data.source.data;
const structure = a.data.sourceData;
const unitTransforms = new StructureUnitTransforms(structure);
unwindStructureAssembly(structure, unitTransforms, params.t);
return new SO.Molecule.Structure.Representation3DState({
state: { unitTransforms },
initialState: { unitTransforms: new StructureUnitTransforms(structure) },
info: structure,
source: a
repr: a.data.repr
}, { label: `Unwind T = ${params.t.toFixed(2)}` });
},
update({ a, b, newParams, oldParams }) {
const structure = b.data.info as Structure;
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
const unitTransforms = b.data.state.unitTransforms!;
unwindStructureAssembly(structure, unitTransforms, newParams.t);
b.label = `Unwind T = ${newParams.t.toFixed(2)}`;
b.data.source = a;
b.data.repr = a.data.repr;
return StateTransformer.UpdateResult.Updated;
}
});
@@ -228,26 +235,26 @@ const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({
return true;
},
apply({ a, params }) {
const structure = a.data.source.data;
const structure = a.data.sourceData;
const unitTransforms = new StructureUnitTransforms(structure.root);
explodeStructure(structure, unitTransforms, params.t);
return new SO.Molecule.Structure.Representation3DState({
state: { unitTransforms },
initialState: { unitTransforms: new StructureUnitTransforms(structure.root) },
info: structure.root,
source: a
repr: a.data.repr
}, { label: `Explode T = ${params.t.toFixed(2)}` });
},
update({ a, b, newParams, oldParams }) {
const structure = a.data.source.data;
const structure = a.data.sourceData;
if (b.data.info !== structure.root) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
const unitTransforms = b.data.state.unitTransforms!;
explodeStructure(structure.root, unitTransforms, newParams.t);
b.label = `Explode T = ${newParams.t.toFixed(2)}`;
b.data.source = a;
b.data.repr = a.data.repr;
return StateTransformer.UpdateResult.Updated;
}
});
@@ -276,28 +283,28 @@ const OverpaintStructureRepresentation3DFromScript = PluginStateTransform.BuiltI
return true;
},
apply({ a, params }) {
const structure = a.data.source.data;
const structure = a.data.sourceData;
const overpaint = Overpaint.ofScript(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { overpaint },
initialState: { overpaint: Overpaint.Empty },
info: structure,
source: a
repr: a.data.repr
}, { label: `Overpaint (${overpaint.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const oldStructure = b.data.info as Structure;
const newStructure = a.data.source.data;
const newStructure = a.data.sourceData;
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const oldOverpaint = b.data.state.overpaint!;
const newOverpaint = Overpaint.ofScript(newParams.layers, newStructure);
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
b.data.state.overpaint = newOverpaint;
b.data.source = a;
b.data.repr = a.data.repr;
b.label = `Overpaint (${newOverpaint.layers.length} Layers)`;
return StateTransformer.UpdateResult.Updated;
}
@@ -328,28 +335,28 @@ const OverpaintStructureRepresentation3DFromBundle = PluginStateTransform.BuiltI
return true;
},
apply({ a, params }) {
const structure = a.data.source.data;
const structure = a.data.sourceData;
const overpaint = Overpaint.ofBundle(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { overpaint },
initialState: { overpaint: Overpaint.Empty },
info: structure,
source: a
repr: a.data.repr
}, { label: `Overpaint (${overpaint.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const oldStructure = b.data.info as Structure;
const newStructure = a.data.source.data;
const newStructure = a.data.sourceData;
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const oldOverpaint = b.data.state.overpaint!;
const newOverpaint = Overpaint.ofBundle(newParams.layers, newStructure);
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
b.data.state.overpaint = newOverpaint;
b.data.source = a;
b.data.repr = a.data.repr;
b.label = `Overpaint (${newOverpaint.layers.length} Layers)`;
return StateTransformer.UpdateResult.Updated;
}
@@ -377,27 +384,27 @@ const TransparencyStructureRepresentation3DFromScript = PluginStateTransform.Bui
return true;
},
apply({ a, params }) {
const structure = a.data.source.data;
const structure = a.data.sourceData;
const transparency = Transparency.ofScript(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { transparency },
initialState: { transparency: Transparency.Empty },
info: structure,
source: a
repr: a.data.repr
}, { label: `Transparency (${transparency.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const structure = b.data.info as Structure;
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const oldTransparency = b.data.state.transparency!;
const newTransparency = Transparency.ofScript(newParams.layers, structure);
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
b.data.state.transparency = newTransparency;
b.data.source = a;
b.data.repr = a.data.repr;
b.label = `Transparency (${newTransparency.layers.length} Layers)`;
return StateTransformer.UpdateResult.Updated;
}
@@ -426,27 +433,27 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
return true;
},
apply({ a, params }) {
const structure = a.data.source.data;
const structure = a.data.sourceData;
const transparency = Transparency.ofBundle(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { transparency },
initialState: { transparency: Transparency.Empty },
info: structure,
source: a
repr: a.data.repr
}, { label: `Transparency (${transparency.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const structure = b.data.info as Structure;
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const oldTransparency = b.data.state.transparency!;
const newTransparency = Transparency.ofBundle(newParams.layers, structure);
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
b.data.state.transparency = newTransparency;
b.data.source = a;
b.data.repr = a.data.repr;
b.label = `Transparency (${newTransparency.layers.length} Layers)`;
return StateTransformer.UpdateResult.Updated;
}
@@ -474,27 +481,27 @@ const ClippingStructureRepresentation3DFromScript = PluginStateTransform.BuiltIn
return true;
},
apply({ a, params }) {
const structure = a.data.source.data;
const structure = a.data.sourceData;
const clipping = Clipping.ofScript(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { clipping },
initialState: { clipping: Clipping.Empty },
info: structure,
source: a
repr: a.data.repr
}, { label: `Clipping (${clipping.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const structure = b.data.info as Structure;
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const oldClipping = b.data.state.clipping!;
const newClipping = Clipping.ofScript(newParams.layers, structure);
if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged;
b.data.state.clipping = newClipping;
b.data.source = a;
b.data.repr = a.data.repr;
b.label = `Clipping (${newClipping.layers.length} Layers)`;
return StateTransformer.UpdateResult.Updated;
}
@@ -523,27 +530,27 @@ const ClippingStructureRepresentation3DFromBundle = PluginStateTransform.BuiltIn
return true;
},
apply({ a, params }) {
const structure = a.data.source.data;
const structure = a.data.sourceData;
const clipping = Clipping.ofBundle(params.layers, structure);
return new SO.Molecule.Structure.Representation3DState({
state: { clipping },
initialState: { clipping: Clipping.Empty },
info: structure,
source: a
repr: a.data.repr
}, { label: `Clipping (${clipping.layers.length} Layers)` });
},
update({ a, b, newParams, oldParams }) {
const structure = b.data.info as Structure;
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
const oldClipping = b.data.state.clipping!;
const newClipping = Clipping.ofBundle(newParams.layers, structure);
if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged;
b.data.state.clipping = newClipping;
b.data.source = a;
b.data.repr = a.data.repr;
b.label = `Clipping (${newClipping.layers.length} Layers)`;
return StateTransformer.UpdateResult.Updated;
}
@@ -646,7 +653,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
const props = params.type.params || {};
await repr.createOrUpdate(props, a.data).runInContext(ctx);
return new SO.Volume.Representation3D({ repr, source: a }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
return new SO.Volume.Representation3D({ repr, sourceData: a.data }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
});
},
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
@@ -659,6 +666,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
const props = { ...b.data.repr.props, ...newParams.type.params };
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, newParams));
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
b.data.sourceData = a.data;
b.description = VolumeRepresentation3DHelpers.getDescription(props);
return StateTransformer.UpdateResult.Updated;
});
@@ -686,13 +694,14 @@ const ShapeRepresentation3D = PluginStateTransform.BuiltIn({
const props = { ...PD.getDefaultValues(a.data.params), ...params };
const repr = ShapeRepresentation(a.data.getShape, a.data.geometryUtils);
await repr.createOrUpdate(props, a.data.data).runInContext(ctx);
return new SO.Shape.Representation3D({ repr, source: a }, { label: a.data.label });
return new SO.Shape.Representation3D({ repr, sourceData: a.data }, { label: a.data.label });
});
},
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
return Task.create('Shape Representation', async ctx => {
const props = { ...b.data.repr.props, ...newParams };
await b.data.repr.createOrUpdate(props, a.data.data).runInContext(ctx);
b.data.sourceData = a.data;
return StateTransformer.UpdateResult.Updated;
});
}
@@ -720,7 +729,7 @@ const ModelUnitcell3D = PluginStateTransform.BuiltIn({
const data = getUnitcellData(a.data, symmetry, params);
const repr = UnitcellRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => UnitcellParams);
await repr.createOrUpdate(params, data).runInContext(ctx);
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Unit Cell`, description: symmetry.spacegroup.name });
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Unit Cell`, description: symmetry.spacegroup.name });
});
},
update({ a, b, newParams }) {
@@ -730,7 +739,42 @@ const ModelUnitcell3D = PluginStateTransform.BuiltIn({
const props = { ...b.data.repr.props, ...newParams };
const data = getUnitcellData(a.data, symmetry, props);
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
b.data.source = a;
b.data.sourceData = data;
return StateTransformer.UpdateResult.Updated;
});
}
});
export { StructureBoundingBox3D };
type StructureBoundingBox3D = typeof StructureBoundingBox3D
const StructureBoundingBox3D = PluginStateTransform.BuiltIn({
name: 'structure-bounding-box-3d',
display: 'Bounding Box',
from: SO.Molecule.Structure,
to: SO.Shape.Representation3D,
params: {
radius: PD.Numeric(0.05, { min: 0.01, max: 4, step: 0.01 }, { isEssential: true }),
color: PD.Color(ColorNames.red, { isEssential: true }),
...Mesh.Params,
}
})({
canAutoUpdate() {
return true;
},
apply({ a, params }, plugin: PluginContext) {
return Task.create('Bounding Box', async ctx => {
const repr = ShapeRepresentation((_, data: { box: Box3D, radius: number, color: Color }, __, shape) => {
const mesh = getBoxMesh(data.box, data.radius, shape?.geometry);
return Shape.create('Bouding Box', data, mesh, () => data.color, () => 1, () => 'Bounding Box');
}, Mesh.Utils);
await repr.createOrUpdate(params, { box: a.data.boundary.box, radius: params.radius, color: params.color }).runInContext(ctx);
return new SO.Shape.Representation3D({ repr, sourceData: a.data }, { label: `Bounding Box` });
});
},
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
return Task.create('Bounding Box', async ctx => {
await b.data.repr.createOrUpdate(newParams, { box: a.data.boundary.box, radius: newParams.radius, color: newParams.color }).runInContext(ctx);
b.data.sourceData = a.data;
return StateTransformer.UpdateResult.Updated;
});
}
@@ -755,7 +799,7 @@ const StructureSelectionsDistance3D = PluginStateTransform.BuiltIn({
const data = getDistanceDataFromStructureSelections(a.data);
const repr = DistanceRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => DistanceParams);
await repr.createOrUpdate(params, data).runInContext(ctx);
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Distance` });
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Distance` });
});
},
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
@@ -763,7 +807,7 @@ const StructureSelectionsDistance3D = PluginStateTransform.BuiltIn({
const props = { ...b.data.repr.props, ...newParams };
const data = getDistanceDataFromStructureSelections(a.data);
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
b.data.source = a;
b.data.sourceData = data;
return StateTransformer.UpdateResult.Updated;
});
},
@@ -788,7 +832,7 @@ const StructureSelectionsAngle3D = PluginStateTransform.BuiltIn({
const data = getAngleDataFromStructureSelections(a.data);
const repr = AngleRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => AngleParams);
await repr.createOrUpdate(params, data).runInContext(ctx);
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Angle` });
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Angle` });
});
},
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
@@ -796,7 +840,7 @@ const StructureSelectionsAngle3D = PluginStateTransform.BuiltIn({
const props = { ...b.data.repr.props, ...newParams };
const data = getAngleDataFromStructureSelections(a.data);
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
b.data.source = a;
b.data.sourceData = data;
return StateTransformer.UpdateResult.Updated;
});
},
@@ -821,7 +865,7 @@ const StructureSelectionsDihedral3D = PluginStateTransform.BuiltIn({
const data = getDihedralDataFromStructureSelections(a.data);
const repr = DihedralRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => DihedralParams);
await repr.createOrUpdate(params, data).runInContext(ctx);
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Dihedral` });
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Dihedral` });
});
},
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
@@ -829,7 +873,7 @@ const StructureSelectionsDihedral3D = PluginStateTransform.BuiltIn({
const props = { ...b.data.repr.props, ...newParams };
const data = getDihedralDataFromStructureSelections(a.data);
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
b.data.source = a;
b.data.sourceData = data;
return StateTransformer.UpdateResult.Updated;
});
},
@@ -854,7 +898,7 @@ const StructureSelectionsLabel3D = PluginStateTransform.BuiltIn({
const data = getLabelDataFromStructureSelections(a.data);
const repr = LabelRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => LabelParams);
await repr.createOrUpdate(params, data).runInContext(ctx);
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Label` });
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Label` });
});
},
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
@@ -862,7 +906,7 @@ const StructureSelectionsLabel3D = PluginStateTransform.BuiltIn({
const props = { ...b.data.repr.props, ...newParams };
const data = getLabelDataFromStructureSelections(a.data);
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
b.data.source = a;
b.data.sourceData = data;
return StateTransformer.UpdateResult.Updated;
});
},
@@ -887,7 +931,7 @@ const StructureSelectionsOrientation3D = PluginStateTransform.BuiltIn({
const data = getOrientationDataFromStructureSelections(a.data);
const repr = OrientationRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => OrientationParams);
await repr.createOrUpdate(params, data).runInContext(ctx);
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Orientation` });
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Orientation` });
});
},
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
@@ -895,7 +939,7 @@ const StructureSelectionsOrientation3D = PluginStateTransform.BuiltIn({
const props = { ...b.data.repr.props, ...newParams };
const data = getOrientationDataFromStructureSelections(a.data);
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
b.data.source = a;
b.data.sourceData = data;
return StateTransformer.UpdateResult.Updated;
});
},

View 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;
}

View File

@@ -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;

View 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();
}
}

View File

@@ -7,7 +7,6 @@
import * as React from 'react';
import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { PluginContext } from '../../mol-plugin/context';
import { Color } from '../../mol-util/color';
import { ColorListName, ColorListOptions, ColorListOptionsScale, ColorListOptionsSet, getColorListFromName } from '../../mol-util/color/lists';
import { Legend as LegendData } from '../../mol-util/legend';
@@ -26,6 +25,7 @@ import { LineGraphComponent } from './line-graph/line-graph-component';
import { Slider, Slider2 } from './slider';
import { Asset } from '../../mol-util/assets';
import { ColorListEntry } from '../../mol-util/color/color';
import { PluginUIContext } from '../context';
export type ParameterControlsCategoryFilter = string | null | (string | null)[]
@@ -104,7 +104,7 @@ export class ParameterControls<P extends PD.Params> extends React.PureComponent<
}
}
export class ParameterMappingControl<S, T> extends PluginUIComponent<{ mapping: ParamMapping<S, T, PluginContext> }> {
export class ParameterMappingControl<S, T> extends PluginUIComponent<{ mapping: ParamMapping<S, T, PluginUIContext> }> {
setSettings = (p: { param: PD.Base<any>, name: string, value: any }, old: any) => {
const values = { ...old, [p.name]: p.value };
const t = this.props.mapping.update(values, this.plugin);
@@ -240,8 +240,8 @@ function renderSimple(options: { props: ParamProps<any>, state: { showHelp: bool
const help = props.param.help
? props.param.help(props.value)
: { description: props.param.description, legend: props.param.legend };
const desc = props.param.description;
const hasHelp = help.description || help.legend;
const desc = label + (hasHelp ? '. Click for help.' : '');
return <>
<ControlRow
className={className}

View File

@@ -6,7 +6,6 @@
import { PluginUIComponent } from '../base';
import { StateTransformParameters } from '../state/common';
import * as React from 'react';
import { VolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/behavior';
import { ExpandableControlRow, IconButton } from '../controls/common';
import { ParamDefinition as PD } from '../../mol-util/param-definition';

View File

@@ -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;

View File

@@ -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>;
}

View File

@@ -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
View 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]
],
});

View File

@@ -4,7 +4,6 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react';
import { State } from '../../mol-state';
import { PluginUIComponent } from '../base';
import { Icon, CodeSvg } from '../controls/icons';

View File

@@ -4,7 +4,6 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react';
import { PluginUIComponent } from '../base';
import { ParameterControls, ParamOnChange } from '../controls/parameters';
import { Button } from '../controls/common';

View File

@@ -8,7 +8,6 @@ import { State, StateTransform, StateTransformer } from '../../mol-state';
import { memoizeLatest } from '../../mol-util/memoize';
import { StateTransformParameters, TransformControlBase } from './common';
import { Observable } from 'rxjs';
import * as React from 'react';
import { PluginUIComponent } from '../base';
export { UpdateTransformControl, TransformUpdaterControl };

View File

@@ -4,7 +4,6 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react';
import { OrderedSet, SortedArray } from '../../mol-data/int';
import { Structure, StructureElement, StructureProperties, Unit } from '../../mol-model/structure';
import { UnitIndex } from '../../mol-model/structure/structure/element/element';

View File

@@ -216,7 +216,7 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
}
get selections() {
return this.props.cell.obj?.data.source as PluginStateObject.Molecule.Structure.Selections | undefined;
return this.props.cell.obj?.data.sourceData as ReadonlyArray<PluginStateObject.Molecule.Structure.SelectionEntry> | undefined;
}
delete = () => {
@@ -234,7 +234,7 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
if (!selections) return;
this.plugin.managers.interactivity.lociHighlights.clearHighlights();
for (const d of selections.data) {
for (const d of selections) {
this.plugin.managers.interactivity.lociHighlights.highlight({ loci: d.loci }, false);
}
this.plugin.managers.interactivity.lociHighlights.highlight({ loci: this.props.cell.obj?.data.repr.getLoci()! }, false);
@@ -250,7 +250,7 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
const selections = this.selections;
if (!selections) return;
const sphere = Loci.getBundleBoundingSphere(toLociBundle(selections.data));
const sphere = Loci.getBundleBoundingSphere(toLociBundle(selections));
if (sphere) {
this.plugin.managers.camera.focusSphere(sphere);
}
@@ -258,11 +258,11 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
get label() {
const selections = this.selections;
switch (selections?.data.length) {
case 1: return lociLabel(selections.data[0].loci, { condensed: true });
case 2: return distanceLabel(toLociBundle(selections.data), { condensed: true, unitLabel: this.plugin.managers.structure.measurement.state.options.distanceUnitLabel });
case 3: return angleLabel(toLociBundle(selections.data), { condensed: true });
case 4: return dihedralLabel(toLociBundle(selections.data), { condensed: true });
switch (selections?.length) {
case 1: return lociLabel(selections[0].loci, { condensed: true });
case 2: return distanceLabel(toLociBundle(selections), { condensed: true, unitLabel: this.plugin.managers.structure.measurement.state.options.distanceUnitLabel });
case 3: return angleLabel(toLociBundle(selections), { condensed: true });
case 4: return dihedralLabel(toLociBundle(selections), { condensed: true });
default: return '';
}
}

View File

@@ -5,7 +5,6 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react';
import { Model } from '../../mol-model/structure';
import { ModelRef, StructureHierarchyRef, TrajectoryRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { StateTransforms } from '../../mol-plugin-state/transforms';
@@ -195,12 +194,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;
}

View File

@@ -4,7 +4,6 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import * as React from 'react';
import { CollapsableControls, PurePluginUIComponent } from '../base';
import { Icon, ArrowUpwardSvg, ArrowDownwardSvg, DeleteOutlinedSvg, HelpOutlineSvg, TuneSvg, SuperposeAtomsSvg, SuperposeChainsSvg, SuperpositionSvg } from '../controls/icons';
import { Button, ToggleButton, IconButton } from '../controls/common';

View File

@@ -4,7 +4,6 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react';
import { PluginUIComponent } from './base';
import { OrderedMap } from 'immutable';
import { TaskManager } from '../mol-plugin/util/task-manager';

View File

@@ -6,7 +6,6 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as React from 'react';
import { PluginUIComponent } from './base';
import { PluginToastManager } from '../mol-plugin/util/toast';
import { IconButton } from './controls/common';

View File

@@ -6,16 +6,15 @@
*/
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 +70,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 +82,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');

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -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' }
});

View File

@@ -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 }

View File

@@ -267,7 +267,7 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
const transform = structure?.models.length === 0 ? void 0 : GlobalModelTransformInfo.get(structure?.models[0]!);
await repr.createOrUpdate(props, channel.data).runInContext(ctx);
if (transform) repr.setState({ transform });
return new SO.Volume.Representation3D({ repr, source: a }, { label: `${Math.round(channel.isoValue.relativeValue * 100) / 100} σ [${srcParams.channel}]` });
return new SO.Volume.Representation3D({ repr, sourceData: channel.data }, { label: `${Math.round(channel.isoValue.relativeValue * 100) / 100} σ [${srcParams.channel}]` });
}),
update: ({ a, b, newParams, spine }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
// TODO : check if params/underlying data/etc have changed; maybe will need to export "data" or some other "tag" in the Representation for this to work
@@ -280,6 +280,7 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
const props = { ...b.data.repr.props, ...params.type.params };
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params));
await b.data.repr.createOrUpdate(props, channel.data).runInContext(ctx);
b.data.sourceData = channel.data;
// TODO: set the transform here as well in case the structure moves?
// doing this here now breaks the code for some reason...

View File

@@ -54,20 +54,20 @@ export function SyncStructureRepresentation3DState(ctx: PluginContext) {
events.object.created.subscribe(e => {
if (!SO.Molecule.Structure.Representation3DState.is(e.obj)) return;
const data = e.obj.data as SO.Molecule.Structure.Representation3DStateData;
data.source.data.repr.setState(data.state);
ctx.canvas3d?.update(data.source.data.repr);
data.repr.setState(data.state);
ctx.canvas3d?.update(data.repr);
});
events.object.updated.subscribe(e => {
if (!SO.Molecule.Structure.Representation3DState.is(e.obj)) return;
const data = e.obj.data as SO.Molecule.Structure.Representation3DStateData;
data.source.data.repr.setState(data.state);
ctx.canvas3d?.update(data.source.data.repr);
data.repr.setState(data.state);
ctx.canvas3d?.update(data.repr);
});
events.object.removed.subscribe(e => {
if (!SO.Molecule.Structure.Representation3DState.is(e.obj)) return;
const data = e.obj.data as SO.Molecule.Structure.Representation3DStateData;
data.source.data.repr.setState(data.initialState);
ctx.canvas3d?.update(data.source.data.repr);
data.repr.setState(data.initialState);
ctx.canvas3d?.update(data.repr);
});
}

View File

@@ -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();

View File

@@ -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'],

View File

@@ -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,

View File

@@ -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
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -24,9 +24,6 @@ import { Visual } from './visual';
import { CustomProperty } from '../mol-model-props/common/custom-property';
import { Clipping } from '../mol-theme/clipping';
// export interface RepresentationProps {
// visuals?: string[]
// }
export type RepresentationProps = { [k: string]: any }
export interface RepresentationContext {
@@ -54,6 +51,8 @@ export interface RepresentationProvider<D = any, P extends PD.Params = any, S ex
attach: (ctx: CustomProperty.Context, data: D) => Promise<void>,
detach: (data: D) => void
}
readonly getData?: (data: D, props: PD.Values<P>) => D
readonly mustRecreate?: (oldProps: PD.Values<P>, newProps: PD.Values<P>) => boolean
}
export namespace RepresentationProvider {
@@ -66,7 +65,7 @@ export namespace RepresentationProvider {
export type AnyRepresentationProvider = RepresentationProvider<any, {}, Representation.State>
export const EmptyRepresentationProvider = {
const EmptyRepresentationProvider = {
label: '',
description: '',
factory: () => Representation.Empty,

View File

@@ -66,7 +66,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
}
function getLoci(pickingId?: PickingId) {
if (pickingId === undefined) return Structure.Loci(_structure);
if (pickingId === undefined) return Structure.Loci(_structure.target);
return visual ? visual.getLoci(pickingId) : EmptyLoci;
}

View File

@@ -24,6 +24,7 @@ const BallAndStickVisuals = {
export const BallAndStickParams = {
...ElementSphereParams,
traceOnly: PD.Boolean(false, { isHidden: true }), // not useful here
...IntraUnitBondCylinderParams,
...InterUnitBondCylinderParams,
unitKinds: getUnitKindsParam(['atomic']),
@@ -50,5 +51,11 @@ export const BallAndStickRepresentationProvider = StructureRepresentationProvide
defaultValues: PD.getDefaultValues(BallAndStickParams),
defaultColorTheme: { name: 'element-symbol' },
defaultSizeTheme: { name: 'physical' },
isApplicable: (structure: Structure) => structure.elementCount > 0
isApplicable: (structure: Structure) => structure.elementCount > 0,
getData: (structure: Structure, props: PD.Values<BallAndStickParams>) => {
return props.includeParent ? structure.asParent() : structure;
},
mustRecreate: (oldProps: PD.Values<BallAndStickParams>, newProps: PD.Values<BallAndStickParams>) => {
return oldProps.includeParent !== newProps.includeParent;
}
});

View File

@@ -25,6 +25,7 @@ export const EllipsoidParams = {
...EllipsoidMeshParams,
...IntraUnitBondCylinderParams,
...InterUnitBondCylinderParams,
adjustCylinderLength: PD.Boolean(false, { isHidden: true }), // not useful here
unitKinds: getUnitKindsParam(['atomic']),
sizeFactor: PD.Numeric(1, { min: 0.01, max: 10, step: 0.01 }),
sizeAspectRatio: PD.Numeric(0.1, { min: 0.01, max: 3, step: 0.01 }),
@@ -50,5 +51,11 @@ export const EllipsoidRepresentationProvider = StructureRepresentationProvider({
defaultValues: PD.getDefaultValues(EllipsoidParams),
defaultColorTheme: { name: 'element-symbol' },
defaultSizeTheme: { name: 'uniform' },
isApplicable: (structure: Structure) => structure.elementCount > 0 && structure.models.some(m => AtomSiteAnisotrop.Provider.isApplicable(m))
isApplicable: (structure: Structure) => structure.elementCount > 0 && structure.models.some(m => AtomSiteAnisotrop.Provider.isApplicable(m)),
getData: (structure: Structure, props: PD.Values<EllipsoidParams>) => {
return props.includeParent ? structure.asParent() : structure;
},
mustRecreate: (oldProps: PD.Values<EllipsoidParams>, newProps: PD.Values<EllipsoidParams>) => {
return oldProps.includeParent !== newProps.includeParent;
}
});

View File

@@ -46,5 +46,11 @@ export const LineRepresentationProvider = StructureRepresentationProvider({
defaultValues: PD.getDefaultValues(LineParams),
defaultColorTheme: { name: 'element-symbol' },
defaultSizeTheme: { name: 'uniform' },
isApplicable: (structure: Structure) => structure.elementCount > 0
isApplicable: (structure: Structure) => structure.elementCount > 0,
getData: (structure: Structure, props: PD.Values<LineParams>) => {
return props.includeParent ? structure.asParent() : structure;
},
mustRecreate: (oldProps: PD.Values<LineParams>, newProps: PD.Values<LineParams>) => {
return oldProps.includeParent !== newProps.includeParent;
}
});

View File

@@ -180,7 +180,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
}
function getLoci(pickingId?: PickingId) {
if (pickingId === undefined) return Structure.Loci(_structure);
if (pickingId === undefined) return Structure.Loci(_structure.target);
let loci: Loci = EmptyLoci;
visuals.forEach(({ visual }) => {
const _loci = visual.getLoci(pickingId);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -19,6 +19,7 @@ import { BondCylinderParams, BondIterator, getInterBondLoci, eachInterBond, make
import { Sphere3D } from '../../../mol-math/geometry';
import { Cylinders } from '../../../mol-geo/geometry/cylinders/cylinders';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { SortedArray } from '../../../mol-data/int/sorted-array';
const tmpRefPosBondIt = new Bond.ElementBondIterator();
function setRefPosition(pos: Vec3, structure: Structure, unit: Unit.Atomic, index: StructureElement.UnitIndex) {
@@ -39,10 +40,33 @@ function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme
const bonds = structure.interUnitBonds;
const { edgeCount, edges } = bonds;
const { sizeFactor, sizeAspectRatio } = props;
const { sizeFactor, sizeAspectRatio, adjustCylinderLength } = props;
const delta = Vec3();
let stub: undefined | ((edgeIndex: number) => boolean);
if (props.includeParent) {
const { child } = structure;
if (!child) throw new Error('expected child to exist');
stub = (edgeIndex: number) => {
const b = edges[edgeIndex];
const childUnitA = child.unitMap.get(b.unitA);
const childUnitB = child.unitMap.get(b.unitB);
const unitA = structure.unitMap.get(b.unitA);
const eA = unitA.elements[b.indexA];
const unitB = structure.unitMap.get(b.unitB);
const eB = unitB.elements[b.indexB];
return (
childUnitA && SortedArray.has(childUnitA.elements, eA) &&
(!childUnitB || !SortedArray.has(childUnitB.elements, eB))
);
};
}
const radius = (edgeIndex: number) => {
const b = edges[edgeIndex];
locB.aUnit = structure.unitMap.get(b.unitA);
@@ -92,19 +116,20 @@ function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme
const uA = structure.unitMap.get(b.unitA);
const uB = structure.unitMap.get(b.unitB);
const rA = radiusA(edgeIndex), rB = radiusB(edgeIndex);
const r = Math.min(rA, rB) * sizeAspectRatio;
const oA = Math.sqrt(Math.max(0, rA * rA - r * r)) - 0.05;
const oB = Math.sqrt(Math.max(0, rB * rB - r * r)) - 0.05;
uA.conformation.position(uA.elements[b.indexA], posA);
uB.conformation.position(uB.elements[b.indexB], posB);
if (oA <= 0.01 && oB <= 0.01) return;
if (adjustCylinderLength) {
const rA = radiusA(edgeIndex), rB = radiusB(edgeIndex);
const r = Math.min(rA, rB) * sizeAspectRatio;
const oA = Math.sqrt(Math.max(0, rA * rA - r * r)) - 0.05;
const oB = Math.sqrt(Math.max(0, rB * rB - r * r)) - 0.05;
if (oA <= 0.01 && oB <= 0.01) return;
Vec3.normalize(delta, Vec3.sub(delta, posB, posA));
Vec3.scaleAndAdd(posA, posA, delta, oA);
Vec3.scaleAndAdd(posB, posB, delta, -oB);
Vec3.normalize(delta, Vec3.sub(delta, posB, posA));
Vec3.scaleAndAdd(posA, posA, delta, oA);
Vec3.scaleAndAdd(posB, posB, delta, -oB);
}
},
style: (edgeIndex: number) => {
const o = edges[edgeIndex].props.order;
@@ -123,7 +148,8 @@ function getInterUnitBondCylinderBuilderProps(structure: Structure, theme: Theme
radius: (edgeIndex: number) => {
return radius(edgeIndex) * sizeAspectRatio;
},
ignore: makeInterBondIgnoreTest(structure, props)
ignore: makeInterBondIgnoreTest(structure, props),
stub
};
}
@@ -133,7 +159,8 @@ function createInterUnitBondCylinderImpostors(ctx: VisualContext, structure: Str
const builderProps = getInterUnitBondCylinderBuilderProps(structure, theme, props);
const m = createLinkCylinderImpostors(ctx, builderProps, props, cylinders);
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * props.sizeFactor);
const { child } = structure;
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * props.sizeFactor);
m.setBoundingSphere(sphere);
return m;
@@ -145,7 +172,8 @@ function createInterUnitBondCylinderMesh(ctx: VisualContext, structure: Structur
const builderProps = getInterUnitBondCylinderBuilderProps(structure, theme, props);
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * props.sizeFactor);
const { child } = structure;
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * props.sizeFactor);
m.setBoundingSphere(sphere);
return m;
@@ -158,6 +186,7 @@ export const InterUnitBondCylinderParams = {
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
sizeAspectRatio: PD.Numeric(2 / 3, { min: 0, max: 3, step: 0.01 }),
tryUseImpostor: PD.Boolean(true),
includeParent: PD.Boolean(false),
};
export type InterUnitBondCylinderParams = typeof InterUnitBondCylinderParams
@@ -183,8 +212,10 @@ export function InterUnitBondCylinderImpostorVisual(materialId: number): Complex
newProps.dashCount !== currentProps.dashCount ||
newProps.dashScale !== currentProps.dashScale ||
newProps.dashCap !== currentProps.dashCap ||
newProps.stubCap !== currentProps.stubCap ||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
);
},
mustRecreate: (structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
@@ -212,8 +243,10 @@ export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisu
newProps.dashCount !== currentProps.dashCount ||
newProps.dashScale !== currentProps.dashScale ||
newProps.dashCap !== currentProps.dashCap ||
newProps.stubCap !== currentProps.stubCap ||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
);
},
mustRecreate: (structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -97,7 +97,8 @@ function createInterUnitBondLines(ctx: VisualContext, structure: Structure, them
const l = createLinkLines(ctx, builderProps, props, lines);
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * sizeFactor);
const { child } = structure;
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * props.sizeFactor);
l.setBoundingSphere(sphere);
return l;
@@ -106,6 +107,7 @@ function createInterUnitBondLines(ctx: VisualContext, structure: Structure, them
export const InterUnitBondLineParams = {
...ComplexLinesParams,
...BondLineParams,
includeParent: PD.Boolean(false),
};
export type InterUnitBondLineParams = typeof InterUnitBondLineParams

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -21,23 +21,39 @@ import { Sphere3D } from '../../../mol-math/geometry';
import { IntAdjacencyGraph } from '../../../mol-math/graph';
import { WebGLContext } from '../../../mol-gl/webgl/context';
import { Cylinders } from '../../../mol-geo/geometry/cylinders/cylinders';
import { SortedArray } from '../../../mol-data/int';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const isBondType = BondType.is;
function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondCylinderParams>) {
const locE = StructureElement.Location.create(structure, unit);
const locB = Bond.Location(structure, unit, undefined, structure, unit, undefined);
const elements = unit.elements;
const bonds = unit.bonds;
const { edgeCount, a, b, edgeProps, offset } = bonds;
const { order: _order, flags: _flags } = edgeProps;
const { sizeFactor, sizeAspectRatio } = props;
const { sizeFactor, sizeAspectRatio, adjustCylinderLength } = props;
const vRef = Vec3(), delta = Vec3();
const pos = unit.conformation.invariantPosition;
let stub: undefined | ((edgeIndex: number) => boolean);
const locE = StructureElement.Location.create(structure, unit);
const locB = Bond.Location(structure, unit, undefined, structure, unit, undefined);
if (props.includeParent) {
const { child } = structure;
if (!child) throw new Error('expected child to exist');
const childUnit = child.unitMap.get(unit.id);
if (!childUnit) throw new Error('expected childUnit to exist');
stub = (edgeIndex: number) => {
const eA = elements[a[edgeIndex]];
const eB = elements[b[edgeIndex]];
return SortedArray.has(childUnit.elements, eA) && !SortedArray.has(childUnit.elements, eB);
};
}
const radius = (edgeIndex: number) => {
locB.aIndex = a[edgeIndex];
locB.bIndex = b[edgeIndex];
@@ -74,19 +90,20 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
return null;
},
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
const rA = radiusA(edgeIndex), rB = radiusB(edgeIndex);
const r = Math.min(rA, rB) * sizeAspectRatio;
const oA = Math.sqrt(Math.max(0, rA * rA - r * r)) - 0.05;
const oB = Math.sqrt(Math.max(0, rB * rB - r * r)) - 0.05;
pos(elements[a[edgeIndex]], posA);
pos(elements[b[edgeIndex]], posB);
if (oA <= 0.01 && oB <= 0.01) return;
if (adjustCylinderLength) {
const rA = radiusA(edgeIndex), rB = radiusB(edgeIndex);
const r = Math.min(rA, rB) * sizeAspectRatio;
const oA = Math.sqrt(Math.max(0, rA * rA - r * r)) - 0.05;
const oB = Math.sqrt(Math.max(0, rB * rB - r * r)) - 0.05;
if (oA <= 0.01 && oB <= 0.01) return;
Vec3.normalize(delta, Vec3.sub(delta, posB, posA));
Vec3.scaleAndAdd(posA, posA, delta, oA);
Vec3.scaleAndAdd(posB, posB, delta, -oB);
Vec3.normalize(delta, Vec3.sub(delta, posB, posA));
Vec3.scaleAndAdd(posA, posA, delta, oA);
Vec3.scaleAndAdd(posB, posB, delta, -oB);
}
},
style: (edgeIndex: number) => {
const o = _order[edgeIndex];
@@ -105,7 +122,8 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
radius: (edgeIndex: number) => {
return radius(edgeIndex) * sizeAspectRatio;
},
ignore: makeIntraBondIgnoreTest(unit, props)
ignore: makeIntraBondIgnoreTest(structure, unit, props),
stub
};
}
@@ -113,10 +131,14 @@ function createIntraUnitBondCylinderImpostors(ctx: VisualContext, unit: Unit, st
if (!Unit.isAtomic(unit)) return Cylinders.createEmpty(cylinders);
if (!unit.bonds.edgeCount) return Cylinders.createEmpty(cylinders);
const { child } = structure;
const childUnit = child?.unitMap.get(unit.id);
if (child && !childUnit) return Cylinders.createEmpty(cylinders);
const builderProps = getIntraUnitBondCylinderBuilderProps(unit, structure, theme, props);
const c = createLinkCylinderImpostors(ctx, builderProps, props, cylinders);
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor);
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * props.sizeFactor);
c.setBoundingSphere(sphere);
return c;
@@ -126,10 +148,14 @@ function createIntraUnitBondCylinderMesh(ctx: VisualContext, unit: Unit, structu
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
if (!unit.bonds.edgeCount) return Mesh.createEmpty(mesh);
const { child } = structure;
const childUnit = child?.unitMap.get(unit.id);
if (child && !childUnit) return Mesh.createEmpty(mesh);
const builderProps = getIntraUnitBondCylinderBuilderProps(unit, structure, theme, props);
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * props.sizeFactor);
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * props.sizeFactor);
m.setBoundingSphere(sphere);
return m;
@@ -142,6 +168,7 @@ export const IntraUnitBondCylinderParams = {
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
sizeAspectRatio: PD.Numeric(2 / 3, { min: 0, max: 3, step: 0.01 }),
tryUseImpostor: PD.Boolean(true),
includeParent: PD.Boolean(false),
};
export type IntraUnitBondCylinderParams = typeof IntraUnitBondCylinderParams
@@ -167,8 +194,10 @@ export function IntraUnitBondCylinderImpostorVisual(materialId: number): UnitsVi
newProps.dashCount !== currentProps.dashCount ||
newProps.dashScale !== currentProps.dashScale ||
newProps.dashCap !== currentProps.dashCap ||
newProps.stubCap !== currentProps.stubCap ||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
);
const newUnit = newStructureGroup.group.units[0];
@@ -207,8 +236,10 @@ export function IntraUnitBondCylinderMeshVisual(materialId: number): UnitsVisual
newProps.dashCount !== currentProps.dashCount ||
newProps.dashScale !== currentProps.dashScale ||
newProps.dashCap !== currentProps.dashCap ||
newProps.stubCap !== currentProps.stubCap ||
!arrayEqual(newProps.includeTypes, currentProps.includeTypes) ||
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
!arrayEqual(newProps.excludeTypes, currentProps.excludeTypes) ||
newProps.adjustCylinderLength !== currentProps.adjustCylinderLength
);
const newUnit = newStructureGroup.group.units[0];

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -25,6 +25,14 @@ const isBondType = BondType.is;
function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<IntraUnitBondLineParams>, lines?: Lines) {
if (!Unit.isAtomic(unit)) return Lines.createEmpty(lines);
const { child } = structure;
const childUnit = child?.unitMap.get(unit.id);
if (child && !childUnit) return Lines.createEmpty(lines);
if (props.includeParent) {
if (!child) throw new Error('expected child to exist');
}
const location = StructureElement.Location.create(structure, unit);
const elements = unit.elements;
@@ -82,12 +90,12 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
const sizeB = theme.size.size(location);
return Math.min(sizeA, sizeB) * sizeFactor;
},
ignore: makeIntraBondIgnoreTest(unit, props)
ignore: makeIntraBondIgnoreTest(structure, unit, props)
};
const l = createLinkLines(ctx, builderProps, props, lines);
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * sizeFactor);
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * sizeFactor);
l.setBoundingSphere(sphere);
return l;
@@ -96,6 +104,7 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
export const IntraUnitBondLineParams = {
...UnitsLinesParams,
...BondLineParams,
includeParent: PD.Boolean(false),
};
export type IntraUnitBondLineParams = typeof IntraUnitBondLineParams

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -29,17 +29,20 @@ export type ElementPointParams = typeof ElementPointParams
export function createElementPoint(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<ElementPointParams>, points: Points) {
// TODO sizeFactor
const { child } = structure;
if (child && !child.unitMap.get(unit.id)) return Points.createEmpty(points);
const elements = unit.elements;
const n = elements.length;
const builder = PointsBuilder.create(n, n / 10, points);
const p = Vec3();
const pos = unit.conformation.invariantPosition;
const ignore = makeElementIgnoreTest(unit, props);
const ignore = makeElementIgnoreTest(structure, unit, props);
if (ignore) {
for (let i = 0; i < n; ++i) {
if (ignore(unit, elements[i])) continue;
if (ignore(elements[i])) continue;
pos(elements[i], p);
builder.add(p[0], p[1], p[2], i);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -22,6 +22,7 @@ import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
import { Sphere3D } from '../../../mol-math/geometry';
import { BaseGeometry } from '../../../mol-geo/geometry/base';
import { SortedArray } from '../../../mol-data/int/sorted-array';
export const EllipsoidMeshParams = {
...UnitsMeshParams,
@@ -57,7 +58,11 @@ export interface EllipsoidMeshProps {
}
export function createEllipsoidMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: EllipsoidMeshProps, mesh?: Mesh): Mesh {
const { detail, sizeFactor } = props;
const { child } = structure;
const childUnit = child?.unitMap.get(unit.id);
if (child && !childUnit) return Mesh.createEmpty(mesh);
const { detail, sizeFactor, ignoreHydrogens } = props;
const { elements, model } = unit;
const { atomicNumber } = unit.model.atomicHierarchy.derived.atom;
@@ -84,7 +89,8 @@ export function createEllipsoidMesh(ctx: VisualContext, unit: Unit, structure: S
const ei = elements[i];
const ai = elementToAnsiotrop[ei];
if (ai === -1) continue;
if (props.ignoreHydrogens && isH(atomicNumber, ei)) continue;
if (((!!childUnit && !SortedArray.has(childUnit.elements, ei))) ||
(ignoreHydrogens && isH(atomicNumber, ei))) continue;
l.element = ei;
pos(ei, v);
@@ -111,7 +117,7 @@ export function createEllipsoidMesh(ctx: VisualContext, unit: Unit, structure: S
const m = MeshBuilder.getMesh(builderState);
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * sizeFactor);
const sphere = Sphere3D.expand(Sphere3D(), (childUnit || unit).boundary.sphere, 1 * sizeFactor);
m.setBoundingSphere(sphere);
return m;

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