mirror of
https://github.com/molstar/molstar.git
synced 2026-06-06 22:54:22 +08:00
Compare commits
62 Commits
v0.7.0-dev
...
v0.7.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6605052db | ||
|
|
8514175da2 | ||
|
|
6a49427fc0 | ||
|
|
7c18e5eb86 | ||
|
|
2a7d258715 | ||
|
|
54fb9beeee | ||
|
|
27ebbc50d5 | ||
|
|
2a1b6e52b2 | ||
|
|
3110e82d92 | ||
|
|
4be999ce32 | ||
|
|
f0d7a4ed2a | ||
|
|
2dacfcb485 | ||
|
|
6218cc5371 | ||
|
|
056ce42097 | ||
|
|
b14b5ca626 | ||
|
|
ffbaa944f2 | ||
|
|
e2ba96174a | ||
|
|
8c5d99bb54 | ||
|
|
b18b3be070 | ||
|
|
2e69b7c419 | ||
|
|
5007f5fb72 | ||
|
|
6fe83a9a70 | ||
|
|
20af084127 | ||
|
|
d6501170e6 | ||
|
|
5f33364514 | ||
|
|
7924c008fa | ||
|
|
2d2a53f28e | ||
|
|
1f7ffabef9 | ||
|
|
16d5c07224 | ||
|
|
2392bfb579 | ||
|
|
b4036f576c | ||
|
|
690d6812dc | ||
|
|
a44aa02f13 | ||
|
|
65ddd6d68a | ||
|
|
754025b3b1 | ||
|
|
f0649c5aa3 | ||
|
|
6df045211c | ||
|
|
8a00540de0 | ||
|
|
0d78905686 | ||
|
|
6edab203c2 | ||
|
|
0abfdb5ee3 | ||
|
|
88369158c9 | ||
|
|
8926575283 | ||
|
|
15b0288ce4 | ||
|
|
28853ec19d | ||
|
|
e3480a076a | ||
|
|
abf6452124 | ||
|
|
2cdd811dd3 | ||
|
|
be6fea39bf | ||
|
|
ed1bc2cd07 | ||
|
|
28d3d5861a | ||
|
|
95d3ef491f | ||
|
|
5c37ddfc6d | ||
|
|
4d9e2d9c91 | ||
|
|
0b1c18913d | ||
|
|
c8c2355d3e | ||
|
|
3d1366024d | ||
|
|
f6964d2a66 | ||
|
|
de60f70af5 | ||
|
|
8f2e619162 | ||
|
|
fbcef01c55 | ||
|
|
641e0639d4 |
2637
package-lock.json
generated
2637
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.7.0-dev.4",
|
||||
"version": "0.7.0-dev.16",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -34,12 +34,12 @@
|
||||
"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",
|
||||
"postversion": "git push && git push --tags",
|
||||
"prepublishOnly": "npm run test && npm run build"
|
||||
"preversion": "npm run test && npm run build",
|
||||
"postversion": "git push && git push --tags"
|
||||
},
|
||||
"files": [
|
||||
"lib/"
|
||||
"lib/",
|
||||
"build/viewer/"
|
||||
],
|
||||
"bin": {
|
||||
"cif2bcif": "lib/apps/cif2bcif/index.js",
|
||||
@@ -82,52 +82,52 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^1.13.2",
|
||||
"@graphql-codegen/cli": "^1.13.2",
|
||||
"@graphql-codegen/time": "^1.13.2",
|
||||
"@graphql-codegen/typescript": "^1.13.2",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.13.2",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.13.2",
|
||||
"@graphql-codegen/typescript-operations": "^1.13.2",
|
||||
"@graphql-codegen/add": "^1.13.3",
|
||||
"@graphql-codegen/cli": "^1.13.3",
|
||||
"@graphql-codegen/time": "^1.13.3",
|
||||
"@graphql-codegen/typescript": "^1.13.3",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.13.3",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.13.3",
|
||||
"@graphql-codegen/typescript-operations": "^1.13.3",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@typescript-eslint/eslint-plugin": "^2.28.0",
|
||||
"@typescript-eslint/parser": "^2.28.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.29.0",
|
||||
"@typescript-eslint/parser": "^2.29.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"circular-dependency-plugin": "^5.2.0",
|
||||
"concurrently": "^5.1.0",
|
||||
"cpx2": "^2.0.0",
|
||||
"css-loader": "^3.5.2",
|
||||
"css-loader": "^3.5.3",
|
||||
"eslint": "^6.8.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.0.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"graphql": "^15.0.0",
|
||||
"http-server": "^0.12.1",
|
||||
"jest": "^25.3.0",
|
||||
"jest": "^25.4.0",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"node-sass": "^4.14.0",
|
||||
"raw-loader": "^4.0.1",
|
||||
"resolve-url-loader": "^3.1.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"simple-git": "^1.132.0",
|
||||
"style-loader": "^1.1.4",
|
||||
"style-loader": "^1.2.0",
|
||||
"ts-jest": "^25.4.0",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-version-file-plugin": "^0.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.9.10",
|
||||
"@material-ui/core": "^4.9.11",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@types/argparse": "^1.0.38",
|
||||
"@types/benchmark": "^1.0.31",
|
||||
"@types/compression": "1.7.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/node": "^13.13.0",
|
||||
"@types/node-fetch": "^2.5.6",
|
||||
"@types/node": "^13.13.2",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/react": "^16.9.34",
|
||||
"@types/react-dom": "^16.9.6",
|
||||
"@types/swagger-ui-dist": "3.0.5",
|
||||
|
||||
65
src/apps/viewer/embedded.html
Normal file
65
src/apps/viewer/embedded.html
Normal file
@@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<title>Embedded Mol* Viewer</title>
|
||||
<style>
|
||||
#app {
|
||||
position: absolute;
|
||||
left: 100px;
|
||||
top: 100px;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<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,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: false,
|
||||
layoutShowLeftPanel: true,
|
||||
|
||||
viewportShowExpand: true,
|
||||
viewportShowSelectionMode: false,
|
||||
viewportShowAnimation: false,
|
||||
|
||||
pdbProvider: 'rcsb',
|
||||
emdbProvider: 'rcsb',
|
||||
});
|
||||
viewer.loadPdb('7bv2');
|
||||
viewer.loadEmdb('EMD-30210');
|
||||
|
||||
// TODO add Volume.customProperty and load suggested isoValue via custom property
|
||||
var sub = viewer.plugin.managers.volume.hierarchy.behaviors.selection.subscribe(function (value) {
|
||||
if (value.volume?.representations[0]) {
|
||||
var ref = value.volume.representations[0].cell;
|
||||
var tree = viewer.plugin.state.data.build().to(ref).update({
|
||||
type: {
|
||||
name: 'isosurface',
|
||||
params: {
|
||||
isoValue: {
|
||||
kind: 'relative',
|
||||
relativeValue: 6
|
||||
}
|
||||
}
|
||||
},
|
||||
colorTheme: ref.transform.params?.colorTheme
|
||||
});
|
||||
viewer.plugin.runTask(viewer.plugin.state.data.updateTree(tree));
|
||||
if (typeof sub !== 'undefined') sub.unsubscribe();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -34,10 +34,43 @@
|
||||
height: 600px;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="app.css" />
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
function getParam(name, regex) {
|
||||
var r = new RegExp(name + '=' + '(' + regex + ')[&]?', 'i');
|
||||
return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || '');
|
||||
}
|
||||
|
||||
var hideControls = getParam('hide-controls', '[^&]+').trim() === '1';
|
||||
var viewer = new molstar.Viewer('app', {
|
||||
layoutShowControls: !hideControls,
|
||||
viewportShowExpand: false,
|
||||
});
|
||||
|
||||
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();
|
||||
if (snapshotUrl && snapshotUrlType) viewer.loadSnapshotFromUrl(snapshotUrl, snapshotUrlType);
|
||||
|
||||
var structureUrl = getParam('structure-url', '[^&]+').trim();
|
||||
var structureUrlFormat = getParam('structure-url-format', '[a-z]+').toLowerCase().trim();
|
||||
var structureUrlIsBinary = getParam('structure-url-is-binary', '[^&]+').trim() === '1';
|
||||
if (structureUrl) viewer.loadStructureFromUrl(structureUrl, structureUrlFormat, structureUrlIsBinary);
|
||||
|
||||
var pdb = getParam('pdb', '[^&]+').trim();
|
||||
if (pdb) viewer.loadPdb(pdb);
|
||||
|
||||
var pdbDev = getParam('pdb-dev', '[^&]+').trim();
|
||||
if (pdbDev) viewer.loadPdbDev(pdbDev);
|
||||
|
||||
var emdb = getParam('emdb', '[^&]+').trim();
|
||||
if (emdb) viewer.loadEmdb(emdb);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -8,84 +8,110 @@
|
||||
import '../../mol-util/polyfill';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html';
|
||||
import './embedded.html';
|
||||
import './favicon.ico';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { DownloadStructure } from '../../mol-plugin-state/actions/structure';
|
||||
import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
import { CellPack } from '../../extensions/cellpack';
|
||||
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { ObjectKeys } from '../../mol-util/type-helpers';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
|
||||
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
function getParam(name: string, regex: string): string {
|
||||
let r = new RegExp(`${name}=(${regex})[&]?`, 'i');
|
||||
return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || '');
|
||||
}
|
||||
const Extensions = {
|
||||
'cellpack': PluginSpec.Behavior(CellPack),
|
||||
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
|
||||
'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport)
|
||||
};
|
||||
|
||||
const hideControls = getParam('hide-controls', `[^&]+`) === '1';
|
||||
const DefaultViewerOptions = {
|
||||
extensions: ObjectKeys(Extensions),
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: true,
|
||||
layoutShowRemoteState: true,
|
||||
layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay,
|
||||
layoutShowSequence: true,
|
||||
layoutShowLog: true,
|
||||
layoutShowLeftPanel: true,
|
||||
|
||||
function init() {
|
||||
const spec: PluginSpec = {
|
||||
actions: [...DefaultPluginSpec.actions],
|
||||
behaviors: [
|
||||
...DefaultPluginSpec.behaviors,
|
||||
PluginSpec.Behavior(CellPack),
|
||||
PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
PluginSpec.Behavior(RCSBAssemblySymmetry),
|
||||
PluginSpec.Behavior(RCSBValidationReport),
|
||||
],
|
||||
animations: [...DefaultPluginSpec.animations || []],
|
||||
customParamEditors: DefaultPluginSpec.customParamEditors,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: true,
|
||||
showControls: !hideControls
|
||||
viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue,
|
||||
viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue,
|
||||
viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue,
|
||||
pluginStateServer: PluginConfig.State.DefaultServer.defaultValue,
|
||||
volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue,
|
||||
pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
|
||||
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
|
||||
};
|
||||
type ViewerOptions = typeof DefaultViewerOptions;
|
||||
|
||||
export class Viewer {
|
||||
plugin: PluginContext
|
||||
|
||||
constructor(elementId: string, options: Partial<ViewerOptions> = {}) {
|
||||
const o = { ...DefaultViewerOptions, ...options };
|
||||
|
||||
const spec: PluginSpec = {
|
||||
actions: [...DefaultPluginSpec.actions],
|
||||
behaviors: [
|
||||
...DefaultPluginSpec.behaviors,
|
||||
...o.extensions.map(e => Extensions[e]),
|
||||
],
|
||||
animations: [...DefaultPluginSpec.animations || []],
|
||||
customParamEditors: DefaultPluginSpec.customParamEditors,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: o.layoutIsExpanded,
|
||||
showControls: o.layoutShowControls,
|
||||
controlsDisplay: o.layoutControlsDisplay,
|
||||
},
|
||||
controls: {
|
||||
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls,
|
||||
top: o.layoutShowSequence ? undefined : 'none',
|
||||
bottom: o.layoutShowLog ? undefined : 'none',
|
||||
left: o.layoutShowLeftPanel ? undefined : 'none',
|
||||
}
|
||||
},
|
||||
controls: {
|
||||
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls
|
||||
}
|
||||
},
|
||||
config: DefaultPluginSpec.config
|
||||
};
|
||||
spec.config?.set(PluginConfig.Viewport.ShowExpand, false);
|
||||
const plugin = createPlugin(document.getElementById('app')!, spec);
|
||||
trySetSnapshot(plugin);
|
||||
tryLoadFromUrl(plugin);
|
||||
}
|
||||
components: {
|
||||
...DefaultPluginSpec.components,
|
||||
remoteState: o.layoutShowRemoteState ? 'default' : 'none',
|
||||
},
|
||||
config: DefaultPluginSpec.config
|
||||
};
|
||||
|
||||
async function trySetSnapshot(ctx: PluginContext) {
|
||||
try {
|
||||
const snapshotUrl = getParam('snapshot-url', `[^&]+`);
|
||||
const snapshotId = getParam('snapshot-id', `[^&]+`);
|
||||
if (!snapshotUrl && !snapshotId) return;
|
||||
// TODO parametrize the server
|
||||
const url = snapshotId
|
||||
? `https://webchem.ncbr.muni.cz/molstar-state/get/${snapshotId}`
|
||||
: snapshotUrl;
|
||||
await PluginCommands.State.Snapshots.Fetch(ctx, { url });
|
||||
} catch (e) {
|
||||
ctx.log.error('Failed to load snapshot.');
|
||||
console.warn('Failed to load snapshot', e);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
async function tryLoadFromUrl(ctx: PluginContext) {
|
||||
const url = getParam('loadFromURL', '[^&]+').trim();
|
||||
try {
|
||||
if (!url) return;
|
||||
async setRemoteSnapshot(id: string) {
|
||||
const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`;
|
||||
await PluginCommands.State.Snapshots.Fetch(this.plugin, { url });
|
||||
}
|
||||
|
||||
let format = 'cif', isBinary = false;
|
||||
switch (getParam('loadFromURLFormat', '[a-z]+').toLocaleLowerCase().trim()) {
|
||||
case 'pdb': format = 'pdb'; break;
|
||||
case 'mmbcif': isBinary = true; break;
|
||||
}
|
||||
async loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) {
|
||||
await PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
|
||||
}
|
||||
|
||||
const params = DownloadStructure.createDefaultParams(void 0 as any, ctx);
|
||||
|
||||
return ctx.runTask(ctx.state.data.applyAction(DownloadStructure, {
|
||||
async loadStructureFromUrl(url: string, format = 'cif', isBinary = false) {
|
||||
const params = DownloadStructure.createDefaultParams(undefined, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'url',
|
||||
params: {
|
||||
@@ -96,10 +122,54 @@ async function tryLoadFromUrl(ctx: PluginContext) {
|
||||
}
|
||||
}
|
||||
}));
|
||||
} catch (e) {
|
||||
ctx.log.error(`Failed to load from URL (${url})`);
|
||||
console.warn(`Failed to load from URL (${url})`, e);
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
async loadPdb(pdb: string) {
|
||||
const params = DownloadStructure.createDefaultParams(undefined, this.plugin);
|
||||
const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!;
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'pdb' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: pdb,
|
||||
server: {
|
||||
name: provider,
|
||||
params: PdbDownloadProvider[provider].defaultValue as any
|
||||
}
|
||||
},
|
||||
options: params.source.params.options,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async loadPdbDev(pdbDev: string) {
|
||||
const params = DownloadStructure.createDefaultParams(undefined, this.plugin);
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
|
||||
source: {
|
||||
name: 'pdb-dev' as const,
|
||||
params: {
|
||||
id: pdbDev,
|
||||
options: params.source.params.options,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async loadEmdb(emdb: string) {
|
||||
const provider = this.plugin.config.get(PluginConfig.Download.DefaultEmdbProvider)!;
|
||||
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, {
|
||||
source: {
|
||||
name: 'pdb-emd-ds' as const,
|
||||
params: {
|
||||
provider: {
|
||||
id: emdb,
|
||||
server: provider,
|
||||
},
|
||||
detail: 3,
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="app.css" />
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -55,13 +55,13 @@
|
||||
</select>
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
<script>
|
||||
function $(id) { return document.getElementById(id); }
|
||||
|
||||
|
||||
var pdbId = '1grm', assemblyId= '1';
|
||||
var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif';
|
||||
var format = 'mmcif';
|
||||
|
||||
|
||||
$('url').value = url;
|
||||
$('url').onchange = function (e) { url = e.target.value; }
|
||||
$('assemblyId').value = assemblyId;
|
||||
@@ -86,7 +86,7 @@
|
||||
|
||||
addHeader('Camera');
|
||||
addControl('Toggle Spin', () => BasicMolStarWrapper.toggleSpin());
|
||||
|
||||
|
||||
addSeparator();
|
||||
|
||||
addHeader('Animation');
|
||||
@@ -115,7 +115,7 @@
|
||||
addControl('Static Superposition', () => BasicMolStarWrapper.tests.staticSuperposition());
|
||||
addControl('Dynamic Superposition', () => BasicMolStarWrapper.tests.dynamicSuperposition());
|
||||
addControl('Validation Tooltip', () => BasicMolStarWrapper.tests.toggleValidationTooltip());
|
||||
|
||||
|
||||
addControl('Show Toasts', () => BasicMolStarWrapper.tests.showToasts());
|
||||
addControl('Hide Toasts', () => BasicMolStarWrapper.tests.hideToasts());
|
||||
|
||||
|
||||
@@ -38,16 +38,16 @@
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="app.css" />
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id='controls'></div>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
<script>
|
||||
LightingDemo.init('app')
|
||||
LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' })
|
||||
|
||||
|
||||
addHeader('Example PDB IDs');
|
||||
addControl('1M07', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/1M07.cif', assemblyId: '1' }));
|
||||
addControl('6HY0', () => LightingDemo.load({ url: 'https://files.rcsb.org/download/6HY0.cif', assemblyId: '1' }));
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
width: 300px;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="app.css" />
|
||||
<link rel="stylesheet" type="text/css" href="molstar.css" />
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -65,7 +65,7 @@
|
||||
<div id="app"></div>
|
||||
<div id="volume-streaming-wrapper"></div>
|
||||
<script>
|
||||
// it might be a good idea to define these colors in a separate script file
|
||||
// it might be a good idea to define these colors in a separate script file
|
||||
var CustomColors = [0x00ff00, 0x0000ff];
|
||||
|
||||
// create an instance of the plugin
|
||||
@@ -74,11 +74,11 @@
|
||||
console.log('Wrapper version', MolStarProteopediaWrapper.VERSION_MAJOR, MolStarProteopediaWrapper.VERSION_MINOR);
|
||||
|
||||
function $(id) { return document.getElementById(id); }
|
||||
|
||||
|
||||
var pdbId = '1cbs', assemblyId= 'preferred', isBinary = true;
|
||||
var url = 'https://www.ebi.ac.uk/pdbe/entry-files/download/' + pdbId + '.bcif'
|
||||
var format = 'cif';
|
||||
|
||||
|
||||
$('url').value = url;
|
||||
$('url').onchange = function (e) { url = e.target.value; }
|
||||
$('assemblyId').value = assemblyId;
|
||||
@@ -92,6 +92,12 @@
|
||||
// var format = 'pdb';
|
||||
// var assemblyId = 'deposited';
|
||||
|
||||
function loadAndSnapshot(params) {
|
||||
PluginWrapper.load(params).then(() => {
|
||||
setTimeout(() => snapshot = PluginWrapper.plugin.state.getSnapshot({ canvas3d: false /* do not save spinning state */ }), 500);
|
||||
});
|
||||
}
|
||||
|
||||
var representationStyle = {
|
||||
// sequence: { coloring: 'proteopedia-custom' }, // or just { }
|
||||
hetGroups: { kind: 'ball-and-stick' }, // or 'spacefill
|
||||
@@ -103,7 +109,7 @@
|
||||
customColorList: CustomColors
|
||||
});
|
||||
PluginWrapper.setBackground(0xffffff);
|
||||
PluginWrapper.load({ url: url, format: format, isBinary: isBinary, assemblyId: assemblyId, representationStyle: representationStyle });
|
||||
loadAndSnapshot({ url: url, format: format, isBinary: isBinary, assemblyId: assemblyId, representationStyle: representationStyle });
|
||||
PluginWrapper.toggleSpin();
|
||||
|
||||
PluginWrapper.events.modelInfo.subscribe(function (info) {
|
||||
@@ -111,8 +117,8 @@
|
||||
listHetGroups(info);
|
||||
});
|
||||
|
||||
addControl('Load Asym Unit', () => PluginWrapper.load({ url: url, format: format, isBinary }));
|
||||
addControl('Load Assembly', () => PluginWrapper.load({ url: url, format: format, isBinary, assemblyId: assemblyId }));
|
||||
addControl('Load Asym Unit', () => loadAndSnapshot({ url: url, format: format, isBinary }));
|
||||
addControl('Load Assembly', () => loadAndSnapshot({ url: url, format: format, isBinary, assemblyId: assemblyId }));
|
||||
|
||||
addSeparator();
|
||||
|
||||
@@ -138,7 +144,7 @@
|
||||
// Same as "wheel icon" and Viewport options
|
||||
// addControl('Clip', () => PluginWrapper.viewport.setSettings({ clip: [33, 66] }));
|
||||
// addControl('Reset Clip', () => PluginWrapper.viewport.setSettings({ clip: [1, 100] }));
|
||||
|
||||
|
||||
addSeparator();
|
||||
|
||||
addHeader('Animation');
|
||||
@@ -171,7 +177,7 @@
|
||||
addControl('Init', () => PluginWrapper.experimentalData.init($('volume-streaming-wrapper')));
|
||||
addControl('Remove', () => PluginWrapper.experimentalData.remove());
|
||||
|
||||
addSeparator();
|
||||
addSeparator();
|
||||
addHeader('State');
|
||||
|
||||
var snapshot;
|
||||
@@ -185,10 +191,10 @@
|
||||
PluginWrapper.snapshot.set(snapshot);
|
||||
});
|
||||
addControl('Download State', () => {
|
||||
snapshot = PluginWrapper.snapshot.download('molj');
|
||||
PluginWrapper.snapshot.download('molj');
|
||||
});
|
||||
addControl('Download Session', () => {
|
||||
snapshot = PluginWrapper.snapshot.download('molx');
|
||||
PluginWrapper.snapshot.download('molx');
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
@@ -5,35 +5,34 @@
|
||||
*/
|
||||
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Canvas3DProps, DefaultCanvas3DParams } from '../../mol-canvas3d/canvas3d';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in';
|
||||
import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
|
||||
import { EvolutionaryConservation } from './annotation';
|
||||
import { LoadParams, SupportedFormats, RepresentationStyle, ModelInfo, StateElements } from './helpers';
|
||||
import { RxEventHelper } from '../../mol-util/rx-event-helper';
|
||||
import { volumeStreamingControls } from './ui/controls';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { Scheduler } from '../../mol-task';
|
||||
import { createProteopediaCustomTheme } from './coloring';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { InitVolumeStreaming, CreateVolumeStreamingInfo } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { DefaultCanvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { getFormattedTime } from '../../mol-util/date';
|
||||
import { PluginStateObject, PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { CreateVolumeStreamingInfo, InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { StateBuilder, StateObject, StateSelection } from '../../mol-state';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { getFormattedTime } from '../../mol-util/date';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { RxEventHelper } from '../../mol-util/rx-event-helper';
|
||||
import { EvolutionaryConservation } from './annotation';
|
||||
import { createProteopediaCustomTheme } from './coloring';
|
||||
import { LoadParams, ModelInfo, RepresentationStyle, StateElements, SupportedFormats } from './helpers';
|
||||
import './index.html';
|
||||
import { volumeStreamingControls } from './ui/controls';
|
||||
require('../../mol-plugin-ui/skin/light.scss');
|
||||
|
||||
class MolStarProteopediaWrapper {
|
||||
static VERSION_MAJOR = 5;
|
||||
static VERSION_MINOR = 4;
|
||||
static VERSION_MINOR = 5;
|
||||
|
||||
private _ev = RxEventHelper.create();
|
||||
|
||||
@@ -233,7 +232,6 @@ class MolStarProteopediaWrapper {
|
||||
await this.updateStyle(representationStyle);
|
||||
|
||||
this.loadedParams = { url, format, assemblyId };
|
||||
Scheduler.setImmediate(() => PluginCommands.Camera.Reset(this.plugin, { }));
|
||||
}
|
||||
|
||||
async updateStyle(style?: RepresentationStyle, partial?: boolean) {
|
||||
@@ -407,7 +405,7 @@ class MolStarProteopediaWrapper {
|
||||
try {
|
||||
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'binary' }));
|
||||
this.loadedParams = { ...this.emptyLoadedParams };
|
||||
await this.plugin.managers.snapshot.open(new File([data], `state.${type}`));
|
||||
return await this.plugin.managers.snapshot.open(new File([data], `state.${type}`));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
@@ -27,61 +27,75 @@ 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 { readFromFile } from '../../mol-util/data-source';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/results/${fileName}`;
|
||||
}
|
||||
|
||||
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, baseUrl: string, file?: Asset.File) {
|
||||
class TrajectoryCache {
|
||||
private map = new Map<string, Model.Trajectory>();
|
||||
set(id: string, trajectory: Model.Trajectory) { this.map.set(id, trajectory); }
|
||||
get(id: string) { return this.map.get(id); }
|
||||
}
|
||||
|
||||
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, baseUrl: string, trajCache: TrajectoryCache, file?: Asset.File) {
|
||||
const assetManager = plugin.managers.asset;
|
||||
const model_id = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
|
||||
const modelIndex = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
|
||||
const surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
|
||||
let model: Model;
|
||||
let trajectory = trajCache.get(id);
|
||||
let assets: Asset.Wrapper[] = [];
|
||||
if (file) {
|
||||
if (file.name.endsWith('.cif')) {
|
||||
const text = await plugin.runTask(assetManager.resolve(file, 'string'));
|
||||
assets.push(text);
|
||||
const cif = (await parseCif(plugin, text.data)).blocks[0];
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(cif)))[model_id];
|
||||
} else if (file.name.endsWith('.bcif')) {
|
||||
const binary = await plugin.runTask(assetManager.resolve(file, 'binary'));
|
||||
assets.push(binary);
|
||||
const cif = (await parseCif(plugin, binary.data)).blocks[0];
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(cif)))[model_id];
|
||||
} else if (file.name.endsWith('.pdb')) {
|
||||
const text = await plugin.runTask(assetManager.resolve(file, 'string'));
|
||||
assets.push(text);
|
||||
const pdb = await parsePDBfile(plugin, text.data, id);
|
||||
model = (await plugin.runTask(trajectoryFromPDB(pdb)))[model_id];
|
||||
} else {
|
||||
throw new Error(`unsupported file type '${file.name}'`);
|
||||
}
|
||||
} else if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
|
||||
if (surface){
|
||||
const data = await getFromOPM(plugin, id, assetManager);
|
||||
if (data.asset){
|
||||
assets.push(data.asset);
|
||||
model = (await plugin.runTask(trajectoryFromPDB(data.pdb)))[model_id];
|
||||
if (!trajectory) {
|
||||
if (file) {
|
||||
if (file.name.endsWith('.cif')) {
|
||||
const text = await plugin.runTask(assetManager.resolve(file, 'string'));
|
||||
assets.push(text);
|
||||
const cif = (await parseCif(plugin, text.data)).blocks[0];
|
||||
trajectory = await plugin.runTask(trajectoryFromMmCIF(cif));
|
||||
} else if (file.name.endsWith('.bcif')) {
|
||||
const binary = await plugin.runTask(assetManager.resolve(file, 'binary'));
|
||||
assets.push(binary);
|
||||
const cif = (await parseCif(plugin, binary.data)).blocks[0];
|
||||
trajectory = await plugin.runTask(trajectoryFromMmCIF(cif));
|
||||
} else if (file.name.endsWith('.pdb')) {
|
||||
const text = await plugin.runTask(assetManager.resolve(file, 'string'));
|
||||
assets.push(text);
|
||||
const pdb = await parsePDBfile(plugin, text.data, id);
|
||||
trajectory = await plugin.runTask(trajectoryFromPDB(pdb));
|
||||
} else {
|
||||
throw new Error(`unsupported file type '${file.name}'`);
|
||||
}
|
||||
} else if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
|
||||
if (surface){
|
||||
try {
|
||||
const data = await getFromOPM(plugin, id, assetManager);
|
||||
assets.push(data.asset);
|
||||
trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
|
||||
} catch (e) {
|
||||
// fallback to getFromPdb
|
||||
// console.error(e);
|
||||
const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
|
||||
assets.push(asset);
|
||||
trajectory = await plugin.runTask(trajectoryFromMmCIF(mmcif));
|
||||
}
|
||||
} else {
|
||||
const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
|
||||
assets.push(asset);
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(mmcif)))[model_id];
|
||||
trajectory = await plugin.runTask(trajectoryFromMmCIF(mmcif));
|
||||
}
|
||||
} else {
|
||||
const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
|
||||
assets.push(asset);
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(mmcif)))[model_id];
|
||||
}
|
||||
} else {
|
||||
const data = await getFromCellPackDB(plugin, id, baseUrl, assetManager);
|
||||
assets.push(data.asset);
|
||||
if ('pdb' in data) {
|
||||
model = (await plugin.runTask(trajectoryFromPDB(data.pdb)))[model_id];
|
||||
} else {
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(data.mmcif)))[model_id];
|
||||
const data = await getFromCellPackDB(plugin, id, baseUrl, assetManager);
|
||||
assets.push(data.asset);
|
||||
if ('pdb' in data) {
|
||||
trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
|
||||
} else {
|
||||
trajectory = await plugin.runTask(trajectoryFromMmCIF(data.mmcif));
|
||||
}
|
||||
}
|
||||
trajCache.set(id, trajectory);
|
||||
}
|
||||
const model = trajectory[modelIndex];
|
||||
return { model, assets };
|
||||
}
|
||||
|
||||
@@ -133,7 +147,6 @@ function getTransform(trans: Vec3, rot: Quat) {
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
function getResultTransforms(results: Ingredient['results'], legacy: boolean) {
|
||||
if (legacy) return results.map((r: Ingredient['results'][0]) => getTransformLegacy(r[0], r[1]));
|
||||
else return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1]));
|
||||
@@ -288,7 +301,7 @@ async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredi
|
||||
return getStructure(plugin, curveModel, ingredient.source);
|
||||
}
|
||||
|
||||
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles) {
|
||||
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache) {
|
||||
const { name, source, results, nbCurve } = ingredient;
|
||||
if (source.pdb === 'None') return;
|
||||
|
||||
@@ -303,7 +316,7 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
|
||||
}
|
||||
|
||||
// model id in case structure is NMR
|
||||
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, file);
|
||||
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, file);
|
||||
if (!model) return;
|
||||
|
||||
let structure: Structure;
|
||||
@@ -354,10 +367,11 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
|
||||
return Task.create('Create Packing Structure', async ctx => {
|
||||
const { ingredients, name } = packing;
|
||||
const assets: Asset.Wrapper[] = [];
|
||||
const trajCache = new TrajectoryCache();
|
||||
const structures: Structure[] = [];
|
||||
for (const iName in ingredients) {
|
||||
if (ctx.shouldUpdate) await ctx.update(iName);
|
||||
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles);
|
||||
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache);
|
||||
if (ingredientStructure) {
|
||||
structures.push(ingredientStructure.structure);
|
||||
assets.push(...ingredientStructure.assets);
|
||||
@@ -444,6 +458,8 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
|
||||
}
|
||||
|
||||
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
|
||||
const ingredientFiles = params.ingredients.files || [];
|
||||
|
||||
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
|
||||
if (params.source.name === 'id') {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl));
|
||||
@@ -451,12 +467,25 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
|
||||
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } });
|
||||
} else {
|
||||
const file = params.source.params;
|
||||
if (file === null) {
|
||||
if (!file?.file) {
|
||||
plugin.log.error('No file selected');
|
||||
return;
|
||||
}
|
||||
|
||||
let jsonFile: Asset.File;
|
||||
if (file.name.toLowerCase().endsWith('.zip')) {
|
||||
const data = await readFromFile(file.file, 'zip').runInContext(runtime);
|
||||
jsonFile = Asset.File(new File([data['model.json']], 'model.json'));
|
||||
objectForEach(data, (v, k) => {
|
||||
if (k === 'model.json') return;
|
||||
ingredientFiles.push(Asset.File(new File([v], k)));
|
||||
});
|
||||
} else {
|
||||
jsonFile = file;
|
||||
}
|
||||
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.ReadFile, { file, isBinary: false, label: file.name }, { state: { isGhost: true } });
|
||||
.apply(StateTransforms.Data.ReadFile, { file: jsonFile, isBinary: false, label: jsonFile.name }, { state: { isGhost: true } });
|
||||
}
|
||||
|
||||
const cellPackBuilder = cellPackJson
|
||||
@@ -469,7 +498,7 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
|
||||
await handleHivRna(plugin, packings, params.baseUrl);
|
||||
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
const p = { packing: i, baseUrl: params.baseUrl, ingredientFiles: params.ingredients.files };
|
||||
const p = { packing: i, baseUrl: params.baseUrl, ingredientFiles };
|
||||
|
||||
const packing = await state.build()
|
||||
.to(cellPackBuilder.ref)
|
||||
@@ -497,8 +526,8 @@ const LoadCellPackModelParams = {
|
||||
['influenza_model1.json', 'influenza_model1'],
|
||||
['ExosomeModel.json', 'ExosomeModel'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
] as const),
|
||||
'file': PD.File({ accept: 'id' }),
|
||||
] 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),
|
||||
ingredients : PD.Group({
|
||||
|
||||
@@ -225,6 +225,7 @@ function getSymbolScale(symbol: string) {
|
||||
function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.RotationAxes, size: number, structure: Structure) {
|
||||
const eye = Vec3();
|
||||
const target = Vec3();
|
||||
const dir = Vec3();
|
||||
const up = Vec3();
|
||||
let pair: Mutable<AssemblySymmetry.RotationAxes> | undefined = undefined;
|
||||
|
||||
@@ -246,8 +247,8 @@ function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.Rota
|
||||
const a5dir = Vec3.sub(Vec3(), a5.end, a5.start);
|
||||
pair = [a5];
|
||||
for (const a of axes.filter(a => a.order === 3)) {
|
||||
let d = radToDeg(Vec3.angle(Vec3.sub(up, a.end, a.start), a5dir));
|
||||
if (equalEps(d, 100.81, 0.1) || equalEps(d, 79.19, 0.1)) {
|
||||
const d = radToDeg(Vec3.angle(Vec3.sub(up, a.end, a.start), a5dir));
|
||||
if (!pair[1] && (equalEps(d, 100.81, 0.1) || equalEps(d, 79.19, 0.1))) {
|
||||
pair[1] = a;
|
||||
break;
|
||||
}
|
||||
@@ -263,8 +264,8 @@ function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.Rota
|
||||
Vec3.copy(target, aA.end);
|
||||
if (aB) {
|
||||
Vec3.sub(up, aB.end, aB.start);
|
||||
const d = Vec3.dot(eye, up);
|
||||
if (d < 0) Vec3.negate(up, up);
|
||||
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));
|
||||
} else {
|
||||
|
||||
@@ -340,7 +340,7 @@ export const ValidationReportDensityFitPreset = StructureRepresentationPresetPro
|
||||
description: 'Color structure based on density fit. Data from wwPDB Validation Report, obtained via RCSB PDB.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]) && Model.hasXrayMap(a.data.models[0]);
|
||||
return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]) && Model.isFromXray(a.data.models[0]) && Model.probablyHasDensityMap(a.data.models[0]);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
|
||||
@@ -67,7 +67,7 @@ export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationRep
|
||||
factory: DensityFitColorTheme,
|
||||
getParams: () => ({}),
|
||||
defaultValues: PD.getDefaultValues({}),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.hasXrayMap(ctx.structure.models[0]),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.isFromXray(ctx.structure.models[0]) && Model.probablyHasDensityMap(ctx.structure.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
|
||||
@@ -59,7 +59,7 @@ export const PostprocessingParams = {
|
||||
outline: PD.MappedStatic('off', {
|
||||
on: PD.Group({
|
||||
scale: PD.Numeric(1, { min: 0, max: 10, step: 1 }),
|
||||
threshold: PD.Numeric(0.8, { min: 0, max: 1, step: 0.01 }),
|
||||
threshold: PD.Numeric(0.8, { min: 0, max: 5, step: 0.01 }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Draw outline around 3D objects' })
|
||||
|
||||
@@ -44,9 +44,6 @@ export function createRenderable<T extends Values<RenderableSchema>>(renderItem:
|
||||
if (values.uAlpha && values.alpha) {
|
||||
ValueCell.updateIfChanged(values.uAlpha, clamp(values.alpha.ref.value * state.alphaFactor, 0, 1));
|
||||
}
|
||||
if (values.uPickable) {
|
||||
ValueCell.updateIfChanged(values.uPickable, state.pickable ? 1 : 0);
|
||||
}
|
||||
renderItem.render(variant);
|
||||
},
|
||||
getProgram: (variant: GraphicsRenderVariant) => renderItem.getProgram(variant),
|
||||
|
||||
@@ -73,7 +73,6 @@ export function DirectVolumeRenderable(ctx: WebGLContext, id: number, values: Di
|
||||
const schema = { ...GlobalUniformSchema, ...InternalSchema, ...DirectVolumeSchema };
|
||||
const internalValues: InternalValues = {
|
||||
uObjectId: ValueCell.create(id),
|
||||
uPickable: ValueCell.create(state.pickable ? 1 : 0),
|
||||
};
|
||||
const shaderCode = DirectVolumeShaderCode;
|
||||
const renderItem = createGraphicsRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }, materialId);
|
||||
|
||||
@@ -33,7 +33,6 @@ export function ImageRenderable(ctx: WebGLContext, id: number, values: ImageValu
|
||||
const schema = { ...GlobalUniformSchema, ...InternalSchema, ...ImageSchema };
|
||||
const internalValues: InternalValues = {
|
||||
uObjectId: ValueCell.create(id),
|
||||
uPickable: ValueCell.create(state.pickable ? 1 : 0),
|
||||
};
|
||||
const shaderCode = ImageShaderCode;
|
||||
const renderItem = createGraphicsRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }, materialId);
|
||||
|
||||
@@ -29,7 +29,6 @@ export function LinesRenderable(ctx: WebGLContext, id: number, values: LinesValu
|
||||
const schema = { ...GlobalUniformSchema, ...InternalSchema, ...LinesSchema };
|
||||
const internalValues: InternalValues = {
|
||||
uObjectId: ValueCell.create(id),
|
||||
uPickable: ValueCell.create(state.pickable ? 1 : 0)
|
||||
};
|
||||
const shaderCode = LinesShaderCode;
|
||||
const renderItem = createGraphicsRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }, materialId);
|
||||
|
||||
@@ -28,7 +28,6 @@ export function MeshRenderable(ctx: WebGLContext, id: number, values: MeshValues
|
||||
const schema = { ...GlobalUniformSchema, ...InternalSchema, ...MeshSchema };
|
||||
const internalValues: InternalValues = {
|
||||
uObjectId: ValueCell.create(id),
|
||||
uPickable: ValueCell.create(state.pickable ? 1 : 0)
|
||||
};
|
||||
const shaderCode = MeshShaderCode;
|
||||
const renderItem = createGraphicsRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }, materialId);
|
||||
|
||||
@@ -26,7 +26,6 @@ export function PointsRenderable(ctx: WebGLContext, id: number, values: PointsVa
|
||||
const schema = { ...GlobalUniformSchema, ...InternalSchema, ...PointsSchema };
|
||||
const internalValues: InternalValues = {
|
||||
uObjectId: ValueCell.create(id),
|
||||
uPickable: ValueCell.create(state.pickable ? 1 : 0)
|
||||
};
|
||||
const shaderCode = PointsShaderCode;
|
||||
const renderItem = createGraphicsRenderItem(ctx, 'points', shaderCode, schema, { ...values, ...internalValues }, materialId);
|
||||
|
||||
@@ -187,7 +187,6 @@ export type GlobalUniformValues = Values<GlobalUniformSchema> // { [k in keyof G
|
||||
|
||||
export const InternalSchema = {
|
||||
uObjectId: UniformSpec('i'),
|
||||
uPickable: UniformSpec('i', true),
|
||||
} as const;
|
||||
export type InternalSchema = typeof InternalSchema
|
||||
export type InternalValues = { [k in keyof InternalSchema]: ValueCell<any> }
|
||||
|
||||
@@ -29,7 +29,6 @@ export function SpheresRenderable(ctx: WebGLContext, id: number, values: Spheres
|
||||
const schema = { ...GlobalUniformSchema, ...InternalSchema, ...SpheresSchema };
|
||||
const internalValues: InternalValues = {
|
||||
uObjectId: ValueCell.create(id),
|
||||
uPickable: ValueCell.create(state.pickable ? 1 : 0)
|
||||
};
|
||||
const shaderCode = SpheresShaderCode;
|
||||
const renderItem = createGraphicsRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }, materialId);
|
||||
|
||||
@@ -38,7 +38,6 @@ export function TextRenderable(ctx: WebGLContext, id: number, values: TextValues
|
||||
const schema = { ...GlobalUniformSchema, ...InternalSchema, ...TextSchema };
|
||||
const internalValues: InternalValues = {
|
||||
uObjectId: ValueCell.create(id),
|
||||
uPickable: ValueCell.create(state.pickable ? 1 : 0)
|
||||
};
|
||||
const shaderCode = TextShaderCode;
|
||||
const renderItem = createGraphicsRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }, materialId);
|
||||
|
||||
@@ -31,7 +31,6 @@ export function TextureMeshRenderable(ctx: WebGLContext, id: number, values: Tex
|
||||
const schema = { ...GlobalUniformSchema, ...InternalSchema, ...TextureMeshSchema };
|
||||
const internalValues: InternalValues = {
|
||||
uObjectId: ValueCell.create(id),
|
||||
uPickable: ValueCell.create(state.pickable ? 1 : 0)
|
||||
};
|
||||
const shaderCode = MeshShaderCode;
|
||||
const renderItem = createGraphicsRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }, materialId);
|
||||
|
||||
@@ -173,51 +173,53 @@ namespace Renderer {
|
||||
let globalUniformsNeedUpdate = true;
|
||||
|
||||
const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: GraphicsRenderVariant) => {
|
||||
if (!r.state.visible || (!r.state.pickable && variant[0] === 'p')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const program = r.getProgram(variant);
|
||||
if (r.state.visible) {
|
||||
if (state.currentProgramId !== program.id) {
|
||||
// console.log('new program')
|
||||
globalUniformsNeedUpdate = true;
|
||||
program.use();
|
||||
}
|
||||
if (state.currentProgramId !== program.id) {
|
||||
// console.log('new program')
|
||||
globalUniformsNeedUpdate = true;
|
||||
program.use();
|
||||
}
|
||||
|
||||
if (globalUniformsNeedUpdate) {
|
||||
// console.log('globalUniformsNeedUpdate')
|
||||
program.setUniforms(globalUniformList);
|
||||
globalUniformsNeedUpdate = false;
|
||||
}
|
||||
if (globalUniformsNeedUpdate) {
|
||||
// console.log('globalUniformsNeedUpdate')
|
||||
program.setUniforms(globalUniformList);
|
||||
globalUniformsNeedUpdate = false;
|
||||
}
|
||||
|
||||
if (r.values.dDoubleSided) {
|
||||
if (r.values.dDoubleSided.ref.value) {
|
||||
state.disable(gl.CULL_FACE);
|
||||
} else {
|
||||
state.enable(gl.CULL_FACE);
|
||||
}
|
||||
} else {
|
||||
// webgl default
|
||||
if (r.values.dDoubleSided) {
|
||||
if (r.values.dDoubleSided.ref.value) {
|
||||
state.disable(gl.CULL_FACE);
|
||||
}
|
||||
|
||||
if (r.values.dFlipSided) {
|
||||
if (r.values.dFlipSided.ref.value) {
|
||||
state.frontFace(gl.CW);
|
||||
state.cullFace(gl.FRONT);
|
||||
} else {
|
||||
state.frontFace(gl.CCW);
|
||||
state.cullFace(gl.BACK);
|
||||
}
|
||||
} else {
|
||||
// webgl default
|
||||
state.enable(gl.CULL_FACE);
|
||||
}
|
||||
} else {
|
||||
// webgl default
|
||||
state.disable(gl.CULL_FACE);
|
||||
}
|
||||
|
||||
if (r.values.dFlipSided) {
|
||||
if (r.values.dFlipSided.ref.value) {
|
||||
state.frontFace(gl.CW);
|
||||
state.cullFace(gl.FRONT);
|
||||
} else {
|
||||
state.frontFace(gl.CCW);
|
||||
state.cullFace(gl.BACK);
|
||||
}
|
||||
|
||||
if (variant === 'color') {
|
||||
state.depthMask(r.state.writeDepth);
|
||||
}
|
||||
|
||||
r.render(variant);
|
||||
} else {
|
||||
// webgl default
|
||||
state.frontFace(gl.CCW);
|
||||
state.cullFace(gl.BACK);
|
||||
}
|
||||
|
||||
if (variant === 'color') {
|
||||
state.depthMask(r.state.writeDepth);
|
||||
}
|
||||
|
||||
r.render(variant);
|
||||
};
|
||||
|
||||
const render = (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean) => {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
export default `
|
||||
float depth = length(vViewPosition);
|
||||
float fogFactor = smoothstep(uFogNear, uFogFar, depth);
|
||||
float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
|
||||
if (uTransparentBackground == 0) {
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
|
||||
if (gl_FragColor.a < 1.0)
|
||||
gl_FragColor.a = fogAlpha;
|
||||
} else {
|
||||
float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
|
||||
gl_FragColor.a = fogAlpha;
|
||||
|
||||
@@ -34,7 +34,7 @@ export default `
|
||||
if (ta < 0.99 && (ta < 0.01 || ta < at)) discard;
|
||||
#endif
|
||||
#elif defined(dRenderVariant_pick)
|
||||
vec4 material = uPickable == 1 ? vColor : vec4(0.0, 0.0, 0.0, 1.0); // set to empty picking id
|
||||
vec4 material = vColor;
|
||||
#elif defined(dRenderVariant_depth)
|
||||
#ifdef enabledFragDepth
|
||||
vec4 material = packDepthToRGBA(gl_FragDepthEXT);
|
||||
|
||||
@@ -21,7 +21,6 @@ uniform vec3 uFogColor;
|
||||
|
||||
uniform float uAlpha;
|
||||
uniform float uPickingAlphaThreshold;
|
||||
uniform int uPickable;
|
||||
uniform int uTransparentBackground;
|
||||
|
||||
uniform float uInteriorDarkening;
|
||||
|
||||
@@ -28,7 +28,6 @@ uniform sampler2D tMarker;
|
||||
|
||||
uniform float uAlpha;
|
||||
uniform float uPickingAlphaThreshold;
|
||||
uniform int uPickable;
|
||||
|
||||
#if defined(dGridTexType_2d)
|
||||
precision highp sampler2D;
|
||||
@@ -117,8 +116,6 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
|
||||
#if defined(dRenderVariant_pick)
|
||||
if (uAlpha < uPickingAlphaThreshold)
|
||||
discard; // ignore so the element below can be picked
|
||||
if (uPickable == 0)
|
||||
return vec4(0.0, 0.0, 0.0, 1.0); // set to empty picking id
|
||||
#endif
|
||||
|
||||
#if defined(dRenderVariant_pickObject)
|
||||
|
||||
@@ -35,20 +35,20 @@ varying float vInstance;
|
||||
const float C = 0.333;
|
||||
#endif
|
||||
|
||||
float cubicFilter( float x ){
|
||||
float cubicFilter(float x){
|
||||
float f = x;
|
||||
if( f < 0.0 ){
|
||||
if (f < 0.0) {
|
||||
f = -f;
|
||||
}
|
||||
if( f < 1.0 ){
|
||||
return ( ( 12.0 - 9.0 * B - 6.0 * C ) * ( f * f * f ) +
|
||||
( -18.0 + 12.0 * B + 6.0 *C ) * ( f * f ) +
|
||||
( 6.0 - 2.0 * B ) ) / 6.0;
|
||||
}else if( f >= 1.0 && f < 2.0 ){
|
||||
return ( ( -B - 6.0 * C ) * ( f * f * f )
|
||||
+ ( 6.0 * B + 30.0 * C ) * ( f *f ) +
|
||||
( - ( 12.0 * B ) - 48.0 * C ) * f +
|
||||
8.0 * B + 24.0 * C ) / 6.0;
|
||||
if (f < 1.0) {
|
||||
return ((12.0 - 9.0 * B - 6.0 * C) * (f * f * f) +
|
||||
(-18.0 + 12.0 * B + 6.0 * C) * (f * f) +
|
||||
(6.0 - 2.0 * B)) / 6.0;
|
||||
}else if (f >= 1.0 && f < 2.0){
|
||||
return ((-B - 6.0 * C) * ( f * f * f)
|
||||
+ (6.0 * B + 30.0 * C) * (f * f) +
|
||||
(-(12.0 * B) - 48.0 * C) * f +
|
||||
8.0 * B + 24.0 * C) / 6.0;
|
||||
}else{
|
||||
return 0.0;
|
||||
}
|
||||
@@ -92,25 +92,23 @@ void main() {
|
||||
#else
|
||||
vec4 imageData = texture2D(tImageTex, vUv);
|
||||
#endif
|
||||
imageData.a = clamp(imageData.a, 0.0, 1.0);
|
||||
if (imageData.a > 0.9) imageData.a = 1.0;
|
||||
|
||||
#if defined(dRenderVariant_pick)
|
||||
if (imageData.a < 0.3)
|
||||
discard;
|
||||
|
||||
if (uPickable == 1) {
|
||||
#if defined(dRenderVariant_pickObject)
|
||||
gl_FragColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);
|
||||
#elif defined(dRenderVariant_pickInstance)
|
||||
gl_FragColor = vec4(encodeFloatRGB(vInstance), 1.0);
|
||||
#elif defined(dRenderVariant_pickGroup)
|
||||
float group = texture2D(tGroupTex, vUv).r;
|
||||
gl_FragColor = vec4(encodeFloatRGB(group), 1.0);
|
||||
#endif
|
||||
} else {
|
||||
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); // set to empty picking id
|
||||
}
|
||||
#if defined(dRenderVariant_pickObject)
|
||||
gl_FragColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);
|
||||
#elif defined(dRenderVariant_pickInstance)
|
||||
gl_FragColor = vec4(encodeFloatRGB(vInstance), 1.0);
|
||||
#elif defined(dRenderVariant_pickGroup)
|
||||
float group = texture2D(tGroupTex, vUv).r;
|
||||
gl_FragColor = vec4(encodeFloatRGB(group), 1.0);
|
||||
#endif
|
||||
#elif defined(dRenderVariant_depth)
|
||||
if (imageData.a < 0.01)
|
||||
if (imageData.a < 0.05)
|
||||
discard;
|
||||
|
||||
#ifdef enabledFragDepth
|
||||
@@ -119,7 +117,7 @@ void main() {
|
||||
gl_FragColor = packDepthToRGBA(gl_FragCoord.z);
|
||||
#endif
|
||||
#elif defined(dRenderVariant_color)
|
||||
if (imageData.a < 0.01)
|
||||
if (imageData.a < 0.05)
|
||||
discard;
|
||||
|
||||
gl_FragColor = imageData;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
import Mol2 from '../mol2/parser';
|
||||
import { parseMol2 } from '../mol2/parser';
|
||||
|
||||
const Mol2String = `@<TRIPOS>MOLECULE
|
||||
5816
|
||||
@@ -246,7 +246,7 @@ GASTEIGER
|
||||
|
||||
describe('mol2 reader', () => {
|
||||
it('basic', async () => {
|
||||
const parsed = await Mol2(Mol2String).run();
|
||||
const parsed = await parseMol2(Mol2String, '').run();
|
||||
if (parsed.isError) {
|
||||
throw new Error(parsed.message);
|
||||
}
|
||||
@@ -297,7 +297,7 @@ describe('mol2 reader', () => {
|
||||
});
|
||||
|
||||
it('multiblocks', async () => {
|
||||
const parsed = await Mol2(Mol2StringMultiBlocks).run();
|
||||
const parsed = await parseMol2(Mol2StringMultiBlocks, '').run();
|
||||
if (parsed.isError) {
|
||||
throw new Error(parsed.message);
|
||||
}
|
||||
@@ -348,7 +348,7 @@ describe('mol2 reader', () => {
|
||||
});
|
||||
|
||||
it('minimal', async () => {
|
||||
const parsed = await Mol2(Mol2StringMinimal).run();
|
||||
const parsed = await parseMol2(Mol2StringMinimal, '').run();
|
||||
if (parsed.isError) {
|
||||
throw new Error(parsed.message);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ const reWhitespace = /\s+/g;
|
||||
function handleMolecule(state: State) {
|
||||
const { tokenizer, molecule } = state;
|
||||
|
||||
while (getTokenString(tokenizer) !== '@<TRIPOS>MOLECULE') {
|
||||
while (getTokenString(tokenizer) !== '@<TRIPOS>MOLECULE' && tokenizer.position < tokenizer.data.length) {
|
||||
markLine(tokenizer);
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ async function handleAtoms(state: State): Promise<Schema.Mol2Atoms> {
|
||||
let hasStatus_bit = false;
|
||||
|
||||
// skip empty lines and '@<TRIPOS>ATOM'
|
||||
while (getTokenString(tokenizer) !== '@<TRIPOS>ATOM') {
|
||||
while (getTokenString(tokenizer) !== '@<TRIPOS>ATOM' && tokenizer.position < tokenizer.data.length) {
|
||||
markLine(tokenizer);
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ async function handleBonds(state: State): Promise<Schema.Mol2Bonds> {
|
||||
const { tokenizer, molecule } = state;
|
||||
let hasStatus_bit = false;
|
||||
|
||||
while (getTokenString(tokenizer) !== '@<TRIPOS>BOND') {
|
||||
while (getTokenString(tokenizer) !== '@<TRIPOS>BOND' && tokenizer.position < tokenizer.data.length) {
|
||||
markLine(tokenizer);
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ async function handleBonds(state: State): Promise<Schema.Mol2Bonds> {
|
||||
return ret;
|
||||
}
|
||||
|
||||
async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<Schema.Mol2File>> {
|
||||
async function parseInternal(ctx: RuntimeContext, data: string, name: string): Promise<Result<Schema.Mol2File>> {
|
||||
const tokenizer = Tokenizer(data);
|
||||
|
||||
ctx.update({ message: 'Parsing...', current: 0, max: data.length });
|
||||
@@ -335,16 +335,15 @@ async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<
|
||||
const atoms = await handleAtoms(state);
|
||||
const bonds = await handleBonds(state);
|
||||
structures.push({ molecule: state.molecule, atoms, bonds });
|
||||
skipWhitespace(tokenizer);
|
||||
}
|
||||
|
||||
const result: Schema.Mol2File = { structures };
|
||||
const result: Schema.Mol2File = { name, structures };
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
export function parse(data: string) {
|
||||
export function parseMol2(data: string, name: string) {
|
||||
return Task.create<Result<Schema.Mol2File>>('Parse MOL2', async ctx => {
|
||||
return await parseInternal(data, ctx);
|
||||
return await parseInternal(ctx, data, name);
|
||||
});
|
||||
}
|
||||
|
||||
export default parse;
|
||||
}
|
||||
1
src/mol-io/reader/mol2/schema.d.ts
vendored
1
src/mol-io/reader/mol2/schema.d.ts
vendored
@@ -63,5 +63,6 @@ export interface Mol2Structure {
|
||||
}
|
||||
|
||||
export interface Mol2File {
|
||||
name: string
|
||||
structures: Mol2Structure[]
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Model } from '../../mol-model/structure/model';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ModelFormat } from './format';
|
||||
import { ModelFormat } from '../format';
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
import { File3DG } from '../../mol-io/reader/3dg/parser';
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ElementSymbol } from '../../../mol-model/structure/model/types';
|
||||
import { Entities } from '../../../mol-model/structure/model/properties/common';
|
||||
import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived';
|
||||
import { AtomSite } from './schema';
|
||||
import { ModelFormat } from '../format';
|
||||
import { ModelFormat } from '../../format';
|
||||
import { SymmetryOperator } from '../../../mol-math/geometry';
|
||||
import { MmcifFormat } from '../mmcif';
|
||||
import { AtomSiteOperatorMappingSchema } from '../../../mol-model/structure/export/categories/atom_site_operator_mapping';
|
||||
|
||||
@@ -15,7 +15,7 @@ import { getAtomicHierarchyAndConformation } from './atomic';
|
||||
import { getCoarse, EmptyCoarse, CoarseData } from './coarse';
|
||||
import { getSequence } from './sequence';
|
||||
import { sortAtomSite } from './sort';
|
||||
import { ModelFormat } from '../format';
|
||||
import { ModelFormat } from '../../format';
|
||||
import { getAtomicRanges } from '../../../mol-model/structure/model/properties/utils/atomic-ranges';
|
||||
import { AtomSite, BasicData } from './schema';
|
||||
import { getProperties } from './properties';
|
||||
|
||||
@@ -12,7 +12,7 @@ import { createModels } from './basic/parser';
|
||||
import { BasicSchema, createBasic } from './basic/schema';
|
||||
import { ComponentBuilder } from './common/component';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
import { ModelFormat } from './format';
|
||||
import { ModelFormat } from '../format';
|
||||
import { CifCore_Database } from '../../mol-io/reader/cif/schema/cif-core';
|
||||
import { CifFrame, CIF } from '../../mol-io/reader/cif';
|
||||
import { Spacegroup, SpacegroupCell } from '../../mol-math/geometry';
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { CustomPropertyDescriptor, Model } from '../../../mol-model/structure';
|
||||
import { ModelFormat } from '../format';
|
||||
import { ModelFormat } from '../../format';
|
||||
|
||||
class FormatRegistry<T> {
|
||||
private map = new Map<ModelFormat['kind'], (model: Model) => T | undefined>()
|
||||
|
||||
@@ -12,7 +12,7 @@ import { createModels } from './basic/parser';
|
||||
import { BasicSchema, createBasic } from './basic/schema';
|
||||
import { ComponentBuilder } from './common/component';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
import { ModelFormat } from './format';
|
||||
import { ModelFormat } from '../format';
|
||||
import { CubeFile } from '../../mol-io/reader/cube/parser';
|
||||
|
||||
async function getModels(cube: CubeFile, ctx: RuntimeContext): Promise<Model[]> {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Model } from '../../mol-model/structure/model';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ModelFormat } from './format';
|
||||
import { ModelFormat } from '../format';
|
||||
import { GroFile, GroAtoms } from '../../mol-io/reader/gro/schema';
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { guessElementSymbolString } from './util';
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { Model } from '../../mol-model/structure/model/model';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ModelFormat } from './format';
|
||||
import { ModelFormat } from '../format';
|
||||
import { CifFrame, CIF } from '../../mol-io/reader/cif';
|
||||
import { mmCIF_Database } from '../../mol-io/reader/cif/schema/mmcif';
|
||||
import { createModels } from './basic/parser';
|
||||
|
||||
@@ -14,7 +14,7 @@ import { createModels } from './basic/parser';
|
||||
import { BasicSchema, createBasic } from './basic/schema';
|
||||
import { ComponentBuilder } from './common/component';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
import { ModelFormat } from './format';
|
||||
import { ModelFormat } from '../format';
|
||||
import { IndexPairBonds } from './property/bonds/index-pair';
|
||||
|
||||
async function getModels(mol: MolFile, ctx: RuntimeContext): Promise<Model[]> {
|
||||
|
||||
98
src/mol-model-formats/structure/mol2.ts
Normal file
98
src/mol-model-formats/structure/mol2.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { Model } from '../../mol-model/structure/model';
|
||||
import { MoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { RuntimeContext, Task } from '../../mol-task';
|
||||
import { createModels } from './basic/parser';
|
||||
import { BasicSchema, createBasic } from './basic/schema';
|
||||
import { ComponentBuilder } from './common/component';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
import { ModelFormat } from '../format';
|
||||
import { IndexPairBonds } from './property/bonds/index-pair';
|
||||
import { Mol2File } from '../../mol-io/reader/mol2/schema';
|
||||
|
||||
async function getModels(mol2: Mol2File, ctx: RuntimeContext): Promise<Model[]> {
|
||||
const models: Model[] = [];
|
||||
|
||||
for (let i = 0, il = mol2.structures.length; i < il; ++i) {
|
||||
const { atoms, bonds } = mol2.structures[i];
|
||||
|
||||
const A = Column.ofConst('A', atoms.count, Column.Schema.str);
|
||||
|
||||
const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
|
||||
auth_asym_id: A,
|
||||
auth_atom_id: Column.asArrayColumn(atoms.atom_type),
|
||||
auth_comp_id: atoms.subst_name,
|
||||
auth_seq_id: atoms.subst_id,
|
||||
Cartn_x: Column.asArrayColumn(atoms.x, Float32Array),
|
||||
Cartn_y: Column.asArrayColumn(atoms.y, Float32Array),
|
||||
Cartn_z: Column.asArrayColumn(atoms.z, Float32Array),
|
||||
id: Column.asArrayColumn(atoms.atom_id),
|
||||
|
||||
label_asym_id: A,
|
||||
label_atom_id: Column.asArrayColumn(atoms.atom_type),
|
||||
label_comp_id: atoms.subst_name,
|
||||
label_seq_id: atoms.subst_id,
|
||||
label_entity_id: Column.ofConst('1', atoms.count, Column.Schema.str),
|
||||
|
||||
occupancy: Column.ofConst(1, atoms.count, Column.Schema.float),
|
||||
type_symbol: Column.asArrayColumn(atoms.atom_name),
|
||||
|
||||
pdbx_PDB_model_num: Column.ofConst(i, atoms.count, Column.Schema.int),
|
||||
}, atoms.count);
|
||||
|
||||
const entityBuilder = new EntityBuilder();
|
||||
entityBuilder.setNames([['MOL', 'Unknown Entity']]);
|
||||
entityBuilder.getEntityId('MOL', MoleculeType.Unknown, 'A');
|
||||
|
||||
const componentBuilder = new ComponentBuilder(atoms.subst_id, atoms.atom_name);
|
||||
for (let i = 0, il = atoms.subst_name.rowCount; i < il; ++i) {
|
||||
componentBuilder.add(atoms.subst_name.value(i), i);
|
||||
}
|
||||
|
||||
const basics = createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
chem_comp: componentBuilder.getChemCompTable(),
|
||||
atom_site
|
||||
});
|
||||
|
||||
const _models = await createModels(basics, Mol2Format.create(mol2), ctx);
|
||||
|
||||
if (_models.length > 0) {
|
||||
const indexA = Column.ofIntArray(Column.mapToArray(bonds.origin_atom_id, x => x - 1, Int32Array));
|
||||
const indexB = Column.ofIntArray(Column.mapToArray(bonds.target_atom_id, x => x - 1, Int32Array));
|
||||
const order = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => x === 'ar' ? 1 : parseInt(x), Int8Array));
|
||||
const pairBonds = IndexPairBonds.fromData({ pairs: { indexA, indexB, order }, count: bonds.count });
|
||||
IndexPairBonds.Provider.set(_models[0], pairBonds);
|
||||
|
||||
models.push(_models[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return models;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export { Mol2Format };
|
||||
|
||||
type Mol2Format = ModelFormat<Mol2File>
|
||||
|
||||
namespace Mol2Format {
|
||||
export function is(x: ModelFormat): x is Mol2Format {
|
||||
return x.kind === 'mol2';
|
||||
}
|
||||
|
||||
export function create(mol2: Mol2File): Mol2Format {
|
||||
return { kind: 'mol2', name: mol2.name, data: mol2 };
|
||||
}
|
||||
}
|
||||
|
||||
export function trajectoryFromMol2(mol2: Mol2File): Task<Model.Trajectory> {
|
||||
return Task.create('Parse MOL2', ctx => getModels(mol2, ctx));
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import { guessElementSymbolString } from './util';
|
||||
import { MoleculeType, getMoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { getChainId } from './common/util';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ModelFormat } from './format';
|
||||
import { ModelFormat } from '../format';
|
||||
import { Topology } from '../../mol-model/structure/topology/topology';
|
||||
import { createBasic, BasicSchema } from './basic/schema';
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { degToRad } from '../../mol-math/misc';
|
||||
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';
|
||||
|
||||
/** When available (e.g. in MRC files) use ORIGIN records instead of N[CRS]START */
|
||||
export function getCcp4Origin(header: Ccp4Header): Vec3 {
|
||||
@@ -75,7 +76,24 @@ export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, of
|
||||
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)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export { Ccp4Format };
|
||||
|
||||
type Ccp4Format = ModelFormat<Ccp4File>
|
||||
|
||||
namespace Ccp4Format {
|
||||
export function is(x: ModelFormat): x is Ccp4Format {
|
||||
return x.kind === 'ccp4';
|
||||
}
|
||||
|
||||
export function create(ccp4: Ccp4File): Ccp4Format {
|
||||
return { kind: 'ccp4', name: ccp4.name, data: ccp4 };
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { Mat4, Tensor } from '../../mol-math/linear-algebra';
|
||||
import { VolumeData } from '../../mol-model/volume/data';
|
||||
import { Task } from '../../mol-task';
|
||||
import { arrayMax, arrayMean, arrayMin, arrayRms } from '../../mol-util/array';
|
||||
import { ModelFormat } from '../format';
|
||||
|
||||
export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number, label?: string }): Task<VolumeData> {
|
||||
return Task.create<VolumeData>('Create Volume Data', async () => {
|
||||
@@ -51,7 +52,24 @@ export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number,
|
||||
max: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: arrayRms(values)
|
||||
}
|
||||
},
|
||||
sourceData: CubeFormat.create(source)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export { CubeFormat };
|
||||
|
||||
type CubeFormat = ModelFormat<CubeFile>
|
||||
|
||||
namespace CubeFormat {
|
||||
export function is(x: ModelFormat): x is CubeFormat {
|
||||
return x.kind === 'cube';
|
||||
}
|
||||
|
||||
export function create(cube: CubeFile): CubeFormat {
|
||||
return { kind: 'cube', name: cube.name, data: cube };
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,9 @@ import { VolumeData } from '../../mol-model/volume/data';
|
||||
import { Task } from '../../mol-task';
|
||||
import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
|
||||
import { Tensor, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { ModelFormat } from '../format';
|
||||
|
||||
function volumeFromDensityServerData(source: DensityServer_Data_Database): Task<VolumeData> {
|
||||
export function volumeFromDensityServerData(source: DensityServer_Data_Database): Task<VolumeData> {
|
||||
return Task.create<VolumeData>('Create Volume Data', async ctx => {
|
||||
const { volume_data_3d_info: info, volume_data_3d: values } = source;
|
||||
const cell = SpacegroupCell.create(
|
||||
@@ -41,9 +42,24 @@ function volumeFromDensityServerData(source: DensityServer_Data_Database): Task<
|
||||
max: info.max_sampled.value(0),
|
||||
mean: info.mean_sampled.value(0),
|
||||
sigma: info.sigma_sampled.value(0)
|
||||
}
|
||||
},
|
||||
sourceData: DscifFormat.create(source)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export { volumeFromDensityServerData };
|
||||
//
|
||||
|
||||
export { DscifFormat };
|
||||
|
||||
type DscifFormat = ModelFormat<DensityServer_Data_Database>
|
||||
|
||||
namespace DscifFormat {
|
||||
export function is(x: ModelFormat): x is DscifFormat {
|
||||
return x.kind === 'dscif';
|
||||
}
|
||||
|
||||
export function create(dscif: DensityServer_Data_Database): DscifFormat {
|
||||
return { kind: 'dscif', name: dscif._name, data: dscif };
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,9 @@ import { Tensor, Vec3 } from '../../mol-math/linear-algebra';
|
||||
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';
|
||||
|
||||
function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3, label?: string }): Task<VolumeData> {
|
||||
export function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3, label?: string }): Task<VolumeData> {
|
||||
return Task.create<VolumeData>('Create Volume Data', async ctx => {
|
||||
const { header, values } = source;
|
||||
const size = Vec3.create(header.xlen, header.ylen, header.zlen);
|
||||
@@ -40,9 +41,24 @@ function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3, label?: s
|
||||
max: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: header.sigma !== undefined ? header.sigma : arrayRms(values)
|
||||
}
|
||||
},
|
||||
sourceData: Dsn6Format.create(source)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export { volumeFromDsn6 };
|
||||
//
|
||||
|
||||
export { Dsn6Format };
|
||||
|
||||
type Dsn6Format = ModelFormat<Dsn6File>
|
||||
|
||||
namespace Dsn6Format {
|
||||
export function is(x: ModelFormat): x is Dsn6Format {
|
||||
return x.kind === 'dsn6';
|
||||
}
|
||||
|
||||
export function create(dsn6: Dsn6File): Dsn6Format {
|
||||
return { kind: 'dsn6', name: dsn6.name, data: dsn6 };
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { Mat4, Tensor } from '../../mol-math/linear-algebra';
|
||||
import { VolumeData } from '../../mol-model/volume/data';
|
||||
import { Task } from '../../mol-task';
|
||||
import { arrayMax, arrayMean, arrayMin, arrayRms } from '../../mol-util/array';
|
||||
import { ModelFormat } from '../format';
|
||||
|
||||
export function volumeFromDx(source: DxFile, params?: { label?: string }): Task<VolumeData> {
|
||||
return Task.create<VolumeData>('Create Volume Data', async () => {
|
||||
@@ -28,7 +29,24 @@ export function volumeFromDx(source: DxFile, params?: { label?: string }): Task<
|
||||
max: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: arrayRms(values)
|
||||
}
|
||||
},
|
||||
sourceData: DxFormat.create(source)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export { DxFormat };
|
||||
|
||||
type DxFormat = ModelFormat<DxFile>
|
||||
|
||||
namespace DxFormat {
|
||||
export function is(x: ModelFormat): x is DxFormat {
|
||||
return x.kind === 'dx';
|
||||
}
|
||||
|
||||
export function create(dx: DxFile): DxFormat {
|
||||
return { kind: 'dx', name: dx.name, data: dx };
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import { CoarseHierarchy, CoarseConformation } from './properties/coarse';
|
||||
import { Entities, ChemicalComponentMap, MissingResidues, StructAsymMap } from './properties/common';
|
||||
import { CustomProperties } from '../common/custom-property';
|
||||
import { SaccharideComponentMap } from '../structure/carbohydrates/constants';
|
||||
import { ModelFormat } from '../../../mol-model-formats/structure/format';
|
||||
import { ModelFormat } from '../../../mol-model-formats/format';
|
||||
import { calcModelCenter } from './util';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { Mutable } from '../../../mol-util/type-helpers';
|
||||
|
||||
@@ -14,6 +14,10 @@ export const enum Elements {
|
||||
H = 'H', D = 'D', T = 'T', HE = 'HE', LI = 'LI', BE = 'BE', B = 'B', C = 'C', N = 'N', O = 'O', F = 'F', NE = 'NE', NA = 'NA', MG = 'MG', AL = 'AL', SI = 'SI', P = 'P', S = 'S', CL = 'CL', AR = 'AR', K = 'K', CA = 'CA', SC = 'SC', TI = 'TI', V = 'V', CR = 'CR', MN = 'MN', FE = 'FE', CO = 'CO', NI = 'NI', CU = 'CU', ZN = 'ZN', GA = 'GA', GE = 'GE', AS = 'AS', SE = 'SE', BR = 'BR', KR = 'KR', RB = 'RB', SR = 'SR', Y = 'Y', ZR = 'ZR', NB = 'NB', MO = 'MO', TC = 'TC', RU = 'RU', RH = 'RH', PD = 'PD', AG = 'AG', CD = 'CD', IN = 'IN', SN = 'SN', SB = 'SB', TE = 'TE', I = 'I', XE = 'XE', CS = 'CS', BA = 'BA', LA = 'LA', CE = 'CE', PR = 'PR', ND = 'ND', PM = 'PM', SM = 'SM', EU = 'EU', GD = 'GD', TB = 'TB', DY = 'DY', HO = 'HO', ER = 'ER', TM = 'TM', YB = 'YB', LU = 'LU', HF = 'HF', TA = 'TA', W = 'W', RE = 'RE', OS = 'OS', IR = 'IR', PT = 'PT', AU = 'AU', HG = 'HG', TL = 'TL', PB = 'PB', BI = 'BI', PO = 'PO', AT = 'AT', RN = 'RN', FR = 'FR', RA = 'RA', AC = 'AC', TH = 'TH', PA = 'PA', U = 'U', NP = 'NP', PU = 'PU', AM = 'AM', CM = 'CM', BK = 'BK', CF = 'CF', ES = 'ES', FM = 'FM', MD = 'MD', NO = 'NO', LR = 'LR', RF = 'RF', DB = 'DB', SG = 'SG', BH = 'BH', HS = 'HS', MT = 'MT', DS = 'DS', RG = 'RG', CN = 'CN', NH = 'NH', FL = 'FL', MC = 'MC', LV = 'LV', TS = 'TS', OG = 'OG'
|
||||
}
|
||||
|
||||
export const ElementNames: { [k: string]: string } = {
|
||||
H: 'Hydrogen', HE: 'Helium', LI: 'Lithium', BE: 'Beryllium', B: 'Boron', C: 'Carbon', N: 'Nitrogen', O: 'Oxygen', F: 'Fluorine', NE: 'Neon', NA: 'Sodium', MG: 'Magnesium', AL: 'Aluminum', SI: 'Silicon', P: 'Phosphorus', S: 'Sulfur', CL: 'Chlorine', AR: 'Argon', K: 'Potassium', CA: 'Calcium', SC: 'Scandium', TI: 'Titanium', V: 'Vanadium', CR: 'Chromium', MN: 'Manganese', FE: 'Iron', CO: 'Cobalt', NI: 'Nickel', CU: 'Copper', ZN: 'Zinc', GA: 'Gallium', GE: 'Germanium', AS: 'Arsenic', SE: 'Selenium', BR: 'Bromine', KR: 'Krypton', RB: 'Rubidium', SR: 'Strontium', Y: 'Yttrium', ZR: 'Zirconium', NB: 'Niobium', MO: 'Molybdenum', TC: 'Technetium', RU: 'Ruthenium', RH: 'Rhodium', PD: 'Palladium', AG: 'Silver', CD: 'Cadmium', IN: 'Indium', SN: 'Tin', SB: 'Antimony', TE: 'Tellurium', I: 'Iodine', XE: 'Xenon', CS: 'Cesium', BA: 'Barium', LA: 'Lanthanum', CE: 'Cerium', PR: 'Praseodymium', ND: 'Neodymium', PM: 'Promethium', SM: 'Samarium', EU: 'Europium', GD: 'Gadolinium', TB: 'Terbium', DY: 'Dysprosium', HO: 'Holmium', ER: 'Erbium', TM: 'Thulium', YB: 'Ytterbium', LU: 'Lutetium', HF: 'Hafnium', TA: 'Tantalum', W: 'Wolfram', RE: 'Rhenium', OS: 'Osmium', IR: 'Iridium', PT: 'Platinum', AU: 'Gold', HG: 'Mercury', TL: 'Thallium', PB: 'Lead', BI: 'Bismuth', PO: 'Polonium', AT: 'Astatine', RN: 'Radon', FR: 'Francium', RA: 'Radium', AC: 'Actinium', TH: 'Thorium', PA: 'Protactinium', U: 'Uranium', NP: 'Neptunium', PU: 'Plutonium', AM: 'Americium', CM: 'Curium', BK: 'Berkelium', CF: 'Californium', ES: 'Einsteinium', FM: 'Fermium', MD: 'Mendelevium', NO: 'Nobelium', LR: 'Lawrencium', RF: 'Rutherfordium', DB: 'Dubnium', SG: 'Seaborgium', BH: 'Bohrium', HS: 'Hassium', MT: 'Meitnerium', DS: 'Darmstadtium', RG: 'Roentgenium', CN: 'Copernicium', NH: 'Nihonium', FL: 'Flerovium', MC: 'Moscovium', LV: 'Livermorium', TS: 'Tennessine', OG: 'Oganesson'
|
||||
};
|
||||
|
||||
export const AlkaliMetals = new Set<ElementSymbol>(['LI', 'NA', 'K', 'RB', 'CS', 'FR'] as ElementSymbol[]);
|
||||
export function isAlkaliMetal(element: ElementSymbol) { return AlkaliMetals.has(element); }
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import { CustomProperties } from '../common/custom-property';
|
||||
import { AtomicHierarchy } from '../model/properties/atomic';
|
||||
import { StructureSelection } from '../query/selection';
|
||||
import { getBoundary } from '../../../mol-math/geometry/boundary';
|
||||
import { ElementSymbol } from '../model/types';
|
||||
|
||||
class Structure {
|
||||
/** Maps unit.id to unit */
|
||||
@@ -50,6 +51,7 @@ class Structure {
|
||||
masterModel?: Model,
|
||||
representativeModel?: Model,
|
||||
uniqueResidueNames?: Set<string>,
|
||||
uniqueElementSymbols?: Set<ElementSymbol>,
|
||||
entityIndices?: ReadonlyArray<EntityIndex>,
|
||||
uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
|
||||
serialMapping?: SerialMapping,
|
||||
@@ -265,6 +267,11 @@ class Structure {
|
||||
|| (this._props.uniqueResidueNames = getUniqueResidueNames(this));
|
||||
}
|
||||
|
||||
get uniqueElementSymbols() {
|
||||
return this._props.uniqueElementSymbols
|
||||
|| (this._props.uniqueElementSymbols = getUniqueElementSymbols(this));
|
||||
}
|
||||
|
||||
get entityIndices() {
|
||||
return this._props.entityIndices
|
||||
|| (this._props.entityIndices = getEntityIndices(this));
|
||||
@@ -403,7 +410,8 @@ function getUniqueResidueNames(s: Structure) {
|
||||
const prop = StructureProperties.residue.label_comp_id;
|
||||
const names = new Set<string>();
|
||||
const loc = StructureElement.Location.create(s);
|
||||
for (const unit of s.units) {
|
||||
for (const unitGroup of s.unitSymmetryGroups) {
|
||||
const unit = unitGroup.units[0];
|
||||
// TODO: support coarse unit?
|
||||
if (!Unit.isAtomic(unit)) continue;
|
||||
const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
|
||||
@@ -417,6 +425,22 @@ function getUniqueResidueNames(s: Structure) {
|
||||
return names;
|
||||
}
|
||||
|
||||
function getUniqueElementSymbols(s: Structure) {
|
||||
const prop = StructureProperties.atom.type_symbol;
|
||||
const symbols = new Set<ElementSymbol>();
|
||||
const loc = StructureElement.Location.create(s);
|
||||
for (const unitGroup of s.unitSymmetryGroups) {
|
||||
const unit = unitGroup.units[0];
|
||||
if (!Unit.isAtomic(unit)) continue;
|
||||
loc.unit = unit;
|
||||
for (let i = 0, il = unit.elements.length; i < il; ++i) {
|
||||
loc.element = unit.elements[i];
|
||||
symbols.add(prop(loc));
|
||||
}
|
||||
}
|
||||
return symbols;
|
||||
}
|
||||
|
||||
function getEntityIndices(structure: Structure): ReadonlyArray<EntityIndex> {
|
||||
const { units } = structure;
|
||||
const l = StructureElement.Location.create(structure);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { Column } from '../../../mol-data/db';
|
||||
import { BasicData } from '../../../mol-model-formats/structure/basic/schema';
|
||||
import { ModelFormat } from '../../../mol-model-formats/structure/format';
|
||||
import { ModelFormat } from '../../../mol-model-formats/format';
|
||||
|
||||
export { Topology };
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
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 {
|
||||
@@ -20,6 +21,7 @@ interface VolumeDataBase {
|
||||
mean: number,
|
||||
sigma: number
|
||||
}>
|
||||
readonly sourceData: ModelFormat,
|
||||
}
|
||||
|
||||
interface VolumeData extends VolumeDataBase {
|
||||
@@ -30,7 +32,8 @@ 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 }
|
||||
dataStats: { min: 0, max: 0, mean: 0, sigma: 0 },
|
||||
sourceData: { kind: '', data: '', name: '' }
|
||||
};
|
||||
|
||||
const _scale = Mat4.zero(), _translate = Mat4.zero();
|
||||
|
||||
@@ -7,12 +7,18 @@
|
||||
import { VolumeData, VolumeIsoValue } from './data';
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
|
||||
import { CubeFormat } from '../../mol-model-formats/volume/cube';
|
||||
|
||||
export namespace Volume {
|
||||
export type CellIndex = { readonly '@type': 'cell-index' } & number
|
||||
|
||||
export function isOrbitals(volume: VolumeData) {
|
||||
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 function isLoci(x: any): x is Loci { return !!x && x.kind === 'volume-loci'; }
|
||||
@@ -86,7 +92,8 @@ export namespace Volume {
|
||||
boundaryHelper.radiusPosition(tmpBoundaryPos);
|
||||
}
|
||||
|
||||
return boundaryHelper.getSphere(boundingSphere);
|
||||
const bs = boundaryHelper.getSphere(boundingSphere);
|
||||
return Sphere3D.expand(bs, bs, Mat4.getMaxScaleOnAxis(transform) * 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { StateTransforms } from '../transforms';
|
||||
import { Download } from '../transforms/data';
|
||||
import { CustomModelProperties, CustomStructureProperties, TrajectoryFromModelAndCoordinates } from '../transforms/model';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
|
||||
const DownloadModelRepresentationOptions = (plugin: PluginContext) => PD.Group({
|
||||
type: RootStructureDefinition.getParams(void 0, 'auto').type,
|
||||
@@ -26,6 +27,16 @@ const DownloadModelRepresentationOptions = (plugin: PluginContext) => PD.Group({
|
||||
asTrajectory: PD.Optional(PD.Boolean(false, { description: 'Load all entries into a single trajectory.' }))
|
||||
}, { isExpanded: false });
|
||||
|
||||
export const PdbDownloadProvider = {
|
||||
'rcsb': PD.Group({
|
||||
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][]),
|
||||
}, { label: 'PDBe', isFlat: true }),
|
||||
};
|
||||
export type PdbDownloadProvider = keyof typeof PdbDownloadProvider;
|
||||
|
||||
export { DownloadStructure };
|
||||
type DownloadStructure = typeof DownloadStructure
|
||||
const DownloadStructure = StateAction.build({
|
||||
@@ -33,19 +44,13 @@ const DownloadStructure = StateAction.build({
|
||||
display: { name: 'Download Structure', description: 'Load a structure from the provided source and create its representation.' },
|
||||
params: (_, plugin: PluginContext) => {
|
||||
const options = DownloadModelRepresentationOptions(plugin);
|
||||
const defaultPdbProvider = plugin.config.get(PluginConfig.Download.DefaultPdbProvider) || 'pdbe';
|
||||
return {
|
||||
source: PD.MappedStatic('pdb', {
|
||||
'pdb': PD.Group({
|
||||
provider: PD.Group({
|
||||
id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma/space separated PDB ids.' }),
|
||||
server: PD.MappedStatic('pdbe', {
|
||||
'rcsb': PD.Group({
|
||||
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][]),
|
||||
}, { label: 'PDBe', isFlat: true }),
|
||||
}),
|
||||
server: PD.MappedStatic(defaultPdbProvider, PdbDownloadProvider),
|
||||
}, { pivot: 'id' }),
|
||||
options
|
||||
}, { isFlat: true, label: 'PDB' }),
|
||||
|
||||
@@ -16,6 +16,8 @@ import { DataFormatProvider } from '../formats/provider';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { StateTransforms } from '../transforms';
|
||||
|
||||
export type EmdbDownloadProvider = 'pdbe' | 'rcsb'
|
||||
|
||||
export { DownloadDensity };
|
||||
type DownloadDensity = typeof DownloadDensity
|
||||
const DownloadDensity = StateAction.build({
|
||||
@@ -42,7 +44,7 @@ const DownloadDensity = StateAction.build({
|
||||
'pdb-emd-ds': PD.Group({
|
||||
provider: PD.Group({
|
||||
id: PD.Text('emd-8004', { label: 'Id' }),
|
||||
server: PD.Select('pdbe', [['pdbe', 'PDBe'], ['rcsb', 'RCSB PDB']]),
|
||||
server: PD.Select<EmdbDownloadProvider>('pdbe', [['pdbe', 'PDBe'], ['rcsb', 'RCSB PDB']]),
|
||||
}, { pivot: 'id' }),
|
||||
detail: PD.Numeric(3, { min: 0, max: 10, step: 1 }, { label: 'Detail' }),
|
||||
}, { isFlat: true }),
|
||||
|
||||
@@ -75,7 +75,8 @@ const auto = StructureRepresentationPresetProvider({
|
||||
case Structure.Size.Medium:
|
||||
return polymerAndLigand.apply(ref, params, plugin);
|
||||
case Structure.Size.Small:
|
||||
return atomicDetail.apply(ref, params, plugin);
|
||||
// `showCarbohydrateSymbol: true` is nice e.g. for PDB 1aga
|
||||
return atomicDetail.apply(ref, { ...params, showCarbohydrateSymbol: true }, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -105,7 +106,7 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
|
||||
polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
|
||||
ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
|
||||
nonStandard: await presetStaticComponent(plugin, structureCell, 'non-standard'),
|
||||
branched: await presetStaticComponent(plugin, structureCell, 'branched'),
|
||||
branched: await presetStaticComponent(plugin, structureCell, 'branched', { label: 'Carbohydrate' }),
|
||||
water: await presetStaticComponent(plugin, structureCell, 'water'),
|
||||
coarse: await presetStaticComponent(plugin, structureCell, 'coarse')
|
||||
};
|
||||
@@ -115,9 +116,9 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams, color }, { tag: 'polymer' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams, color }, { tag: 'ligand' }),
|
||||
nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color: color || 'polymer-id' }, { tag: 'non-standard' }),
|
||||
branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.15 }, color }, { tag: 'branched-ball-and-stick' }),
|
||||
branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.3 }, color }, { tag: 'branched-ball-and-stick' }),
|
||||
branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }, { tag: 'branched-snfg-3d' }),
|
||||
water: builder.buildRepresentation(update, components.water, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.51 }, color }, { tag: 'water' }),
|
||||
water: builder.buildRepresentation(update, components.water, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.6 }, color }, { tag: 'water' }),
|
||||
coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'polymer-id' }, { tag: 'coarse' })
|
||||
};
|
||||
|
||||
@@ -226,19 +227,33 @@ const atomicDetail = StructureRepresentationPresetProvider({
|
||||
name: 'Atomic Detail', group: BuiltInPresetGroupName,
|
||||
description: 'Shows everything in atomic detail with Ball & Stick.'
|
||||
},
|
||||
params: () => CommonParams,
|
||||
params: () => ({
|
||||
...CommonParams,
|
||||
showCarbohydrateSymbol: PD.Boolean(false)
|
||||
}),
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
|
||||
const components = {
|
||||
all: await presetStaticComponent(plugin, structureCell, 'all'),
|
||||
branched: undefined
|
||||
};
|
||||
if (params.showCarbohydrateSymbol) {
|
||||
Object.assign(components, {
|
||||
branched: await presetStaticComponent(plugin, structureCell, 'branched', { label: 'Carbohydrate' }),
|
||||
});
|
||||
}
|
||||
|
||||
const { update, builder, typeParams, color } = reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
all: builder.buildRepresentation(update, components.all, { type: 'ball-and-stick', typeParams, color }, { tag: 'all' })
|
||||
all: builder.buildRepresentation(update, components.all, { type: 'ball-and-stick', typeParams, color }, { tag: 'all' }),
|
||||
};
|
||||
if (params.showCarbohydrateSymbol) {
|
||||
Object.assign(representations, {
|
||||
snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color }, { tag: 'snfg-3d' }),
|
||||
});
|
||||
}
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
return { components, representations };
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { shallowMergeArray } from '../mol-util/object';
|
||||
import { RxEventHelper } from '../mol-util/rx-event-helper';
|
||||
import { Subscription, Observable } from 'rxjs';
|
||||
import { arraySetRemove } from '../mol-util/array';
|
||||
|
||||
export class PluginComponent {
|
||||
private _ev: RxEventHelper | undefined;
|
||||
@@ -14,7 +15,18 @@ export class PluginComponent {
|
||||
|
||||
protected subscribe<T>(obs: Observable<T>, action: (v: T) => void) {
|
||||
if (typeof this.subs === 'undefined') this.subs = [];
|
||||
this.subs.push(obs.subscribe(action));
|
||||
|
||||
let sub: Subscription | undefined = obs.subscribe(action);
|
||||
this.subs.push(sub);
|
||||
|
||||
return {
|
||||
unsubscribe: () => {
|
||||
if (sub && this.subs && arraySetRemove(this.subs, sub)) {
|
||||
sub.unsubscribe();
|
||||
sub = void 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected get ev() {
|
||||
|
||||
@@ -114,14 +114,22 @@ export const Provider3dg: TrajectoryFormatProvider = {
|
||||
};
|
||||
|
||||
export const MolProvider: TrajectoryFormatProvider = {
|
||||
label: 'MOL',
|
||||
description: 'MOL',
|
||||
label: 'MOL/SDF',
|
||||
description: 'MOL/SDF',
|
||||
category: Category,
|
||||
stringExtensions: ['mol', 'sdf'],
|
||||
stringExtensions: ['mol', 'sdf', 'sd'],
|
||||
parse: directTrajectory(StateTransforms.Model.TrajectoryFromMOL),
|
||||
visuals: defaultVisuals
|
||||
};
|
||||
|
||||
export const Mol2Provider: TrajectoryFormatProvider = {
|
||||
label: 'MOL2',
|
||||
description: 'MOL2',
|
||||
category: Category,
|
||||
stringExtensions: ['mol2'],
|
||||
parse: directTrajectory(StateTransforms.Model.TrajectoryFromMOL2),
|
||||
visuals: defaultVisuals
|
||||
};
|
||||
|
||||
export const BuiltInTrajectoryFormats = [
|
||||
['mmcif', MmcifProvider] as const,
|
||||
@@ -129,7 +137,8 @@ export const BuiltInTrajectoryFormats = [
|
||||
['pdb', PdbProvider] as const,
|
||||
['gro', GroProvider] as const,
|
||||
['3dg', Provider3dg] as const,
|
||||
['mol', MolProvider] as const
|
||||
['mol', MolProvider] as const,
|
||||
['mol2', Mol2Provider] as const,
|
||||
] as const;
|
||||
|
||||
export type BuiltInTrajectoryFormat = (typeof BuiltInTrajectoryFormats)[number][0]
|
||||
@@ -15,6 +15,7 @@ import { ColorNames } from '../../mol-util/color/names';
|
||||
import { VolumeIsoValue } 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';
|
||||
|
||||
@@ -104,30 +105,41 @@ export const CubeProvider = DataFormatProvider({
|
||||
visuals: async (plugin: PluginContext, data: { volume: StateObjectSelector<PluginStateObject.Volume.Data>, structure: StateObjectSelector<PluginStateObject.Molecule.Structure> }) => {
|
||||
const surfaces = plugin.build();
|
||||
|
||||
const volumeReprs: StateObjectSelector<PluginStateObject.Volume.Representation3D>[] = [];
|
||||
const volumeData = data.volume.cell?.obj?.data;
|
||||
const volumePos = surfaces.to(data.volume).apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(plugin, volumeData, {
|
||||
type: 'isosurface',
|
||||
typeParams: { isoValue: VolumeIsoValue.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 },
|
||||
color: 'uniform',
|
||||
colorParams: { value: ColorNames.red }
|
||||
}));
|
||||
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 },
|
||||
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 },
|
||||
color: 'uniform',
|
||||
colorParams: { value: ColorNames.red }
|
||||
}));
|
||||
volumeReprs.push(volumePos.selector, volumeNeg.selector);
|
||||
} else {
|
||||
const volume = surfaces.to(data.volume).apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(plugin, volumeData, {
|
||||
type: 'isosurface',
|
||||
typeParams: { isoValue: VolumeIsoValue.relative(2), alpha: 0.4 },
|
||||
color: 'uniform',
|
||||
colorParams: { value: ColorNames.grey }
|
||||
}));
|
||||
volumeReprs.push(volume.selector);
|
||||
}
|
||||
|
||||
const structure = await plugin.builders.structure.representation.applyPreset(data.structure, 'auto');
|
||||
await surfaces.commit();
|
||||
|
||||
const structureReprs: StateObjectSelector<PluginStateObject.Molecule.Structure.Representation3D>[] = [];
|
||||
|
||||
objectForEach(structure?.representations as any, (r: any) => {
|
||||
if (r) structureReprs.push(r);
|
||||
});
|
||||
|
||||
return [volumePos.selector, volumeNeg.selector, ...structureReprs];
|
||||
return [...volumeReprs, ...structureReprs];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -164,7 +176,7 @@ 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: 0.3 }, 'uniform', { value: ColorNames.teal }))
|
||||
.apply(StateTransforms.Representation.VolumeRepresentation3D, VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: VolumeIsoValue.relative(1.5), alpha: 1 }, 'uniform', { value: ColorNames.teal }))
|
||||
.selector;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
*/
|
||||
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
import { QueryContext, Structure, StructureQuery, StructureSelection } from '../../mol-model/structure';
|
||||
import { BondType, NucleicBackboneAtoms, ProteinBackboneAtoms, SecondaryStructureType } from '../../mol-model/structure/model/types';
|
||||
import { QueryContext, Structure, StructureQuery, StructureSelection, StructureProperties, StructureElement } from '../../mol-model/structure';
|
||||
import { BondType, NucleicBackboneAtoms, ProteinBackboneAtoms, SecondaryStructureType, AminoAcidNamesL, RnaBaseNames, DnaBaseNames, WaterNames, ElementSymbol } from '../../mol-model/structure/model/types';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import Expression from '../../mol-script/language/expression';
|
||||
@@ -15,9 +15,9 @@ import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
import { StateBuilder } from '../../mol-state';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { SetUtils } from '../../mol-util/set';
|
||||
import { stringToWords } from '../../mol-util/string';
|
||||
import { PluginStateObject } from '../objects';
|
||||
import { StateTransforms } from '../transforms';
|
||||
import { ElementNames } from '../../mol-model/structure/model/properties/atomic/types';
|
||||
|
||||
export enum StructureSelectionCategory {
|
||||
Type = 'Type',
|
||||
@@ -41,6 +41,7 @@ interface StructureSelectionQuery {
|
||||
readonly description: string
|
||||
readonly category: string
|
||||
readonly isHidden: boolean
|
||||
readonly priority: number
|
||||
readonly referencesCurrent: boolean
|
||||
readonly query: StructureQuery
|
||||
readonly ensureCustomProperties?: (ctx: CustomProperty.Context, structure: Structure) => Promise<void>
|
||||
@@ -51,6 +52,7 @@ interface StructureSelectionQueryProps {
|
||||
description?: string
|
||||
category?: string
|
||||
isHidden?: boolean
|
||||
priority?: number
|
||||
referencesCurrent?: boolean
|
||||
ensureCustomProperties?: (ctx: CustomProperty.Context, structure: Structure) => Promise<void>
|
||||
}
|
||||
@@ -63,6 +65,7 @@ function StructureSelectionQuery(label: string, expression: Expression, props: S
|
||||
description: props.description || '',
|
||||
category: props.category ?? StructureSelectionCategory.Misc,
|
||||
isHidden: !!props.isHidden,
|
||||
priority: props.priority || 0,
|
||||
referencesCurrent: !!props.referencesCurrent,
|
||||
get query() {
|
||||
if (!_query) _query = compile<StructureSelection>(expression);
|
||||
@@ -81,7 +84,7 @@ function StructureSelectionQuery(label: string, expression: Expression, props: S
|
||||
};
|
||||
}
|
||||
|
||||
const all = StructureSelectionQuery('All', MS.struct.generator.all(), { category: '' });
|
||||
const all = StructureSelectionQuery('All', MS.struct.generator.all(), { category: '', priority: 1000 });
|
||||
const current = StructureSelectionQuery('Current Selection', MS.internal.generator.current(), { category: '', referencesCurrent: true });
|
||||
|
||||
const polymer = StructureSelectionQuery('Polymer', MS.struct.modifier.union([
|
||||
@@ -229,14 +232,14 @@ const branchedPlusConnected = StructureSelectionQuery('Carbohydrate with Connect
|
||||
MS.struct.modifier.includeConnected({
|
||||
0: branched.expression, 'layer-count': 1, 'as-whole-residues': true
|
||||
})
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
const branchedConnectedOnly = StructureSelectionQuery('Connected to Carbohydrate', MS.struct.modifier.union([
|
||||
MS.struct.modifier.exceptBy({
|
||||
0: branchedPlusConnected.expression,
|
||||
by: branched.expression
|
||||
})
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
const ligand = StructureSelectionQuery('Ligand', MS.struct.modifier.union([
|
||||
MS.struct.combinator.merge([
|
||||
@@ -290,14 +293,14 @@ const ligandPlusConnected = StructureSelectionQuery('Ligand with Connected', MS.
|
||||
]),
|
||||
by: branched.expression
|
||||
})
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
const ligandConnectedOnly = StructureSelectionQuery('Connected to Ligand', MS.struct.modifier.union([
|
||||
MS.struct.modifier.exceptBy({
|
||||
0: ligandPlusConnected.expression,
|
||||
by: ligand.expression
|
||||
})
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
// residues connected to ligands or branched entities
|
||||
const connectedOnly = StructureSelectionQuery('Connected to Ligand or Carbohydrate', MS.struct.modifier.union([
|
||||
@@ -305,7 +308,7 @@ const connectedOnly = StructureSelectionQuery('Connected to Ligand or Carbohydra
|
||||
branchedConnectedOnly.expression,
|
||||
ligandConnectedOnly.expression
|
||||
]),
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
const disulfideBridges = StructureSelectionQuery('Disulfide Bridges', MS.struct.modifier.union([
|
||||
MS.struct.modifier.wholeResidues([
|
||||
@@ -380,45 +383,147 @@ const bonded = StructureSelectionQuery('Residues Bonded to Selection', MS.struct
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const wholeResidues = StructureSelectionQuery('Whole Residues of Selection', MS.struct.modifier.union([
|
||||
MS.struct.modifier.wholeResidues({
|
||||
0: MS.internal.generator.current()
|
||||
})
|
||||
]), {
|
||||
description: 'Expand current selection to whole residues.',
|
||||
category: StructureSelectionCategory.Manipulate,
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const StandardAminoAcids = [
|
||||
[['HIS'], 'HISTIDINE'],
|
||||
[['ARG'], 'ARGININE'],
|
||||
[['LYS'], 'LYSINE'],
|
||||
[['ILE'], 'ISOLEUCINE'],
|
||||
[['PHE'], 'PHENYLALANINE'],
|
||||
[['LEU'], 'LEUCINE'],
|
||||
[['TRP'], 'TRYPTOPHAN'],
|
||||
[['ALA'], 'ALANINE'],
|
||||
[['MET'], 'METHIONINE'],
|
||||
[['CYS'], 'CYSTEINE'],
|
||||
[['ASN'], 'ASPARAGINE'],
|
||||
[['VAL'], 'VALINE'],
|
||||
[['GLY'], 'GLYCINE'],
|
||||
[['SER'], 'SERINE'],
|
||||
[['GLN'], 'GLUTAMINE'],
|
||||
[['TYR'], 'TYROSINE'],
|
||||
[['ASP'], 'ASPARTIC ACID'],
|
||||
[['GLU'], 'GLUTAMIC ACID'],
|
||||
[['THR'], 'THREONINE'],
|
||||
[['SEC'], 'SELENOCYSTEINE'],
|
||||
[['PYL'], 'PYRROLYSINE'],
|
||||
[['HIS'], 'Histidine'],
|
||||
[['ARG'], 'Arginine'],
|
||||
[['LYS'], 'Lysine'],
|
||||
[['ILE'], 'Isoleucine'],
|
||||
[['PHE'], 'Phenylalanine'],
|
||||
[['LEU'], 'Leucine'],
|
||||
[['TRP'], 'Tryptophan'],
|
||||
[['ALA'], 'Alanine'],
|
||||
[['MET'], 'Methionine'],
|
||||
[['PRO'], 'Proline'],
|
||||
[['CYS'], 'Cysteine'],
|
||||
[['ASN'], 'Asparagine'],
|
||||
[['VAL'], 'Valine'],
|
||||
[['GLY'], 'Glycine'],
|
||||
[['SER'], 'Serine'],
|
||||
[['GLN'], 'Glutamine'],
|
||||
[['TYR'], 'Tyrosine'],
|
||||
[['ASP'], 'Aspartic Acid'],
|
||||
[['GLU'], 'Glutamic Acid'],
|
||||
[['THR'], 'Threonine'],
|
||||
[['SEC'], 'Selenocysteine'],
|
||||
[['PYL'], 'Pyrrolysine'],
|
||||
[['UNK'], 'Unknown'],
|
||||
].sort((a, b) => a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0) as [string[], string][];
|
||||
|
||||
const StandardNucleicBases = [
|
||||
[['A', 'DA'], 'ADENOSINE'],
|
||||
[['C', 'DC'], 'CYTIDINE'],
|
||||
[['T', 'DT'], 'THYMIDINE'],
|
||||
[['G', 'DG'], 'GUANOSINE'],
|
||||
[['I', 'DI'], 'INOSINE'],
|
||||
[['U', 'DU'], 'URIDINE'],
|
||||
[['A', 'DA'], 'Adenosine'],
|
||||
[['C', 'DC'], 'Cytidine'],
|
||||
[['T', 'DT'], 'Thymidine'],
|
||||
[['G', 'DG'], 'Guanosine'],
|
||||
[['I', 'DI'], 'Inosine'],
|
||||
[['U', 'DU'], 'Uridine'],
|
||||
[['N', 'DN'], 'Unknown'],
|
||||
].sort((a, b) => a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0) as [string[], string][];
|
||||
|
||||
function ResidueQuery([names, label]: [string[], string], category: string) {
|
||||
return StructureSelectionQuery(`${stringToWords(label)} (${names.join(', ')})`, MS.struct.modifier.union([
|
||||
export function ResidueQuery([names, label]: [string[], string], category: string, priority = 0) {
|
||||
const description = names.length === 1 && !StandardResidues.has(names[0])
|
||||
? `[${names[0]}] ${label}`
|
||||
: `${label} (${names.join(', ')})`;
|
||||
return StructureSelectionQuery(description, MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'residue-test': MS.core.set.has([MS.set(...names), MS.ammp('auth_comp_id')])
|
||||
})
|
||||
]), { category });
|
||||
]), { category, priority, description });
|
||||
}
|
||||
|
||||
export function ElementSymbolQuery([names, label]: [string[], string], category: string, priority: number) {
|
||||
const description = `${label} (${names.join(', ')})`;
|
||||
return StructureSelectionQuery(description, MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'atom-test': MS.core.set.has([MS.set(...names), MS.acp('elementSymbol')])
|
||||
})
|
||||
]), { category, priority, description });
|
||||
}
|
||||
|
||||
export function EntityDescriptionQuery([names, label]: [string[], string], category: string, priority: number) {
|
||||
const description = `${label}`;
|
||||
return StructureSelectionQuery(`${label}`, MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.list.equal([MS.list(...names), MS.ammp('entityDescription')])
|
||||
})
|
||||
]), { category, priority, description });
|
||||
}
|
||||
|
||||
const StandardResidues = SetUtils.unionMany(
|
||||
AminoAcidNamesL, RnaBaseNames, DnaBaseNames, WaterNames
|
||||
);
|
||||
|
||||
export function getElementQueries(structures: Structure[]) {
|
||||
const uniqueElements = new Set<ElementSymbol>();
|
||||
for (const structure of structures) {
|
||||
structure.uniqueElementSymbols.forEach(e => uniqueElements.add(e));
|
||||
}
|
||||
|
||||
const queries: StructureSelectionQuery[] = [];
|
||||
uniqueElements.forEach(e => {
|
||||
const label = ElementNames[e] || e;
|
||||
queries.push(ElementSymbolQuery([[e], label], 'Element Symbol', 0));
|
||||
});
|
||||
return queries;
|
||||
}
|
||||
|
||||
export function getNonStandardResidueQueries(structures: Structure[]) {
|
||||
const residueLabels = new Map<string, string>();
|
||||
const uniqueResidues = new Set<string>();
|
||||
for (const structure of structures) {
|
||||
structure.uniqueResidueNames.forEach(r => uniqueResidues.add(r));
|
||||
for (const m of structure.models) {
|
||||
structure.uniqueResidueNames.forEach(r => {
|
||||
const comp = m.properties.chemicalComponentMap.get(r);
|
||||
if (comp) residueLabels.set(r, comp.name);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const queries: StructureSelectionQuery[] = [];
|
||||
SetUtils.difference(uniqueResidues, StandardResidues).forEach(r => {
|
||||
const label = residueLabels.get(r) || r;
|
||||
queries.push(ResidueQuery([[r], label], 'Ligand/Non-standard Residue', 200));
|
||||
});
|
||||
return queries;
|
||||
}
|
||||
|
||||
export function getPolymerAndBranchedEntityQueries(structures: Structure[]) {
|
||||
const uniqueEntities = new Map<string, string[]>();
|
||||
const l = StructureElement.Location.create();
|
||||
for (const structure of structures) {
|
||||
l.structure = structure;
|
||||
for (const ug of structure.unitSymmetryGroups) {
|
||||
l.unit = ug.units[0];
|
||||
l.element = ug.elements[0];
|
||||
const entityType = StructureProperties.entity.type(l);
|
||||
if (entityType === 'polymer' || entityType === 'branched') {
|
||||
const description = StructureProperties.entity.pdbx_description(l);
|
||||
uniqueEntities.set(description.join(', '), description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const queries: StructureSelectionQuery[] = [];
|
||||
uniqueEntities.forEach((v, k) => {
|
||||
queries.push(EntityDescriptionQuery([v, k], 'Polymer/Carbohydrate Entities', 300));
|
||||
});
|
||||
return queries;
|
||||
}
|
||||
|
||||
export function applyBuiltInSelection(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, query: keyof typeof StructureSelectionQueries, customTag?: string) {
|
||||
return to.apply(StateTransforms.Model.StructureSelectionFromExpression,
|
||||
{ expression: StructureSelectionQueries[query].expression, label: StructureSelectionQueries[query].label },
|
||||
{ tags: customTag ? [query, customTag] : [query] });
|
||||
}
|
||||
|
||||
export const StructureSelectionQueries = {
|
||||
@@ -447,14 +552,9 @@ export const StructureSelectionQueries = {
|
||||
surroundings,
|
||||
complement,
|
||||
bonded,
|
||||
wholeResidues,
|
||||
};
|
||||
|
||||
export function applyBuiltInSelection(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, query: keyof typeof StructureSelectionQueries, customTag?: string) {
|
||||
return to.apply(StateTransforms.Model.StructureSelectionFromExpression,
|
||||
{ expression: StructureSelectionQueries[query].expression, label: StructureSelectionQueries[query].label },
|
||||
{ tags: customTag ? [query, customTag] : [query] });
|
||||
}
|
||||
|
||||
export class StructureSelectionQueryRegistry {
|
||||
list: StructureSelectionQuery[] = []
|
||||
options: [StructureSelectionQuery, string, string][] = []
|
||||
|
||||
@@ -44,6 +44,7 @@ class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationMana
|
||||
}
|
||||
|
||||
updateParams(newParams: Partial<PluginAnimationManager.State['params']>) {
|
||||
if (this.isEmpty) return;
|
||||
this.updateState({ params: { ...this.state.params, ...newParams } });
|
||||
const anim = this.map.get(this.state.params.current)!;
|
||||
const params = anim.params(this.context) as PD.Params;
|
||||
@@ -59,6 +60,7 @@ class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationMana
|
||||
}
|
||||
|
||||
updateCurrentParams(values: any) {
|
||||
if (this.isEmpty) return;
|
||||
this._current.paramValues = { ...this._current.paramValues, ...values };
|
||||
this.triggerUpdate();
|
||||
}
|
||||
@@ -163,6 +165,7 @@ class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationMana
|
||||
}
|
||||
|
||||
setSnapshot(snapshot: PluginAnimationManager.Snapshot) {
|
||||
if (this.isEmpty) return;
|
||||
this.updateState({ animationState: snapshot.state.animationState });
|
||||
this.updateParams(snapshot.state.params);
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
async setStateSnapshot(snapshot: PluginStateSnapshotManager.StateSnapshot): Promise<PluginState.Snapshot | undefined> {
|
||||
if (snapshot.version !== PLUGIN_VERSION) {
|
||||
// TODO
|
||||
console.warn('state snapshot version mismatch');
|
||||
// console.warn('state snapshot version mismatch');
|
||||
}
|
||||
|
||||
this.clear();
|
||||
|
||||
@@ -37,6 +37,12 @@ export interface StructureMeasurementManagerState {
|
||||
options: StructureMeasurementOptions
|
||||
}
|
||||
|
||||
type StructureMeasurementManagerAddOptions = {
|
||||
customText?: string,
|
||||
selectionTags?: string | string[],
|
||||
reprTags?: string | string[]
|
||||
}
|
||||
|
||||
class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasurementManagerState> {
|
||||
readonly behaviors = {
|
||||
state: this.ev.behavior(this.state)
|
||||
@@ -80,7 +86,7 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree: update, options: { doNotLogTiming: true } });
|
||||
}
|
||||
|
||||
async addDistance(a: StructureElement.Loci, b: StructureElement.Loci) {
|
||||
async addDistance(a: StructureElement.Loci, b: StructureElement.Loci, options?: StructureMeasurementManagerAddOptions) {
|
||||
const cellA = this.plugin.helpers.substructureParent.get(a.structure);
|
||||
const cellB = this.plugin.helpers.substructureParent.get(b.structure);
|
||||
|
||||
@@ -98,17 +104,18 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
|
||||
],
|
||||
isTransitive: true,
|
||||
label: 'Distance'
|
||||
}, { dependsOn })
|
||||
}, { dependsOn, tags: options?.selectionTags })
|
||||
.apply(StateTransforms.Representation.StructureSelectionsDistance3D, {
|
||||
customText: options?.customText || '',
|
||||
unitLabel: this.state.options.distanceUnitLabel,
|
||||
textColor: this.state.options.textColor
|
||||
});
|
||||
}, { tags: options?.reprTags });
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
|
||||
}
|
||||
|
||||
async addAngle(a: StructureElement.Loci, b: StructureElement.Loci, c: StructureElement.Loci) {
|
||||
async addAngle(a: StructureElement.Loci, b: StructureElement.Loci, c: StructureElement.Loci, options?: StructureMeasurementManagerAddOptions) {
|
||||
const cellA = this.plugin.helpers.substructureParent.get(a.structure);
|
||||
const cellB = this.plugin.helpers.substructureParent.get(b.structure);
|
||||
const cellC = this.plugin.helpers.substructureParent.get(c.structure);
|
||||
@@ -129,16 +136,17 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
|
||||
],
|
||||
isTransitive: true,
|
||||
label: 'Angle'
|
||||
}, { dependsOn })
|
||||
}, { dependsOn, tags: options?.selectionTags })
|
||||
.apply(StateTransforms.Representation.StructureSelectionsAngle3D, {
|
||||
customText: options?.customText || '',
|
||||
textColor: this.state.options.textColor
|
||||
});
|
||||
}, { tags: options?.reprTags });
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
|
||||
}
|
||||
|
||||
async addDihedral(a: StructureElement.Loci, b: StructureElement.Loci, c: StructureElement.Loci, d: StructureElement.Loci) {
|
||||
async addDihedral(a: StructureElement.Loci, b: StructureElement.Loci, c: StructureElement.Loci, d: StructureElement.Loci, options?: StructureMeasurementManagerAddOptions) {
|
||||
const cellA = this.plugin.helpers.substructureParent.get(a.structure);
|
||||
const cellB = this.plugin.helpers.substructureParent.get(b.structure);
|
||||
const cellC = this.plugin.helpers.substructureParent.get(c.structure);
|
||||
@@ -162,16 +170,17 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
|
||||
],
|
||||
isTransitive: true,
|
||||
label: 'Dihedral'
|
||||
}, { dependsOn })
|
||||
}, { dependsOn, tags: options?.selectionTags })
|
||||
.apply(StateTransforms.Representation.StructureSelectionsDihedral3D, {
|
||||
customText: options?.customText || '',
|
||||
textColor: this.state.options.textColor
|
||||
});
|
||||
}, { tags: options?.reprTags });
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
|
||||
}
|
||||
|
||||
async addLabel(a: StructureElement.Loci) {
|
||||
async addLabel(a: StructureElement.Loci, options?: Omit<StructureMeasurementManagerAddOptions, 'customText'>) {
|
||||
const cellA = this.plugin.helpers.substructureParent.get(a.structure);
|
||||
|
||||
if (!cellA) return;
|
||||
@@ -186,10 +195,10 @@ class StructureMeasurementManager extends StatefulPluginComponent<StructureMeasu
|
||||
],
|
||||
isTransitive: true,
|
||||
label: 'Label'
|
||||
}, { dependsOn })
|
||||
}, { dependsOn, tags: options?.selectionTags })
|
||||
.apply(StateTransforms.Representation.StructureSelectionsLabel3D, {
|
||||
textColor: this.state.options.textColor
|
||||
});
|
||||
}, { tags: options?.reprTags });
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotLogTiming: true } });
|
||||
|
||||
@@ -35,6 +35,8 @@ import { parseMol } from '../../mol-io/reader/mol/parser';
|
||||
import { trajectoryFromMol } from '../../mol-model-formats/structure/mol';
|
||||
import { trajectoryFromCifCore } from '../../mol-model-formats/structure/cif-core';
|
||||
import { trajectoryFromCube } from '../../mol-model-formats/structure/cube';
|
||||
import { parseMol2 } from '../../mol-io/reader/mol2/parser';
|
||||
import { trajectoryFromMol2 } from '../../mol-model-formats/structure/mol2';
|
||||
|
||||
export { CoordinatesFromDcd };
|
||||
export { TopologyFromPsf };
|
||||
@@ -44,6 +46,7 @@ export { TrajectoryFromMmCif };
|
||||
export { TrajectoryFromPDB };
|
||||
export { TrajectoryFromGRO };
|
||||
export { TrajectoryFromMOL };
|
||||
export { TrajectoryFromMOL2 };
|
||||
export { TrajectoryFromCube };
|
||||
export { TrajectoryFromCifCore };
|
||||
export { TrajectoryFrom3DG };
|
||||
@@ -235,6 +238,24 @@ const TrajectoryFromMOL = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
});
|
||||
|
||||
type TrajectoryFromMOL2 = typeof TrajectoryFromMOL
|
||||
const TrajectoryFromMOL2 = PluginStateTransform.BuiltIn({
|
||||
name: 'trajectory-from-mol2',
|
||||
display: { name: 'Parse MOL2', description: 'Parse MOL2 string and create trajectory.' },
|
||||
from: [SO.Data.String],
|
||||
to: SO.Molecule.Trajectory
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse MOL2', async ctx => {
|
||||
const parsed = await parseMol2(a.data, a.label).runInContext(ctx);
|
||||
if (parsed.isError) throw new Error(parsed.message);
|
||||
const models = await trajectoryFromMol2(parsed.result).runInContext(ctx);
|
||||
const props = { label: `${models[0].entry}`, description: `${models.length} model${models.length === 1 ? '' : 's'}` };
|
||||
return new SO.Molecule.Trajectory(models, props);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
type TrajectoryFromCube = typeof TrajectoryFromCube
|
||||
const TrajectoryFromCube = PluginStateTransform.BuiltIn({
|
||||
name: 'trajectory-from-cube',
|
||||
|
||||
@@ -29,6 +29,7 @@ import { StructureMeasurementsControls } from './structure/measurements';
|
||||
import { StructureSelectionActionsControls } from './structure/selection';
|
||||
import { StructureSourceControls } from './structure/source';
|
||||
import { VolumeStreamingControls, VolumeSourceControls } from './structure/volume';
|
||||
import { PluginConfig } from '../mol-plugin/config';
|
||||
|
||||
export class TrajectoryViewportControls extends PluginUIComponent<{}, { show: boolean, label: string }> {
|
||||
state = { show: false, label: '' }
|
||||
@@ -219,9 +220,8 @@ export class AnimationViewportControls extends PluginUIComponent<{}, { isEmpty:
|
||||
}
|
||||
|
||||
render() {
|
||||
// if (!this.state.show) return null;
|
||||
const isPlaying = this.plugin.managers.snapshot.state.isPlaying;
|
||||
if (isPlaying || this.state.isEmpty) return null;
|
||||
if (isPlaying || this.state.isEmpty || this.plugin.managers.animation.isEmpty || !this.plugin.config.get(PluginConfig.Viewport.ShowAnimation)) return null;
|
||||
|
||||
const isAnimating = this.state.isAnimating;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export class ActionMenu extends React.PureComponent<ActionMenu.Props> {
|
||||
const cmd = this.props;
|
||||
const section = <Section items={cmd.items} onSelect={cmd.onSelect} current={cmd.current} multiselect={this.props.multiselect} noOffset={this.props.noOffset} noAccent={this.props.noAccent} />;
|
||||
return <div className={`msp-action-menu-options${cmd.header ? '' : ' msp-action-menu-options-no-header'}`}>
|
||||
{cmd.header && <ControlGroup header={cmd.header} initialExpanded={true} hideExpander={true} hideOffset onHeaderClick={this.hide} topRightIcon={Close}>
|
||||
{cmd.header && <ControlGroup header={cmd.header} title={cmd.title} initialExpanded={true} hideExpander={true} hideOffset onHeaderClick={this.hide} topRightIcon={Close}>
|
||||
{section}
|
||||
</ControlGroup>}
|
||||
{!cmd.header && section}
|
||||
@@ -33,6 +33,7 @@ export namespace ActionMenu {
|
||||
items: Items,
|
||||
onSelect: OnSelect | OnSelectMany,
|
||||
header?: string,
|
||||
title?: string,
|
||||
current?: Item,
|
||||
multiselect?: boolean,
|
||||
noOffset?: boolean,
|
||||
|
||||
@@ -16,6 +16,7 @@ export type ColorAccent = 'cyan' | 'red' | 'gray' | 'green' | 'purple' | 'blue'
|
||||
|
||||
export class ControlGroup extends React.Component<{
|
||||
header: string,
|
||||
title?: string,
|
||||
initialExpanded?: boolean,
|
||||
hideExpander?: boolean,
|
||||
hideOffset?: boolean,
|
||||
@@ -41,7 +42,7 @@ export class ControlGroup extends React.Component<{
|
||||
|
||||
// TODO: customize header style (bg color, togle button etc)
|
||||
return <div className='msp-control-group-wrapper' style={{ position: 'relative', marginTop: this.props.noTopMargin ? 0 : void 0 }}>
|
||||
<div className='msp-control-group-header' style={{ marginLeft: this.props.headerLeftMargin }}>
|
||||
<div className='msp-control-group-header' style={{ marginLeft: this.props.headerLeftMargin }} title={this.props.title}>
|
||||
<Button onClick={this.headerClicked}>
|
||||
{!this.props.hideExpander && <Icon svg={this.state.isExpanded ? ArrowRight : ArrowDropDown} />}
|
||||
{this.props.topRightIcon && <Icon svg={this.props.topRightIcon} style={{ position: 'absolute', right: '2px', top: 0 }} />}
|
||||
|
||||
@@ -88,21 +88,18 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
// .msp-btn-icon, .msp-btn-icon-small {
|
||||
// svg {
|
||||
// display: inline-flex;
|
||||
// vertical-align: middle;
|
||||
// font-size: 1rem;
|
||||
// margin-bottom: 3px;
|
||||
// }
|
||||
// }
|
||||
|
||||
.msp-material-icon {
|
||||
svg {
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
font-size: 1rem;
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 3px;
|
||||
|
||||
fill: currentColor;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
flex-shrink: 0;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +267,7 @@ select[multiple],
|
||||
select[size] {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
||||
// Reset height for `textarea`s
|
||||
textarea.msp-form-control {
|
||||
height: auto;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -49,8 +49,8 @@ export class StateTree extends PluginUIComponent<{ state: State }, { showActions
|
||||
const ref = this.props.state.tree.root.ref;
|
||||
if (this.state.showActions) {
|
||||
return <div style={{ margin: '10px', cursor: 'default' }}>
|
||||
<p>Nothing to see here.</p>
|
||||
<p>Structures can be loaded from the <Icon svg={HomeOutlined} /> tab.</p>
|
||||
<p>Nothing to see here yet.</p>
|
||||
<p>Structures and Volumes can be loaded from the <Icon svg={HomeOutlined} /> tab.</p>
|
||||
</div>;
|
||||
}
|
||||
return <StateTreeNode cell={this.props.state.cells.get(ref)!} depth={0} />;
|
||||
|
||||
@@ -11,7 +11,7 @@ import Brush from '@material-ui/icons/Brush';
|
||||
import Restore from '@material-ui/icons/Restore';
|
||||
import Remove from '@material-ui/icons/Remove';
|
||||
import * as React from 'react';
|
||||
import { StructureSelectionQueries, StructureSelectionQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { StructureSelectionQueries, StructureSelectionQuery, getNonStandardResidueQueries, getElementQueries, getPolymerAndBranchedEntityQueries } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { InteractivityManager } from '../../mol-plugin-state/manager/interactivity';
|
||||
import { StructureComponentManager } from '../../mol-plugin-state/manager/structure/component';
|
||||
import { StructureRef, StructureComponentRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
@@ -25,6 +25,7 @@ import { Button, ControlGroup, IconButton, ToggleButton } from '../controls/comm
|
||||
import { ParameterControls, ParamOnChange, PureSelectControl } from '../controls/parameters';
|
||||
import { Union, Subtract, Intersect, SetSvg as SetSvg, CubeSvg } from '../controls/icons';
|
||||
import { AddComponentControls } from './components';
|
||||
import Structure from '../../mol-model/structure/structure/structure';
|
||||
|
||||
const StructureSelectionParams = {
|
||||
granularity: InteractivityManager.Params.granularity,
|
||||
@@ -60,6 +61,9 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
if (this.state.isEmpty !== isEmpty) {
|
||||
this.setState({ isEmpty });
|
||||
}
|
||||
// trigger elementQueries and nonStandardResidueQueries recalculation
|
||||
this.queriesVersion = -1;
|
||||
this.forceUpdate();
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, v => {
|
||||
@@ -83,15 +87,28 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
this.plugin.managers.structure.selection.fromSelectionQuery(modifier, selectionQuery, false);
|
||||
}
|
||||
|
||||
selectQuery: ActionMenu.OnSelect = item => {
|
||||
selectQuery: ActionMenu.OnSelect = (item, e) => {
|
||||
if (!item || !this.state.action) {
|
||||
this.setState({ action: void 0 });
|
||||
return;
|
||||
}
|
||||
const q = this.state.action! as StructureSelectionModifier;
|
||||
this.setState({ action: void 0 }, () => {
|
||||
if (e?.shiftKey) {
|
||||
this.set(q, item.value as StructureSelectionQuery);
|
||||
});
|
||||
} else {
|
||||
this.setState({ action: void 0 }, () => {
|
||||
this.set(q, item.value as StructureSelectionQuery);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get structures () {
|
||||
const structures: Structure[] = [];
|
||||
for (const s of this.plugin.managers.structure.hierarchy.selection.structures) {
|
||||
const structure = s.cell.obj?.data;
|
||||
if (structure) structures.push(structure);
|
||||
}
|
||||
return structures;
|
||||
}
|
||||
|
||||
private queriesItems: ActionMenu.Items[] = []
|
||||
@@ -99,8 +116,15 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
get queries () {
|
||||
const { registry } = this.plugin.query.structure;
|
||||
if (registry.version !== this.queriesVersion) {
|
||||
this.queriesItems = ActionMenu.createItems(registry.list, {
|
||||
filter: q => q !== StructureSelectionQueries.current,
|
||||
const structures = this.structures;
|
||||
const queries = [
|
||||
...registry.list,
|
||||
...getPolymerAndBranchedEntityQueries(structures),
|
||||
...getNonStandardResidueQueries(structures),
|
||||
...getElementQueries(structures)
|
||||
].sort((a, b) => b.priority - a.priority);
|
||||
this.queriesItems = ActionMenu.createItems(queries, {
|
||||
filter: q => q !== StructureSelectionQueries.current && !q.isHidden,
|
||||
label: q => q.label,
|
||||
category: q => q.category,
|
||||
description: q => q.description
|
||||
@@ -148,29 +172,29 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
|
||||
return <>
|
||||
<div className='msp-flex-row' style={{ background: 'none' }}>
|
||||
<PureSelectControl title={`Picking Level`} param={StructureSelectionParams.granularity} name='granularity' value={granularity} onChange={this.setGranuality} isDisabled={this.isDisabled} />
|
||||
<ToggleButton icon={Union} title={ActionHeader.get('add')} toggle={this.toggleAdd} isSelected={this.state.action === 'add'} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={Subtract} title={ActionHeader.get('remove')} toggle={this.toggleRemove} isSelected={this.state.action === 'remove'} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={Intersect} title={ActionHeader.get('intersect')} toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={SetSvg} title={ActionHeader.get('set')} toggle={this.toggleSet} isSelected={this.state.action === 'set'} disabled={this.isDisabled} />
|
||||
<PureSelectControl title={`Picking Level for selecting and highlighting`} param={StructureSelectionParams.granularity} name='granularity' value={granularity} onChange={this.setGranuality} isDisabled={this.isDisabled} />
|
||||
<ToggleButton icon={Union} title={`${ActionHeader.get('add')}. Hold shift key to keep menu open.`} toggle={this.toggleAdd} isSelected={this.state.action === 'add'} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={Subtract} title={`${ActionHeader.get('remove')}. Hold shift key to keep menu open.`} toggle={this.toggleRemove} isSelected={this.state.action === 'remove'} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={Intersect} title={`${ActionHeader.get('intersect')}. Hold shift key to keep menu open.`} toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={SetSvg} title={`${ActionHeader.get('set')}. Hold shift key to keep menu open.`} toggle={this.toggleSet} isSelected={this.state.action === 'set'} disabled={this.isDisabled} />
|
||||
|
||||
<ToggleButton icon={Brush} title='Color' toggle={this.toggleColor} isSelected={this.state.action === 'color'} disabled={this.isDisabled} style={{ marginLeft: '10px' }} />
|
||||
<ToggleButton icon={CubeSvg} title='Create Representation' toggle={this.toggleAddRepr} isSelected={this.state.action === 'add-repr'} disabled={this.isDisabled} />
|
||||
<IconButton svg={Remove} title='Subtract from Representations' onClick={this.subtract} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={Brush} title='Color Selection' toggle={this.toggleColor} isSelected={this.state.action === 'color'} disabled={this.isDisabled} style={{ marginLeft: '10px' }} />
|
||||
<ToggleButton icon={CubeSvg} title='Create Representation of Selection' toggle={this.toggleAddRepr} isSelected={this.state.action === 'add-repr'} disabled={this.isDisabled} />
|
||||
<IconButton svg={Remove} title='Subtract Selection from Representations' onClick={this.subtract} disabled={this.isDisabled} />
|
||||
<IconButton svg={Restore} onClick={this.undo} disabled={!this.state.canUndo || this.isDisabled} title={undoTitle} />
|
||||
|
||||
<IconButton svg={CancelOutlined} title='Turn selection mode off' onClick={this.turnOff} style={{ marginLeft: '10px' }} />
|
||||
</div>
|
||||
{(this.state.action && this.state.action !== 'color' && this.state.action !== 'add-repr') && <div className='msp-selection-viewport-controls-actions'>
|
||||
<ActionMenu header={ActionHeader.get(this.state.action as StructureSelectionModifier)} items={this.queries} onSelect={this.selectQuery} noOffset />
|
||||
<ActionMenu header={ActionHeader.get(this.state.action as StructureSelectionModifier)} title='Click to close.' items={this.queries} onSelect={this.selectQuery} noOffset />
|
||||
</div>}
|
||||
{this.state.action === 'color' && <div className='msp-selection-viewport-controls-actions'>
|
||||
<ControlGroup header='Color' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleColor} topRightIcon={Close}>
|
||||
<ControlGroup header='Color' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleColor} topRightIcon={Close}>
|
||||
<ApplyColorControls onApply={this.toggleColor} />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
{this.state.action === 'add-repr' && <div className='msp-selection-viewport-controls-actions'>
|
||||
<ControlGroup header='Add Representation' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleAddRepr} topRightIcon={Close}>
|
||||
<ControlGroup header='Add Representation' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleAddRepr} topRightIcon={Close}>
|
||||
<AddComponentControls onApply={this.toggleAddRepr} forSelection />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
|
||||
@@ -104,8 +104,8 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
|
||||
</div>
|
||||
<div>
|
||||
<div className='msp-semi-transparent-background' />
|
||||
{this.icon(BuildOutlined, this.toggleControls, 'Toggle Controls', this.plugin.layout.state.showControls)}
|
||||
{this.plugin.config.get(PluginConfig.Viewport.ShowExpand) && this.icon(Fullscreen, this.toggleExpanded, 'Toggle Expanded', this.plugin.layout.state.isExpanded)}
|
||||
{this.icon(BuildOutlined, this.toggleControls, 'Toggle Controls Panel', this.plugin.layout.state.showControls)}
|
||||
{this.plugin.config.get(PluginConfig.Viewport.ShowExpand) && this.icon(Fullscreen, this.toggleExpanded, 'Toggle Expanded Viewport', this.plugin.layout.state.isExpanded)}
|
||||
{this.icon(Tune, this.toggleSettingsExpanded, 'Settings / Controls Info', this.state.isSettingsExpanded)}
|
||||
</div>
|
||||
{this.plugin.config.get(PluginConfig.Viewport.ShowSelectionMode) && <div>
|
||||
@@ -114,13 +114,13 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
|
||||
</div>}
|
||||
</div>
|
||||
{this.state.isScreenshotExpanded && <div className='msp-viewport-controls-panel'>
|
||||
<ControlGroup header='Screenshot / State' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleScreenshotExpanded}
|
||||
<ControlGroup header='Screenshot / State' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleScreenshotExpanded}
|
||||
topRightIcon={Close} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
|
||||
<DownloadScreenshotControls close={this.toggleScreenshotExpanded} />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
{this.state.isSettingsExpanded && <div className='msp-viewport-controls-panel'>
|
||||
<ControlGroup header='Settings / Controls Info' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleSettingsExpanded}
|
||||
<ControlGroup header='Settings / Controls Info' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleSettingsExpanded}
|
||||
topRightIcon={Close} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
|
||||
<SimpleSettingsControl />
|
||||
</ControlGroup>
|
||||
@@ -130,14 +130,7 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
|
||||
}
|
||||
|
||||
export const Logo = () =>
|
||||
<div className='msp-logo'>
|
||||
<div>
|
||||
<div>
|
||||
<div />
|
||||
<div className='msp-logo-image' />
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
<a className='msp-logo' href='https://molstar.org' target='_blank' />;
|
||||
|
||||
interface ViewportState {
|
||||
noWebGl: boolean
|
||||
|
||||
@@ -187,4 +187,9 @@ export function Snapshots(ctx: PluginContext) {
|
||||
PluginCommands.State.Snapshots.OpenFile.subscribe(ctx, ({ file }) => {
|
||||
return ctx.managers.snapshot.open(file);
|
||||
});
|
||||
|
||||
PluginCommands.State.Snapshots.OpenUrl.subscribe(ctx, async ({ url, type }) => {
|
||||
const data = await ctx.runTask(ctx.fetch({ url, type: 'binary' }));
|
||||
return ctx.managers.snapshot.open(new File([data], `state.${type}`));
|
||||
});
|
||||
}
|
||||
@@ -37,8 +37,9 @@ export const PluginCommands = {
|
||||
Upload: PluginCommand<{ name?: string, description?: string, playOnLoad?: boolean, serverUrl: string, params?: PluginState.SnapshotParams }>(),
|
||||
Fetch: PluginCommand<{ url: string }>(),
|
||||
|
||||
DownloadToFile: PluginCommand<{ name?: string, type: 'json' | 'molj' | 'zip' | 'molx', params?: PluginState.SnapshotParams }>(),
|
||||
DownloadToFile: PluginCommand<{ name?: string, type: PluginState.SnapshotType, params?: PluginState.SnapshotParams }>(),
|
||||
OpenFile: PluginCommand<{ file: File }>(),
|
||||
OpenUrl: PluginCommand<{ url: string, type: PluginState.SnapshotType }>(),
|
||||
}
|
||||
},
|
||||
Interactivity: {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
import { Structure, Model } from '../mol-model/structure';
|
||||
import { PluginContext } from './context';
|
||||
import { PdbDownloadProvider } from '../mol-plugin-state/actions/structure';
|
||||
import { EmdbDownloadProvider } from '../mol-plugin-state/actions/volume';
|
||||
|
||||
export class PluginConfigItem<T = any> {
|
||||
toString() { return this.key; }
|
||||
@@ -34,7 +36,12 @@ export const PluginConfig = {
|
||||
},
|
||||
Viewport: {
|
||||
ShowExpand: item('viewer.show-expand-button', true),
|
||||
ShowSelectionMode: item('viewer.show-selection-model-button', true)
|
||||
ShowSelectionMode: item('viewer.show-selection-model-button', true),
|
||||
ShowAnimation: item('viewer.show-animation-button', true),
|
||||
},
|
||||
Download: {
|
||||
DefaultPdbProvider: item<PdbDownloadProvider>('download.default-pdb-provider', 'pdbe'),
|
||||
DefaultEmdbProvider: item<EmdbDownloadProvider>('download.default-emdb-provider', 'pdbe'),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ import { PluginStateSnapshotManager } from '../mol-plugin-state/manager/snapshot
|
||||
import { PluginAnimationManager } from '../mol-plugin-state/manager/animation';
|
||||
import { objectForEach } from '../mol-util/object';
|
||||
import { VolumeHierarchyManager } from '../mol-plugin-state/manager/volume/hierarchy';
|
||||
import { filter, take } from 'rxjs/operators';
|
||||
|
||||
export class PluginContext {
|
||||
runTask = <T>(task: Task<T>) => this.tasks.run(task);
|
||||
@@ -73,13 +74,13 @@ export class PluginContext {
|
||||
log: this.ev<LogEntry>(),
|
||||
task: this.tasks.events,
|
||||
canvas3d: {
|
||||
initialized: this.ev(),
|
||||
settingsUpdated: this.ev(),
|
||||
}
|
||||
} as const
|
||||
|
||||
readonly config = new PluginConfigManager(this.spec.config);
|
||||
|
||||
private canvas3dInit = this.ev.behavior<boolean>(false);
|
||||
readonly behaviors = {
|
||||
state: {
|
||||
isAnimating: this.ev.behavior<boolean>(false),
|
||||
@@ -96,8 +97,11 @@ export class PluginContext {
|
||||
},
|
||||
layout: {
|
||||
leftPanelTabName: this.ev.behavior<LeftPanelTabName>('root')
|
||||
},
|
||||
canvas3d: {
|
||||
initialized: this.canvas3dInit.pipe(filter(v => !!v), take(1))
|
||||
}
|
||||
} as const
|
||||
} as const;
|
||||
|
||||
readonly canvas3d: Canvas3D | undefined;
|
||||
readonly layout = new PluginLayout(this);
|
||||
@@ -174,8 +178,7 @@ export class PluginContext {
|
||||
if (this.spec.layout && this.spec.layout.initial) this.layout.setProps(this.spec.layout.initial);
|
||||
|
||||
(this.canvas3d as Canvas3D) = Canvas3D.fromCanvas(canvas);
|
||||
this.events.canvas3d.initialized.next();
|
||||
this.events.canvas3d.initialized.isStopped = true; // TODO is this a good way?
|
||||
this.canvas3dInit.next(true);
|
||||
const renderer = this.canvas3d!.props.renderer;
|
||||
PluginCommands.Canvas3D.SetSettings(this, { settings: { renderer: { ...renderer, backgroundColor: Color(0xFCFBF9) } } });
|
||||
this.canvas3d!.animate();
|
||||
|
||||
@@ -166,4 +166,6 @@ namespace PluginState {
|
||||
structureFocus?: StructureFocusSnapshot,
|
||||
durationInMs?: number
|
||||
}
|
||||
|
||||
export type SnapshotType = 'json' | 'molj' | 'zip' | 'molx'
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { transformPrimitive } from '../../../mol-geo/primitive/primitive';
|
||||
import { MarkerActions, MarkerAction } from '../../../mol-util/marker-action';
|
||||
import { angleLabel } from '../../../mol-theme/label';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { MeasurementRepresentationCommonTextParams } from './common';
|
||||
|
||||
export interface AngleData {
|
||||
triples: Loci.Bundle<3>[]
|
||||
@@ -62,9 +63,8 @@ type SectorParams = typeof SectorParams
|
||||
|
||||
const TextParams = {
|
||||
...Text.Params,
|
||||
borderWidth: PD.Numeric(0.2, { min: 0, max: 0.5, step: 0.01 }),
|
||||
textColor: PD.Color(ColorNames.black),
|
||||
textSize: PD.Numeric(0.4, { min: 0.1, max: 5, step: 0.1 }),
|
||||
...MeasurementRepresentationCommonTextParams,
|
||||
borderWidth: PD.Numeric(0.2, { min: 0, max: 0.5, step: 0.01 })
|
||||
};
|
||||
type TextParams = typeof TextParams
|
||||
|
||||
@@ -227,7 +227,7 @@ function buildText(data: AngleData, props: AngleProps, text?: Text): Text {
|
||||
Vec3.add(tmpVec, tmpState.sphereB.center, tmpVec);
|
||||
|
||||
const angle = radToDeg(tmpState.angle).toFixed(2);
|
||||
const label = `${angle}\u00B0`;
|
||||
const label = props.customText || `${angle}\u00B0`;
|
||||
const radius = Math.max(2, tmpState.sphereA.radius, tmpState.sphereB.radius, tmpState.sphereC.radius);
|
||||
const scale = radius / 2;
|
||||
builder.add(label, tmpVec[0], tmpVec[1], tmpVec[2], 0.1, scale, i);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../../mol-util/color/names';
|
||||
|
||||
export const MeasurementRepresentationCommonTextParams = {
|
||||
customText: PD.Text('', { label: 'Text', description: 'Override the label with custom value.' }),
|
||||
textColor: PD.Color(ColorNames.black, { isEssential: true }),
|
||||
textSize: PD.Numeric(0.5, { min: 0.1, max: 5, step: 0.1 }, { isEssential: true }),
|
||||
};
|
||||
@@ -76,6 +76,7 @@ type TextParams = typeof TextParams
|
||||
const DihedralVisuals = {
|
||||
'vectors': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, VectorsParams>) => ShapeRepresentation(getVectorsShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
|
||||
'extenders': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, ExtendersParams>) => ShapeRepresentation(getExtendersShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
|
||||
'connector': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, ExtendersParams>) => ShapeRepresentation(getConnectorShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
|
||||
'arc': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, ArcParams>) => ShapeRepresentation(getArcShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
|
||||
'sector': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, SectorParams>) => ShapeRepresentation(getSectorShape, Mesh.Utils, { modifyProps: p => ({ ...p, alpha: p.sectorOpacity }), modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
|
||||
'text': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, TextParams>) => ShapeRepresentation(getTextShape, Text.Utils, { modifyState: s => ({ ...s, markerActions: MarkerAction.None }) }),
|
||||
@@ -156,7 +157,8 @@ function setDihedralState(quad: Loci.Bundle<4>, state: DihedralState, arcScale:
|
||||
Vec3.add(arcPointA, arcCenter, arcDirA);
|
||||
Vec3.add(arcPointD, arcCenter, arcDirD);
|
||||
state.radius = radius;
|
||||
state.angle = Vec3.angle(arcDirA, arcDirD);
|
||||
|
||||
state.angle = Vec3.dihedralAngle(sphereA.center, sphereB.center, sphereC.center, sphereD.center);
|
||||
|
||||
Vec3.matchDirection(tmpVec, arcNormal, Vec3.sub(tmpVec, arcPointA, sphereA.center));
|
||||
const angleA = Vec3.angle(dirBA, tmpVec);
|
||||
@@ -175,11 +177,11 @@ function getCircle(state: DihedralState, segmentLength?: number) {
|
||||
const { radius, angle } = state;
|
||||
const segments = segmentLength ? arcLength(angle, radius) / segmentLength : 32;
|
||||
|
||||
Mat4.targetTo(tmpMat, state.arcCenter, angle > halfPI ? state.arcPointA : state.arcPointD, state.arcNormal);
|
||||
Mat4.targetTo(tmpMat, state.arcCenter, angle < 0 ? state.arcPointD : state.arcPointA, state.arcNormal);
|
||||
Mat4.setTranslation(tmpMat, state.arcCenter);
|
||||
Mat4.mul(tmpMat, tmpMat, Mat4.rotY180);
|
||||
|
||||
const circle = Circle({ radius, thetaLength: angle, segments });
|
||||
const circle = Circle({ radius, thetaLength: Math.abs(angle), segments });
|
||||
return transformPrimitive(circle, tmpMat);
|
||||
}
|
||||
|
||||
@@ -209,6 +211,23 @@ function getVectorsShape(ctx: RuntimeContext, data: DihedralData, props: Dihedra
|
||||
|
||||
//
|
||||
|
||||
function buildConnectorLine(data: DihedralData, props: DihedralProps, lines?: Lines): Lines {
|
||||
const builder = LinesBuilder.create(128, 64, lines);
|
||||
for (let i = 0, il = data.quads.length; i < il; ++i) {
|
||||
setDihedralState(data.quads[i], tmpState, props.arcScale);
|
||||
builder.addFixedLengthDashes(tmpState.sphereB.center, tmpState.sphereC.center, props.dashLength, i);
|
||||
}
|
||||
return builder.getLines();
|
||||
}
|
||||
|
||||
function getConnectorShape(ctx: RuntimeContext, data: DihedralData, props: DihedralProps, shape?: Shape<Lines>) {
|
||||
const lines = buildConnectorLine(data, props, shape && shape.geometry);
|
||||
const name = getDihedralName(data);
|
||||
return Shape.create(name, data, lines, () => props.color, () => props.linesSize, () => '');
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
function buildExtendersLines(data: DihedralData, props: DihedralProps, lines?: Lines): Lines {
|
||||
const builder = LinesBuilder.create(128, 64, lines);
|
||||
for (let i = 0, il = data.quads.length; i < il; ++i) {
|
||||
@@ -287,8 +306,8 @@ function buildText(data: DihedralData, props: DihedralProps, text?: Text): Text
|
||||
Vec3.setMagnitude(tmpVec, tmpVec, tmpState.radius);
|
||||
Vec3.add(tmpVec, tmpState.arcCenter, tmpVec);
|
||||
|
||||
const angle = radToDeg(tmpState.angle).toFixed(2);
|
||||
const label = `${angle}\u00B0`;
|
||||
const angle = Math.abs(radToDeg(tmpState.angle)).toFixed(2);
|
||||
const label = props.customText || `${angle}\u00B0`;
|
||||
const radius = Math.max(2, tmpState.sphereA.radius, tmpState.sphereB.radius, tmpState.sphereC.radius, tmpState.sphereD.radius);
|
||||
const scale = radius / 2;
|
||||
builder.add(label, tmpVec[0], tmpVec[1], tmpVec[2], 0.1, scale, i);
|
||||
|
||||
@@ -118,7 +118,7 @@ function buildText(data: DistanceData, props: DistanceProps, text?: Text): Text
|
||||
for (let i = 0, il = data.pairs.length; i < il; ++i) {
|
||||
setDistanceState(data.pairs[i], tmpState);
|
||||
const { center, distance, sphereA, sphereB } = tmpState;
|
||||
const label = `${distance.toFixed(2)} ${props.unitLabel}`;
|
||||
const label = props.customText || `${distance.toFixed(2)} ${props.unitLabel}`;
|
||||
const radius = Math.max(2, sphereA.radius, sphereB.radius);
|
||||
const scale = radius / 2;
|
||||
builder.add(label, center[0], center[1], center[2], 1, scale, i);
|
||||
|
||||
@@ -26,6 +26,7 @@ import { EmptyLoci, Loci } from '../../../mol-model/loci';
|
||||
import { VisualContext } from '../../../mol-repr/visual';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { getAltResidueLociFromId } from './util/common';
|
||||
import { BaseGeometry } from '../../../mol-geo/geometry/base';
|
||||
|
||||
const t = Mat4.identity();
|
||||
const sVec = Vec3.zero();
|
||||
@@ -162,7 +163,7 @@ function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Structure,
|
||||
|
||||
export const CarbohydrateSymbolParams = {
|
||||
...ComplexMeshParams,
|
||||
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }),
|
||||
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
|
||||
sizeFactor: PD.Numeric(1.75, { min: 0, max: 10, step: 0.01 }),
|
||||
};
|
||||
export type CarbohydrateSymbolParams = typeof CarbohydrateSymbolParams
|
||||
|
||||
@@ -10,12 +10,13 @@ import { UnitsMeshParams, UnitsSpheresParams, UnitsVisual, UnitsSpheresVisual, U
|
||||
import { WebGLContext } from '../../../mol-gl/webgl/context';
|
||||
import { createElementSphereImpostor, ElementIterator, getElementLoci, eachElement, createElementSphereMesh } from './util/element';
|
||||
import { VisualUpdateState } from '../../util';
|
||||
import { BaseGeometry } from '../../../mol-geo/geometry/base';
|
||||
|
||||
export const ElementSphereParams = {
|
||||
...UnitsMeshParams,
|
||||
...UnitsSpheresParams,
|
||||
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
|
||||
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }),
|
||||
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
traceOnly: PD.Boolean(false),
|
||||
};
|
||||
|
||||
@@ -21,11 +21,12 @@ import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/property
|
||||
import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
|
||||
import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { BaseGeometry } from '../../../mol-geo/geometry/base';
|
||||
|
||||
export const EllipsoidMeshParams = {
|
||||
...UnitsMeshParams,
|
||||
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
|
||||
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }),
|
||||
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
|
||||
ignoreHydrogens: PD.Boolean(false),
|
||||
};
|
||||
export type EllipsoidMeshParams = typeof EllipsoidMeshParams
|
||||
|
||||
@@ -148,7 +148,7 @@ function createElementText(ctx: VisualContext, structure: Structure, theme: Them
|
||||
const { label_atom_id, label_alt_id } = StructureProperties.atom;
|
||||
const { cumulativeUnitElementCount } = serialMapping;
|
||||
|
||||
const sizeTheme = PhysicalSizeTheme({}, {});
|
||||
const sizeTheme = PhysicalSizeTheme({}, { scale: 1 });
|
||||
|
||||
const count = structure.elementCount;
|
||||
const { elementScale } = props;
|
||||
|
||||
@@ -21,11 +21,12 @@ import { EmptyLoci, Loci } from '../../../mol-model/loci';
|
||||
import { UnitIndex } from '../../../mol-model/structure/structure/element/element';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { MoleculeType } from '../../../mol-model/structure/model/types';
|
||||
import { BaseGeometry } from '../../../mol-geo/geometry/base';
|
||||
|
||||
export const OrientationEllipsoidMeshParams = {
|
||||
...UnitsMeshParams,
|
||||
sizeFactor: PD.Numeric(1, { min: 0, max: 2, step: 0.1 }),
|
||||
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }),
|
||||
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
|
||||
};
|
||||
export type OrientationEllipsoidMeshParams = typeof OrientationEllipsoidMeshParams
|
||||
|
||||
@@ -80,7 +81,7 @@ export function createOrientationEllipsoidMesh(ctx: VisualContext, unit: Unit, s
|
||||
const radiusScale = Vec3.create(size[2], size[1], size[0]);
|
||||
|
||||
builderState.currentGroup = 0;
|
||||
addEllipsoid(builderState, origin, dirA, dirB, radiusScale, detail);
|
||||
addEllipsoid(builderState, origin, dirA, dirB, radiusScale, detail + 1);
|
||||
|
||||
const m = MeshBuilder.getMesh(builderState);
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ export function getUnitConformationAndRadius(structure: Structure, unit: Unit, p
|
||||
const boundary = unit === rootUnit ? unit.boundary : getBoundary(position);
|
||||
|
||||
const l = StructureElement.Location.create(structure, rootUnit);
|
||||
const sizeTheme = PhysicalSizeTheme({}, {});
|
||||
const sizeTheme = PhysicalSizeTheme({}, { scale: 1 });
|
||||
const radius = (index: number) => {
|
||||
l.element = index as ElementIndex;
|
||||
return sizeTheme.size(l);
|
||||
@@ -186,7 +186,7 @@ export function getUnitConformationAndRadius(structure: Structure, unit: Unit, p
|
||||
|
||||
export function getStructureConformationAndRadius(structure: Structure, ignoreHydrogens: boolean, traceOnly: boolean) {
|
||||
const l = StructureElement.Location.create(structure);
|
||||
const sizeTheme = PhysicalSizeTheme({}, {});
|
||||
const sizeTheme = PhysicalSizeTheme({}, { scale: 1 });
|
||||
|
||||
let xs: ArrayLike<number>;
|
||||
let ys: ArrayLike<number>;
|
||||
|
||||
@@ -51,6 +51,7 @@ export interface QualityProps {
|
||||
linearSegments: number
|
||||
resolution: number
|
||||
doubleSided: boolean
|
||||
alpha: number
|
||||
}
|
||||
|
||||
export const DefaultQualityThresholds = {
|
||||
@@ -151,6 +152,10 @@ export function getQualityProps(props: Partial<QualityProps>, data?: any) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (props.alpha !== undefined && props.alpha < 1) {
|
||||
doubleSided = false;
|
||||
}
|
||||
|
||||
return {
|
||||
detail,
|
||||
radialSegments,
|
||||
|
||||
@@ -145,7 +145,8 @@ const str = {
|
||||
|
||||
const list = {
|
||||
'@header': 'Lists',
|
||||
getAt: symbol(Arguments.Dictionary({ 0: Argument(Types.List()), 1: Argument(Type.Num) }), Types.AnyVar)
|
||||
getAt: symbol(Arguments.Dictionary({ 0: Argument(Types.List()), 1: Argument(Type.Num) }), Types.AnyVar),
|
||||
equal: symbol(Arguments.Dictionary({ 0: Argument(Types.List()), 1: Argument(Types.List()) }), Type.Bool)
|
||||
};
|
||||
|
||||
const set = {
|
||||
|
||||
@@ -308,6 +308,7 @@ const atomProperty = {
|
||||
|
||||
entityType: atomProp(Types.EntityType, 'Type of the entity as defined in mmCIF (polymer, non-polymer, branched, water)'),
|
||||
entitySubtype: atomProp(Types.EntitySubtype, 'Subtype of the entity as defined in mmCIF _entity_poly.type and _pdbx_entity_branch.type (other, polypeptide(D), polypeptide(L), polydeoxyribonucleotide, polyribonucleotide, polydeoxyribonucleotide/polyribonucleotide hybrid, cyclic-pseudo-peptide, peptide nucleic acid, oligosaccharide)'),
|
||||
entityDescription: atomProp(Core.Types.List(Type.Str)),
|
||||
objectPrimitive: atomProp(Types.ObjectPrimitive, 'Type of the primitive object used to model this segment as defined in mmCIF/IHM (atomistic, sphere, gaussian, other)'),
|
||||
|
||||
secondaryStructureKey: atomProp(Type.AnyValue, 'Unique value for each secondary structure element.'),
|
||||
|
||||
@@ -14,6 +14,7 @@ import { upperCaseAny } from '../../../mol-util/string';
|
||||
import { VdwRadius, AtomWeight, AtomNumber } from '../../../mol-model/structure/model/properties/atomic';
|
||||
import { cantorPairing } from '../../../mol-data/util';
|
||||
import { bundleElementImpl, bundleGenerator } from '../../../mol-model/structure/query/queries/internal';
|
||||
import { arrayEqual } from '../../../mol-util/array';
|
||||
|
||||
const C = QuerySymbolRuntime.Const;
|
||||
const D = QuerySymbolRuntime.Dynamic;
|
||||
@@ -151,6 +152,7 @@ const symbols = [
|
||||
|
||||
// ============= LIST ================
|
||||
C(MolScript.core.list.getAt, (ctx, v) => v[0](ctx)[v[1](ctx)]),
|
||||
C(MolScript.core.list.equal, (ctx, v) => arrayEqual(v[0](ctx), v[1](ctx))),
|
||||
|
||||
// ============= SET ================
|
||||
C(MolScript.core.set.has, function core_set_has(ctx, v) { return v[0](ctx).has(v[1](ctx)); }),
|
||||
@@ -334,6 +336,7 @@ const symbols = [
|
||||
|
||||
D(MolScript.structureQuery.atomProperty.macromolecular.entityType, atomProp(StructureProperties.entity.type)),
|
||||
D(MolScript.structureQuery.atomProperty.macromolecular.entitySubtype, atomProp(StructureProperties.entity.subtype)),
|
||||
D(MolScript.structureQuery.atomProperty.macromolecular.entityDescription, atomProp(StructureProperties.entity.pdbx_description)),
|
||||
D(MolScript.structureQuery.atomProperty.macromolecular.objectPrimitive, atomProp(StructureProperties.unit.object_primitive)),
|
||||
|
||||
D(MolScript.structureQuery.atomProperty.macromolecular.isNonStandard, atomProp(StructureProperties.residue.isNonStandard)),
|
||||
|
||||
@@ -234,6 +234,7 @@ export const SymbolTable = [
|
||||
Alias(MolScript.structureQuery.atomProperty.macromolecular.B_iso_or_equiv, 'atom.B_iso_or_equiv', 'atom.bfactor'),
|
||||
Alias(MolScript.structureQuery.atomProperty.macromolecular.entityType, 'atom.entity-type'),
|
||||
Alias(MolScript.structureQuery.atomProperty.macromolecular.entitySubtype, 'atom.entity-subtype'),
|
||||
Alias(MolScript.structureQuery.atomProperty.macromolecular.entityDescription, 'atom.entity-description'),
|
||||
Alias(MolScript.structureQuery.atomProperty.macromolecular.objectPrimitive, 'atom.object-primitive'),
|
||||
Alias(MolScript.structureQuery.atomProperty.macromolecular.chemCompType, 'atom.chem-comp-type'),
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ interface StateAction<A extends StateObject = StateObject, T = any, P extends {}
|
||||
readonly id: UUID,
|
||||
readonly definition: StateAction.Definition<A, T, P>,
|
||||
/** create a fresh copy of the params which can be edited in place */
|
||||
createDefaultParams(a: A, globalCtx: unknown): P
|
||||
createDefaultParams(a: A | undefined, globalCtx: unknown): P
|
||||
}
|
||||
|
||||
namespace StateAction {
|
||||
@@ -54,7 +54,7 @@ namespace StateAction {
|
||||
export interface Definition<A extends StateObject = StateObject, T = any, P extends {} = {}> extends DefinitionBase<A, T, P> {
|
||||
readonly from: StateObject.Ctor[],
|
||||
readonly display: { readonly name: string, readonly description?: string },
|
||||
params?(a: A, globalCtx: unknown): { [K in keyof P]: PD.Any }
|
||||
params?(a: A | undefined, globalCtx: unknown): { [K in keyof P]: PD.Any }
|
||||
}
|
||||
|
||||
export function create<A extends StateObject, T, P extends {} = {}>(definition: Definition<A, T, P>): StateAction<A, T, P> {
|
||||
|
||||
@@ -173,7 +173,7 @@ export function _bundleLabel(bundle: Loci.Bundle<any>, options: LabelOptions) {
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
for (let i = 0, il = Math.min(...labels.map(l => l.length)); i < il; ++i) {
|
||||
for (let i = 0, il = Math.min(...labels.map(l => l.length)) - 1; i < il; ++i) {
|
||||
let areIdentical = true;
|
||||
for (let j = 1, jl = labels.length; j < jl; ++j) {
|
||||
if (labels[0][i] !== labels[j][i]) {
|
||||
@@ -330,7 +330,7 @@ export function angleLabel(triple: Loci.Bundle<3>, options: Partial<LabelOptions
|
||||
export function dihedralLabel(quad: Loci.Bundle<4>, options: Partial<LabelOptions & { measureOnly: boolean }> = {}) {
|
||||
const o = { ...DefaultLabelOptions, measureOnly: false, ...options };
|
||||
const [cA, cB, cC, cD] = quad.loci.map(l => Loci.getCenter(l)!);
|
||||
const dihedral = `${radToDeg(Vec3.dihedralAngle(cA, cB, cC, cD)).toFixed(2)}\u00B0`;
|
||||
const dihedral = `${Math.abs(radToDeg(Vec3.dihedralAngle(cA, cB, cC, cD))).toFixed(2)}\u00B0`;
|
||||
if (o.measureOnly) return dihedral;
|
||||
const label = bundleLabel(quad, o);
|
||||
return o.condensed ? `${dihedral} | ${label}` : `Dihedral ${dihedral}</br>${label}`;
|
||||
|
||||
@@ -14,7 +14,9 @@ import { ThemeDataContext } from '../../mol-theme/theme';
|
||||
const DefaultSize = 1;
|
||||
const Description = 'Assigns a physical size, i.e. vdW radius for atoms or given radius for coarse spheres.';
|
||||
|
||||
export const PhysicalSizeThemeParams = {};
|
||||
export const PhysicalSizeThemeParams = {
|
||||
scale: PD.Numeric(1, { min: 0.1, max: 5, step: 0.1 })
|
||||
};
|
||||
export type PhysicalSizeThemeParams = typeof PhysicalSizeThemeParams
|
||||
export function getPhysicalSizeThemeParams(ctx: ThemeDataContext) {
|
||||
return PhysicalSizeThemeParams; // TODO return copy
|
||||
@@ -35,14 +37,16 @@ export function getPhysicalRadius(unit: Unit, element: ElementIndex): number {
|
||||
* i.e. vdw for atoms and radius for coarse spheres
|
||||
*/
|
||||
export function PhysicalSizeTheme(ctx: ThemeDataContext, props: PD.Values<PhysicalSizeThemeParams>): SizeTheme<PhysicalSizeThemeParams> {
|
||||
const scale = props.scale === void 0 ? 1 : props.scale;
|
||||
|
||||
function size(location: Location): number {
|
||||
let size: number;
|
||||
if (StructureElement.Location.is(location)) {
|
||||
size = getPhysicalRadius(location.unit, location.element);
|
||||
size = scale * getPhysicalRadius(location.unit, location.element);
|
||||
} else if (Bond.isLocation(location)) {
|
||||
size = getPhysicalRadius(location.aUnit, location.aUnit.elements[location.aIndex]);
|
||||
size = scale * getPhysicalRadius(location.aUnit, location.aUnit.elements[location.aIndex]);
|
||||
} else {
|
||||
size = DefaultSize;
|
||||
size = scale * DefaultSize;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ export function arraySetRemove<T>(xs: T[], x: T) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function arrayEqual<T>(xs?: T[], ys?: T[]) {
|
||||
export function arrayEqual<T>(xs?: ArrayLike<T>, ys?: ArrayLike<T>) {
|
||||
if (!xs || xs.length === 0) return !ys || ys.length === 0;
|
||||
if (!ys) return false;
|
||||
|
||||
|
||||
@@ -241,9 +241,8 @@ function processAjax<T extends DataType>(req: XMLHttpRequest, type: T): DataResp
|
||||
}
|
||||
throw new Error(`could not get requested response data '${type}'`);
|
||||
} else {
|
||||
const status = req.statusText;
|
||||
RequestPool.deposit(req);
|
||||
throw new Error(status);
|
||||
throw new Error(`Download failed with status code ${req.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user