Compare commits

...

90 Commits

Author SHA1 Message Date
Alexander Rose
3415fe0847 0.7.1-dev.2 2020-05-09 11:54:33 -07:00
Alexander Rose
1569958a29 debug-mode url param, canvas3d console stats 2020-05-09 11:52:09 -07:00
Alexander Rose
3543faa0c2 fix snapshot loading 2020-05-09 11:03:58 -07:00
Alexander Rose
251dbf3877 0.7.1-dev.1 2020-05-08 15:30:57 -07:00
Alexander Rose
32d35efef0 add grid size to volume transform description and incr version 2020-05-08 15:27:39 -07:00
Alexander Rose
8b6428a61d 0.7.0 2020-05-08 10:45:43 -07:00
Alexander Rose
dda43370cf add setProductionMode, setDebugMode exports 2020-05-08 10:42:38 -07:00
Alexander Rose
92c1e979c0 add version to viewer module export 2020-05-08 10:36:26 -07:00
Alexander Rose
ad38a33943 basic support for aligning coarse structures 2020-05-08 10:20:09 -07:00
Alexander Rose
88c276a4c7 inline option for ToggleSelectionModeButton 2020-05-07 16:16:32 -07:00
Alexander Rose
0a3d19235d 0.7.0-dev.21 2020-05-06 20:16:36 -07:00
Alexander Rose
0d90fd1f06 tweak version script
- so the build contains the correct version number
2020-05-06 20:14:52 -07:00
Alexander Rose
02d3274e83 0.7.0-dev.20 2020-05-06 18:49:10 -07:00
Alexander Rose
2531af2b94 use cursor icon for selection mode 2020-05-06 18:02:53 -07:00
Alexander Rose
850328be4e fix coloring of bonds via overpaint 2020-05-06 17:29:55 -07:00
Alexander Rose
f8ce9cbb65 determine type of sequence for alignment 2020-05-06 15:38:51 -07:00
Alexander Rose
2af9d1cabf limit by chains superposition to polymers 2020-05-06 11:32:31 -07:00
Alexander Rose
e8d1737d40 backbone/sidechain query fixes
- handle non-polymer components in polymers
2020-05-06 10:56:01 -07:00
Alexander Rose
0328e93518 interactivity: selectOnly, only deselect for the structure of the given loci 2020-05-06 10:37:11 -07:00
Alexander Rose
8a4ab9bdb9 more selection helper fixes
- use structure from last decorator as reference
- handle that oldObj is not defined for inserts
2020-05-05 22:10:53 -07:00
Alexander Rose
410cdb193d selection manager fixes
- add removed/updated events to substructure-parent-helper
- remap selections
2020-05-05 17:03:04 -07:00
Alexander Rose
a278337b4c Merge branch 'master' of https://github.com/molstar/molstar 2020-05-05 11:08:46 -07:00
Alexander Rose
b1308de0b9 StructureSelectionManager improvements
- remap/clear referenceLoci onUpdate
- remap/clear history onUpdate
- removed unused prevHighlight
- support group-by-structure in modifyHistory
2020-05-05 11:08:05 -07:00
Alexander Rose
9705078970 set writeDepth specifically for points and text geo 2020-05-05 10:12:51 -07:00
Alexander Rose
b1ca98e945 ignore atoms with zero occupancy for bond computation
- assuming they are not actually atoms
2020-05-05 09:54:47 -07:00
David Sehnal
35054eaca9 0.7.0-dev.19 2020-05-05 17:45:21 +02:00
David Sehnal
2747c743c9 fix ElementLocationIterator.advance
- extra element was added if last unit size === 1
2020-05-05 17:41:17 +02:00
Alexander Rose
031d08a8d4 Merge branch 'master' of https://github.com/molstar/molstar 2020-05-04 19:31:09 -07:00
Alexander Rose
7cc6c4a9c8 superposition fixes 2020-05-04 19:30:57 -07:00
David Sehnal
ff27098514 refactored PluginSpec.config 2020-05-05 00:32:43 +02:00
David Sehnal
545cd65066 Merge branch 'master' of https://github.com/molstar/molstar 2020-05-04 22:32:53 +02:00
David Sehnal
84bfc6e7a9 VolumeStreamingControls show "update" button with "reset params" button 2020-05-04 22:31:55 +02:00
David Sehnal
2f71c4c5e4 VolumeStreamingCustomControls bounded ranges for fine grained control 2020-05-04 22:09:28 +02:00
Alexander Rose
1448f7aeb6 added PluginConfig.Structure.SizeThresholds 2020-05-04 13:00:45 -07:00
David Sehnal
79d66a5cfc revert select binding change 2020-05-04 19:42:33 +02:00
Alexander Rose
2ec19ac04c fixed rsrz label 2020-05-04 10:08:48 -07:00
David Sehnal
f62a6d4512 Selection mode: change binding to right-click
- this way the non-selection mode behavior stays the same and the right click adds functionality
2020-05-04 17:48:37 +02:00
David Sehnal
4fbcee3953 Basic Selection mode UI help
- could use some improvement
2020-05-04 17:42:31 +02:00
David Sehnal
12bb283b97 StructureSelectionManager.clear also clears selection history 2020-05-04 17:19:48 +02:00
David Sehnal
13d776c7cb Icon tweaks
- measurement remove
- selection mode
2020-05-04 17:14:40 +02:00
David Sehnal
f45b48c6e1 slider CSS fix 2020-05-04 17:00:02 +02:00
David Sehnal
ff14c94a90 PluginContext.isBusy fix + related UI fix 2020-05-04 12:08:57 +02:00
David Sehnal
0a0ef35b74 CombinedColorControl: RGB input as separate boxes 2020-05-04 11:48:45 +02:00
David Sehnal
e3dc10c085 ObjectListEditor: bugfix 2020-05-04 11:31:30 +02:00
David Sehnal
46113bf3d4 PluginStateAnimation.canApply 2020-05-03 20:43:49 +02:00
David Sehnal
0f3ef61f7d isBusy behavior bugfix
- was causing the animation button not to display if the state was loaded too fast
2020-05-03 13:10:52 +02:00
Alexander Rose
86aae08257 assembly-symmetry: dihedral cage fixes 2020-05-02 12:59:54 -07:00
Alexander Rose
06bf2c39a1 add pdb-dev download bcif encoding 2020-05-02 12:59:31 -07:00
Alexander Rose
66a23bc2a2 alignment fixes 2020-05-02 12:58:32 -07:00
David Sehnal
51e86f1e43 Superposition UI: add "toggle selection" button to help 2020-05-02 13:40:27 +02:00
Alexander Rose
78c70b3f5b structure superposition by chains or by atoms
- superposition by chains can be guided by sequence alignment
- TODO not working with already transformed structures in the general case
2020-05-01 19:08:31 -07:00
Alexander Rose
8f52ffe061 move state/session io warning to snapshot ui 2020-05-01 13:06:25 -07:00
Alexander Rose
e95b91ab84 typo 2020-04-30 15:51:54 -07:00
Alexander Rose
f4dbd66496 Merge branch 'master' of https://github.com/molstar/molstar 2020-04-30 15:49:31 -07:00
Alexander Rose
5895df0499 tweaked elementInstances granularity label 2020-04-30 15:49:16 -07:00
Alexander Rose
6d0d88f3be added sidechain queries 2020-04-30 15:44:41 -07:00
David Sehnal
7e71428cc3 RemoteStateSnapshots: fix unmounted setState 2020-05-01 00:15:22 +02:00
David Sehnal
2e215440f7 Merge branch 'master' of https://github.com/molstar/molstar 2020-05-01 00:09:29 +02:00
David Sehnal
c04fa56c6c fixed expandConnected query, added expand to cov/metallic bonds 2020-05-01 00:09:13 +02:00
Alexander Rose
6c70b5e38f take structure volume into account in getQualityProps for resolution 2020-04-30 15:00:40 -07:00
Alexander Rose
ad9160a4a3 guard against overly high surface resolution 2020-04-30 15:00:07 -07:00
Alexander Rose
c747d3928e always apply marker in shader 2020-04-30 11:34:50 -07:00
David Sehnal
68e8d67054 mol-plugin: PluginState.dispose unregisters behaviors 2020-04-30 18:29:47 +02:00
David Sehnal
9e8fc76d28 StructureSelectionManager: loci events 2020-04-30 16:02:16 +02:00
Alexander Rose
d8970305de cellpack: cleanup
- renamed GetAllAssamblyinOne to StructureFromAssemblies
- removed GetAllAssamblyinOneStructure
2020-04-29 22:21:22 -07:00
Alexander Rose
824675a658 cellpack: split color themes 2020-04-29 21:49:37 -07:00
Alexander Rose
090ad613cc Merge pull request #35 from corredD/forkdev
Forkdev
2020-04-29 20:49:00 -07:00
Alexander Rose
ea35e09c96 focus repr carbon color tweaks 2020-04-29 14:19:45 -07:00
Alexander Rose
d8b9a1a560 basic nw sequence alignment 2020-04-29 13:54:00 -07:00
Alexander Rose
c259f58e63 make carbon color configurable in element symbol color theme 2020-04-29 13:33:31 -07:00
Alexander Rose
9d4c2a1147 support setting pdb/emdb provider in viewer index.html 2020-04-29 13:32:58 -07:00
David Sehnal
f13c3fe38b Viewport resize handling fix 2020-04-29 15:14:35 +02:00
David Sehnal
60409df145 better Viewport resize handling 2020-04-29 15:13:53 +02:00
autin
1d7321cd6f Merge branch 'master' of https://github.com/molstar/molstar into forkdev
# Conflicts:
#	src/extensions/cellpack/property.ts
2020-04-29 10:01:39 +02:00
Alexander Rose
eb68ccbf6b very basic handle-helper 2020-04-28 23:59:54 -07:00
Alexander Rose
b1ece44c49 0.7.0-dev.18 2020-04-28 17:06:40 -07:00
Alexander Rose
37ae274fb6 force bond compute for sequence positions with micro-heterogeneity
- see e.g. 3NIR
2020-04-28 17:04:13 -07:00
Alexander Rose
c900045fcd refactored sequence to handle partial structures 2020-04-28 16:34:12 -07:00
Alexander Rose
50d95ccf6a volume refactoring
- renamed VolumeData to Grid
- renamed VolumeData.data to cells
- renamed VolumeData.dataStats to stats
- added grid to Volume
- added label to Volume
- added custom props to volume
- use Volume instead of VolumeData/Grid as main object
2020-04-28 12:19:42 -07:00
Alexander Rose
c9171444eb added State.root and fix StateAction.createDefaultParams/.params 2020-04-28 10:06:22 -07:00
autin
56ea62af71 Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2020-04-28 11:06:27 +02:00
Alexander Rose
9e81626928 use extensions in embedded viewer to not break compat 2020-04-27 23:35:21 -07:00
Alexander Rose
84fda6e35d 0.7.0-dev.17 2020-04-27 20:01:02 -07:00
Alexander Rose
0f758cf554 fix webpack prod build setup 2020-04-27 19:59:25 -07:00
autin
b4808f2909 resampling optional. added the latest models 2020-04-27 12:32:20 +02:00
autin
1a2e9eaa84 Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2020-04-27 09:24:00 +02:00
autin
7e3cca5780 Merge branch 'master' of https://github.com/molstar/molstar into forkdev
# Conflicts:
#	src/extensions/cellpack/model.ts
2020-04-26 11:50:33 +02:00
autin
0b9371527e Merge branch 'master' of https://github.com/molstar/molstar into forkdev 2020-04-25 08:30:19 +02:00
autin
7aee2d805d eslint fix 2020-04-24 15:01:21 +02:00
autin
06f03f399a lipid membrane tiles as assembly support, added ingredient colors if provided. 2020-04-24 14:57:14 +02:00
144 changed files with 2635 additions and 1052 deletions

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.7.0-dev.16",
"version": "0.7.1-dev.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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++) {

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -207,5 +207,6 @@ export namespace Points {
!props.pointFilledCircle ||
(props.pointFilledCircle && props.pointEdgeBleach === 0)
);
state.writeDepth = state.opaque;
}
}

View File

@@ -283,6 +283,7 @@ export namespace Text {
BaseGeometry.updateRenderableState(state, props);
state.pickable = false;
state.opaque = false;
state.writeDepth = true;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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