Compare commits

...

37 Commits

Author SHA1 Message Date
Alexander Rose
a6605052db 0.7.0-dev.16 2020-04-27 19:54:37 -07:00
Alexander Rose
8514175da2 add build/viewer/ to published files 2020-04-27 19:53:01 -07:00
Alexander Rose
6a49427fc0 0.7.0-dev.15 2020-04-27 19:44:01 -07:00
Alexander Rose
7c18e5eb86 refactored viewer app to make it usable for simple embedded use-cases 2020-04-27 19:41:43 -07:00
Alexander Rose
2a7d258715 add Download providers to Config 2020-04-27 19:32:42 -07:00
Alexander Rose
54fb9beeee add State.Snapshots.OpenUrl 2020-04-27 19:31:55 -07:00
Alexander Rose
27ebbc50d5 allow animation button to be hidden 2020-04-27 19:31:07 -07:00
Alexander Rose
2a1b6e52b2 be clear that StateAction.params need to work without data object 2020-04-27 19:29:55 -07:00
David Sehnal
3110e82d92 0.7.0-dev.14 2020-04-28 01:03:33 +02:00
David Sehnal
4be999ce32 StructureMeasurementManagerState add options 2020-04-28 01:01:17 +02:00
David Sehnal
f0d7a4ed2a custom label for distance/angle/dihedral 2020-04-27 18:15:43 +02:00
David Sehnal
2dacfcb485 Dihedrals: fix arcs and labels 2020-04-27 18:08:22 +02:00
David Sehnal
6218cc5371 PluginComponent.subscribe returns the subscription 2020-04-27 17:24:58 +02:00
David Sehnal
056ce42097 mol-plugin: canvas3d.initialized event/behavior fix 2020-04-27 03:25:58 +02:00
David Sehnal
b14b5ca626 0.7.0-dev.13 2020-04-27 02:05:52 +02:00
David Sehnal
ffbaa944f2 tweak publish scripts 2020-04-27 02:03:47 +02:00
David Sehnal
e2ba96174a Merge branch 'master' of https://github.com/molstar/molstar 2020-04-27 02:02:31 +02:00
David Sehnal
8c5d99bb54 higher quality logo, add link, move to corner 2020-04-27 02:02:21 +02:00
Alexander Rose
b18b3be070 basic mol2 format support 2020-04-26 14:08:19 -07:00
Alexander Rose
2e69b7c419 CubeProvider tweaks, check if orbitals 2020-04-26 13:06:35 -07:00
Alexander Rose
5007f5fb72 added VolumeData.sourceData: ModelFormat 2020-04-26 13:05:39 -07:00
Alexander Rose
6fe83a9a70 cellpack: fixed pdb fallback when opm fails 2020-04-26 12:27:30 -07:00
Alexander Rose
20af084127 increate outline threshold max value 2020-04-26 11:49:54 -07:00
Alexander Rose
d6501170e6 Merge branch 'master' of https://github.com/molstar/molstar 2020-04-26 11:34:29 -07:00
Alexander Rose
5f33364514 ignore pickable=false renderObjects completely 2020-04-26 11:34:07 -07:00
David Sehnal
7924c008fa proteopedia-wrapper: return result of snapshot.fetch 2020-04-26 20:32:12 +02:00
David Sehnal
2d2a53f28e 0.7.0-dev.12 2020-04-26 19:00:27 +02:00
David Sehnal
1f7ffabef9 added PhysicalSizeTheme.scale 2020-04-26 18:59:17 +02:00
David Sehnal
16d5c07224 0.7.0-dev.11 2020-04-26 18:04:53 +02:00
David Sehnal
2392bfb579 ParamDefinition.mergeParam fix 2020-04-26 17:54:42 +02:00
David Sehnal
b4036f576c proteopedia-wrapper tweaks 2020-04-26 13:19:10 +02:00
Alexander Rose
690d6812dc cellpack: simple cache to avoid parsing trajectories more than once 2020-04-26 00:56:03 -07:00
Alexander Rose
a44aa02f13 cellpack: support for loading zip files containing model.json and ingredients 2020-04-25 23:50:27 -07:00
Alexander Rose
65ddd6d68a Mol file description and extension tweaks 2020-04-25 15:56:13 -07:00
Alexander Rose
754025b3b1 fix bond label between identically named elements/atoms 2020-04-25 13:11:17 -07:00
Alexander Rose
f0649c5aa3 improved structure selection query labels 2020-04-25 12:51:08 -07:00
Alexander Rose
6df045211c fixed atomicDetail repr preset 2020-04-25 12:50:32 -07:00
83 changed files with 904 additions and 442 deletions

