mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 22:31:26 +08:00
Compare commits
90 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 | ||
|
|
b1ece44c49 | ||
|
|
37ae274fb6 | ||
|
|
c900045fcd | ||
|
|
50d95ccf6a | ||
|
|
c9171444eb | ||
|
|
56ea62af71 | ||
|
|
9e81626928 | ||
|
|
84fda6e35d | ||
|
|
0f758cf554 | ||
|
|
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.16",
|
||||
"version": "0.7.1-dev.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.7.0-dev.16",
|
||||
"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": [
|
||||
|
||||
@@ -117,7 +117,7 @@ export function printSequence(model: Model) {
|
||||
for (const key of Object.keys(byEntityKey)) {
|
||||
const { sequence, entityId } = byEntityKey[+key];
|
||||
const { seqId, compId } = sequence;
|
||||
console.log(`${entityId} (${sequence.kind} ${seqId.value(0)} (offset ${sequence.offset}), ${seqId.value(seqId.rowCount - 1)}) (${compId.value(0)}, ${compId.value(compId.rowCount - 1)})`);
|
||||
console.log(`${entityId} (${sequence.kind} ${seqId.value(0)}, ${seqId.value(seqId.rowCount - 1)}) (${compId.value(0)}, ${compId.value(compId.rowCount - 1)})`);
|
||||
console.log(`${Sequence.getSequenceString(sequence)}`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
@@ -8,38 +8,36 @@ import * as fs from 'fs';
|
||||
import * as argparse from 'argparse';
|
||||
import * as util from 'util';
|
||||
|
||||
import { VolumeData, VolumeIsoValue } from '../../mol-model/volume';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { downloadCif } from './helpers';
|
||||
import { CIF } from '../../mol-io/reader/cif';
|
||||
import { DensityServer_Data_Database } from '../../mol-io/reader/cif/schema/density-server';
|
||||
import { Table } from '../../mol-data/db';
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
import { Task } from '../../mol-task';
|
||||
import { createVolumeIsosurfaceMesh } from '../../mol-repr/volume/isosurface';
|
||||
import { Theme } from '../../mol-theme/theme';
|
||||
import { volumeFromDensityServerData } from '../../mol-model-formats/volume/density-server';
|
||||
import { volumeFromDensityServerData, DscifFormat } from '../../mol-model-formats/volume/density-server';
|
||||
|
||||
require('util.promisify').shim();
|
||||
const writeFileAsync = util.promisify(fs.writeFile);
|
||||
|
||||
type Volume = { source: DensityServer_Data_Database, volume: VolumeData }
|
||||
|
||||
async function getVolume(url: string): Promise<Volume> {
|
||||
const cif = await downloadCif(url, true);
|
||||
const data = CIF.schema.densityServer(cif.blocks[1]);
|
||||
return { source: data, volume: await volumeFromDensityServerData(data).run() };
|
||||
return await volumeFromDensityServerData(data).run();
|
||||
}
|
||||
|
||||
function print(data: Volume) {
|
||||
const { volume_data_3d_info } = data.source;
|
||||
function print(volume: Volume) {
|
||||
if (!DscifFormat.is(volume.sourceData)) return;
|
||||
const { volume_data_3d_info } = volume.sourceData.data;
|
||||
const row = Table.getRow(volume_data_3d_info, 0);
|
||||
console.log(row);
|
||||
if (data.volume.transform) console.log(data.volume.transform);
|
||||
console.log(data.volume.dataStats);
|
||||
console.log(volume.grid.transform);
|
||||
console.log(volume.grid.stats);
|
||||
}
|
||||
|
||||
async function doMesh(data: Volume, filename: string) {
|
||||
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, data.volume, Theme.createEmpty(), { isoValue: VolumeIsoValue.absolute(1.5) } )).run();
|
||||
async function doMesh(volume: Volume, filename: string) {
|
||||
const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) } )).run();
|
||||
console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
|
||||
|
||||
// Export the mesh in OBJ format.
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
var viewer = new molstar.Viewer('app', {
|
||||
extensions: [],
|
||||
|
||||
layoutIsExpanded: false,
|
||||
layoutShowControls: false,
|
||||
layoutShowRemoteState: false,
|
||||
|
||||
@@ -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);
|
||||
@@ -110,7 +114,7 @@ export class Viewer {
|
||||
}
|
||||
|
||||
async loadStructureFromUrl(url: string, format = 'cif', isBinary = false) {
|
||||
const params = DownloadStructure.createDefaultParams(undefined, this.plugin);
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'url',
|
||||
@@ -125,7 +129,7 @@ export class Viewer {
|
||||
}
|
||||
|
||||
async loadPdb(pdb: string) {
|
||||
const params = DownloadStructure.createDefaultParams(undefined, this.plugin);
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!;
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
@@ -145,12 +149,15 @@ export class Viewer {
|
||||
}
|
||||
|
||||
async loadPdbDev(pdbDev: string) {
|
||||
const params = DownloadStructure.createDefaultParams(undefined, this.plugin);
|
||||
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
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 };
|
||||
}
|
||||
});
|
||||
@@ -5,17 +5,20 @@
|
||||
*/
|
||||
|
||||
import { CustomStructureProperty } from '../../mol-model-props/common/custom-structure-property';
|
||||
import { Structure, CustomPropertyDescriptor } from '../../mol-model/structure';
|
||||
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}` });
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { toTable } from '../../mol-io/reader/cif/schema';
|
||||
import { CifWriter } from '../../mol-io/writer/cif';
|
||||
import { Model, CustomPropertyDescriptor } from '../../mol-model/structure';
|
||||
import { Model } from '../../mol-model/structure';
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
|
||||
export namespace PDBePreferredAssembly {
|
||||
export type Property = string
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { toTable } from '../../mol-io/reader/cif/schema';
|
||||
import { CifWriter } from '../../mol-io/writer/cif';
|
||||
import { Model, CustomPropertyDescriptor } from '../../mol-model/structure';
|
||||
import { Model } from '../../mol-model/structure';
|
||||
import { PropertyWrapper } from '../../mol-model-props/common/wrapper';
|
||||
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
|
||||
export namespace PDBeStructRefDomain {
|
||||
export type Property = PropertyWrapper<Table<Schema['pdbe_struct_ref_domain']> | undefined>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Column, Table } from '../../../mol-data/db';
|
||||
import { toTable } from '../../../mol-io/reader/cif/schema';
|
||||
import { mmCIF_residueId_schema } from '../../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
import { CifWriter } from '../../../mol-io/writer/cif';
|
||||
import { Model, CustomPropertyDescriptor, ResidueIndex, Unit, IndexedCustomProperty } from '../../../mol-model/structure';
|
||||
import { Model, ResidueIndex, Unit, IndexedCustomProperty } from '../../../mol-model/structure';
|
||||
import { residueIdFields } from '../../../mol-model/structure/export/categories/atom_site';
|
||||
import { StructureElement, CifExportContext, Structure } from '../../../mol-model/structure/structure';
|
||||
import { CustomPropSymbol } from '../../../mol-script/language/symbol';
|
||||
@@ -22,6 +22,7 @@ import { PropertyWrapper } from '../../../mol-model-props/common/wrapper';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
|
||||
import { Asset } from '../../../mol-util/assets';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
|
||||
|
||||
export { StructureQualityReport };
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { AssemblySymmetryQuery, AssemblySymmetryQueryVariables } from '../graphq
|
||||
import query from '../graphql/symmetry.gql';
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { CustomPropertyDescriptor, Structure, Model, StructureSelection, QueryContext } from '../../../mol-model/structure';
|
||||
import { Structure, Model, StructureSelection, QueryContext } from '../../../mol-model/structure';
|
||||
import { Database as _Database, Column } from '../../../mol-data/db';
|
||||
import { GraphQLClient } from '../../../mol-util/graphql-client';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
@@ -19,6 +19,7 @@ import { ReadonlyVec3 } from '../../../mol-math/linear-algebra/3d/vec3';
|
||||
import { SetUtils } from '../../../mol-util/set';
|
||||
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
|
||||
import { compile } from '../../../mol-script/runtime/query/compiler';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
|
||||
|
||||
const BiologicalAssemblyNames = new Set([
|
||||
'author_and_software_defined_assembly',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { CustomPropertyDescriptor, Structure, Unit } from '../../../mol-model/structure';
|
||||
import { Structure, Unit } from '../../../mol-model/structure';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
|
||||
import { Model, ElementIndex, ResidueIndex } from '../../../mol-model/structure/model';
|
||||
@@ -21,6 +21,7 @@ import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
|
||||
import { CustomPropSymbol } from '../../../mol-script/language/symbol';
|
||||
import Type from '../../../mol-script/language/type';
|
||||
import { Asset } from '../../../mol-util/assets';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
|
||||
|
||||
export { ValidationReport };
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -10,7 +10,7 @@ import { RuntimeContext } from '../../../mol-task';
|
||||
import UUID from '../../../mol-util/uuid';
|
||||
import { Model } from '../../../mol-model/structure/model/model';
|
||||
import { Entities } from '../../../mol-model/structure/model/properties/common';
|
||||
import { CustomProperties } from '../../../mol-model/structure';
|
||||
import { CustomProperties } from '../../../mol-model/custom-property';
|
||||
import { getAtomicHierarchyAndConformation } from './atomic';
|
||||
import { getCoarse, EmptyCoarse, CoarseData } from './coarse';
|
||||
import { getSequence } from './sequence';
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CustomPropertyDescriptor, Model } from '../../../mol-model/structure';
|
||||
import { Model } from '../../../mol-model/structure';
|
||||
import { ModelFormat } from '../../format';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
|
||||
|
||||
class FormatRegistry<T> {
|
||||
private map = new Map<ModelFormat['kind'], (model: Model) => T | undefined>()
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { Table, Column } from '../../../mol-data/db';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/structure';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
|
||||
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { CifWriter } from '../../../mol-io/writer/cif';
|
||||
import { FormatPropertyProvider } from '../common/property';
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { Model } from '../../../../mol-model/structure/model/model';
|
||||
import { BondType } from '../../../../mol-model/structure/model/types';
|
||||
import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
|
||||
import { CustomPropertyDescriptor } from '../../../../mol-model/custom-property';
|
||||
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { CifWriter } from '../../../../mol-io/writer/cif';
|
||||
import { Table } from '../../../../mol-data/db';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
|
||||
import { CustomPropertyDescriptor } from '../../../../mol-model/custom-property';
|
||||
import { IntAdjacencyGraph } from '../../../../mol-math/graph';
|
||||
import { Column } from '../../../../mol-data/db';
|
||||
import { FormatPropertyProvider } from '../../common/property';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Model } from '../../../../mol-model/structure/model/model';
|
||||
import { Structure } from '../../../../mol-model/structure';
|
||||
import { BondType } from '../../../../mol-model/structure/model/types';
|
||||
import { Column, Table } from '../../../../mol-data/db';
|
||||
import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
|
||||
import { CustomPropertyDescriptor } from '../../../../mol-model/custom-property';
|
||||
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { SortedArray } from '../../../../mol-data/int';
|
||||
import { CifWriter } from '../../../../mol-io/writer/cif';
|
||||
|
||||
@@ -13,7 +13,7 @@ import { SecondaryStructure } from '../../../mol-model/structure/model/propertie
|
||||
import { Column, Table } from '../../../mol-data/db';
|
||||
import { ChainIndex, ResidueIndex } from '../../../mol-model/structure/model/indexing';
|
||||
import { FormatPropertyProvider } from '../common/property';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/structure';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
|
||||
|
||||
export { ModelSecondaryStructure };
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/
|
||||
import { Tensor, Vec3, Mat3 } from '../../../mol-math/linear-algebra';
|
||||
import { Symmetry } from '../../../mol-model/structure/model/properties/symmetry';
|
||||
import { createAssemblies } from './assembly';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/structure';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
|
||||
import { FormatPropertyProvider } from '../common/property';
|
||||
import { Table } from '../../../mol-data/db';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { VolumeData } from '../../mol-model/volume/data';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { Task } from '../../mol-task';
|
||||
import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
|
||||
import { Tensor, Vec3 } from '../../mol-math/linear-algebra';
|
||||
@@ -14,6 +14,7 @@ import { getCcp4ValueType } from '../../mol-io/reader/ccp4/parser';
|
||||
import { TypedArrayValueType } from '../../mol-io/common/typed-array';
|
||||
import { arrayMin, arrayRms, arrayMean, arrayMax } from '../../mol-util/array';
|
||||
import { ModelFormat } from '../format';
|
||||
import { CustomProperties } from '../../mol-model/custom-property';
|
||||
|
||||
/** When available (e.g. in MRC files) use ORIGIN records instead of N[CRS]START */
|
||||
export function getCcp4Origin(header: Ccp4Header): Vec3 {
|
||||
@@ -39,8 +40,8 @@ function getTypedArrayCtor(header: Ccp4Header) {
|
||||
throw Error(`${valueType} is not a supported value format.`);
|
||||
}
|
||||
|
||||
export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, offset?: Vec3, label?: string }): Task<VolumeData> {
|
||||
return Task.create<VolumeData>('Create Volume Data', async ctx => {
|
||||
export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, offset?: Vec3, label?: string }): Task<Volume> {
|
||||
return Task.create<Volume>('Create Volume', async ctx => {
|
||||
const { header, values } = source;
|
||||
const size = Vec3.create(header.xLength, header.yLength, header.zLength);
|
||||
if (params && params.voxelSize) Vec3.mul(size, size, params.voxelSize);
|
||||
@@ -69,15 +70,19 @@ export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, of
|
||||
|
||||
return {
|
||||
label: params?.label,
|
||||
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
|
||||
data,
|
||||
dataStats: {
|
||||
min: isNaN(header.AMIN) ? arrayMin(values) : header.AMIN,
|
||||
max: isNaN(header.AMAX) ? arrayMax(values) : header.AMAX,
|
||||
mean: isNaN(header.AMEAN) ? arrayMean(values) : header.AMEAN,
|
||||
sigma: (isNaN(header.ARMS) || header.ARMS === 0) ? arrayRms(values) : header.ARMS
|
||||
grid: {
|
||||
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
|
||||
cells: data,
|
||||
stats: {
|
||||
min: isNaN(header.AMIN) ? arrayMin(values) : header.AMIN,
|
||||
max: isNaN(header.AMAX) ? arrayMax(values) : header.AMAX,
|
||||
mean: isNaN(header.AMEAN) ? arrayMean(values) : header.AMEAN,
|
||||
sigma: (isNaN(header.ARMS) || header.ARMS === 0) ? arrayRms(values) : header.ARMS
|
||||
},
|
||||
},
|
||||
sourceData: Ccp4Format.create(source)
|
||||
sourceData: Ccp4Format.create(source),
|
||||
customProperties: new CustomProperties(),
|
||||
_propertyData: Object.create(null),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
|
||||
import { CubeFile } from '../../mol-io/reader/cube/parser';
|
||||
import { Mat4, Tensor } from '../../mol-math/linear-algebra';
|
||||
import { VolumeData } from '../../mol-model/volume/data';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { Task } from '../../mol-task';
|
||||
import { arrayMax, arrayMean, arrayMin, arrayRms } from '../../mol-util/array';
|
||||
import { ModelFormat } from '../format';
|
||||
import { CustomProperties } from '../../mol-model/custom-property';
|
||||
|
||||
export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number, label?: string }): Task<VolumeData> {
|
||||
return Task.create<VolumeData>('Create Volume Data', async () => {
|
||||
export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number, label?: string }): Task<Volume> {
|
||||
return Task.create<Volume>('Create Volume', async () => {
|
||||
const { header, values: sourceValues } = source;
|
||||
const space = Tensor.Space(header.dim, [0, 1, 2], Float64Array);
|
||||
|
||||
@@ -45,15 +46,19 @@ export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number,
|
||||
|
||||
return {
|
||||
label: params?.label,
|
||||
transform: { kind: 'matrix', matrix },
|
||||
data,
|
||||
dataStats: {
|
||||
min: arrayMin(values),
|
||||
max: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: arrayRms(values)
|
||||
grid: {
|
||||
transform: { kind: 'matrix', matrix },
|
||||
cells: data,
|
||||
stats: {
|
||||
min: arrayMin(values),
|
||||
max: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: arrayRms(values)
|
||||
},
|
||||
},
|
||||
sourceData: CubeFormat.create(source)
|
||||
sourceData: CubeFormat.create(source),
|
||||
customProperties: new CustomProperties(),
|
||||
_propertyData: Object.create(null),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,14 +5,15 @@
|
||||
*/
|
||||
|
||||
import { DensityServer_Data_Database } from '../../mol-io/reader/cif/schema/density-server';
|
||||
import { VolumeData } from '../../mol-model/volume/data';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { Task } from '../../mol-task';
|
||||
import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
|
||||
import { Tensor, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { ModelFormat } from '../format';
|
||||
import { CustomProperties } from '../../mol-model/custom-property';
|
||||
|
||||
export function volumeFromDensityServerData(source: DensityServer_Data_Database): Task<VolumeData> {
|
||||
return Task.create<VolumeData>('Create Volume Data', async ctx => {
|
||||
export function volumeFromDensityServerData(source: DensityServer_Data_Database): Task<Volume> {
|
||||
return Task.create<Volume>('Create Volume', async ctx => {
|
||||
const { volume_data_3d_info: info, volume_data_3d: values } = source;
|
||||
const cell = SpacegroupCell.create(
|
||||
info.spacegroup_number.value(0),
|
||||
@@ -35,15 +36,19 @@ export function volumeFromDensityServerData(source: DensityServer_Data_Database)
|
||||
const dimensions = Vec3.ofArray(normalizeOrder(info.dimensions.value(0)));
|
||||
|
||||
return {
|
||||
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin, Vec3.add(Vec3.zero(), origin, dimensions)) },
|
||||
data,
|
||||
dataStats: {
|
||||
min: info.min_sampled.value(0),
|
||||
max: info.max_sampled.value(0),
|
||||
mean: info.mean_sampled.value(0),
|
||||
sigma: info.sigma_sampled.value(0)
|
||||
grid: {
|
||||
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin, Vec3.add(Vec3.zero(), origin, dimensions)) },
|
||||
cells: data,
|
||||
stats: {
|
||||
min: info.min_sampled.value(0),
|
||||
max: info.max_sampled.value(0),
|
||||
mean: info.mean_sampled.value(0),
|
||||
sigma: info.sigma_sampled.value(0)
|
||||
},
|
||||
},
|
||||
sourceData: DscifFormat.create(source)
|
||||
sourceData: DscifFormat.create(source),
|
||||
customProperties: new CustomProperties(),
|
||||
_propertyData: Object.create(null),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { VolumeData } from '../../mol-model/volume/data';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { Task } from '../../mol-task';
|
||||
import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
|
||||
import { Tensor, Vec3 } from '../../mol-math/linear-algebra';
|
||||
@@ -12,9 +12,10 @@ import { degToRad } from '../../mol-math/misc';
|
||||
import { Dsn6File } from '../../mol-io/reader/dsn6/schema';
|
||||
import { arrayMin, arrayMax, arrayMean, arrayRms } from '../../mol-util/array';
|
||||
import { ModelFormat } from '../format';
|
||||
import { CustomProperties } from '../../mol-model/custom-property';
|
||||
|
||||
export function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3, label?: string }): Task<VolumeData> {
|
||||
return Task.create<VolumeData>('Create Volume Data', async ctx => {
|
||||
export function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3, label?: string }): Task<Volume> {
|
||||
return Task.create<Volume>('Create Volume', async ctx => {
|
||||
const { header, values } = source;
|
||||
const size = Vec3.create(header.xlen, header.ylen, header.zlen);
|
||||
if (params && params.voxelSize) Vec3.mul(size, size, params.voxelSize);
|
||||
@@ -34,15 +35,19 @@ export function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3, la
|
||||
|
||||
return {
|
||||
label: params?.label,
|
||||
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
|
||||
data,
|
||||
dataStats: {
|
||||
min: arrayMin(values),
|
||||
max: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: header.sigma !== undefined ? header.sigma : arrayRms(values)
|
||||
grid: {
|
||||
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
|
||||
cells: data,
|
||||
stats: {
|
||||
min: arrayMin(values),
|
||||
max: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: header.sigma !== undefined ? header.sigma : arrayRms(values)
|
||||
},
|
||||
},
|
||||
sourceData: Dsn6Format.create(source)
|
||||
sourceData: Dsn6Format.create(source),
|
||||
customProperties: new CustomProperties(),
|
||||
_propertyData: Object.create(null),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
|
||||
import { DxFile } from '../../mol-io/reader/dx/parser';
|
||||
import { Mat4, Tensor } from '../../mol-math/linear-algebra';
|
||||
import { VolumeData } from '../../mol-model/volume/data';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { Task } from '../../mol-task';
|
||||
import { arrayMax, arrayMean, arrayMin, arrayRms } from '../../mol-util/array';
|
||||
import { ModelFormat } from '../format';
|
||||
import { CustomProperties } from '../../mol-model/custom-property';
|
||||
|
||||
export function volumeFromDx(source: DxFile, params?: { label?: string }): Task<VolumeData> {
|
||||
return Task.create<VolumeData>('Create Volume Data', async () => {
|
||||
export function volumeFromDx(source: DxFile, params?: { label?: string }): Task<Volume> {
|
||||
return Task.create<Volume>('Create Volume', async () => {
|
||||
const { header, values } = source;
|
||||
const space = Tensor.Space(header.dim, [0, 1, 2], Float64Array);
|
||||
const data = Tensor.create(space, Tensor.Data1(values));
|
||||
@@ -22,15 +23,19 @@ export function volumeFromDx(source: DxFile, params?: { label?: string }): Task<
|
||||
|
||||
return {
|
||||
label: params?.label,
|
||||
transform: { kind: 'matrix', matrix },
|
||||
data,
|
||||
dataStats: {
|
||||
min: arrayMin(values),
|
||||
max: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: arrayRms(values)
|
||||
grid: {
|
||||
transform: { kind: 'matrix', matrix },
|
||||
cells: data,
|
||||
stats: {
|
||||
min: arrayMin(values),
|
||||
max: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: arrayRms(values)
|
||||
},
|
||||
},
|
||||
sourceData: DxFormat.create(source)
|
||||
sourceData: DxFormat.create(source),
|
||||
customProperties: new CustomProperties(),
|
||||
_propertyData: Object.create(null),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ElementIndex, Model, CustomPropertyDescriptor } from '../../mol-model/structure';
|
||||
import { ElementIndex, Model } from '../../mol-model/structure';
|
||||
import { StructureElement } from '../../mol-model/structure/structure';
|
||||
import { Location } from '../../mol-model/location';
|
||||
import { ThemeDataContext } from '../../mol-theme/theme';
|
||||
@@ -16,6 +16,7 @@ import { OrderedSet } from '../../mol-data/int';
|
||||
import { CustomModelProperty } from './custom-model-property';
|
||||
import { CustomProperty } from './custom-property';
|
||||
import { LociLabelProvider } from '../../mol-plugin-state/manager/loci-label';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
|
||||
export { CustomElementProperty };
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CustomPropertyDescriptor, Model } from '../../mol-model/structure';
|
||||
import { Model } from '../../mol-model/structure';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ValueBox } from '../../mol-util';
|
||||
import { CustomProperty } from './custom-property';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
|
||||
export { CustomModelProperty };
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/structure';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ValueBox } from '../../mol-util';
|
||||
import { OrderedMap } from 'immutable';
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CustomPropertyDescriptor, Structure } from '../../mol-model/structure';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ValueBox } from '../../mol-util';
|
||||
import { CustomProperty } from './custom-property';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
|
||||
export { CustomStructureProperty };
|
||||
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ShrakeRupleyComputationParams, AccessibleSurfaceArea } from './accessible-surface-area/shrake-rupley';
|
||||
import { Structure, CustomPropertyDescriptor, Unit } from '../../mol-model/structure';
|
||||
import { Structure, Unit } from '../../mol-model/structure';
|
||||
import { CustomStructureProperty } from '../common/custom-structure-property';
|
||||
import { CustomProperty } from '../common/custom-property';
|
||||
import { QuerySymbolRuntime } from '../../mol-script/runtime/query/compiler';
|
||||
import { CustomPropSymbol } from '../../mol-script/language/symbol';
|
||||
import Type from '../../mol-script/language/type';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
|
||||
export const AccessibleSurfaceAreaParams = {
|
||||
...ShrakeRupleyComputationParams
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CustomPropertyDescriptor, Structure } from '../../mol-model/structure';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { computeInteractions, Interactions, InteractionsParams as _InteractionsParams } from './interactions/interactions';
|
||||
import { CustomStructureProperty } from '../common/custom-structure-property';
|
||||
import { CustomProperty } from '../common/custom-property';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
|
||||
export const InteractionsParams = {
|
||||
..._InteractionsParams
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Unit } from '../../mol-model/structure/structure';
|
||||
import { CustomStructureProperty } from '../common/custom-structure-property';
|
||||
import { CustomProperty } from '../common/custom-property';
|
||||
import { ModelSecondaryStructure } from '../../mol-model-formats/structure/property/secondary-structure';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/structure/common/custom-property';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
import { Model } from '../../mol-model/structure/model';
|
||||
|
||||
function getSecondaryStructureParams(data?: Structure) {
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CustomPropertyDescriptor, Structure } from '../../mol-model/structure';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { calcValenceModel, ValenceModel, ValenceModelParams as _ValenceModelParams } from './chemistry/valence-model';
|
||||
import { CustomStructureProperty } from '../common/custom-structure-property';
|
||||
import { CustomProperty } from '../common/custom-property';
|
||||
import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
|
||||
|
||||
export const ValenceModelParams = {
|
||||
..._ValenceModelParams
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
import { Model } from '../../../mol-model/structure/model/model';
|
||||
import { Table } from '../../../mol-data/db';
|
||||
import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Unit, CustomPropertyDescriptor } from '../../../mol-model/structure';
|
||||
import { Unit } from '../../../mol-model/structure';
|
||||
import { ElementIndex } from '../../../mol-model/structure/model/indexing';
|
||||
import { FormatPropertyProvider } from '../../../mol-model-formats/structure/common/property';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
|
||||
|
||||
export { ModelCrossLinkRestraint };
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { ModelCrossLinkRestraint } from './format';
|
||||
import { Unit, StructureElement, Structure, CustomPropertyDescriptor, Bond} from '../../../mol-model/structure';
|
||||
import { Unit, StructureElement, Structure, Bond} from '../../../mol-model/structure';
|
||||
import { PairRestraints, PairRestraint } from '../pair-restraints';
|
||||
import { CustomStructureProperty } from '../../common/custom-structure-property';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
@@ -15,6 +15,7 @@ import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
|
||||
import { bondLabel } from '../../../mol-theme/label';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
|
||||
|
||||
export type CrossLinkRestraintValue = PairRestraints<CrossLinkRestraint>
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CifWriter } from '../../../mol-io/writer/cif';
|
||||
import { CifExportContext } from '../export/mmcif';
|
||||
import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { Asset } from '../../../mol-util/assets';
|
||||
import { CifWriter } from '../mol-io/writer/cif';
|
||||
import { CifExportContext } from './structure/export/mmcif';
|
||||
import { QuerySymbolRuntime } from '../mol-script/runtime/query/compiler';
|
||||
import { UUID } from '../mol-util';
|
||||
import { Asset } from '../mol-util/assets';
|
||||
|
||||
export { CustomPropertyDescriptor, CustomProperties };
|
||||
|
||||
@@ -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;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-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>
|
||||
@@ -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
|
||||
|
||||
@@ -24,14 +23,18 @@ namespace Sequence {
|
||||
export interface Base<K extends Kind, Alphabet extends string> {
|
||||
readonly kind: K,
|
||||
readonly length: number,
|
||||
readonly offset: number,
|
||||
|
||||
/** One letter code */
|
||||
readonly code: Column<Alphabet>
|
||||
readonly label: Column<string>
|
||||
|
||||
readonly seqId: Column<number>
|
||||
/** Component id */
|
||||
readonly compId: Column<string>
|
||||
|
||||
/** returns index for given seqId */
|
||||
readonly index: (seqId: number) => number
|
||||
|
||||
/** maps seqId to list of compIds */
|
||||
readonly microHet: ReadonlyMap<number, string[]>
|
||||
}
|
||||
@@ -41,11 +44,6 @@ namespace Sequence {
|
||||
export interface DNA extends Base<Kind.DNA, NuclecicAlphabet> { }
|
||||
export interface Generic extends Base<Kind.Generic, 'X' | '-'> { }
|
||||
|
||||
export function create<K extends Kind, Alphabet extends string>(kind: K, code: Column<Alphabet>, label: Column<string>, seqId: Column<number>, compId: Column<string>, microHet: Map<number, string[]>, offset: number = 0): Base<K, Alphabet> {
|
||||
const length = code.rowCount;
|
||||
return { kind, code, label, seqId, compId, microHet, offset, length };
|
||||
}
|
||||
|
||||
export function getSequenceString(seq: Sequence) {
|
||||
const array = seq.code.toArray();
|
||||
return (array instanceof Array ? array : Array.from(array)).join('');
|
||||
@@ -88,100 +86,60 @@ namespace Sequence {
|
||||
}
|
||||
|
||||
class ResidueNamesImpl<K extends Kind, Alphabet extends string> implements Base<K, Alphabet> {
|
||||
private _offset = 0;
|
||||
private _length = 0;
|
||||
private _microHet: ReadonlyMap<number, string[]> | undefined = void 0;
|
||||
private _code: Column<Alphabet> | undefined = undefined
|
||||
private _label: Column<string> | undefined = undefined
|
||||
public length: number
|
||||
public code: Column<Alphabet>
|
||||
public label: Column<string>
|
||||
public seqId: Column<number>
|
||||
public compId: Column<string>
|
||||
public microHet: ReadonlyMap<number, string[]> = new Map()
|
||||
|
||||
private codeFromName: (name: string) => string
|
||||
|
||||
get code(): Column<Alphabet> {
|
||||
if (this._code !== void 0) return this._code;
|
||||
this.create();
|
||||
return this._code!;
|
||||
private indexMap: Map<number, number>
|
||||
index(seqId: number) {
|
||||
return this.indexMap.get(seqId)!;
|
||||
}
|
||||
|
||||
get label(): Column<string> {
|
||||
if (this._label !== void 0) return this._label;
|
||||
this.create();
|
||||
return this._label!;
|
||||
}
|
||||
constructor(public kind: K, compId: Column<string>, seqId: Column<number>) {
|
||||
const codeFromName = codeProvider(kind);
|
||||
const codes: string[] = [];
|
||||
const compIds: string[] = [];
|
||||
const seqIds: number[] = [];
|
||||
const microHet = new Map<number, string[]>();
|
||||
|
||||
get offset() {
|
||||
if (this._code !== void 0) return this._offset;
|
||||
this.create();
|
||||
return this._offset;
|
||||
}
|
||||
let idx = 0;
|
||||
const indexMap = new Map<number, number>();
|
||||
for (let i = 0, il = seqId.rowCount; i < il; ++i) {
|
||||
const seq_id = seqId.value(i);
|
||||
|
||||
get length() {
|
||||
if (this._code !== void 0) return this._length;
|
||||
this.create();
|
||||
return this._length;
|
||||
}
|
||||
|
||||
get microHet(): ReadonlyMap<number, string[]> {
|
||||
if (this._microHet !== void 0) return this._microHet;
|
||||
this.create();
|
||||
return this._microHet!;
|
||||
}
|
||||
|
||||
private create() {
|
||||
let maxSeqId = 0, minSeqId = Number.MAX_SAFE_INTEGER;
|
||||
for (let i = 0, _i = this.seqId.rowCount; i < _i; i++) {
|
||||
const id = this.seqId.value(i);
|
||||
if (maxSeqId < id) maxSeqId = id;
|
||||
if (id < minSeqId) minSeqId = id;
|
||||
}
|
||||
|
||||
const count = maxSeqId - minSeqId + 1;
|
||||
const sequenceArray = new Array<string>(maxSeqId + 1);
|
||||
const labels = new Array<string[]>(maxSeqId + 1);
|
||||
for (let i = 0; i < count; i++) {
|
||||
sequenceArray[i] = '-';
|
||||
labels[i] = [];
|
||||
}
|
||||
|
||||
const compIds = new Array<string[]>(maxSeqId + 1);
|
||||
for (let i = minSeqId; i <= maxSeqId; ++i) {
|
||||
compIds[i] = [];
|
||||
}
|
||||
|
||||
for (let i = 0, _i = this.seqId.rowCount; i < _i; i++) {
|
||||
const seqId = this.seqId.value(i);
|
||||
const idx = seqId - minSeqId;
|
||||
const name = this.compId.value(i);
|
||||
const code = this.codeFromName(name);
|
||||
// in case of MICROHETEROGENEITY `sequenceArray[idx]` may already be set
|
||||
if (!sequenceArray[idx] || sequenceArray[idx] === '-') {
|
||||
sequenceArray[idx] = code;
|
||||
if (!indexMap.has(seq_id)) {
|
||||
indexMap.set(seq_id, idx);
|
||||
const comp_id = compId.value(i);
|
||||
compIds[idx] = comp_id;
|
||||
seqIds[idx] = seq_id;
|
||||
codes[idx] = codeFromName(comp_id);
|
||||
idx += 1;
|
||||
} else {
|
||||
// micro-heterogeneity
|
||||
if (!microHet.has(seq_id)) {
|
||||
microHet.set(seq_id, [compIds[indexMap.get(seq_id)!], compId.value(i)]);
|
||||
} else {
|
||||
microHet.get(seq_id)!.push(compId.value(i));
|
||||
}
|
||||
}
|
||||
labels[idx].push(code === 'X' ? name : code);
|
||||
compIds[seqId].push(name);
|
||||
}
|
||||
|
||||
const microHet = new Map();
|
||||
for (let i = minSeqId; i <= maxSeqId; ++i) {
|
||||
if (compIds[i].length > 1) microHet.set(i, compIds[i]);
|
||||
const labels: string[] = [];
|
||||
for (let i = 0, il = idx; i < il; ++i) {
|
||||
const mh = microHet.get(seqIds[i]);
|
||||
labels[i] = mh ? `(${mh.join('|')})` : codes[i];
|
||||
}
|
||||
|
||||
this._code = Column.ofStringArray(sequenceArray) as Column<Alphabet>;
|
||||
this._label = Column.ofLambda({
|
||||
value: i => {
|
||||
const l = labels[i];
|
||||
return l.length > 1 ? `(${l.join('|')})` : l.join('');
|
||||
},
|
||||
rowCount: labels.length,
|
||||
schema: Column.Schema.str
|
||||
});
|
||||
this._microHet = microHet;
|
||||
this._offset = minSeqId - 1;
|
||||
this._length = count;
|
||||
}
|
||||
|
||||
constructor(public kind: K, public compId: Column<string>, public seqId: Column<number>) {
|
||||
|
||||
this.codeFromName = codeProvider(kind);
|
||||
this.length = idx;
|
||||
this.code = Column.ofStringArray(codes) as Column<Alphabet>;
|
||||
this.compId = Column.ofStringArray(compIds);
|
||||
this.seqId = Column.ofIntArray(seqIds);
|
||||
this.label = Column.ofStringArray(labels);
|
||||
this.microHet = microHet;
|
||||
this.indexMap = indexMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,13 +150,17 @@ namespace Sequence {
|
||||
}
|
||||
|
||||
class SequenceRangesImpl<K extends Kind, Alphabet extends string> implements Base<K, Alphabet> {
|
||||
public offset: number
|
||||
public length: number
|
||||
public code: Column<Alphabet>
|
||||
public label: Column<string>
|
||||
public seqId: Column<number>
|
||||
public compId: Column<string>
|
||||
public microHet: ReadonlyMap<number, string[]>
|
||||
public microHet: ReadonlyMap<number, string[]> = new Map()
|
||||
|
||||
private minSeqId: number
|
||||
index(seqId: number) {
|
||||
return seqId - this.minSeqId;
|
||||
}
|
||||
|
||||
constructor(public kind: K, private seqIdStart: Column<number>, private seqIdEnd: Column<number>) {
|
||||
let maxSeqId = 0, minSeqId = Number.MAX_SAFE_INTEGER;
|
||||
@@ -220,8 +182,8 @@ namespace Sequence {
|
||||
});
|
||||
this.compId = Column.ofConst('', count, Column.Schema.str);
|
||||
|
||||
this.offset = minSeqId - 1;
|
||||
this.length = count;
|
||||
this.minSeqId = minSeqId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,4 @@ export * from './structure/coordinates';
|
||||
export * from './structure/topology';
|
||||
export * from './structure/model';
|
||||
export * from './structure/structure';
|
||||
export * from './structure/query';
|
||||
export * from './structure/common/custom-property';
|
||||
export * from './structure/query';
|
||||
@@ -15,7 +15,7 @@ import { _chem_comp, _pdbx_chem_comp_identifier, _pdbx_nonpoly_scheme } from './
|
||||
import { Model } from '../model';
|
||||
import { getUniqueEntityIndicesFromStructures, copy_mmCif_category, copy_source_mmCifCategory } from './categories/utils';
|
||||
import { _struct_asym, _entity_poly, _entity_poly_seq } from './categories/sequence';
|
||||
import { CustomPropertyDescriptor } from '../common/custom-property';
|
||||
import { CustomPropertyDescriptor } from '../../custom-property';
|
||||
import { atom_site_operator_mapping } from './categories/atom_site_operator_mapping';
|
||||
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import StructureSequence from './properties/sequence';
|
||||
import { AtomicHierarchy, AtomicConformation, AtomicRanges } from './properties/atomic';
|
||||
import { CoarseHierarchy, CoarseConformation } from './properties/coarse';
|
||||
import { Entities, ChemicalComponentMap, MissingResidues, StructAsymMap } from './properties/common';
|
||||
import { CustomProperties } from '../common/custom-property';
|
||||
import { CustomProperties } from '../../custom-property';
|
||||
import { SaccharideComponentMap } from '../structure/carbohydrates/constants';
|
||||
import { ModelFormat } from '../../../mol-model-formats/format';
|
||||
import { calcModelCenter } from './util';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -25,7 +25,7 @@ import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
|
||||
import { idFactory } from '../../../mol-util/id-factory';
|
||||
import { GridLookup3D } from '../../../mol-math/geometry';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { CustomProperties } from '../common/custom-property';
|
||||
import { CustomProperties } from '../../custom-property';
|
||||
import { AtomicHierarchy } from '../model/properties/atomic';
|
||||
import { StructureSelection } from '../query/selection';
|
||||
import { getBoundary } from '../../../mol-math/geometry/boundary';
|
||||
@@ -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;
|
||||
|
||||
@@ -37,9 +37,12 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
|
||||
|
||||
const { x, y, z } = unit.model.atomicConformation;
|
||||
const atomCount = unit.elements.length;
|
||||
const { elements: atoms, residueIndex } = unit;
|
||||
const { elements: atoms, residueIndex, chainIndex } = unit;
|
||||
const { type_symbol, label_atom_id, label_alt_id } = unit.model.atomicHierarchy.atoms;
|
||||
const { label_comp_id } = unit.model.atomicHierarchy.residues;
|
||||
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;
|
||||
const query3d = unit.lookup3d;
|
||||
|
||||
const structConn = StructConn.Provider.get(unit.model);
|
||||
@@ -101,13 +104,22 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
|
||||
|
||||
if (!props.forceCompute && raI !== lastResidue) {
|
||||
if (!!component && component.entries.has(compId)) {
|
||||
componentMap = component.entries.get(compId)!.map;
|
||||
const entitySeq = byEntityKey[index.getEntityFromChain(chainIndex[aI])];
|
||||
if (entitySeq && entitySeq.sequence.microHet.has(label_seq_id.value(raI))) {
|
||||
// compute for sequence positions with micro-heterogeneity
|
||||
componentMap = void 0;
|
||||
} else {
|
||||
componentMap = component.entries.get(compId)!.map;
|
||||
}
|
||||
} else {
|
||||
componentMap = void 0;
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-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>
|
||||
*/
|
||||
|
||||
export * from './volume/data';
|
||||
export * from './volume/volume';
|
||||
export * from './volume/grid';
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2018-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 { SpacegroupCell, Box3D } from '../../mol-math/geometry';
|
||||
import { Tensor, Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
|
||||
import { ModelFormat } from '../../mol-model-formats/format';
|
||||
|
||||
/** The basic unit cell that contains the data. */
|
||||
interface VolumeDataBase {
|
||||
readonly label?: string,
|
||||
readonly transform: { kind: 'spacegroup', cell: SpacegroupCell, fractionalBox: Box3D } | { kind: 'matrix', matrix: Mat4 },
|
||||
readonly data: Tensor,
|
||||
readonly dataStats: Readonly<{
|
||||
min: number,
|
||||
max: number,
|
||||
mean: number,
|
||||
sigma: number
|
||||
}>
|
||||
readonly sourceData: ModelFormat,
|
||||
}
|
||||
|
||||
interface VolumeData extends VolumeDataBase {
|
||||
readonly colorVolume?: VolumeDataBase
|
||||
}
|
||||
|
||||
namespace VolumeData {
|
||||
export const One: VolumeData = {
|
||||
transform: { kind: 'matrix', matrix: Mat4.identity() },
|
||||
data: Tensor.create(Tensor.Space([1, 1, 1], [0, 1, 2]), Tensor.Data1([0])),
|
||||
dataStats: { min: 0, max: 0, mean: 0, sigma: 0 },
|
||||
sourceData: { kind: '', data: '', name: '' }
|
||||
};
|
||||
|
||||
const _scale = Mat4.zero(), _translate = Mat4.zero();
|
||||
export function getGridToCartesianTransform(volume: VolumeData) {
|
||||
if (volume.transform.kind === 'matrix') {
|
||||
return Mat4.copy(Mat4(), volume.transform.matrix);
|
||||
}
|
||||
|
||||
if (volume.transform.kind === 'spacegroup') {
|
||||
const { data: { space } } = volume;
|
||||
const scale = Mat4.fromScaling(_scale, Vec3.div(Vec3.zero(), Box3D.size(Vec3.zero(), volume.transform.fractionalBox), Vec3.ofArray(space.dimensions)));
|
||||
const translate = Mat4.fromTranslation(_translate, volume.transform.fractionalBox.min);
|
||||
return Mat4.mul3(Mat4.zero(), volume.transform.cell.fromFractional, translate, scale);
|
||||
}
|
||||
|
||||
return Mat4.identity();
|
||||
}
|
||||
|
||||
export function areEquivalent(volA: VolumeData, volB: VolumeData) {
|
||||
return volA === volB;
|
||||
}
|
||||
}
|
||||
|
||||
type VolumeIsoValue = VolumeIsoValue.Absolute | VolumeIsoValue.Relative
|
||||
|
||||
namespace VolumeIsoValue {
|
||||
export type Relative = Readonly<{ kind: 'relative', relativeValue: number }>
|
||||
export type Absolute = Readonly<{ kind: 'absolute', absoluteValue: number }>
|
||||
|
||||
export function areSame(a: VolumeIsoValue, b: VolumeIsoValue, stats: VolumeData['dataStats']) {
|
||||
return equalEps(toAbsolute(a, stats).absoluteValue, toAbsolute(b, stats).absoluteValue, stats.sigma / 100);
|
||||
}
|
||||
|
||||
export function absolute(value: number): Absolute { return { kind: 'absolute', absoluteValue: value }; }
|
||||
export function relative(value: number): Relative { return { kind: 'relative', relativeValue: value }; }
|
||||
|
||||
export function calcAbsolute(stats: VolumeData['dataStats'], relativeValue: number): number {
|
||||
return relativeValue * stats.sigma + stats.mean;
|
||||
}
|
||||
|
||||
export function calcRelative(stats: VolumeData['dataStats'], absoluteValue: number): number {
|
||||
return stats.sigma === 0 ? 0 : ((absoluteValue - stats.mean) / stats.sigma);
|
||||
}
|
||||
|
||||
export function toAbsolute(value: VolumeIsoValue, stats: VolumeData['dataStats']): Absolute {
|
||||
return value.kind === 'absolute' ? value : { kind: 'absolute', absoluteValue: VolumeIsoValue.calcAbsolute(stats, value.relativeValue) };
|
||||
}
|
||||
|
||||
export function toRelative(value: VolumeIsoValue, stats: VolumeData['dataStats']): Relative {
|
||||
return value.kind === 'relative' ? value : { kind: 'relative', relativeValue: VolumeIsoValue.calcRelative(stats, value.absoluteValue) };
|
||||
}
|
||||
|
||||
export function toString(value: VolumeIsoValue) {
|
||||
return value.kind === 'relative'
|
||||
? `${value.relativeValue.toFixed(2)} σ`
|
||||
: `${value.absoluteValue.toPrecision(4)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export { VolumeData, VolumeIsoValue };
|
||||
57
src/mol-model/volume/grid.ts
Normal file
57
src/mol-model/volume/grid.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2018-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 { SpacegroupCell, Box3D } from '../../mol-math/geometry';
|
||||
import { Tensor, Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
|
||||
/** The basic unit cell that contains the grid data. */
|
||||
interface Grid {
|
||||
readonly transform: Grid.Transform,
|
||||
readonly cells: Tensor,
|
||||
readonly stats: Readonly<{
|
||||
min: number,
|
||||
max: number,
|
||||
mean: number,
|
||||
sigma: number
|
||||
}>
|
||||
}
|
||||
|
||||
namespace Grid {
|
||||
export const One: Grid = {
|
||||
transform: { kind: 'matrix', matrix: Mat4.identity() },
|
||||
cells: Tensor.create(Tensor.Space([1, 1, 1], [0, 1, 2]), Tensor.Data1([0])),
|
||||
stats: { min: 0, max: 0, mean: 0, sigma: 0 },
|
||||
};
|
||||
|
||||
export type Transform = { kind: 'spacegroup', cell: SpacegroupCell, fractionalBox: Box3D } | { kind: 'matrix', matrix: Mat4 }
|
||||
|
||||
const _scale = Mat4.zero(), _translate = Mat4.zero();
|
||||
export function getGridToCartesianTransform(grid: Grid) {
|
||||
if (grid.transform.kind === 'matrix') {
|
||||
return Mat4.copy(Mat4(), grid.transform.matrix);
|
||||
}
|
||||
|
||||
if (grid.transform.kind === 'spacegroup') {
|
||||
const { cells: { space } } = grid;
|
||||
const scale = Mat4.fromScaling(_scale, Vec3.div(Vec3.zero(), Box3D.size(Vec3.zero(), grid.transform.fractionalBox), Vec3.ofArray(space.dimensions)));
|
||||
const translate = Mat4.fromTranslation(_translate, grid.transform.fractionalBox.min);
|
||||
return Mat4.mul3(Mat4.zero(), grid.transform.cell.fromFractional, translate, scale);
|
||||
}
|
||||
|
||||
return Mat4.identity();
|
||||
}
|
||||
|
||||
export function areEquivalent(gridA: Grid, gridB: Grid) {
|
||||
return gridA === gridB;
|
||||
}
|
||||
|
||||
export function isEmpty(grid: Grid) {
|
||||
return grid.cells.data.length === 0;
|
||||
}
|
||||
}
|
||||
|
||||
export { Grid };
|
||||
@@ -4,32 +4,105 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { VolumeData, VolumeIsoValue } from './data';
|
||||
import { Grid } from './grid';
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
|
||||
import { CubeFormat } from '../../mol-model-formats/volume/cube';
|
||||
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
|
||||
import { ModelFormat } from '../../mol-model-formats/format';
|
||||
import { CustomProperties } from '../custom-property';
|
||||
|
||||
export interface Volume {
|
||||
readonly label?: string
|
||||
readonly grid: Grid
|
||||
readonly sourceData: ModelFormat
|
||||
|
||||
// TODO use...
|
||||
customProperties: CustomProperties
|
||||
|
||||
/**
|
||||
* Not to be accessed directly, each custom property descriptor
|
||||
* defines property accessors that use this field to store the data.
|
||||
*/
|
||||
_propertyData: { [name: string]: any }
|
||||
|
||||
// TODO add as customProperty?
|
||||
readonly colorVolume?: Volume
|
||||
}
|
||||
|
||||
export namespace Volume {
|
||||
export type CellIndex = { readonly '@type': 'cell-index' } & number
|
||||
|
||||
export function isOrbitals(volume: VolumeData) {
|
||||
export type IsoValue = IsoValue.Absolute | IsoValue.Relative
|
||||
|
||||
export namespace IsoValue {
|
||||
export type Relative = Readonly<{ kind: 'relative', relativeValue: number }>
|
||||
export type Absolute = Readonly<{ kind: 'absolute', absoluteValue: number }>
|
||||
|
||||
export function areSame(a: IsoValue, b: IsoValue, stats: Grid['stats']) {
|
||||
return equalEps(toAbsolute(a, stats).absoluteValue, toAbsolute(b, stats).absoluteValue, stats.sigma / 100);
|
||||
}
|
||||
|
||||
export function absolute(value: number): Absolute { return { kind: 'absolute', absoluteValue: value }; }
|
||||
export function relative(value: number): Relative { return { kind: 'relative', relativeValue: value }; }
|
||||
|
||||
export function calcAbsolute(stats: Grid['stats'], relativeValue: number): number {
|
||||
return relativeValue * stats.sigma + stats.mean;
|
||||
}
|
||||
|
||||
export function calcRelative(stats: Grid['stats'], absoluteValue: number): number {
|
||||
return stats.sigma === 0 ? 0 : ((absoluteValue - stats.mean) / stats.sigma);
|
||||
}
|
||||
|
||||
export function toAbsolute(value: IsoValue, stats: Grid['stats']): Absolute {
|
||||
return value.kind === 'absolute' ? value : { kind: 'absolute', absoluteValue: IsoValue.calcAbsolute(stats, value.relativeValue) };
|
||||
}
|
||||
|
||||
export function toRelative(value: IsoValue, stats: Grid['stats']): Relative {
|
||||
return value.kind === 'relative' ? value : { kind: 'relative', relativeValue: IsoValue.calcRelative(stats, value.absoluteValue) };
|
||||
}
|
||||
|
||||
export function toString(value: IsoValue) {
|
||||
return value.kind === 'relative'
|
||||
? `${value.relativeValue.toFixed(2)} σ`
|
||||
: `${value.absoluteValue.toPrecision(4)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const One: Volume = {
|
||||
label: '',
|
||||
grid: Grid.One,
|
||||
sourceData: { kind: '', name: '', data: {} },
|
||||
customProperties: new CustomProperties(),
|
||||
_propertyData: Object.create(null),
|
||||
};
|
||||
|
||||
export function areEquivalent(volA: Volume, volB: Volume) {
|
||||
return Grid.areEquivalent(volA.grid, volB.grid);
|
||||
}
|
||||
|
||||
export function isEmpty(vol: Volume) {
|
||||
return Grid.isEmpty(vol.grid);
|
||||
}
|
||||
|
||||
export function isOrbitals(volume: Volume) {
|
||||
if (!CubeFormat.is(volume.sourceData)) return false;
|
||||
return volume.sourceData.data.header.orbitals;
|
||||
}
|
||||
|
||||
export interface Loci { readonly kind: 'volume-loci', readonly volume: VolumeData }
|
||||
export function Loci(volume: VolumeData): Loci { return { kind: 'volume-loci', volume }; }
|
||||
export interface Loci { readonly kind: 'volume-loci', readonly volume: Volume }
|
||||
export function Loci(volume: Volume): Loci { return { kind: 'volume-loci', volume }; }
|
||||
export function isLoci(x: any): x is Loci { return !!x && x.kind === 'volume-loci'; }
|
||||
export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume; }
|
||||
export function isLociEmpty(loci: Loci) { return loci.volume.data.data.length === 0; }
|
||||
export function isLociEmpty(loci: Loci) { return Grid.isEmpty(loci.volume.grid); }
|
||||
|
||||
export function getBoundingSphere(volume: VolumeData, boundingSphere?: Sphere3D) {
|
||||
export function getBoundingSphere(volume: Volume, boundingSphere?: Sphere3D) {
|
||||
if (!boundingSphere) boundingSphere = Sphere3D();
|
||||
|
||||
const transform = VolumeData.getGridToCartesianTransform(volume);
|
||||
const [x, y, z] = volume.data.space.dimensions;
|
||||
const transform = Grid.getGridToCartesianTransform(volume.grid);
|
||||
const [x, y, z] = volume.grid.cells.space.dimensions;
|
||||
|
||||
const cpA = Vec3.create(0, 0, 0); Vec3.transformMat4(cpA, cpA, transform);
|
||||
const cpB = Vec3.create(x, y, z); Vec3.transformMat4(cpB, cpB, transform);
|
||||
@@ -52,31 +125,31 @@ export namespace Volume {
|
||||
}
|
||||
|
||||
export namespace Isosurface {
|
||||
export interface Loci { readonly kind: 'isosurface-loci', readonly volume: VolumeData, readonly isoValue: VolumeIsoValue }
|
||||
export function Loci(volume: VolumeData, isoValue: VolumeIsoValue): Loci { return { kind: 'isosurface-loci', volume, isoValue }; }
|
||||
export interface Loci { readonly kind: 'isosurface-loci', readonly volume: Volume, readonly isoValue: Volume.IsoValue }
|
||||
export function Loci(volume: Volume, isoValue: Volume.IsoValue): Loci { return { kind: 'isosurface-loci', volume, isoValue }; }
|
||||
export function isLoci(x: any): x is Loci { return !!x && x.kind === 'isosurface-loci'; }
|
||||
export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume && VolumeIsoValue.areSame(a.isoValue, b.isoValue, a.volume.dataStats); }
|
||||
export function isLociEmpty(loci: Loci) { return loci.volume.data.data.length === 0; }
|
||||
export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume && Volume.IsoValue.areSame(a.isoValue, b.isoValue, a.volume.grid.stats); }
|
||||
export function isLociEmpty(loci: Loci) { return loci.volume.grid.cells.data.length === 0; }
|
||||
|
||||
export function getBoundingSphere(volume: VolumeData, isoValue: VolumeIsoValue, boundingSphere?: Sphere3D) {
|
||||
export function getBoundingSphere(volume: Volume, isoValue: Volume.IsoValue, boundingSphere?: Sphere3D) {
|
||||
// TODO get bounding sphere for subgrid with values >= isoValue
|
||||
return Volume.getBoundingSphere(volume, boundingSphere);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Cell {
|
||||
export interface Loci { readonly kind: 'cell-loci', readonly volume: VolumeData, readonly indices: OrderedSet<CellIndex> }
|
||||
export function Loci(volume: VolumeData, indices: OrderedSet<CellIndex>): Loci { return { kind: 'cell-loci', volume, indices }; }
|
||||
export interface Loci { readonly kind: 'cell-loci', readonly volume: Volume, readonly indices: OrderedSet<CellIndex> }
|
||||
export function Loci(volume: Volume, indices: OrderedSet<CellIndex>): Loci { return { kind: 'cell-loci', volume, indices }; }
|
||||
export function isLoci(x: any): x is Loci { return !!x && x.kind === 'cell-loci'; }
|
||||
export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume && OrderedSet.areEqual(a.indices, b.indices); }
|
||||
export function isLociEmpty(loci: Loci) { return OrderedSet.size(loci.indices) === 0; }
|
||||
|
||||
const boundaryHelper = new BoundaryHelper('98');
|
||||
const tmpBoundaryPos = Vec3();
|
||||
export function getBoundingSphere(volume: VolumeData, indices: OrderedSet<CellIndex>, boundingSphere?: Sphere3D) {
|
||||
export function getBoundingSphere(volume: Volume, indices: OrderedSet<CellIndex>, boundingSphere?: Sphere3D) {
|
||||
boundaryHelper.reset();
|
||||
const transform = VolumeData.getGridToCartesianTransform(volume);
|
||||
const { getCoords } = volume.data.space;
|
||||
const transform = Grid.getGridToCartesianTransform(volume.grid);
|
||||
const { getCoords } = volume.grid.cells.space;
|
||||
|
||||
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
|
||||
const o = OrderedSet.getAt(indices, i);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -12,10 +12,9 @@ import { StateObjectSelector } from '../../mol-state';
|
||||
import { PluginStateObject } from '../objects';
|
||||
import { VolumeRepresentation3DHelpers } from '../transforms/representation';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { VolumeIsoValue } from '../../mol-model/volume';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { createVolumeRepresentationParams } from '../helpers/volume-representation-params';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
import { Volume } from '../../mol-model/volume/volume';
|
||||
|
||||
const Category = 'Volume';
|
||||
|
||||
@@ -110,13 +109,13 @@ export const CubeProvider = DataFormatProvider({
|
||||
if (volumeData && Volume.isOrbitals(volumeData)) {
|
||||
const volumePos = surfaces.to(data.volume).apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(plugin, volumeData, {
|
||||
type: 'isosurface',
|
||||
typeParams: { isoValue: VolumeIsoValue.relative(1), alpha: 0.4 },
|
||||
typeParams: { isoValue: Volume.IsoValue.relative(1), alpha: 0.4 },
|
||||
color: 'uniform',
|
||||
colorParams: { value: ColorNames.blue }
|
||||
}));
|
||||
const volumeNeg = surfaces.to(data.volume).apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(plugin, volumeData, {
|
||||
type: 'isosurface',
|
||||
typeParams: { isoValue: VolumeIsoValue.relative(-1), alpha: 0.4 },
|
||||
typeParams: { isoValue: Volume.IsoValue.relative(-1), alpha: 0.4 },
|
||||
color: 'uniform',
|
||||
colorParams: { value: ColorNames.red }
|
||||
}));
|
||||
@@ -124,7 +123,7 @@ export const CubeProvider = DataFormatProvider({
|
||||
} else {
|
||||
const volume = surfaces.to(data.volume).apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(plugin, volumeData, {
|
||||
type: 'isosurface',
|
||||
typeParams: { isoValue: VolumeIsoValue.relative(2), alpha: 0.4 },
|
||||
typeParams: { isoValue: Volume.IsoValue.relative(2), alpha: 0.4 },
|
||||
color: 'uniform',
|
||||
colorParams: { value: ColorNames.grey }
|
||||
}));
|
||||
@@ -176,13 +175,13 @@ export const DscifProvider = DataFormatProvider({
|
||||
if (volumes.length > 0) {
|
||||
visuals[0] = tree
|
||||
.to(volumes[0])
|
||||
.apply(StateTransforms.Representation.VolumeRepresentation3D, VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: VolumeIsoValue.relative(1.5), alpha: 1 }, 'uniform', { value: ColorNames.teal }))
|
||||
.apply(StateTransforms.Representation.VolumeRepresentation3D, VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: Volume.IsoValue.relative(1.5), alpha: 1 }, 'uniform', { value: ColorNames.teal }))
|
||||
.selector;
|
||||
}
|
||||
|
||||
if (volumes.length > 1) {
|
||||
const posParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: VolumeIsoValue.relative(3), alpha: 0.3 }, 'uniform', { value: ColorNames.green });
|
||||
const negParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: VolumeIsoValue.relative(-3), alpha: 0.3 }, 'uniform', { value: ColorNames.red });
|
||||
const posParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: Volume.IsoValue.relative(3), alpha: 0.3 }, 'uniform', { value: ColorNames.green });
|
||||
const negParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: Volume.IsoValue.relative(-3), alpha: 0.3 }, 'uniform', { value: ColorNames.red });
|
||||
visuals[visuals.length] = tree.to(volumes[1]).apply(StateTransforms.Representation.VolumeRepresentation3D, posParams).selector;
|
||||
visuals[visuals.length] = tree.to(volumes[1]).apply(StateTransforms.Representation.VolumeRepresentation3D, negParams).selector;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { VolumeData } from '../../mol-model/volume';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { RepresentationProvider } from '../../mol-repr/representation';
|
||||
import { VolumeRepresentationRegistry } from '../../mol-repr/volume/registry';
|
||||
@@ -30,7 +30,7 @@ export interface VolumeRepresentationBuiltInProps<
|
||||
}
|
||||
|
||||
export interface VolumeRepresentationProps<
|
||||
R extends RepresentationProvider<VolumeData> = RepresentationProvider<VolumeData>,
|
||||
R extends RepresentationProvider<Volume> = RepresentationProvider<Volume>,
|
||||
C extends ColorTheme.Provider = ColorTheme.Provider,
|
||||
S extends SizeTheme.Provider = SizeTheme.Provider> {
|
||||
type?: R,
|
||||
@@ -41,43 +41,43 @@ export interface VolumeRepresentationProps<
|
||||
sizeParams?: Partial<SizeTheme.ParamValues<S>>
|
||||
}
|
||||
|
||||
export function createVolumeRepresentationParams<R extends VolumeRepresentationRegistry.BuiltIn, C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(ctx: PluginContext, volume?: VolumeData, props?: VolumeRepresentationBuiltInProps<R, C, S>): StateTransformer.Params<VolumeRepresentation3D>
|
||||
export function createVolumeRepresentationParams<R extends RepresentationProvider<VolumeData>, C extends ColorTheme.Provider, S extends SizeTheme.Provider>(ctx: PluginContext, volume?: VolumeData, props?: VolumeRepresentationProps<R, C, S>): StateTransformer.Params<VolumeRepresentation3D>
|
||||
export function createVolumeRepresentationParams(ctx: PluginContext, volume?: VolumeData, props: any = {}): StateTransformer.Params<VolumeRepresentation3D> {
|
||||
export function createVolumeRepresentationParams<R extends VolumeRepresentationRegistry.BuiltIn, C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(ctx: PluginContext, volume?: Volume, props?: VolumeRepresentationBuiltInProps<R, C, S>): StateTransformer.Params<VolumeRepresentation3D>
|
||||
export function createVolumeRepresentationParams<R extends RepresentationProvider<Volume>, C extends ColorTheme.Provider, S extends SizeTheme.Provider>(ctx: PluginContext, volume?: Volume, props?: VolumeRepresentationProps<R, C, S>): StateTransformer.Params<VolumeRepresentation3D>
|
||||
export function createVolumeRepresentationParams(ctx: PluginContext, volume?: Volume, props: any = {}): StateTransformer.Params<VolumeRepresentation3D> {
|
||||
const p = props as VolumeRepresentationBuiltInProps;
|
||||
if (typeof p.type === 'string' || typeof p.color === 'string' || typeof p.size === 'string') return createParamsByName(ctx, volume || VolumeData.One, props);
|
||||
return createParamsProvider(ctx, volume || VolumeData.One, props);
|
||||
if (typeof p.type === 'string' || typeof p.color === 'string' || typeof p.size === 'string') return createParamsByName(ctx, volume || Volume.One, props);
|
||||
return createParamsProvider(ctx, volume || Volume.One, props);
|
||||
}
|
||||
|
||||
export function getVolumeThemeTypes(ctx: PluginContext, volume?: VolumeData) {
|
||||
export function getVolumeThemeTypes(ctx: PluginContext, volume?: Volume) {
|
||||
const { themes: themeCtx } = ctx.representation.volume;
|
||||
if (!volume) return themeCtx.colorThemeRegistry.types;
|
||||
return themeCtx.colorThemeRegistry.getApplicableTypes({ volume });
|
||||
}
|
||||
|
||||
export function createVolumeColorThemeParams<T extends ColorTheme.BuiltIn>(ctx: PluginContext, volume: VolumeData | undefined, typeName: string | undefined, themeName: T, params?: ColorTheme.BuiltInParams<T>): StateTransformer.Params<VolumeRepresentation3D>['colorTheme']
|
||||
export function createVolumeColorThemeParams(ctx: PluginContext, volume: VolumeData | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['colorTheme']
|
||||
export function createVolumeColorThemeParams(ctx: PluginContext, volume: VolumeData | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['colorTheme'] {
|
||||
export function createVolumeColorThemeParams<T extends ColorTheme.BuiltIn>(ctx: PluginContext, volume: Volume | undefined, typeName: string | undefined, themeName: T, params?: ColorTheme.BuiltInParams<T>): StateTransformer.Params<VolumeRepresentation3D>['colorTheme']
|
||||
export function createVolumeColorThemeParams(ctx: PluginContext, volume: Volume | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['colorTheme']
|
||||
export function createVolumeColorThemeParams(ctx: PluginContext, volume: Volume | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['colorTheme'] {
|
||||
const { registry, themes } = ctx.representation.volume;
|
||||
const repr = registry.get(typeName || registry.default.name);
|
||||
const color = themes.colorThemeRegistry.get(themeName || repr.defaultColorTheme.name);
|
||||
const colorDefaultParams = PD.getDefaultValues(color.getParams({ volume: volume || VolumeData.One }));
|
||||
const colorDefaultParams = PD.getDefaultValues(color.getParams({ volume: volume || Volume.One }));
|
||||
if (color.name === repr.defaultColorTheme.name) Object.assign(colorDefaultParams, repr.defaultColorTheme.props);
|
||||
return { name: color.name, params: Object.assign(colorDefaultParams, params) };
|
||||
}
|
||||
|
||||
export function createVolumeSizeThemeParams<T extends SizeTheme.BuiltIn>(ctx: PluginContext, volume: VolumeData | undefined, typeName: string | undefined, themeName: T, params?: SizeTheme.BuiltInParams<T>): StateTransformer.Params<VolumeRepresentation3D>['sizeTheme']
|
||||
export function createVolumeSizeThemeParams(ctx: PluginContext, volume: VolumeData | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['sizeTheme']
|
||||
export function createVolumeSizeThemeParams(ctx: PluginContext, volume: VolumeData | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['sizeTheme'] {
|
||||
export function createVolumeSizeThemeParams<T extends SizeTheme.BuiltIn>(ctx: PluginContext, volume: Volume | undefined, typeName: string | undefined, themeName: T, params?: SizeTheme.BuiltInParams<T>): StateTransformer.Params<VolumeRepresentation3D>['sizeTheme']
|
||||
export function createVolumeSizeThemeParams(ctx: PluginContext, volume: Volume | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['sizeTheme']
|
||||
export function createVolumeSizeThemeParams(ctx: PluginContext, volume: Volume | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['sizeTheme'] {
|
||||
const { registry, themes } = ctx.representation.volume;
|
||||
const repr = registry.get(typeName || registry.default.name);
|
||||
const size = themes.sizeThemeRegistry.get(themeName || repr.defaultSizeTheme.name);
|
||||
const sizeDefaultParams = PD.getDefaultValues(size.getParams({ volume: volume || VolumeData.One }));
|
||||
const sizeDefaultParams = PD.getDefaultValues(size.getParams({ volume: volume || Volume.One }));
|
||||
if (size.name === repr.defaultSizeTheme.name) Object.assign(sizeDefaultParams, repr.defaultSizeTheme.props);
|
||||
return { name: size.name, params: Object.assign(sizeDefaultParams, params) };
|
||||
}
|
||||
|
||||
function createParamsByName(ctx: PluginContext, volume: VolumeData, props: VolumeRepresentationBuiltInProps): StateTransformer.Params<VolumeRepresentation3D> {
|
||||
function createParamsByName(ctx: PluginContext, volume: Volume, props: VolumeRepresentationBuiltInProps): StateTransformer.Params<VolumeRepresentation3D> {
|
||||
const typeProvider = (props.type && ctx.representation.volume.registry.get(props.type))
|
||||
|| ctx.representation.volume.registry.default.provider;
|
||||
const colorProvider = (props.color && ctx.representation.volume.themes.colorThemeRegistry.get(props.color))
|
||||
@@ -95,7 +95,7 @@ function createParamsByName(ctx: PluginContext, volume: VolumeData, props: Volum
|
||||
});
|
||||
}
|
||||
|
||||
function createParamsProvider(ctx: PluginContext, volume: VolumeData, props: VolumeRepresentationProps = {}): StateTransformer.Params<VolumeRepresentation3D> {
|
||||
function createParamsProvider(ctx: PluginContext, volume: Volume, props: VolumeRepresentationProps = {}): StateTransformer.Params<VolumeRepresentation3D> {
|
||||
const { themes: themeCtx } = ctx.representation.volume;
|
||||
const themeDataCtx = { volume };
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { PlyFile } from '../mol-io/reader/ply/schema';
|
||||
import { PsfFile } from '../mol-io/reader/psf/parser';
|
||||
import { ShapeProvider } from '../mol-model/shape/provider';
|
||||
import { Coordinates as _Coordinates, Model as _Model, Structure as _Structure, StructureElement, Topology as _Topology } from '../mol-model/structure';
|
||||
import { VolumeData } from '../mol-model/volume';
|
||||
import { Volume as _Volume } from '../mol-model/volume';
|
||||
import { PluginBehavior } from '../mol-plugin/behavior/behavior';
|
||||
import { Representation } from '../mol-repr/representation';
|
||||
import { ShapeRepresentation } from '../mol-repr/shape/representation';
|
||||
@@ -121,7 +121,7 @@ export namespace PluginStateObject {
|
||||
}
|
||||
|
||||
export namespace Volume {
|
||||
export class Data extends Create<VolumeData>({ name: 'Volume Data', typeClass: 'Object' }) { }
|
||||
export class Data extends Create<_Volume>({ name: 'Volume', typeClass: 'Object' }) { }
|
||||
export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>>({ name: 'Volume 3D' }) { }
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { Structure, StructureElement } from '../../mol-model/structure';
|
||||
import { VolumeData, VolumeIsoValue } from '../../mol-model/volume';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { VolumeRepresentationRegistry } from '../../mol-repr/volume/registry';
|
||||
import { VolumeParams } from '../../mol-repr/volume/representation';
|
||||
@@ -441,7 +441,7 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
|
||||
//
|
||||
|
||||
export namespace VolumeRepresentation3DHelpers {
|
||||
export function getDefaultParams(ctx: PluginContext, name: VolumeRepresentationRegistry.BuiltIn, volume: VolumeData, volumeParams?: Partial<PD.Values<VolumeParams>>): StateTransformer.Params<VolumeRepresentation3D> {
|
||||
export function getDefaultParams(ctx: PluginContext, name: VolumeRepresentationRegistry.BuiltIn, volume: Volume, volumeParams?: Partial<PD.Values<VolumeParams>>): StateTransformer.Params<VolumeRepresentation3D> {
|
||||
const type = ctx.representation.volume.registry.get(name);
|
||||
|
||||
const themeDataCtx = { volume };
|
||||
@@ -467,7 +467,7 @@ export namespace VolumeRepresentation3DHelpers {
|
||||
}
|
||||
|
||||
export function getDescription(props: any) {
|
||||
return props.isoValue && VolumeIsoValue.toString(props.isoValue);
|
||||
return props.isoValue && Volume.IsoValue.toString(props.isoValue);
|
||||
}
|
||||
}
|
||||
type VolumeRepresentation3D = typeof VolumeRepresentation3D
|
||||
@@ -485,16 +485,16 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
|
||||
type: PD.Mapped<any>(
|
||||
registry.default.name,
|
||||
registry.types,
|
||||
name => PD.Group<any>(registry.get(name).getParams(themeCtx, VolumeData.One))),
|
||||
name => PD.Group<any>(registry.get(name).getParams(themeCtx, Volume.One))),
|
||||
colorTheme: PD.Mapped<any>(
|
||||
type.defaultColorTheme.name,
|
||||
themeCtx.colorThemeRegistry.types,
|
||||
name => PD.Group<any>(themeCtx.colorThemeRegistry.get(name).getParams({ volume: VolumeData.One }))
|
||||
name => PD.Group<any>(themeCtx.colorThemeRegistry.get(name).getParams({ volume: Volume.One }))
|
||||
),
|
||||
sizeTheme: PD.Mapped<any>(
|
||||
type.defaultSizeTheme.name,
|
||||
themeCtx.sizeThemeRegistry.types,
|
||||
name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams({ volume: VolumeData.One }))
|
||||
name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams({ volume: Volume.One }))
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../objects';
|
||||
import { volumeFromCube } from '../../mol-model-formats/volume/cube';
|
||||
import { volumeFromDx } from '../../mol-model-formats/volume/dx';
|
||||
import { VolumeData } from '../../mol-model/volume';
|
||||
import { Volume } from '../../mol-model/volume';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateSelection } from '../../mol-state';
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -160,8 +160,8 @@ const AssignColorVolume = PluginStateTransform.BuiltIn({
|
||||
if (!dependencies || !dependencies[params.ref]) {
|
||||
throw new Error('Dependency not available.');
|
||||
}
|
||||
const colorVolume = dependencies[params.ref].data as VolumeData;
|
||||
const volume: VolumeData = {
|
||||
const colorVolume = dependencies[params.ref].data as Volume;
|
||||
const volume: Volume = {
|
||||
...a.data,
|
||||
colorVolume
|
||||
};
|
||||
|
||||
@@ -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 } });
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ExpandableControlRow, IconButton } from '../controls/common';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ParameterControls, ParamOnChange } from '../controls/parameters';
|
||||
import { Slider } from '../controls/slider';
|
||||
import { VolumeIsoValue, VolumeData } from '../../mol-model/volume';
|
||||
import { Volume, Grid } from '../../mol-model/volume';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { toPrecision } from '../../mol-util/number';
|
||||
@@ -28,17 +28,25 @@ 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,
|
||||
channels: { [k: string]: VolumeStreaming.ChannelParams },
|
||||
isRelative: boolean,
|
||||
params: StateTransformParameters.Props,
|
||||
stats: VolumeData['dataStats'],
|
||||
stats: Grid['stats'],
|
||||
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>}
|
||||
@@ -111,7 +135,7 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
...old.entry.params.channels,
|
||||
[name]: {
|
||||
...(old.entry.params.channels as any)[name],
|
||||
isoValue: isRelative ? VolumeIsoValue.relative(value) : VolumeIsoValue.absolute(value)
|
||||
isoValue: isRelative ? Volume.IsoValue.relative(value) : Volume.IsoValue.absolute(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,11 +163,11 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
});
|
||||
};
|
||||
|
||||
convert(channel: any, stats: VolumeData['dataStats'], isRelative: boolean) {
|
||||
convert(channel: any, stats: Grid['stats'], isRelative: boolean) {
|
||||
return {
|
||||
...channel, isoValue: isRelative
|
||||
? VolumeIsoValue.toRelative(channel.isoValue, stats)
|
||||
: VolumeIsoValue.toAbsolute(channel.isoValue, stats)
|
||||
? Volume.IsoValue.toRelative(channel.isoValue, stats)
|
||||
: Volume.IsoValue.toAbsolute(channel.isoValue, stats)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -215,34 +241,41 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
const params = this.props.params as VolumeStreaming.Params;
|
||||
const detailLevel = ((this.props.info.params as VolumeStreaming.ParamDefinition)
|
||||
.entry.map(params.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>).params.detailLevel;
|
||||
const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as VolumeIsoValue).kind === 'relative';
|
||||
const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as Volume.IsoValue).kind === 'relative';
|
||||
|
||||
const sampling = b.info.header.sampling[0];
|
||||
|
||||
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} />
|
||||
</>;
|
||||
|
||||
@@ -44,18 +44,18 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
|
||||
mark(loci: Loci, action: MarkerAction): boolean {
|
||||
let changed = false;
|
||||
const { structure } = this.data;
|
||||
const index = (seqId: number) => this.sequence.index(seqId);
|
||||
if (StructureElement.Loci.is(loci)) {
|
||||
if (!Structure.areRootsEquivalent(loci.structure, structure)) return false;
|
||||
loci = StructureElement.Loci.remap(loci, structure);
|
||||
|
||||
const { offset } = this.sequence;
|
||||
for (const e of loci.elements) {
|
||||
if (!this.unitMap.has(e.unit.id)) continue;
|
||||
|
||||
if (Unit.isAtomic(e.unit)) {
|
||||
changed = applyMarkerAtomic(e, action, this.markerArray, offset) || changed;
|
||||
changed = applyMarkerAtomic(e, action, this.markerArray, index) || changed;
|
||||
} else {
|
||||
changed = applyMarkerCoarse(e, action, this.markerArray, offset) || changed;
|
||||
changed = applyMarkerCoarse(e, action, this.markerArray, index) || changed;
|
||||
}
|
||||
}
|
||||
} else if (Structure.isLoci(loci)) {
|
||||
@@ -117,27 +117,28 @@ function createResidueQuery(chainGroupId: number, operatorName: string, label_se
|
||||
});
|
||||
}
|
||||
|
||||
function applyMarkerAtomic(e: StructureElement.Loci.Element, action: MarkerAction, markerArray: Uint8Array, offset: number) {
|
||||
function applyMarkerAtomic(e: StructureElement.Loci.Element, action: MarkerAction, markerArray: Uint8Array, index: (seqId: number) => number) {
|
||||
const { model, elements } = e.unit;
|
||||
const { index } = model.atomicHierarchy.residueAtomSegments;
|
||||
const { index: residueIndex } = model.atomicHierarchy.residueAtomSegments;
|
||||
const { label_seq_id } = model.atomicHierarchy.residues;
|
||||
|
||||
let changed = false;
|
||||
OrderedSet.forEachSegment(e.indices, i => index[elements[i]], rI => {
|
||||
OrderedSet.forEachSegment(e.indices, i => residueIndex[elements[i]], rI => {
|
||||
const seqId = label_seq_id.value(rI);
|
||||
changed = applyMarkerActionAtPosition(markerArray, seqId - 1 - offset, action) || changed;
|
||||
changed = applyMarkerActionAtPosition(markerArray, index(seqId), action) || changed;
|
||||
});
|
||||
return changed;
|
||||
}
|
||||
|
||||
function applyMarkerCoarse(e: StructureElement.Loci.Element, action: MarkerAction, markerArray: Uint8Array, offset: number) {
|
||||
function applyMarkerCoarse(e: StructureElement.Loci.Element, action: MarkerAction, markerArray: Uint8Array, index: (seqId: number) => number) {
|
||||
const { model, elements } = e.unit;
|
||||
const begin = Unit.isSpheres(e.unit) ? model.coarseHierarchy.spheres.seq_id_begin : model.coarseHierarchy.gaussians.seq_id_begin;
|
||||
const end = Unit.isSpheres(e.unit) ? model.coarseHierarchy.spheres.seq_id_end : model.coarseHierarchy.gaussians.seq_id_end;
|
||||
|
||||
let changed = false;
|
||||
OrderedSet.forEach(e.indices, i => {
|
||||
for (let s = begin.value(elements[i]) - 1 - offset, e = end.value(elements[i]) - 1 - offset; s <= e; s++) {
|
||||
const eI = elements[i];
|
||||
for (let s = index(begin.value(eI)), e = index(end.value(eI)); s <= e; s++) {
|
||||
changed = applyMarkerActionAtPosition(markerArray, s, action) || changed;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
</>;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user