mirror of
https://github.com/molstar/molstar.git
synced 2026-06-06 06:34:23 +08:00
Compare commits
82 Commits
v0.7.0-dev
...
v0.7.1-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3415fe0847 | ||
|
|
1569958a29 | ||
|
|
3543faa0c2 | ||
|
|
251dbf3877 | ||
|
|
32d35efef0 | ||
|
|
8b6428a61d | ||
|
|
dda43370cf | ||
|
|
92c1e979c0 | ||
|
|
ad38a33943 | ||
|
|
88c276a4c7 | ||
|
|
0a3d19235d | ||
|
|
0d90fd1f06 | ||
|
|
02d3274e83 | ||
|
|
2531af2b94 | ||
|
|
850328be4e | ||
|
|
f8ce9cbb65 | ||
|
|
2af9d1cabf | ||
|
|
e8d1737d40 | ||
|
|
0328e93518 | ||
|
|
8a4ab9bdb9 | ||
|
|
410cdb193d | ||
|
|
a278337b4c | ||
|
|
b1308de0b9 | ||
|
|
9705078970 | ||
|
|
b1ca98e945 | ||
|
|
35054eaca9 | ||
|
|
2747c743c9 | ||
|
|
031d08a8d4 | ||
|
|
7cc6c4a9c8 | ||
|
|
ff27098514 | ||
|
|
545cd65066 | ||
|
|
84bfc6e7a9 | ||
|
|
2f71c4c5e4 | ||
|
|
1448f7aeb6 | ||
|
|
79d66a5cfc | ||
|
|
2ec19ac04c | ||
|
|
f62a6d4512 | ||
|
|
4fbcee3953 | ||
|
|
12bb283b97 | ||
|
|
13d776c7cb | ||
|
|
f45b48c6e1 | ||
|
|
ff14c94a90 | ||
|
|
0a0ef35b74 | ||
|
|
e3dc10c085 | ||
|
|
46113bf3d4 | ||
|
|
0f3ef61f7d | ||
|
|
86aae08257 | ||
|
|
06bf2c39a1 | ||
|
|
66a23bc2a2 | ||
|
|
51e86f1e43 | ||
|
|
78c70b3f5b | ||
|
|
8f52ffe061 | ||
|
|
e95b91ab84 | ||
|
|
f4dbd66496 | ||
|
|
5895df0499 | ||
|
|
6d0d88f3be | ||
|
|
7e71428cc3 | ||
|
|
2e215440f7 | ||
|
|
c04fa56c6c | ||
|
|
6c70b5e38f | ||
|
|
ad9160a4a3 | ||
|
|
c747d3928e | ||
|
|
68e8d67054 | ||
|
|
9e8fc76d28 | ||
|
|
d8970305de | ||
|
|
824675a658 | ||
|
|
090ad613cc | ||
|
|
ea35e09c96 | ||
|
|
d8b9a1a560 | ||
|
|
c259f58e63 | ||
|
|
9d4c2a1147 | ||
|
|
f13c3fe38b | ||
|
|
60409df145 | ||
|
|
1d7321cd6f | ||
|
|
eb68ccbf6b | ||
|
|
56ea62af71 | ||
|
|
b4808f2909 | ||
|
|
1a2e9eaa84 | ||
|
|
7e3cca5780 | ||
|
|
0b9371527e | ||
|
|
7aee2d805d | ||
|
|
06f03f399a |
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.7.0-dev.18",
|
||||
"version": "0.7.1-dev.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.7.0-dev.18",
|
||||
"version": "0.7.1-dev.2",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -34,7 +34,8 @@
|
||||
"model-server-watch": "nodemon --watch lib lib/servers/servers/model/server.js",
|
||||
"volume-server-test": "node lib/servers/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
|
||||
"plugin-state": "node lib/servers/servers/plugin-state/index.js",
|
||||
"preversion": "npm run test && npm run build",
|
||||
"preversion": "npm run test",
|
||||
"version": "npm run build",
|
||||
"postversion": "git push && git push --tags"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -45,17 +45,24 @@
|
||||
return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || '');
|
||||
}
|
||||
|
||||
var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
|
||||
if (debugMode) molstar.setDebugMode(debugMode);
|
||||
|
||||
var hideControls = getParam('hide-controls', '[^&]+').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,
|
||||
pdbProvider: pdbProvider || 'pdbe',
|
||||
emdbProvider: emdbProvider || 'pdbe',
|
||||
});
|
||||
|
||||
var snapshotId = getParam('snapshot-id', '[^&]+').trim();
|
||||
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);
|
||||
|
||||
var snapshotUrl = getParam('snapshot-url', '[^&]+').trim();
|
||||
var snapshotUrlType = getParam('snapshot-url-type', '[^&]+').toLowerCase().trim();
|
||||
var snapshotUrlType = getParam('snapshot-url-type', '[^&]+').toLowerCase().trim() || 'molj';
|
||||
if (snapshotUrl && snapshotUrlType) viewer.loadSnapshotFromUrl(snapshotUrl, snapshotUrlType);
|
||||
|
||||
var structureUrl = getParam('structure-url', '[^&]+').trim();
|
||||
|
||||
@@ -23,8 +23,12 @@ import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setProductionMode, setDebugMode } from '../../mol-util/debug';
|
||||
|
||||
const Extensions = {
|
||||
'cellpack': PluginSpec.Behavior(CellPack),
|
||||
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
@@ -83,18 +87,18 @@ export class Viewer {
|
||||
...DefaultPluginSpec.components,
|
||||
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
|
||||
},
|
||||
config: DefaultPluginSpec.config
|
||||
config: [
|
||||
[PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],
|
||||
[PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode],
|
||||
[PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation],
|
||||
[PluginConfig.State.DefaultServer, o.pluginStateServer],
|
||||
[PluginConfig.State.CurrentServer, o.pluginStateServer],
|
||||
[PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
|
||||
[PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
|
||||
[PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider]
|
||||
]
|
||||
};
|
||||
|
||||
spec.config?.set(PluginConfig.Viewport.ShowExpand, o.viewportShowExpand);
|
||||
spec.config?.set(PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode);
|
||||
spec.config?.set(PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation);
|
||||
spec.config?.set(PluginConfig.State.DefaultServer, o.pluginStateServer);
|
||||
spec.config?.set(PluginConfig.State.CurrentServer, o.pluginStateServer);
|
||||
spec.config?.set(PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer);
|
||||
spec.config?.set(PluginConfig.Download.DefaultPdbProvider, o.pdbProvider);
|
||||
spec.config?.set(PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider);
|
||||
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) throw new Error(`Could not get element with id '${elementId}'`);
|
||||
this.plugin = createPlugin(element, spec);
|
||||
@@ -150,7 +154,10 @@ export class Viewer {
|
||||
source: {
|
||||
name: 'pdb-dev' as const,
|
||||
params: {
|
||||
id: pdbDev,
|
||||
provider: {
|
||||
id: pdbDev,
|
||||
encoding: 'bcif',
|
||||
},
|
||||
options: params.source.params.options,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { QueryContext, StructureSelection } from '../../mol-model/structure';
|
||||
import { superposeStructures } from '../../mol-model/structure/structure/util/superposition';
|
||||
import { superpose } from '../../mol-model/structure/structure/util/superposition';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
@@ -79,7 +79,7 @@ export function dynamicSuperpositionTest(plugin: PluginContext, src: string[], c
|
||||
const xs = plugin.managers.structure.hierarchy.current.structures;
|
||||
const selections = xs.map(s => StructureSelection.toLociWithCurrentUnits(query(new QueryContext(s.cell.obj!.data))));
|
||||
|
||||
const transforms = superposeStructures(selections);
|
||||
const transforms = superpose(selections);
|
||||
|
||||
await siteVisual(plugin, xs[0].cell, pivot, rest);
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
|
||||
@@ -249,9 +249,7 @@ class MolStarProteopediaWrapper {
|
||||
toggleSpin() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
const trackball = this.plugin.canvas3d.props.trackball;
|
||||
const spinning = trackball.spin;
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
if (!spinning) PluginCommands.Camera.Reset(this.plugin, { });
|
||||
}
|
||||
|
||||
viewport = {
|
||||
|
||||
@@ -4,29 +4,28 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ThemeDataContext } from '../../mol-theme/theme';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { getPalette } from '../../mol-util/color/palette';
|
||||
import { ColorTheme, LocationColor } from '../../mol-theme/color';
|
||||
import { ScaleLegend, TableLegend } from '../../mol-util/legend';
|
||||
import { StructureElement, Bond } from '../../mol-model/structure';
|
||||
import { Location } from '../../mol-model/location';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
import { distinctColors } from '../../mol-util/color/distinct';
|
||||
import { Hcl } from '../../mol-util/color/spaces/hcl';
|
||||
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { getPalette } from '../../../mol-util/color/palette';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
|
||||
import { StructureElement, Bond } from '../../../mol-model/structure';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { CellPackInfoProvider } from '../property';
|
||||
import { distinctColors } from '../../../mol-util/color/distinct';
|
||||
import { Hcl } from '../../../mol-util/color/spaces/hcl';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
const Description = 'Gives every model in a CellPack packing a unique color similar to other models in the packing.';
|
||||
const Description = 'Gives every model in a CellPack packing a unique generated color similar to other models in the packing.';
|
||||
|
||||
export const CellPackColorThemeParams = {};
|
||||
export type CellPackColorThemeParams = typeof CellPackColorThemeParams
|
||||
export function getCellPackColorThemeParams(ctx: ThemeDataContext) {
|
||||
return CellPackColorThemeParams; // TODO return copy
|
||||
export const CellPackGenerateColorThemeParams = {};
|
||||
export type CellPackGenerateColorThemeParams = typeof CellPackGenerateColorThemeParams
|
||||
export function getCellPackGenerateColorThemeParams(ctx: ThemeDataContext) {
|
||||
return CellPackGenerateColorThemeParams; // TODO return copy
|
||||
}
|
||||
|
||||
export function CellPackColorTheme(ctx: ThemeDataContext, props: PD.Values<CellPackColorThemeParams>): ColorTheme<CellPackColorThemeParams> {
|
||||
export function CellPackGenerateColorTheme(ctx: ThemeDataContext, props: PD.Values<CellPackGenerateColorThemeParams>): ColorTheme<CellPackGenerateColorThemeParams> {
|
||||
let color: LocationColor;
|
||||
let legend: ScaleLegend | TableLegend | undefined;
|
||||
|
||||
@@ -34,7 +33,8 @@ export function CellPackColorTheme(ctx: ThemeDataContext, props: PD.Values<CellP
|
||||
|
||||
if (ctx.structure && info) {
|
||||
const colors = distinctColors(info.packingsCount);
|
||||
const hcl = Hcl.fromColor(Hcl(), colors[info.packingIndex]);
|
||||
let hcl = Hcl.fromColor(Hcl(), colors[info.packingIndex]);
|
||||
|
||||
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number];
|
||||
|
||||
const { models } = ctx.structure.root;
|
||||
@@ -70,7 +70,7 @@ export function CellPackColorTheme(ctx: ThemeDataContext, props: PD.Values<CellP
|
||||
}
|
||||
|
||||
return {
|
||||
factory: CellPackColorTheme,
|
||||
factory: CellPackGenerateColorTheme,
|
||||
granularity: 'instance',
|
||||
color,
|
||||
props,
|
||||
@@ -79,13 +79,13 @@ export function CellPackColorTheme(ctx: ThemeDataContext, props: PD.Values<CellP
|
||||
};
|
||||
}
|
||||
|
||||
export const CellPackColorThemeProvider: ColorTheme.Provider<CellPackColorThemeParams, 'cellpack'> = {
|
||||
name: 'cellpack',
|
||||
label: 'CellPack',
|
||||
export const CellPackGenerateColorThemeProvider: ColorTheme.Provider<CellPackGenerateColorThemeParams, 'cellpack-generate'> = {
|
||||
name: 'cellpack-generate',
|
||||
label: 'CellPack Generate',
|
||||
category: ColorTheme.Category.Chain,
|
||||
factory: CellPackColorTheme,
|
||||
getParams: getCellPackColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(CellPackColorThemeParams),
|
||||
factory: CellPackGenerateColorTheme,
|
||||
getParams: getCellPackGenerateColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(CellPackGenerateColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => {
|
||||
return (
|
||||
!!ctx.structure && ctx.structure.elementCount > 0 &&
|
||||
@@ -93,4 +93,5 @@ export const CellPackColorThemeProvider: ColorTheme.Provider<CellPackColorThemeP
|
||||
!!CellPackInfoProvider.get(ctx.structure).value
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
72
src/extensions/cellpack/color/provided.ts
Normal file
72
src/extensions/cellpack/color/provided.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { CellPackInfoProvider } from '../property';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
const Description = 'Gives every model in a CellPack the color provied in the packing data.';
|
||||
|
||||
export const CellPackProvidedColorThemeParams = {};
|
||||
export type CellPackProvidedColorThemeParams = typeof CellPackProvidedColorThemeParams
|
||||
export function getCellPackProvidedColorThemeParams(ctx: ThemeDataContext) {
|
||||
return CellPackProvidedColorThemeParams; // TODO return copy
|
||||
}
|
||||
|
||||
export function CellPackProvidedColorTheme(ctx: ThemeDataContext, props: PD.Values<CellPackProvidedColorThemeParams>): ColorTheme<CellPackProvidedColorThemeParams> {
|
||||
let color: LocationColor;
|
||||
let legend: ScaleLegend | TableLegend | undefined;
|
||||
|
||||
const info = ctx.structure && CellPackInfoProvider.get(ctx.structure).value;
|
||||
|
||||
if (ctx.structure && info?.colors) {
|
||||
const { models } = ctx.structure.root;
|
||||
const modelColor = new Map<number, Color>();
|
||||
for (let i = 0, il = models.length; i < il; ++i) {
|
||||
const idx = models[i].trajectoryInfo.index;
|
||||
modelColor.set(models[i].trajectoryInfo.index, info.colors[idx]);
|
||||
}
|
||||
|
||||
color = (location: Location): Color => {
|
||||
return StructureElement.Location.is(location)
|
||||
? modelColor.get(location.unit.model.trajectoryInfo.index)!
|
||||
: DefaultColor;
|
||||
};
|
||||
} else {
|
||||
color = () => DefaultColor;
|
||||
}
|
||||
|
||||
return {
|
||||
factory: CellPackProvidedColorTheme,
|
||||
granularity: 'instance',
|
||||
color,
|
||||
props,
|
||||
description: Description,
|
||||
legend
|
||||
};
|
||||
}
|
||||
|
||||
export const CellPackProvidedColorThemeProvider: ColorTheme.Provider<CellPackProvidedColorThemeParams, 'cellpack-provided'> = {
|
||||
name: 'cellpack-provided',
|
||||
label: 'CellPack Provided',
|
||||
category: ColorTheme.Category.Chain,
|
||||
factory: CellPackProvidedColorTheme,
|
||||
getParams: getCellPackProvidedColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(CellPackProvidedColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => {
|
||||
return (
|
||||
!!ctx.structure && ctx.structure.elementCount > 0 &&
|
||||
ctx.structure.models[0].trajectoryInfo.size > 1 &&
|
||||
!!CellPackInfoProvider.get(ctx.structure).value?.colors
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -191,8 +191,14 @@ function GetMiniFrame(points: Vec3[], normals: Vec3[]) {
|
||||
|
||||
const rpTmpVec1 = Vec3();
|
||||
|
||||
export function getMatFromResamplePoints(points: NumberArray, segmentLength: number) {
|
||||
const new_points = ResampleControlPoints(points, segmentLength);
|
||||
export function getMatFromResamplePoints(points: NumberArray, segmentLength: number, resample: boolean) {
|
||||
let new_points: Vec3[] = [];
|
||||
if (resample) new_points = ResampleControlPoints(points, segmentLength);
|
||||
else {
|
||||
for (let idx = 0; idx < points.length / 3; ++idx){
|
||||
new_points.push(Vec3.fromArray(Vec3.zero(), points, idx * 3));
|
||||
}
|
||||
}
|
||||
const npoints = new_points.length;
|
||||
const new_normal = GetSmoothNormals(new_points);
|
||||
const frames = GetMiniFrame(new_points, new_normal);
|
||||
|
||||
@@ -59,6 +59,7 @@ export interface Ingredient {
|
||||
radii?: [Radii];
|
||||
/** Number of `curveX` properties in the object where `X` is a 0-indexed number */
|
||||
nbCurve?: number;
|
||||
uLength?: number;
|
||||
/** Curve properties are Vec3[] but that is not expressable in TypeScript */
|
||||
[curveX: string]: unknown;
|
||||
/** the orientation in the membrane */
|
||||
@@ -66,6 +67,8 @@ export interface Ingredient {
|
||||
/** offset along membrane */
|
||||
offset?: Vec3;
|
||||
ingtype?: string;
|
||||
color?: Vec3;
|
||||
confidence?: number;
|
||||
}
|
||||
|
||||
export interface IngredientSource {
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
*/
|
||||
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior';
|
||||
import { CellPackColorThemeProvider } from './color';
|
||||
import { LoadCellPackModel } from './model';
|
||||
|
||||
import { CellPackGenerateColorThemeProvider } from './color/generate';
|
||||
import { CellPackProvidedColorThemeProvider } from './color/provided';
|
||||
|
||||
export const CellPack = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
|
||||
name: 'cellpack',
|
||||
@@ -19,12 +19,14 @@ export const CellPack = PluginBehavior.create<{ autoAttach: boolean, showTooltip
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
|
||||
register(): void {
|
||||
this.ctx.state.data.actions.add(LoadCellPackModel);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(CellPackColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(CellPackGenerateColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(CellPackProvidedColorThemeProvider);
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.ctx.state.data.actions.remove(LoadCellPackModel);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(CellPackColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(CellPackGenerateColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(CellPackProvidedColorThemeProvider);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -17,7 +17,7 @@ import { Mat4, Vec3, Quat } from '../../mol-math/linear-algebra';
|
||||
import { SymmetryOperator } from '../../mol-math/geometry';
|
||||
import { Task, RuntimeContext } from '../../mol-task';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl } from './state';
|
||||
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies } from './state';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { getMatFromResamplePoints } from './curve';
|
||||
import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
@@ -27,6 +27,7 @@ import { Column } from '../../mol-data/db';
|
||||
import { createModels } from '../../mol-model-formats/structure/basic/parser';
|
||||
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { readFromFile } from '../../mol-util/data-source';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
|
||||
@@ -108,7 +109,7 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi
|
||||
}
|
||||
let query;
|
||||
if (source.selection){
|
||||
const asymIds: string[] = source.selection.replace(' :', '').split(' or');
|
||||
const asymIds: string[] = source.selection.replace(' ', '').replace(':', '').split('or');
|
||||
query = MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
@@ -155,11 +156,15 @@ function getResultTransforms(results: Ingredient['results'], legacy: boolean) {
|
||||
function getCurveTransforms(ingredient: Ingredient) {
|
||||
const n = ingredient.nbCurve || 0;
|
||||
const instances: Mat4[] = [];
|
||||
const segmentLength = ingredient.radii
|
||||
? (ingredient.radii[0].radii
|
||||
let segmentLength = 3.4;
|
||||
if (ingredient.uLength){
|
||||
segmentLength = ingredient.uLength;
|
||||
} else if (ingredient.radii){
|
||||
segmentLength = ingredient.radii[0].radii
|
||||
? ingredient.radii[0].radii[0] * 2.0
|
||||
: 3.4)
|
||||
: 3.4;
|
||||
: 3.4;
|
||||
}
|
||||
let resampling: boolean = false;
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const cname = `curve${i}`;
|
||||
if (!(cname in ingredient)) {
|
||||
@@ -171,12 +176,17 @@ function getCurveTransforms(ingredient: Ingredient) {
|
||||
// TODO handle curve with 2 or less points
|
||||
continue;
|
||||
}
|
||||
// test for resampling
|
||||
let distance: number = Vec3.distance(_points[0], _points[1]);
|
||||
if (distance >= segmentLength + 2.0) {
|
||||
console.info(distance);
|
||||
resampling = true;
|
||||
}
|
||||
const points = new Float32Array(_points.length * 3);
|
||||
for (let i = 0, il = _points.length; i < il; ++i) Vec3.toArray(_points[i], points, i * 3);
|
||||
const newInstances = getMatFromResamplePoints(points, segmentLength);
|
||||
const newInstances = getMatFromResamplePoints(points, segmentLength, resampling);
|
||||
instances.push(...newInstances);
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
@@ -290,7 +300,6 @@ function getCifCurve(name: string, transforms: Mat4[], model: Model) {
|
||||
|
||||
async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredient, transforms: Mat4[], model: Model) {
|
||||
const cif = getCifCurve(name, transforms, model);
|
||||
|
||||
const curveModelTask = Task.create('Curve Model', async ctx => {
|
||||
const format = MmcifFormat.fromFrame(cif);
|
||||
const models = await createModels(format.data.db, format, ctx);
|
||||
@@ -308,10 +317,10 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
|
||||
const file = ingredientFiles[source.pdb];
|
||||
if (!file) {
|
||||
// TODO can these be added to the library?
|
||||
if (name === 'HIV1_CAhex_0_1_0') return;
|
||||
if (name === 'HIV1_CAhexCyclophilA_0_1_0') return;
|
||||
if (name === 'iLDL') return;
|
||||
if (name === 'peptides') return;
|
||||
if (name === 'HIV1_CAhex_0_1_0') return; // 1VU4CtoH_hex.pdb
|
||||
if (name === 'HIV1_CAhexCyclophilA_0_1_0') return; // 1AK4fitTo1VU4hex.pdb
|
||||
if (name === 'iLDL') return; // EMD-5239
|
||||
if (name === 'peptides') return; // peptide.pdb
|
||||
if (name === 'lypoglycane') return;
|
||||
}
|
||||
|
||||
@@ -369,12 +378,20 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
|
||||
const assets: Asset.Wrapper[] = [];
|
||||
const trajCache = new TrajectoryCache();
|
||||
const structures: Structure[] = [];
|
||||
const colors: Color[] = [];
|
||||
let skipColors: boolean = false;
|
||||
for (const iName in ingredients) {
|
||||
if (ctx.shouldUpdate) await ctx.update(iName);
|
||||
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache);
|
||||
if (ingredientStructure) {
|
||||
structures.push(ingredientStructure.structure);
|
||||
assets.push(...ingredientStructure.assets);
|
||||
const c = ingredients[iName].color;
|
||||
if (c){
|
||||
colors.push(Color.fromNormalizedRgb(c[0], c[1], c[2]));
|
||||
} else {
|
||||
skipColors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,7 +416,7 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
|
||||
trajectoryInfo.size = il;
|
||||
trajectoryInfo.index = i;
|
||||
}
|
||||
return { structure, assets };
|
||||
return { structure, assets, colors: skipColors ? undefined : colors };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -435,11 +452,25 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!file){
|
||||
// check for cif directly
|
||||
const cifileName = `${name}.cif`;
|
||||
for (const f of params.ingredients.files) {
|
||||
if (cifileName === f.name) {
|
||||
file = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let b = state.build().toRoot();
|
||||
if (file) {
|
||||
b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } });
|
||||
if (file.name.endsWith('.cif')) {
|
||||
b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: false, label: file.name }, { state: { isGhost: true } });
|
||||
} else if (file.name.endsWith('.bcif')) {
|
||||
b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } });
|
||||
}
|
||||
} else {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
|
||||
@@ -448,12 +479,13 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
|
||||
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.StructureFromModel)
|
||||
.apply(StructureFromAssemblies, undefined, { state: { isGhost: true } })
|
||||
.commit({ revertOnError: true });
|
||||
|
||||
const membraneParams = {
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
|
||||
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
|
||||
}
|
||||
|
||||
@@ -510,7 +542,7 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackPackingPreset.apply(packing, packingParams, plugin);
|
||||
if ( packings[i].location === 'surface' ){
|
||||
if ( packings[i].location === 'surface' && params.membrane){
|
||||
await loadMembrane(plugin, packings[i].name, state, params);
|
||||
}
|
||||
}
|
||||
@@ -518,18 +550,21 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
|
||||
|
||||
const LoadCellPackModelParams = {
|
||||
source: PD.MappedStatic('id', {
|
||||
'id': PD.Select('influenza_model1.json', [
|
||||
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
||||
['hiv_lipids.bcif', 'hiv_lipids'],
|
||||
['influenza_model1.json', 'influenza_model1'],
|
||||
['ExosomeModel.json', 'ExosomeModel'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
'id': PD.Select('InfluenzaModel2.json', [
|
||||
['blood_hiv_immature_inside.json', 'Blood HIV immature'],
|
||||
['HIV_immature_model.json', 'HIV immature'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'Blood HIV'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV'],
|
||||
['influenza_model1.json', 'Influenza envelope'],
|
||||
['InfluenzaModel2.json', 'Influenza Complete'],
|
||||
['ExosomeModel.json', 'Exosome Model'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma simple'],
|
||||
['MycoplasmaModel.json', 'Mycoplasma WholeCell model'],
|
||||
] as const, { description: 'Download the model definition with `id` from the server at `baseUrl.`' }),
|
||||
'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.' }),
|
||||
}, { options: [['id', 'Id'], ['file', 'File']] }),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
membrane: PD.Boolean(true),
|
||||
ingredients : PD.Group({
|
||||
files: PD.FileList({ accept: '.cif,.bcif,.pdb' })
|
||||
}, { isExpanded: true }),
|
||||
@@ -545,9 +580,5 @@ export const LoadCellPackModel = StateAction.build({
|
||||
params: LoadCellPackModelParams,
|
||||
from: PSO.Root
|
||||
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
|
||||
if (params.source.name === 'id' && params.source.params === 'hiv_lipids.bcif') {
|
||||
await loadMembrane(ctx, 'hiv_lipids', state, params);
|
||||
} else {
|
||||
await loadPackings(ctx, taskCtx, state, params);
|
||||
}
|
||||
await loadPackings(ctx, taskCtx, state, params);
|
||||
}));
|
||||
@@ -8,7 +8,9 @@ import { StateObjectRef } from '../../mol-state';
|
||||
import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { CellPackColorThemeProvider } from './color';
|
||||
import { CellPackGenerateColorThemeProvider } from './color/generate';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
import { CellPackProvidedColorThemeProvider } from './color/provided';
|
||||
|
||||
export const CellpackPackingPresetParams = {
|
||||
traceOnly: PD.Boolean(true),
|
||||
@@ -40,8 +42,10 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
|
||||
Object.assign(reprProps, { sizeFactor: 2 });
|
||||
}
|
||||
|
||||
const info = structureCell.obj?.data && CellPackInfoProvider.get(structureCell.obj?.data).value;
|
||||
const color = info?.colors ? CellPackProvidedColorThemeProvider.name : CellPackGenerateColorThemeProvider.name;
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
|
||||
const color = CellPackColorThemeProvider.name;
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation<any>(update, components.polymer, { type: params.representation, typeParams: { ...typeParams, ...reprProps }, color }, { tag: 'polymer' })
|
||||
};
|
||||
@@ -85,6 +89,7 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({
|
||||
};
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
@@ -8,15 +8,17 @@ import { CustomStructureProperty } from '../../mol-model-props/common/custom-str
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
|
||||
export type CellPackInfoValue = {
|
||||
packingsCount: number
|
||||
packingIndex: number
|
||||
colors?: Color[]
|
||||
}
|
||||
|
||||
const CellPackInfoParams = {
|
||||
info: PD.Value<CellPackInfoValue>({ packingsCount: 1, packingIndex: 0 }, { isHidden: true })
|
||||
info: PD.Value<CellPackInfoValue>({ packingsCount: 1, packingIndex: 0, colors: undefined }, { isHidden: true })
|
||||
};
|
||||
type CellPackInfoParams = PD.Values<typeof CellPackInfoParams>
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import { IngredientFiles } from './util';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
import { Structure, StructureSymmetry, Unit } from '../../mol-model/structure';
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
|
||||
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/';
|
||||
|
||||
@@ -71,10 +73,9 @@ const StructureFromCellpack = PluginStateTransform.BuiltIn({
|
||||
ingredientFiles[file.name] = file;
|
||||
}
|
||||
}
|
||||
const { structure, assets } = await createStructureFromCellPack(plugin, packing, params.baseUrl, ingredientFiles).runInContext(ctx);
|
||||
|
||||
const { structure, assets, colors } = await createStructureFromCellPack(plugin, packing, params.baseUrl, ingredientFiles).runInContext(ctx);
|
||||
await CellPackInfoProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, structure, {
|
||||
info: { packingsCount: a.data.packings.length, packingIndex: params.packing }
|
||||
info: { packingsCount: a.data.packings.length, packingIndex: params.packing, colors }
|
||||
});
|
||||
|
||||
(cache as any).assets = assets;
|
||||
@@ -94,4 +95,110 @@ const StructureFromCellpack = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export { StructureFromAssemblies };
|
||||
type StructureFromAssemblies = typeof StructureFromAssemblies
|
||||
const StructureFromAssemblies = PluginStateTransform.BuiltIn({
|
||||
name: 'Structure from all assemblies',
|
||||
display: { name: 'Structure from all assemblies' },
|
||||
from: PSO.Molecule.Model,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: {
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ newParams }) {
|
||||
return true;
|
||||
},
|
||||
apply({ a, params }) {
|
||||
return Task.create('Build Structure', async ctx => {
|
||||
// TODO: optimze
|
||||
// TODO: think of ways how to fast-track changes to this for animations
|
||||
const model = a.data;
|
||||
let initial_structure = Structure.ofModel(model);
|
||||
const structures: Structure[] = [];
|
||||
let structure: Structure = initial_structure;
|
||||
// the list of asambly *?
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
if (symmetry && symmetry.assemblies.length !== 0){
|
||||
for (const a of symmetry.assemblies) {
|
||||
const s = await StructureSymmetry.buildAssembly(initial_structure, a.id).runInContext(ctx);
|
||||
structures.push(s);
|
||||
}
|
||||
const builder = Structure.Builder({ label: name });
|
||||
let offsetInvariantId = 0;
|
||||
for (const s of structures) {
|
||||
let maxInvariantId = 0;
|
||||
for (const u of s.units) {
|
||||
const invariantId = u.invariantId + offsetInvariantId;
|
||||
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
|
||||
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId);
|
||||
}
|
||||
offsetInvariantId += maxInvariantId + 1;
|
||||
}
|
||||
structure = builder.getStructure();
|
||||
for( let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
const { trajectoryInfo } = structure.models[i];
|
||||
trajectoryInfo.size = il;
|
||||
trajectoryInfo.index = i;
|
||||
}
|
||||
}
|
||||
return new PSO.Molecule.Structure(structure, { label: a.label, description: `${a.description}` });
|
||||
});
|
||||
},
|
||||
dispose({ b }) {
|
||||
b?.data.customPropertyDescriptors.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
// export { GetAllAssamblyinOneStructure };
|
||||
// type GetAllAssamblyinOneStructure = typeof GetAllAssamblyinOneStructure
|
||||
// const GetAllAssamblyinOneStructure = PluginStateTransform.BuiltIn({
|
||||
// name: 'get assambly from structure',
|
||||
// display: { name: 'get assambly from structure' },
|
||||
// isDecorator: true,
|
||||
// from: PSO.Molecule.Structure,
|
||||
// to: PSO.Molecule.Structure,
|
||||
// params(a) {
|
||||
// return { };
|
||||
// }
|
||||
// })({
|
||||
// apply({ a, params }) {
|
||||
// return Task.create('Build Structure Assemblies', async ctx => {
|
||||
// // TODO: optimze
|
||||
// // TODO: think of ways how to fast-track changes to this for animations
|
||||
// const initial_structure = a.data;
|
||||
// const structures: Structure[] = [];
|
||||
// let structure: Structure = initial_structure;
|
||||
// // the list of asambly *?
|
||||
// const symmetry = ModelSymmetry.Provider.get(initial_structure.model);
|
||||
// if (symmetry){
|
||||
// if (symmetry.assemblies.length !== 0) {
|
||||
// for (const a of symmetry.assemblies) {
|
||||
// const s = await StructureSymmetry.buildAssembly(initial_structure, a.id!).runInContext(ctx);
|
||||
// structures.push(s);
|
||||
// }
|
||||
// const builder = Structure.Builder({ label: name });
|
||||
// let offsetInvariantId = 0;
|
||||
// for (const s of structures) {
|
||||
// let maxInvariantId = 0;
|
||||
// for (const u of s.units) {
|
||||
// const invariantId = u.invariantId + offsetInvariantId;
|
||||
// if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
|
||||
// builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId);
|
||||
// }
|
||||
// offsetInvariantId += maxInvariantId + 1;
|
||||
// }
|
||||
// structure = builder.getStructure();
|
||||
// for( let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
// const { trajectoryInfo } = structure.models[i];
|
||||
// trajectoryInfo.size = il;
|
||||
// trajectoryInfo.index = i;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return new PSO.Molecule.Structure(structure, { label: a.label, description: `${a.description}` });
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
|
||||
@@ -186,12 +186,22 @@ const getSymbolCage = memoize1((symbol: string): Cage | undefined => {
|
||||
if (symbol.startsWith('D') || symbol.startsWith('C')) {
|
||||
// z axis is prism axis, x/y axes cut through edge midpoints
|
||||
const fold = parseInt(symbol.substr(1));
|
||||
let cage: Cage;
|
||||
if (fold === 2) {
|
||||
return PrismCage(polygon(4, false));
|
||||
cage = PrismCage(polygon(4, false));
|
||||
} else if (fold === 3) {
|
||||
return WedgeCage();
|
||||
cage = WedgeCage();
|
||||
} else if (fold > 3) {
|
||||
return PrismCage(polygon(fold, false));
|
||||
cage = PrismCage(polygon(fold, false));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (fold % 2 === 0) {
|
||||
return cage;
|
||||
} else {
|
||||
const m = Mat4.identity();
|
||||
Mat4.rotate(m, m, 1 / fold * Math.PI / 2, Vec3.unitZ);
|
||||
return transformCage(cloneCage(cage), m);
|
||||
}
|
||||
} else if (symbol === 'O') {
|
||||
// x/y/z axes cut through order 4 vertices
|
||||
@@ -237,7 +247,7 @@ function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.Rota
|
||||
pair = axes.filter(a => a.order === 2);
|
||||
} else if (fold >= 3) {
|
||||
const aN = axes.filter(a => a.order === fold)[0];
|
||||
const a2 = axes.filter(a => a.order === 2)[0];
|
||||
const a2 = axes.filter(a => a.order === 2)[1];
|
||||
pair = [aN, a2];
|
||||
}
|
||||
} else if (symbol === 'O') {
|
||||
@@ -267,7 +277,19 @@ function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.Rota
|
||||
Vec3.sub(dir, eye, target);
|
||||
if (Vec3.dot(dir, up) < 0) Vec3.negate(up, up);
|
||||
Mat4.targetTo(t, eye, target, up);
|
||||
Mat4.scaleUniformly(t, t, size * getSymbolScale(symbol));
|
||||
|
||||
if (symbol.startsWith('D')) {
|
||||
const { sphere } = structure.lookup3d.boundary;
|
||||
let sizeXY = (sphere.radius * 2) * 0.8; // fallback for missing extrema
|
||||
if (Sphere3D.hasExtrema(sphere)) {
|
||||
const n = Mat3.directionTransform(Mat3(), t);
|
||||
const dirs = unitCircleDirections.map(d => Vec3.transformMat3(Vec3(), d, n));
|
||||
sizeXY = getMaxProjectedDistance(sphere.extrema, dirs, sphere.center) * 1.6;
|
||||
}
|
||||
Mat4.scale(t, t, Vec3.create(sizeXY, sizeXY, Vec3.distance(aA.start, aA.end) * 0.9));
|
||||
} else {
|
||||
Mat4.scaleUniformly(t, t, size * getSymbolScale(symbol));
|
||||
}
|
||||
} else {
|
||||
if (Vec3.dot(Vec3.unitY, Vec3.sub(tmpV, aA.end, aA.start)) === 0) {
|
||||
Vec3.copy(up, Vec3.unitY);
|
||||
@@ -283,7 +305,6 @@ function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.Rota
|
||||
const dirs = unitCircleDirections.map(d => Vec3.transformMat3(Vec3(), d, n));
|
||||
sizeXY = getMaxProjectedDistance(sphere.extrema, dirs, sphere.center);
|
||||
}
|
||||
|
||||
Mat4.scale(t, t, Vec3.create(sizeXY, sizeXY, size * 0.9));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,12 +222,12 @@ function densityFitLabel(loci: Loci): string | undefined {
|
||||
if (rsrzSeen.size) {
|
||||
const rsrzCount = `<small>(${rsrzSeen.size} ${rsrzSeen.size > 1 ? 'Residues avg.' : 'Residue'})</small>`;
|
||||
const rsrzAvg = rsrzSum / rsrzSeen.size;
|
||||
summary.push(`Real Space R ${rsrzCount}: ${rsrzAvg.toFixed(2)}`);
|
||||
summary.push(`Real-Space R Z-score ${rsrzCount}: ${rsrzAvg.toFixed(2)}`);
|
||||
}
|
||||
if (rsccSeen.size) {
|
||||
const rsccCount = `<small>(${rsccSeen.size} ${rsccSeen.size > 1 ? 'Residues avg.' : 'Residue'})</small>`;
|
||||
const rsccAvg = rsccSum / rsccSeen.size;
|
||||
summary.push(`Real Space Correlation Coefficient ${rsccCount}: ${rsccAvg.toFixed(2)}`);
|
||||
summary.push(`Real-Space Correlation Coefficient ${rsccCount}: ${rsccAvg.toFixed(2)}`);
|
||||
}
|
||||
|
||||
if (summary.length) {
|
||||
|
||||
@@ -36,6 +36,7 @@ import { Sphere3D } from '../mol-math/geometry';
|
||||
import { isDebugMode } from '../mol-util/debug';
|
||||
import { CameraHelperParams } from './helper/camera-helper';
|
||||
import { produce } from 'immer';
|
||||
import { HandleHelper, HandleHelperParams } from './helper/handle-helper';
|
||||
|
||||
export const Canvas3DParams = {
|
||||
camera: PD.Group({
|
||||
@@ -60,7 +61,8 @@ export const Canvas3DParams = {
|
||||
postprocessing: PD.Group(PostprocessingParams),
|
||||
renderer: PD.Group(RendererParams),
|
||||
trackball: PD.Group(TrackballControlsParams),
|
||||
debug: PD.Group(DebugHelperParams)
|
||||
debug: PD.Group(DebugHelperParams),
|
||||
handle: PD.Group(HandleHelperParams),
|
||||
};
|
||||
export const DefaultCanvas3DParams = PD.getDefaultValues(Canvas3DParams);
|
||||
export type Canvas3DProps = PD.Values<typeof Canvas3DParams>
|
||||
@@ -188,12 +190,13 @@ namespace Canvas3D {
|
||||
const controls = TrackballControls.create(input, camera, p.trackball);
|
||||
const renderer = Renderer.create(webgl, p.renderer);
|
||||
const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
|
||||
const handleHelper = new HandleHelper(webgl, p.handle);
|
||||
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
|
||||
|
||||
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, {
|
||||
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, handleHelper, {
|
||||
cameraHelper: p.camera.helper
|
||||
});
|
||||
const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5);
|
||||
const pickPass = new PickPass(webgl, renderer, scene, camera, handleHelper, 0.5);
|
||||
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing);
|
||||
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample);
|
||||
|
||||
@@ -205,6 +208,7 @@ namespace Canvas3D {
|
||||
function getLoci(pickingId: PickingId) {
|
||||
let loci: Loci = EmptyLoci;
|
||||
let repr: Representation.Any = Representation.Empty;
|
||||
loci = handleHelper.getLoci(pickingId);
|
||||
reprRenderObjects.forEach((_, _repr) => {
|
||||
const _loci = _repr.getLoci(pickingId);
|
||||
if (!isEmptyLoci(_loci)) {
|
||||
@@ -224,10 +228,12 @@ namespace Canvas3D {
|
||||
if (repr) {
|
||||
changed = repr.mark(loci, action);
|
||||
} else {
|
||||
changed = handleHelper.mark(loci, action);
|
||||
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
|
||||
}
|
||||
if (changed) {
|
||||
scene.update(void 0, true);
|
||||
handleHelper.scene.update(void 0, true);
|
||||
const prevPickDirty = pickPass.pickDirty;
|
||||
draw(true);
|
||||
pickPass.pickDirty = prevPickDirty; // marking does not change picking buffers
|
||||
@@ -356,10 +362,19 @@ namespace Canvas3D {
|
||||
|
||||
camera.setState({ radiusMax: scene.boundingSphere.radius }, 0);
|
||||
reprCount.next(reprRenderObjects.size);
|
||||
if (isDebugMode) consoleStats();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function consoleStats() {
|
||||
console.table(scene.renderables.map(r => ({
|
||||
drawCount: r.values.drawCount.ref.value,
|
||||
instanceCount: r.values.instanceCount.ref.value,
|
||||
materialId: r.materialId,
|
||||
})));
|
||||
}
|
||||
|
||||
function add(repr: Representation.Any) {
|
||||
registerAutoUpdate(repr);
|
||||
|
||||
@@ -378,6 +393,7 @@ namespace Canvas3D {
|
||||
reprRenderObjects.set(repr, newRO);
|
||||
|
||||
scene.update(repr.renderObjects, false);
|
||||
if (isDebugMode) consoleStats();
|
||||
}
|
||||
|
||||
function remove(repr: Representation.Any) {
|
||||
@@ -388,6 +404,7 @@ namespace Canvas3D {
|
||||
renderObjects.forEach(o => scene.remove(o));
|
||||
reprRenderObjects.delete(repr);
|
||||
scene.update(repr.renderObjects, false, true);
|
||||
if (isDebugMode) consoleStats();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,7 +445,8 @@ namespace Canvas3D {
|
||||
multiSample: { ...multiSample.props },
|
||||
renderer: { ...renderer.props },
|
||||
trackball: { ...controls.props },
|
||||
debug: { ...debugHelper.props }
|
||||
debug: { ...debugHelper.props },
|
||||
handle: { ...handleHelper.props },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -535,11 +553,12 @@ namespace Canvas3D {
|
||||
if (props.renderer) renderer.setProps(props.renderer);
|
||||
if (props.trackball) controls.setProps(props.trackball);
|
||||
if (props.debug) debugHelper.setProps(props.debug);
|
||||
if (props.handle) handleHelper.setProps(props.handle);
|
||||
|
||||
requestDraw(true);
|
||||
},
|
||||
getImagePass: (props: Partial<ImageProps> = {}) => {
|
||||
return new ImagePass(webgl, renderer, scene, camera, debugHelper, props);
|
||||
return new ImagePass(webgl, renderer, scene, camera, debugHelper, handleHelper, props);
|
||||
},
|
||||
|
||||
get props() {
|
||||
@@ -563,7 +582,8 @@ namespace Canvas3D {
|
||||
multiSample: { ...multiSample.props },
|
||||
renderer: { ...renderer.props },
|
||||
trackball: { ...controls.props },
|
||||
debug: { ...debugHelper.props }
|
||||
debug: { ...debugHelper.props },
|
||||
handle: { ...handleHelper.props },
|
||||
};
|
||||
},
|
||||
get input() {
|
||||
|
||||
208
src/mol-canvas3d/helper/handle-helper.ts
Normal file
208
src/mol-canvas3d/helper/handle-helper.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import Scene from '../../mol-gl/scene';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3, Mat4, Mat3 } 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 { ValueCell } from '../../mol-util';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import produce from 'immer';
|
||||
import { Shape } from '../../mol-model/shape';
|
||||
import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { Camera } from '../camera';
|
||||
import { DataLoci, EmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
|
||||
import { Visual } from '../../mol-repr/visual';
|
||||
import { Interval } from '../../mol-data/int';
|
||||
|
||||
const HandleParams = {
|
||||
...Mesh.Params,
|
||||
alpha: { ...Mesh.Params.alpha, defaultValue: 1 },
|
||||
ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
|
||||
colorX: PD.Color(ColorNames.red, { isEssential: true }),
|
||||
colorY: PD.Color(ColorNames.green, { isEssential: true }),
|
||||
colorZ: PD.Color(ColorNames.blue, { isEssential: true }),
|
||||
scale: PD.Numeric(0.33, { min: 0.1, max: 2, step: 0.1 }, { isEssential: true }),
|
||||
};
|
||||
type HandleParams = typeof HandleParams
|
||||
type HandleProps = PD.Values<HandleParams>
|
||||
|
||||
export const HandleHelperParams = {
|
||||
handle: PD.MappedStatic('off', {
|
||||
on: PD.Group(HandleParams),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Show handle tool' }),
|
||||
};
|
||||
export type HandleHelperParams = typeof HandleHelperParams
|
||||
export type HandleHelperProps = PD.Values<HandleHelperParams>
|
||||
|
||||
export class HandleHelper {
|
||||
scene: Scene
|
||||
props: HandleHelperProps = {
|
||||
handle: { name: 'off', params: {} }
|
||||
}
|
||||
|
||||
private renderObject: GraphicsRenderObject | undefined
|
||||
|
||||
private _transform = Mat4();
|
||||
getBoundingSphere(out: Sphere3D, instanceId: number) {
|
||||
if (this.renderObject) {
|
||||
Sphere3D.copy(out, this.renderObject.values.invariantBoundingSphere.ref.value);
|
||||
Mat4.fromArray(this._transform, this.renderObject.values.aTransform.ref.value, instanceId * 16);
|
||||
Sphere3D.transform(out, out, this._transform);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
setProps(props: Partial<HandleHelperProps>) {
|
||||
this.props = produce(this.props, p => {
|
||||
if (props.handle !== undefined) {
|
||||
p.handle.name = props.handle.name;
|
||||
if (props.handle.name === 'on') {
|
||||
this.scene.clear();
|
||||
const params = { ...props.handle.params, scale: props.handle.params.scale * this.webgl.pixelRatio };
|
||||
this.renderObject = createHandleRenderObject(params);
|
||||
this.scene.add(this.renderObject);
|
||||
this.scene.commit();
|
||||
|
||||
p.handle.params = { ...props.handle.params };
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get isEnabled() {
|
||||
return this.props.handle.name === 'on';
|
||||
}
|
||||
|
||||
// TODO could be a lists of position/rotation if we want to show more than one handle tool,
|
||||
// they would be distingishable by their instanceId
|
||||
update(camera: Camera, position: Vec3, rotation: Mat3) {
|
||||
if (!this.renderObject) return;
|
||||
|
||||
Mat4.setTranslation(this.renderObject.values.aTransform.ref.value as unknown as Mat4, position);
|
||||
Mat4.fromMat3(this.renderObject.values.aTransform.ref.value as unknown as Mat4, rotation);
|
||||
|
||||
// TODO make invariant to camera scaling by adjusting renderObject transform
|
||||
|
||||
ValueCell.update(this.renderObject.values.aTransform, this.renderObject.values.aTransform.ref.value);
|
||||
this.scene.update([this.renderObject], true);
|
||||
}
|
||||
|
||||
getLoci(pickingId: PickingId) {
|
||||
const { objectId, groupId, instanceId } = pickingId;
|
||||
if (!this.renderObject || objectId !== this.renderObject.id) return EmptyLoci;
|
||||
return HandleLoci(this, groupId, instanceId);
|
||||
}
|
||||
|
||||
private eachGroup = (loci: Loci, apply: (interval: Interval) => boolean): boolean => {
|
||||
if (!this.renderObject) return false;
|
||||
if (!isHandleLoci(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 (!isHandleLoci(loci)) return false;
|
||||
if (loci.data !== this) return false;
|
||||
return Visual.mark(this.renderObject, loci, action, this.eachGroup);
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, props: Partial<HandleHelperProps> = {}) {
|
||||
this.scene = Scene.create(webgl);
|
||||
this.setProps(props);
|
||||
}
|
||||
}
|
||||
|
||||
function createHandleMesh(scale: number, mesh?: Mesh) {
|
||||
const state = MeshBuilder.createState(512, 256, mesh);
|
||||
const radius = 0.05 * scale;
|
||||
const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
|
||||
const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
|
||||
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
|
||||
|
||||
state.currentGroup = HandleGroup.TranslateScreenXY;
|
||||
addSphere(state, Vec3.origin, radius * 3, 2);
|
||||
|
||||
state.currentGroup = HandleGroup.TranslateObjectX;
|
||||
addSphere(state, x, radius, 2);
|
||||
addCylinder(state, Vec3.origin, x, 1, cylinderProps);
|
||||
|
||||
state.currentGroup = HandleGroup.TranslateObjectY;
|
||||
addSphere(state, y, radius, 2);
|
||||
addCylinder(state, Vec3.origin, y, 1, cylinderProps);
|
||||
|
||||
state.currentGroup = HandleGroup.TranslateObjectZ;
|
||||
addSphere(state, z, radius, 2);
|
||||
addCylinder(state, Vec3.origin, z, 1, cylinderProps);
|
||||
|
||||
// TODO add more helper geometries for the other HandleGroup options
|
||||
// TODO add props to create subset of geometries
|
||||
|
||||
return MeshBuilder.getMesh(state);
|
||||
}
|
||||
|
||||
export const HandleGroup = {
|
||||
None: 0,
|
||||
TranslateScreenXY: 1,
|
||||
// TranslateScreenZ: 2,
|
||||
TranslateObjectX: 3,
|
||||
TranslateObjectY: 4,
|
||||
TranslateObjectZ: 5,
|
||||
// TranslateObjectXY: 6,
|
||||
// TranslateObjectXZ: 7,
|
||||
// TranslateObjectYZ: 8,
|
||||
|
||||
// RotateScreenZ: 9,
|
||||
// RotateObjectX: 10,
|
||||
// RotateObjectY: 11,
|
||||
// RotateObjectZ: 12,
|
||||
} as const;
|
||||
|
||||
function HandleLoci(handleHelper: HandleHelper, groupId: number, instanceId: number) {
|
||||
return DataLoci('handle', handleHelper, [{ groupId, instanceId }],
|
||||
(boundingSphere: Sphere3D) => handleHelper.getBoundingSphere(boundingSphere, instanceId),
|
||||
() => `Handle Helper | Group Id ${groupId} | Instance Id ${instanceId}`);
|
||||
}
|
||||
export type HandleLoci = ReturnType<typeof HandleLoci>
|
||||
export function isHandleLoci(x: Loci): x is HandleLoci {
|
||||
return x.kind === 'data-loci' && x.tag === 'handle';
|
||||
}
|
||||
|
||||
function getHandleShape(props: HandleProps, shape?: Shape<Mesh>) {
|
||||
const scale = 10 * props.scale;
|
||||
const mesh = createHandleMesh(scale, shape?.geometry);
|
||||
mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4));
|
||||
const getColor = (groupId: number) => {
|
||||
switch (groupId) {
|
||||
case HandleGroup.TranslateObjectX: return props.colorX;
|
||||
case HandleGroup.TranslateObjectY: return props.colorY;
|
||||
case HandleGroup.TranslateObjectZ: return props.colorZ;
|
||||
default: return ColorNames.grey;
|
||||
}
|
||||
};
|
||||
return Shape.create('handle', {}, mesh, getColor, () => 1, () => '');
|
||||
}
|
||||
|
||||
function createHandleRenderObject(props: HandleProps) {
|
||||
const shape = getHandleShape(props);
|
||||
return Shape.createRenderObject(shape, props);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { Texture } from '../../mol-gl/webgl/texture';
|
||||
import { Camera } from '../camera';
|
||||
import { CameraHelper, CameraHelperParams } from '../helper/camera-helper';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { HandleHelper } from '../helper/handle-helper';
|
||||
|
||||
export const DrawPassParams = {
|
||||
cameraHelper: PD.Group(CameraHelperParams)
|
||||
@@ -29,7 +30,7 @@ export class DrawPass {
|
||||
|
||||
private depthTarget: RenderTarget | null
|
||||
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, props: Partial<DrawPassProps> = {}) {
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, private handleHelper: HandleHelper, props: Partial<DrawPassProps> = {}) {
|
||||
const { gl, extensions, resources } = webgl;
|
||||
const width = gl.drawingBufferWidth;
|
||||
const height = gl.drawingBufferHeight;
|
||||
@@ -89,12 +90,15 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
private renderInternal(variant: 'color' | 'depth', transparentBackground: boolean) {
|
||||
const { renderer, scene, camera, debugHelper, cameraHelper } = this;
|
||||
const { renderer, scene, camera, debugHelper, cameraHelper, handleHelper } = this;
|
||||
renderer.render(scene, camera, variant, true, transparentBackground);
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility();
|
||||
renderer.render(debugHelper.scene, camera, variant, false, transparentBackground);
|
||||
}
|
||||
if (handleHelper.isEnabled) {
|
||||
renderer.render(handleHelper.scene, camera, variant, false, transparentBackground);
|
||||
}
|
||||
if (cameraHelper.isEnabled) {
|
||||
cameraHelper.update(camera);
|
||||
renderer.render(cameraHelper.scene, cameraHelper.camera, variant, false, transparentBackground);
|
||||
|
||||
@@ -15,6 +15,7 @@ import { PostprocessingPass, PostprocessingParams } from './postprocessing';
|
||||
import { MultiSamplePass, MultiSampleParams } from './multi-sample';
|
||||
import { Camera } from '../camera';
|
||||
import { Viewport } from '../camera/util';
|
||||
import { HandleHelper } from '../helper/handle-helper';
|
||||
|
||||
export const ImageParams = {
|
||||
transparentBackground: PD.Boolean(false),
|
||||
@@ -40,12 +41,12 @@ export class ImagePass {
|
||||
get width() { return this._width; }
|
||||
get height() { return this._height; }
|
||||
|
||||
constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, props: Partial<ImageProps>) {
|
||||
constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, handleHelper: HandleHelper, props: Partial<ImageProps>) {
|
||||
const p = { ...PD.getDefaultValues(ImageParams), ...props };
|
||||
|
||||
this._transparentBackground = p.transparentBackground;
|
||||
|
||||
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, p.drawPass);
|
||||
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, handleHelper, p.drawPass);
|
||||
this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing);
|
||||
this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import Scene from '../../mol-gl/scene';
|
||||
import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { decodeFloatRGB } from '../../mol-util/float-packing';
|
||||
import { Camera } from '../camera';
|
||||
import { HandleHelper } from '../helper/handle-helper';
|
||||
|
||||
export class PickPass {
|
||||
pickDirty = true
|
||||
@@ -27,7 +28,7 @@ export class PickPass {
|
||||
private pickWidth: number
|
||||
private pickHeight: number
|
||||
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private pickBaseScale: number) {
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private handleHelper: HandleHelper, private pickBaseScale: number) {
|
||||
const { gl } = webgl;
|
||||
const width = gl.drawingBufferWidth;
|
||||
const height = gl.drawingBufferHeight;
|
||||
@@ -65,14 +66,18 @@ export class PickPass {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { renderer, scene, camera } = this;
|
||||
const { renderer, scene, camera, handleHelper: { scene: handleScene } } = this;
|
||||
renderer.setViewport(0, 0, this.pickWidth, this.pickHeight);
|
||||
|
||||
this.objectPickTarget.bind();
|
||||
renderer.render(scene, camera, 'pickObject', true, false);
|
||||
renderer.render(handleScene, camera, 'pickObject', false, false);
|
||||
this.instancePickTarget.bind();
|
||||
renderer.render(scene, camera, 'pickInstance', true, false);
|
||||
renderer.render(handleScene, camera, 'pickInstance', false, false);
|
||||
this.groupPickTarget.bind();
|
||||
renderer.render(scene, camera, 'pickGroup', true, false);
|
||||
renderer.render(handleScene, camera, 'pickGroup', false, false);
|
||||
|
||||
this.pickDirty = false;
|
||||
}
|
||||
|
||||
@@ -207,5 +207,6 @@ export namespace Points {
|
||||
!props.pointFilledCircle ||
|
||||
(props.pointFilledCircle && props.pointEdgeBleach === 0)
|
||||
);
|
||||
state.writeDepth = state.opaque;
|
||||
}
|
||||
}
|
||||
@@ -283,6 +283,7 @@ export namespace Text {
|
||||
BaseGeometry.updateRenderableState(state, props);
|
||||
state.pickable = false;
|
||||
state.opaque = false;
|
||||
state.writeDepth = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
export default `
|
||||
// only mark elements with an alpha above the picking threshold
|
||||
if (gl_FragColor.a >= uPickingAlphaThreshold) {
|
||||
float marker = floor(vMarker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
if (marker > 0.1) {
|
||||
if (intMod(marker, 2.0) > 0.1) {
|
||||
gl_FragColor.rgb = mix(uHighlightColor, gl_FragColor.rgb, 0.3);
|
||||
} else {
|
||||
gl_FragColor.rgb = mix(uSelectColor, gl_FragColor.rgb, 0.3);
|
||||
}
|
||||
float marker = floor(vMarker * 255.0 + 0.5); // rounding required to work on some cards on win
|
||||
if (marker > 0.1) {
|
||||
if (intMod(marker, 2.0) > 0.1) {
|
||||
gl_FragColor.rgb = mix(uHighlightColor, gl_FragColor.rgb, 0.3);
|
||||
} else {
|
||||
gl_FragColor.rgb = mix(uSelectColor, gl_FragColor.rgb, 0.3);
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -255,11 +255,12 @@ namespace Loci {
|
||||
};
|
||||
export type Granularity = keyof typeof Granularity
|
||||
export const GranularityOptions = ParamDefinition.objectToOptions(Granularity, k => {
|
||||
if (k.indexOf('Instances') > 0) return [stringToWords(k), 'With Symmetry'];
|
||||
switch (k) {
|
||||
case 'element': return'Atom/Coarse Element';
|
||||
case 'elementInstances': return ['Atom/Coarse Element Instances', 'With Symmetry'];
|
||||
case 'structure': return'Structure/Shape';
|
||||
default: return stringToWords(k);
|
||||
default: return k.indexOf('Instances')
|
||||
? [stringToWords(k), 'With Symmetry'] : stringToWords(k);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
0
src/mol-model/sequence/alignment.ts
Normal file
0
src/mol-model/sequence/alignment.ts
Normal file
26
src/mol-model/sequence/alignment/_spec/alignment.spec.ts
Normal file
26
src/mol-model/sequence/alignment/_spec/alignment.spec.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { align } from '../alignment';
|
||||
|
||||
describe('Alignment', () => {
|
||||
it('basic', () => {
|
||||
// 3PQR: Rhodopsin
|
||||
const seqA = 'MNGTEGPNFYVPFSNKTGVVRSPFEAPQYYLAEPWQFSMLAAYMFLLIMLGFPINFLTLYVTVQHKKLRTPLNYILLNLAVADLFMVFGGFTTTLYTSLHGYFVFGPTGCNLEGFFATLGGEIALWSLVVLAIERYVVVCKPMSNFRFGENHAIMGVAFTWVMALACAAPPLVGWSRYIPEGMQCSCGIDYYTPHEETNNESFVIYMFVVHFIIPLIVIFFCYGQLVFTVKEAAAQQQESATTQKAEKEVTRMVIIMVIAFLICWLPYAGVAFYIFTHQGSDFGPIFMTIPAFFAKTSAVYNPVIYIMMNKQFRNCMVTTLCCGKNPLGDDEASTTVSKTETSQVAPA';
|
||||
// 3SN6: Endolysin,Beta-2 adrenergic
|
||||
const seqB = 'DYKDDDDAENLYFQGNIFEMLRIDEGLRLKIYKDTEGYYTIGIGHLLTKSPSLNAAKSELDKAIGRNTNGVITKDEAEKLFNQDVDAAVRGILRNAKLKPVYDSLDAVRRAALINMVFQMGETGVAGFTNSLRMLQQKRWDEAAVNLAKSRWYNQTPNRAKRVITTFRTGTWDAYAADEVWVVGMGIVMSLIVLAIVFGNVLVITAIAKFERLQTVTNYFITSLACADLVMGLAVVPFGAAHILTKTWTFGNFWCEFWTSIDVLCVTASIETLCVIAVDRYFAITSPFKYQSLLTKNKARVIILMVWIVSGLTSFLPIQMHWYRATHQEAINCYAEETCCDFFTNQAYAIASSIVSFYVPLVIMVFVYSRVFQEAKRQLQKIDKSEGRFHVQNLSQVEQDGRTGHGLRRSSKFCLKEHKALKTLGIIMGTFTLCWLPFFIVNIVHVIQDNLIRKEVYILLNWIGYVNSGFNPLIYCRSPDFRIAFQELLCLRRSSLKAYGNGYSSNGNTGEQSG';
|
||||
|
||||
const { aliA, aliB, score } = align(seqA, seqB, {
|
||||
gapPenalty: -11,
|
||||
gapExtensionPenalty: -1,
|
||||
substMatrix: 'blosum62'
|
||||
});
|
||||
|
||||
expect(aliA).toEqual('------------------------------------------------------------------------------------------------------------------------------------------------MNGTEGPNFYVPFSNKTGVVRSPFEA---PQYYLAEPWQFSM--LAAYMFLLIMLGFPINFLTLYVTVQHKKLRTPLNYILLNLAVADLFMVFGGFTTTLYTSLH---GYFVFGPTGCNLEGFFATLGGEIALWSLVVLAIERYVVVCKPMS-NFRFGENHAIMGVAFTWVMA-LACAAPPLVGWSRYI-PEGMQC----SCGIDYYTPHEETNNESFVIYMFVVHFIIPLIVIFFCYGQLV----------------FTVKEAAAQQQESATTQ----------KAEKEVTRMVIIMVIAFLICWLPYAGVAFYIFTHQGSDFGPIFMTIPAFFAKTSAVYNPVIYIMMNKQFRNCMVTTLCCGKNPLGDDEASTTVSKTETSQVAPA');
|
||||
expect(aliB).toEqual('DYKDDDDAENLYFQGNIFEMLRIDEGLRLKIYKDTEGYYTIGIGHLLTKSPSLNAAKSELDKAIGRNTNGVITKDEAEKLFNQDVDAAVRGILRNAKLKPVYDSLDAVRRAALINMVFQMGETGVAGFTNSLRMLQQKRWDEAAVNLAKS-RWYNQTPNRAKRVITTFRTGTWDAYAADEVWVVGMGIVMSLIVLAIVFG---NVLVITAIAKFERLQTVTNYFITSLACADLVM---GLAVVPFGAAHILTKTWTFGNFWCEFWTSIDVLCVTASIETLCVIAVDRYFAITSPFKYQSLLTKNKARVIILMVWIVSGLTSFLPIQMHWYRATHQEAINCYAEETC-CDFFT------NQAYAIASSIVSFYVPLVIMVFVYSRVFQEAKRQLQKIDKSEGRFHVQNLSQVEQDGRTGHGLRRSSKFCLKEHKALKTLGIIMG-TFTLCWLPFF-IVNIVHVIQDNLIRKEVYILLNWIGYVNSGFNPLIY-CRSPDFRIAFQELLCLRRSSL--KAYGNGYSSNGNTGEQSG');
|
||||
expect(score).toEqual(118);
|
||||
});
|
||||
});
|
||||
196
src/mol-model/sequence/alignment/alignment.ts
Normal file
196
src/mol-model/sequence/alignment/alignment.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { SubstitutionMatrix, SubstitutionMatrices, SubstitutionMatrixData } from './substitution-matrix';
|
||||
|
||||
const DefaultAlignmentOptions = {
|
||||
gapPenalty: -11,
|
||||
gapExtensionPenalty: -1,
|
||||
substMatrix: 'default' as SubstitutionMatrix | 'default'
|
||||
};
|
||||
export type AlignmentOptions = typeof DefaultAlignmentOptions;
|
||||
|
||||
export function align(seqA: ArrayLike<string>, seqB: ArrayLike<string>, options: Partial<AlignmentOptions> = {}) {
|
||||
const o = { ...DefaultAlignmentOptions, ...options };
|
||||
const alignment = new Alignment(seqA, seqB, o);
|
||||
alignment.calculate();
|
||||
return alignment.trace();
|
||||
}
|
||||
|
||||
class Alignment {
|
||||
readonly gapPenalty: number; readonly gapExtensionPenalty: number
|
||||
readonly substMatrix: SubstitutionMatrixData | undefined
|
||||
|
||||
readonly n: number; readonly m: number
|
||||
readonly S: number[][] = []; readonly V: number[][] = []; readonly H: number[][] = []
|
||||
|
||||
constructor (readonly seqA: ArrayLike<string>, readonly seqB: ArrayLike<string>, options: AlignmentOptions) {
|
||||
this.gapPenalty = options.gapPenalty;
|
||||
this.gapExtensionPenalty = options.gapExtensionPenalty;
|
||||
this.substMatrix = options.substMatrix === 'default' ? undefined : SubstitutionMatrices[options.substMatrix];
|
||||
|
||||
this.n = this.seqA.length;
|
||||
this.m = this.seqB.length;
|
||||
}
|
||||
|
||||
private initMatrices () {
|
||||
const { n, m, gapPenalty, S, V, H } = this;
|
||||
|
||||
for (let i = 0; i <= n; ++i) {
|
||||
S[i] = [], V[i] = [], H[i] = [];
|
||||
|
||||
for (let j = 0; j <= m; ++j) {
|
||||
S[i][j] = 0, V[i][j] = 0, H[i][j] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i <= n; ++i) {
|
||||
S[i][0] = gapPenalty;
|
||||
H[i][0] = -Infinity;
|
||||
}
|
||||
|
||||
for (let j = 0; j <= m; ++j) {
|
||||
S[0][j] = gapPenalty;
|
||||
V[0][j] = -Infinity;
|
||||
}
|
||||
|
||||
S[0][0] = 0;
|
||||
}
|
||||
|
||||
private makeScoreFn () {
|
||||
const { seqA, seqB, substMatrix } = this;
|
||||
|
||||
if (substMatrix) {
|
||||
return function score (i: number, j: number) {
|
||||
const cA = seqA[i];
|
||||
const cB = seqB[j];
|
||||
return substMatrix[cA]?.[cB] ?? -4;
|
||||
};
|
||||
} else {
|
||||
return function scoreNoSubstMat (i: number, j: number) {
|
||||
const cA = seqA[i];
|
||||
const cB = seqB[j];
|
||||
return cA === cB ? 5 : -3;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
calculate () {
|
||||
this.initMatrices();
|
||||
|
||||
const scoreFn = this.makeScoreFn();
|
||||
const { V, H, S, n, m, gapExtensionPenalty, gapPenalty } = this;
|
||||
|
||||
let Vi1, Si1, Vi, Hi, Si;
|
||||
for (let i = 1; i <= n; ++i) {
|
||||
Si1 = S[i - 1], Vi1 = V[i - 1];
|
||||
Vi = V[i], Hi = H[i], Si = S[i];
|
||||
|
||||
for (let j = 1; j <= m; ++j) {
|
||||
Vi[j] = Math.max(
|
||||
Si1[j] + gapPenalty,
|
||||
Vi1[j] + gapExtensionPenalty
|
||||
);
|
||||
|
||||
Hi[j] = Math.max(
|
||||
Si[j - 1] + gapPenalty,
|
||||
Hi[j - 1] + gapExtensionPenalty
|
||||
);
|
||||
|
||||
Si[j] = Math.max(
|
||||
Si1[j - 1] + scoreFn(i - 1, j - 1), // match
|
||||
Vi[j], // del
|
||||
Hi[j] // ins
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace (): { aliA: ArrayLike<string>, aliB: ArrayLike<string>, score: number } {
|
||||
const scoreFn = this.makeScoreFn();
|
||||
const { V, H, S, seqA, seqB, gapExtensionPenalty, gapPenalty } = this;
|
||||
|
||||
let i = this.n;
|
||||
let j = this.m;
|
||||
let mat: 'S' | 'V' | 'H';
|
||||
let score: number;
|
||||
|
||||
let aliA = '';
|
||||
let aliB = '';
|
||||
|
||||
if (S[i][j] >= V[i][j]) {
|
||||
mat = 'S';
|
||||
score = S[i][j];
|
||||
} else if (V[i][j] >= H[i][j]) {
|
||||
mat = 'V';
|
||||
score = V[i][j];
|
||||
} else {
|
||||
mat = 'H';
|
||||
score = H[i][j];
|
||||
}
|
||||
|
||||
while (i > 0 && j > 0) {
|
||||
if (mat === 'S') {
|
||||
if (S[i][j] === S[i - 1][j - 1] + scoreFn(i - 1, j - 1)) {
|
||||
aliA = seqA[i - 1] + aliA;
|
||||
aliB = seqB[j - 1] + aliB;
|
||||
--i;
|
||||
--j;
|
||||
mat = 'S';
|
||||
} else if (S[i][j] === V[i][j]) {
|
||||
mat = 'V';
|
||||
} else if (S[i][j] === H[i][j]) {
|
||||
mat = 'H';
|
||||
} else {
|
||||
--i;
|
||||
--j;
|
||||
}
|
||||
} else if (mat === 'V') {
|
||||
if (V[i][j] === V[i - 1][j] + gapExtensionPenalty) {
|
||||
aliA = seqA[i - 1] + aliA;
|
||||
aliB = '-' + aliB;
|
||||
--i;
|
||||
mat = 'V';
|
||||
} else if (V[i][j] === S[i - 1][j] + gapPenalty) {
|
||||
aliA = seqA[i - 1] + aliA;
|
||||
aliB = '-' + aliB;
|
||||
--i;
|
||||
mat = 'S';
|
||||
} else {
|
||||
--i;
|
||||
}
|
||||
} else if (mat === 'H') {
|
||||
if (H[i][j] === H[i][j - 1] + gapExtensionPenalty) {
|
||||
aliA = '-' + aliA;
|
||||
aliB = seqB[j - 1] + aliB;
|
||||
--j;
|
||||
mat = 'H';
|
||||
} else if (H[i][j] === S[i][j - 1] + gapPenalty) {
|
||||
aliA = '-' + aliA;
|
||||
aliB = seqB[j - 1] + aliB;
|
||||
--j;
|
||||
mat = 'S';
|
||||
} else {
|
||||
--j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (i > 0) {
|
||||
aliA = seqA[i - 1] + aliA;
|
||||
aliB = '-' + aliB;
|
||||
--i;
|
||||
}
|
||||
|
||||
while (j > 0) {
|
||||
aliA = '-' + aliA;
|
||||
aliB = seqB[j - 1] + aliB;
|
||||
--j;
|
||||
}
|
||||
|
||||
return { aliA, aliB, score };
|
||||
}
|
||||
}
|
||||
110
src/mol-model/sequence/alignment/sequence.ts
Normal file
110
src/mol-model/sequence/alignment/sequence.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StructureElement, Unit } from '../../structure/structure';
|
||||
import { AlignmentOptions, align } from './alignment';
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
|
||||
export { AlignSequences };
|
||||
|
||||
namespace AlignSequences {
|
||||
export type Input = {
|
||||
a: StructureElement.Loci.Element,
|
||||
b: StructureElement.Loci.Element
|
||||
}
|
||||
/** `a` and `b` contain matching pairs, i.e. `a.indices[0]` aligns with `b.indices[0]` */
|
||||
export type Result = {
|
||||
a: StructureElement.Loci.Element,
|
||||
b: StructureElement.Loci.Element,
|
||||
score: number
|
||||
}
|
||||
|
||||
function createSeqIdIndicesMap(element: StructureElement.Loci.Element) {
|
||||
const seqIds = new Map<number, StructureElement.UnitIndex[]>();
|
||||
if (Unit.isAtomic(element.unit)) {
|
||||
const { label_seq_id } = element.unit.model.atomicHierarchy.residues;
|
||||
const { residueIndex } = element.unit;
|
||||
for (let i = 0, il = OrderedSet.size(element.indices); i < il; ++i) {
|
||||
const uI = OrderedSet.getAt(element.indices, i);
|
||||
const eI = element.unit.elements[uI];
|
||||
const seqId = label_seq_id.value(residueIndex[eI]);
|
||||
if (seqIds.has(seqId)) seqIds.get(seqId)!.push(uI);
|
||||
else seqIds.set(seqId, [uI]);
|
||||
}
|
||||
} else if (Unit.isCoarse(element.unit)) {
|
||||
const { seq_id_begin } = Unit.isSpheres(element.unit)
|
||||
? element.unit.model.coarseHierarchy.spheres
|
||||
: element.unit.model.coarseHierarchy.gaussians;
|
||||
for (let i = 0, il = OrderedSet.size(element.indices); i < il; ++i) {
|
||||
const uI = OrderedSet.getAt(element.indices, i);
|
||||
const eI = element.unit.elements[uI];
|
||||
const seqId = seq_id_begin.value(eI);
|
||||
seqIds.set(seqId, [uI]);
|
||||
}
|
||||
}
|
||||
return seqIds;
|
||||
}
|
||||
|
||||
export function compute(input: Input, options: Partial<AlignmentOptions> = {}): Result {
|
||||
const seqA = getSequence(input.a.unit);
|
||||
const seqB = getSequence(input.b.unit);
|
||||
|
||||
const seqIdIndicesA = createSeqIdIndicesMap(input.a);
|
||||
const seqIdIndicesB = createSeqIdIndicesMap(input.b);
|
||||
|
||||
const indicesA: StructureElement.UnitIndex[] = [];
|
||||
const indicesB: StructureElement.UnitIndex[] = [];
|
||||
const { aliA, aliB, score } = align(seqA.code.toArray(), seqB.code.toArray(), options);
|
||||
|
||||
let seqIdxA = 0, seqIdxB = 0;
|
||||
for (let i = 0, il = aliA.length; i < il; ++i) {
|
||||
if (aliA[i] === '-' || aliB[i] === '-') {
|
||||
if (aliA[i] !== '-') seqIdxA += 1;
|
||||
if (aliB[i] !== '-') seqIdxB += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const seqIdA = seqA.seqId.value(seqIdxA);
|
||||
const seqIdB = seqB.seqId.value(seqIdxB);
|
||||
|
||||
if (seqIdIndicesA.has(seqIdA) && seqIdIndicesB.has(seqIdB)) {
|
||||
const iA = seqIdIndicesA.get(seqIdA)!;
|
||||
const iB = seqIdIndicesB.get(seqIdB)!;
|
||||
// use min length to guard against alternate locations
|
||||
for (let j = 0, jl = Math.min(iA.length, iB.length); j < jl; ++j) {
|
||||
indicesA.push(iA[j]);
|
||||
indicesB.push(iB[j]);
|
||||
}
|
||||
}
|
||||
|
||||
seqIdxA += 1, seqIdxB += 1;
|
||||
}
|
||||
|
||||
const outA = OrderedSet.intersect(OrderedSet.ofSortedArray(indicesA), input.a.indices);
|
||||
const outB = OrderedSet.intersect(OrderedSet.ofSortedArray(indicesB), input.b.indices);
|
||||
|
||||
return {
|
||||
a: { unit: input.a.unit, indices: outA },
|
||||
b: { unit: input.b.unit, indices: outB },
|
||||
score
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function entityKey(unit: Unit) {
|
||||
switch (unit.kind) {
|
||||
case Unit.Kind.Atomic:
|
||||
return unit.model.atomicHierarchy.index.getEntityFromChain(unit.chainIndex[unit.elements[0]]);
|
||||
case Unit.Kind.Spheres:
|
||||
return unit.model.coarseHierarchy.spheres.entityKey[unit.elements[0]];
|
||||
case Unit.Kind.Gaussians:
|
||||
return unit.model.coarseHierarchy.gaussians.entityKey[unit.elements[0]];
|
||||
}
|
||||
}
|
||||
|
||||
function getSequence(unit: Unit) {
|
||||
return unit.model.sequence.byEntityKey[entityKey(unit)].sequence;
|
||||
}
|
||||
81
src/mol-model/sequence/alignment/substitution-matrix.ts
Normal file
81
src/mol-model/sequence/alignment/substitution-matrix.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mutable } from '../../../mol-util/type-helpers';
|
||||
|
||||
const aminoacidsX = 'ACDEFGHIKLMNPQRSTVWY';
|
||||
const aminoacids = 'ARNDCQEGHILKMFPSTWYVBZX';
|
||||
|
||||
const blosum62x = [
|
||||
[4, 0, -2, -1, -2, 0, -2, -1, -1, -1, -1, -2, -1, -1, -1, 1, 0, 0, -3, -2], // A
|
||||
[0, 9, -3, -4, -2, -3, -3, -1, -3, -1, -1, -3, -3, -3, -3, -1, -1, -1, -2, -2], // C
|
||||
[-2, -3, 6, 2, -3, -1, -1, -3, -1, -4, -3, 1, -1, 0, -2, 0, -1, -3, -4, -3], // D
|
||||
[-1, -4, 2, 5, -3, -2, 0, -3, 1, -3, -2, 0, -1, 2, 0, 0, -1, -2, -3, -2], // E
|
||||
[-2, -2, -3, -3, 6, -3, -1, 0, -3, 0, 0, -3, -4, -3, -3, -2, -2, -1, 1, 3], // F
|
||||
[0, -3, -1, -2, -3, 6, -2, -4, -2, -4, -3, 0, -2, -2, -2, 0, -2, -3, -2, -3], // G
|
||||
[-2, -3, -1, 0, -1, -2, 8, -3, -1, -3, -2, 1, -2, 0, 0, -1, -2, -3, -2, 2], // H
|
||||
[-1, -1, -3, -3, 0, -4, -3, 4, -3, 2, 1, -3, -3, -3, -3, -2, -1, 3, -3, -1], // I
|
||||
[-1, -3, -1, 1, -3, -2, -1, -3, 5, -2, -1, 0, -1, 1, 2, 0, -1, -2, -3, -2], // K
|
||||
[-1, -1, -4, -3, 0, -4, -3, 2, -2, 4, 2, -3, -3, -2, -2, -2, -1, 1, -2, -1], // L
|
||||
[-1, -1, -3, -2, 0, -3, -2, 1, -1, 2, 5, -2, -2, 0, -1, -1, -1, 1, -1, -1], // M
|
||||
[-2, -3, 1, 0, -3, 0, 1, -3, 0, -3, -2, 6, -2, 0, 0, 1, 0, -3, -4, -2], // N
|
||||
[-1, -3, -1, -1, -4, -2, -2, -3, -1, -3, -2, -2, 7, -1, -2, -1, -1, -2, -4, -3], // P
|
||||
[-1, -3, 0, 2, -3, -2, 0, -3, 1, -2, 0, 0, -1, 5, 1, 0, -1, -2, -2, -1], // Q
|
||||
[-1, -3, -2, 0, -3, -2, 0, -3, 2, -2, -1, 0, -2, 1, 5, -1, -1, -3, -3, -2], // R
|
||||
[1, -1, 0, 0, -2, 0, -1, -2, 0, -2, -1, 1, -1, 0, -1, 4, 1, -2, -3, -2], // S
|
||||
[0, -1, -1, -1, -2, -2, -2, -1, -1, -1, -1, 0, -1, -1, -1, 1, 5, 0, -2, -2], // T
|
||||
[0, -1, -3, -2, -1, -3, -3, 3, -2, 1, 1, -3, -2, -2, -3, -2, 0, 4, -3, -1], // V
|
||||
[-3, -2, -4, -3, 1, -2, -2, -3, -3, -2, -1, -4, -4, -2, -3, -3, -2, -3, 11, 2], // W
|
||||
[-2, -2, -3, -2, 3, -3, 2, -1, -2, -1, -1, -2, -3, -1, -2, -2, -2, -1, 2, 7] // Y
|
||||
];
|
||||
|
||||
const blosum62 = [
|
||||
// A R N D C Q E G H I L K M F P S T W Y V B Z X
|
||||
[4, -1, -2, -2, 0, -1, -1, 0, -2, -1, -1, -1, -1, -2, -1, 1, 0, -3, -2, 0, -2, -1, 0], // A
|
||||
[-1, 5, 0, -2, -3, 1, 0, -2, 0, -3, -2, 2, -1, -3, -2, -1, -1, -3, -2, -3, -1, 0, -1], // R
|
||||
[-2, 0, 6, 1, -3, 0, 0, 0, 1, -3, -3, 0, -2, -3, -2, 1, 0, -4, -2, -3, 3, 0, -1], // N
|
||||
[-2, -2, 1, 6, -3, 0, 2, -1, -1, -3, -4, -1, -3, -3, -1, 0, -1, -4, -3, -3, 4, 1, -1], // D
|
||||
[0, -3, -3, -3, 9, -3, -4, -3, -3, -1, -1, -3, -1, -2, -3, -1, -1, -2, -2, -1, -3, -3, -2], // C
|
||||
[-1, 1, 0, 0, -3, 5, 2, -2, 0, -3, -2, 1, 0, -3, -1, 0, -1, -2, -1, -2, 0, 3, -1], // Q
|
||||
[-1, 0, 0, 2, -4, 2, 5, -2, 0, -3, -3, 1, -2, -3, -1, 0, -1, -3, -2, -2, 1, 4, -1], // E
|
||||
[0, -2, 0, -1, -3, -2, -2, 6, -2, -4, -4, -2, -3, -3, -2, 0, -2, -2, -3, -3, -1, -2, -1], // G
|
||||
[-2, 0, 1, -1, -3, 0, 0, -2, 8, -3, -3, -1, -2, -1, -2, -1, -2, -2, 2, -3, 0, 0, -1], // H
|
||||
[-1, -3, -3, -3, -1, -3, -3, -4, -3, 4, 2, -3, 1, 0, -3, -2, -1, -3, -1, 3, -3, -3, -1], // I
|
||||
[-1, -2, -3, -4, -1, -2, -3, -4, -3, 2, 4, -2, 2, 0, -3, -2, -1, -2, -1, 1, -4, -3, -1], // L
|
||||
[-1, 2, 0, -1, -3, 1, 1, -2, -1, -3, -2, 5, -1, -3, -1, 0, -1, -3, -2, -2, 0, 1, -1], // K
|
||||
[-1, -1, -2, -3, -1, 0, -2, -3, -2, 1, 2, -1, 5, 0, -2, -1, -1, -1, -1, 1, -3, -1, -1], // M
|
||||
[-2, -3, -3, -3, -2, -3, -3, -3, -1, 0, 0, -3, 0, 6, -4, -2, -2, 1, 3, -1, -3, -3, -1], // F
|
||||
[-1, -2, -2, -1, -3, -1, -1, -2, -2, -3, -3, -1, -2, -4, 7, -1, -1, -4, -3, -2, -2, -1, -2], // P
|
||||
[1, -1, 1, 0, -1, 0, 0, 0, -1, -2, -2, 0, -1, -2, -1, 4, 1, -3, -2, -2, 0, 0, 0], // S
|
||||
[0, -1, 0, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, -2, -1, 1, 5, -2, -2, 0, -1, -1, 0], // T
|
||||
[-3, -3, -4, -4, -2, -2, -3, -2, -2, -3, -2, -3, -1, 1, -4, -3, -2, 11, 2, -3, -4, -3, -2], // W
|
||||
[-2, -2, -2, -3, -2, -1, -2, -3, 2, -1, -1, -2, -1, 3, -3, -2, -2, 2, 7, -1, -3, -2, -1], // Y
|
||||
[0, -3, -3, -3, -1, -2, -2, -3, -3, 3, 1, -2, 1, -1, -2, -2, 0, -3, -1, 4, -3, -2, -1], // V
|
||||
[-2, -1, 3, 4, -3, 0, 1, -1, 0, -3, -4, 0, -3, -3, -2, 0, -1, -4, -3, -3, 4, 1, -1], // B
|
||||
[-1, 0, 0, 1, -3, 3, 4, -2, 0, -3, -3, 1, -1, -3, -1, 0, -1, -3, -2, -2, 1, 4, -1], // Z
|
||||
[0, -1, -1, -1, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, 0, 0, -2, -1, -1, -1, -1, -1] // X
|
||||
];
|
||||
|
||||
export type SubstitutionMatrixData = Readonly<{ [k: string]: Readonly<{ [k: string]: number }> }>;
|
||||
|
||||
function prepareMatrix (cellNames: string, mat: number[][]): SubstitutionMatrixData {
|
||||
let j: number;
|
||||
let i = 0;
|
||||
const matDict: Mutable<SubstitutionMatrixData> = {};
|
||||
mat.forEach(row => {
|
||||
j = 0;
|
||||
const rowDict: { [k: string]: number } = {};
|
||||
row.forEach(elm => rowDict[cellNames[j++]] = elm);
|
||||
matDict[cellNames[i++]] = rowDict;
|
||||
});
|
||||
return matDict;
|
||||
}
|
||||
|
||||
export const SubstitutionMatrices = (() => ({
|
||||
blosum62: prepareMatrix(aminoacids, blosum62),
|
||||
blosum62x: prepareMatrix(aminoacidsX, blosum62x)
|
||||
}))();
|
||||
export type SubstitutionMatrix = keyof typeof SubstitutionMatrices;
|
||||
@@ -9,7 +9,6 @@ import { AminoAlphabet, NuclecicAlphabet, getProteinOneLetterCode, getRnaOneLett
|
||||
import { Column } from '../../mol-data/db';
|
||||
|
||||
// TODO add mapping support to other sequence spaces, e.g. uniprot
|
||||
// TODO sequence alignment (take NGL code as starting point)
|
||||
|
||||
type Sequence = Sequence.Protein | Sequence.DNA | Sequence.RNA | Sequence.Generic
|
||||
|
||||
|
||||
@@ -340,9 +340,6 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
|
||||
const interBonds = inputStructure.interUnitBonds;
|
||||
const builder = new StructureUniqueSubsetBuilder(inputStructure);
|
||||
|
||||
// Note: each bond is visited twice so that bond.atom-a and bond.atom-b both get the "swapped values"
|
||||
const visitedSourceUnits = new Set<number>();
|
||||
|
||||
const atomicBond = ctx.atomicBond;
|
||||
|
||||
// Process intra unit bonds
|
||||
@@ -394,7 +391,6 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
|
||||
|
||||
// Process inter unit bonds
|
||||
for (const bondedUnit of interBonds.getConnectedUnits(inputUnitA)) {
|
||||
if (visitedSourceUnits.has(bondedUnit.unitB.id)) continue;
|
||||
const currentUnitB = structure.unitMap.get(bondedUnit.unitB.id);
|
||||
|
||||
for (const aI of bondedUnit.connectedIndices) {
|
||||
@@ -422,8 +418,6 @@ function expandConnected(ctx: QueryContext, structure: Structure) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitedSourceUnits.add(inputUnitA.id);
|
||||
}
|
||||
|
||||
return builder.getStructure();
|
||||
|
||||
@@ -956,6 +956,9 @@ namespace Structure {
|
||||
this.current.unit = this.structure.units[this.unitIndex];
|
||||
this.elements = this.current.unit.elements;
|
||||
this.maxIdx = this.elements.length - 1;
|
||||
if (this.maxIdx === 0) {
|
||||
this.hasNext = this.unitIndex + 1 < this.structure.units.length;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private structure: Structure) {
|
||||
@@ -1057,14 +1060,19 @@ namespace Structure {
|
||||
//
|
||||
|
||||
export const DefaultSizeThresholds = {
|
||||
/** Must be lower to be small */
|
||||
smallResidueCount: 10,
|
||||
/** Must be lower to be medium */
|
||||
mediumResidueCount: 3000,
|
||||
/** large ribosomes like 4UG0 should still be `large` */
|
||||
/** Must be lower to be large (big ribosomes like 4UG0 should still be `large`) */
|
||||
largeResidueCount: 20000,
|
||||
/**
|
||||
* Structures above `largeResidueCount` are consider huge when they have
|
||||
* a `highSymmetryUnitCount` or gigantic when not
|
||||
*/
|
||||
highSymmetryUnitCount: 10,
|
||||
/** Fiber-like structure are consider small when below this */
|
||||
fiberResidueCount: 15,
|
||||
|
||||
residueCountFactor: 1
|
||||
};
|
||||
export type SizeThresholds = typeof DefaultSizeThresholds
|
||||
|
||||
@@ -1094,9 +1102,13 @@ namespace Structure {
|
||||
|
||||
export enum Size { Small, Medium, Large, Huge, Gigantic }
|
||||
|
||||
export function getSize(structure: Structure, thresholds: Partial<SizeThresholds> = {}): Size {
|
||||
/**
|
||||
* @param residueCountFactor - modifies the threshold counts, useful when estimating
|
||||
* the size of a structure comprised of multiple models
|
||||
*/
|
||||
export function getSize(structure: Structure, thresholds: Partial<SizeThresholds> = {}, residueCountFactor = 1): Size {
|
||||
const t = { ...DefaultSizeThresholds, ...thresholds };
|
||||
if (structure.polymerResidueCount >= t.largeResidueCount * t.residueCountFactor) {
|
||||
if (structure.polymerResidueCount >= t.largeResidueCount * residueCountFactor) {
|
||||
if (hasHighSymmetry(structure, t)) {
|
||||
return Size.Huge;
|
||||
} else {
|
||||
@@ -1104,9 +1116,9 @@ namespace Structure {
|
||||
}
|
||||
} else if (isFiberLike(structure, t)) {
|
||||
return Size.Small;
|
||||
} else if (structure.polymerResidueCount < t.smallResidueCount * t.residueCountFactor) {
|
||||
} else if (structure.polymerResidueCount < t.smallResidueCount * residueCountFactor) {
|
||||
return Size.Small;
|
||||
} else if (structure.polymerResidueCount < t.mediumResidueCount * t.residueCountFactor) {
|
||||
} else if (structure.polymerResidueCount < t.mediumResidueCount * residueCountFactor) {
|
||||
return Size.Medium;
|
||||
} else {
|
||||
return Size.Large;
|
||||
|
||||
@@ -102,6 +102,10 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
if (added) continue;
|
||||
}
|
||||
|
||||
// ignore atoms with zero occupancy (assuming they are not actually atoms)
|
||||
const occA = occupancyA.value(aI);
|
||||
if (hasOccupancy && occA === 0) continue;
|
||||
|
||||
const { indices, count, squaredDistances } = lookup3d.find(imageA[0], imageA[1], imageA[2], MAX_RADIUS);
|
||||
if (count === 0) continue;
|
||||
|
||||
@@ -112,7 +116,6 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
|
||||
const metalA = MetalsSet.has(aeI);
|
||||
const atomIdA = label_atom_idA.value(aI);
|
||||
const compIdA = label_comp_idA.value(residueIndexA[aI]);
|
||||
const occA = occupancyA.value(aI);
|
||||
|
||||
for (let ni = 0; ni < count; ni++) {
|
||||
const _bI = indices[ni] as StructureElement.UnitIndex;
|
||||
|
||||
@@ -39,6 +39,7 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
|
||||
const atomCount = unit.elements.length;
|
||||
const { elements: atoms, residueIndex, chainIndex } = unit;
|
||||
const { type_symbol, label_atom_id, label_alt_id } = unit.model.atomicHierarchy.atoms;
|
||||
const { occupancy } = unit.model.atomicConformation;
|
||||
const { label_comp_id, label_seq_id } = unit.model.atomicHierarchy.residues;
|
||||
const { index } = unit.model.atomicHierarchy;
|
||||
const { byEntityKey } = unit.model.sequence;
|
||||
@@ -116,6 +117,9 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
|
||||
}
|
||||
lastResidue = raI;
|
||||
|
||||
// ignore atoms with zero occupancy (assuming they are not actually atoms)
|
||||
if (occupancy.isDefined && occupancy.value(aI) === 0) continue;
|
||||
|
||||
const aeI = getElementIdx(type_symbol.value(aI));
|
||||
const atomIdA = label_atom_id.value(aI);
|
||||
const componentPairs = componentMap ? componentMap.get(atomIdA) : void 0;
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { MinimizeRmsd } from '../../../../mol-math/linear-algebra/3d/minimize-rmsd';
|
||||
import StructureElement from '../element';
|
||||
import { OrderedSet } from '../../../../mol-data/int';
|
||||
import { AlignSequences } from '../../../sequence/alignment/sequence';
|
||||
import StructureProperties from '../properties';
|
||||
|
||||
export function superposeStructures(xs: StructureElement.Loci[]): MinimizeRmsd.Result[] {
|
||||
export function superpose(xs: StructureElement.Loci[]): MinimizeRmsd.Result[] {
|
||||
const ret: MinimizeRmsd.Result[] = [];
|
||||
if (xs.length <= 0) return ret;
|
||||
|
||||
const n = getMinSize(xs);
|
||||
const input: MinimizeRmsd.Input = { a: getPositionTable(xs[0], n), b: getPositionTable(xs[1], n) };
|
||||
const input: MinimizeRmsd.Input = {
|
||||
a: getPositionTable(xs[0], n),
|
||||
b: getPositionTable(xs[1], n)
|
||||
};
|
||||
|
||||
ret[0] = MinimizeRmsd.compute(input);
|
||||
for (let i = 2; i < xs.length; i++) {
|
||||
input.b = getPositionTable(xs[i], n);
|
||||
@@ -24,6 +31,39 @@ export function superposeStructures(xs: StructureElement.Loci[]): MinimizeRmsd.R
|
||||
return ret;
|
||||
}
|
||||
|
||||
type AlignAndSuperposeResult = MinimizeRmsd.Result & { alignmentScore: number };
|
||||
const reProtein = /(polypeptide|cyclic-pseudo-peptide)/i;
|
||||
|
||||
export function alignAndSuperpose(xs: StructureElement.Loci[]): AlignAndSuperposeResult[] {
|
||||
const ret: AlignAndSuperposeResult[] = [];
|
||||
if (xs.length <= 0) return ret;
|
||||
|
||||
const l = StructureElement.Loci.getFirstLocation(xs[0])!;
|
||||
const subtype = StructureProperties.entity.subtype(l);
|
||||
const substMatrix = subtype.match(reProtein) ? 'blosum62' : 'default';
|
||||
|
||||
for (let i = 1; i < xs.length; i++) {
|
||||
const { a, b, score } = AlignSequences.compute({
|
||||
a: xs[0].elements[0],
|
||||
b: xs[i].elements[0],
|
||||
}, { substMatrix });
|
||||
|
||||
const lociA = StructureElement.Loci(xs[0].structure, [a]);
|
||||
const lociB = StructureElement.Loci(xs[i].structure, [b]);
|
||||
const n = OrderedSet.size(a.indices);
|
||||
|
||||
ret.push({
|
||||
...MinimizeRmsd.compute({
|
||||
a: getPositionTable(lociA, n),
|
||||
b: getPositionTable(lociB, n)
|
||||
}),
|
||||
alignmentScore: score
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getPositionTable(xs: StructureElement.Loci, n: number): MinimizeRmsd.Positions {
|
||||
const ret = MinimizeRmsd.Positions.empty(n);
|
||||
let o = 0;
|
||||
|
||||
@@ -32,7 +32,7 @@ export const PdbDownloadProvider = {
|
||||
encoding: PD.Select('bcif', [['cif', 'cif'], ['bcif', 'bcif']] as ['cif' | 'bcif', string][]),
|
||||
}, { label: 'RCSB PDB', isFlat: true }),
|
||||
'pdbe': PD.Group({
|
||||
variant: PD.Select('updated-bcif', [['updated-bcif', 'Updated (bcif)'], ['updated', 'Updated'], ['archival', 'Archival']] as ['updated' | 'archival', string][]),
|
||||
variant: PD.Select('updated-bcif', [['updated-bcif', 'Updated (bcif)'], ['updated', 'Updated'], ['archival', 'Archival']] as ['updated' | 'updtaed-bcif' | 'archival', string][]),
|
||||
}, { label: 'PDBe', isFlat: true }),
|
||||
};
|
||||
export type PdbDownloadProvider = keyof typeof PdbDownloadProvider;
|
||||
@@ -55,7 +55,10 @@ const DownloadStructure = StateAction.build({
|
||||
options
|
||||
}, { isFlat: true, label: 'PDB' }),
|
||||
'pdb-dev': PD.Group({
|
||||
id: PD.Text('PDBDEV_00000001', { label: 'PDBDev Id(s)', description: 'One or more comma/space separated ids.' }),
|
||||
provider: PD.Group({
|
||||
id: PD.Text('PDBDEV_00000001', { label: 'PDBDev Id(s)', description: 'One or more comma/space separated ids.' }),
|
||||
encoding: PD.Select('bcif', [['cif', 'cif'], ['bcif', 'bcif']] as ['cif' | 'bcif', string][]),
|
||||
}, { pivot: 'id' }),
|
||||
options
|
||||
}, { isFlat: true, label: 'PDBDEV' }),
|
||||
'bcif-static': PD.Group({
|
||||
@@ -104,13 +107,15 @@ const DownloadStructure = StateAction.build({
|
||||
asTrajectory = !!src.params.options.asTrajectory;
|
||||
break;
|
||||
case 'pdb-dev':
|
||||
downloadParams = getDownloadParams(src.params.id,
|
||||
downloadParams = getDownloadParams(src.params.provider.id,
|
||||
id => {
|
||||
const nId = id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`;
|
||||
return `https://pdb-dev.wwpdb.org/cif/${nId.toUpperCase()}.cif`;
|
||||
return src.params.provider.encoding === 'bcif'
|
||||
? `https://pdb-dev.wwpdb.org/bcif/${nId.toUpperCase()}.bcif`
|
||||
: `https://pdb-dev.wwpdb.org/cif/${nId.toUpperCase()}.cif`;
|
||||
},
|
||||
id => id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`,
|
||||
false
|
||||
src.params.provider.encoding === 'bcif'
|
||||
);
|
||||
asTrajectory = !!src.params.options.asTrajectory;
|
||||
break;
|
||||
|
||||
@@ -23,6 +23,15 @@ export const AnimateModelIndex = PluginStateAnimation.create({
|
||||
}, { options: [['palindrome', 'Palindrome'], ['loop', 'Loop'], ['once', 'Once']] }),
|
||||
maxFPS: PD.Numeric(15, { min: 1, max: 30, step: 1 })
|
||||
}),
|
||||
canApply(ctx) {
|
||||
const state = ctx.state.data;
|
||||
const models = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Model.ModelFromTrajectory));
|
||||
for (const m of models) {
|
||||
const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]);
|
||||
if (parent && parent.obj && parent.obj.data.length > 1) return { canApply: true };
|
||||
}
|
||||
return { canApply: false, reason: 'No trajectory to animate' };
|
||||
},
|
||||
initialState: () => ({} as { palindromeDirections?: { [id: string]: -1 | 1 | undefined } }),
|
||||
async apply(animState, t, ctx) {
|
||||
// limit fps
|
||||
@@ -48,6 +57,8 @@ export const AnimateModelIndex = PluginStateAnimation.create({
|
||||
const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]);
|
||||
if (!parent || !parent.obj) continue;
|
||||
const traj = parent.obj;
|
||||
if (traj.data.length <= 1) continue;
|
||||
|
||||
update.to(m).update(old => {
|
||||
const len = traj.data.length;
|
||||
if (len !== 1) {
|
||||
@@ -79,7 +90,9 @@ export const AnimateModelIndex = PluginStateAnimation.create({
|
||||
});
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } });
|
||||
if (!allSingles) {
|
||||
await PluginCommands.State.Update(ctx.plugin, { state, tree: update, options: { doNotLogTiming: true } });
|
||||
}
|
||||
|
||||
if (allSingles || (params.mode.name === 'once' && isEnd)) return { kind: 'finished' };
|
||||
if (params.mode.name === 'palindrome') return { kind: 'next', state: { palindromeDirections } };
|
||||
@@ -104,6 +117,12 @@ export const AnimateAssemblyUnwind = PluginStateAnimation.create({
|
||||
target: PD.Select(targets[0][0], targets)
|
||||
};
|
||||
},
|
||||
canApply(plugin) {
|
||||
const state = plugin.state.data;
|
||||
const root = StateTransform.RootRef;
|
||||
const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D, root));
|
||||
return { canApply: reprs.length > 0 };
|
||||
},
|
||||
initialState: () => ({ t: 0 }),
|
||||
setup(params, plugin) {
|
||||
const state = plugin.state.data;
|
||||
@@ -231,6 +250,9 @@ export const AnimateStateInterpolation = PluginStateAnimation.create({
|
||||
params: () => ({
|
||||
transtionDurationInMs: PD.Numeric(2000, { min: 100, max: 30000, step: 10 })
|
||||
}),
|
||||
canApply(plugin) {
|
||||
return { canApply: plugin.managers.snapshot.state.entries.size > 1 };
|
||||
},
|
||||
initialState: () => ({ }),
|
||||
async apply(animState, t, ctx) {
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@ export { PluginStateAnimation };
|
||||
interface PluginStateAnimation<P = any, S = any> {
|
||||
name: string,
|
||||
readonly display: { readonly name: string, readonly description?: string },
|
||||
|
||||
params(ctx: PluginContext): PD.For<P>,
|
||||
canApply?(ctx: PluginContext): { canApply: true } | { canApply: false, reason?: string },
|
||||
initialState(params: P, ctx: PluginContext): S,
|
||||
|
||||
// TODO: support state in setup/teardown?
|
||||
|
||||
@@ -15,6 +15,7 @@ import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateObjectRef, StateObjectSelector } from '../../../mol-state';
|
||||
import { StaticStructureComponentType } from '../../helpers/structure-component';
|
||||
import { StructureSelectionQueries as Q } from '../../helpers/structure-selection-query';
|
||||
import { PluginConfig } from '../../../mol-plugin/config';
|
||||
|
||||
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; }
|
||||
@@ -64,7 +65,9 @@ const auto = StructureRepresentationPresetProvider({
|
||||
apply(ref, params, plugin) {
|
||||
const structure = StateObjectRef.resolveAndCheck(plugin.state.data, ref)?.obj?.data;
|
||||
if (!structure) return { };
|
||||
const size = Structure.getSize(structure);
|
||||
|
||||
const thresholds = plugin.config.get(PluginConfig.Structure.SizeThresholds) || Structure.DefaultSizeThresholds;
|
||||
const size = Structure.getSize(structure, thresholds);
|
||||
|
||||
switch (size) {
|
||||
case Structure.Size.Gigantic:
|
||||
|
||||
@@ -74,7 +74,7 @@ function StructureSelectionQuery(label: string, expression: Expression, props: S
|
||||
ensureCustomProperties: props.ensureCustomProperties,
|
||||
async getSelection(plugin, runtime, structure) {
|
||||
const current = plugin.managers.structure.selection.getStructure(structure);
|
||||
const currentSelection = current ? StructureSelection.Singletons(structure, current) : StructureSelection.Empty(structure);
|
||||
const currentSelection = current ? StructureSelection.Sequence(structure, [current]) : StructureSelection.Empty(structure);
|
||||
if (props.ensureCustomProperties) {
|
||||
await props.ensureCustomProperties({ runtime, assetManager: plugin.managers.asset }, structure);
|
||||
}
|
||||
@@ -119,71 +119,132 @@ const trace = StructureSelectionQuery('Trace', MS.struct.modifier.union([
|
||||
])
|
||||
]), { category: StructureSelectionCategory.Structure });
|
||||
|
||||
// TODO maybe pre-calculate atom properties like backbone/sidechain
|
||||
const _proteinEntityTest = MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
MS.core.str.match([
|
||||
MS.re('(polypeptide|cyclic-pseudo-peptide)', 'i'),
|
||||
MS.ammp('entitySubtype')
|
||||
])
|
||||
]);
|
||||
|
||||
const _nucleiEntityTest = MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
MS.core.str.match([
|
||||
MS.re('(nucleotide|peptide nucleic acid)', 'i'),
|
||||
MS.ammp('entitySubtype')
|
||||
])
|
||||
]);
|
||||
|
||||
/**
|
||||
* this is to get non-polymer and peptide terminus components in polymer entities,
|
||||
* - non-polymer, e.g. PXZ in 4HIV or generally ACE
|
||||
* - carboxy terminus, e.g. FC0 in 4BP9, or ETA in 6DDE
|
||||
* - amino terminus, e.g. ARF in 3K4V, or 4MM in 3EGV
|
||||
*/
|
||||
const _nonPolymerResidueTest = MS.core.str.match([
|
||||
MS.re('non-polymer|(amino|carboxy) terminus|peptide-like', 'i'),
|
||||
MS.ammp('chemCompType')
|
||||
]);
|
||||
|
||||
// TODO maybe pre-calculate backbone atom properties
|
||||
const backbone = StructureSelectionQuery('Backbone', MS.struct.modifier.union([
|
||||
MS.struct.combinator.merge([
|
||||
MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
MS.core.str.match([
|
||||
MS.re('(polypeptide|cyclic-pseudo-peptide)', 'i'),
|
||||
MS.ammp('entitySubtype')
|
||||
])
|
||||
]),
|
||||
'entity-test': _proteinEntityTest,
|
||||
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
|
||||
'residue-test': MS.core.logic.not([_nonPolymerResidueTest]),
|
||||
'atom-test': MS.core.set.has([MS.set(...SetUtils.toArray(ProteinBackboneAtoms)), MS.ammp('label_atom_id')])
|
||||
})
|
||||
]),
|
||||
MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
MS.core.str.match([
|
||||
MS.re('(nucleotide|peptide nucleic acid)', 'i'),
|
||||
MS.ammp('entitySubtype')
|
||||
])
|
||||
]),
|
||||
'entity-test': _nucleiEntityTest,
|
||||
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
|
||||
'residue-test': MS.core.logic.not([_nonPolymerResidueTest]),
|
||||
'atom-test': MS.core.set.has([MS.set(...SetUtils.toArray(NucleicBackboneAtoms)), MS.ammp('label_atom_id')])
|
||||
})
|
||||
])
|
||||
])
|
||||
]), { category: StructureSelectionCategory.Structure });
|
||||
|
||||
const protein = StructureSelectionQuery('Protein', MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
MS.core.str.match([
|
||||
MS.re('(polypeptide|cyclic-pseudo-peptide)', 'i'),
|
||||
MS.ammp('entitySubtype')
|
||||
])
|
||||
// TODO maybe pre-calculate sidechain atom property
|
||||
const sidechain = StructureSelectionQuery('Sidechain', MS.struct.modifier.union([
|
||||
MS.struct.combinator.merge([
|
||||
MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': _proteinEntityTest,
|
||||
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
|
||||
'residue-test': MS.core.logic.not([_nonPolymerResidueTest]),
|
||||
'atom-test': MS.core.logic.or([
|
||||
MS.core.logic.not([
|
||||
MS.core.set.has([MS.set(...SetUtils.toArray(ProteinBackboneAtoms)), MS.ammp('label_atom_id')])
|
||||
])
|
||||
])
|
||||
})
|
||||
]),
|
||||
MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': _nucleiEntityTest,
|
||||
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
|
||||
'residue-test': MS.core.logic.not([_nonPolymerResidueTest]),
|
||||
'atom-test': MS.core.logic.or([
|
||||
MS.core.logic.not([
|
||||
MS.core.set.has([MS.set(...SetUtils.toArray(NucleicBackboneAtoms)), MS.ammp('label_atom_id')])
|
||||
])
|
||||
])
|
||||
})
|
||||
])
|
||||
})
|
||||
])
|
||||
]), { category: StructureSelectionCategory.Structure });
|
||||
|
||||
// TODO maybe pre-calculate sidechain atom property
|
||||
const sidechainWithTrace = StructureSelectionQuery('Sidechain with Trace', MS.struct.modifier.union([
|
||||
MS.struct.combinator.merge([
|
||||
MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': _proteinEntityTest,
|
||||
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
|
||||
'residue-test': MS.core.logic.not([_nonPolymerResidueTest]),
|
||||
'atom-test': MS.core.logic.or([
|
||||
MS.core.logic.not([
|
||||
MS.core.set.has([MS.set(...SetUtils.toArray(ProteinBackboneAtoms)), MS.ammp('label_atom_id')])
|
||||
]),
|
||||
MS.core.rel.eq([MS.ammp('label_atom_id'), 'CA']),
|
||||
MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp('auth_comp_id'), 'PRO']),
|
||||
MS.core.rel.eq([MS.ammp('label_atom_id'), 'N'])
|
||||
])
|
||||
])
|
||||
})
|
||||
]),
|
||||
MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': _nucleiEntityTest,
|
||||
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
|
||||
'residue-test': MS.core.logic.not([_nonPolymerResidueTest]),
|
||||
'atom-test': MS.core.logic.or([
|
||||
MS.core.logic.not([
|
||||
MS.core.set.has([MS.set(...SetUtils.toArray(NucleicBackboneAtoms)), MS.ammp('label_atom_id')])
|
||||
]),
|
||||
MS.core.rel.eq([MS.ammp('label_atom_id'), 'P'])
|
||||
])
|
||||
})
|
||||
])
|
||||
])
|
||||
]), { category: StructureSelectionCategory.Structure });
|
||||
|
||||
const protein = StructureSelectionQuery('Protein', MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({ 'entity-test': _proteinEntityTest })
|
||||
]), { category: StructureSelectionCategory.Type });
|
||||
|
||||
const nucleic = StructureSelectionQuery('Nucleic', MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
MS.core.str.match([
|
||||
MS.re('(nucleotide|peptide nucleic acid)', 'i'),
|
||||
MS.ammp('entitySubtype')
|
||||
])
|
||||
])
|
||||
})
|
||||
MS.struct.generator.atomGroups({ 'entity-test': _nucleiEntityTest })
|
||||
]), { category: StructureSelectionCategory.Type });
|
||||
|
||||
const helix = StructureSelectionQuery('Helix', MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
MS.core.str.match([
|
||||
MS.re('(polypeptide|cyclic-pseudo-peptide)', 'i'),
|
||||
MS.ammp('entitySubtype')
|
||||
])
|
||||
]),
|
||||
'entity-test': _proteinEntityTest,
|
||||
'residue-test': MS.core.flags.hasAny([
|
||||
MS.ammp('secondaryStructureFlags'),
|
||||
MS.core.type.bitflags([SecondaryStructureType.Flag.Helix])
|
||||
@@ -193,13 +254,7 @@ const helix = StructureSelectionQuery('Helix', MS.struct.modifier.union([
|
||||
|
||||
const beta = StructureSelectionQuery('Beta Strand/Sheet', MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
MS.core.str.match([
|
||||
MS.re('(polypeptide|cyclic-pseudo-peptide)', 'i'),
|
||||
MS.ammp('entitySubtype')
|
||||
])
|
||||
]),
|
||||
'entity-test': _proteinEntityTest,
|
||||
'residue-test': MS.core.flags.hasAny([
|
||||
MS.ammp('secondaryStructureFlags'),
|
||||
MS.core.type.bitflags([SecondaryStructureType.Flag.Beta])
|
||||
@@ -258,18 +313,11 @@ const ligand = StructureSelectionQuery('Ligand', MS.struct.modifier.union([
|
||||
])
|
||||
})
|
||||
]),
|
||||
// this is to get non-polymer and peptide terminus components in polymer entities,
|
||||
// - non-polymer, e.g. PXZ in 4HIV or generally ACE
|
||||
// - carboxy terminus, e.g. FC0 in 4BP9, or ETA in 6DDE
|
||||
// - amino terminus, e.g. ARF in 3K4V, or 4MM in 3EGV
|
||||
MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
|
||||
'residue-test': MS.core.str.match([
|
||||
MS.re('non-polymer|(amino|carboxy) terminus|peptide-like', 'i'),
|
||||
MS.ammp('chemCompType')
|
||||
])
|
||||
'residue-test': _nonPolymerResidueTest
|
||||
})
|
||||
])
|
||||
]),
|
||||
@@ -373,7 +421,7 @@ const complement = StructureSelectionQuery('Inverse / Complement of Selection',
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const bonded = StructureSelectionQuery('Residues Bonded to Selection', MS.struct.modifier.union([
|
||||
const covalentlyBonded = StructureSelectionQuery('Residues Covalently Bonded to Selection', MS.struct.modifier.union([
|
||||
MS.struct.modifier.includeConnected({
|
||||
0: MS.internal.generator.current(), 'layer-count': 1, 'as-whole-residues': true
|
||||
})
|
||||
@@ -383,6 +431,24 @@ const bonded = StructureSelectionQuery('Residues Bonded to Selection', MS.struct
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const covalentlyOrMetallicBonded = StructureSelectionQuery('Residues with Cov. or Metallic Bond to Selection', MS.struct.modifier.union([
|
||||
MS.struct.modifier.includeConnected({
|
||||
0: MS.internal.generator.current(),
|
||||
'layer-count': 1,
|
||||
'as-whole-residues': true,
|
||||
'bond-test': MS.core.flags.hasAny([
|
||||
MS.struct.bondProperty.flags(),
|
||||
MS.core.type.bitflags([
|
||||
BondType.Flag.Covalent | BondType.Flag.MetallicCoordination
|
||||
])
|
||||
])
|
||||
})
|
||||
]), {
|
||||
description: 'Select residues with covalent or metallic bond to current selection.',
|
||||
category: StructureSelectionCategory.Manipulate,
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const wholeResidues = StructureSelectionQuery('Whole Residues of Selection', MS.struct.modifier.union([
|
||||
MS.struct.modifier.wholeResidues({
|
||||
0: MS.internal.generator.current()
|
||||
@@ -532,6 +598,8 @@ export const StructureSelectionQueries = {
|
||||
polymer,
|
||||
trace,
|
||||
backbone,
|
||||
sidechain,
|
||||
sidechainWithTrace,
|
||||
protein,
|
||||
nucleic,
|
||||
helix,
|
||||
@@ -551,7 +619,8 @@ export const StructureSelectionQueries = {
|
||||
aromaticRing,
|
||||
surroundings,
|
||||
complement,
|
||||
bonded,
|
||||
covalentlyBonded,
|
||||
covalentlyOrMetallicBonded,
|
||||
wholeResidues,
|
||||
};
|
||||
|
||||
|
||||
@@ -204,9 +204,10 @@ namespace InteractivityManager {
|
||||
}
|
||||
|
||||
selectOnly(current: Representation.Loci, applyGranularity = true) {
|
||||
this.deselectAll();
|
||||
const normalized = this.normalizedLoci(current, applyGranularity);
|
||||
if (StructureElement.Loci.is(normalized.loci)) {
|
||||
// only deselect for the structure of the given loci
|
||||
this.deselect({ loci: Structure.toStructureElementLoci(normalized.loci.structure), repr: normalized.repr }, false);
|
||||
this.sel.modify('set', normalized.loci);
|
||||
}
|
||||
this.mark(normalized, MarkerAction.Select);
|
||||
|
||||
@@ -231,6 +231,8 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
|
||||
if (PluginStateSnapshotManager.isStateSnapshot(snapshot)) {
|
||||
return this.setStateSnapshot(snapshot);
|
||||
} else if (PluginStateSnapshotManager.isStateSnapshot(snapshot.data)) {
|
||||
return this.setStateSnapshot(snapshot.data);
|
||||
} else {
|
||||
this.plugin.state.setSnapshot(snapshot);
|
||||
}
|
||||
@@ -330,7 +332,7 @@ namespace PluginStateSnapshotManager {
|
||||
snapshot: PluginState.Snapshot
|
||||
}
|
||||
|
||||
export function Entry(snapshot: PluginState.Snapshot, params: {name?: string, description?: string }): Entry {
|
||||
export function Entry(snapshot: PluginState.Snapshot, params: { name?: string, description?: string }): Entry {
|
||||
return { timestamp: +new Date(), snapshot, ...params };
|
||||
}
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@ import { EmptyLoci, Loci } from '../../../mol-model/loci';
|
||||
import { Structure, StructureElement, StructureSelection } from '../../../mol-model/structure';
|
||||
import { Boundary } from '../../../mol-model/structure/structure/util/boundary';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateObject, StateObjectRef } from '../../../mol-state';
|
||||
import { StateObjectRef } from '../../../mol-state';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { structureElementStatsLabel } from '../../../mol-theme/label';
|
||||
import { arrayRemoveAtInPlace } from '../../../mol-util/array';
|
||||
import { StatefulPluginComponent } from '../../component';
|
||||
import { StructureSelectionQuery } from '../../helpers/structure-selection-query';
|
||||
import { PluginStateObject } from '../../objects';
|
||||
import { PluginStateObject as PSO } from '../../objects';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { StructureRef } from './hierarchy-state';
|
||||
|
||||
@@ -30,17 +30,23 @@ interface StructureSelectionManagerState {
|
||||
}
|
||||
|
||||
const boundaryHelper = new BoundaryHelper('98');
|
||||
const HISTORY_CAPACITY = 8;
|
||||
const HISTORY_CAPACITY = 24;
|
||||
|
||||
export type StructureSelectionModifier = 'add' | 'remove' | 'intersect' | 'set'
|
||||
|
||||
export class StructureSelectionManager extends StatefulPluginComponent<StructureSelectionManagerState> {
|
||||
readonly events = {
|
||||
changed: this.ev<undefined>(),
|
||||
additionsHistoryUpdated: this.ev<undefined>()
|
||||
additionsHistoryUpdated: this.ev<undefined>(),
|
||||
|
||||
loci: {
|
||||
add: this.ev<StructureElement.Loci>(),
|
||||
remove: this.ev<StructureElement.Loci>(),
|
||||
clear: this.ev<undefined>()
|
||||
}
|
||||
}
|
||||
|
||||
private referenceLoci: Loci | undefined
|
||||
private referenceLoci: StructureElement.Loci | undefined
|
||||
|
||||
get entries() { return this.state.entries; }
|
||||
get additionsHistory() { return this.state.additionsHistory; }
|
||||
@@ -51,7 +57,8 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
}
|
||||
|
||||
private getEntry(s: Structure) {
|
||||
const cell = this.plugin.helpers.substructureParent.get(s);
|
||||
// ignore decorators to get stable ref
|
||||
const cell = this.plugin.helpers.substructureParent.get(s, true);
|
||||
if (!cell) return;
|
||||
const ref = cell.transform.ref;
|
||||
if (!this.entries.has(ref)) {
|
||||
@@ -94,6 +101,7 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
entry.selection = StructureElement.Loci.union(entry.selection, loci);
|
||||
this.tryAddHistory(loci);
|
||||
this.referenceLoci = loci;
|
||||
this.events.loci.add.next(loci);
|
||||
return !StructureElement.Loci.areEqual(sel, entry.selection);
|
||||
}
|
||||
|
||||
@@ -107,6 +115,7 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
entry.selection = StructureElement.Loci.subtract(entry.selection, loci);
|
||||
// this.addHistory(loci);
|
||||
this.referenceLoci = loci;
|
||||
this.events.loci.remove.next(loci);
|
||||
return !StructureElement.Loci.areEqual(sel, entry.selection);
|
||||
}
|
||||
|
||||
@@ -136,26 +145,34 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
return !StructureElement.Loci.areEqual(sel, entry.selection);
|
||||
}
|
||||
|
||||
modifyHistory(entry: StructureSelectionHistoryEntry, action: 'remove' | 'up' | 'down', modulus?: number) {
|
||||
const idx = this.additionsHistory.indexOf(entry);
|
||||
modifyHistory(entry: StructureSelectionHistoryEntry, action: 'remove' | 'up' | 'down', modulus?: number, groupByStructure = false) {
|
||||
const history = this.additionsHistory;
|
||||
const idx = history.indexOf(entry);
|
||||
if (idx < 0) return;
|
||||
|
||||
let swapWith: number | undefined = void 0;
|
||||
|
||||
switch (action) {
|
||||
case 'remove': arrayRemoveAtInPlace(this.additionsHistory, idx); break;
|
||||
case 'remove': arrayRemoveAtInPlace(history, idx); break;
|
||||
case 'up': swapWith = idx - 1; break;
|
||||
case 'down': swapWith = idx + 1; break;
|
||||
}
|
||||
|
||||
if (swapWith !== void 0) {
|
||||
const mod = modulus ? Math.min(this.additionsHistory.length, modulus) : this.additionsHistory.length;
|
||||
swapWith = swapWith % mod;
|
||||
if (swapWith < 0) swapWith += mod;
|
||||
const mod = modulus ? Math.min(history.length, modulus) : history.length;
|
||||
while (true) {
|
||||
swapWith = swapWith % mod;
|
||||
if (swapWith < 0) swapWith += mod;
|
||||
|
||||
const t = this.additionsHistory[idx];
|
||||
this.additionsHistory[idx] = this.additionsHistory[swapWith];
|
||||
this.additionsHistory[swapWith] = t;
|
||||
if (!groupByStructure || history[idx].loci.structure === history[swapWith].loci.structure) {
|
||||
const t = history[idx];
|
||||
history[idx] = history[swapWith];
|
||||
history[swapWith] = t;
|
||||
break;
|
||||
} else {
|
||||
swapWith += action === 'up' ? -1 : +1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.events.additionsHistoryUpdated.next();
|
||||
@@ -190,33 +207,88 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
this.events.additionsHistoryUpdated.next();
|
||||
}
|
||||
|
||||
private onRemove(ref: string) {
|
||||
if (this.entries.has(ref)) {
|
||||
this.entries.delete(ref);
|
||||
// TODO: property update the latest loci
|
||||
private clearHistory() {
|
||||
if (this.state.additionsHistory.length !== 0) {
|
||||
this.state.additionsHistory = [];
|
||||
this.referenceLoci = undefined;
|
||||
this.events.additionsHistoryUpdated.next();
|
||||
}
|
||||
}
|
||||
|
||||
private onUpdate(ref: string, oldObj: StateObject | undefined, obj: StateObject) {
|
||||
if (!PluginStateObject.Molecule.Structure.is(obj)) return;
|
||||
private clearHistoryForStructure(structure: Structure) {
|
||||
const historyEntryToRemove: StructureSelectionHistoryEntry[] = [];
|
||||
for (const e of this.state.additionsHistory) {
|
||||
if (e.loci.structure.root === structure.root) {
|
||||
historyEntryToRemove.push(e);
|
||||
}
|
||||
}
|
||||
for (const e of historyEntryToRemove) {
|
||||
this.modifyHistory(e, 'remove');
|
||||
}
|
||||
if (historyEntryToRemove.length !== 0) {
|
||||
this.events.additionsHistoryUpdated.next();
|
||||
}
|
||||
}
|
||||
|
||||
private onRemove(ref: string, obj: PSO.Molecule.Structure | undefined) {
|
||||
if (this.entries.has(ref)) {
|
||||
if (!PluginStateObject.Molecule.Structure.is(oldObj) || oldObj === obj || oldObj.data === obj.data) return;
|
||||
this.entries.delete(ref);
|
||||
if (obj?.data) {
|
||||
this.clearHistoryForStructure(obj.data);
|
||||
}
|
||||
if (this.referenceLoci?.structure === obj?.data) {
|
||||
this.referenceLoci = undefined;
|
||||
}
|
||||
this.state.stats = void 0;
|
||||
this.events.changed.next();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: property update the latest loci & reference loci
|
||||
this.state.additionsHistory = [];
|
||||
this.referenceLoci = undefined;
|
||||
private onUpdate(ref: string, oldObj: PSO.Molecule.Structure | undefined, obj: PSO.Molecule.Structure) {
|
||||
|
||||
// remap the old selection to be related to the new object if possible.
|
||||
if (Structure.areUnitAndIndicesEqual(oldObj.data, obj.data)) {
|
||||
this.entries.set(ref, remapSelectionEntry(this.entries.get(ref)!, obj.data));
|
||||
return;
|
||||
// no change to structure
|
||||
if (oldObj === obj || oldObj?.data === obj.data) return;
|
||||
|
||||
// ignore decorators to get stable ref
|
||||
const cell = this.plugin.helpers.substructureParent.get(obj.data, true);
|
||||
if (!cell) return;
|
||||
|
||||
ref = cell.transform.ref;
|
||||
if (!this.entries.has(ref)) return;
|
||||
|
||||
// use structure from last decorator as reference
|
||||
const structure = this.plugin.helpers.substructureParent.get(obj.data)?.obj?.data;
|
||||
if (!structure) return;
|
||||
|
||||
// oldObj is not defined for inserts (e.g. TransformStructureConformation)
|
||||
if (!oldObj || Structure.areUnitAndIndicesEqual(oldObj.data, obj.data)) {
|
||||
this.entries.set(ref, remapSelectionEntry(this.entries.get(ref)!, structure));
|
||||
|
||||
// remap referenceLoci & prevHighlight if needed and possible
|
||||
if (this.referenceLoci?.structure.root === structure.root) {
|
||||
this.referenceLoci = StructureElement.Loci.remap(this.referenceLoci, structure);
|
||||
}
|
||||
|
||||
// clear the selection
|
||||
this.entries.set(ref, new SelectionEntry(StructureElement.Loci(obj.data, [])));
|
||||
// remap history locis if needed and possible
|
||||
let changedHistory = false;
|
||||
for (const e of this.state.additionsHistory) {
|
||||
if (e.loci.structure.root === structure.root) {
|
||||
e.loci = StructureElement.Loci.remap(e.loci, structure);
|
||||
changedHistory = true;
|
||||
}
|
||||
}
|
||||
if (changedHistory) this.events.additionsHistoryUpdated.next();
|
||||
} else {
|
||||
// clear the selection for ref
|
||||
this.entries.set(ref, new SelectionEntry(StructureElement.Loci(structure, [])));
|
||||
|
||||
if (this.referenceLoci?.structure.root === structure.root) {
|
||||
this.referenceLoci = undefined;
|
||||
}
|
||||
|
||||
this.clearHistoryForStructure(structure);
|
||||
|
||||
this.state.stats = void 0;
|
||||
this.events.changed.next();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +306,8 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
this.referenceLoci = undefined;
|
||||
this.state.stats = void 0;
|
||||
this.events.changed.next();
|
||||
this.events.loci.clear.next();
|
||||
this.clearHistory();
|
||||
return selections;
|
||||
}
|
||||
|
||||
@@ -276,7 +350,7 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
if (!xs) return;
|
||||
|
||||
const ref = this.referenceLoci;
|
||||
if (!ref || !StructureElement.Loci.is(ref) || ref.structure.root !== loci.structure.root) return;
|
||||
if (!ref || !StructureElement.Loci.is(ref) || ref.structure !== loci.structure) return;
|
||||
|
||||
let e: StructureElement.Loci['elements'][0] | undefined;
|
||||
for (const _e of ref.elements) {
|
||||
@@ -289,26 +363,7 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
|
||||
if (xs.unit !== e.unit) return;
|
||||
|
||||
return getElementRange(loci.structure.root, e, xs);
|
||||
}
|
||||
|
||||
private prevHighlight: StructureElement.Loci | undefined = void 0;
|
||||
|
||||
accumulateInteractiveHighlight(loci: Loci) {
|
||||
if (StructureElement.Loci.is(loci)) {
|
||||
if (this.prevHighlight) {
|
||||
this.prevHighlight = StructureElement.Loci.union(this.prevHighlight, loci);
|
||||
} else {
|
||||
this.prevHighlight = loci;
|
||||
}
|
||||
}
|
||||
return this.prevHighlight;
|
||||
}
|
||||
|
||||
clearInteractiveHighlight() {
|
||||
const ret = this.prevHighlight;
|
||||
this.prevHighlight = void 0;
|
||||
return ret || EmptyLoci;
|
||||
return getElementRange(loci.structure, e, xs);
|
||||
}
|
||||
|
||||
/** Count of all selected elements */
|
||||
@@ -411,11 +466,11 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
}));
|
||||
}
|
||||
|
||||
fromSelections(ref: StateObjectRef<PluginStateObject.Molecule.Structure.Selections>) {
|
||||
fromSelections(ref: StateObjectRef<PSO.Molecule.Structure.Selections>) {
|
||||
const cell = StateObjectRef.resolveAndCheck(this.plugin.state.data, ref);
|
||||
if (!cell || !cell.obj) return;
|
||||
|
||||
if (!PluginStateObject.Molecule.Structure.Selections.is(cell.obj)) {
|
||||
if (!PSO.Molecule.Structure.Selections.is(cell.obj)) {
|
||||
console.warn('fromSelections applied to wrong object type.', cell.obj);
|
||||
return;
|
||||
}
|
||||
@@ -429,8 +484,9 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
constructor(private plugin: PluginContext) {
|
||||
super({ entries: new Map(), additionsHistory: [], stats: SelectionStats() });
|
||||
|
||||
plugin.state.data.events.object.removed.subscribe(e => this.onRemove(e.ref));
|
||||
plugin.state.data.events.object.updated.subscribe(e => this.onUpdate(e.ref, e.oldObj, e.obj));
|
||||
// listen to events from substructureParent helper to ensure it is updated
|
||||
plugin.helpers.substructureParent.events.removed.subscribe(e => this.onRemove(e.ref, e.obj));
|
||||
plugin.helpers.substructureParent.events.updated.subscribe(e => this.onUpdate(e.ref, e.oldObj, e.obj));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ const VolumeFromCcp4 = PluginStateTransform.BuiltIn({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Create volume from CCP4/MRC/MAP', async ctx => {
|
||||
const volume = await volumeFromCcp4(a.data, { ...params, label: a.data.name || a.label }).runInContext(ctx);
|
||||
const props = { label: volume.label || 'Volume', description: 'Volume' };
|
||||
const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.NX}\u00D7${a.data.header.NX}\u00D7${a.data.header.NX}` };
|
||||
return new SO.Volume.Data(volume, props);
|
||||
});
|
||||
}
|
||||
@@ -63,7 +63,7 @@ const VolumeFromDsn6 = PluginStateTransform.BuiltIn({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Create volume from DSN6/BRIX', async ctx => {
|
||||
const volume = await volumeFromDsn6(a.data, { ...params, label: a.data.name || a.label }).runInContext(ctx);
|
||||
const props = { label: volume.label || 'Volume', description: 'Volume' };
|
||||
const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.xExtent}\u00D7${a.data.header.yExtent}\u00D7${a.data.header.zExtent}` };
|
||||
return new SO.Volume.Data(volume, props);
|
||||
});
|
||||
}
|
||||
@@ -85,7 +85,7 @@ const VolumeFromCube = PluginStateTransform.BuiltIn({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Create volume from Cube', async ctx => {
|
||||
const volume = await volumeFromCube(a.data, { ...params, label: a.data.name || a.label }).runInContext(ctx);
|
||||
const props = { label: volume.label || 'Volume', description: 'Volume' };
|
||||
const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.dim[0]}\u00D7${a.data.header.dim[1]}\u00D7${a.data.header.dim[2]}` };
|
||||
return new SO.Volume.Data(volume, props);
|
||||
});
|
||||
}
|
||||
@@ -102,8 +102,7 @@ const VolumeFromDx = PluginStateTransform.BuiltIn({
|
||||
return Task.create('Parse DX', async ctx => {
|
||||
console.log(a);
|
||||
const volume = await volumeFromDx(a.data, { label: a.data.name || a.label }).runInContext(ctx);
|
||||
console.log(volume);
|
||||
const props = { label: volume.label || 'Volume', description: 'Volume' };
|
||||
const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.dim[0]}\u00D7${a.data.header.dim[1]}\u00D7${a.data.header.dim[2]}` };
|
||||
return new SO.Volume.Data(volume, props);
|
||||
});
|
||||
}
|
||||
@@ -135,7 +134,8 @@ const VolumeFromDensityServerCif = PluginStateTransform.BuiltIn({
|
||||
if (!block) throw new Error(`Data block '${[header]}' not found.`);
|
||||
const densityServerCif = CIF.schema.densityServer(block);
|
||||
const volume = await volumeFromDensityServerData(densityServerCif).runInContext(ctx);
|
||||
const props = { label: densityServerCif.volume_data_3d_info.name.value(0), description: `${densityServerCif.volume_data_3d_info.name.value(0)}` };
|
||||
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}` };
|
||||
return new SO.Volume.Data(volume, props);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import { StructureSelectionActionsControls } from './structure/selection';
|
||||
import { StructureSourceControls } from './structure/source';
|
||||
import { VolumeStreamingControls, VolumeSourceControls } from './structure/volume';
|
||||
import { PluginConfig } from '../mol-plugin/config';
|
||||
import { StructureSuperpositionControls } from './structure/superposition';
|
||||
|
||||
export class TrajectoryViewportControls extends PluginUIComponent<{}, { show: boolean, label: string }> {
|
||||
state = { show: false, label: '' }
|
||||
@@ -296,6 +297,7 @@ export class DefaultStructureTools extends PluginUIComponent {
|
||||
|
||||
<StructureSourceControls />
|
||||
<StructureMeasurementsControls />
|
||||
<StructureSuperpositionControls />
|
||||
<StructureComponentControls />
|
||||
<VolumeStreamingControls />
|
||||
<VolumeSourceControls />
|
||||
|
||||
@@ -34,10 +34,22 @@ export class CombinedColorControl extends React.PureComponent<ParamProps<PD.Colo
|
||||
}
|
||||
}
|
||||
|
||||
onChangeText = (value: Color) => {
|
||||
if (value !== this.props.value) {
|
||||
this.update(value);
|
||||
}
|
||||
onR = (v: number) => {
|
||||
const [, g, b] = Color.toRgb(this.props.value);
|
||||
const value = Color.fromRgb(v, g, b);
|
||||
if (value !== this.props.value) this.update(value);
|
||||
}
|
||||
|
||||
onG = (v: number) => {
|
||||
const [r, , b] = Color.toRgb(this.props.value);
|
||||
const value = Color.fromRgb(r, v, b);
|
||||
if (value !== this.props.value) this.update(value);
|
||||
}
|
||||
|
||||
onB = (v: number) => {
|
||||
const [r, g, ] = Color.toRgb(this.props.value);
|
||||
const value = Color.fromRgb(r, g, v);
|
||||
if (value !== this.props.value) this.update(value);
|
||||
}
|
||||
|
||||
swatch() {
|
||||
@@ -49,44 +61,23 @@ export class CombinedColorControl extends React.PureComponent<ParamProps<PD.Colo
|
||||
|
||||
render() {
|
||||
const label = this.props.param.label || camelCaseToWords(this.props.name);
|
||||
const [r, g, b] = Color.toRgb(this.props.value);
|
||||
return <>
|
||||
<ControlRow title={this.props.param.description}
|
||||
label={label}
|
||||
control={<Button onClick={this.toggleExpanded} inline className='msp-combined-color-button' style={{ background: Color.toStyle(this.props.value) }} />} />
|
||||
{this.state.isExpanded && <div className='msp-control-offset'>
|
||||
{this.swatch()}
|
||||
<ControlRow label='RGB'
|
||||
control={<TextInput onChange={this.onChangeText} value={this.props.value}
|
||||
fromValue={formatColorRGB} toValue={getColorFromString} isValid={isValidColorString}
|
||||
className='msp-form-control' onEnter={this.props.onEnter} blurOnEnter={true} blurOnEscape={true}
|
||||
placeholder='e.g. 127 127 127' delayMs={250} />} />
|
||||
<ControlRow label='RGB' control={<div style={{ display: 'flex', textAlignLast: 'center' }}>
|
||||
<TextInput onChange={this.onR} numeric value={r} delayMs={250} style={{ order: 1, flex: '1 1 auto', minWidth: 0 }} className='msp-form-control' onEnter={this.props.onEnter} blurOnEnter={true} blurOnEscape={true} />
|
||||
<TextInput onChange={this.onG} numeric value={g} delayMs={250} style={{ order: 2, flex: '1 1 auto', minWidth: 0 }} className='msp-form-control' onEnter={this.props.onEnter} blurOnEnter={true} blurOnEscape={true} />
|
||||
<TextInput onChange={this.onB} numeric value={b} delayMs={250} style={{ order: 3, flex: '1 1 auto', minWidth: 0 }} className='msp-form-control' onEnter={this.props.onEnter} blurOnEnter={true} blurOnEscape={true} />
|
||||
</div>}/>
|
||||
</div>}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
function formatColorRGB(c: Color) {
|
||||
const [r, g, b] = Color.toRgb(c);
|
||||
return `${r} ${g} ${b}`;
|
||||
}
|
||||
|
||||
function getColorFromString(s: string) {
|
||||
const cs = s.split(/\s+/g);
|
||||
return Color.fromRgb(+cs[0], +cs[1], +cs[2]);
|
||||
}
|
||||
|
||||
function isValidColorString(s: string) {
|
||||
const cs = s.split(/\s+/g);
|
||||
if (cs.length !== 3 && !(cs.length === 4 && cs[3] === '')) return false;
|
||||
for (const c of cs) {
|
||||
if (c === '') continue;
|
||||
const n = +c;
|
||||
if ('' + n !== c) return false;
|
||||
if (n < 0 || n > 255) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
let _colors: React.ReactFragment | undefined = void 0;
|
||||
export function ColorOptions() {
|
||||
if (_colors) return _colors;
|
||||
|
||||
@@ -24,7 +24,8 @@ export class ControlGroup extends React.Component<{
|
||||
headerLeftMargin?: string,
|
||||
onHeaderClick?: () => void,
|
||||
noTopMargin?: boolean,
|
||||
childrenClassName?: string
|
||||
childrenClassName?: string,
|
||||
maxHeight?: string
|
||||
}, { isExpanded: boolean }> {
|
||||
state = { isExpanded: !!this.props.initialExpanded }
|
||||
|
||||
@@ -49,7 +50,7 @@ export class ControlGroup extends React.Component<{
|
||||
<b>{this.props.header}</b>
|
||||
</Button>
|
||||
</div>
|
||||
{this.state.isExpanded && <div className={groupClassName} style={{ display: this.state.isExpanded ? 'block' : 'none' }}>
|
||||
{this.state.isExpanded && <div className={groupClassName} style={{ display: this.state.isExpanded ? 'block' : 'none', maxHeight: this.props.maxHeight, overflow: 'hidden', overflowY: 'auto' }}>
|
||||
{this.props.children}
|
||||
</div>}
|
||||
</div>;
|
||||
|
||||
@@ -21,13 +21,14 @@ export function Icon(props: {
|
||||
|
||||
//
|
||||
|
||||
export function Union() { return _union; }
|
||||
export function Subtract() { return _subtract; }
|
||||
export function Intersect() { return _intersect; }
|
||||
export function UnionSvg() { return _union; }
|
||||
export function SubtractSvg() { return _subtract; }
|
||||
export function IntersectSvg() { return _intersect; }
|
||||
export function SetSvg() { return _set; }
|
||||
export function MoleculeSvg() { return _molecule; }
|
||||
export function RulerSvg() { return _ruler; }
|
||||
export function CubeSvg() { return _cube; }
|
||||
export function CursorSvg() { return _cursor; }
|
||||
|
||||
const circleLeft = <circle r="6px" id="circle-left" cy="12px" cx="8px" strokeWidth="1" />;
|
||||
const circleRight = <circle r="6px" id="circle-right" cy="12px" cx="16px" strokeWidth="1" />;
|
||||
@@ -87,64 +88,25 @@ const _set = <svg width="24px" height="24px" viewBox="0 0 24 24">
|
||||
|
||||
const _molecule = <svg width="17px" height="17px" viewBox="0 0 299.463 299.463">
|
||||
<g>
|
||||
<path d="M256.851,173.832v-48.201c22.916-4.918,34.151-30.668,22.556-50.771c-11.547-20.004-39.486-23.251-55.242-5.844
|
||||
l-41.746-24.106C189.618,22.603,172.861,0,149.734,0c-23.132,0-39.881,22.609-32.685,44.911L75.305,69.016
|
||||
C59.522,51.586,31.597,54.88,20.061,74.863c-11.63,20.163-0.298,45.862,22.557,50.769v48.2
|
||||
c-22.821,4.898-34.195,30.591-22.556,50.771c11.529,19.972,39.454,23.285,55.242,5.845l41.746,24.106
|
||||
c-7.199,22.308,9.559,44.911,32.685,44.911c23.132,0,39.88-22.609,32.685-44.911l41.745-24.106
|
||||
c15.817,17.469,43.73,14.099,55.242-5.844c0,0,0-0.001,0.001-0.002c4.587-7.953,5.805-17.213,3.431-26.076
|
||||
C279.392,185.657,269.129,176.461,256.851,173.832z M249.62,72.088c20.568,0,27.428,27.191,10.008,37.239
|
||||
c-0.003,0.002-0.006,0.003-0.009,0.005c-10.04,5.81-22.85,1.762-27.877-8.475C225.206,87.548,234.938,72.088,249.62,72.088z
|
||||
M149.734,14.4c11.005,0,19.958,8.954,19.958,19.959c0,11.127-9.077,19.958-19.958,19.958c-10.95,0-19.958-8.9-19.958-19.958
|
||||
C129.776,23.354,138.729,14.4,149.734,14.4z M39.84,109.328c-17.451-10.067-10.534-37.24,10.01-37.24
|
||||
c15.311,0,24.922,16.653,17.251,29.942C61.681,111.397,49.517,114.925,39.84,109.328z M59.802,224.702
|
||||
c-9.535,5.503-21.768,2.229-27.268-7.298c-7.639-13.242,1.887-29.945,17.236-29.945c0.013,0,0.027,0,0.04,0
|
||||
C70.07,187.48,77.49,214.469,59.802,224.702z M149.734,285.062c-11.005,0-19.958-8.954-19.958-19.958
|
||||
c0-11.127,9.077-19.958,19.958-19.958c10.954,0,19.958,8.903,19.958,19.958C169.693,276.109,160.74,285.062,149.734,285.062z
|
||||
M216.953,217.982l-41.727,24.095c-13.778-15.22-37.459-14.94-50.983,0l-41.728-24.096c6.196-19.289-5.541-39.835-25.498-44.149
|
||||
V125.63c19.752-4.268,31.762-24.65,25.498-44.149l41.727-24.095c13.629,15.055,37.32,15.093,50.983,0l41.728,24.096
|
||||
c-6.196,19.29,5.534,39.835,25.498,44.149v48.202C222.61,178.123,210.721,198.581,216.953,217.982z M266.935,217.404
|
||||
c-5.501,9.528-17.732,12.802-27.261,7.302c-17.682-10.23-10.301-37.247,10.032-37.247
|
||||
C264.984,187.459,274.602,204.112,266.935,217.404z"/>
|
||||
<path d="M256.851,173.832v-48.201c22.916-4.918,34.151-30.668,22.556-50.771c-11.547-20.004-39.486-23.251-55.242-5.844 l-41.746-24.106C189.618,22.603,172.861,0,149.734,0c-23.132,0-39.881,22.609-32.685,44.911L75.305,69.016 C59.522,51.586,31.597,54.88,20.061,74.863c-11.63,20.163-0.298,45.862,22.557,50.769v48.2 c-22.821,4.898-34.195,30.591-22.556,50.771c11.529,19.972,39.454,23.285,55.242,5.845l41.746,24.106 c-7.199,22.308,9.559,44.911,32.685,44.911c23.132,0,39.88-22.609,32.685-44.911l41.745-24.106 c15.817,17.469,43.73,14.099,55.242-5.844c0,0,0-0.001,0.001-0.002c4.587-7.953,5.805-17.213,3.431-26.076 C279.392,185.657,269.129,176.461,256.851,173.832z M249.62,72.088c20.568,0,27.428,27.191,10.008,37.239 c-0.003,0.002-0.006,0.003-0.009,0.005c-10.04,5.81-22.85,1.762-27.877-8.475C225.206,87.548,234.938,72.088,249.62,72.088z M149.734,14.4c11.005,0,19.958,8.954,19.958,19.959c0,11.127-9.077,19.958-19.958,19.958c-10.95,0-19.958-8.9-19.958-19.958 C129.776,23.354,138.729,14.4,149.734,14.4z M39.84,109.328c-17.451-10.067-10.534-37.24,10.01-37.24 c15.311,0,24.922,16.653,17.251,29.942C61.681,111.397,49.517,114.925,39.84,109.328z M59.802,224.702 c-9.535,5.503-21.768,2.229-27.268-7.298c-7.639-13.242,1.887-29.945,17.236-29.945c0.013,0,0.027,0,0.04,0 C70.07,187.48,77.49,214.469,59.802,224.702z M149.734,285.062c-11.005,0-19.958-8.954-19.958-19.958 c0-11.127,9.077-19.958,19.958-19.958c10.954,0,19.958,8.903,19.958,19.958C169.693,276.109,160.74,285.062,149.734,285.062z M216.953,217.982l-41.727,24.095c-13.778-15.22-37.459-14.94-50.983,0l-41.728-24.096c6.196-19.289-5.541-39.835-25.498-44.149 V125.63c19.752-4.268,31.762-24.65,25.498-44.149l41.727-24.095c13.629,15.055,37.32,15.093,50.983,0l41.728,24.096 c-6.196,19.29,5.534,39.835,25.498,44.149v48.202C222.61,178.123,210.721,198.581,216.953,217.982z M266.935,217.404 c-5.501,9.528-17.732,12.802-27.261,7.302c-17.682-10.23-10.301-37.247,10.032-37.247 C264.984,187.459,274.602,204.112,266.935,217.404z"/>
|
||||
</g>
|
||||
</svg>;
|
||||
|
||||
const _ruler = <svg viewBox="0 0 508.073 508.073" width="17px" height="17px">
|
||||
<g>
|
||||
<path d="M470.459,378.925c-0.7-2.1-1.9-4-3.4-5.5l-113.9-113.9l149.8-149.8c10-10,2.6-17.3,0-19.9l-85.7-85.7
|
||||
c-5.5-5.5-14.4-5.5-19.9,0l-149.8,149.8l-134.7-134.7c-25.5-25.5-67.8-25.6-93.4,0c-25.8,25.8-25.8,67.7,0,93.5l134.6,134.7
|
||||
l-149.9,149.9c-5.5,5.5-5.5,14.4,0,19.9l85.6,85.7c2.6,2.6,10,10,19.9,0l150-149.9l113.9,113.9c1.5,1.5,3.4,2.7,5.5,3.4
|
||||
l110.4,36.9c6,2,10.7,0.3,14.4-3.4c3.8-3.8,5.1-9.4,3.4-14.4L470.459,378.925z M276.159,165.225l29.9,29.9
|
||||
c5.5,5.5,14.4,5.5,19.9,0c5-5,5.5-14.4,0-19.9l-29.9-29.9l23.7-23.7l13,13c5.5,5.5,14.4,5.5,19.9,0c5.2-5.2,6.2-13.7,0-19.9
|
||||
l-13-13l23.7-23.7l29.4,29.4c5.5,5.5,14.4,5.5,19.9,0c5.6-5.6,5.5-14.4,0-19.9l-29.4-29.5l24-24l65.8,65.7l-139.8,139.9
|
||||
l-65.8-65.8L276.159,165.225z M39.359,92.825c-14.8-14.8-14.8-38.8,0-53.6c14.1-14.1,38.8-14.7,53.6,0l15.5,15.5l-53.6,53.6
|
||||
L39.359,92.825z M99.759,473.025l-65.7-65.7l24-24l13.2,13.2c5.5,5.5,14.4,5.6,19.9,0c5.5-5.5,5.5-14.4,0-19.9l-13.1-13.3
|
||||
l23.7-23.7l29.6,29.6c6.2,6.2,14.7,5.2,19.9,0c5.5-5.5,5.5-14.4,0-19.9l-29.6-29.6l23.7-23.7l13.2,13.2c5.5,5.5,14.9,5,19.9,0
|
||||
c5.5-5.5,5.5-14.4,0-19.9l-13.2-13.2l8.8-8.8l65.8,65.7L99.759,473.025z M74.759,128.225l53.6-53.6l308.7,308.7l-53.6,53.6
|
||||
L74.759,128.225z M409.659,450.725l41.3-41.3l20.7,61.9L409.659,450.725z"/>
|
||||
<path d="M470.459,378.925c-0.7-2.1-1.9-4-3.4-5.5l-113.9-113.9l149.8-149.8c10-10,2.6-17.3,0-19.9l-85.7-85.7 c-5.5-5.5-14.4-5.5-19.9,0l-149.8,149.8l-134.7-134.7c-25.5-25.5-67.8-25.6-93.4,0c-25.8,25.8-25.8,67.7,0,93.5l134.6,134.7 l-149.9,149.9c-5.5,5.5-5.5,14.4,0,19.9l85.6,85.7c2.6,2.6,10,10,19.9,0l150-149.9l113.9,113.9c1.5,1.5,3.4,2.7,5.5,3.4 l110.4,36.9c6,2,10.7,0.3,14.4-3.4c3.8-3.8,5.1-9.4,3.4-14.4L470.459,378.925z M276.159,165.225l29.9,29.9 c5.5,5.5,14.4,5.5,19.9,0c5-5,5.5-14.4,0-19.9l-29.9-29.9l23.7-23.7l13,13c5.5,5.5,14.4,5.5,19.9,0c5.2-5.2,6.2-13.7,0-19.9 l-13-13l23.7-23.7l29.4,29.4c5.5,5.5,14.4,5.5,19.9,0c5.6-5.6,5.5-14.4,0-19.9l-29.4-29.5l24-24l65.8,65.7l-139.8,139.9 l-65.8-65.8L276.159,165.225z M39.359,92.825c-14.8-14.8-14.8-38.8,0-53.6c14.1-14.1,38.8-14.7,53.6,0l15.5,15.5l-53.6,53.6 L39.359,92.825z M99.759,473.025l-65.7-65.7l24-24l13.2,13.2c5.5,5.5,14.4,5.6,19.9,0c5.5-5.5,5.5-14.4,0-19.9l-13.1-13.3 l23.7-23.7l29.6,29.6c6.2,6.2,14.7,5.2,19.9,0c5.5-5.5,5.5-14.4,0-19.9l-29.6-29.6l23.7-23.7l13.2,13.2c5.5,5.5,14.9,5,19.9,0 c5.5-5.5,5.5-14.4,0-19.9l-13.2-13.2l8.8-8.8l65.8,65.7L99.759,473.025z M74.759,128.225l53.6-53.6l308.7,308.7l-53.6,53.6 L74.759,128.225z M409.659,450.725l41.3-41.3l20.7,61.9L409.659,450.725z"/>
|
||||
</g>
|
||||
</svg>;
|
||||
|
||||
const _cube = <svg viewBox="0 0 270.06 270.06" width="17px" height="17px">
|
||||
<g>
|
||||
<path d="M264.898,0.007
|
||||
c-0.181,0.006-0.362,0.023-0.541,0.049H84.996c-1.326,0-2.598,0.527-3.535,1.465l-80,80C0.525,82.459-0.001,83.73,0,85.056v180
|
||||
c0,2.761,2.239,5,5,5h180c1.326,0,2.598-0.527,3.535-1.465l80-80c0.938-0.938,1.465-2.209,1.465-3.535V5.819
|
||||
c0.14-0.893,0.035-1.807-0.303-2.645c-0.016-0.045-0.033-0.09-0.051-0.135c-0.808-1.89-2.69-3.093-4.744-3.033L264.898,0.007z
|
||||
M87.066,10.056h165.859l-70,70H17.066L87.066,10.056z M259.996,17.126v165.859l-70,70V87.126L259.996,17.126L259.996,17.126z
|
||||
M84.92,19.985c-2.759,0.042-4.963,2.311-4.924,5.07v40c-0.039,2.761,2.168,5.032,4.929,5.071s5.032-2.168,5.071-4.929
|
||||
c0.001-0.047,0.001-0.094,0-0.141v-40c0.039-2.761-2.168-5.031-4.93-5.07C85.018,19.984,84.969,19.984,84.92,19.985z M9.996,90.056
|
||||
h170v170h-170V90.056L9.996,90.056z M84.92,99.985c-2.759,0.042-4.963,2.311-4.924,5.07v30c-0.039,2.761,2.168,5.032,4.929,5.071
|
||||
s5.032-2.168,5.071-4.929c0.001-0.047,0.001-0.094,0-0.141v-30c0.039-2.761-2.168-5.031-4.93-5.07
|
||||
C85.018,99.984,84.969,99.984,84.92,99.985z M84.92,159.985c-2.759,0.042-4.963,2.311-4.924,5.07v17.93L61.461,201.52
|
||||
c-1.992,1.913-2.057,5.078-0.144,7.07c1.913,1.992,5.078,2.057,7.07,0.144c0.049-0.047,0.097-0.095,0.144-0.144l18.535-18.535h17.93
|
||||
c2.761,0.039,5.032-2.168,5.071-4.929s-2.168-5.032-4.929-5.071c-0.047-0.001-0.094-0.001-0.141,0h-15v-15
|
||||
c0.039-2.761-2.168-5.031-4.93-5.07C85.018,159.984,84.969,159.984,84.92,159.985z M134.996,180.055
|
||||
c-2.761-0.039-5.032,2.168-5.071,4.929c-0.039,2.761,2.168,5.032,4.929,5.071c0.047,0.001,0.094,0.001,0.141,0h30
|
||||
c2.761,0.039,5.032-2.168,5.071-4.929c0.039-2.761-2.168-5.032-4.929-5.071c-0.047-0.001-0.094-0.001-0.141,0H134.996z
|
||||
M204.996,180.055c-2.761-0.039-5.032,2.168-5.071,4.929c-0.039,2.761,2.168,5.032,4.929,5.071c0.047,0.001,0.094,0.001,0.141,0h40
|
||||
c2.761,0.039,5.032-2.168,5.071-4.929s-2.168-5.032-4.929-5.071c-0.047-0.001-0.094-0.001-0.141,0H204.996z M44.898,220.007
|
||||
c-1.299,0.039-2.532,0.582-3.438,1.514l-20,20c-1.992,1.913-2.057,5.078-0.144,7.07c1.913,1.992,5.078,2.057,7.07,0.144
|
||||
c0.049-0.047,0.097-0.095,0.144-0.144l20-20c1.98-1.925,2.025-5.091,0.1-7.071C47.654,220.514,46.3,219.965,44.898,220.007z"/>
|
||||
<path d="M264.898,0.007 c-0.181,0.006-0.362,0.023-0.541,0.049H84.996c-1.326,0-2.598,0.527-3.535,1.465l-80,80C0.525,82.459-0.001,83.73,0,85.056v180 c0,2.761,2.239,5,5,5h180c1.326,0,2.598-0.527,3.535-1.465l80-80c0.938-0.938,1.465-2.209,1.465-3.535V5.819 c0.14-0.893,0.035-1.807-0.303-2.645c-0.016-0.045-0.033-0.09-0.051-0.135c-0.808-1.89-2.69-3.093-4.744-3.033L264.898,0.007z M87.066,10.056h165.859l-70,70H17.066L87.066,10.056z M259.996,17.126v165.859l-70,70V87.126L259.996,17.126L259.996,17.126z M84.92,19.985c-2.759,0.042-4.963,2.311-4.924,5.07v40c-0.039,2.761,2.168,5.032,4.929,5.071s5.032-2.168,5.071-4.929 c0.001-0.047,0.001-0.094,0-0.141v-40c0.039-2.761-2.168-5.031-4.93-5.07C85.018,19.984,84.969,19.984,84.92,19.985z M9.996,90.056 h170v170h-170V90.056L9.996,90.056z M84.92,99.985c-2.759,0.042-4.963,2.311-4.924,5.07v30c-0.039,2.761,2.168,5.032,4.929,5.071 s5.032-2.168,5.071-4.929c0.001-0.047,0.001-0.094,0-0.141v-30c0.039-2.761-2.168-5.031-4.93-5.07 C85.018,99.984,84.969,99.984,84.92,99.985z M84.92,159.985c-2.759,0.042-4.963,2.311-4.924,5.07v17.93L61.461,201.52 c-1.992,1.913-2.057,5.078-0.144,7.07c1.913,1.992,5.078,2.057,7.07,0.144c0.049-0.047,0.097-0.095,0.144-0.144l18.535-18.535h17.93 c2.761,0.039,5.032-2.168,5.071-4.929s-2.168-5.032-4.929-5.071c-0.047-0.001-0.094-0.001-0.141,0h-15v-15 c0.039-2.761-2.168-5.031-4.93-5.07C85.018,159.984,84.969,159.984,84.92,159.985z M134.996,180.055 c-2.761-0.039-5.032,2.168-5.071,4.929c-0.039,2.761,2.168,5.032,4.929,5.071c0.047,0.001,0.094,0.001,0.141,0h30 c2.761,0.039,5.032-2.168,5.071-4.929c0.039-2.761-2.168-5.032-4.929-5.071c-0.047-0.001-0.094-0.001-0.141,0H134.996z M204.996,180.055c-2.761-0.039-5.032,2.168-5.071,4.929c-0.039,2.761,2.168,5.032,4.929,5.071c0.047,0.001,0.094,0.001,0.141,0h40 c2.761,0.039,5.032-2.168,5.071-4.929s-2.168-5.032-4.929-5.071c-0.047-0.001-0.094-0.001-0.141,0H204.996z M44.898,220.007 c-1.299,0.039-2.532,0.582-3.438,1.514l-20,20c-1.992,1.913-2.057,5.078-0.144,7.07c1.913,1.992,5.078,2.057,7.07,0.144 c0.049-0.047,0.097-0.095,0.144-0.144l20-20c1.98-1.925,2.025-5.091,0.1-7.071C47.654,220.514,46.3,219.965,44.898,220.007z"/>
|
||||
</g>
|
||||
</svg>;
|
||||
|
||||
// Icons below are adapted from https://materialdesignicons.com/ and
|
||||
// licensed with https://github.com/Templarian/MaterialDesign/blob/master/LICENSE
|
||||
|
||||
const _cursor = <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>;
|
||||
@@ -1073,7 +1073,7 @@ export class MappedControl extends React.PureComponent<ParamProps<PD.Mapped<any>
|
||||
|
||||
type ObjectListEditorProps = { params: PD.Params, value: object, isUpdate?: boolean, apply: (value: any) => void, isDisabled?: boolean }
|
||||
class ObjectListEditor extends React.PureComponent<ObjectListEditorProps, { current: object }> {
|
||||
state = { current: void 0 as any };
|
||||
state = { current: this.props.value };
|
||||
|
||||
onChangeParam: ParamOnChange = e => {
|
||||
this.setState({ current: { ...this.state.current, [e.name]: e.value } });
|
||||
|
||||
@@ -28,6 +28,13 @@ const ChannelParams = {
|
||||
};
|
||||
type ChannelParams = PD.Values<typeof ChannelParams>
|
||||
|
||||
const Bounds = new Map<VolumeStreaming.ChannelType, [number, number]>([
|
||||
['em', [-5, 5]],
|
||||
['2fo-fc', [0, 3]],
|
||||
['fo-fc(+ve)', [1, 5]],
|
||||
['fo-fc(-ve)', [-5, -1]],
|
||||
]);
|
||||
|
||||
class Channel extends PluginUIComponent<{
|
||||
label: string,
|
||||
name: VolumeStreaming.ChannelType,
|
||||
@@ -38,7 +45,8 @@ class Channel extends PluginUIComponent<{
|
||||
changeIso: (name: string, value: number, isRelative: boolean) => void,
|
||||
changeParams: (name: string, param: string, value: any) => void,
|
||||
bCell: StateObjectCell,
|
||||
isDisabled?: boolean
|
||||
isDisabled?: boolean,
|
||||
isUnbounded?: boolean
|
||||
}> {
|
||||
private ref = StateSelection.findTagInSubtree(this.plugin.state.data.tree, this.props.bCell!.transform.ref, this.props.name);
|
||||
|
||||
@@ -73,15 +81,31 @@ class Channel extends PluginUIComponent<{
|
||||
|
||||
const { min, max, mean, sigma } = stats;
|
||||
const value = Math.round(100 * (channel.isoValue.kind === 'relative' ? channel.isoValue.relativeValue : channel.isoValue.absoluteValue)) / 100;
|
||||
const relMin = (min - mean) / sigma;
|
||||
const relMax = (max - mean) / sigma;
|
||||
const step = toPrecision(isRelative ? Math.round(((max - min) / sigma)) / 100 : sigma / 100, 2);
|
||||
let relMin = (min - mean) / sigma;
|
||||
let relMax = (max - mean) / sigma;
|
||||
|
||||
if (!this.props.isUnbounded) {
|
||||
const bounds = Bounds.get(this.props.name)!;
|
||||
if (this.props.name === 'em') {
|
||||
relMin = Math.max(bounds[0], relMin);
|
||||
relMax = Math.min(bounds[1], relMax);
|
||||
} else {
|
||||
relMin = bounds[0];
|
||||
relMax = bounds[1];
|
||||
}
|
||||
}
|
||||
|
||||
const vMin = mean + sigma * relMin, vMax = mean + sigma * relMax;
|
||||
|
||||
const step = toPrecision(isRelative ? Math.round(((vMax - vMin) / sigma)) / 100 : sigma / 100, 2);
|
||||
const ctrlMin = isRelative ? relMin : vMin;
|
||||
const ctrlMax = isRelative ? relMax : vMax;
|
||||
|
||||
return <ExpandableControlRow
|
||||
label={props.label + (props.isRelative ? ' \u03C3' : '')}
|
||||
colorStripe={channel.color}
|
||||
pivot={<div className='msp-volume-channel-inline-controls'>
|
||||
<Slider value={value} min={isRelative ? relMin : min} max={isRelative ? relMax : max} step={step}
|
||||
<Slider value={value} min={ctrlMin} max={ctrlMax} step={step}
|
||||
onChange={v => props.changeIso(props.name, v, isRelative)} disabled={props.params.isDisabled} onEnter={props.params.events.onEnter} />
|
||||
<IconButton svg={this.getVisible() ? VisibilityOutlined : VisibilityOffOutlined} onClick={this.toggleVisible} toggleState={false} disabled={props.params.isDisabled} />
|
||||
</div>}
|
||||
@@ -163,6 +187,7 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
const isEM = b.info.kind === 'em';
|
||||
|
||||
const isRelative = value.params.isRelative;
|
||||
|
||||
const sampling = b.info.header.sampling[0];
|
||||
const oldChannels = old.entry.params.channels as any;
|
||||
|
||||
@@ -180,6 +205,7 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
viewParams.bottomLeft = value.params.bottomLeft;
|
||||
viewParams.topRight = value.params.topRight;
|
||||
}
|
||||
viewParams.isUnbounded = !!value.params.isUnbounded;
|
||||
|
||||
this.newParams({
|
||||
...old,
|
||||
@@ -221,28 +247,35 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
|
||||
const isRelativeParam = PD.Boolean(isRelative, { description: 'Use normalized or absolute isocontour scale.', label: 'Normalized' });
|
||||
|
||||
const isUnbounded = !!(params.entry.params.view.params as any).isUnbounded;
|
||||
const isUnboundedParam = PD.Boolean(isUnbounded, { description: 'Show full/limited range of iso-values for more fine-grained control.', label: 'Unbounded' });
|
||||
|
||||
const isOff = params.entry.params.view.name === 'off';
|
||||
// TODO: factor common things out, cache
|
||||
const OptionsParams = {
|
||||
entry: PD.Select(params.entry.name, b.data.entries.map(info => [info.dataId, info.dataId] as [string, string]), { isHidden: isOff, description: 'Which entry with volume data to display.' }),
|
||||
view: PD.MappedStatic(params.entry.params.view.name, {
|
||||
'off': PD.Group({
|
||||
isRelative: PD.Boolean(isRelative, { isHidden: true })
|
||||
isRelative: PD.Boolean(isRelative, { isHidden: true }),
|
||||
isUnbounded: PD.Boolean(isUnbounded, { isHidden: true }),
|
||||
}, { description: 'Display off.' }),
|
||||
'box': PD.Group({
|
||||
bottomLeft: PD.Vec3(Vec3.zero()),
|
||||
topRight: PD.Vec3(Vec3.zero()),
|
||||
detailLevel,
|
||||
isRelative: isRelativeParam
|
||||
isRelative: isRelativeParam,
|
||||
isUnbounded: isUnboundedParam,
|
||||
}, { description: 'Static box defined by cartesian coords.' }),
|
||||
'selection-box': PD.Group({
|
||||
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
|
||||
detailLevel,
|
||||
isRelative: isRelativeParam
|
||||
isRelative: isRelativeParam,
|
||||
isUnbounded: isUnboundedParam,
|
||||
}, { description: 'Box around focused element.' }),
|
||||
'cell': PD.Group({
|
||||
detailLevel,
|
||||
isRelative: isRelativeParam
|
||||
isRelative: isRelativeParam,
|
||||
isUnbounded: isUnboundedParam,
|
||||
}, { description: 'Box around the structure\'s bounding box.' }),
|
||||
// 'auto': PD.Group({ }), // TODO based on camera distance/active selection/whatever, show whole structure or slice.
|
||||
}, { options: VolumeStreaming.ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Focus" shows the volume around the element/atom last interacted with. "Whole Structure" shows the volume for the whole structure.' })
|
||||
@@ -256,7 +289,8 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
radius: (params.entry.params.view.params as any).radius,
|
||||
bottomLeft: (params.entry.params.view.params as any).bottomLeft,
|
||||
topRight: (params.entry.params.view.params as any).topRight,
|
||||
isRelative
|
||||
isRelative,
|
||||
isUnbounded
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -266,10 +300,10 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
}
|
||||
|
||||
return <>
|
||||
{!isEM && <Channel label='2Fo-Fc' name='2fo-fc' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} />}
|
||||
{!isEM && <Channel label='Fo-Fc(+ve)' name='fo-fc(+ve)' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} />}
|
||||
{!isEM && <Channel label='Fo-Fc(-ve)' name='fo-fc(-ve)' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} />}
|
||||
{isEM && <Channel label='EM' name='em' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} />}
|
||||
{!isEM && <Channel label='2Fo-Fc' name='2fo-fc' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} isUnbounded={isUnbounded} />}
|
||||
{!isEM && <Channel label='Fo-Fc(+ve)' name='fo-fc(+ve)' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} isUnbounded={isUnbounded} />}
|
||||
{!isEM && <Channel label='Fo-Fc(-ve)' name='fo-fc(-ve)' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} isUnbounded={isUnbounded} />}
|
||||
{isEM && <Channel label='EM' name='em' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} isUnbounded={isUnbounded} />}
|
||||
|
||||
<ParameterControls onChange={this.changeOption} params={OptionsParams} values={options} onEnter={this.props.events.onEnter} isDisabled={this.props.isDisabled} />
|
||||
</>;
|
||||
|
||||
@@ -239,7 +239,7 @@
|
||||
// TODO : get rid of the important
|
||||
.msp-control-group-header {
|
||||
background: $default-background;
|
||||
> button {
|
||||
> button, div {
|
||||
padding-left: 4px; // $control-spacing / 2 !important;
|
||||
text-align: left;
|
||||
height: 24px !important; // 2 * $row-height / 3 !important;
|
||||
|
||||
@@ -427,8 +427,7 @@
|
||||
position: relative;
|
||||
// display: inline-block;
|
||||
margin: $control-spacing auto 0 auto;
|
||||
width: 400px;
|
||||
line-height: $row-height;
|
||||
width: 430px;
|
||||
|
||||
&-actions {
|
||||
position: absolute;
|
||||
|
||||
@@ -100,15 +100,16 @@
|
||||
}
|
||||
|
||||
&-disabled {
|
||||
background-color: #e9e9e9;
|
||||
|
||||
.msp-slider-base-track {
|
||||
background-color: $slider-disabledColor;
|
||||
}
|
||||
|
||||
background: $default-background;
|
||||
opacity: 0.35;
|
||||
|
||||
// .msp-slider-base-track {
|
||||
// background-color: $slider-disabledColor;
|
||||
// }
|
||||
|
||||
.msp-slider-base-handle, .msp-slider-base-dot {
|
||||
border-color: $slider-disabledColor;
|
||||
background-color: #fff;
|
||||
// border-color: $slider-disabledColor;
|
||||
// background-color: color-lower-contrast($font-color, 10%);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +37,15 @@ export class AnimationControls extends PluginUIComponent<{ onStart?: () => void
|
||||
if (anim.isEmpty) return null;
|
||||
|
||||
const isDisabled = anim.state.animationState === 'playing';
|
||||
const canApply = anim.current.anim.canApply?.(this.plugin);
|
||||
|
||||
return <>
|
||||
<ParameterControls params={anim.getParams()} values={anim.state.params} onChange={this.updateParams} isDisabled={isDisabled} />
|
||||
<ParameterControls params={anim.current.params} values={anim.current.paramValues} onChange={this.updateCurrentParams} isDisabled={isDisabled} />
|
||||
|
||||
<div className='msp-flex-row'>
|
||||
<Button icon={anim.state.animationState !== 'playing' ? void 0 : PlayArrow} onClick={this.startOrStop}>
|
||||
{anim.state.animationState === 'playing' ? 'Stop' : 'Start'}
|
||||
<Button icon={anim.state.animationState !== 'playing' ? void 0 : PlayArrow} onClick={this.startOrStop} disabled={canApply !== void 0 && canApply.canApply === false}>
|
||||
{anim.state.animationState === 'playing' ? 'Stop' : canApply === void 0 || canApply.canApply ? 'Start' : canApply.reason || 'Start'}
|
||||
</Button>
|
||||
</div>
|
||||
</>;
|
||||
|
||||
@@ -14,6 +14,7 @@ import OpenInBrowser from '@material-ui/icons/OpenInBrowser';
|
||||
import SaveOutlined from '@material-ui/icons/SaveOutlined';
|
||||
import SwapHoriz from '@material-ui/icons/SwapHoriz';
|
||||
import Refresh from '@material-ui/icons/Refresh';
|
||||
import Warning from '@material-ui/icons/Warning';
|
||||
import { OrderedMap } from 'immutable';
|
||||
import * as React from 'react';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
@@ -73,17 +74,22 @@ export class StateExportImportControls extends PluginUIComponent<{ onAction?: ()
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className='msp-flex-row'>
|
||||
<Button icon={GetApp} onClick={this.downloadToFileJson} title='Save the state description. Input data are loaded using the provided sources. Does not work if local files are used as input.'>
|
||||
State
|
||||
</Button>
|
||||
<Button icon={GetApp} onClick={this.downloadToFileZip} title='Save the state including the input data.'>
|
||||
Session
|
||||
</Button>
|
||||
<div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file'>
|
||||
<Icon svg={OpenInBrowser} inline /> Open <input onChange={this.open} type='file' multiple={false} accept='.molx,.molj' />
|
||||
return <>
|
||||
<div className='msp-flex-row'>
|
||||
<Button icon={GetApp} onClick={this.downloadToFileJson} title='Save the state description. Input data are loaded using the provided sources. Does not work if local files are used as input.'>
|
||||
State
|
||||
</Button>
|
||||
<Button icon={GetApp} onClick={this.downloadToFileZip} title='Save the state including the input data.'>
|
||||
Session
|
||||
</Button>
|
||||
<div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file'>
|
||||
<Icon svg={OpenInBrowser} inline /> Open <input onChange={this.open} type='file' multiple={false} accept='.molx,.molj' />
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
<div className='msp-help-text' style={{ padding: '10px'}}>
|
||||
<Icon svg={Warning} /> This is an experimental feature and stored states/sessions might not be openable in a future version.
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,11 +217,18 @@ export class RemoteStateSnapshots extends PluginUIComponent<
|
||||
}, { isFlat: true })
|
||||
};
|
||||
|
||||
private _mounted = false;
|
||||
componentDidMount() {
|
||||
this.refresh();
|
||||
// TODO: solve this by using "PluginComponent" with behaviors intead
|
||||
this._mounted = true;
|
||||
// this.subscribe(UploadedEvent, this.refresh);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._mounted = false;
|
||||
}
|
||||
|
||||
serverUrl(q?: string) {
|
||||
if (!q) return this.state.params.options.serverUrl;
|
||||
return urlCombine(this.state.params.options.serverUrl, q);
|
||||
@@ -242,10 +255,10 @@ export class RemoteStateSnapshots extends PluginUIComponent<
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ entries: entries.asImmutable(), isBusy: false });
|
||||
if (this._mounted) this.setState({ entries: entries.asImmutable(), isBusy: false });
|
||||
} catch (e) {
|
||||
this.plugin.log.error('Fetching Remote Snapshots: ' + e);
|
||||
this.setState({ entries: OrderedMap(), isBusy: false });
|
||||
if (this._mounted) this.setState({ entries: OrderedMap(), isBusy: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,11 +273,15 @@ export class RemoteStateSnapshots extends PluginUIComponent<
|
||||
serverUrl: this.state.params.options.serverUrl
|
||||
});
|
||||
|
||||
this.setState({ isBusy: false });
|
||||
this.plugin.log.message('Snapshot uploaded.');
|
||||
this.refresh();
|
||||
|
||||
if (this._mounted) {
|
||||
this.setState({ isBusy: false });
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fetch = async (e: React.MouseEvent<HTMLElement>) => {
|
||||
const id = e.currentTarget.getAttribute('data-id');
|
||||
if (!id) return;
|
||||
@@ -275,7 +292,7 @@ export class RemoteStateSnapshots extends PluginUIComponent<
|
||||
try {
|
||||
await PluginCommands.State.Snapshots.Fetch(this.plugin, { url: entry.url });
|
||||
} finally {
|
||||
this.setState({ isBusy: false });
|
||||
if (this._mounted) this.setState({ isBusy: false });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
import { CollapsableControls, CollapsableState, PurePluginUIComponent } from '../base';
|
||||
import { ActionMenu } from '../controls/action-menu';
|
||||
import { Button, ExpandGroup, IconButton, ToggleButton } from '../controls/common';
|
||||
import { CubeSvg, Intersect, SetSvg, Subtract, Union } from '../controls/icons';
|
||||
import { CubeSvg, IntersectSvg, SetSvg, SubtractSvg, UnionSvg } from '../controls/icons';
|
||||
import { ParameterControls } from '../controls/parameters';
|
||||
import { UpdateTransformControl } from '../state/update-transform';
|
||||
import { GenericEntryListControls } from './generic';
|
||||
@@ -267,9 +267,9 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo
|
||||
if (mng.canBeModified(this.props.group[0])) {
|
||||
ret.push([
|
||||
ActionMenu.Header('Modify by Selection'),
|
||||
ActionMenu.Item('Include', () => mng.modifyByCurrentSelection(this.props.group, 'union'), { icon: Union }),
|
||||
ActionMenu.Item('Subtract', () => mng.modifyByCurrentSelection(this.props.group, 'subtract'), { icon: Subtract }),
|
||||
ActionMenu.Item('Intersect', () => mng.modifyByCurrentSelection(this.props.group, 'intersect'), { icon: Intersect })
|
||||
ActionMenu.Item('Include', () => mng.modifyByCurrentSelection(this.props.group, 'union'), { icon: UnionSvg }),
|
||||
ActionMenu.Item('Subtract', () => mng.modifyByCurrentSelection(this.props.group, 'subtract'), { icon: SubtractSvg }),
|
||||
ActionMenu.Item('Intersect', () => mng.modifyByCurrentSelection(this.props.group, 'intersect'), { icon: IntersectSvg })
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,9 @@ import ArrowUpward from '@material-ui/icons/ArrowUpward';
|
||||
import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
|
||||
import HelpOutline from '@material-ui/icons/HelpOutline';
|
||||
import MoreHoriz from '@material-ui/icons/MoreHoriz';
|
||||
import RemoveOutlined from '@material-ui/icons/RemoveOutlined';
|
||||
import Tune from '@material-ui/icons/Tune';
|
||||
import VisibilityOutlined from '@material-ui/icons/VisibilityOutlined';
|
||||
import VisibilityOffOutlined from '@material-ui/icons/VisibilityOffOutlined';
|
||||
import VisibilityOutlined from '@material-ui/icons/VisibilityOutlined';
|
||||
import * as React from 'react';
|
||||
import { Loci } from '../../mol-model/loci';
|
||||
import { StructureElement } from '../../mol-model/structure';
|
||||
@@ -27,9 +26,10 @@ 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, SetSvg, RulerSvg } from '../controls/icons';
|
||||
import { Icon, RulerSvg, SetSvg } from '../controls/icons';
|
||||
import { ParameterControls } from '../controls/parameters';
|
||||
import { UpdateTransformControl } from '../state/update-transform';
|
||||
import { ToggleSelectionModeButton } from './selection';
|
||||
|
||||
// TODO details, options (e.g. change text for labels)
|
||||
|
||||
@@ -172,7 +172,7 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo
|
||||
{entries}
|
||||
</div>}
|
||||
{entries.length === 0 && <div className='msp-control-offset msp-help-text'>
|
||||
<div className='msp-help-description'><Icon svg={HelpOutline} inline />Add one or more selections</div>
|
||||
<div className='msp-help-description'><Icon svg={HelpOutline} inline />Add one or more selections (toggle <ToggleSelectionModeButton inline /> mode)</div>
|
||||
</div>}
|
||||
</>;
|
||||
}
|
||||
@@ -298,7 +298,7 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen
|
||||
<span dangerouslySetInnerHTML={{ __html: this.label }} />
|
||||
</button>
|
||||
<IconButton svg={cell.state.isHidden ? VisibilityOffOutlined : VisibilityOutlined} toggleState={false} small className='msp-form-control' onClick={this.toggleVisibility} flex title={cell.state.isHidden ? 'Show' : 'Hide'} />
|
||||
<IconButton svg={RemoveOutlined} small className='msp-form-control' onClick={this.delete} flex title='Delete' />
|
||||
<IconButton svg={DeleteOutlined} small className='msp-form-control' onClick={this.delete} flex title='Delete' toggleState={false} />
|
||||
<IconButton svg={MoreHoriz} className='msp-form-control' onClick={this.toggleUpdate} flex title='Actions' toggleState={this.state.showUpdate} />
|
||||
</div>
|
||||
{this.state.showUpdate && cell.parent && <>
|
||||
|
||||
@@ -10,6 +10,7 @@ import CancelOutlined from '@material-ui/icons/CancelOutlined';
|
||||
import Brush from '@material-ui/icons/Brush';
|
||||
import Restore from '@material-ui/icons/Restore';
|
||||
import Remove from '@material-ui/icons/Remove';
|
||||
import HelpOutline from '@material-ui/icons/HelpOutline';
|
||||
import * as React from 'react';
|
||||
import { StructureSelectionQueries, StructureSelectionQuery, getNonStandardResidueQueries, getElementQueries, getPolymerAndBranchedEntityQueries } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { InteractivityManager } from '../../mol-plugin-state/manager/interactivity';
|
||||
@@ -23,9 +24,31 @@ import { PluginUIComponent, PurePluginUIComponent } from '../base';
|
||||
import { ActionMenu } from '../controls/action-menu';
|
||||
import { Button, ControlGroup, IconButton, ToggleButton } from '../controls/common';
|
||||
import { ParameterControls, ParamOnChange, PureSelectControl } from '../controls/parameters';
|
||||
import { Union, Subtract, Intersect, SetSvg as SetSvg, CubeSvg } from '../controls/icons';
|
||||
import { UnionSvg, SubtractSvg, IntersectSvg, SetSvg, CubeSvg, Icon, CursorSvg } from '../controls/icons';
|
||||
import { AddComponentControls } from './components';
|
||||
import Structure from '../../mol-model/structure/structure/structure';
|
||||
import { ViewportHelpContent, HelpGroup, HelpText } from '../viewport/help';
|
||||
|
||||
|
||||
export class ToggleSelectionModeButton extends PurePluginUIComponent<{ inline?: boolean }> {
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
|
||||
this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
|
||||
this.subscribe(this.plugin.behaviors.interaction.selectionMode, () => this.forceUpdate());
|
||||
}
|
||||
|
||||
_toggleSelMode = () => {
|
||||
this.plugin.selectionMode = !this.plugin.selectionMode;
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = this.props.inline
|
||||
? { background: 'transparent', width: 'auto', height: 'auto', lineHeight: 'unset' }
|
||||
: { background: 'transparent' };
|
||||
return <IconButton svg={CursorSvg} onClick={this._toggleSelMode} title={'Toggle Selection Mode'} style={style} toggleState={this.plugin.selectionMode} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const StructureSelectionParams = {
|
||||
granularity: InteractivityManager.Params.granularity,
|
||||
@@ -36,7 +59,7 @@ interface StructureSelectionActionsControlsState {
|
||||
isBusy: boolean,
|
||||
canUndo: boolean,
|
||||
|
||||
action?: StructureSelectionModifier | 'color' | 'add-repr'
|
||||
action?: StructureSelectionModifier | 'color' | 'add-repr' | 'help'
|
||||
}
|
||||
|
||||
const ActionHeader = new Map<StructureSelectionModifier, string>([
|
||||
@@ -144,6 +167,7 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
toggleSet = this.showAction('set')
|
||||
toggleColor = this.showAction('color')
|
||||
toggleAddRepr = this.showAction('add-repr')
|
||||
toggleHelp = this.showAction('help')
|
||||
|
||||
setGranuality: ParamOnChange = ({ value }) => {
|
||||
this.plugin.managers.interactivity.setProps({ granularity: value });
|
||||
@@ -173,9 +197,9 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
return <>
|
||||
<div className='msp-flex-row' style={{ background: 'none' }}>
|
||||
<PureSelectControl title={`Picking Level for selecting and highlighting`} param={StructureSelectionParams.granularity} name='granularity' value={granularity} onChange={this.setGranuality} isDisabled={this.isDisabled} />
|
||||
<ToggleButton icon={Union} title={`${ActionHeader.get('add')}. Hold shift key to keep menu open.`} toggle={this.toggleAdd} isSelected={this.state.action === 'add'} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={Subtract} title={`${ActionHeader.get('remove')}. Hold shift key to keep menu open.`} toggle={this.toggleRemove} isSelected={this.state.action === 'remove'} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={Intersect} title={`${ActionHeader.get('intersect')}. Hold shift key to keep menu open.`} toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={UnionSvg} title={`${ActionHeader.get('add')}. Hold shift key to keep menu open.`} toggle={this.toggleAdd} isSelected={this.state.action === 'add'} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={SubtractSvg} title={`${ActionHeader.get('remove')}. Hold shift key to keep menu open.`} toggle={this.toggleRemove} isSelected={this.state.action === 'remove'} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={IntersectSvg} title={`${ActionHeader.get('intersect')}. Hold shift key to keep menu open.`} toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={SetSvg} title={`${ActionHeader.get('set')}. Hold shift key to keep menu open.`} toggle={this.toggleSet} isSelected={this.state.action === 'set'} disabled={this.isDisabled} />
|
||||
|
||||
<ToggleButton icon={Brush} title='Color Selection' toggle={this.toggleColor} isSelected={this.state.action === 'color'} disabled={this.isDisabled} style={{ marginLeft: '10px' }} />
|
||||
@@ -183,9 +207,10 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
<IconButton svg={Remove} title='Subtract Selection from Representations' onClick={this.subtract} disabled={this.isDisabled} />
|
||||
<IconButton svg={Restore} onClick={this.undo} disabled={!this.state.canUndo || this.isDisabled} title={undoTitle} />
|
||||
|
||||
<IconButton svg={CancelOutlined} title='Turn selection mode off' onClick={this.turnOff} style={{ marginLeft: '10px' }} />
|
||||
<ToggleButton icon={HelpOutline} title='Show/hide help' toggle={this.toggleHelp} style={{ marginLeft: '10px' }} isSelected={this.state.action === 'help'} />
|
||||
<IconButton svg={CancelOutlined} title='Turn selection mode off' onClick={this.turnOff} />
|
||||
</div>
|
||||
{(this.state.action && this.state.action !== 'color' && this.state.action !== 'add-repr') && <div className='msp-selection-viewport-controls-actions'>
|
||||
{(this.state.action && this.state.action !== 'color' && this.state.action !== 'add-repr' && this.state.action !== 'help') && <div className='msp-selection-viewport-controls-actions'>
|
||||
<ActionMenu header={ActionHeader.get(this.state.action as StructureSelectionModifier)} title='Click to close.' items={this.queries} onSelect={this.selectQuery} noOffset />
|
||||
</div>}
|
||||
{this.state.action === 'color' && <div className='msp-selection-viewport-controls-actions'>
|
||||
@@ -198,6 +223,17 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
<AddComponentControls onApply={this.toggleAddRepr} forSelection />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
{this.state.action === 'help' && <div className='msp-selection-viewport-controls-actions'>
|
||||
<ControlGroup header='Help' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleHelp} topRightIcon={Close} maxHeight='300px'>
|
||||
<HelpGroup header='Selection Operations'>
|
||||
<HelpText>Use <Icon svg={UnionSvg} inline /> <Icon svg={SubtractSvg} inline /> <Icon svg={IntersectSvg} inline /> <Icon svg={SetSvg} inline /> to modify the selection.</HelpText>
|
||||
</HelpGroup>
|
||||
<HelpGroup header='Representation Operations'>
|
||||
<HelpText>Use <Icon svg={Brush} inline /> <Icon svg={CubeSvg} inline /> <Icon svg={Remove} inline /> <Icon svg={Restore} inline /> to color, create selection/representation, subtract it, or undo actions.</HelpText>
|
||||
</HelpGroup>
|
||||
<ViewportHelpContent selectOnly={true} />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +254,7 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
|
||||
const { selection } = this.plugin.managers.structure.hierarchy;
|
||||
if (selection.structures.length !== 1) return null;
|
||||
const pivot = selection.structures[0];
|
||||
if (!pivot.cell.parent) return null;
|
||||
const t = StateSelection.tryFindDecorator(this.plugin.state.data, pivot.cell.transform.ref, StateTransforms.Model.TransformStructureConformation);
|
||||
if (!t) return;
|
||||
|
||||
|
||||
323
src/mol-plugin-ui/structure/superposition.tsx
Normal file
323
src/mol-plugin-ui/structure/superposition.tsx
Normal file
@@ -0,0 +1,323 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import FlipToFrontIcon from '@material-ui/icons/FlipToFront';
|
||||
import HelpOutline from '@material-ui/icons/HelpOutline';
|
||||
import LinearScaleIcon from '@material-ui/icons/LinearScale';
|
||||
import ScatterPlotIcon from '@material-ui/icons/ScatterPlot';
|
||||
import ArrowDownward from '@material-ui/icons/ArrowDownward';
|
||||
import ArrowUpward from '@material-ui/icons/ArrowUpward';
|
||||
import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
|
||||
import Tune from '@material-ui/icons/Tune';
|
||||
import { CollapsableControls, PurePluginUIComponent } from '../base';
|
||||
import { Icon } from '../controls/icons';
|
||||
import { Button, ToggleButton, IconButton } from '../controls/common';
|
||||
import { StructureElement, StructureSelection, QueryContext, Structure, StructureProperties } from '../../mol-model/structure';
|
||||
import { Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { StateObjectRef, StateObjectCell, StateSelection } from '../../mol-state';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { alignAndSuperpose, superpose } from '../../mol-model/structure/structure/util/superposition';
|
||||
import { StructureSelectionQueries } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { structureElementStatsLabel, elementLabel } from '../../mol-theme/label';
|
||||
import { ParameterControls } from '../controls/parameters';
|
||||
import { stripTags } from '../../mol-util/string';
|
||||
import { StructureSelectionHistoryEntry } from '../../mol-plugin-state/manager/structure/selection';
|
||||
import { ToggleSelectionModeButton } from './selection';
|
||||
|
||||
export class StructureSuperpositionControls extends CollapsableControls {
|
||||
defaultState() {
|
||||
return {
|
||||
isCollapsed: false,
|
||||
header: 'Superposition',
|
||||
brand: { accent: 'gray' as const, svg: FlipToFrontIcon },
|
||||
isHidden: true
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, sel => {
|
||||
this.setState({ isHidden: sel.structures.length < 2 });
|
||||
});
|
||||
}
|
||||
|
||||
renderControls() {
|
||||
return <>
|
||||
<SuperpositionControls />
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
export const StructureSuperpositionParams = {
|
||||
alignSequences: PD.Boolean(true, { isEssential: true, description: 'Perform a sequence alignment and use the aligned residue pairs to guide the 3D superposition.' }),
|
||||
};
|
||||
const DefaultStructureSuperpositionOptions = PD.getDefaultValues(StructureSuperpositionParams);
|
||||
export type StructureSuperpositionOptions = PD.ValuesFor<typeof StructureSuperpositionParams>
|
||||
|
||||
const SuperpositionTag = 'SuperpositionTransform';
|
||||
|
||||
type SuperpositionControlsState = {
|
||||
isBusy: boolean,
|
||||
action?: 'byChains' | 'byAtoms' | 'options',
|
||||
options: StructureSuperpositionOptions
|
||||
}
|
||||
|
||||
interface LociEntry {
|
||||
loci: StructureElement.Loci,
|
||||
label: string,
|
||||
cell: StateObjectCell<PluginStateObject.Molecule.Structure>
|
||||
};
|
||||
|
||||
interface AtomsLociEntry extends LociEntry {
|
||||
atoms: StructureSelectionHistoryEntry[]
|
||||
};
|
||||
|
||||
export class SuperpositionControls extends PurePluginUIComponent<{}, SuperpositionControlsState> {
|
||||
state: SuperpositionControlsState = {
|
||||
isBusy: false,
|
||||
action: undefined,
|
||||
options: DefaultStructureSuperpositionOptions
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.selection.events.changed, () => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
|
||||
this.subscribe(this.selection.events.additionsHistoryUpdated, () => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, v => {
|
||||
this.setState({ isBusy: v });
|
||||
});
|
||||
}
|
||||
|
||||
get selection() {
|
||||
return this.plugin.managers.structure.selection;
|
||||
}
|
||||
|
||||
async transform(s: StateObjectRef<PluginStateObject.Molecule.Structure>, matrix: Mat4) {
|
||||
const r = StateObjectRef.resolveAndCheck(this.plugin.state.data, s);
|
||||
if (!r) return;
|
||||
// TODO should find any TransformStructureConformation decorator instance
|
||||
const o = StateSelection.findTagInSubtree(this.plugin.state.data.tree, r.transform.ref, SuperpositionTag);
|
||||
|
||||
const params = {
|
||||
transform: {
|
||||
name: 'matrix' as const,
|
||||
params: { data: matrix, transpose: false }
|
||||
}
|
||||
};
|
||||
// TODO add .insertOrUpdate to StateBuilder?
|
||||
let b = o
|
||||
? this.plugin.state.data.build().to(o).update(params)
|
||||
: this.plugin.state.data.build().to(s)
|
||||
.insert(StateTransforms.Model.TransformStructureConformation, params, { tags: SuperpositionTag });
|
||||
await this.plugin.runTask(this.plugin.state.data.updateTree(b));
|
||||
}
|
||||
|
||||
superposeChains = async () => {
|
||||
const { query } = StructureSelectionQueries.trace;
|
||||
const entries = this.chainEntries;
|
||||
|
||||
const traceLocis = entries.map((e, i) => {
|
||||
const s = StructureElement.Loci.toStructure(e.loci);
|
||||
const loci = StructureSelection.toLociWithSourceUnits(query(new QueryContext(s)));
|
||||
return StructureElement.Loci.remap(loci, i === 0
|
||||
? this.plugin.helpers.substructureParent.get(e.loci.structure.root)!.obj!.data
|
||||
: loci.structure.root
|
||||
);
|
||||
});
|
||||
|
||||
const transforms = this.state.options.alignSequences
|
||||
? alignAndSuperpose(traceLocis)
|
||||
: superpose(traceLocis);
|
||||
|
||||
const eA = entries[0];
|
||||
for (let i = 1, il = traceLocis.length; i < il; ++i) {
|
||||
const eB = entries[i];
|
||||
const { bTransform, rmsd } = transforms[i - 1];
|
||||
await this.transform(eB.cell, bTransform);
|
||||
const labelA = stripTags(eA.label);
|
||||
const labelB = stripTags(eB.label);
|
||||
this.plugin.log.info(`Superposed [${labelA}] and [${labelB}] with RMSD ${rmsd.toFixed(2)}.`);
|
||||
}
|
||||
}
|
||||
|
||||
superposeAtoms = async () => {
|
||||
const entries = this.atomEntries;
|
||||
|
||||
const atomLocis = entries.map((e, i) => {
|
||||
return StructureElement.Loci.remap(e.loci, i === 0
|
||||
? this.plugin.helpers.substructureParent.get(e.loci.structure.root)!.obj!.data
|
||||
: e.loci.structure.root
|
||||
);
|
||||
});
|
||||
const transforms = superpose(atomLocis);
|
||||
|
||||
const eA = entries[0];
|
||||
for (let i = 1, il = atomLocis.length; i < il; ++i) {
|
||||
const eB = entries[i];
|
||||
const { bTransform, rmsd } = transforms[i - 1];
|
||||
await this.transform(eB.cell, bTransform);
|
||||
const labelA = stripTags(eA.label);
|
||||
const labelB = stripTags(eB.label);
|
||||
const count = entries[i].atoms.length;
|
||||
this.plugin.log.info(`Superposed ${count} ${count === 1 ? 'atom' : 'atoms'} of [${labelA}] and [${labelB}] with RMSD ${rmsd.toFixed(2)}.`);
|
||||
}
|
||||
}
|
||||
|
||||
toggleByChains = () => this.setState({ action: this.state.action === 'byChains' ? void 0 : 'byChains' });
|
||||
toggleByAtoms = () => this.setState({ action: this.state.action === 'byAtoms' ? void 0 : 'byAtoms' });
|
||||
toggleOptions = () => this.setState({ action: this.state.action === 'options' ? void 0 : 'options' });
|
||||
|
||||
highlight(loci: StructureElement.Loci) {
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci }, false);
|
||||
}
|
||||
|
||||
moveHistory(e: StructureSelectionHistoryEntry, direction: 'up' | 'down') {
|
||||
this.plugin.managers.structure.selection.modifyHistory(e, direction, void 0, true);
|
||||
}
|
||||
|
||||
focusLoci(loci: StructureElement.Loci) {
|
||||
this.plugin.managers.camera.focusLoci(loci);
|
||||
}
|
||||
|
||||
lociEntry(e: LociEntry, idx: number) {
|
||||
return <div className='msp-flex-row' key={idx}>
|
||||
<Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={this.plugin.managers.interactivity.lociHighlights.clearHighlights}>
|
||||
<span dangerouslySetInnerHTML={{ __html: e.label }} />
|
||||
</Button>
|
||||
</div>;
|
||||
}
|
||||
|
||||
historyEntry(e: StructureSelectionHistoryEntry, idx: number) {
|
||||
const history = this.plugin.managers.structure.selection.additionsHistory;
|
||||
return <div className='msp-flex-row' key={e.id}>
|
||||
<Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={this.plugin.managers.interactivity.lociHighlights.clearHighlights}>
|
||||
{idx}. <span dangerouslySetInnerHTML={{ __html: e.label }} />
|
||||
</Button>
|
||||
{history.length > 1 && <IconButton svg={ArrowUpward} small={true} className='msp-form-control' onClick={() => this.moveHistory(e, 'up')} flex='20px' title={'Move up'} />}
|
||||
{history.length > 1 && <IconButton svg={ArrowDownward} small={true} className='msp-form-control' onClick={() => this.moveHistory(e, 'down')} flex='20px' title={'Move down'} />}
|
||||
<IconButton svg={DeleteOutlined} small={true} className='msp-form-control' onClick={() => this.plugin.managers.structure.selection.modifyHistory(e, 'remove')} flex title={'Remove'} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
atomsLociEntry(e: AtomsLociEntry, idx: number) {
|
||||
return <div key={idx}>
|
||||
<div className='msp-control-group-header'>
|
||||
<div className='msp-no-overflow' title={e.label}>{e.label}</div>
|
||||
</div>
|
||||
<div className='msp-control-offset'>
|
||||
{e.atoms.map((h, i) => this.historyEntry(h, i))}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
get chainEntries() {
|
||||
const location = StructureElement.Location.create();
|
||||
const entries: LociEntry[] = [];
|
||||
this.plugin.managers.structure.selection.entries.forEach(({ selection }, ref) => {
|
||||
const cell = StateObjectRef.resolveAndCheck(this.plugin.state.data, ref);
|
||||
if (!cell || StructureElement.Loci.isEmpty(selection)) return;
|
||||
|
||||
// only single polymer chain selections
|
||||
const l = StructureElement.Loci.getFirstLocation(selection, location)!;
|
||||
if (selection.elements.length > 1 || StructureProperties.entity.type(l) !== 'polymer') return;
|
||||
|
||||
const stats = StructureElement.Stats.ofLoci(selection);
|
||||
const counts = structureElementStatsLabel(stats, { countsOnly: true });
|
||||
const chain = elementLabel(l, { reverse: true, granularity: 'chain' }).split('|');
|
||||
const label = `${counts} | ${chain[0]} | ${chain[chain.length - 1]}`;
|
||||
entries.push({ loci: selection, label, cell });
|
||||
});
|
||||
return entries;
|
||||
}
|
||||
|
||||
get atomEntries() {
|
||||
const structureEntries = new Map<Structure, StructureSelectionHistoryEntry[]>();
|
||||
const history = this.plugin.managers.structure.selection.additionsHistory;
|
||||
|
||||
for (let i = 0, il = history.length; i < il; ++i) {
|
||||
const e = history[i];
|
||||
if (StructureElement.Loci.size(e.loci) !== 1) continue;
|
||||
|
||||
const k = e.loci.structure;
|
||||
if (structureEntries.has(k)) structureEntries.get(k)!.push(e);
|
||||
else structureEntries.set(k, [e]);
|
||||
}
|
||||
|
||||
const entries: AtomsLociEntry[] = [];
|
||||
structureEntries.forEach((atoms, structure) => {
|
||||
const cell = this.plugin.helpers.substructureParent.get(structure)!;
|
||||
|
||||
const elements: StructureElement.Loci['elements'][0][] = [];
|
||||
for (let i = 0, il = atoms.length; i < il; ++i) {
|
||||
// note, we don't do loci union here to keep order of selected atoms
|
||||
// for atom pairing during superposition
|
||||
elements.push(atoms[i].loci.elements[0]);
|
||||
}
|
||||
|
||||
const loci = StructureElement.Loci(atoms[0].loci.structure, elements);
|
||||
const label = loci.structure.label.split(' | ')[0];
|
||||
entries.push({ loci, label, cell, atoms });
|
||||
});
|
||||
return entries;
|
||||
}
|
||||
|
||||
addByChains() {
|
||||
const entries = this.chainEntries;
|
||||
return <>
|
||||
{entries.length > 0 && <div className='msp-control-offset'>
|
||||
{entries.map((e, i) => this.lociEntry(e, i))}
|
||||
</div>}
|
||||
{entries.length < 2 && <div className='msp-control-offset msp-help-text'>
|
||||
<div className='msp-help-description'><Icon svg={HelpOutline} inline />Add 2 or more selections (toggle <ToggleSelectionModeButton inline /> mode) from separate structures. Selections must be limited to single polymer chains or residues therein.</div>
|
||||
</div>}
|
||||
{entries.length > 1 && <Button title='Superpose structures by selected chains.' className='msp-btn-commit msp-btn-commit-on' onClick={this.superposeChains} style={{ marginTop: '1px' }}>
|
||||
Superpose
|
||||
</Button>}
|
||||
</>;
|
||||
}
|
||||
|
||||
addByAtoms() {
|
||||
const entries = this.atomEntries;
|
||||
return <>
|
||||
{entries.length > 0 && <div className='msp-control-offset'>
|
||||
{entries.map((e, i) => this.atomsLociEntry(e, i))}
|
||||
</div>}
|
||||
{entries.length < 2 && <div className='msp-control-offset msp-help-text'>
|
||||
<div className='msp-help-description'><Icon svg={HelpOutline} inline />Add 1 or more selections (toggle <ToggleSelectionModeButton inline /> mode) from
|
||||
separate structures. Selections must be limited to single atoms.</div>
|
||||
</div>}
|
||||
{entries.length > 1 && <Button title='Superpose structures by selected atoms.' className='msp-btn-commit msp-btn-commit-on' onClick={this.superposeAtoms} style={{ marginTop: '1px' }}>
|
||||
Superpose
|
||||
</Button>}
|
||||
</>;
|
||||
}
|
||||
|
||||
private setOptions = (values: StructureSuperpositionOptions) => {
|
||||
this.setState({ options: values });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <>
|
||||
<div className='msp-flex-row'>
|
||||
<ToggleButton icon={LinearScaleIcon} label='By Chains' toggle={this.toggleByChains} isSelected={this.state.action === 'byChains'} disabled={this.state.isBusy} />
|
||||
<ToggleButton icon={ScatterPlotIcon} label='By Atoms' toggle={this.toggleByAtoms} isSelected={this.state.action === 'byAtoms'} disabled={this.state.isBusy} />
|
||||
<ToggleButton icon={Tune} label='' title='Options' toggle={this.toggleOptions} isSelected={this.state.action === 'options'} disabled={this.state.isBusy} style={{ flex: '0 0 40px', padding: 0 }} />
|
||||
</div>
|
||||
{this.state.action === 'byChains' && this.addByChains()}
|
||||
{this.state.action === 'byAtoms' && this.addByAtoms()}
|
||||
{this.state.action === 'options' && <div className='msp-control-offset'>
|
||||
<ParameterControls params={StructureSuperpositionParams} values={this.state.options} onChangeValues={this.setOptions} isDisabled={this.state.isBusy} />
|
||||
</div>}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ export class VolumeStreamingControls extends CollapsableControls<{}, VolumeStrea
|
||||
if (!pivot.cell.parent) return null;
|
||||
const bindings = pivot.volumeStreaming?.cell.transform.params?.entry.params.view.name === 'selection-box' && this.plugin.state.behaviors.cells.get(FocusLoci.id)?.params?.values?.bindings;
|
||||
return <>
|
||||
<UpdateTransformControl state={pivot.cell.parent} transform={pivot.volumeStreaming!.cell.transform} customHeader='none' noMargin autoHideApply />
|
||||
<UpdateTransformControl state={pivot.cell.parent} transform={pivot.volumeStreaming!.cell.transform} customHeader='none' noMargin />
|
||||
{bindings && <ExpandGroup header='Controls Help'>
|
||||
<BindingsHelp bindings={bindings} />
|
||||
</ExpandGroup>}
|
||||
|
||||
@@ -9,16 +9,18 @@ import Autorenew from '@material-ui/icons/Autorenew';
|
||||
import BuildOutlined from '@material-ui/icons/BuildOutlined';
|
||||
import CameraOutlined from '@material-ui/icons/CameraOutlined';
|
||||
import Close from '@material-ui/icons/Close';
|
||||
import Crop from '@material-ui/icons/Crop';
|
||||
import Fullscreen from '@material-ui/icons/Fullscreen';
|
||||
import Tune from '@material-ui/icons/Tune';
|
||||
import * as React from 'react';
|
||||
import { Subject } from 'rxjs';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
import { resizeCanvas } from '../mol-canvas3d/util';
|
||||
import { PluginCommands } from '../mol-plugin/commands';
|
||||
import { PluginConfig } from '../mol-plugin/config';
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
import { PluginUIComponent } from './base';
|
||||
import { ControlGroup, IconButton } from './controls/common';
|
||||
import { ToggleSelectionModeButton } from './structure/selection';
|
||||
import { DownloadScreenshotControls } from './viewport/screenshot';
|
||||
import { SimpleSettingsControl } from './viewport/simple-settings';
|
||||
|
||||
@@ -60,10 +62,6 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
|
||||
PluginCommands.Layout.Update(this.plugin, { state: { isExpanded: !this.plugin.layout.state.isExpanded } });
|
||||
}
|
||||
|
||||
toggleSelectionMode = () => {
|
||||
this.plugin.selectionMode = !this.plugin.selectionMode;
|
||||
}
|
||||
|
||||
setSettings = (p: { param: PD.Base<any>, name: string, value: any }) => {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { [p.name]: p.value } });
|
||||
}
|
||||
@@ -79,7 +77,6 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
|
||||
this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
|
||||
this.subscribe(this.plugin.behaviors.interaction.selectionMode, () => this.forceUpdate());
|
||||
}
|
||||
|
||||
icon(icon: React.FC, onClick: (e: React.MouseEvent<HTMLButtonElement>) => void, title: string, isOn = true) {
|
||||
@@ -110,7 +107,7 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
|
||||
</div>
|
||||
{this.plugin.config.get(PluginConfig.Viewport.ShowSelectionMode) && <div>
|
||||
<div className='msp-semi-transparent-background' />
|
||||
{this.icon(Crop, this.toggleSelectionMode, 'Toggle Selection Mode', this.plugin.behaviors.interaction.selectionMode.value)}
|
||||
<ToggleSelectionModeButton />
|
||||
</div>}
|
||||
</div>
|
||||
{this.state.isScreenshotExpanded && <div className='msp-viewport-controls-panel'>
|
||||
@@ -169,13 +166,15 @@ export class Viewport extends PluginUIComponent<{ }, ViewportState> {
|
||||
|
||||
const canvas3d = this.plugin.canvas3d!;
|
||||
this.subscribe(canvas3d.reprCount, this.handleLogo);
|
||||
this.subscribe(canvas3d.input.resize, this.handleResize);
|
||||
|
||||
const resized = new Subject();
|
||||
const resize = () => resized.next();
|
||||
|
||||
this.subscribe(resized.pipe(debounceTime(1000 / 24)), () => this.handleResize());
|
||||
this.subscribe(canvas3d.input.resize, resize);
|
||||
this.subscribe(canvas3d.interaction.click, e => this.plugin.behaviors.interaction.click.next(e));
|
||||
this.subscribe(canvas3d.interaction.hover, e => this.plugin.behaviors.interaction.hover.next(e));
|
||||
this.subscribe(this.plugin.layout.events.updated, () => {
|
||||
setTimeout(this.handleResize, 50);
|
||||
});
|
||||
this.subscribe(this.plugin.layout.events.updated, resize);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
||||
@@ -40,7 +40,7 @@ export class BindingsHelp extends React.PureComponent<{ bindings: { [k: string]:
|
||||
}
|
||||
}
|
||||
|
||||
class HelpText extends React.PureComponent {
|
||||
export class HelpText extends React.PureComponent {
|
||||
render() {
|
||||
return <div className='msp-help-text'>
|
||||
<div>{this.props.children}</div>
|
||||
@@ -48,7 +48,7 @@ class HelpText extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
class HelpGroup extends React.PureComponent<{ header: string, initiallyExpanded?: boolean }, { isExpanded: boolean }> {
|
||||
export class HelpGroup extends React.PureComponent<{ header: string, initiallyExpanded?: boolean }, { isExpanded: boolean }> {
|
||||
state = {
|
||||
header: this.props.header,
|
||||
isExpanded: !!this.props.initiallyExpanded
|
||||
@@ -75,7 +75,7 @@ function HelpSection(props: { header: string }) {
|
||||
return <div className='msp-simple-help-section'>{props.header}</div>;
|
||||
}
|
||||
|
||||
export class ViewportHelpContent extends PluginUIComponent {
|
||||
export class ViewportHelpContent extends PluginUIComponent<{ selectOnly?: boolean }> {
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
|
||||
}
|
||||
@@ -87,10 +87,10 @@ export class ViewportHelpContent extends PluginUIComponent {
|
||||
if (bindings) Object.assign(interactionBindings, bindings);
|
||||
});
|
||||
return <>
|
||||
{this.plugin.canvas3d && <HelpGroup key='trackball' header='Moving in 3D'>
|
||||
{(!this.props.selectOnly && this.plugin.canvas3d) && <HelpGroup key='trackball' header='Moving in 3D'>
|
||||
<BindingsHelp bindings={this.plugin.canvas3d.props.trackball.bindings} />
|
||||
</HelpGroup>}
|
||||
<HelpGroup key='interactions' header='Select, Highlight, Focus'>
|
||||
<HelpGroup key='interactions' header='Mouse Controls'>
|
||||
<BindingsHelp bindings={interactionBindings} />
|
||||
</HelpGroup>
|
||||
</>;
|
||||
|
||||
@@ -16,9 +16,7 @@ import { Button, ExpandGroup } from '../controls/common';
|
||||
import { CameraHelperProps } from '../../mol-canvas3d/helper/camera-helper';
|
||||
import GetApp from '@material-ui/icons/GetApp';
|
||||
import Launch from '@material-ui/icons/Launch';
|
||||
import Warning from '@material-ui/icons/Warning';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { Icon } from '../controls/icons';
|
||||
import { StateExportImportControls, LocalStateSnapshotParams } from '../state/snapshots';
|
||||
|
||||
interface ImageControlsState {
|
||||
@@ -152,9 +150,6 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
|
||||
<ExpandGroup header='Save Options' initiallyExpanded={false} noOffset>
|
||||
<LocalStateSnapshotParams />
|
||||
</ExpandGroup>
|
||||
<div className='msp-help-text' style={{ padding: '10px'}}>
|
||||
<Icon svg={Warning} /> This is an experimental feature and stored states might not be openable in a future version.
|
||||
</div>
|
||||
</ExpandGroup>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -18,19 +18,19 @@ import { SizeTheme } from '../../../../mol-theme/size';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { PluginCommands } from '../../../commands';
|
||||
import { PluginContext } from '../../../context';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
|
||||
const StructureFocusRepresentationParams = (plugin: PluginContext) => {
|
||||
const reprParams = StateTransforms.Representation.StructureRepresentation3D.definition.params!(void 0, plugin) as PD.Params;
|
||||
return {
|
||||
// TODO: min = 0 to turn them off?
|
||||
expandRadius: PD.Numeric(5, { min: 1, max: 10, step: 1 }),
|
||||
targetParams: PD.Group(reprParams, {
|
||||
label: 'Target',
|
||||
customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', size: 'physical', typeParams: { sizeFactor: 0.17 } })
|
||||
customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', size: 'physical', typeParams: { sizeFactor: 0.17 }, colorParams: { carbon: Color.fromRgb(100, 100, 100) } })
|
||||
}),
|
||||
surroundingsParams: PD.Group(reprParams, {
|
||||
label: 'Surroundings',
|
||||
customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', color: 'element-symbol', size: 'physical', typeParams: { sizeFactor: 0.16 } })
|
||||
customDefault: createStructureRepresentationParams(plugin, void 0, { type: 'ball-and-stick', color: 'element-symbol', size: 'physical', typeParams: { sizeFactor: 0.16 }, colorParams: { carbon: Color.fromRgb(160, 160, 160) } })
|
||||
}),
|
||||
nciParams: PD.Group(reprParams, {
|
||||
label: 'Non-covalent Int.',
|
||||
|
||||
@@ -42,6 +42,9 @@ export const PluginConfig = {
|
||||
Download: {
|
||||
DefaultPdbProvider: item<PdbDownloadProvider>('download.default-pdb-provider', 'pdbe'),
|
||||
DefaultEmdbProvider: item<EmdbDownloadProvider>('download.default-emdb-provider', 'pdbe'),
|
||||
},
|
||||
Structure: {
|
||||
SizeThresholds: item('structure.size-thresholds', Structure.DefaultSizeThresholds),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -61,8 +64,8 @@ export class PluginConfigManager {
|
||||
this._config.delete(key);
|
||||
}
|
||||
|
||||
constructor(initial?: Map<PluginConfigItem, unknown>) {
|
||||
constructor(initial?: [PluginConfigItem, unknown][]) {
|
||||
if (!initial) return;
|
||||
initial.forEach((v, k) => this._config.set(k, v));
|
||||
initial.forEach(([k, v]) => this._config.set(k, v));
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,9 @@ export class PluginContext {
|
||||
state: {
|
||||
isAnimating: this.ev.behavior<boolean>(false),
|
||||
isUpdating: this.ev.behavior<boolean>(false),
|
||||
// TODO: should there be separate "updated" event?
|
||||
// Often, this is used to indicate that the state has updated
|
||||
// and it might not be the best way to react to state updates.
|
||||
isBusy: this.ev.behavior<boolean>(false)
|
||||
},
|
||||
interaction: {
|
||||
@@ -134,6 +137,11 @@ export class PluginContext {
|
||||
return this.state.data.build();
|
||||
}
|
||||
|
||||
readonly helpers = {
|
||||
substructureParent: new SubstructureParentHelper(this),
|
||||
viewportScreenshot: void 0 as ViewportScreenshotHelper | undefined
|
||||
} as const;
|
||||
|
||||
readonly managers = {
|
||||
structure: {
|
||||
hierarchy: new StructureHierarchyManager(this),
|
||||
@@ -161,11 +169,6 @@ export class PluginContext {
|
||||
readonly customStructureControls = new Map<string, { new(): PluginUIComponent<any, any, any> }>();
|
||||
readonly genericRepresentationControls = new Map<string, (selection: StructureHierarchyManager['selection']) => [StructureHierarchyRef[], string]>();
|
||||
|
||||
readonly helpers = {
|
||||
substructureParent: new SubstructureParentHelper(this),
|
||||
viewportScreenshot: void 0 as ViewportScreenshotHelper | undefined
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Used to store application specific custom state which is then available
|
||||
* to State Actions and similar constructs via the PluginContext.
|
||||
@@ -240,6 +243,7 @@ export class PluginContext {
|
||||
this.state.dispose();
|
||||
this.tasks.dispose();
|
||||
this.layout.dispose();
|
||||
this.helpers.substructureParent.dispose();
|
||||
|
||||
objectForEach(this.managers, m => (m as any)?.dispose?.());
|
||||
objectForEach(this.managers.structure, m => (m as any)?.dispose?.());
|
||||
@@ -257,23 +261,25 @@ export class PluginContext {
|
||||
|
||||
let timeout: any = void 0;
|
||||
const setBusy = () => {
|
||||
isBusy.next(true);
|
||||
if (!isBusy.value) isBusy.next(true);
|
||||
};
|
||||
const reset = () => {
|
||||
if (timeout !== void 0) clearTimeout(timeout);
|
||||
timeout = void 0;
|
||||
};
|
||||
|
||||
merge(this.behaviors.state.isUpdating, this.behaviors.state.isAnimating).subscribe(v => {
|
||||
const isUpdating = this.behaviors.state.isUpdating.value;
|
||||
const isAnimating = this.behaviors.state.isAnimating.value;
|
||||
|
||||
if ((isUpdating || isAnimating) && !isBusy.value) {
|
||||
if (timeout !== void 0) clearTimeout(timeout);
|
||||
timeout = setTimeout(setBusy, timeoutMs);
|
||||
// isBusy.next(true);
|
||||
} else {
|
||||
if (timeout !== void 0) clearTimeout(timeout);
|
||||
timeout = void 0;
|
||||
if (isBusy.value) {
|
||||
isBusy.next(false);
|
||||
if (isUpdating || isAnimating) {
|
||||
if (!isBusy.value) {
|
||||
reset();
|
||||
timeout = setTimeout(setBusy, timeoutMs);
|
||||
}
|
||||
} else {
|
||||
reset();
|
||||
isBusy.next(false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import { Plugin } from '../mol-plugin-ui/plugin';
|
||||
import { PluginBehaviors } from './behavior';
|
||||
import { StructureFocusRepresentation } from './behavior/dynamic/selection/structure-focus-representation';
|
||||
import { BoxifyVolumeStreaming, CreateVolumeStreamingBehavior, InitVolumeStreaming } from './behavior/dynamic/volume-streaming/transformers';
|
||||
import { PluginConfig } from './config';
|
||||
import { PluginContext } from './context';
|
||||
import { PluginSpec } from './spec';
|
||||
import { AssignColorVolume } from '../mol-plugin-state/actions/volume';
|
||||
@@ -89,11 +88,7 @@ export const DefaultPluginSpec: PluginSpec = {
|
||||
AnimateAssemblyUnwind,
|
||||
AnimateUnitsExplode,
|
||||
AnimateStateInterpolation
|
||||
],
|
||||
config: new Map([
|
||||
[PluginConfig.State.DefaultServer, 'https://webchem.ncbr.muni.cz/molstar-state'],
|
||||
[PluginConfig.VolumeStreaming.DefaultServer, 'https://ds.litemol.org']
|
||||
])
|
||||
]
|
||||
};
|
||||
|
||||
export function createPlugin(target: HTMLElement, spec?: PluginSpec): PluginContext {
|
||||
|
||||
@@ -30,7 +30,7 @@ interface PluginSpec {
|
||||
controls?: React.ComponentClass
|
||||
}
|
||||
},
|
||||
config?: Map<PluginConfigItem, unknown>
|
||||
config?: [PluginConfigItem, unknown][]
|
||||
}
|
||||
|
||||
namespace PluginSpec {
|
||||
|
||||
@@ -114,6 +114,12 @@ class PluginState extends PluginComponent {
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.behaviors.cells.forEach(cell => {
|
||||
if (PluginBehavior.Behavior.is(cell.obj)) {
|
||||
cell.obj.data.unregister();
|
||||
}
|
||||
});
|
||||
|
||||
super.dispose();
|
||||
this.data.dispose();
|
||||
this.behaviors.dispose();
|
||||
|
||||
@@ -8,10 +8,18 @@ import { Structure } from '../../mol-model/structure';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { State, StateObject, StateObjectCell, StateSelection } from '../../mol-state';
|
||||
import { PluginContext } from '../context';
|
||||
import { RxEventHelper } from '../../mol-util/rx-event-helper';
|
||||
|
||||
export { SubstructureParentHelper };
|
||||
|
||||
class SubstructureParentHelper {
|
||||
private ev = RxEventHelper.create();
|
||||
|
||||
readonly events = {
|
||||
updated: this.ev<{ ref: string, oldObj: PluginStateObject.Molecule.Structure | undefined, obj: PluginStateObject.Molecule.Structure }>(),
|
||||
removed: this.ev<{ ref: string, obj: PluginStateObject.Molecule.Structure | undefined }>(),
|
||||
}
|
||||
|
||||
// private decorators = new Map<string, string[]>();
|
||||
private root = new Map<Structure, { ref: string, count: number }>();
|
||||
private tracked = new Map<string, Structure>();
|
||||
@@ -36,7 +44,7 @@ class SubstructureParentHelper {
|
||||
}
|
||||
|
||||
private addMapping(state: State, ref: string, obj: StateObject) {
|
||||
if (!PluginStateObject.Molecule.Structure.is(obj)) return;
|
||||
if (!PluginStateObject.Molecule.Structure.is(obj)) return false;
|
||||
|
||||
this.tracked.set(ref, obj.data);
|
||||
|
||||
@@ -53,10 +61,11 @@ class SubstructureParentHelper {
|
||||
this.root.set(obj.data, { ref: parent.transform.ref, count: 1 });
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private removeMapping(ref: string) {
|
||||
if (!this.tracked.has(ref)) return;
|
||||
if (!this.tracked.has(ref)) return false;
|
||||
|
||||
const s = this.tracked.get(ref)!;
|
||||
this.tracked.delete(ref);
|
||||
@@ -68,13 +77,19 @@ class SubstructureParentHelper {
|
||||
} else {
|
||||
this.root.delete(s);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private updateMapping(state: State, ref: string, oldObj: StateObject | undefined, obj: StateObject) {
|
||||
if (!PluginStateObject.Molecule.Structure.is(obj)) return;
|
||||
if (!PluginStateObject.Molecule.Structure.is(obj)) return false;
|
||||
|
||||
this.removeMapping(ref);
|
||||
this.addMapping(state, ref, obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.ev.dispose();
|
||||
}
|
||||
|
||||
constructor(private plugin: PluginContext) {
|
||||
@@ -83,11 +98,15 @@ class SubstructureParentHelper {
|
||||
});
|
||||
|
||||
plugin.state.data.events.object.removed.subscribe(e => {
|
||||
this.removeMapping(e.ref);
|
||||
if (this.removeMapping(e.ref)) {
|
||||
this.events.removed.next({ ref: e.ref, obj: e.obj });
|
||||
}
|
||||
});
|
||||
|
||||
plugin.state.data.events.object.updated.subscribe(e => {
|
||||
this.updateMapping(e.state, e.ref, e.oldObj, e.obj);
|
||||
if (this.updateMapping(e.state, e.ref, e.oldObj, e.obj)) {
|
||||
this.events.updated.next({ ref: e.ref, oldObj: e.oldObj, obj: e.obj });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ interface ComplexVisualBuilder<P extends StructureParams, G extends Geometry> {
|
||||
createGeometry(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<P>, geometry?: G): Promise<G> | G
|
||||
createLocationIterator(structure: Structure): LocationIterator
|
||||
getLoci(pickingId: PickingId, structure: Structure, id: number): Loci
|
||||
eachLocation(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean): boolean,
|
||||
eachLocation(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean, isMarking: boolean): boolean,
|
||||
setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure): void
|
||||
}
|
||||
|
||||
@@ -173,11 +173,11 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
|
||||
return false;
|
||||
}
|
||||
|
||||
function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
|
||||
function lociApply(loci: Loci, apply: (interval: Interval) => boolean, isMarking: boolean) {
|
||||
if (lociIsSuperset(loci)) {
|
||||
return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount));
|
||||
} else {
|
||||
return eachLocation(loci, currentStructure, apply);
|
||||
return eachLocation(loci, currentStructure, apply, isMarking);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
if (!_structure && !structure) {
|
||||
throw new Error('missing structure');
|
||||
} else if (structure && !_structure) {
|
||||
// console.log(label, 'initial structure')
|
||||
// console.log(label, 'initial structure');
|
||||
// First call with a structure, create visuals for each group.
|
||||
_groups = structure.unitSymmetryGroups;
|
||||
for (let i = 0; i < _groups.length; i++) {
|
||||
@@ -75,7 +75,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
const group = _groups[i];
|
||||
const visualGroup = oldVisuals.get(group.hashCode);
|
||||
if (visualGroup) {
|
||||
// console.log(label, 'found visualGroup to reuse')
|
||||
// console.log(label, 'found visualGroup to reuse');
|
||||
// console.log('old', visualGroup.group)
|
||||
// console.log('new', group)
|
||||
const { visual } = visualGroup;
|
||||
@@ -91,7 +91,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
applyMarkerAction(arr, Interval.ofBounds(0, arr.length), MarkerAction.RemoveHighlight);
|
||||
}
|
||||
} else {
|
||||
// console.log(label, 'did not find visualGroup to reuse, creating new')
|
||||
// console.log(label, 'did not find visualGroup to reuse, creating new');
|
||||
// newGroups.push(group)
|
||||
const visual = visualCtor(materialId);
|
||||
const promise = visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, { group, structure });
|
||||
@@ -118,7 +118,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
// })
|
||||
// unusedVisuals.forEach(visual => visual.destroy())
|
||||
} else if (structure && structure !== _structure && Structure.areEquivalent(structure, _structure)) {
|
||||
// console.log(label, 'structures equivalent but not identical')
|
||||
// console.log(label, 'structures equivalent but not identical');
|
||||
// Expects that for structures with the same hashCode,
|
||||
// the unitSymmetryGroups are the same as well.
|
||||
// Re-uses existing visuals for the groups of the new structure.
|
||||
@@ -132,14 +132,13 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
|
||||
const promise = visualGroup.visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, { group, structure });
|
||||
if (promise) await promise;
|
||||
visualGroup.group = group;
|
||||
|
||||
} else {
|
||||
throw new Error(`expected to find visual for hashCode ${group.hashCode}`);
|
||||
}
|
||||
if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length });
|
||||
}
|
||||
} else {
|
||||
// console.log(label, 'no new structure')
|
||||
// console.log(label, 'no new structure');
|
||||
// No new structure given, just update all visuals with new props.
|
||||
const visualsList: [ UnitsVisual<P>, Unit.SymmetryGroup ][] = []; // TODO avoid allocation
|
||||
visuals.forEach(({ visual, group }) => visualsList.push([ visual, group ]));
|
||||
|
||||
@@ -54,7 +54,7 @@ interface UnitsVisualBuilder<P extends StructureParams, G extends Geometry> {
|
||||
createGeometry(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<P>, geometry?: G): Promise<G> | G
|
||||
createLocationIterator(structureGroup: StructureGroup): LocationIterator
|
||||
getLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number): Loci
|
||||
eachLocation(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean): boolean
|
||||
eachLocation(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean, isMarking: boolean): boolean
|
||||
setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructureGroup: StructureGroup, currentStructureGroup: StructureGroup): void
|
||||
}
|
||||
|
||||
@@ -224,11 +224,11 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
|
||||
return false;
|
||||
}
|
||||
|
||||
function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
|
||||
function lociApply(loci: Loci, apply: (interval: Interval) => boolean, isMarking: boolean) {
|
||||
if (lociIsSuperset(loci)) {
|
||||
return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount));
|
||||
} else {
|
||||
return eachLocation(loci, currentStructureGroup, apply);
|
||||
return eachLocation(loci, currentStructureGroup, apply, isMarking);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ function getBondLoci(pickingId: PickingId, structure: Structure, id: number) {
|
||||
return EmptyLoci;
|
||||
}
|
||||
|
||||
function eachBond(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
|
||||
function eachBond(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean, isMarking: boolean) {
|
||||
let changed = false;
|
||||
if (Bond.isLoci(loci)) {
|
||||
if (!Structure.areEquivalent(loci.structure, structure)) return false;
|
||||
@@ -181,7 +181,7 @@ function eachBond(loci: Loci, structure: Structure, apply: (interval: Interval)
|
||||
OrderedSet.forEach(e.indices, v => {
|
||||
if (!b.connectedIndices.includes(v)) return;
|
||||
b.getEdges(v).forEach(bi => {
|
||||
if (OrderedSet.has(otherLociIndices, bi.indexB)) {
|
||||
if (!isMarking || OrderedSet.has(otherLociIndices, bi.indexB)) {
|
||||
const idx = structure.interUnitBonds.getEdgeIndex(v, unit, bi.indexB, b.unitB);
|
||||
if (apply(Interval.ofSingleton(idx))) changed = true;
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ function getBondLoci(pickingId: PickingId, structureGroup: StructureGroup, id: n
|
||||
return EmptyLoci;
|
||||
}
|
||||
|
||||
function eachBond(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
|
||||
function eachBond(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean, isMarking: boolean) {
|
||||
let changed = false;
|
||||
if (Bond.isLoci(loci)) {
|
||||
const { structure, group } = structureGroup;
|
||||
@@ -183,7 +183,7 @@ function eachBond(loci: Loci, structureGroup: StructureGroup, apply: (interval:
|
||||
const { offset, b } = unit.bonds;
|
||||
OrderedSet.forEach(e.indices, v => {
|
||||
for (let t = offset[v], _t = offset[v + 1]; t < _t; t++) {
|
||||
if (OrderedSet.has(e.indices, b[t])) {
|
||||
if (!isMarking || OrderedSet.has(e.indices, b[t])) {
|
||||
if (apply(Interval.ofSingleton(unitIdx * groupCount + t))) changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { fillSerial } from '../../../../mol-util/array';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { AssignableArrayLike } from '../../../../mol-util/type-helpers';
|
||||
import { getBoundary } from '../../../../mol-math/geometry/boundary';
|
||||
import { Box3D } from '../../../../mol-math/geometry';
|
||||
|
||||
/** Return a Loci for the elements of a whole residue the elementIndex belongs to. */
|
||||
export function getResidueLoci(structure: Structure, unit: Unit.Atomic, elementIndex: ElementIndex): Loci {
|
||||
@@ -99,6 +100,16 @@ export function includesUnitKind(unitKinds: UnitKind[], unit: Unit) {
|
||||
|
||||
//
|
||||
|
||||
const MaxCells = 500_000_000;
|
||||
|
||||
/** guard against overly high resolution for the given box size */
|
||||
export function ensureReasonableResolution<T>(box: Box3D, props: { resolution: number } & T) {
|
||||
const volume = Box3D.volume(box);
|
||||
const approxCells = volume / props.resolution;
|
||||
const resolution = approxCells > MaxCells ? volume / MaxCells : props.resolution;
|
||||
return { ...props, resolution };
|
||||
}
|
||||
|
||||
export function getConformation(unit: Unit) {
|
||||
switch (unit.kind) {
|
||||
case Unit.Kind.Atomic: return unit.model.atomicConformation;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { GaussianDensityTexture, GaussianDensityTexture2d } from '../../../../mol-math/geometry/gaussian-density/gpu';
|
||||
import { Texture } from '../../../../mol-gl/webgl/texture';
|
||||
import { WebGLContext } from '../../../../mol-gl/webgl/context';
|
||||
import { getUnitConformationAndRadius, getStructureConformationAndRadius, CommonSurfaceParams } from './common';
|
||||
import { getUnitConformationAndRadius, getStructureConformationAndRadius, CommonSurfaceParams, ensureReasonableResolution } from './common';
|
||||
import { BaseGeometry } from '../../../../mol-geo/geometry/base';
|
||||
|
||||
const SharedGaussianDensityParams = {
|
||||
@@ -37,38 +37,48 @@ export type GaussianDensityTextureProps = typeof DefaultGaussianDensityTexturePr
|
||||
//
|
||||
|
||||
export function computeUnitGaussianDensity(structure: Structure, unit: Unit, props: GaussianDensityProps, webgl?: WebGLContext) {
|
||||
const { position, radius } = getUnitConformationAndRadius(structure, unit, props);
|
||||
const { box } = unit.lookup3d.boundary;
|
||||
const p = ensureReasonableResolution(box, props);
|
||||
const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
|
||||
return Task.create('Gaussian Density', async ctx => {
|
||||
return await GaussianDensity(ctx, position, unit.lookup3d.boundary.box, radius, props, webgl);
|
||||
return await GaussianDensity(ctx, position, box, radius, p, webgl);
|
||||
});
|
||||
}
|
||||
|
||||
export function computeUnitGaussianDensityTexture(structure: Structure, unit: Unit, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) {
|
||||
const { position, radius } = getUnitConformationAndRadius(structure, unit, props);
|
||||
const { box } = unit.lookup3d.boundary;
|
||||
const p = ensureReasonableResolution(box, props);
|
||||
const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
|
||||
return Task.create('Gaussian Density', async ctx => {
|
||||
return GaussianDensityTexture(webgl, position, unit.lookup3d.boundary.box, radius, props, texture);
|
||||
return GaussianDensityTexture(webgl, position, box, radius, p, texture);
|
||||
});
|
||||
}
|
||||
|
||||
export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) {
|
||||
const { position, radius } = getUnitConformationAndRadius(structure, unit, props);
|
||||
const { box } = unit.lookup3d.boundary;
|
||||
const p = ensureReasonableResolution(box, props);
|
||||
const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
|
||||
return Task.create('Gaussian Density', async ctx => {
|
||||
return GaussianDensityTexture2d(webgl, position, unit.lookup3d.boundary.box, radius, props, texture);
|
||||
return GaussianDensityTexture2d(webgl, position, box, radius, p, texture);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export function computeStructureGaussianDensity(structure: Structure, props: GaussianDensityProps, webgl?: WebGLContext) {
|
||||
const { box } = structure.lookup3d.boundary;
|
||||
const p = ensureReasonableResolution(box, props);
|
||||
const { position, radius } = getStructureConformationAndRadius(structure, props.ignoreHydrogens, props.traceOnly);
|
||||
return Task.create('Gaussian Density', async ctx => {
|
||||
return await GaussianDensity(ctx, position, structure.lookup3d.boundary.box, radius, props, webgl);
|
||||
return await GaussianDensity(ctx, position, box, radius, p, webgl);
|
||||
});
|
||||
}
|
||||
|
||||
export function computeStructureGaussianDensityTexture(structure: Structure, props: GaussianDensityTextureProps, webgl: WebGLContext, texture?: Texture) {
|
||||
const { box } = structure.lookup3d.boundary;
|
||||
const p = ensureReasonableResolution(box, props);
|
||||
const { position, radius } = getStructureConformationAndRadius(structure, props.ignoreHydrogens, props.traceOnly);
|
||||
return Task.create('Gaussian Density', async ctx => {
|
||||
return GaussianDensityTexture(webgl, position, structure.lookup3d.boundary.box, radius, props, texture);
|
||||
return GaussianDensityTexture(webgl, position, box, radius, p, texture);
|
||||
});
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Unit, Structure } from '../../../../mol-model/structure';
|
||||
import { Task, RuntimeContext } from '../../../../mol-task';
|
||||
import { getUnitConformationAndRadius, CommonSurfaceProps } from './common';
|
||||
import { getUnitConformationAndRadius, CommonSurfaceProps, ensureReasonableResolution } from './common';
|
||||
import { PositionData, DensityData, Box3D } from '../../../../mol-math/geometry';
|
||||
import { MolecularSurfaceCalculationProps, calcMolecularSurface } from '../../../../mol-math/geometry/molecular-surface';
|
||||
import { OrderedSet } from '../../../../mol-data/int';
|
||||
@@ -34,9 +34,10 @@ function getPositionDataAndMaxRadius(structure: Structure, unit: Unit, props: Mo
|
||||
|
||||
export function computeUnitMolecularSurface(structure: Structure, unit: Unit, props: MolecularSurfaceProps) {
|
||||
const { box } = unit.lookup3d.boundary;
|
||||
const { position, boundary, maxRadius } = getPositionDataAndMaxRadius(structure, unit, props);
|
||||
const p = ensureReasonableResolution(box, props);
|
||||
const { position, boundary, maxRadius } = getPositionDataAndMaxRadius(structure, unit, p);
|
||||
return Task.create('Molecular Surface', async ctx => {
|
||||
return await MolecularSurface(ctx, position, boundary, maxRadius, box, props);
|
||||
return await MolecularSurface(ctx, position, boundary, maxRadius, box, p);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { defaults } from '../mol-util';
|
||||
import { Structure } from '../mol-model/structure';
|
||||
import { VisualQuality } from '../mol-geo/geometry/base';
|
||||
import { Box3D } from '../mol-math/geometry';
|
||||
|
||||
export interface VisualUpdateState {
|
||||
updateTransform: boolean
|
||||
@@ -93,8 +94,10 @@ export function getQualityProps(props: Partial<QualityProps>, data?: any) {
|
||||
let resolution = defaults(props.resolution, 2);
|
||||
let doubleSided = defaults(props.doubleSided, true);
|
||||
|
||||
let volume = 0;
|
||||
if (quality === 'auto' && data instanceof Structure) {
|
||||
quality = getStructureQuality(data.root);
|
||||
volume = Box3D.volume(data.boundary.box);
|
||||
}
|
||||
|
||||
switch (quality) {
|
||||
@@ -102,49 +105,49 @@ export function getQualityProps(props: Partial<QualityProps>, data?: any) {
|
||||
detail = 3;
|
||||
radialSegments = 36;
|
||||
linearSegments = 18;
|
||||
resolution = 0.1;
|
||||
resolution = Math.max(volume / 500_000_000, 0.1);
|
||||
doubleSided = true;
|
||||
break;
|
||||
case 'higher':
|
||||
detail = 3;
|
||||
radialSegments = 28;
|
||||
linearSegments = 14;
|
||||
resolution = 0.3;
|
||||
resolution = Math.max(volume / 400_000_000, 0.3);
|
||||
doubleSided = true;
|
||||
break;
|
||||
case 'high':
|
||||
detail = 2;
|
||||
radialSegments = 20;
|
||||
linearSegments = 10;
|
||||
resolution = 0.5;
|
||||
resolution = Math.max(volume / 300_000_000, 0.5);
|
||||
doubleSided = true;
|
||||
break;
|
||||
case 'medium':
|
||||
detail = 1;
|
||||
radialSegments = 12;
|
||||
linearSegments = 8;
|
||||
resolution = 1;
|
||||
resolution = Math.max(volume / 200_000_000, 1);
|
||||
doubleSided = true;
|
||||
break;
|
||||
case 'low':
|
||||
detail = 0;
|
||||
radialSegments = 8;
|
||||
linearSegments = 3;
|
||||
resolution = 2;
|
||||
resolution = Math.max(volume / 150_000_000, 2);
|
||||
doubleSided = false;
|
||||
break;
|
||||
case 'lower':
|
||||
detail = 0;
|
||||
radialSegments = 4;
|
||||
linearSegments = 2;
|
||||
resolution = 4;
|
||||
resolution = Math.max(volume / 100_000_000, 4);
|
||||
doubleSided = false;
|
||||
break;
|
||||
case 'lowest':
|
||||
detail = 0;
|
||||
radialSegments = 2;
|
||||
linearSegments = 1;
|
||||
resolution = 8;
|
||||
resolution = Math.max(volume / 75_000_000, 8);
|
||||
doubleSided = false;
|
||||
break;
|
||||
case 'custom':
|
||||
|
||||
@@ -45,7 +45,7 @@ interface Visual<D, P extends PD.Params> {
|
||||
destroy: () => void
|
||||
}
|
||||
namespace Visual {
|
||||
export type LociApply = (loci: Loci, apply: (interval: Interval) => boolean) => boolean
|
||||
export type LociApply = (loci: Loci, apply: (interval: Interval) => boolean, isMarking: boolean) => boolean
|
||||
|
||||
export function setVisibility(renderObject: GraphicsRenderObject | undefined, visible: boolean) {
|
||||
if (renderObject) renderObject.state.visible = visible;
|
||||
@@ -70,7 +70,7 @@ namespace Visual {
|
||||
if (isEveryLoci(loci)) {
|
||||
changed = applyMarkerAction(array, Interval.ofLength(count), action);
|
||||
} else if (!isEmptyLoci(loci)) {
|
||||
changed = lociApply(loci, interval => applyMarkerAction(array, interval, action));
|
||||
changed = lociApply(loci, interval => applyMarkerAction(array, interval, action), true);
|
||||
}
|
||||
if (changed) ValueCell.update(tMarker, tMarker.ref.value);
|
||||
return changed;
|
||||
@@ -98,7 +98,7 @@ namespace Visual {
|
||||
? clearOverpaint(array, start, end)
|
||||
: applyOverpaintColor(array, start, end, color, overpaint.alpha);
|
||||
};
|
||||
lociApply(loci, apply);
|
||||
lociApply(loci, apply, false);
|
||||
}
|
||||
ValueCell.update(tOverpaint, tOverpaint.ref.value);
|
||||
}
|
||||
@@ -123,7 +123,7 @@ namespace Visual {
|
||||
const end = Interval.end(interval);
|
||||
return applyTransparencyValue(array, start, end, value);
|
||||
};
|
||||
lociApply(loci, apply);
|
||||
lociApply(loci, apply, false);
|
||||
|
||||
ValueCell.update(tTransparency, tTransparency.ref.value);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ const DefaultElementSymbolColor = Color(0xFFFFFF);
|
||||
const Description = 'Assigns a color to every atom according to its chemical element.';
|
||||
|
||||
export const ElementSymbolColorThemeParams = {
|
||||
carbon: PD.Color(ElementSymbolColors.C),
|
||||
saturation: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
|
||||
lightness: PD.Numeric(0.7, { min: -6, max: 6, step: 0.1 })
|
||||
};
|
||||
@@ -39,17 +40,20 @@ export function elementSymbolColor(colorMap: ElementSymbolColors, element: Eleme
|
||||
|
||||
export function ElementSymbolColorTheme(ctx: ThemeDataContext, props: PD.Values<ElementSymbolColorThemeParams>): ColorTheme<ElementSymbolColorThemeParams> {
|
||||
const colorMap = getAdjustedColorMap(ElementSymbolColors, props.saturation, props.lightness);
|
||||
const carbonColor = Color.darken(Color.saturate(props.carbon, props.saturation), -props.lightness);
|
||||
|
||||
function color(location: Location): Color {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
if (Unit.isAtomic(location.unit)) {
|
||||
const { type_symbol } = location.unit.model.atomicHierarchy.atoms;
|
||||
return elementSymbolColor(colorMap, type_symbol.value(location.element));
|
||||
const element = type_symbol.value(location.element);
|
||||
return element === 'C' ? carbonColor : elementSymbolColor(colorMap, element);
|
||||
}
|
||||
} else if (Bond.isLocation(location)) {
|
||||
if (Unit.isAtomic(location.aUnit)) {
|
||||
const { type_symbol } = location.aUnit.model.atomicHierarchy.atoms;
|
||||
return elementSymbolColor(colorMap, type_symbol.value(location.aUnit.elements[location.aIndex]));
|
||||
const element = type_symbol.value(location.aUnit.elements[location.aIndex]);
|
||||
return element === 'C' ? carbonColor : elementSymbolColor(colorMap, element);
|
||||
}
|
||||
}
|
||||
return DefaultElementSymbolColor;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -16,13 +16,12 @@ let isProductionMode = process.env.NODE_ENV === 'production';
|
||||
*/
|
||||
let isDebugMode = process.env.DEBUG === '*' || process.env.DEBUG === 'molstar';
|
||||
|
||||
if (typeof window !== 'undefined' && !(window as any).setMolStarDebugMode) {
|
||||
try {
|
||||
(window as any).setMolStarDebugMode = function setMolStarDebugMode(isDebug?: boolean, isProduction?: boolean) {
|
||||
if (typeof isDebug !== 'undefined') isDebugMode = isDebug;
|
||||
if (typeof isProduction !== 'undefined') isProductionMode = isProduction;
|
||||
};
|
||||
} catch { }
|
||||
export { isProductionMode, isDebugMode };
|
||||
|
||||
export function setProductionMode(value?: boolean) {
|
||||
if (typeof value !== 'undefined') isProductionMode = value;
|
||||
}
|
||||
|
||||
export { isProductionMode, isDebugMode };
|
||||
export function setDebugMode(value?: boolean) {
|
||||
if (typeof value !== 'undefined') isDebugMode = value;
|
||||
}
|
||||
Reference in New Issue
Block a user