2
package-lock.json generated
View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.7.0-dev.10",
"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",

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,6 @@ uniform vec3 uFogColor;
uniform float uAlpha;
uniform float uPickingAlphaThreshold;
uniform int uPickable;
uniform int uTransparentBackground;
uniform float uInteriorDarkening;

View File

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

View File

@@ -99,18 +99,14 @@ void main() {
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.05)
discard;

View File

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

View File

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

View File

@@ -63,5 +63,6 @@ export interface Mol2Structure {
}
export interface Mol2File {
name: string
structures: Mol2Structure[]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,10 +9,16 @@ import { OrderedSet } from '../../mol-data/int';
import { Sphere3D } from '../../mol-math/geometry';
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
import { CubeFormat } from '../../mol-model-formats/volume/cube';
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'; }

View File

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

View File

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

View File

@@ -237,7 +237,13 @@ const atomicDetail = StructureRepresentationPresetProvider({
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 = {
@@ -245,7 +251,7 @@ const atomicDetail = StructureRepresentationPresetProvider({
};
if (params.showCarbohydrateSymbol) {
Object.assign(representations, {
snfg3d: builder.buildRepresentation(update, components.all, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color }, { tag: 'snfg-3d' }),
snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color }, { tag: 'snfg-3d' }),
});
}

View File

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

View File

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

View File

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

View File

@@ -394,63 +394,68 @@ const wholeResidues = StructureSelectionQuery('Whole Residues of Selection', MS.
});
const StandardAminoAcids = [
[['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'],
[['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'],
[['N', 'DN'], 'UNKNOWN'],
[['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][];
export function ResidueQuery([names, label]: [string[], string], category: string, priority = 0) {
return StructureSelectionQuery(`${label} (${names.join(', ')})`, MS.struct.modifier.union([
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, priority, description: label });
]), { category, priority, description });
}
export function ElementSymbolQuery([names, label]: [string[], string], category: string, priority: number) {
return StructureSelectionQuery(`${label} (${names.join(', ')})`, MS.struct.modifier.union([
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 });
]), { category, priority, description });
}
export function EntityDescriptionQuery([description, label]: [string[], string], category: string, priority: number) {
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(...description), MS.ammp('entityDescription')])
'entity-test': MS.core.list.equal([MS.list(...names), MS.ammp('entityDescription')])
})
]), { category, priority, description: description.join(', ') });
]), { category, priority, description });
}
const StandardResidues = SetUtils.unionMany(

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -166,4 +166,6 @@ namespace PluginState {
structureFocus?: StructureFocusSnapshot,
durationInMs?: number
}
export type SnapshotType = 'json' | 'molj' | 'zip' | 'molx'
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -271,7 +271,7 @@ export namespace ParamDefinition {
getLabel(t: T): string
}
export function ObjectList<T>(element: For<T>, getLabel: (e: T) => string, info?: Info & { defaultValue?: T[], ctor?: () => T }): ObjectList<Normalize<T>> {
return setInfo<ObjectList<Normalize<T>>>({ type: 'object-list', element: element as any as Params, getLabel, ctor: _defaultObjectListCtor, defaultValue: (info?.defaultValue) || [] }, info);
return setInfo<ObjectList<Normalize<T>>>({ type: 'object-list', element: element as any as Params, getLabel, ctor: _defaultObjectListCtor, defaultValue: (info?.defaultValue) || [] }, info);
}
function _defaultObjectListCtor(this: ObjectList) { return getDefaultValues(this.element) as any; }
@@ -316,7 +316,7 @@ export namespace ParamDefinition {
export type ValuesFor<T extends For<any>> = Normalize<{ [k in keyof T]: T[k]['defaultValue'] }>
type Optionals<P> = { [K in keyof P]-?: undefined extends P[K] ? K : never }[keyof P]
type NonOptionals<P> = { [K in keyof P]-?: undefined extends P[K] ? never: K }[keyof P]
type NonOptionals<P> = { [K in keyof P]-?: undefined extends P[K] ? never : K }[keyof P]
export type Normalize<P> = Pick<P, NonOptionals<P>> & Partial<Pick<P, Optionals<P>>>
export type For<P> = { [K in keyof P]-?: Base<P[K]> }
export type Def<P> = { [K in keyof P]: Any }
@@ -435,8 +435,8 @@ export namespace ParamDefinition {
}
export function mergeParam(p: Any, a: any, b: any): any {
if (a === undefined) return typeof b === 'object' ? { ...b } : b;
if (b === undefined) return typeof a === 'object' ? { ...a } : a;
if (a === undefined) return typeof b === 'object' && !Array.isArray(b) ? { ...b } : b;
if (b === undefined) return typeof a === 'object' && !Array.isArray(a) ? { ...a } : a;
if (p.type === 'group') {
return merge(p.params, a, b);
@@ -448,7 +448,12 @@ export namespace ParamDefinition {
name: v.name,
params: mergeParam(map, u.params, v.params)
};
} else if (p.type === 'value') {
return b;
} else if (typeof a === 'object' && typeof b === 'object') {
if (Array.isArray(b)) {
return b;
}
return { ...a, ...b };
} else {
return b;

View File

@@ -43,7 +43,7 @@ const sharedConfig = {
// __VERSION_TIMESTAMP__: webpack.DefinePlugin.runtimeValue(() => `${new Date().valueOf()}`, true),
'process.env.DEBUG': JSON.stringify(process.env.DEBUG)
}),
new MiniCssExtractPlugin({ filename: 'app.css' }),
new MiniCssExtractPlugin({ filename: 'molstar.css', }),
new VersionFile({
extras: { timestamp: `${new Date().valueOf()}` },
packageFile: path.resolve(__dirname, 'package.json'),
@@ -73,11 +73,11 @@ function createEntry(src, outFolder, outFilename, isNode) {
}
}
function createEntryPoint(name, dir, out) {
function createEntryPoint(name, dir, out, library) {
return {
node: { fs: 'empty' }, // TODO find better solution? Currently used in file-handle.ts
entry: path.resolve(__dirname, `lib/${dir}/${name}.js`),
output: { filename: `${name}.js`, path: path.resolve(__dirname, `build/${out}`) },
output: { filename: `${library || name}.js`, path: path.resolve(__dirname, `build/${out}`), library: library || out, libraryTarget: 'umd' },
...sharedConfig
}
}
@@ -97,7 +97,7 @@ function createNodeEntryPoint(name, dir, out) {
}
}
function createApp(name) { return createEntryPoint('index', `apps/${name}`, name) }
function createApp(name, library) { return createEntryPoint('index', `apps/${name}`, name, library) }
function createExample(name) { return createEntry(`examples/${name}/index`, `examples/${name}`, 'index') }
function createBrowserTest(name) { return createEntryPoint(name, 'tests/browser', 'tests') }
function createNodeApp(name) { return createNodeEntryPoint('index', `apps/${name}`, name) }

View File

@@ -1,5 +1,5 @@
const common = require('./webpack.config.common.js');
const createApp = common.createApp;
module.exports = [
createApp('viewer')
createApp('viewer', 'molstar')
]