mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 21:34:23 +08:00
Compare commits
97 Commits
v2.0.0-dev
...
v2.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a76a3fa64 | ||
|
|
71bf4e21f5 | ||
|
|
e0d36c30d3 | ||
|
|
b53debcfef | ||
|
|
d0705ac226 | ||
|
|
e01eacb3fe | ||
|
|
d4102b476b | ||
|
|
83ce17174a | ||
|
|
18023d7f26 | ||
|
|
a8541d5967 | ||
|
|
8b21818f2e | ||
|
|
0b290247dc | ||
|
|
fb5010e962 | ||
|
|
178789d327 | ||
|
|
4fae526073 | ||
|
|
05f1d8085a | ||
|
|
38bbabd742 | ||
|
|
3ab958a93c | ||
|
|
f59d589a30 | ||
|
|
11f7e54704 | ||
|
|
16ebd8266e | ||
|
|
7a796a4d3d | ||
|
|
1cbb915962 | ||
|
|
80486d58c3 | ||
|
|
81bc116c4d | ||
|
|
4249064dd1 | ||
|
|
028c02f50d | ||
|
|
76e97d7b59 | ||
|
|
ad1181a75b | ||
|
|
5d683462fb | ||
|
|
42422bb0ea | ||
|
|
861e5c3e97 | ||
|
|
614cffda96 | ||
|
|
2e0379d202 | ||
|
|
b5cfdcd2a3 | ||
|
|
c00de6fde0 | ||
|
|
da3a8e56f3 | ||
|
|
103d6fe775 | ||
|
|
5df55e6bf7 | ||
|
|
3b285086d4 | ||
|
|
91793bc3cc | ||
|
|
fa3828e820 | ||
|
|
31ba8212da | ||
|
|
fe27d8e134 | ||
|
|
83dcdfdc4b | ||
|
|
f9aaabc1f7 | ||
|
|
034370b44c | ||
|
|
b87666df3e | ||
|
|
c98c3228fe | ||
|
|
9419980dfc | ||
|
|
42d60420e5 | ||
|
|
5b1df333a7 | ||
|
|
0bb376706d | ||
|
|
eca7da2c72 | ||
|
|
b0bdb3ddb6 | ||
|
|
3180d7c305 | ||
|
|
2faa821c50 | ||
|
|
7f355ae501 | ||
|
|
7f79ff9ff2 | ||
|
|
02de871c59 | ||
|
|
00cb783d4c | ||
|
|
c925919ee5 | ||
|
|
324820890a | ||
|
|
2687b29d4d | ||
|
|
7084aaee1a | ||
|
|
520a2f7850 | ||
|
|
9264987817 | ||
|
|
b736ed3ea4 | ||
|
|
166d660fa7 | ||
|
|
b8249cde4d | ||
|
|
f12f5eca90 | ||
|
|
cd3798b46f | ||
|
|
0240e54737 | ||
|
|
6a735d902e | ||
|
|
57a942ecb5 | ||
|
|
f67605a398 | ||
|
|
aaafa1d5ad | ||
|
|
a1d9a77653 | ||
|
|
f2f1181af3 | ||
|
|
864befc48a | ||
|
|
73f6793bd8 | ||
|
|
87ee9d88f2 | ||
|
|
b1e245e913 | ||
|
|
78c0471f39 | ||
|
|
c57b9b9214 | ||
|
|
34f33c5bbb | ||
|
|
57da2a7ebb | ||
|
|
d45d5c0e55 | ||
|
|
42ed425e65 | ||
|
|
f752ee5094 | ||
|
|
044c796942 | ||
|
|
0aabbcfaab | ||
|
|
24274cc53b | ||
|
|
870cef2fd4 | ||
|
|
bf7b1f5bfd | ||
|
|
9c9a0312db | ||
|
|
724fa2a7cd |
@@ -1 +1 @@
|
||||
tsconfig.commonjs.buildinfo
|
||||
tsconfig.commonjs.tsbuildinfo
|
||||
39
CHANGELOG.md
Normal file
39
CHANGELOG.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Change Log
|
||||
All notable changes to this project will be documented in this file, following the suggestions of [Keep a CHANGELOG](http://keepachangelog.com/). This project adheres to [Semantic Versioning](http://semver.org/) for its most widely used - and defacto - public interfaces.
|
||||
|
||||
Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here.
|
||||
|
||||
## [v2.0.4] - 2021-04-20
|
||||
|
||||
- [WIP] Mesh export extension
|
||||
- ``Structure.eachAtomicHierarchyElement`` (#161)
|
||||
- Fixed reading multi-line values in SDF format
|
||||
- Fixed Measurements UI labels (#166)
|
||||
|
||||
## [v2.0.3] - 2021-04-09
|
||||
### Added
|
||||
- Support for ``ColorTheme.palette`` designed for providing gradient-like coloring.
|
||||
|
||||
### Changed
|
||||
- [Breaking] The `zip` function is now asynchronous and expects a `RuntimeContext`. Also added `Zip()` returning a `Task`.
|
||||
- [Breaking] Add ``CubeGridFormat`` in ``alpha-orbitals`` extension.
|
||||
|
||||
## [v2.0.2] - 2021-03-29
|
||||
### Added
|
||||
- `Canvas3D.getRenderObjects`.
|
||||
- [WIP] Animate state interpolating, including model trajectories
|
||||
|
||||
### Changed
|
||||
- Recognise MSE, SEP, TPO, PTR and PCA as non-standard amino-acids.
|
||||
|
||||
### Fixed
|
||||
- VolumeFromDensityServerCif transform label
|
||||
|
||||
|
||||
## [v2.0.1] - 2021-03-23
|
||||
### Fixed
|
||||
- Exclude tsconfig.commonjs.tsbuildinfo from npm bundle
|
||||
|
||||
|
||||
## [v2.0.0] - 2021-03-23
|
||||
Too many changes to list as this is the start of the changelog... Notably, default exports are now forbidden.
|
||||
15
README.md
15
README.md
@@ -5,15 +5,12 @@
|
||||
|
||||
# Mol*
|
||||
|
||||
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that will serve as a basis for the next-generation data delivery and analysis tools for macromolecular structure data. This is a collaboration between PDBe and RCSB PDB teams and the development will be open-source and available to anyone who wants to use it for developing visualization tools for macromolecular structure data available from [PDB](https://www.wwpdb.org/) and other institutions.
|
||||
The goal of **Mol\*** (*/'mol-star/*) is to provide a technology stack that serves as a basis for the next-generation data delivery and analysis tools for (not only) macromolecular structure data. Mol* development was jointly initiated by PDBe and RCSB PDB to combine and build on the strengths of [LiteMol](https://litemol.org) (developed by PDBe) and [NGL](https://nglviewer.org) (developed by RCSB PDB) viewers.
|
||||
|
||||
This particular project is the implementation of this technology (still under development).
|
||||
|
||||
*If you are looking for the "MOLeculAR structure annoTator", that package is now available on NPM as [MolArt](https://www.npmjs.com/package/molart).*
|
||||
## Project Structure Overview
|
||||
|
||||
## Project Overview
|
||||
|
||||
The core of Mol* currently consists of these modules (see under `src/`):
|
||||
The core of Mol* consists of these modules (see under `src/`):
|
||||
|
||||
- `mol-task` Computation abstraction with progress tracking and cancellation support.
|
||||
- `mol-data` Collections (integer-based sets, interface to columns/tables, etc.)
|
||||
@@ -29,7 +26,6 @@ The core of Mol* currently consists of these modules (see under `src/`):
|
||||
- `mol-gl` A wrapper around WebGL.
|
||||
- `mol-canvas3d` A low-level 3d view component. Uses `mol-geo` to generate geometries.
|
||||
- `mol-state` State representation tree with state saving and automatic updates.
|
||||
- `mol-app` Components for building UIs.
|
||||
- `mol-plugin` Allow to define modular Mol* plugin instances utilizing `mol-state` and `mol-canvas3d`.
|
||||
- `mol-plugin-state` State transformations, builders, and managers.
|
||||
- `mol-plugin-ui` React-based user interface for the Mol* plugin. Some components of the UI are usable outside the main plugin and can be integrated into 3rd party solutions.
|
||||
@@ -41,7 +37,7 @@ Moreover, the project contains the implementation of `servers`, including
|
||||
- `servers/volume` A tool for accessing volumetric experimental data related to molecular structures.
|
||||
- `servers/plugin-state` A basic server to store Mol* Plugin states.
|
||||
|
||||
The project also contains performance tests (`perf-tests`), `examples`, and basic proof of concept `cli` apps (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
|
||||
The project also contains performance tests (`perf-tests`), `examples`, and `cli` apps (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
|
||||
|
||||
## Previous Work
|
||||
This project builds on experience from previous solutions:
|
||||
@@ -169,9 +165,6 @@ To get syntax highlighting for shader and graphql files add the following to Vis
|
||||
## Contributing
|
||||
Just open an issue or make a pull request. All contributions are welcome.
|
||||
|
||||
## Roadmap
|
||||
Continually develop this prototype project. As individual modules become stable, make them into standalone libraries.
|
||||
|
||||
## Funding
|
||||
Funding sources include but are not limited to:
|
||||
* [RCSB PDB](https://www.rcsb.org) funding by a grant [DBI-1338415; PI: SK Burley] from the NSF, the NIH, and the US DoE
|
||||
|
||||
@@ -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)
|
||||
|
||||
6280
package-lock.json
generated
6280
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.0.0-dev.8",
|
||||
"version": "2.0.4",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -14,6 +14,7 @@
|
||||
"lint": "eslint .",
|
||||
"lint-fix": "eslint . --fix",
|
||||
"test": "npm run lint && jest",
|
||||
"jest": "jest",
|
||||
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
|
||||
"build-viewer": "npm run build-tsc && npm run build-extra && npm run build-webpack-viewer",
|
||||
"build-tsc": "concurrently \"tsc --incremental\" \"tsc --build tsconfig.commonjs.json --incremental\"",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -49,11 +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', {
|
||||
layoutShowControls: !hideControls,
|
||||
viewportShowExpand: false,
|
||||
collapseLeftPanel: collapseLeftPanel,
|
||||
pdbProvider: pdbProvider || 'pdbe',
|
||||
emdbProvider: emdbProvider || 'pdbe',
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import { CellPack } from '../../extensions/cellpack';
|
||||
import { DnatcoConfalPyramids } from '../../extensions/dnatco';
|
||||
import { G3DFormat, G3dProvider } from '../../extensions/g3d/format';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { GeometryExport } from '../../extensions/geo-export';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
|
||||
@@ -55,7 +56,8 @@ const Extensions = {
|
||||
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
|
||||
'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation),
|
||||
'g3d': PluginSpec.Behavior(G3DFormat),
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export)
|
||||
'mp4-export': PluginSpec.Behavior(Mp4Export),
|
||||
'geo-export': PluginSpec.Behavior(GeometryExport)
|
||||
};
|
||||
|
||||
const DefaultViewerOptions = {
|
||||
@@ -68,6 +70,7 @@ const DefaultViewerOptions = {
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: true,
|
||||
layoutShowLeftPanel: true,
|
||||
collapseLeftPanel: false,
|
||||
disableAntialiasing: false,
|
||||
pixelScale: 1,
|
||||
enableWboit: true,
|
||||
@@ -106,6 +109,12 @@ export class Viewer {
|
||||
isExpanded: o.layoutIsExpanded,
|
||||
showControls: o.layoutShowControls,
|
||||
controlsDisplay: o.layoutControlsDisplay,
|
||||
regionState: {
|
||||
bottom: 'full',
|
||||
left: o.collapseLeftPanel ? 'collapsed' : 'full',
|
||||
right: 'full',
|
||||
top: 'full',
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -174,7 +174,8 @@ export class AlphaOrbitalsExample {
|
||||
kind,
|
||||
relativeIsovalue: this.state.value.isoValue,
|
||||
pickable: false,
|
||||
xrayShaded: true
|
||||
xrayShaded: true,
|
||||
tryUseGpu: false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
import { PluginUIComponent } from '../../mol-plugin-ui/base';
|
||||
import * as React from 'react';
|
||||
|
||||
export class CustomToastMessage extends PluginUIComponent {
|
||||
render() {
|
||||
|
||||
51
src/examples/basic-wrapper/custom-theme.ts
Normal file
51
src/examples/basic-wrapper/custom-theme.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { isPositionLocation } from '../../mol-geo/util/location-iterator';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { ColorTheme } from '../../mol-theme/color';
|
||||
import { ThemeDataContext } from '../../mol-theme/theme';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
export function CustomColorTheme(
|
||||
ctx: ThemeDataContext,
|
||||
props: PD.Values<{}>
|
||||
): ColorTheme<{}> {
|
||||
const { radius, center } = ctx.structure?.boundary.sphere!;
|
||||
const radiusSq = Math.max(radius * radius, 0.001);
|
||||
const scale = ColorTheme.PaletteScale;
|
||||
|
||||
return {
|
||||
factory: CustomColorTheme,
|
||||
granularity: 'vertex',
|
||||
color: location => {
|
||||
if (!isPositionLocation(location)) return ColorNames.black;
|
||||
const dist = Vec3.squaredDistance(location.position, center);
|
||||
const t = Math.min(dist / radiusSq, 1);
|
||||
return ((t * scale) | 0) as Color;
|
||||
},
|
||||
palette: {
|
||||
filter: 'nearest',
|
||||
colors: [
|
||||
ColorNames.red,
|
||||
ColorNames.pink,
|
||||
ColorNames.violet,
|
||||
ColorNames.orange,
|
||||
ColorNames.yellow,
|
||||
ColorNames.green,
|
||||
ColorNames.blue
|
||||
]
|
||||
},
|
||||
props: props,
|
||||
description: '',
|
||||
};
|
||||
}
|
||||
|
||||
export const CustomColorThemeProvider: ColorTheme.Provider<{}, 'basic-wrapper-custom-color-theme'> = {
|
||||
name: 'basic-wrapper-custom-color-theme',
|
||||
label: 'Custom Color Theme',
|
||||
category: ColorTheme.Category.Misc,
|
||||
factory: CustomColorTheme,
|
||||
getParams: () => ({}),
|
||||
defaultValues: { },
|
||||
isApplicable: (ctx: ThemeDataContext) => true,
|
||||
};
|
||||
@@ -97,6 +97,7 @@
|
||||
addHeader('Misc');
|
||||
|
||||
addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes());
|
||||
addControl('Apply Custom Theme', () => BasicMolStarWrapper.coloring.applyCustomTheme());
|
||||
addControl('Default Coloring', () => BasicMolStarWrapper.coloring.applyDefault());
|
||||
|
||||
addHeader('Interactivity');
|
||||
|
||||
@@ -18,6 +18,7 @@ import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { StripedResidues } from './coloring';
|
||||
import { CustomToastMessage } from './controls';
|
||||
import { CustomColorThemeProvider } from './custom-theme';
|
||||
import './index.html';
|
||||
import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData } from './superposition';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
@@ -42,6 +43,7 @@ class BasicWrapper {
|
||||
});
|
||||
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!);
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(CustomColorThemeProvider);
|
||||
this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
|
||||
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
|
||||
}
|
||||
@@ -103,6 +105,13 @@ class BasicWrapper {
|
||||
}
|
||||
});
|
||||
},
|
||||
applyCustomTheme: async () => {
|
||||
this.plugin.dataTransaction(async () => {
|
||||
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(s.components, { color: CustomColorThemeProvider.name as any });
|
||||
}
|
||||
});
|
||||
},
|
||||
applyDefault: async () => {
|
||||
this.plugin.dataTransaction(async () => {
|
||||
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* @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';
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Grid } from '../../mol-model/volume';
|
||||
import { SphericalBasisOrder } from './spherical-functions';
|
||||
import { Box3D, RegularGrid3d } from '../../mol-math/geometry';
|
||||
import { arrayMin, arrayMax, arrayRms, arrayMean } from '../../mol-util/array';
|
||||
import { ModelFormat } from '../../mol-model-formats/format';
|
||||
|
||||
// Note: generally contracted gaussians are currently not supported.
|
||||
export interface SphericalElectronShell {
|
||||
@@ -59,6 +60,17 @@ export interface CubeGrid {
|
||||
isovalues?: { negative?: number; positive?: number };
|
||||
}
|
||||
|
||||
export type CubeGridFormat = ModelFormat<CubeGrid>;
|
||||
|
||||
// eslint-disable-next-line
|
||||
export function CubeGridFormat(grid: CubeGrid): CubeGridFormat {
|
||||
return { name: 'custom grid', kind: 'cube-grid', data: grid };
|
||||
}
|
||||
|
||||
export function isCubeGridData(f: ModelFormat): f is CubeGridFormat {
|
||||
return f.kind === 'cube-grid';
|
||||
}
|
||||
|
||||
export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo {
|
||||
const geometry = params.basis.atoms.map(a => a.center);
|
||||
const { gridSpacing: spacing, boxExpand: expand } = params;
|
||||
|
||||
@@ -17,7 +17,7 @@ import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers
|
||||
import { StateTransformer } from '../../mol-state';
|
||||
import { Theme } from '../../mol-theme/theme';
|
||||
import { VolumeRepresentation3DHelpers } from '../../mol-plugin-state/transforms/representation';
|
||||
import { AlphaOrbital, Basis, CubeGrid } from './data-model';
|
||||
import { AlphaOrbital, Basis, CubeGrid, CubeGridFormat, isCubeGridData } from './data-model';
|
||||
import { createSphericalCollocationDensityGrid } from './density';
|
||||
import { Tensor } from '../../mol-math/linear-algebra';
|
||||
|
||||
@@ -114,7 +114,7 @@ export const CreateOrbitalVolume = PluginStateTransform.BuiltIn({
|
||||
}, a.data.orbitals[params.index], plugin.canvas3d?.webgl).runInContext(ctx);
|
||||
const volume: Volume = {
|
||||
grid: data.grid,
|
||||
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
|
||||
sourceData: CubeGridFormat(data),
|
||||
customProperties: new CustomProperties(),
|
||||
_propertyData: Object.create(null),
|
||||
};
|
||||
@@ -146,7 +146,7 @@ export const CreateOrbitalDensityVolume = PluginStateTransform.BuiltIn({
|
||||
}, a.data.orbitals, plugin.canvas3d?.webgl).runInContext(ctx);
|
||||
const volume: Volume = {
|
||||
grid: data.grid,
|
||||
sourceData: { name: 'custom grid', kind: 'alpha-orbitals', data },
|
||||
sourceData: CubeGridFormat(data),
|
||||
customProperties: new CustomProperties(),
|
||||
_propertyData: Object.create(null),
|
||||
};
|
||||
@@ -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;
|
||||
@@ -208,9 +210,9 @@ export const CreateOrbitalRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
});
|
||||
|
||||
function volumeParams(plugin: PluginContext, volume: PluginStateObject.Volume.Data, params: StateTransformer.Params<typeof CreateOrbitalRepresentation3D>) {
|
||||
if (volume.data.sourceData.kind !== 'alpha-orbitals') throw new Error('Invalid data source kind.');
|
||||
if (!isCubeGridData(volume.data.sourceData)) throw new Error('Invalid data source kind.');
|
||||
|
||||
const { isovalues } = volume.data.sourceData.data as CubeGrid;
|
||||
const { isovalues } = volume.data.sourceData.data;
|
||||
if (!isovalues) throw new Error('Isovalues are not computed.');
|
||||
|
||||
const value = isovalues[params.kind];
|
||||
@@ -229,7 +231,7 @@ function volumeParams(plugin: PluginContext, volume: PluginStateObject.Volume.Da
|
||||
colorParams: { value: params.color }
|
||||
} : {
|
||||
type: 'isosurface',
|
||||
typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded },
|
||||
typeParams: { isoValue: { kind: 'absolute', absoluteValue: (value ?? 1000) * params.relativeIsovalue }, alpha: params.alpha, xrayShaded: params.xrayShaded, tryUseGpu: params.tryUseGpu },
|
||||
color: 'uniform',
|
||||
colorParams: { value: params.color }
|
||||
});
|
||||
|
||||
@@ -121,7 +121,7 @@ const MembraneOrientation3D = PluginStateTransform.BuiltIn({
|
||||
await MembraneOrientationProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
|
||||
const repr = MembraneOrientationRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => MembraneOrientationParams);
|
||||
await repr.createOrUpdate(params, a.data).runInContext(ctx);
|
||||
return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: 'Membrane Orientation' });
|
||||
return new PluginStateObject.Shape.Representation3D({ repr, sourceData: a.data }, { label: 'Membrane Orientation' });
|
||||
});
|
||||
},
|
||||
update({ a, b, newParams }, plugin: PluginContext) {
|
||||
@@ -129,6 +129,7 @@ const MembraneOrientation3D = PluginStateTransform.BuiltIn({
|
||||
await MembraneOrientationProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -414,7 +414,7 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
|
||||
const structure = new Structure(units);
|
||||
const structure = Structure.create(units);
|
||||
for( let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
|
||||
}
|
||||
|
||||
69
src/extensions/geo-export/controls.ts
Normal file
69
src/extensions/geo-export/controls.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
*/
|
||||
|
||||
import { PluginComponent } from '../../mol-plugin-state/component';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ObjExporter } from './export';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { StateSelection } from '../../mol-state';
|
||||
import { SetUtils } from '../../mol-util/set';
|
||||
import { zip } from '../../mol-util/zip/zip';
|
||||
|
||||
export class GeometryControls extends PluginComponent {
|
||||
getFilename() {
|
||||
const models = this.plugin.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Model)).map(s => s.obj!.data);
|
||||
const uniqueIds = new Set<string>();
|
||||
models.forEach(m => uniqueIds.add(m.entryId.toUpperCase()));
|
||||
const idString = SetUtils.toArray(uniqueIds).join('-');
|
||||
return `${idString || 'molstar-model'}`;
|
||||
}
|
||||
|
||||
exportObj() {
|
||||
const task = Task.create('Export OBJ', async ctx => {
|
||||
try {
|
||||
const renderObjects = this.plugin.canvas3d?.getRenderObjects()!;
|
||||
|
||||
const filename = this.getFilename();
|
||||
const objExporter = new ObjExporter(filename);
|
||||
for (let i = 0, il = renderObjects.length; i < il; ++i) {
|
||||
await ctx.update({ message: `Exporting object ${i}/${il}` });
|
||||
await objExporter.add(renderObjects[i], ctx);
|
||||
}
|
||||
const { obj, mtl } = objExporter.getData();
|
||||
|
||||
const asciiWrite = (data: Uint8Array, str: string) => {
|
||||
for (let i = 0, il = str.length; i < il; ++i) {
|
||||
data[i] = str.charCodeAt(i);
|
||||
}
|
||||
};
|
||||
const objData = new Uint8Array(obj.length);
|
||||
asciiWrite(objData, obj);
|
||||
const mtlData = new Uint8Array(mtl.length);
|
||||
asciiWrite(mtlData, mtl);
|
||||
|
||||
const zipDataObj = {
|
||||
[filename + '.obj']: objData,
|
||||
[filename + '.mtl']: mtlData
|
||||
};
|
||||
const zipData = await zip(ctx, zipDataObj);
|
||||
return {
|
||||
zipData,
|
||||
filename: filename + '.zip'
|
||||
};
|
||||
} catch (e) {
|
||||
this.plugin.log.error('' + e);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
return this.plugin.runTask(task, { useOverlay: true });
|
||||
}
|
||||
|
||||
constructor(private plugin: PluginContext) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
321
src/extensions/geo-export/export.ts
Normal file
321
src/extensions/geo-export/export.ts
Normal file
@@ -0,0 +1,321 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
*/
|
||||
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
import { MeshValues } from '../../mol-gl/renderable/mesh';
|
||||
import { LinesValues } from '../../mol-gl/renderable/lines';
|
||||
import { PointsValues } from '../../mol-gl/renderable/points';
|
||||
import { SpheresValues } from '../../mol-gl/renderable/spheres';
|
||||
import { CylindersValues } from '../../mol-gl/renderable/cylinders';
|
||||
import { BaseValues, SizeValues } from '../../mol-gl/renderable/schema';
|
||||
import { TextureImage } from '../../mol-gl/renderable/util';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { Vec3, Mat3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
import { Color } from '../../mol-util/color/color';
|
||||
import { decodeFloatRGB } from '../../mol-util/float-packing';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3fromArray = Vec3.fromArray;
|
||||
const v3transformMat4 = Vec3.transformMat4;
|
||||
const v3transformMat3 = Vec3.transformMat3;
|
||||
const mat3directionTransform = Mat3.directionTransform;
|
||||
|
||||
type RenderObjectExportData = {
|
||||
[k: string]: string | Uint8Array | undefined
|
||||
}
|
||||
|
||||
interface RenderObjectExporter<D extends RenderObjectExportData> {
|
||||
add(renderObject: GraphicsRenderObject, ctx: RuntimeContext): Promise<void> | undefined
|
||||
getData(): D
|
||||
}
|
||||
|
||||
// http://paulbourke.net/dataformats/obj/
|
||||
// http://paulbourke.net/dataformats/mtl/
|
||||
|
||||
export type ObjData = {
|
||||
obj: string
|
||||
mtl: string
|
||||
}
|
||||
|
||||
export class ObjExporter implements RenderObjectExporter<ObjData> {
|
||||
private obj = StringBuilder.create();
|
||||
private mtl = StringBuilder.create();
|
||||
private vertexOffset = 0;
|
||||
private currentColor: Color | undefined;
|
||||
private currentAlpha: number | undefined;
|
||||
private materialSet = new Set<string>();
|
||||
|
||||
private static getSizeFromTexture(tSize: TextureImage<Uint8Array>, i: number): number {
|
||||
const r = tSize.array[i * 3];
|
||||
const g = tSize.array[i * 3 + 1];
|
||||
const b = tSize.array[i * 3 + 2];
|
||||
return decodeFloatRGB(r, g, b);
|
||||
}
|
||||
|
||||
private static getSize(values: BaseValues & SizeValues, instanceIndex: number, group: number): number {
|
||||
const tSize = values.tSize.ref.value;
|
||||
let size = 0;
|
||||
switch (values.dSizeType.ref.value) {
|
||||
case 'uniform':
|
||||
size = values.uSize.ref.value;
|
||||
break;
|
||||
case 'instance':
|
||||
size = ObjExporter.getSizeFromTexture(tSize, instanceIndex) / 100;
|
||||
break;
|
||||
case 'group':
|
||||
size = ObjExporter.getSizeFromTexture(tSize, group) / 100;
|
||||
break;
|
||||
case 'groupInstance':
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
size = ObjExporter.getSizeFromTexture(tSize, instanceIndex * groupCount + group) / 100;
|
||||
break;
|
||||
}
|
||||
return size * values.uSizeFactor.ref.value;
|
||||
}
|
||||
|
||||
private updateMaterial(color: Color, alpha: number) {
|
||||
if (this.currentColor === color && this.currentAlpha === alpha) return;
|
||||
|
||||
this.currentColor = color;
|
||||
this.currentAlpha = alpha;
|
||||
const material = Color.toHexString(color) + alpha;
|
||||
StringBuilder.writeSafe(this.obj, `usemtl ${material}`);
|
||||
StringBuilder.newline(this.obj);
|
||||
if (!this.materialSet.has(material)) {
|
||||
this.materialSet.add(material);
|
||||
const [r, g, b] = Color.toRgbNormalized(color);
|
||||
const mtl = this.mtl;
|
||||
StringBuilder.writeSafe(mtl, `newmtl ${material}\n`);
|
||||
StringBuilder.writeSafe(mtl, 'illum 2\n'); // illumination model
|
||||
StringBuilder.writeSafe(mtl, 'Ns 163\n'); // specular exponent
|
||||
StringBuilder.writeSafe(mtl, 'Ni 0.001\n'); // optical density a.k.a. index of refraction
|
||||
StringBuilder.writeSafe(mtl, 'Ka 0 0 0\n'); // ambient reflectivity
|
||||
StringBuilder.writeSafe(mtl, 'Kd '); // diffuse reflectivity
|
||||
StringBuilder.writeFloat(mtl, r, 1000);
|
||||
StringBuilder.whitespace1(mtl);
|
||||
StringBuilder.writeFloat(mtl, g, 1000);
|
||||
StringBuilder.whitespace1(mtl);
|
||||
StringBuilder.writeFloat(mtl, b, 1000);
|
||||
StringBuilder.newline(mtl);
|
||||
StringBuilder.writeSafe(mtl, 'Ks 0.25 0.25 0.25\n'); // specular reflectivity
|
||||
StringBuilder.writeSafe(mtl, 'd '); // dissolve
|
||||
StringBuilder.writeFloat(mtl, alpha, 1000);
|
||||
StringBuilder.newline(mtl);
|
||||
}
|
||||
}
|
||||
|
||||
private async addMeshWithColors(vertices: Float32Array, normals: Float32Array, indices: Uint32Array, groups: Float32Array, vertexCount: number, drawCount: number, values: BaseValues, instanceIndex: number, ctx: RuntimeContext) {
|
||||
const obj = this.obj;
|
||||
const t = Mat4();
|
||||
const n = Mat3();
|
||||
const tmpV = Vec3();
|
||||
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const tColor = values.tColor.ref.value.array;
|
||||
const uAlpha = values.uAlpha.ref.value;
|
||||
const aTransform = values.aTransform.ref.value;
|
||||
|
||||
Mat4.fromArray(t, aTransform, instanceIndex * 16);
|
||||
mat3directionTransform(n, t);
|
||||
|
||||
const currentProgress = (vertexCount * 2 + drawCount) * instanceIndex;
|
||||
await ctx.update({ isIndeterminate: false, current: currentProgress, max: (vertexCount * 2 + drawCount) * values.uInstanceCount.ref.value });
|
||||
|
||||
// position
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + i });
|
||||
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * 3), t);
|
||||
StringBuilder.writeSafe(obj, 'v ');
|
||||
StringBuilder.writeFloat(obj, tmpV[0], 1000);
|
||||
StringBuilder.whitespace1(obj);
|
||||
StringBuilder.writeFloat(obj, tmpV[1], 1000);
|
||||
StringBuilder.whitespace1(obj);
|
||||
StringBuilder.writeFloat(obj, tmpV[2], 1000);
|
||||
StringBuilder.newline(obj);
|
||||
}
|
||||
|
||||
// normal
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount + i });
|
||||
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * 3), n);
|
||||
StringBuilder.writeSafe(obj, 'vn ');
|
||||
StringBuilder.writeFloat(obj, tmpV[0], 100);
|
||||
StringBuilder.whitespace1(obj);
|
||||
StringBuilder.writeFloat(obj, tmpV[1], 100);
|
||||
StringBuilder.whitespace1(obj);
|
||||
StringBuilder.writeFloat(obj, tmpV[2], 100);
|
||||
StringBuilder.newline(obj);
|
||||
}
|
||||
|
||||
// face
|
||||
for (let i = 0; i < drawCount; i += 3) {
|
||||
if (i % 3000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount * 2 + i });
|
||||
let color: Color;
|
||||
switch (colorType) {
|
||||
case 'uniform':
|
||||
color = Color.fromNormalizedArray(values.uColor.ref.value, 0);
|
||||
break;
|
||||
case 'instance':
|
||||
color = Color.fromArray(tColor, instanceIndex * 3);
|
||||
break;
|
||||
case 'group':
|
||||
color = Color.fromArray(tColor, groups[indices[i]] * 3);
|
||||
break;
|
||||
case 'groupInstance':
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
const group = groups[indices[i]];
|
||||
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
|
||||
break;
|
||||
case 'vertex':
|
||||
color = Color.fromArray(tColor, i * 3);
|
||||
break;
|
||||
case 'vertexInstance':
|
||||
color = Color.fromArray(tColor, (instanceIndex * drawCount + i) * 3);
|
||||
break;
|
||||
default: throw new Error('Unsupported color type.');
|
||||
}
|
||||
this.updateMaterial(color, uAlpha);
|
||||
|
||||
const v1 = this.vertexOffset + indices[i] + 1;
|
||||
const v2 = this.vertexOffset + indices[i + 1] + 1;
|
||||
const v3 = this.vertexOffset + indices[i + 2] + 1;
|
||||
StringBuilder.writeSafe(obj, 'f ');
|
||||
StringBuilder.writeInteger(obj, v1);
|
||||
StringBuilder.writeSafe(obj, '//');
|
||||
StringBuilder.writeIntegerAndSpace(obj, v1);
|
||||
StringBuilder.writeInteger(obj, v2);
|
||||
StringBuilder.writeSafe(obj, '//');
|
||||
StringBuilder.writeIntegerAndSpace(obj, v2);
|
||||
StringBuilder.writeInteger(obj, v3);
|
||||
StringBuilder.writeSafe(obj, '//');
|
||||
StringBuilder.writeInteger(obj, v3);
|
||||
StringBuilder.newline(obj);
|
||||
}
|
||||
|
||||
this.vertexOffset += vertexCount;
|
||||
}
|
||||
|
||||
private async addMesh(values: MeshValues, ctx: RuntimeContext) {
|
||||
const aPosition = values.aPosition.ref.value;
|
||||
const aNormal = values.aNormal.ref.value;
|
||||
const elements = values.elements.ref.value;
|
||||
const aGroup = values.aGroup.ref.value;
|
||||
const instanceCount = values.instanceCount.ref.value;
|
||||
const vertexCount = values.uVertexCount.ref.value;
|
||||
const drawCount = values.drawCount.ref.value;
|
||||
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
await this.addMeshWithColors(aPosition, aNormal, elements, aGroup, vertexCount, drawCount, values, instanceIndex, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
private async addLines(values: LinesValues, ctx: RuntimeContext) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private async addPoints(values: PointsValues, ctx: RuntimeContext) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private async addSpheres(values: SpheresValues, ctx: RuntimeContext) {
|
||||
const center = Vec3();
|
||||
|
||||
const aPosition = values.aPosition.ref.value;
|
||||
const aGroup = values.aGroup.ref.value;
|
||||
const instanceCount = values.instanceCount.ref.value;
|
||||
const vertexCount = values.uVertexCount.ref.value;
|
||||
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
const state = MeshBuilder.createState(512, 256);
|
||||
|
||||
for (let i = 0; i < vertexCount; i += 4) {
|
||||
v3fromArray(center, aPosition, i * 3);
|
||||
|
||||
const group = aGroup[i];
|
||||
const radius = ObjExporter.getSize(values, instanceIndex, group);
|
||||
state.currentGroup = group;
|
||||
addSphere(state, center, radius, 2);
|
||||
}
|
||||
|
||||
const mesh = MeshBuilder.getMesh(state);
|
||||
const vertices = mesh.vertexBuffer.ref.value;
|
||||
const normals = mesh.normalBuffer.ref.value;
|
||||
const indices = mesh.indexBuffer.ref.value;
|
||||
const groups = mesh.groupBuffer.ref.value;
|
||||
await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
private async addCylinders(values: CylindersValues, ctx: RuntimeContext) {
|
||||
const start = Vec3();
|
||||
const end = Vec3();
|
||||
|
||||
const aStart = values.aStart.ref.value;
|
||||
const aEnd = values.aEnd.ref.value;
|
||||
const aScale = values.aScale.ref.value;
|
||||
const aCap = values.aCap.ref.value;
|
||||
const aGroup = values.aGroup.ref.value;
|
||||
const instanceCount = values.instanceCount.ref.value;
|
||||
const vertexCount = values.uVertexCount.ref.value;
|
||||
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
const state = MeshBuilder.createState(512, 256);
|
||||
|
||||
for (let i = 0; i < vertexCount; i += 6) {
|
||||
v3fromArray(start, aStart, i * 3);
|
||||
v3fromArray(end, aEnd, i * 3);
|
||||
|
||||
const group = aGroup[i];
|
||||
const radius = ObjExporter.getSize(values, instanceIndex, group) * aScale[i];
|
||||
const cap = aCap[i];
|
||||
const topCap = cap === 1 || cap === 3;
|
||||
const bottomCap = cap >= 2;
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius, topCap, bottomCap, radialSegments: 32 };
|
||||
state.currentGroup = aGroup[i];
|
||||
addCylinder(state, start, end, 1, cylinderProps);
|
||||
}
|
||||
|
||||
const mesh = MeshBuilder.getMesh(state);
|
||||
const vertices = mesh.vertexBuffer.ref.value;
|
||||
const normals = mesh.normalBuffer.ref.value;
|
||||
const indices = mesh.indexBuffer.ref.value;
|
||||
const groups = mesh.groupBuffer.ref.value;
|
||||
await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
add(renderObject: GraphicsRenderObject, ctx: RuntimeContext) {
|
||||
if (!renderObject.state.visible) return;
|
||||
|
||||
switch (renderObject.type) {
|
||||
case 'mesh':
|
||||
return this.addMesh(renderObject.values as MeshValues, ctx);
|
||||
case 'lines':
|
||||
return this.addLines(renderObject.values as LinesValues, ctx);
|
||||
case 'points':
|
||||
return this.addPoints(renderObject.values as PointsValues, ctx);
|
||||
case 'spheres':
|
||||
return this.addSpheres(renderObject.values as SpheresValues, ctx);
|
||||
case 'cylinders':
|
||||
return this.addCylinders(renderObject.values as CylindersValues, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
getData() {
|
||||
return {
|
||||
obj: StringBuilder.getString(this.obj),
|
||||
mtl: StringBuilder.getString(this.mtl)
|
||||
};
|
||||
}
|
||||
|
||||
constructor(filename: string) {
|
||||
StringBuilder.writeSafe(this.obj, `mtllib ${filename}.mtl\n`);
|
||||
}
|
||||
}
|
||||
30
src/extensions/geo-export/index.ts
Normal file
30
src/extensions/geo-export/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
*/
|
||||
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
|
||||
import { GeometryExporterUI } from './ui';
|
||||
|
||||
export const GeometryExport = PluginBehavior.create<{ }>({
|
||||
name: 'extension-geo-export',
|
||||
category: 'misc',
|
||||
display: {
|
||||
name: 'Geometry Export'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ }> {
|
||||
register(): void {
|
||||
this.ctx.customStructureControls.set('geo-export', GeometryExporterUI as any);
|
||||
}
|
||||
|
||||
update() {
|
||||
return false;
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.ctx.customStructureControls.delete('geo-export');
|
||||
}
|
||||
},
|
||||
params: () => ({ })
|
||||
});
|
||||
64
src/extensions/geo-export/ui.tsx
Normal file
64
src/extensions/geo-export/ui.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
|
||||
*/
|
||||
|
||||
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
|
||||
import { Button } from '../../mol-plugin-ui/controls/common';
|
||||
import { GetAppSvg, CubeSendSvg } from '../../mol-plugin-ui/controls/icons';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { GeometryControls } from './controls';
|
||||
|
||||
interface State {
|
||||
busy?: boolean
|
||||
}
|
||||
|
||||
export class GeometryExporterUI extends CollapsableControls<{}, State> {
|
||||
private _controls: GeometryControls | undefined;
|
||||
|
||||
get controls() {
|
||||
return this._controls || (this._controls = new GeometryControls(this.plugin));
|
||||
}
|
||||
|
||||
protected defaultState(): State & CollapsableState {
|
||||
return {
|
||||
header: 'Export Geometries',
|
||||
isCollapsed: true,
|
||||
brand: { accent: 'cyan', svg: CubeSendSvg }
|
||||
};
|
||||
}
|
||||
|
||||
protected renderControls(): JSX.Element {
|
||||
return <>
|
||||
<Button icon={GetAppSvg}
|
||||
onClick={this.saveObj} style={{ marginTop: 1 }}
|
||||
disabled={this.state.busy || !this.plugin.canvas3d?.reprCount.value}>
|
||||
Save OBJ + MTL
|
||||
</Button>
|
||||
</>;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.canvas3d!.reprCount, () => {
|
||||
if (!this.state.isCollapsed) this.forceUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._controls?.dispose();
|
||||
this._controls = void 0;
|
||||
}
|
||||
|
||||
saveObj = async () => {
|
||||
try {
|
||||
this.setState({ busy: true });
|
||||
const data = await this.controls.exportObj();
|
||||
this.setState({ busy: false });
|
||||
|
||||
download(new Blob([data.zipData]), data.filename);
|
||||
} catch {
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { merge } from 'rxjs';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
import { CollapsableControls, CollapsableState } from '../../mol-plugin-ui/base';
|
||||
|
||||
@@ -124,7 +124,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
|
||||
const repr = AssemblySymmetryRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => AssemblySymmetryParams);
|
||||
await repr.createOrUpdate(params, a.data).runInContext(ctx);
|
||||
const { type, kind, symbol } = assemblySymmetry;
|
||||
return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: kind, description: `${type} (${symbol})` });
|
||||
return new PluginStateObject.Shape.Representation3D({ repr, sourceData: a.data }, { label: kind, description: `${type} (${symbol})` });
|
||||
});
|
||||
},
|
||||
update({ a, b, newParams }, plugin: PluginContext) {
|
||||
@@ -138,6 +138,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
const { type, kind, symbol } = assemblySymmetry;
|
||||
b.label = kind;
|
||||
b.description = `${type} (${symbol})`;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { CollapsableState, CollapsableControls } from '../../../mol-plugin-ui/base';
|
||||
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
|
||||
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry } from './behavior';
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra';
|
||||
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
|
||||
import { CameraTransitionManager } from './camera/transition';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Scene } from '../mol-gl/scene';
|
||||
|
||||
export { ICamera, Camera };
|
||||
|
||||
@@ -126,6 +127,23 @@ class Camera implements ICamera {
|
||||
return state;
|
||||
}
|
||||
|
||||
getInvariantFocus(target: Vec3, radius: number, up: Vec3, dir: Vec3): Partial<Camera.Snapshot> {
|
||||
const r = Math.max(radius, 0.01);
|
||||
const targetDistance = this.getTargetDistance(r);
|
||||
|
||||
Vec3.copy(this.deltaDirection, dir);
|
||||
Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance);
|
||||
Vec3.sub(this.newPosition, target, this.deltaDirection);
|
||||
|
||||
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
|
||||
state.target = Vec3.clone(target);
|
||||
state.radius = r;
|
||||
state.position = Vec3.clone(this.newPosition);
|
||||
Vec3.copy(state.up, up);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
focus(target: Vec3, radius: number, durationMs?: number, up?: Vec3, dir?: Vec3) {
|
||||
if (radius > 0) {
|
||||
this.setState(this.getFocus(target, radius, up, dir), durationMs);
|
||||
@@ -150,6 +168,8 @@ class Camera implements ICamera {
|
||||
namespace Camera {
|
||||
export type Mode = 'perspective' | 'orthographic'
|
||||
|
||||
export type SnapshotProvider = Partial<Snapshot> | ((scene: Scene, camera: Camera) => Partial<Snapshot>)
|
||||
|
||||
/**
|
||||
* Sets an offseted view in a larger frustum. This is useful for
|
||||
* - multi-window or multi-monitor/multi-machine setups
|
||||
|
||||
@@ -229,11 +229,12 @@ 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
|
||||
getImagePass(props: Partial<ImageProps>): ImagePass
|
||||
getRenderObjects(): GraphicsRenderObject[]
|
||||
|
||||
/** Returns a copy of the current Canvas3D instance props */
|
||||
readonly props: Readonly<Canvas3DProps>
|
||||
@@ -296,7 +297,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 +306,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 +332,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 +460,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);
|
||||
}
|
||||
|
||||
@@ -753,6 +770,11 @@ namespace Canvas3D {
|
||||
getImagePass: (props: Partial<ImageProps> = {}) => {
|
||||
return new ImagePass(webgl, renderer, scene, camera, helper, passes.draw.wboitEnabled, props);
|
||||
},
|
||||
getRenderObjects(): GraphicsRenderObject[] {
|
||||
const renderObjects: GraphicsRenderObject[] = [];
|
||||
scene.forEach((_, ro) => renderObjects.push(ro));
|
||||
return renderObjects;
|
||||
},
|
||||
|
||||
get props() {
|
||||
return getProps();
|
||||
|
||||
@@ -49,7 +49,21 @@ export const TrackballControlsParams = {
|
||||
minDistance: PD.Numeric(0.01, {}, { isHidden: true }),
|
||||
maxDistance: PD.Numeric(1e150, {}, { isHidden: true }),
|
||||
|
||||
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true })
|
||||
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true }),
|
||||
|
||||
/**
|
||||
* minDistance = minDistanceFactor * boundingSphere.radius + minDistancePadding
|
||||
* maxDistance = max(maxDistanceFactor * boundingSphere.radius, maxDistanceMin)
|
||||
*/
|
||||
autoAdjustMinMaxDistance: PD.MappedStatic('on', {
|
||||
off: PD.EmptyGroup(),
|
||||
on: PD.Group({
|
||||
minDistanceFactor: PD.Numeric(0),
|
||||
minDistancePadding: PD.Numeric(5),
|
||||
maxDistanceFactor: PD.Numeric(10),
|
||||
maxDistanceMin: PD.Numeric(20)
|
||||
})
|
||||
}, { isHidden: true })
|
||||
};
|
||||
export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams>
|
||||
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Scene } from '../../mol-gl/scene';
|
||||
import { Camera, ICamera } from '../camera';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { Viewport } from '../camera/util';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import produce from 'immer';
|
||||
import { Interval } from '../../mol-data/int/interval';
|
||||
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
import { Scene } from '../../mol-gl/scene';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { DataLoci, EmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { Visual } from '../../mol-repr/visual';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Camera, ICamera } from '../camera';
|
||||
import { Viewport } from '../camera/util';
|
||||
|
||||
// TODO add scale line/grid
|
||||
|
||||
const AxesParams = {
|
||||
...Mesh.Params,
|
||||
alpha: { ...Mesh.Params.alpha, defaultValue: 0.33 },
|
||||
alpha: { ...Mesh.Params.alpha, defaultValue: 0.51 },
|
||||
ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
|
||||
colorX: PD.Color(ColorNames.red, { isEssential: true }),
|
||||
colorY: PD.Color(ColorNames.green, { isEssential: true }),
|
||||
@@ -87,6 +92,32 @@ export class CameraHelper {
|
||||
return this.props.axes.name === 'on';
|
||||
}
|
||||
|
||||
getLoci(pickingId: PickingId) {
|
||||
const { objectId, groupId, instanceId } = pickingId;
|
||||
if (!this.renderObject || objectId !== this.renderObject.id || groupId === CameraHelperAxis.None) return EmptyLoci;
|
||||
return CameraAxesLoci(this, groupId, instanceId);
|
||||
}
|
||||
|
||||
private eachGroup = (loci: Loci, apply: (interval: Interval) => boolean): boolean => {
|
||||
if (!this.renderObject) return false;
|
||||
if (!isCameraAxesLoci(loci)) return false;
|
||||
let changed = false;
|
||||
const groupCount = this.renderObject.values.uGroupCount.ref.value;
|
||||
const { elements } = loci;
|
||||
for (const { groupId, instanceId } of elements) {
|
||||
const idx = instanceId * groupCount + groupId;
|
||||
if (apply(Interval.ofSingleton(idx))) changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
mark(loci: Loci, action: MarkerAction) {
|
||||
if (!MarkerActions.is(MarkerActions.Highlighting, action)) return false;
|
||||
if (!isCameraAxesLoci(loci)) return false;
|
||||
if (loci.data !== this) return false;
|
||||
return Visual.mark(this.renderObject, loci, action, this.eachGroup);
|
||||
}
|
||||
|
||||
update(camera: ICamera) {
|
||||
if (!this.renderObject) return;
|
||||
|
||||
@@ -102,6 +133,38 @@ export class CameraHelper {
|
||||
}
|
||||
}
|
||||
|
||||
export const enum CameraHelperAxis {
|
||||
None = 0,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
XY,
|
||||
XZ,
|
||||
YZ
|
||||
}
|
||||
|
||||
function getAxisLabel(axis: number) {
|
||||
switch (axis) {
|
||||
case CameraHelperAxis.X: return 'X Axis';
|
||||
case CameraHelperAxis.Y: return 'Y Axis';
|
||||
case CameraHelperAxis.Z: return 'Z Axis';
|
||||
case CameraHelperAxis.XY: return 'XY Plane';
|
||||
case CameraHelperAxis.XZ: return 'XZ Plane';
|
||||
case CameraHelperAxis.YZ: return 'YZ Plane';
|
||||
default: return 'Axes';
|
||||
}
|
||||
}
|
||||
|
||||
function CameraAxesLoci(cameraHelper: CameraHelper, groupId: number, instanceId: number) {
|
||||
return DataLoci('camera-axes', cameraHelper, [{ groupId, instanceId }],
|
||||
void 0 /** bounding sphere */,
|
||||
() => getAxisLabel(groupId));
|
||||
}
|
||||
export type CameraAxesLoci = ReturnType<typeof CameraAxesLoci>
|
||||
export function isCameraAxesLoci(x: Loci): x is CameraAxesLoci {
|
||||
return x.kind === 'data-loci' && x.tag === 'camera-axes';
|
||||
}
|
||||
|
||||
function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.ViewOffset) {
|
||||
const { near, far } = camera;
|
||||
|
||||
@@ -134,27 +197,52 @@ function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.Vie
|
||||
|
||||
function createAxesMesh(scale: number, mesh?: Mesh) {
|
||||
const state = MeshBuilder.createState(512, 256, mesh);
|
||||
const radius = 0.05 * scale;
|
||||
const radius = 0.075 * scale;
|
||||
const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
|
||||
const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
|
||||
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
|
||||
|
||||
state.currentGroup = 0;
|
||||
state.currentGroup = CameraHelperAxis.None;
|
||||
addSphere(state, Vec3.origin, radius, 2);
|
||||
|
||||
state.currentGroup = 1;
|
||||
state.currentGroup = CameraHelperAxis.X;
|
||||
addSphere(state, x, radius, 2);
|
||||
addCylinder(state, Vec3.origin, x, 1, cylinderProps);
|
||||
|
||||
state.currentGroup = 2;
|
||||
state.currentGroup = CameraHelperAxis.Y;
|
||||
addSphere(state, y, radius, 2);
|
||||
addCylinder(state, Vec3.origin, y, 1, cylinderProps);
|
||||
|
||||
state.currentGroup = 3;
|
||||
state.currentGroup = CameraHelperAxis.Z;
|
||||
addSphere(state, z, radius, 2);
|
||||
addCylinder(state, Vec3.origin, z, 1, cylinderProps);
|
||||
|
||||
Vec3.scale(x, x, 0.5);
|
||||
Vec3.scale(y, y, 0.5);
|
||||
Vec3.scale(z, z, 0.5);
|
||||
|
||||
state.currentGroup = CameraHelperAxis.XY;
|
||||
MeshBuilder.addTriangle(state, Vec3.origin, x, y);
|
||||
MeshBuilder.addTriangle(state, Vec3.origin, y, x);
|
||||
const xy = Vec3.add(Vec3(), x, y);
|
||||
MeshBuilder.addTriangle(state, xy, x, y);
|
||||
MeshBuilder.addTriangle(state, xy, y, x);
|
||||
|
||||
state.currentGroup = CameraHelperAxis.XZ;
|
||||
MeshBuilder.addTriangle(state, Vec3.origin, x, z);
|
||||
MeshBuilder.addTriangle(state, Vec3.origin, z, x);
|
||||
const xz = Vec3.add(Vec3(), x, z);
|
||||
MeshBuilder.addTriangle(state, xz, x, z);
|
||||
MeshBuilder.addTriangle(state, xz, z, x);
|
||||
|
||||
state.currentGroup = CameraHelperAxis.YZ;
|
||||
MeshBuilder.addTriangle(state, Vec3.origin, y, z);
|
||||
MeshBuilder.addTriangle(state, Vec3.origin, z, y);
|
||||
const yz = Vec3.add(Vec3(), y, z);
|
||||
MeshBuilder.addTriangle(state, yz, y, z);
|
||||
MeshBuilder.addTriangle(state, yz, z, y);
|
||||
|
||||
return MeshBuilder.getMesh(state);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,14 +66,20 @@ export class PickPass {
|
||||
private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant) {
|
||||
const depth = this.drawPass.depthTexturePrimitives;
|
||||
renderer.clear(false);
|
||||
|
||||
renderer.update(camera);
|
||||
renderer.renderPick(scene.primitives, camera, variant, null);
|
||||
renderer.renderPick(scene.volumes, camera, variant, depth);
|
||||
renderer.renderPick(helper.handle.scene, camera, variant, null);
|
||||
|
||||
if (helper.camera.isEnabled) {
|
||||
helper.camera.update(camera);
|
||||
renderer.update(helper.camera.camera);
|
||||
renderer.renderPick(helper.camera.scene, helper.camera.camera, variant, null);
|
||||
}
|
||||
}
|
||||
|
||||
render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper) {
|
||||
renderer.update(camera);
|
||||
|
||||
this.objectPickTarget.bind();
|
||||
this.renderVariant(renderer, camera, scene, helper, 'pickObject');
|
||||
|
||||
|
||||
@@ -12,13 +12,13 @@ export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height:
|
||||
}
|
||||
|
||||
/** Resize canvas to container element taking `devicePixelRatio` into account */
|
||||
export function resizeCanvas (canvas: HTMLCanvasElement, container: Element, scale = 1) {
|
||||
export function resizeCanvas (canvas: HTMLCanvasElement, container: HTMLElement, scale = 1) {
|
||||
let width = window.innerWidth;
|
||||
let height = window.innerHeight;
|
||||
if (container !== document.body) {
|
||||
let bounds = container.getBoundingClientRect();
|
||||
width = bounds.right - bounds.left;
|
||||
height = bounds.bottom - bounds.top;
|
||||
// fixes issue #molstar/molstar#147, offsetWidth/offsetHeight is correct size when css transform:scale is used
|
||||
width = container.offsetWidth;
|
||||
height = container.offsetHeight;
|
||||
}
|
||||
setCanvasSize(canvas, width, height, scale);
|
||||
}
|
||||
|
||||
@@ -18,11 +18,24 @@ export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' | 've
|
||||
export type ColorData = {
|
||||
uColor: ValueCell<Vec3>,
|
||||
tColor: ValueCell<TextureImage<Uint8Array>>,
|
||||
tPalette: ValueCell<TextureImage<Uint8Array>>,
|
||||
uColorTexDim: ValueCell<Vec2>,
|
||||
dColorType: ValueCell<string>,
|
||||
dUsePalette: ValueCell<boolean>,
|
||||
}
|
||||
|
||||
export function createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData {
|
||||
const data = _createColors(locationIt, positionIt, colorTheme, colorData);
|
||||
if (colorTheme.palette) {
|
||||
ValueCell.updateIfChanged(data.dUsePalette, true);
|
||||
updatePaletteTexture(colorTheme.palette, data.tPalette);
|
||||
} else {
|
||||
ValueCell.updateIfChanged(data.dUsePalette, false);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function _createColors(locationIt: LocationIterator, positionIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData {
|
||||
switch (Geometry.getGranularity(locationIt, colorTheme.granularity)) {
|
||||
case 'uniform': return createUniformColor(locationIt, colorTheme.color, colorData);
|
||||
case 'instance': return createInstanceColor(locationIt, colorTheme.color, colorData);
|
||||
@@ -42,18 +55,20 @@ export function createValueColor(value: Color, colorData?: ColorData): ColorData
|
||||
return {
|
||||
uColor: ValueCell.create(Color.toVec3Normalized(Vec3(), value)),
|
||||
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
|
||||
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
|
||||
uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
|
||||
dColorType: ValueCell.create('uniform'),
|
||||
dUsePalette: ValueCell.create(false),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates color uniform */
|
||||
export function createUniformColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
|
||||
function createUniformColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
|
||||
return createValueColor(color(NullLocation, false), colorData);
|
||||
}
|
||||
|
||||
export function createTextureColor(colors: TextureImage<Uint8Array>, type: ColorType, colorData?: ColorData): ColorData {
|
||||
function createTextureColor(colors: TextureImage<Uint8Array>, type: ColorType, colorData?: ColorData): ColorData {
|
||||
if (colorData) {
|
||||
ValueCell.update(colorData.tColor, colors);
|
||||
ValueCell.update(colorData.uColorTexDim, Vec2.create(colors.width, colors.height));
|
||||
@@ -63,14 +78,16 @@ export function createTextureColor(colors: TextureImage<Uint8Array>, type: Color
|
||||
return {
|
||||
uColor: ValueCell.create(Vec3()),
|
||||
tColor: ValueCell.create(colors),
|
||||
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
|
||||
uColorTexDim: ValueCell.create(Vec2.create(colors.width, colors.height)),
|
||||
dColorType: ValueCell.create(type),
|
||||
dUsePalette: ValueCell.create(false),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates color texture with color for each instance */
|
||||
export function createInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
|
||||
function createInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
|
||||
const { instanceCount } = locationIt;
|
||||
const colors = createTextureImage(Math.max(1, instanceCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
|
||||
locationIt.reset();
|
||||
@@ -83,7 +100,7 @@ export function createInstanceColor(locationIt: LocationIterator, color: Locatio
|
||||
}
|
||||
|
||||
/** Creates color texture with color for each group (i.e. shared across instances) */
|
||||
export function createGroupColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
|
||||
function createGroupColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
|
||||
const { groupCount } = locationIt;
|
||||
const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
|
||||
locationIt.reset();
|
||||
@@ -95,7 +112,7 @@ export function createGroupColor(locationIt: LocationIterator, color: LocationCo
|
||||
}
|
||||
|
||||
/** Creates color texture with color for each group in each instance */
|
||||
export function createGroupInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
|
||||
function createGroupInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
|
||||
const { groupCount, instanceCount } = locationIt;
|
||||
const count = instanceCount * groupCount;
|
||||
const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
|
||||
@@ -108,7 +125,7 @@ export function createGroupInstanceColor(locationIt: LocationIterator, color: Lo
|
||||
}
|
||||
|
||||
/** Creates color texture with color for each vertex (i.e. shared across instances) */
|
||||
export function createVertexColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
|
||||
function createVertexColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
|
||||
const { groupCount, stride } = locationIt;
|
||||
const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
|
||||
locationIt.reset();
|
||||
@@ -124,7 +141,7 @@ export function createVertexColor(locationIt: LocationIterator, color: LocationC
|
||||
}
|
||||
|
||||
/** Creates color texture with color for each vertex in each instance */
|
||||
export function createVertexInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
|
||||
function createVertexInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
|
||||
const { groupCount, instanceCount, stride } = locationIt;
|
||||
const count = instanceCount * groupCount;
|
||||
const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array);
|
||||
@@ -138,3 +155,34 @@ export function createVertexInstanceColor(locationIt: LocationIterator, color: L
|
||||
}
|
||||
return createTextureColor(colors, 'vertexInstance', colorData);
|
||||
}
|
||||
|
||||
function updatePaletteTexture(palette: ColorTheme.Palette, cell: ValueCell<TextureImage<Uint8Array>>) {
|
||||
let isSynced = true;
|
||||
const texture = cell.ref.value;
|
||||
if (palette.colors.length !== texture.width || texture.filter !== palette.filter) {
|
||||
isSynced = false;
|
||||
} else {
|
||||
const data = texture.array;
|
||||
let o = 0;
|
||||
for (const c of palette.colors) {
|
||||
const [r, g, b] = Color.toRgb(c);
|
||||
if (data[o++] !== r || data[o++] !== g || data[o++] !== b) {
|
||||
isSynced = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSynced) return;
|
||||
|
||||
const array = new Uint8Array(palette.colors.length * 3);
|
||||
let o = 0;
|
||||
for (const c of palette.colors) {
|
||||
const [r, g, b] = Color.toRgb(c);
|
||||
array[o++] = r;
|
||||
array[o++] = g;
|
||||
array[o++] = b;
|
||||
}
|
||||
|
||||
ValueCell.update(cell, { array, height: 1, width: palette.colors.length, filter: palette.filter });
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ describe('renderer', () => {
|
||||
scene.add(points);
|
||||
scene.commit();
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(6);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(7);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(6);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(6);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(12);
|
||||
|
||||
@@ -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 "High‐speed Marching Cubes using HistoPyramids"
|
||||
* by C Dyken, G Ziegler, C Theobalt, HP Seidel
|
||||
* https://doi.org/10.1111/j.1467-8659.2008.01182.x
|
||||
*
|
||||
* Implementation based on http://www.miaumiau.cat/2016/10/stream-compaction-in-webgl/
|
||||
*/
|
||||
export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
|
||||
// console.time('calcActiveVoxels');
|
||||
const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
|
||||
// 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');
|
||||
|
||||
|
||||
@@ -185,7 +185,9 @@ export const ColorSchema = {
|
||||
uColor: UniformSpec('v3', 'material'),
|
||||
uColorTexDim: UniformSpec('v2'),
|
||||
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
|
||||
tPalette: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
|
||||
dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
|
||||
dUsePalette: DefineSpec('boolean'),
|
||||
} as const;
|
||||
export type ColorSchema = typeof ColorSchema
|
||||
export type ColorValues = Values<ColorSchema>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
|
||||
import { TextureFilter } from '../webgl/texture';
|
||||
|
||||
export function calculateTextureInfo (n: number, itemSize: number) {
|
||||
n = Math.max(n, 2); // observed issues with 1 pixel textures
|
||||
@@ -22,6 +23,7 @@ export interface TextureImage<T extends Uint8Array | Float32Array | Int32Array>
|
||||
readonly width: number
|
||||
readonly height: number
|
||||
readonly flipY?: boolean
|
||||
readonly filter?: TextureFilter
|
||||
}
|
||||
|
||||
export interface TextureVolume<T extends Uint8Array | Float32Array> {
|
||||
|
||||
@@ -14,6 +14,10 @@ export const assign_color_varying = `
|
||||
vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb;
|
||||
#endif
|
||||
|
||||
#ifdef dUsePalette
|
||||
vPaletteV = ((vColor.r * 256.0 * 256.0 * 255.0 + vColor.g * 256.0 * 255.0 + vColor.b * 255.0) - 1.0) / 16777215.0;
|
||||
#endif
|
||||
|
||||
#ifdef dOverpaint
|
||||
vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export const assign_material_color = `
|
||||
#if defined(dRenderVariant_color)
|
||||
#if defined(dColorType_uniform)
|
||||
#if defined(dUsePalette)
|
||||
vec4 material = vec4(texture2D(tPalette, vec2(vPaletteV, 0.5)).rgb, uAlpha);
|
||||
#elif defined(dColorType_uniform)
|
||||
vec4 material = vec4(uColor, uAlpha);
|
||||
#elif defined(dColorType_varying)
|
||||
vec4 material = vec4(vColor.rgb, uAlpha);
|
||||
|
||||
@@ -21,4 +21,9 @@ export const color_frag_params = `
|
||||
varying float vGroup;
|
||||
varying float vTransparency;
|
||||
#endif
|
||||
|
||||
#ifdef dUsePalette
|
||||
uniform sampler2D tPalette;
|
||||
varying float vPaletteV;
|
||||
#endif
|
||||
`;
|
||||
@@ -30,4 +30,8 @@ export const color_vert_params = `
|
||||
uniform vec2 uTransparencyTexDim;
|
||||
uniform sampler2D tTransparency;
|
||||
#endif
|
||||
|
||||
#ifdef dUsePalette
|
||||
varying float vPaletteV;
|
||||
#endif
|
||||
`;
|
||||
@@ -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)));
|
||||
}
|
||||
`;
|
||||
@@ -283,6 +283,9 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, format, type, data);
|
||||
} else if (isTexture2d(data, target, gl)) {
|
||||
const _filter = data.filter ? getFilter(gl, data.filter) : filter;
|
||||
gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, _filter);
|
||||
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, _filter);
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !!data.flipY);
|
||||
if (sub) {
|
||||
gl.texSubImage2D(target, 0, 0, 0, data.width, data.height, format, type, data.array);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { parseSdf } from '../sdf/parser';
|
||||
|
||||
const SdfString = `
|
||||
Mrv1718007121815122D
|
||||
Mrv1718007121815122D
|
||||
|
||||
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
|
||||
@@ -17,7 +17,7 @@ const SdfString = `
|
||||
M CHG 3 1 -1 3 -1 5 -1
|
||||
M END
|
||||
> <DATABASE_ID>
|
||||
DB14523
|
||||
0
|
||||
|
||||
> <DATABASE_NAME>
|
||||
drugbank
|
||||
@@ -112,7 +112,225 @@ 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
|
||||
> <DATABASE_ID>
|
||||
1
|
||||
|
||||
$$$$
|
||||
|
||||
2244
|
||||
-OEChem-04122119123D
|
||||
|
||||
21 21 0 0 0 0 0 0 0999 V2000
|
||||
1.2333 0.5540 0.7792 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.6952 -2.7148 -0.7502 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0.7958 -2.1843 0.8685 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1.7813 0.8105 -1.4821 O 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.0857 0.6088 0.4403 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.7927 -0.5515 0.1244 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.7288 1.8464 0.4133 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.1426 -0.4741 -0.2184 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.0787 1.9238 0.0706 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.7855 0.7636 -0.2453 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.1409 -1.8536 0.1477 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
2.1094 0.6715 -0.3113 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.5305 0.5996 0.1635 C 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.1851 2.7545 0.6593 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.7247 -1.3605 -0.4564 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-2.5797 2.8872 0.0506 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-3.8374 0.8238 -0.5090 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.7290 1.4184 0.8593 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
4.2045 0.6969 -0.6924 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
3.7105 -0.3659 0.6426 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
-0.2555 -3.5916 -0.7337 H 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
1 5 1 0 0 0 0
|
||||
1 12 1 0 0 0 0
|
||||
2 11 1 0 0 0 0
|
||||
2 21 1 0 0 0 0
|
||||
3 11 2 0 0 0 0
|
||||
4 12 2 0 0 0 0
|
||||
5 6 1 0 0 0 0
|
||||
5 7 2 0 0 0 0
|
||||
6 8 2 0 0 0 0
|
||||
6 11 1 0 0 0 0
|
||||
7 9 1 0 0 0 0
|
||||
7 14 1 0 0 0 0
|
||||
8 10 1 0 0 0 0
|
||||
8 15 1 0 0 0 0
|
||||
9 10 2 0 0 0 0
|
||||
9 16 1 0 0 0 0
|
||||
10 17 1 0 0 0 0
|
||||
12 13 1 0 0 0 0
|
||||
13 18 1 0 0 0 0
|
||||
13 19 1 0 0 0 0
|
||||
13 20 1 0 0 0 0
|
||||
M END
|
||||
> <PUBCHEM_COMPOUND_CID>
|
||||
2244
|
||||
|
||||
> <PUBCHEM_CONFORMER_RMSD>
|
||||
0.6
|
||||
|
||||
> <PUBCHEM_CONFORMER_DIVERSEORDER>
|
||||
1
|
||||
11
|
||||
10
|
||||
3
|
||||
15
|
||||
17
|
||||
13
|
||||
5
|
||||
16
|
||||
7
|
||||
14
|
||||
9
|
||||
8
|
||||
4
|
||||
18
|
||||
6
|
||||
12
|
||||
2
|
||||
|
||||
> <PUBCHEM_MMFF94_PARTIAL_CHARGES>
|
||||
18
|
||||
1 -0.23
|
||||
10 -0.15
|
||||
11 0.63
|
||||
12 0.66
|
||||
13 0.06
|
||||
14 0.15
|
||||
15 0.15
|
||||
16 0.15
|
||||
17 0.15
|
||||
2 -0.65
|
||||
21 0.5
|
||||
3 -0.57
|
||||
4 -0.57
|
||||
5 0.08
|
||||
6 0.09
|
||||
7 -0.15
|
||||
8 -0.15
|
||||
9 -0.15
|
||||
|
||||
> <PUBCHEM_EFFECTIVE_ROTOR_COUNT>
|
||||
3
|
||||
|
||||
> <PUBCHEM_PHARMACOPHORE_FEATURES>
|
||||
5
|
||||
1 2 acceptor
|
||||
1 3 acceptor
|
||||
1 4 acceptor
|
||||
3 2 3 11 anion
|
||||
6 5 6 7 8 9 10 rings
|
||||
|
||||
> <PUBCHEM_HEAVY_ATOM_COUNT>
|
||||
13
|
||||
|
||||
> <PUBCHEM_ATOM_DEF_STEREO_COUNT>
|
||||
0
|
||||
|
||||
> <PUBCHEM_ATOM_UDEF_STEREO_COUNT>
|
||||
0
|
||||
|
||||
> <PUBCHEM_BOND_DEF_STEREO_COUNT>
|
||||
0
|
||||
|
||||
> <PUBCHEM_BOND_UDEF_STEREO_COUNT>
|
||||
0
|
||||
|
||||
> <PUBCHEM_ISOTOPIC_ATOM_COUNT>
|
||||
0
|
||||
|
||||
> <PUBCHEM_COMPONENT_COUNT>
|
||||
1
|
||||
|
||||
> <PUBCHEM_CACTVS_TAUTO_COUNT>
|
||||
1
|
||||
|
||||
> <PUBCHEM_CONFORMER_ID>
|
||||
000008C400000001
|
||||
|
||||
> <PUBCHEM_MMFF94_ENERGY>
|
||||
39.5952
|
||||
|
||||
> <PUBCHEM_FEATURE_SELFOVERLAP>
|
||||
25.432
|
||||
|
||||
> <PUBCHEM_SHAPE_FINGERPRINT>
|
||||
1 1 18265615372930943622
|
||||
100427 49 16967750034970055351
|
||||
12138202 97 18271247217817981012
|
||||
12423570 1 16692715976000295083
|
||||
12524768 44 16753525617747228747
|
||||
12716758 59 18341332292274886536
|
||||
13024252 1 17968377969333732145
|
||||
14181834 199 17830728755827362645
|
||||
14614273 12 18262232214645093005
|
||||
15207287 21 17703787037639964108
|
||||
15775835 57 18340488876329928641
|
||||
16945 1 18271533103414939405
|
||||
193761 8 17907860604865584321
|
||||
20645476 183 17677348215414174190
|
||||
20871998 184 18198632231250704846
|
||||
21040471 1 18411412921197846465
|
||||
21501502 16 18123463883164380929
|
||||
23402539 116 18271795865171824860
|
||||
23419403 2 13539898140662769886
|
||||
23552423 10 18048876295495619569
|
||||
23559900 14 18272369794190581304
|
||||
241688 4 16179044415907240795
|
||||
257057 1 17478316999871287486
|
||||
2748010 2 18339085878070479087
|
||||
305870 269 18263645056784260212
|
||||
528862 383 18117272558388284091
|
||||
53812653 8 18410289211719108569
|
||||
7364860 26 17910392788380644719
|
||||
81228 2 18050568744116491203
|
||||
|
||||
> <PUBCHEM_SHAPE_MULTIPOLES>
|
||||
244.06
|
||||
3.86
|
||||
2.45
|
||||
0.89
|
||||
1.95
|
||||
1.58
|
||||
0.15
|
||||
-1.85
|
||||
0.38
|
||||
-0.61
|
||||
-0.02
|
||||
0.29
|
||||
0.01
|
||||
-0.33
|
||||
|
||||
> <PUBCHEM_SHAPE_SELFOVERLAP>
|
||||
513.037
|
||||
|
||||
> <PUBCHEM_SHAPE_VOLUME>
|
||||
136
|
||||
|
||||
> <PUBCHEM_COORDINATE_TYPE>
|
||||
2
|
||||
5
|
||||
10
|
||||
|
||||
$$$$
|
||||
`;
|
||||
|
||||
describe('sdf reader', () => {
|
||||
it('basic', async () => {
|
||||
@@ -120,14 +338,21 @@ 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 compound3 = parsed.result.compounds[2];
|
||||
const { molFile, dataItems } = compound1;
|
||||
const { atoms, bonds } = molFile;
|
||||
|
||||
expect(parsed.result.compounds.length).toBe(3);
|
||||
|
||||
// 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);
|
||||
@@ -138,12 +363,21 @@ describe('sdf reader', () => {
|
||||
expect(bonds.order.value(3)).toBe(1);
|
||||
|
||||
expect(dataItems.dataHeader.value(0)).toBe('DATABASE_ID');
|
||||
expect(dataItems.data.value(0)).toBe('DB14523');
|
||||
expect(dataItems.data.value(0)).toBe('0');
|
||||
|
||||
expect(dataItems.dataHeader.value(1)).toBe('DATABASE_NAME');
|
||||
expect(dataItems.data.value(1)).toBe('drugbank');
|
||||
|
||||
expect(dataItems.dataHeader.value(31)).toBe('SYNONYMS');
|
||||
expect(dataItems.data.value(31)).toBe('Orthophosphate; Phosphate');
|
||||
|
||||
expect(compound1.dataItems.data.value(0)).toBe('0');
|
||||
expect(compound2.dataItems.data.value(0)).toBe('1');
|
||||
|
||||
expect(compound3.dataItems.dataHeader.value(2)).toBe('PUBCHEM_CONFORMER_DIVERSEORDER');
|
||||
expect(compound3.dataItems.data.value(2)).toBe('1\n11\n10\n3\n15\n17\n13\n5\n16\n7\n14\n9\n8\n4\n18\n6\n12\n2');
|
||||
|
||||
expect(compound3.dataItems.dataHeader.value(21)).toBe('PUBCHEM_COORDINATE_TYPE');
|
||||
expect(compound3.dataItems.data.value(21)).toBe('2\n5\n10');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* 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 Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Column } from '../../../mol-data/db';
|
||||
@@ -22,24 +23,36 @@ 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);
|
||||
|
||||
let sawHeaderToken = false;
|
||||
while (tokenizer.position < tokenizer.length) {
|
||||
const line = Tokenizer.readLine(tokenizer);
|
||||
if (!!line) {
|
||||
if (line.startsWith('> <')) {
|
||||
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 3, tokenizer.tokenEnd - 1);
|
||||
sawHeaderToken = true;
|
||||
} else if (sawHeaderToken) {
|
||||
TokenBuilder.add(data, tokenizer.tokenStart, tokenizer.tokenEnd);
|
||||
sawHeaderToken = false;
|
||||
// TODO can there be multiline values?
|
||||
if (line.startsWith(delimiter)) break;
|
||||
if (!line) continue;
|
||||
|
||||
if (line.startsWith('> <')) {
|
||||
TokenBuilder.add(dataHeader, tokenizer.tokenStart + 3, tokenizer.tokenEnd - 1);
|
||||
|
||||
Tokenizer.markLine(tokenizer);
|
||||
const start = tokenizer.tokenStart;
|
||||
let end = tokenizer.tokenEnd;
|
||||
let added = false;
|
||||
while (tokenizer.position < tokenizer.length) {
|
||||
const line2 = Tokenizer.readLine(tokenizer);
|
||||
if (!line2 || line2.startsWith(delimiter) || line2.startsWith('> <')) {
|
||||
TokenBuilder.add(data, start, end);
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
end = tokenizer.tokenEnd;
|
||||
}
|
||||
|
||||
if (!added) {
|
||||
TokenBuilder.add(data, start, end);
|
||||
}
|
||||
} else {
|
||||
sawHeaderToken = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +62,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 +71,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 +90,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) {
|
||||
|
||||
@@ -32,7 +32,7 @@ function handleMolecule(tokenizer: Tokenizer): XyzFile['molecules'][number] {
|
||||
const type_symbol = new Array<string>(count);
|
||||
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const line = Tokenizer.readLine(tokenizer);
|
||||
const line = Tokenizer.readLineTrim(tokenizer);
|
||||
const fields = line.split(/\s+/g);
|
||||
type_symbol[i] = fields[0];
|
||||
x[i] = +fields[1];
|
||||
|
||||
@@ -111,33 +111,41 @@ namespace Spacegroup {
|
||||
|
||||
const _translationRef = Vec3();
|
||||
const _translationRefSymop = Vec3();
|
||||
const _translationRefOffset = Vec3();
|
||||
const _translationSymop = Vec3();
|
||||
export function setOperatorMatrixRef(spacegroup: Spacegroup, index: number, i: number, j: number, k: number, ref: Vec3, target: Mat4) {
|
||||
Vec3.set(_ijkVec, i, j, k);
|
||||
Vec3.floor(_translationRef, ref);
|
||||
|
||||
Mat4.copy(target, spacegroup.operators[index]);
|
||||
|
||||
Vec3.floor(_translationRefSymop, Vec3.transformMat4(_translationRefSymop, ref, target));
|
||||
|
||||
Mat4.getTranslation(_translationSymop, target);
|
||||
Vec3.sub(_translationSymop, _translationSymop, _translationRefSymop);
|
||||
Vec3.add(_translationSymop, _translationSymop, _translationRef);
|
||||
Vec3.add(_translationSymop, _translationSymop, _ijkVec);
|
||||
|
||||
Mat4.setTranslation(target, _translationSymop);
|
||||
Mat4.mul(target, spacegroup.cell.fromFractional, target);
|
||||
Mat4.mul(target, target, spacegroup.cell.toFractional);
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Symmetry operator for transformation around the given
|
||||
* reference point `ref` in fractional coordinates
|
||||
*/
|
||||
export function getSymmetryOperatorRef(spacegroup: Spacegroup, spgrOp: number, i: number, j: number, k: number, ref: Vec3) {
|
||||
const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
|
||||
return SymmetryOperator.create(`${spgrOp + 1}_${5 + i}${5 + j}${5 + k}`, operator, { hkl: Vec3.create(i, j, k), spgrOp });
|
||||
|
||||
const operator = Mat4.zero();
|
||||
|
||||
Vec3.set(_ijkVec, i, j, k);
|
||||
Vec3.floor(_translationRef, ref);
|
||||
|
||||
Mat4.copy(operator, spacegroup.operators[spgrOp]);
|
||||
|
||||
Vec3.floor(_translationRefSymop, Vec3.transformMat4(_translationRefSymop, ref, operator));
|
||||
|
||||
Mat4.getTranslation(_translationSymop, operator);
|
||||
Vec3.sub(_translationSymop, _translationSymop, _translationRefSymop);
|
||||
Vec3.add(_translationSymop, _translationSymop, _translationRef);
|
||||
Vec3.add(_translationSymop, _translationSymop, _ijkVec);
|
||||
|
||||
Mat4.setTranslation(operator, _translationSymop);
|
||||
Mat4.mul(operator, spacegroup.cell.fromFractional, operator);
|
||||
Mat4.mul(operator, operator, spacegroup.cell.toFractional);
|
||||
|
||||
Vec3.sub(_translationRefOffset, _translationRefSymop, _translationRef);
|
||||
|
||||
const _i = i - _translationRefOffset[0];
|
||||
const _j = j - _translationRefOffset[1];
|
||||
const _k = k - _translationRefOffset[2];
|
||||
|
||||
// const operator = setOperatorMatrixRef(spacegroup, spgrOp, i, j, k, ref, Mat4.zero());
|
||||
return SymmetryOperator.create(`${spgrOp + 1}_${5 + _i}${5 + _j}${5 + _k}`, operator, { hkl: Vec3.create(_i, _j, _k), spgrOp });
|
||||
}
|
||||
|
||||
function getOperatorMatrix(ids: number[]) {
|
||||
|
||||
@@ -590,6 +590,9 @@ namespace Vec3 {
|
||||
export const unitX: ReadonlyVec3 = create(1, 0, 0);
|
||||
export const unitY: ReadonlyVec3 = create(0, 1, 0);
|
||||
export const unitZ: ReadonlyVec3 = create(0, 0, 1);
|
||||
export const negUnitX: ReadonlyVec3 = create(-1, 0, 0);
|
||||
export const negUnitY: ReadonlyVec3 = create(0, -1, 0);
|
||||
export const negUnitZ: ReadonlyVec3 = create(0, 0, -1);
|
||||
}
|
||||
|
||||
export { Vec3 };
|
||||
@@ -54,6 +54,12 @@ const StandardComponents = (function() {
|
||||
{ id: 'SEC', name: 'SELENOCYSTEINE', type: 'L-peptide linking' },
|
||||
{ id: 'PYL', name: 'PYRROLYSINE', type: 'L-peptide linking' },
|
||||
|
||||
{ id: 'MSE', name: 'SELENOMETHIONINE', type: 'L-peptide linking' },
|
||||
{ id: 'SEP', name: 'PHOSPHOSERINE', type: 'L-peptide linking' },
|
||||
{ id: 'TPO', name: 'PHOSPHOTHREONINE', type: 'L-peptide linking' },
|
||||
{ id: 'PTR', name: 'O-PHOSPHOTYROSINE', type: 'L-peptide linking' },
|
||||
{ id: 'PCA', name: 'PYROGLUTAMIC ACID', type: 'L-peptide linking' },
|
||||
|
||||
{ id: 'A', name: 'ADENOSINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
|
||||
{ id: 'C', name: 'CYTIDINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
|
||||
{ id: 'T', name: 'THYMIDINE-5\'-MONOPHOSPHATE', type: 'RNA linking' },
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -15,7 +15,7 @@ import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../../../mo
|
||||
import { VisualUpdateState } from '../../../mol-repr/util';
|
||||
import { PickingId } from '../../../mol-geo/geometry/picking';
|
||||
import { EmptyLoci, Loci } from '../../../mol-model/loci';
|
||||
import { Interval, OrderedSet } from '../../../mol-data/int';
|
||||
import { Interval, OrderedSet, SortedArray } from '../../../mol-data/int';
|
||||
import { Interactions } from '../interactions/interactions';
|
||||
import { InteractionsProvider } from '../interactions';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
@@ -35,6 +35,8 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
|
||||
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh);
|
||||
|
||||
const { child } = structure;
|
||||
|
||||
const builderProps = {
|
||||
linkCount: edgeCount,
|
||||
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
|
||||
@@ -63,12 +65,28 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
|
||||
const sizeB = theme.size.size(l);
|
||||
return Math.min(sizeA, sizeB) * sizeFactor;
|
||||
},
|
||||
ignore: (edgeIndex: number) => edges[edgeIndex].props.flag === InteractionFlag.Filtered
|
||||
ignore: (edgeIndex: number) => {
|
||||
if (edges[edgeIndex].props.flag === InteractionFlag.Filtered) return true;
|
||||
|
||||
if (child) {
|
||||
const b = edges[edgeIndex];
|
||||
const childUnitA = child.unitMap.get(b.unitA);
|
||||
if (!childUnitA) return true;
|
||||
|
||||
const unitA = structure.unitMap.get(b.unitA);
|
||||
const fA = unitsFeatures.get(b.unitA);
|
||||
// TODO: check all members
|
||||
const eA = unitA.elements[fA.members[fA.offsets[b.indexA]]];
|
||||
if (!SortedArray.has(childUnitA.elements, eA)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, 1 * sizeFactor);
|
||||
const sphere = Sphere3D.expand(Sphere3D(), (child ?? structure).boundary.sphere, 1 * sizeFactor);
|
||||
m.setBoundingSphere(sphere);
|
||||
|
||||
return m;
|
||||
@@ -80,6 +98,7 @@ export const InteractionsInterUnitParams = {
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
|
||||
dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
|
||||
includeParent: PD.Boolean(false),
|
||||
};
|
||||
export type InteractionsInterUnitParams = typeof InteractionsInterUnitParams
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -7,7 +7,7 @@
|
||||
import { Unit, Structure, StructureElement } from '../../../mol-model/structure';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { Loci, EmptyLoci } from '../../../mol-model/loci';
|
||||
import { Interval, OrderedSet } from '../../../mol-data/int';
|
||||
import { Interval, OrderedSet, SortedArray } from '../../../mol-data/int';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { PickingId } from '../../../mol-geo/geometry/picking';
|
||||
@@ -25,6 +25,10 @@ import { Sphere3D } from '../../../mol-math/geometry';
|
||||
async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<InteractionsIntraUnitParams>, mesh?: Mesh) {
|
||||
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
|
||||
|
||||
const { child } = structure;
|
||||
const childUnit = child?.unitMap.get(unit.id);
|
||||
if (child && !childUnit) return Mesh.createEmpty(mesh);
|
||||
|
||||
const location = StructureElement.Location.create(structure, unit);
|
||||
|
||||
const interactions = InteractionsProvider.get(structure).value!;
|
||||
@@ -51,12 +55,16 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
|
||||
const sizeB = theme.size.size(location);
|
||||
return Math.min(sizeA, sizeB) * sizeFactor;
|
||||
},
|
||||
ignore: (edgeIndex: number) => flag[edgeIndex] === InteractionFlag.Filtered
|
||||
ignore: (edgeIndex: number) => (
|
||||
flag[edgeIndex] === InteractionFlag.Filtered ||
|
||||
// TODO: check all members
|
||||
(!!childUnit && !SortedArray.has(childUnit.elements, unit.elements[members[offsets[a[edgeIndex]]]]))
|
||||
)
|
||||
};
|
||||
|
||||
const m = createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, 1 * sizeFactor);
|
||||
const sphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, 1 * sizeFactor);
|
||||
m.setBoundingSphere(sphere);
|
||||
|
||||
return m;
|
||||
@@ -68,6 +76,7 @@ export const InteractionsIntraUnitParams = {
|
||||
sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
|
||||
dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }),
|
||||
dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }),
|
||||
includeParent: PD.Boolean(false),
|
||||
};
|
||||
export type InteractionsIntraUnitParams = typeof InteractionsIntraUnitParams
|
||||
|
||||
@@ -147,7 +156,7 @@ function eachInteraction(loci: Loci, structureGroup: StructureGroup, apply: (int
|
||||
const { offset } = contacts;
|
||||
const { offsets: fOffsets, indices: fIndices } = features.elementsIndex;
|
||||
|
||||
// TODO when isMarking, all elements of contact features need to be in the loci
|
||||
// TODO: when isMarking, all elements of contact features need to be in the loci
|
||||
for (const e of loci.elements) {
|
||||
const unitIdx = group.unitIndexMap.get(e.unit.id);
|
||||
if (unitIdx !== undefined) continue;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
@@ -37,9 +37,10 @@ export interface DataLoci<T = unknown, E = unknown> {
|
||||
readonly kind: 'data-loci',
|
||||
readonly tag: string
|
||||
readonly data: T,
|
||||
readonly elements: ReadonlyArray<E>
|
||||
readonly elements: ReadonlyArray<E>,
|
||||
|
||||
getBoundingSphere(boundingSphere: Sphere3D): Sphere3D
|
||||
/** if undefined, won't zoom */
|
||||
getBoundingSphere?(boundingSphere: Sphere3D): Sphere3D
|
||||
getLabel(): string
|
||||
}
|
||||
export function isDataLoci(x?: Loci): x is DataLoci {
|
||||
@@ -159,7 +160,7 @@ namespace Loci {
|
||||
} else if (loci.kind === 'group-loci') {
|
||||
return ShapeGroup.getBoundingSphere(loci, boundingSphere);
|
||||
} else if (loci.kind === 'data-loci') {
|
||||
return loci.getBoundingSphere(boundingSphere);
|
||||
return loci.getBoundingSphere?.(boundingSphere);
|
||||
} else if (loci.kind === 'volume-loci') {
|
||||
return Volume.getBoundingSphere(loci.volume, boundingSphere);
|
||||
} else if (loci.kind === 'isosurface-loci') {
|
||||
|
||||
107
src/mol-model/structure/model/properties/utils/residue-set.ts
Normal file
107
src/mol-model/structure/model/properties/utils/residue-set.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { StructureElement } from '../../../structure/element';
|
||||
import { StructureProperties } from '../../../structure/properties';
|
||||
|
||||
export interface ResidueSetEntry {
|
||||
label_asym_id: string,
|
||||
label_comp_id: string,
|
||||
label_seq_id: number,
|
||||
label_alt_id: string,
|
||||
ins_code: string,
|
||||
// 1_555 by default
|
||||
operator_name?: string
|
||||
}
|
||||
|
||||
export class ResidueSet {
|
||||
private index = new Map<string, Map<number, ResidueSetEntry[]>>();
|
||||
private checkOperator: boolean = false;
|
||||
|
||||
add(entry: ResidueSetEntry) {
|
||||
let root = this.index.get(entry.label_asym_id);
|
||||
if (!root) {
|
||||
root = new Map();
|
||||
this.index.set(entry.label_asym_id, root);
|
||||
}
|
||||
|
||||
let entries = root.get(entry.label_seq_id);
|
||||
if (!entries) {
|
||||
entries = [];
|
||||
root.set(entry.label_seq_id, entries);
|
||||
}
|
||||
|
||||
const exists = this._find(entry, entries);
|
||||
if (!exists) {
|
||||
entries.push(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
hasLabelAsymId(asym_id: string) {
|
||||
return this.index.has(asym_id);
|
||||
}
|
||||
|
||||
has(loc: StructureElement.Location) {
|
||||
const asym_id = _asym_id(loc);
|
||||
if (!this.index.has(asym_id)) return;
|
||||
const root = this.index.get(asym_id)!;
|
||||
const seq_id = _seq_id(loc);
|
||||
if (!root.has(seq_id)) return;
|
||||
const entries = root.get(seq_id)!;
|
||||
|
||||
const comp_id = _comp_id(loc);
|
||||
const alt_id = _alt_id(loc);
|
||||
const ins_code = _ins_code(loc);
|
||||
const op_name = _op_name(loc) ?? '1_555';
|
||||
|
||||
for (const e of entries) {
|
||||
if (e.label_comp_id !== comp_id || e.label_alt_id !== alt_id || e.ins_code !== ins_code) continue;
|
||||
if (this.checkOperator && (e.operator_name ?? '1_555') !== op_name) continue;
|
||||
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
static getLabel(entry: ResidueSetEntry, checkOperator = false) {
|
||||
return `${entry.label_asym_id} ${entry.label_comp_id} ${entry.label_seq_id}:${entry.ins_code}:${entry.label_alt_id}${checkOperator ? ' ' + (entry.operator_name ?? '1_555') : ''}`;
|
||||
}
|
||||
|
||||
static getEntryFromLocation(loc: StructureElement.Location): ResidueSetEntry {
|
||||
return {
|
||||
label_asym_id: _asym_id(loc),
|
||||
label_comp_id: _comp_id(loc),
|
||||
label_seq_id: _seq_id(loc),
|
||||
label_alt_id: _alt_id(loc),
|
||||
ins_code: _ins_code(loc),
|
||||
operator_name: _op_name(loc) ?? '1_555'
|
||||
};
|
||||
}
|
||||
|
||||
private _find(entry: ResidueSetEntry, xs: ResidueSetEntry[]) {
|
||||
for (const e of xs) {
|
||||
if (e.label_comp_id !== entry.label_comp_id || e.label_alt_id !== entry.label_alt_id || e.ins_code !== entry.ins_code) continue;
|
||||
if (this.checkOperator && (e.operator_name ?? '1_555') !== (entry.operator_name ?? '1_555')) continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constructor(options?: { checkOperator?: boolean }) {
|
||||
this.checkOperator = options?.checkOperator ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
const _asym_id = StructureProperties.chain.label_asym_id;
|
||||
const _seq_id = StructureProperties.residue.label_seq_id;
|
||||
const _comp_id = StructureProperties.atom.label_comp_id;
|
||||
const _alt_id = StructureProperties.atom.label_alt_id;
|
||||
const _ins_code = StructureProperties.residue.pdbx_PDB_ins_code;
|
||||
const _op_name = StructureProperties.unit.operator_name;
|
||||
@@ -245,7 +245,9 @@ export const WaterNames = new Set([
|
||||
export const AminoAcidNamesL = new Set([
|
||||
'HIS', 'ARG', 'LYS', 'ILE', 'PHE', 'LEU', 'TRP', 'ALA', 'MET', 'PRO', 'CYS',
|
||||
'ASN', 'VAL', 'GLY', 'SER', 'GLN', 'TYR', 'ASP', 'GLU', 'THR', 'SEC', 'PYL',
|
||||
'UNK' // unknown amino acid from CCD
|
||||
'UNK', // unknown amino acid from CCD
|
||||
'MSE', 'SEP', 'TPO', 'PTR', 'PCA' // common
|
||||
|
||||
]);
|
||||
export const AminoAcidNamesD = new Set([
|
||||
'DAL', // D-ALANINE
|
||||
|
||||
@@ -43,7 +43,7 @@ export function atomicSequence(): StructureQuery {
|
||||
|
||||
units.push(unit);
|
||||
}
|
||||
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
|
||||
return StructureSelection.Singletons(inputStructure, Structure.create(units, { parent: inputStructure }));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export function water(): StructureQuery {
|
||||
if (P.entity.type(l) !== 'water') continue;
|
||||
units.push(unit);
|
||||
}
|
||||
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
|
||||
return StructureSelection.Singletons(inputStructure, Structure.create(units, { parent: inputStructure }));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export function atomicHet(): StructureQuery {
|
||||
|
||||
units.push(unit);
|
||||
}
|
||||
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
|
||||
return StructureSelection.Singletons(inputStructure, Structure.create(units, { parent: inputStructure }));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ export function spheres(): StructureQuery {
|
||||
if (unit.kind !== Unit.Kind.Spheres) continue;
|
||||
units.push(unit);
|
||||
}
|
||||
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
|
||||
return StructureSelection.Singletons(inputStructure, Structure.create(units, { parent: inputStructure }));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,14 @@ import { StructureSelection } from '../selection';
|
||||
import { UniqueStructuresBuilder } from '../utils/builders';
|
||||
import { StructureUniqueSubsetBuilder } from '../../structure/util/unique-subset-builder';
|
||||
import { QueryContext, QueryFn } from '../context';
|
||||
import { structureIntersect, structureSubtract } from '../utils/structure-set';
|
||||
import { structureIntersect, structureSubtract, structureUnion } from '../utils/structure-set';
|
||||
import { UniqueArray } from '../../../../mol-data/generic';
|
||||
import { StructureSubsetBuilder } from '../../structure/util/subset-builder';
|
||||
import { StructureElement } from '../../structure/element';
|
||||
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { ResidueSet, ResidueSetEntry } from '../../model/properties/utils/residue-set';
|
||||
import { StructureProperties } from '../../structure/properties';
|
||||
import { arraySetAdd } from '../../../../mol-util/array';
|
||||
|
||||
function getWholeResidues(ctx: QueryContext, source: Structure, structure: Structure) {
|
||||
const builder = source.subsetBuilder(true);
|
||||
@@ -435,4 +439,252 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
|
||||
return builder.getStructure();
|
||||
}
|
||||
|
||||
export interface SurroundingLigandsParams {
|
||||
query: StructureQuery,
|
||||
radius: number,
|
||||
includeWater: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes expanded surrounding ligands based on radius from the source, struct_conn entries & pdbx_molecule entries.
|
||||
*/
|
||||
export function surroundingLigands({ query, radius, includeWater }: SurroundingLigandsParams): StructureQuery {
|
||||
return function query_surroundingLigands(ctx) {
|
||||
|
||||
const inner = StructureSelection.unionStructure(query(ctx));
|
||||
const surroundings = getWholeResidues(ctx, ctx.inputStructure, getIncludeSurroundings(ctx, ctx.inputStructure, inner, { radius }));
|
||||
|
||||
const prd = getPrdAsymIdx(ctx.inputStructure);
|
||||
const graph = getStructConnInfo(ctx.inputStructure);
|
||||
|
||||
const l = StructureElement.Location.create(surroundings);
|
||||
|
||||
const includedPrdChains = new Map<string, string[]>();
|
||||
|
||||
const componentResidues = new ResidueSet({ checkOperator: true });
|
||||
|
||||
for (const unit of surroundings.units) {
|
||||
if (unit.kind !== Unit.Kind.Atomic) continue;
|
||||
|
||||
l.unit = unit;
|
||||
|
||||
const { elements } = unit;
|
||||
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
|
||||
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
|
||||
|
||||
while (chainsIt.hasNext) {
|
||||
const chainSegment = chainsIt.move();
|
||||
l.element = elements[chainSegment.start];
|
||||
|
||||
const asym_id = StructureProperties.chain.label_asym_id(l);
|
||||
const op_name = StructureProperties.unit.operator_name(l);
|
||||
|
||||
// check for PRD molecules
|
||||
if (prd.has(asym_id)) {
|
||||
if (includedPrdChains.has(asym_id)) {
|
||||
arraySetAdd(includedPrdChains.get(asym_id)!, op_name);
|
||||
} else {
|
||||
includedPrdChains.set(asym_id, [op_name]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const entityType = StructureProperties.entity.type(l);
|
||||
|
||||
// test entity and chain
|
||||
if (entityType === 'water' || entityType === 'polymer') continue;
|
||||
|
||||
residuesIt.setSegment(chainSegment);
|
||||
while (residuesIt.hasNext) {
|
||||
const residueSegment = residuesIt.move();
|
||||
l.element = elements[residueSegment.start];
|
||||
graph.addComponent(ResidueSet.getEntryFromLocation(l), componentResidues);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.throwIfTimedOut();
|
||||
}
|
||||
|
||||
// assemble the core structure
|
||||
|
||||
const builder = ctx.inputStructure.subsetBuilder(true);
|
||||
for (const unit of ctx.inputStructure.units) {
|
||||
if (unit.kind !== Unit.Kind.Atomic) continue;
|
||||
|
||||
l.unit = unit;
|
||||
const { elements } = unit;
|
||||
const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, elements);
|
||||
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
|
||||
|
||||
builder.beginUnit(unit.id);
|
||||
|
||||
while (chainsIt.hasNext) {
|
||||
const chainSegment = chainsIt.move();
|
||||
l.element = elements[chainSegment.start];
|
||||
|
||||
const asym_id = StructureProperties.chain.label_asym_id(l);
|
||||
const op_name = StructureProperties.unit.operator_name(l);
|
||||
|
||||
if (includedPrdChains.has(asym_id) && includedPrdChains.get(asym_id)!.indexOf(op_name) >= 0) {
|
||||
builder.addElementRange(elements, chainSegment.start, chainSegment.end);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!componentResidues.hasLabelAsymId(asym_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
residuesIt.setSegment(chainSegment);
|
||||
while (residuesIt.hasNext) {
|
||||
const residueSegment = residuesIt.move();
|
||||
l.element = elements[residueSegment.start];
|
||||
|
||||
if (!componentResidues.has(l)) continue;
|
||||
builder.addElementRange(elements, residueSegment.start, residueSegment.end);
|
||||
}
|
||||
}
|
||||
builder.commitUnit();
|
||||
|
||||
ctx.throwIfTimedOut();
|
||||
}
|
||||
|
||||
const components = structureUnion(ctx.inputStructure, [builder.getStructure(), inner]);
|
||||
|
||||
// add water
|
||||
if (includeWater) {
|
||||
const finalBuilder = new StructureUniqueSubsetBuilder(ctx.inputStructure);
|
||||
const lookup = ctx.inputStructure.lookup3d;
|
||||
for (const unit of components.units) {
|
||||
const { x, y, z } = unit.conformation;
|
||||
const elements = unit.elements;
|
||||
for (let i = 0, _i = elements.length; i < _i; i++) {
|
||||
const e = elements[i];
|
||||
lookup.findIntoBuilderIf(x(e), y(e), z(e), radius, finalBuilder, testIsWater);
|
||||
finalBuilder.addToUnit(unit.id, e);
|
||||
}
|
||||
|
||||
ctx.throwIfTimedOut();
|
||||
}
|
||||
|
||||
return StructureSelection.Sequence(ctx.inputStructure, [finalBuilder.getStructure()]);
|
||||
} else {
|
||||
return StructureSelection.Sequence(ctx.inputStructure, [components]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const _entity_type = StructureProperties.entity.type;
|
||||
function testIsWater(l: StructureElement.Location) {
|
||||
return _entity_type(l) === 'water';
|
||||
}
|
||||
|
||||
function getPrdAsymIdx(structure: Structure) {
|
||||
const model = structure.models[0];
|
||||
const ids = new Set<string>();
|
||||
if (!MmcifFormat.is(model.sourceData)) return ids;
|
||||
const { _rowCount, asym_id } = model.sourceData.data.db.pdbx_molecule;
|
||||
for (let i = 0; i < _rowCount; i++) {
|
||||
ids.add(asym_id.value(i));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
function getStructConnInfo(structure: Structure) {
|
||||
const model = structure.models[0];
|
||||
const graph = new StructConnGraph();
|
||||
|
||||
if (!MmcifFormat.is(model.sourceData)) return graph;
|
||||
|
||||
const struct_conn = model.sourceData.data.db.struct_conn;
|
||||
const { conn_type_id } = struct_conn;
|
||||
const { ptnr1_label_asym_id, ptnr1_label_comp_id, ptnr1_label_seq_id, ptnr1_symmetry, pdbx_ptnr1_label_alt_id, pdbx_ptnr1_PDB_ins_code } = struct_conn;
|
||||
const { ptnr2_label_asym_id, ptnr2_label_comp_id, ptnr2_label_seq_id, ptnr2_symmetry, pdbx_ptnr2_label_alt_id, pdbx_ptnr2_PDB_ins_code } = struct_conn;
|
||||
|
||||
for (let i = 0; i < struct_conn._rowCount; i++) {
|
||||
const bondType = conn_type_id.value(i);
|
||||
if (bondType !== 'covale' && bondType !== 'metalc') continue;
|
||||
|
||||
const a: ResidueSetEntry = {
|
||||
label_asym_id: ptnr1_label_asym_id.value(i),
|
||||
label_comp_id: ptnr1_label_comp_id.value(i),
|
||||
label_seq_id: ptnr1_label_seq_id.value(i),
|
||||
label_alt_id: pdbx_ptnr1_label_alt_id.value(i),
|
||||
ins_code: pdbx_ptnr1_PDB_ins_code.value(i),
|
||||
operator_name: ptnr1_symmetry.value(i) ?? '1_555'
|
||||
};
|
||||
|
||||
const b: ResidueSetEntry = {
|
||||
label_asym_id: ptnr2_label_asym_id.value(i),
|
||||
label_comp_id: ptnr2_label_comp_id.value(i),
|
||||
label_seq_id: ptnr2_label_seq_id.value(i),
|
||||
label_alt_id: pdbx_ptnr2_label_alt_id.value(i),
|
||||
ins_code: pdbx_ptnr2_PDB_ins_code.value(i),
|
||||
operator_name: ptnr2_symmetry.value(i) ?? '1_555'
|
||||
};
|
||||
|
||||
graph.addEdge(a, b);
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
class StructConnGraph {
|
||||
vertices = new Map<string, ResidueSetEntry>();
|
||||
edges = new Map<string, string[]>();
|
||||
|
||||
private addVertex(e: ResidueSetEntry, label: string) {
|
||||
if (this.vertices.has(label)) return;
|
||||
this.vertices.set(label, e);
|
||||
this.edges.set(label, []);
|
||||
}
|
||||
|
||||
addEdge(a: ResidueSetEntry, b: ResidueSetEntry) {
|
||||
const al = ResidueSet.getLabel(a);
|
||||
const bl = ResidueSet.getLabel(b);
|
||||
this.addVertex(a, al);
|
||||
this.addVertex(b, bl);
|
||||
arraySetAdd(this.edges.get(al)!, bl);
|
||||
arraySetAdd(this.edges.get(bl)!, al);
|
||||
}
|
||||
|
||||
addComponent(start: ResidueSetEntry, set: ResidueSet) {
|
||||
const startLabel = ResidueSet.getLabel(start);
|
||||
|
||||
if (!this.vertices.has(startLabel)) {
|
||||
set.add(start);
|
||||
return;
|
||||
}
|
||||
|
||||
const visited = new Set<string>();
|
||||
const added = new Set<string>();
|
||||
const stack = [startLabel];
|
||||
|
||||
added.add(startLabel);
|
||||
set.add(start);
|
||||
|
||||
while (stack.length > 0) {
|
||||
const a = stack.pop()!;
|
||||
visited.add(a);
|
||||
|
||||
const u = this.vertices.get(a)!;
|
||||
|
||||
for (const b of this.edges.get(a)!) {
|
||||
if (visited.has(b)) continue;
|
||||
stack.push(b);
|
||||
|
||||
if (added.has(b)) continue;
|
||||
added.add(b);
|
||||
|
||||
const v = this.vertices.get(b)!;
|
||||
if (u.operator_name === v.operator_name) {
|
||||
set.add({ ...v, operator_name: start.operator_name });
|
||||
} else {
|
||||
set.add(v);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: unionBy (skip this one?), cluster
|
||||
@@ -23,6 +23,9 @@ import { StructureProperties } from '../properties';
|
||||
import { BoundaryHelper } from '../../../../mol-math/geometry/boundary-helper';
|
||||
import { Boundary } from '../../../../mol-math/geometry/boundary';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const osSize = OrderedSet.size;
|
||||
|
||||
/** Represents multiple structure element index locations */
|
||||
export interface Loci {
|
||||
readonly kind: 'element-loci',
|
||||
@@ -71,7 +74,7 @@ export namespace Loci {
|
||||
|
||||
export function size(loci: Loci) {
|
||||
let s = 0;
|
||||
for (const u of loci.elements) s += OrderedSet.size(u.indices);
|
||||
for (const u of loci.elements) s += osSize(u.indices);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,12 +98,12 @@ const residue = {
|
||||
microheterogeneityCompIds: p(microheterogeneityCompIds),
|
||||
secondary_structure_type: p(l => {
|
||||
if (!Unit.isAtomic(l.unit)) notAtomic();
|
||||
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.id);
|
||||
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
|
||||
return secStruc?.type[l.unit.residueIndex[l.element]] ?? SecondaryStructureType.Flag.NA;
|
||||
}),
|
||||
secondary_structure_key: p(l => {
|
||||
if (!Unit.isAtomic(l.unit)) notAtomic();
|
||||
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.id);
|
||||
const secStruc = SecondaryStructureProvider.get(l.structure).value?.get(l.unit.invariantId);
|
||||
return secStruc?.key[l.unit.residueIndex[l.element]] ?? -1;
|
||||
}),
|
||||
chem_comp_type: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.type),
|
||||
|
||||
@@ -23,7 +23,7 @@ import { Carbohydrates } from './carbohydrates/data';
|
||||
import { computeCarbohydrates } from './carbohydrates/compute';
|
||||
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
|
||||
import { idFactory } from '../../../mol-util/id-factory';
|
||||
import { Box3D, GridLookup3D } from '../../../mol-math/geometry';
|
||||
import { GridLookup3D } from '../../../mol-math/geometry';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { CustomProperties } from '../../custom-property';
|
||||
import { AtomicHierarchy } from '../model/properties/atomic';
|
||||
@@ -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 {
|
||||
@@ -1155,6 +1167,57 @@ namespace Structure {
|
||||
}
|
||||
}
|
||||
|
||||
export interface ForEachAtomicHierarchyElementParams {
|
||||
// Called for 1st element of each chain
|
||||
// Note that chains can be split, meaning each chain would be called multiple times.
|
||||
chain?: (e: StructureElement.Location<Unit.Atomic>) => void,
|
||||
// Called for 1st element of each residue
|
||||
residue?: (e: StructureElement.Location<Unit.Atomic>) => void,
|
||||
// Called for each element
|
||||
atom?: (e: StructureElement.Location<Unit.Atomic>) => void,
|
||||
};
|
||||
|
||||
export function eachAtomicHierarchyElement(structure: Structure, { chain, residue, atom }: ForEachAtomicHierarchyElementParams) {
|
||||
const l = StructureElement.Location.create<Unit.Atomic>(structure);
|
||||
for (const unit of structure.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();
|
||||
|
||||
if (chain) {
|
||||
l.element = elements[chainSegment.start];
|
||||
chain(l);
|
||||
}
|
||||
|
||||
if (!residue && !atom) continue;
|
||||
|
||||
residuesIt.setSegment(chainSegment);
|
||||
while (residuesIt.hasNext) {
|
||||
const residueSegment = residuesIt.move();
|
||||
|
||||
if (residue) {
|
||||
l.element = elements[residueSegment.start];
|
||||
residue(l);
|
||||
}
|
||||
|
||||
if (!atom) continue;
|
||||
|
||||
for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
|
||||
l.element = elements[j];
|
||||
atom(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export const DefaultSizeThresholds = {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { StateTransform } from '../../../mol-state';
|
||||
import { shallowEqual } from '../../../mol-util';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { PluginStateAnimation } from '../model';
|
||||
|
||||
export const AnimateStateInterpolation = PluginStateAnimation.create({
|
||||
name: 'built-in.animate-state-interpolation',
|
||||
display: { name: 'Animate State Interpolation' },
|
||||
display: { name: 'Animate State (experimental)' },
|
||||
params: () => ({
|
||||
transtionDurationInMs: PD.Numeric(2000, { min: 100, max: 30000, step: 10 })
|
||||
}),
|
||||
@@ -42,15 +43,25 @@ export const AnimateStateInterpolation = PluginStateAnimation.create({
|
||||
|
||||
for (const s of src) {
|
||||
for (const t of tar) {
|
||||
// TODO: better than quadratic alg.
|
||||
// TODO: support for adding/removing nodes
|
||||
if (t.ref !== s.ref) continue;
|
||||
if (t.version === s.version) continue;
|
||||
|
||||
const e = StateTransform.fromJSON(s), f = StateTransform.fromJSON(t);
|
||||
|
||||
const oldState = state.cells.get(s.ref);
|
||||
if (!oldState) continue;
|
||||
|
||||
let newState;
|
||||
if (!e.transformer.definition.interpolate) {
|
||||
update.to(s.ref).update(currentT <= 0.5 ? e.params : f.params);
|
||||
newState = currentT <= 0.5 ? e.params : f.params;
|
||||
} else {
|
||||
update.to(s.ref).update(e.transformer.definition.interpolate(e.params, f.params, currentT, ctx.plugin));
|
||||
newState = e.transformer.definition.interpolate(e.params, f.params, currentT, ctx.plugin);
|
||||
}
|
||||
|
||||
if (!shallowEqual(oldState, newState)) {
|
||||
update.to(s.ref).update(newState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function setStructureClipping(plugin: PluginContext, components: St
|
||||
await eachRepr(plugin, components, async (update, repr, clippingCell) => {
|
||||
if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return;
|
||||
|
||||
const structure = repr.obj!.data.source.data;
|
||||
const structure = repr.obj!.data.sourceData;
|
||||
// always use the root structure to get the loci so the clipping
|
||||
// stays applicable as long as the root structure does not change
|
||||
const loci = await lociGetter(structure.root);
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function setStructureOverpaint(plugin: PluginContext, components: S
|
||||
await eachRepr(plugin, components, async (update, repr, overpaintCell) => {
|
||||
if (types && types.length > 0 && !types.includes(repr.params!.values.type.name)) return;
|
||||
|
||||
const structure = repr.obj!.data.source.data;
|
||||
const structure = repr.obj!.data.sourceData;
|
||||
// always use the root structure to get the loci so the overpaint
|
||||
// stays applicable as long as the root structure does not change
|
||||
const loci = await lociGetter(structure.root);
|
||||
|
||||
@@ -425,6 +425,18 @@ const surroundings = StructureSelectionQuery('Surrounding Residues (5 \u212B) of
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const surroundingLigands = StructureSelectionQuery('Surrounding Ligands (5 \u212B) of Selection', MS.struct.modifier.union([
|
||||
MS.struct.modifier.surroundingLigands({
|
||||
0: MS.internal.generator.current(),
|
||||
radius: 5,
|
||||
'include-water': true
|
||||
})
|
||||
]), {
|
||||
description: 'Select ligand components within 5 \u212B of the current selection.',
|
||||
category: StructureSelectionCategory.Manipulate,
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const complement = StructureSelectionQuery('Inverse / Complement of Selection', MS.struct.modifier.union([
|
||||
MS.struct.modifier.exceptBy({
|
||||
0: MS.struct.generator.all(),
|
||||
@@ -645,6 +657,7 @@ export const StructureSelectionQueries = {
|
||||
ring,
|
||||
aromaticRing,
|
||||
surroundings,
|
||||
surroundingLigands,
|
||||
complement,
|
||||
covalentlyBonded,
|
||||
covalentlyOrMetallicBonded,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -12,7 +12,7 @@ import { StatefulPluginComponent } from '../component';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { utf8ByteCount, utf8Write } from '../../mol-io/common/utf8';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { zip } from '../../mol-util/zip/zip';
|
||||
import { Zip } from '../../mol-util/zip/zip';
|
||||
import { readFromFile } from '../../mol-util/data-source';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
import { PLUGIN_VERSION } from '../../mol-plugin/version';
|
||||
@@ -217,7 +217,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
zipDataObj['assets.json'] = data;
|
||||
}
|
||||
|
||||
const zipFile = zip(zipDataObj);
|
||||
const zipFile = await this.plugin.runTask(Zip(zipDataObj));
|
||||
return new Blob([zipFile], {type : 'application/zip'});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ export namespace PluginStateObject {
|
||||
return !!o && o.type.typeClass === 'Behavior';
|
||||
}
|
||||
|
||||
export interface Representation3DData<T extends Representation.Any, S extends StateObject = StateObject> { repr: T, source: S }
|
||||
export function CreateRepresentation3D<T extends Representation.Any, S extends StateObject = StateObject>(type: { name: string }) {
|
||||
export interface Representation3DData<T extends Representation.Any, S = any> { repr: T, sourceData: S }
|
||||
export function CreateRepresentation3D<T extends Representation.Any, S = any>(type: { name: string }) {
|
||||
return Create<Representation3DData<T, S>>({ ...type, typeClass: 'Representation3D' });
|
||||
}
|
||||
|
||||
@@ -102,10 +102,10 @@ export namespace PluginStateObject {
|
||||
export class Structure extends Create<_Structure>({ name: 'Structure', typeClass: 'Object' }) { }
|
||||
|
||||
export namespace Structure {
|
||||
export class Representation3D extends CreateRepresentation3D<StructureRepresentation<any> | ShapeRepresentation<any, any, any>, Structure>({ name: 'Structure 3D' }) { }
|
||||
export class Representation3D extends CreateRepresentation3D<StructureRepresentation<any>, _Structure>({ name: 'Structure 3D' }) { }
|
||||
|
||||
export interface Representation3DStateData {
|
||||
source: Representation3D,
|
||||
repr: StructureRepresentation<any>,
|
||||
/** used to restore state when the obj is removed */
|
||||
initialState: Partial<StructureRepresentationState>,
|
||||
state: Partial<StructureRepresentationState>,
|
||||
@@ -120,12 +120,12 @@ export namespace PluginStateObject {
|
||||
|
||||
export namespace Volume {
|
||||
export class Data extends Create<_Volume>({ name: 'Volume', typeClass: 'Object' }) { }
|
||||
export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>>({ name: 'Volume 3D' }) { }
|
||||
export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>, _Volume>({ name: 'Volume 3D' }) { }
|
||||
}
|
||||
|
||||
export namespace Shape {
|
||||
export class Provider extends Create<ShapeProvider<any, any, any>>({ name: 'Shape Provider', typeClass: 'Object' }) { }
|
||||
export class Representation3D extends CreateRepresentation3D<ShapeRepresentation<any, any, any>>({ name: 'Shape 3D' }) { }
|
||||
export class Representation3D extends CreateRepresentation3D<ShapeRepresentation<any, any, any>, unknown>({ name: 'Shape 3D' }) { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,15 @@ import * as Misc from './transforms/misc';
|
||||
import * as Model from './transforms/model';
|
||||
import * as Volume from './transforms/volume';
|
||||
import * as Representation from './transforms/representation';
|
||||
import * as Shape from './transforms/shape';
|
||||
|
||||
export const StateTransforms = {
|
||||
Data,
|
||||
Misc,
|
||||
Model,
|
||||
Volume,
|
||||
Representation
|
||||
Representation,
|
||||
Shape
|
||||
};
|
||||
|
||||
export type StateTransforms = typeof StateTransforms
|
||||
@@ -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',
|
||||
@@ -382,6 +414,10 @@ const ModelFromTrajectory = PluginStateTransform.BuiltIn({
|
||||
return new SO.Molecule.Model(model, { label, description });
|
||||
});
|
||||
},
|
||||
interpolate(a, b, t) {
|
||||
const modelIndex = t >= 1 ? b.modelIndex : a.modelIndex + Math.floor((b.modelIndex - a.modelIndex + 1) * t);
|
||||
return { modelIndex };
|
||||
},
|
||||
dispose({ b }) {
|
||||
b?.data.customProperties.dispose();
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@ import { DihedralParams, DihedralRepresentation } from '../../mol-repr/shape/loc
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
import { Clipping } from '../../mol-theme/clipping';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { getBoxMesh } from './shape';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
|
||||
export { StructureRepresentation3D };
|
||||
export { ExplodeStructureRepresentation3D };
|
||||
@@ -126,14 +130,15 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
return Task.create('Structure Representation', async ctx => {
|
||||
const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset };
|
||||
const provider = plugin.representation.structure.registry.get(params.type.name);
|
||||
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
|
||||
const data = provider.getData?.(a.data, params.type.params) || a.data;
|
||||
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, data);
|
||||
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, provider.getParams);
|
||||
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, params);
|
||||
repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: a.data }, params));
|
||||
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: data }, params);
|
||||
repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: data }, params));
|
||||
|
||||
const props = params.type.params || {};
|
||||
await repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
return new SO.Molecule.Structure.Representation3D({ repr, source: a }, { label: provider.label });
|
||||
await repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
return new SO.Molecule.Structure.Representation3D({ repr, sourceData: a.data }, { label: provider.label });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams, cache }, plugin: PluginContext) {
|
||||
@@ -141,26 +146,28 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
if (newParams.type.name !== oldParams.type.name) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const provider = plugin.representation.structure.registry.get(newParams.type.name);
|
||||
if (provider.mustRecreate?.(oldParams.type.params, newParams.type.params)) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const data = provider.getData?.(a.data, newParams.type.params) || a.data;
|
||||
const propertyCtx = { runtime: ctx, assetManager: plugin.managers.asset };
|
||||
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
|
||||
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, data);
|
||||
|
||||
// TODO: if themes had a .needsUpdate method the following block could
|
||||
// be optimized and only executed conditionally
|
||||
// dispose isn't called on update so we need to handle it manually
|
||||
Theme.releaseDependencies(plugin.representation.structure.themes, { structure: b.data.source.data }, oldParams);
|
||||
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, newParams);
|
||||
b.data.repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: a.data }, newParams));
|
||||
Theme.releaseDependencies(plugin.representation.structure.themes, { structure: b.data.sourceData }, oldParams);
|
||||
await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: data }, newParams);
|
||||
b.data.repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: data }, newParams));
|
||||
|
||||
const props = { ...b.data.repr.props, ...newParams.type.params };
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
dispose({ b, params }, plugin: PluginContext) {
|
||||
if (!b || !params) return;
|
||||
|
||||
const structure = b.data.source.data;
|
||||
const structure = b.data.sourceData;
|
||||
const provider = plugin.representation.structure.registry.get(params.type.name);
|
||||
if (provider.ensureCustomProperties) provider.ensureCustomProperties.detach(structure);
|
||||
Theme.releaseDependencies(plugin.representation.structure.themes, { structure }, params);
|
||||
@@ -191,26 +198,26 @@ const UnwindStructureAssemblyRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const unitTransforms = new StructureUnitTransforms(structure);
|
||||
unwindStructureAssembly(structure, unitTransforms, params.t);
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { unitTransforms },
|
||||
initialState: { unitTransforms: new StructureUnitTransforms(structure) },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Unwind T = ${params.t.toFixed(2)}` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
|
||||
const unitTransforms = b.data.state.unitTransforms!;
|
||||
unwindStructureAssembly(structure, unitTransforms, newParams.t);
|
||||
b.label = `Unwind T = ${newParams.t.toFixed(2)}`;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
});
|
||||
@@ -228,26 +235,26 @@ const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const unitTransforms = new StructureUnitTransforms(structure.root);
|
||||
explodeStructure(structure, unitTransforms, params.t);
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { unitTransforms },
|
||||
initialState: { unitTransforms: new StructureUnitTransforms(structure.root) },
|
||||
info: structure.root,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Explode T = ${params.t.toFixed(2)}` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
if (b.data.info !== structure.root) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
if (oldParams.t === newParams.t) return StateTransformer.UpdateResult.Unchanged;
|
||||
const unitTransforms = b.data.state.unitTransforms!;
|
||||
explodeStructure(structure.root, unitTransforms, newParams.t);
|
||||
b.label = `Explode T = ${newParams.t.toFixed(2)}`;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
});
|
||||
@@ -276,28 +283,28 @@ const OverpaintStructureRepresentation3DFromScript = PluginStateTransform.BuiltI
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const overpaint = Overpaint.ofScript(params.layers, structure);
|
||||
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { overpaint },
|
||||
initialState: { overpaint: Overpaint.Empty },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Overpaint (${overpaint.layers.length} Layers)` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const oldStructure = b.data.info as Structure;
|
||||
const newStructure = a.data.source.data;
|
||||
const newStructure = a.data.sourceData;
|
||||
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldOverpaint = b.data.state.overpaint!;
|
||||
const newOverpaint = Overpaint.ofScript(newParams.layers, newStructure);
|
||||
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
b.data.state.overpaint = newOverpaint;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
b.label = `Overpaint (${newOverpaint.layers.length} Layers)`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
@@ -328,28 +335,28 @@ const OverpaintStructureRepresentation3DFromBundle = PluginStateTransform.BuiltI
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const overpaint = Overpaint.ofBundle(params.layers, structure);
|
||||
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { overpaint },
|
||||
initialState: { overpaint: Overpaint.Empty },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Overpaint (${overpaint.layers.length} Layers)` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const oldStructure = b.data.info as Structure;
|
||||
const newStructure = a.data.source.data;
|
||||
const newStructure = a.data.sourceData;
|
||||
if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldOverpaint = b.data.state.overpaint!;
|
||||
const newOverpaint = Overpaint.ofBundle(newParams.layers, newStructure);
|
||||
if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
b.data.state.overpaint = newOverpaint;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
b.label = `Overpaint (${newOverpaint.layers.length} Layers)`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
@@ -377,27 +384,27 @@ const TransparencyStructureRepresentation3DFromScript = PluginStateTransform.Bui
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const transparency = Transparency.ofScript(params.layers, structure);
|
||||
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { transparency },
|
||||
initialState: { transparency: Transparency.Empty },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Transparency (${transparency.layers.length} Layers)` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldTransparency = b.data.state.transparency!;
|
||||
const newTransparency = Transparency.ofScript(newParams.layers, structure);
|
||||
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
b.data.state.transparency = newTransparency;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
b.label = `Transparency (${newTransparency.layers.length} Layers)`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
@@ -426,27 +433,27 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const transparency = Transparency.ofBundle(params.layers, structure);
|
||||
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { transparency },
|
||||
initialState: { transparency: Transparency.Empty },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Transparency (${transparency.layers.length} Layers)` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldTransparency = b.data.state.transparency!;
|
||||
const newTransparency = Transparency.ofBundle(newParams.layers, structure);
|
||||
if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
b.data.state.transparency = newTransparency;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
b.label = `Transparency (${newTransparency.layers.length} Layers)`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
@@ -474,27 +481,27 @@ const ClippingStructureRepresentation3DFromScript = PluginStateTransform.BuiltIn
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const clipping = Clipping.ofScript(params.layers, structure);
|
||||
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { clipping },
|
||||
initialState: { clipping: Clipping.Empty },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Clipping (${clipping.layers.length} Layers)` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldClipping = b.data.state.clipping!;
|
||||
const newClipping = Clipping.ofScript(newParams.layers, structure);
|
||||
if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
b.data.state.clipping = newClipping;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
b.label = `Clipping (${newClipping.layers.length} Layers)`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
@@ -523,27 +530,27 @@ const ClippingStructureRepresentation3DFromBundle = PluginStateTransform.BuiltIn
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
const structure = a.data.source.data;
|
||||
const structure = a.data.sourceData;
|
||||
const clipping = Clipping.ofBundle(params.layers, structure);
|
||||
|
||||
return new SO.Molecule.Structure.Representation3DState({
|
||||
state: { clipping },
|
||||
initialState: { clipping: Clipping.Empty },
|
||||
info: structure,
|
||||
source: a
|
||||
repr: a.data.repr
|
||||
}, { label: `Clipping (${clipping.layers.length} Layers)` });
|
||||
},
|
||||
update({ a, b, newParams, oldParams }) {
|
||||
const structure = b.data.info as Structure;
|
||||
if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.source.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
|
||||
if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
|
||||
|
||||
const oldClipping = b.data.state.clipping!;
|
||||
const newClipping = Clipping.ofBundle(newParams.layers, structure);
|
||||
if (Clipping.areEqual(oldClipping, newClipping)) return StateTransformer.UpdateResult.Unchanged;
|
||||
|
||||
b.data.state.clipping = newClipping;
|
||||
b.data.source = a;
|
||||
b.data.repr = a.data.repr;
|
||||
b.label = `Clipping (${newClipping.layers.length} Layers)`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
}
|
||||
@@ -646,7 +653,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
|
||||
const props = params.type.params || {};
|
||||
await repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
return new SO.Volume.Representation3D({ repr, source: a }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
|
||||
return new SO.Volume.Representation3D({ repr, sourceData: a.data }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
@@ -659,6 +666,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams.type.params };
|
||||
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, newParams));
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
b.description = VolumeRepresentation3DHelpers.getDescription(props);
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
@@ -686,13 +694,14 @@ const ShapeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...PD.getDefaultValues(a.data.params), ...params };
|
||||
const repr = ShapeRepresentation(a.data.getShape, a.data.geometryUtils);
|
||||
await repr.createOrUpdate(props, a.data.data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: a.data.label });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: a.data }, { label: a.data.label });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
return Task.create('Shape Representation', async ctx => {
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
await b.data.repr.createOrUpdate(props, a.data.data).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
}
|
||||
@@ -720,7 +729,7 @@ const ModelUnitcell3D = PluginStateTransform.BuiltIn({
|
||||
const data = getUnitcellData(a.data, symmetry, params);
|
||||
const repr = UnitcellRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => UnitcellParams);
|
||||
await repr.createOrUpdate(params, data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Unit Cell`, description: symmetry.spacegroup.name });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Unit Cell`, description: symmetry.spacegroup.name });
|
||||
});
|
||||
},
|
||||
update({ a, b, newParams }) {
|
||||
@@ -730,7 +739,42 @@ const ModelUnitcell3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
const data = getUnitcellData(a.data, symmetry, props);
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
b.data.sourceData = data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export { StructureBoundingBox3D };
|
||||
type StructureBoundingBox3D = typeof StructureBoundingBox3D
|
||||
const StructureBoundingBox3D = PluginStateTransform.BuiltIn({
|
||||
name: 'structure-bounding-box-3d',
|
||||
display: 'Bounding Box',
|
||||
from: SO.Molecule.Structure,
|
||||
to: SO.Shape.Representation3D,
|
||||
params: {
|
||||
radius: PD.Numeric(0.05, { min: 0.01, max: 4, step: 0.01 }, { isEssential: true }),
|
||||
color: PD.Color(ColorNames.red, { isEssential: true }),
|
||||
...Mesh.Params,
|
||||
}
|
||||
})({
|
||||
canAutoUpdate() {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Bounding Box', async ctx => {
|
||||
const repr = ShapeRepresentation((_, data: { box: Box3D, radius: number, color: Color }, __, shape) => {
|
||||
const mesh = getBoxMesh(data.box, data.radius, shape?.geometry);
|
||||
return Shape.create('Bouding Box', data, mesh, () => data.color, () => 1, () => 'Bounding Box');
|
||||
}, Mesh.Utils);
|
||||
await repr.createOrUpdate(params, { box: a.data.boundary.box, radius: params.radius, color: params.color }).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: a.data }, { label: `Bounding Box` });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
return Task.create('Bounding Box', async ctx => {
|
||||
await b.data.repr.createOrUpdate(newParams, { box: a.data.boundary.box, radius: newParams.radius, color: newParams.color }).runInContext(ctx);
|
||||
b.data.sourceData = a.data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
}
|
||||
@@ -755,7 +799,7 @@ const StructureSelectionsDistance3D = PluginStateTransform.BuiltIn({
|
||||
const data = getDistanceDataFromStructureSelections(a.data);
|
||||
const repr = DistanceRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => DistanceParams);
|
||||
await repr.createOrUpdate(params, data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Distance` });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Distance` });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
@@ -763,7 +807,7 @@ const StructureSelectionsDistance3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
const data = getDistanceDataFromStructureSelections(a.data);
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
b.data.sourceData = data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
@@ -788,7 +832,7 @@ const StructureSelectionsAngle3D = PluginStateTransform.BuiltIn({
|
||||
const data = getAngleDataFromStructureSelections(a.data);
|
||||
const repr = AngleRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => AngleParams);
|
||||
await repr.createOrUpdate(params, data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Angle` });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Angle` });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
@@ -796,7 +840,7 @@ const StructureSelectionsAngle3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
const data = getAngleDataFromStructureSelections(a.data);
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
b.data.sourceData = data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
@@ -821,7 +865,7 @@ const StructureSelectionsDihedral3D = PluginStateTransform.BuiltIn({
|
||||
const data = getDihedralDataFromStructureSelections(a.data);
|
||||
const repr = DihedralRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => DihedralParams);
|
||||
await repr.createOrUpdate(params, data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Dihedral` });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Dihedral` });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
@@ -829,7 +873,7 @@ const StructureSelectionsDihedral3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
const data = getDihedralDataFromStructureSelections(a.data);
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
b.data.sourceData = data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
@@ -854,7 +898,7 @@ const StructureSelectionsLabel3D = PluginStateTransform.BuiltIn({
|
||||
const data = getLabelDataFromStructureSelections(a.data);
|
||||
const repr = LabelRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => LabelParams);
|
||||
await repr.createOrUpdate(params, data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Label` });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Label` });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
@@ -862,7 +906,7 @@ const StructureSelectionsLabel3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
const data = getLabelDataFromStructureSelections(a.data);
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
b.data.sourceData = data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
@@ -887,7 +931,7 @@ const StructureSelectionsOrientation3D = PluginStateTransform.BuiltIn({
|
||||
const data = getOrientationDataFromStructureSelections(a.data);
|
||||
const repr = OrientationRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => OrientationParams);
|
||||
await repr.createOrUpdate(params, data).runInContext(ctx);
|
||||
return new SO.Shape.Representation3D({ repr, source: a }, { label: `Orientation` });
|
||||
return new SO.Shape.Representation3D({ repr, sourceData: data }, { label: `Orientation` });
|
||||
});
|
||||
},
|
||||
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
|
||||
@@ -895,7 +939,7 @@ const StructureSelectionsOrientation3D = PluginStateTransform.BuiltIn({
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
const data = getOrientationDataFromStructureSelections(a.data);
|
||||
await b.data.repr.createOrUpdate(props, data).runInContext(ctx);
|
||||
b.data.source = a;
|
||||
b.data.sourceData = data;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
|
||||
69
src/mol-plugin-state/transforms/shape.ts
Normal file
69
src/mol-plugin-state/transforms/shape.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { BoxCage } from '../../mol-geo/primitive/box';
|
||||
import { Box3D, Sphere3D } from '../../mol-math/geometry';
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../objects';
|
||||
|
||||
export { BoxShape3D };
|
||||
type BoxShape3D = typeof BoxShape3D
|
||||
const BoxShape3D = PluginStateTransform.BuiltIn({
|
||||
name: 'box-shape-3d',
|
||||
display: 'Box Shape',
|
||||
from: SO.Root,
|
||||
to: SO.Shape.Provider,
|
||||
params: {
|
||||
bottomLeft: PD.Vec3(Vec3()),
|
||||
topRight: PD.Vec3(Vec3.create(1, 1, 1)),
|
||||
radius: PD.Numeric(0.15, { min: 0.01, max: 4, step: 0.01 }),
|
||||
color: PD.Color(ColorNames.red)
|
||||
}
|
||||
})({
|
||||
canAutoUpdate() {
|
||||
return true;
|
||||
},
|
||||
apply({ params }) {
|
||||
return Task.create('Shape Representation', async ctx => {
|
||||
return new SO.Shape.Provider({
|
||||
label: 'Box',
|
||||
data: params,
|
||||
params: Mesh.Params,
|
||||
getShape: (_, data: typeof params) => {
|
||||
const mesh = getBoxMesh(Box3D.create(params.bottomLeft, params.topRight), params.radius);
|
||||
return Shape.create('Box', data, mesh, () => data.color, () => 1, () => 'Box');
|
||||
},
|
||||
geometryUtils: Mesh.Utils
|
||||
}, { label: 'Box' });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export function getBoxMesh(box: Box3D, radius: number, oldMesh?: Mesh) {
|
||||
const diag = Vec3.sub(Vec3(), box.max, box.min);
|
||||
const translateUnit = Mat4.fromTranslation(Mat4(), Vec3.create(0.5, 0.5, 0.5));
|
||||
const scale = Mat4.fromScaling(Mat4(), diag);
|
||||
const translate = Mat4.fromTranslation(Mat4(), box.min);
|
||||
const transform = Mat4.mul3(Mat4(), translate, scale, translateUnit);
|
||||
|
||||
// TODO: optimize?
|
||||
const state = MeshBuilder.createState(256, 128, oldMesh);
|
||||
state.currentGroup = 1;
|
||||
MeshBuilder.addCage(state, transform, BoxCage(), radius, 2, 20);
|
||||
const mesh = MeshBuilder.getMesh(state);
|
||||
|
||||
const center = Vec3.scaleAndAdd(Vec3(), box.min, diag, 0.5);
|
||||
const sphereRadius = Vec3.distance(box.min, center);
|
||||
mesh.setBoundingSphere(Sphere3D.create(center, sphereRadius));
|
||||
|
||||
return mesh;
|
||||
}
|
||||
@@ -151,7 +151,7 @@ const VolumeFromDensityServerCif = PluginStateTransform.BuiltIn({
|
||||
const densityServerCif = CIF.schema.densityServer(block);
|
||||
const volume = await volumeFromDensityServerData(densityServerCif, { entryId: params.entryId }).runInContext(ctx);
|
||||
const [x, y, z] = volume.grid.cells.space.dimensions;
|
||||
const props = { label: densityServerCif.volume_data_3d_info.name.value(0), description: `Volume ${x}\u00D7${y}\u00D7${z}` };
|
||||
const props = { label: params.entryId ?? densityServerCif.volume_data_3d_info.name.value(0), description: `Volume ${x}\u00D7${y}\u00D7${z}` };
|
||||
return new SO.Volume.Data(volume, props);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -41,6 +41,9 @@ export function MoleculeSvg() { return _Molecule; }
|
||||
const _CubeOutline = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z" /></svg>;
|
||||
export function CubeOutlineSvg() { return _CubeOutline; }
|
||||
|
||||
const _CubeSend = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M16,4L9,8.04V15.96L16,20L23,15.96V8.04M16,6.31L19.8,8.5L16,10.69L12.21,8.5M0,7V9H7V7M11,10.11L15,12.42V17.11L11,14.81M21,10.11V14.81L17,17.11V12.42M2,11V13H7V11M4,15V17H7V15" /></svg>;
|
||||
export function CubeSendSvg() { return _CubeSend; }
|
||||
|
||||
const _CursorDefaultOutline = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M10.07,14.27C10.57,14.03 11.16,14.25 11.4,14.75L13.7,19.74L15.5,18.89L13.19,13.91C12.95,13.41 13.17,12.81 13.67,12.58L13.95,12.5L16.25,12.05L8,5.12V15.9L9.82,14.43L10.07,14.27M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z' /></svg>;
|
||||
export function CursorDefaultOutlineSvg() { return _CursorDefaultOutline; }
|
||||
|
||||
|
||||
@@ -240,8 +240,8 @@ function renderSimple(options: { props: ParamProps<any>, state: { showHelp: bool
|
||||
const help = props.param.help
|
||||
? props.param.help(props.value)
|
||||
: { description: props.param.description, legend: props.param.legend };
|
||||
const desc = props.param.description;
|
||||
const hasHelp = help.description || help.legend;
|
||||
const desc = label + (hasHelp ? '. Click for help.' : '');
|
||||
return <>
|
||||
<ControlRow
|
||||
className={className}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { StateTransformParameters } from '../state/common';
|
||||
import * as React from 'react';
|
||||
import { VolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/behavior';
|
||||
import { ExpandableControlRow, IconButton } from '../controls/common';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -5,13 +5,12 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateActions } from '../mol-plugin-state/actions';
|
||||
import { AssignColorVolume } from '../mol-plugin-state/actions/volume';
|
||||
import { StateTransforms } from '../mol-plugin-state/transforms';
|
||||
|
||||
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 { BoxifyVolumeStreaming, CreateVolumeStreamingBehavior, InitVolumeStreaming } from '../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { VolumeStreamingCustomControls } from './custom/volume';
|
||||
|
||||
export { PluginUISpec };
|
||||
|
||||
@@ -40,50 +39,7 @@ namespace PluginUISpec {
|
||||
|
||||
export const DefaultPluginUISpec = (): PluginUISpec => ({
|
||||
...DefaultPluginSpec(),
|
||||
actions: [
|
||||
PluginSpec.Action(StateActions.Structure.DownloadStructure),
|
||||
PluginSpec.Action(StateActions.Structure.AddTrajectory),
|
||||
PluginSpec.Action(StateActions.Volume.DownloadDensity),
|
||||
PluginSpec.Action(StateActions.DataFormat.DownloadFile),
|
||||
PluginSpec.Action(StateActions.DataFormat.OpenFiles),
|
||||
PluginSpec.Action(StateActions.Structure.EnableModelCustomProps),
|
||||
PluginSpec.Action(StateActions.Structure.EnableStructureCustomProps),
|
||||
|
||||
// Volume streaming
|
||||
PluginSpec.Action(InitVolumeStreaming),
|
||||
PluginSpec.Action(BoxifyVolumeStreaming),
|
||||
PluginSpec.Action(CreateVolumeStreamingBehavior),
|
||||
|
||||
PluginSpec.Action(StateTransforms.Data.Download),
|
||||
PluginSpec.Action(StateTransforms.Data.ParseCif),
|
||||
PluginSpec.Action(StateTransforms.Data.ParseCcp4),
|
||||
PluginSpec.Action(StateTransforms.Data.ParseDsn6),
|
||||
|
||||
PluginSpec.Action(StateTransforms.Model.TrajectoryFromMmCif),
|
||||
PluginSpec.Action(StateTransforms.Model.TrajectoryFromCifCore),
|
||||
PluginSpec.Action(StateTransforms.Model.TrajectoryFromPDB),
|
||||
PluginSpec.Action(StateTransforms.Model.TransformStructureConformation),
|
||||
PluginSpec.Action(StateTransforms.Model.StructureFromModel),
|
||||
PluginSpec.Action(StateTransforms.Model.StructureFromTrajectory),
|
||||
PluginSpec.Action(StateTransforms.Model.ModelFromTrajectory),
|
||||
PluginSpec.Action(StateTransforms.Model.StructureSelectionFromScript),
|
||||
PluginSpec.Action(StateTransforms.Representation.StructureRepresentation3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.StructureSelectionsDistance3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.StructureSelectionsAngle3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.StructureSelectionsDihedral3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.StructureSelectionsLabel3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.StructureSelectionsOrientation3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.ModelUnitcell3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.ExplodeStructureRepresentation3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.UnwindStructureAssemblyRepresentation3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.OverpaintStructureRepresentation3DFromScript),
|
||||
PluginSpec.Action(StateTransforms.Representation.TransparencyStructureRepresentation3DFromScript),
|
||||
|
||||
PluginSpec.Action(AssignColorVolume),
|
||||
PluginSpec.Action(StateTransforms.Volume.VolumeFromCcp4),
|
||||
PluginSpec.Action(StateTransforms.Volume.VolumeFromDsn6),
|
||||
PluginSpec.Action(StateTransforms.Volume.VolumeFromCube),
|
||||
PluginSpec.Action(StateTransforms.Volume.VolumeFromDx),
|
||||
PluginSpec.Action(StateTransforms.Representation.VolumeRepresentation3D),
|
||||
]
|
||||
customParamEditors: [
|
||||
[CreateVolumeStreamingBehavior, VolumeStreamingCustomControls]
|
||||
],
|
||||
});
|
||||
@@ -4,7 +4,6 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { State } from '../../mol-state';
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { Icon, CodeSvg } from '../controls/icons';
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { ParameterControls, ParamOnChange } from '../controls/parameters';
|
||||
import { Button } from '../controls/common';
|
||||
|
||||
@@ -8,7 +8,6 @@ import { State, StateTransform, StateTransformer } from '../../mol-state';
|
||||
import { memoizeLatest } from '../../mol-util/memoize';
|
||||
import { StateTransformParameters, TransformControlBase } from './common';
|
||||
import { Observable } from 'rxjs';
|
||||
import * as React from 'react';
|
||||
import { PluginUIComponent } from '../base';
|
||||
|
||||
export { UpdateTransformControl, TransformUpdaterControl };
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -10,14 +10,17 @@ import { Loci } from '../../mol-model/loci';
|
||||
import { StructureElement } from '../../mol-model/structure';
|
||||
import { StructureMeasurementCell, StructureMeasurementOptions, StructureMeasurementParams } from '../../mol-plugin-state/manager/structure/measurement';
|
||||
import { StructureSelectionHistoryEntry } from '../../mol-plugin-state/manager/structure/selection';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { AngleData } from '../../mol-repr/shape/loci/angle';
|
||||
import { DihedralData } from '../../mol-repr/shape/loci/dihedral';
|
||||
import { DistanceData } from '../../mol-repr/shape/loci/distance';
|
||||
import { LabelData } from '../../mol-repr/shape/loci/label';
|
||||
import { angleLabel, dihedralLabel, distanceLabel, lociLabel } from '../../mol-theme/label';
|
||||
import { FiniteArray } from '../../mol-util/type-helpers';
|
||||
import { CollapsableControls, PurePluginUIComponent } from '../base';
|
||||
import { ActionMenu } from '../controls/action-menu';
|
||||
import { Button, ExpandGroup, IconButton, ToggleButton } from '../controls/common';
|
||||
import { Icon, PencilRulerSvg, SetSvg, ArrowUpwardSvg, ArrowDownwardSvg, DeleteOutlinedSvg, HelpOutlineSvg, AddSvg, TuneSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg, MoreHorizSvg } from '../controls/icons';
|
||||
import { AddSvg, ArrowDownwardSvg, ArrowUpwardSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, MoreHorizSvg, PencilRulerSvg, SetSvg, TuneSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg } from '../controls/icons';
|
||||
import { ParameterControls } from '../controls/parameters';
|
||||
import { UpdateTransformControl } from '../state/update-transform';
|
||||
import { ToggleSelectionModeButton } from './selection';
|
||||
@@ -216,7 +219,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 Partial<DistanceData & AngleData & DihedralData & LabelData> | undefined;
|
||||
}
|
||||
|
||||
delete = () => {
|
||||
@@ -234,8 +237,8 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
|
||||
if (!selections) return;
|
||||
|
||||
this.plugin.managers.interactivity.lociHighlights.clearHighlights();
|
||||
for (const d of selections.data) {
|
||||
this.plugin.managers.interactivity.lociHighlights.highlight({ loci: d.loci }, false);
|
||||
for (const loci of this.lociArray) {
|
||||
this.plugin.managers.interactivity.lociHighlights.highlight({ loci }, false);
|
||||
}
|
||||
this.plugin.managers.interactivity.lociHighlights.highlight({ loci: this.props.cell.obj?.data.repr.getLoci()! }, false);
|
||||
}
|
||||
@@ -250,21 +253,31 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
|
||||
const selections = this.selections;
|
||||
if (!selections) return;
|
||||
|
||||
const sphere = Loci.getBundleBoundingSphere(toLociBundle(selections.data));
|
||||
const sphere = Loci.getBundleBoundingSphere({ loci: this.lociArray });
|
||||
if (sphere) {
|
||||
this.plugin.managers.camera.focusSphere(sphere);
|
||||
}
|
||||
}
|
||||
|
||||
private get lociArray(): FiniteArray<Loci> {
|
||||
const selections = this.selections;
|
||||
if (!selections) return [];
|
||||
if (selections.infos) return [selections.infos[0].loci];
|
||||
if (selections.pairs) return selections.pairs[0].loci;
|
||||
if (selections.triples) return selections.triples[0].loci;
|
||||
if (selections.quads) return selections.quads[0].loci;
|
||||
return [];
|
||||
}
|
||||
|
||||
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 });
|
||||
default: return '';
|
||||
}
|
||||
|
||||
if (!selections) return '<empty>';
|
||||
if (selections.infos) return lociLabel(selections.infos[0].loci, { condensed: true });
|
||||
if (selections.pairs) return distanceLabel(selections.pairs[0], { condensed: true, unitLabel: this.plugin.managers.structure.measurement.state.options.distanceUnitLabel });
|
||||
if (selections.triples) return angleLabel(selections.triples[0], { condensed: true });
|
||||
if (selections.quads) return dihedralLabel(selections.quads[0], { condensed: true });
|
||||
return '<empty>';
|
||||
}
|
||||
|
||||
get actions(): ActionMenu.Items {
|
||||
@@ -302,8 +315,4 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
|
||||
</>}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
function toLociBundle(data: FiniteArray<{ loci: Loci }, any>): { loci: FiniteArray<Loci, any> } {
|
||||
return { loci: (data.map(d => d.loci) as unknown as FiniteArray<Loci, any>) };
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
*/
|
||||
|
||||
import { produce } from 'immer';
|
||||
import * as React from 'react';
|
||||
import { Canvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { StateTransform } from '../../mol-state';
|
||||
|
||||
@@ -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' }
|
||||
});
|
||||
@@ -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...
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@ 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';
|
||||
import { AnimateStateInterpolation } from '../mol-plugin-state/animation/built-in/state-interpolation';
|
||||
|
||||
export { PluginSpec };
|
||||
|
||||
@@ -55,12 +60,60 @@ namespace PluginSpec {
|
||||
}
|
||||
|
||||
export const DefaultPluginSpec = (): PluginSpec => ({
|
||||
actions: [
|
||||
PluginSpec.Action(StateActions.Structure.DownloadStructure),
|
||||
PluginSpec.Action(StateActions.Structure.AddTrajectory),
|
||||
PluginSpec.Action(StateActions.Volume.DownloadDensity),
|
||||
PluginSpec.Action(StateActions.DataFormat.DownloadFile),
|
||||
PluginSpec.Action(StateActions.DataFormat.OpenFiles),
|
||||
PluginSpec.Action(StateActions.Structure.EnableModelCustomProps),
|
||||
PluginSpec.Action(StateActions.Structure.EnableStructureCustomProps),
|
||||
|
||||
// Volume streaming
|
||||
PluginSpec.Action(InitVolumeStreaming),
|
||||
PluginSpec.Action(BoxifyVolumeStreaming),
|
||||
PluginSpec.Action(CreateVolumeStreamingBehavior),
|
||||
|
||||
PluginSpec.Action(StateTransforms.Data.Download),
|
||||
PluginSpec.Action(StateTransforms.Data.ParseCif),
|
||||
PluginSpec.Action(StateTransforms.Data.ParseCcp4),
|
||||
PluginSpec.Action(StateTransforms.Data.ParseDsn6),
|
||||
|
||||
PluginSpec.Action(StateTransforms.Model.TrajectoryFromMmCif),
|
||||
PluginSpec.Action(StateTransforms.Model.TrajectoryFromCifCore),
|
||||
PluginSpec.Action(StateTransforms.Model.TrajectoryFromPDB),
|
||||
PluginSpec.Action(StateTransforms.Model.TransformStructureConformation),
|
||||
PluginSpec.Action(StateTransforms.Model.StructureFromModel),
|
||||
PluginSpec.Action(StateTransforms.Model.StructureFromTrajectory),
|
||||
PluginSpec.Action(StateTransforms.Model.ModelFromTrajectory),
|
||||
PluginSpec.Action(StateTransforms.Model.StructureSelectionFromScript),
|
||||
PluginSpec.Action(StateTransforms.Representation.StructureRepresentation3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.StructureSelectionsDistance3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.StructureSelectionsAngle3D),
|
||||
PluginSpec.Action(StateTransforms.Representation.StructureSelectionsDihedral3D),
|
||||
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),
|
||||
PluginSpec.Action(StateTransforms.Representation.TransparencyStructureRepresentation3DFromScript),
|
||||
|
||||
PluginSpec.Action(AssignColorVolume),
|
||||
PluginSpec.Action(StateTransforms.Volume.VolumeFromCcp4),
|
||||
PluginSpec.Action(StateTransforms.Volume.VolumeFromDsn6),
|
||||
PluginSpec.Action(StateTransforms.Volume.VolumeFromCube),
|
||||
PluginSpec.Action(StateTransforms.Volume.VolumeFromDx),
|
||||
PluginSpec.Action(StateTransforms.Representation.VolumeRepresentation3D),
|
||||
],
|
||||
behaviors: [
|
||||
PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci),
|
||||
PluginSpec.Behavior(PluginBehaviors.Representation.SelectLoci),
|
||||
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),
|
||||
@@ -74,6 +127,7 @@ export const DefaultPluginSpec = (): PluginSpec => ({
|
||||
AnimateModelIndex,
|
||||
AnimateCameraSpin,
|
||||
AnimateStateSnapshots,
|
||||
AnimateAssemblyUnwind
|
||||
AnimateAssemblyUnwind,
|
||||
AnimateStateInterpolation
|
||||
]
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user