Compare commits

...

64 Commits

Author SHA1 Message Date
Alexander Rose
590c00114f 0.3.8 2019-10-25 15:48:20 -07:00
Alexander Rose
111eded34a disable some ui elements when state tree is busy 2019-10-25 12:18:46 -07:00
Alexander Rose
d767900fb1 fix, canvas.render not always returning true when it did render 2019-10-25 12:06:14 -07:00
Alexander Rose
9074d5390a add support for sequence numbers in coarse units 2019-10-24 16:28:06 -07:00
Alexander Rose
91027459ca typos 2019-10-24 15:02:23 -07:00
Alexander Rose
a2eda6c5af added controls display options: outside, portrait, landscape, reactive 2019-10-24 11:32:00 -07:00
Alexander Rose
338489febd 0.3.7 2019-10-24 07:54:31 -07:00
David Sehnal
94e7820baf mol-data: SortedArray fix 2019-10-24 07:40:35 +02:00
Alexander Rose
a47df57709 more SortedArray.union tests (one failing) 2019-10-23 19:32:41 -07:00
Alexander Rose
f73d64f732 0.3.6 2019-10-23 18:15:46 -07:00
Alexander Rose
cfaef4c567 added more volume param docs 2019-10-23 18:14:16 -07:00
Alexander Rose
3ec2c6ded4 ui layout tweaks 2019-10-23 17:18:16 -07:00
Alexander Rose
2afaa35170 increased default wireframe thickness from 1 to 1.5 2019-10-23 17:17:34 -07:00
Alexander Rose
efcf4a77c6 add molecular surface wireframe visual 2019-10-23 17:16:40 -07:00
Alexander Rose
221e1de4e7 improved entity-source color theme 2019-10-23 12:25:59 -07:00
Alexander Rose
f2de2983c8 added isInteger helper 2019-10-23 12:25:38 -07:00
Alexander Rose
82c8499789 updated cif schemas 2019-10-23 11:43:19 -07:00
David Sehnal
d66ccdb255 mol-plugin: sequence view tweaks 2019-10-23 09:27:59 +02:00
David Sehnal
8d13c514b0 mol-data: SortedArray fix 2019-10-23 09:17:25 +02:00
Alexander Rose
f57aafba19 improved sequence numbers in sequence widget UI 2019-10-22 17:25:57 -07:00
Alexander Rose
0a7406db15 added more SortedArray.union tests, one failing 2019-10-22 15:17:18 -07:00
Alexander Rose
2f97e8b329 overset residue numbers in sequence ui 2019-10-22 12:24:53 -07:00
David Sehnal
22a8758852 mol-data: fixed SortedArray.union 2019-10-22 20:48:16 +02:00
Alexander Rose
bc6bc1d57a fix StructureElement.Stats 2019-10-22 10:22:02 -07:00
Alexander Rose
4a692b9a88 ensure applicable repr types are up-to-date 2019-10-22 10:20:55 -07:00
Alexander Rose
0094f800dc fixed entity-source color theme labels 2019-10-21 18:18:26 -07:00
Alexander Rose
9bd616f36f ensure file extension for image download 2019-10-21 17:59:09 -07:00
David Sehnal
4acc36628d mol-plugin: show sequence offsets in UI (wip) 2019-10-21 15:06:58 +02:00
David Sehnal
e550413778 mol-state: added createDefaultParams to StateAction and StateTransformer 2019-10-21 13:45:18 +02:00
David Sehnal
ccdc894979 ability to set default params for volume streaming 2019-10-21 13:39:05 +02:00
David Sehnal
322bc71d52 changed default color list to dark-2 to avoid a clash with EM volume map colors 2019-10-21 12:49:04 +02:00
Alexander Rose
7c55e4d234 0.3.5 2019-10-18 17:02:14 -07:00
Alexander Rose
8c8058290c fixed spacegroup index/number handling 2019-10-18 16:58:53 -07:00
David Sehnal
9d413bf0eb mol-util: palette fix missing valueLabel function 2019-10-18 15:29:45 +02:00
Alexander Rose
8fde5dd1bf 0.3.4 2019-10-17 11:47:10 -07:00
Alexander Rose
2f9ecb9396 package updates 2019-10-17 11:46:26 -07:00
Alexander Rose
85092279fa better handle unsupported extensions 2019-10-17 11:40:32 -07:00
Alexander Rose
d0189523e4 fix Loci stats for partial residue selections 2019-10-17 11:10:10 -07:00
Alexander Rose
a26d03205a tweak bindings to work better with one-button mouse 2019-10-16 18:26:22 -07:00
Alexander Rose
862eda6dd4 better min size limit in image ui 2019-10-16 17:54:46 -07:00
Alexander Rose
4a7f0fc206 hide non-applicable repr types in repr ui 2019-10-16 17:46:27 -07:00
Alexander Rose
6bbee58d39 guard against undefined values in ParameterControls 2019-10-16 16:42:14 -07:00
Alexander Rose
989faed410 added StructureSelectionQuery wrapping expr & query 2019-10-16 16:30:38 -07:00
Alexander Rose
198e2f2043 support for currentSelection in StructureSelectionHelper and surroundings query 2019-10-16 15:48:39 -07:00
Alexander Rose
6ae3d4110d added StructureElement.Loci.toStructure 2019-10-16 15:47:57 -07:00
David Sehnal
c34e1be43b Merge branch 'master' of https://github.com/molstar/molstar 2019-10-16 20:46:40 +02:00
David Sehnal
17a440ad9c moved StructureQuery.runExpr to Script.getStructureSelection to fix cyclic dep 2019-10-16 20:46:25 +02:00
Alexander Rose
04dcfc7adb check seqId compatibility for cyclic peptides 2019-10-16 11:01:07 -07:00
David Sehnal
ca66e334c1 mol-model: added StructureQuery.runExpr 2019-10-16 13:47:06 +02:00
David Sehnal
54bba4c92f mol-script: added internal.generator.current symbol 2019-10-16 13:38:58 +02:00
David Sehnal
9c046b808c mol-script: added Bundle support 2019-10-16 13:26:05 +02:00
David Sehnal
ca5d1865cc mol-model: Loci fixes 2019-10-16 12:48:21 +02:00
Alexander Rose
d8f1aa5bc9 support for streaming multiple em volumes, limit to associate maps 2019-10-15 17:38:42 -07:00
David Sehnal
6ac790e083 mol-model: renamed StructureSelection.toLoci* 2019-10-15 08:49:06 +02:00
David Sehnal
d0870e4bbb added highlight example to basic wrapper, better empty loci handling 2019-10-15 08:44:50 +02:00
Alexander Rose
5138595f36 added more StructureSelectionQueries 2019-10-14 16:26:54 -07:00
Alexander Rose
d9aa5684a9 show non-polymer residues of polymer entities as ball&stick 2019-10-14 14:09:34 -07:00
Alexander Rose
8e2521a7a9 consider coarse/non-coarse backbone when checking distance for backbone links 2019-10-14 14:09:12 -07:00
Alexander Rose
7dd7a117cb wip, terminal gaps 2019-10-11 16:55:39 -07:00
Alexander Rose
defbadf4d7 handle polymer ends in visuals properly 2019-10-11 16:55:22 -07:00
Alexander Rose
131e88080a 0.3.3 2019-10-10 10:43:52 -07:00
Alexander Rose
fb78b886c1 collapse link labels to hide repeated ids 2019-10-10 10:41:56 -07:00
Alexander Rose
aed0b87b16 fix fractional canvas/image dimensions 2019-10-10 10:35:44 -07:00
Alexander Rose
1316cc6a40 fix StructureSelectionControls.focus for single element 2019-10-09 17:52:59 -07:00
100 changed files with 1616 additions and 672 deletions

62
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.3.2",
"version": "0.3.8",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1345,9 +1345,9 @@
}
},
"@types/jest": {
"version": "24.0.18",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.18.tgz",
"integrity": "sha512-jcDDXdjTcrQzdN06+TSVsPPqxvsZA/5QkYfIZlq1JMw7FdP5AZylbOc+6B/cuDurctRe+MziUMtQ3xQdrbjqyQ==",
"version": "24.0.19",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.19.tgz",
"integrity": "sha512-YYiqfSjocv7lk5H/T+v5MjATYjaTMsUkbDnjGqSMoO88jWdtJXJV4ST/7DKZcoMHMBvB2SeSfyOzZfkxXHR5xg==",
"requires": {
"@types/jest-diff": "*"
}
@@ -1363,9 +1363,9 @@
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw=="
},
"@types/node": {
"version": "12.7.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz",
"integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ=="
"version": "12.11.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz",
"integrity": "sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A=="
},
"@types/node-fetch": {
"version": "2.5.2",
@@ -1392,18 +1392,18 @@
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA=="
},
"@types/react": {
"version": "16.9.5",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.5.tgz",
"integrity": "sha512-jQ12VMiFOWYlp+j66dghOWcmDDwhca0bnlcTxS4Qz/fh5gi6wpaZDthPEu/Gc/YlAuO87vbiUXL8qKstFvuOaA==",
"version": "16.9.9",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.9.tgz",
"integrity": "sha512-L+AudFJkDukk+ukInYvpoAPyJK5q1GanFOINOJnM0w6tUgITuWvJ4jyoBPFL7z4/L8hGLd+K/6xR5uUjXu0vVg==",
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
}
},
"@types/react-dom": {
"version": "16.9.1",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.1.tgz",
"integrity": "sha512-1S/akvkKr63qIUWVu5IKYou2P9fHLb/P2VAwyxVV85JGaGZTcUniMiTuIqM3lXFB25ej6h+CYEQ27ERVwi6eGA==",
"version": "16.9.2",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.2.tgz",
"integrity": "sha512-hgPbBoI1aTSTvZwo8HYw35UaTldW6n2ETLvHAcfcg1FaOuBV3olmyCe5eMpx2WybWMBPv0MdU2t5GOcQhP+3zA==",
"requires": {
"@types/react": "*"
}
@@ -2281,9 +2281,9 @@
}
},
"bluebird": {
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz",
"integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==",
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz",
"integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==",
"dev": true
},
"bn.js": {
@@ -2559,9 +2559,9 @@
"dev": true
},
"yallist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
}
}
@@ -2707,9 +2707,9 @@
}
},
"chownr": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz",
"integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==",
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz",
"integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==",
"dev": true
},
"chrome-trace-event": {
@@ -13280,9 +13280,9 @@
}
},
"swagger-ui-dist": {
"version": "3.23.11",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.23.11.tgz",
"integrity": "sha512-ipENHHH/sqpngTpHXUwg55eAOZ7b2UVayUwwuWPA6nQSPhjBVXX4zOPpNKUwQIFOl3oIwVvZF7mqoxH7pMgVzA=="
"version": "3.24.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.24.0.tgz",
"integrity": "sha512-5uAjeEqV+zbtalBDXAIrkqUZwsUHYwvBSeGYlFcLj1ERS3jfprL4OPLSSriDoeXCtNmWzpz5aooV2qJW+DqdUQ=="
},
"swap-case": {
"version": "1.1.2",
@@ -13324,9 +13324,9 @@
}
},
"terser": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.3.2.tgz",
"integrity": "sha512-obxk4x19Zlzj9zY4QeXj9iPCb5W8YGn4v3pn4/fHj0Nw8+R7N02Kvwvz9VpOItCZZD8RC+vnYCDL0gP6FAJ7Xg==",
"version": "4.3.9",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.3.9.tgz",
"integrity": "sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==",
"dev": true,
"requires": {
"commander": "^2.20.0",
@@ -14526,9 +14526,9 @@
"dev": true
},
"webpack": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.0.tgz",
"integrity": "sha512-yNV98U4r7wX1VJAj5kyMsu36T8RPPQntcb5fJLOsMz/pt/WrKC0Vp1bAlqPLkA1LegSwQwf6P+kAbyhRKVQ72g==",
"version": "4.41.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.2.tgz",
"integrity": "sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.8.5",

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.3.2",
"version": "0.3.8",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -87,7 +87,7 @@
"ts-jest": "^24.1.0",
"tslint": "^5.20.0",
"typescript": "^3.6.4",
"webpack": "^4.41.0",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.9"
},
"dependencies": {
@@ -95,11 +95,11 @@
"@types/benchmark": "^1.0.31",
"@types/compression": "1.0.1",
"@types/express": "^4.17.1",
"@types/jest": "^24.0.18",
"@types/node": "^12.7.12",
"@types/jest": "^24.0.19",
"@types/node": "^12.11.1",
"@types/node-fetch": "^2.5.2",
"@types/react": "^16.9.5",
"@types/react-dom": "^16.9.1",
"@types/react": "^16.9.9",
"@types/react-dom": "^16.9.2",
"@types/swagger-ui-dist": "3.0.3",
"@types/webgl2": "0.0.5",
"argparse": "^1.0.10",
@@ -111,7 +111,7 @@
"react": "^16.10.2",
"react-dom": "^16.10.2",
"rxjs": "^6.5.3",
"swagger-ui-dist": "^3.23.11",
"swagger-ui-dist": "^3.24.0",
"util.promisify": "^1.0.0",
"xhr2": "^0.2.0"
}

View File

@@ -105,6 +105,10 @@
addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes());
addHeader('Interactivity');
addControl('Highlight seq_id=7', () => BasicMolStarWrapper.interactivity.highlightOn());
addControl('Clear Highlight', () => BasicMolStarWrapper.interactivity.clearHighlight());
addHeader('Tests');
addControl('Static Superposition', () => BasicMolStarWrapper.tests.staticSuperposition());

View File

@@ -11,14 +11,16 @@ import { PluginCommands } from '../../mol-plugin/command';
import { StateTransforms } from '../../mol-plugin/state/transforms';
import { StructureRepresentation3DHelpers } from '../../mol-plugin/state/transforms/representation';
import { Color } from '../../mol-util/color';
import { PluginStateObject as PSO } from '../../mol-plugin/state/objects';
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin/state/objects';
import { AnimateModelIndex } from '../../mol-plugin/state/animation/built-in';
import { StateBuilder, StateTransform } from '../../mol-state';
import { StripedResidues } from './coloring';
// import { BasicWrapperControls } from './controls';
import { StaticSuperpositionTestData, buildStaticSuperposition, dynamicSuperpositionTest } from './superposition';
import { PDBeStructureQualityReport } from '../../mol-plugin/behavior/dynamic/custom-props';
import { CustomToastMessage } from './controls';
import { EmptyLoci } from '../../mol-model/loci';
import { StructureSelection } from '../../mol-model/structure';
import { Script } from '../../mol-script/script';
require('mol-plugin/skin/light.scss')
type SupportedFormats = 'cif' | 'pdb'
@@ -63,7 +65,7 @@ class BasicWrapper {
}
private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: 'seq' })
.apply(StateTransforms.Representation.StructureRepresentation3D,
StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'cartoon'), { ref: 'seq-visual' });
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
@@ -144,6 +146,22 @@ class BasicWrapper {
}
}
interactivity = {
highlightOn: () => {
const seq_id = 7;
const data = (this.plugin.state.dataState.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data;
const sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
'residue-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), seq_id]),
'group-by': Q.struct.atomProperty.macromolecular.residueKey()
}), data);
const loci = StructureSelection.toLociWithSourceUnits(sel);
this.plugin.interactivity.lociHighlights.highlightOnly({ loci });
},
clearHighlight: () => {
this.plugin.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
}
}
tests = {
staticSuperposition: async () => {
const state = this.plugin.state.dataState;

View File

@@ -86,7 +86,7 @@ export async function dynamicSuperpositionTest(ctx: PluginContext, src: string[]
const query = compile<StructureSelection>(pivot);
const xs = state.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure));
const selections = xs.map(s => StructureSelection.toLoci(query(new QueryContext(s.obj!.data))));
const selections = xs.map(s => StructureSelection.toLociWithCurrentUnits(query(new QueryContext(s.obj!.data))));
const transforms = superposeStructures(selections);
const visuals = state.build();

View File

@@ -28,7 +28,6 @@ import { BuiltInColorThemes } from '../../mol-theme/color';
import { BuiltInSizeThemes } from '../../mol-theme/size';
import { ColorNames } from '../../mol-util/color/names';
import { InitVolumeStreaming, CreateVolumeStreamingInfo } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
import { ParamDefinition } from '../../mol-util/param-definition';
import { DefaultCanvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d';
// import { Vec3 } from 'mol-math/linear-algebra';
// import { ParamDefinition } from 'mol-util/param-definition';
@@ -315,9 +314,11 @@ class MolStarProteopediaWrapper {
experimentalData = {
init: async (parent: Element) => {
const asm = this.state.select(StateElements.Assembly)[0].obj!;
const params = ParamDefinition.getDefaultValues(InitVolumeStreaming.definition.params!(asm, this.plugin));
params.behaviorRef = StateElements.VolumeStreaming;
const params = InitVolumeStreaming.createDefaultParams(asm, this.plugin);
params.options.behaviorRef = StateElements.VolumeStreaming;
params.defaultView = 'box';
params.options.channelParams['fo-fc(+ve)'] = { wireframe: true };
params.options.channelParams['fo-fc(-ve)'] = { wireframe: true };
await this.plugin.runTask(this.state.applyAction(InitVolumeStreaming, params, StateElements.Assembly));
this.experimentalDataElement = parent;
volumeStreamingControls(this.plugin, parent);

View File

@@ -202,7 +202,7 @@ namespace Canvas3D {
didRender = true
}
return didRender && cameraChanged;
return didRender;
}
let forceNextDraw = false;

View File

@@ -21,15 +21,15 @@ const M = ModifiersKeys
const Trigger = Binding.Trigger
export const DefaultTrackballBindings = {
dragRotate: Binding(Trigger(B.Flag.Primary, M.create()), 'Rotate the 3D scene by dragging using ${trigger}'),
dragRotateZ: Binding(Trigger(B.Flag.Primary, M.create({ shift: true })), 'Rotate the 3D scene around the z-axis by dragging using ${trigger}'),
dragPan: Binding(Trigger(B.Flag.Secondary, M.create()), 'Pan the 3D scene by dragging using ${trigger}'),
dragRotate: Binding([Trigger(B.Flag.Primary, M.create())], 'Rotate the 3D scene by dragging using ${triggers}'),
dragRotateZ: Binding([Trigger(B.Flag.Primary, M.create({ shift: true }))], 'Rotate the 3D scene around the z-axis by dragging using ${triggers}'),
dragPan: Binding([Trigger(B.Flag.Secondary, M.create()), Trigger(B.Flag.Primary, M.create({ control: true }))], 'Pan the 3D scene by dragging using ${triggers}'),
dragZoom: Binding.Empty,
dragFocus: Binding(Trigger(B.Flag.Forth, M.create()), 'Focus the 3D scene by dragging using ${trigger}'),
dragFocusZoom: Binding(Trigger(B.Flag.Auxilary, M.create()), 'Focus and zoom the 3D scene by dragging using ${trigger}'),
dragFocus: Binding([Trigger(B.Flag.Forth, M.create())], 'Focus the 3D scene by dragging using ${triggers}'),
dragFocusZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Focus and zoom the 3D scene by dragging using ${triggers}'),
scrollZoom: Binding(Trigger(B.Flag.Auxilary, M.create()), 'Zoom the 3D scene by scrolling using ${trigger}'),
scrollFocus: Binding(Trigger(B.Flag.Auxilary, M.create({ shift: true })), 'Focus the 3D scene by scrolling using ${trigger}'),
scrollZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Zoom the 3D scene by scrolling using ${triggers}'),
scrollFocus: Binding([Trigger(B.Flag.Auxilary, M.create({ shift: true }))], 'Focus the 3D scene by scrolling using ${triggers}'),
scrollFocusZoom: Binding.Empty,
}

View File

@@ -6,8 +6,8 @@
/** Set canvas size taking `devicePixelRatio` into account */
export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height: number) {
canvas.width = window.devicePixelRatio * width
canvas.height = window.devicePixelRatio * height
canvas.width = Math.round(window.devicePixelRatio * width)
canvas.height = Math.round(window.devicePixelRatio * height)
Object.assign(canvas.style, { width: `${width}px`, height: `${height}px` })
}
@@ -28,7 +28,7 @@ function _canvasToBlob(canvas: HTMLCanvasElement, callback: BlobCallback, type?:
const len = bin.length
const len32 = len >> 2
const a8 = new Uint8Array(len)
const a32 = new Uint32Array( a8.buffer, 0, len32 )
const a32 = new Uint32Array(a8.buffer, 0, len32)
let j = 0
for (let i = 0; i < len32; ++i) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
@@ -65,6 +65,73 @@ describe('sortedArray', () => {
test('intersectionSize', SortedArray.intersectionSize(a1234, a2468), 2);
// console.log(Interval.findPredecessorIndexInInterval(Interval.ofBounds(0, 3), 2, Interval.ofBounds(0, 3)))
// console.log(SortedArray.findPredecessorIndexInInterval(SortedArray.ofSortedArray([0, 1, 2]), 2, Interval.ofBounds(0, 3)))
it('union1', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([830, 831, 832, 833, 834, 836, 837, 838, 839, 840, 841, 842, 843]),
SortedArray.ofSortedArray([835])
),
SortedArray.ofSortedArray([830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843])
)
})
it('union2', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([830, 832, 833]),
SortedArray.ofSortedArray([831])
),
SortedArray.ofSortedArray([830, 831, 832, 833])
)
})
it('union3ab', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([830, 831, 832, 833, 834, 835]),
SortedArray.ofSortedArray([836, 837, 838, 839, 840, 841, 842, 843])
),
SortedArray.ofSortedArray([830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843])
)
})
it('union3ba', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([836, 837, 838, 839, 840, 841, 842, 843]),
SortedArray.ofSortedArray([830, 831, 832, 833, 834, 835])
),
SortedArray.ofSortedArray([830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843])
)
})
it('union4', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([1, 3, 5, 7, 9]),
SortedArray.ofSortedArray([2, 4, 6, 8])
),
SortedArray.ofSortedArray([1, 2, 3, 4, 5, 6, 7, 8, 9])
)
})
it('union5', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([2, 3, 4, 20, 21, 22]),
SortedArray.ofSortedArray([10, 11, 12])
),
SortedArray.ofSortedArray([2, 3, 4, 10, 11, 12, 20, 21, 22])
)
})
it('union6', () => {
compareArrays(
SortedArray.union(
SortedArray.ofSortedArray([768, 769, 770, 771, 772, 773, 774, 775, 776, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 1811, 1812, 1813, 1814, 1815, 1816, 1817, 1818, 1819]),
SortedArray.ofSortedArray([1751, 1752, 1753, 1754, 1755, 1756, 1757, 1758])
),
SortedArray.ofSortedArray([768, 769, 770, 771, 772, 773, 774, 775, 776, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 1751, 1752, 1753, 1754, 1755, 1756, 1757, 1758, 1811, 1812, 1813, 1814, 1815, 1816, 1817, 1818, 1819])
)
})
});

View File

@@ -171,28 +171,30 @@ export function isSubset(a: Nums, b: Nums) {
return equal === lenB;
}
export function union(a: Nums, b: Nums) {
export function union(a: Nums, b: Nums): Nums {
if (a === b) return a;
const lenA = a.length, lenB = b.length;
if (lenA === 0) return b;
if (lenB === 0) return a;
if (a[0] > b[0]) return union(b, a);
const { startI, startJ, endI, endJ } = getSuitableIntersectionRange(a, b);
const commonCount = getCommonCount(a, b, startI, startJ, endI, endJ);
const lenA = a.length, lenB = b.length;
// A === B || B is subset of A ==> A
if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return a;
// A is subset of B ===> B
if (commonCount === lenA) return b;
const indices = new Int32Array(lenA + lenB - commonCount);
let offset = 0;
let i = 0, j = 0, offset = 0;
// insert the "prefixes"
for (let k = 0; k < startI; k++) indices[offset++] = a[k];
for (let k = 0; k < startJ; k++) indices[offset++] = b[k];
for (i = 0; i < startI; i++) indices[offset++] = a[i];
while (j < endJ && a[startI] > b[j]) indices[offset++] = b[j++];
// insert the common part
let i = startI;
let j = startJ;
while (i < endI && j < endJ) {
const x = a[i], y = b[j];
if (x < y) { indices[offset++] = x; i++; }

View File

@@ -54,6 +54,12 @@ export function Pyramid(points: ArrayLike<number>): Primitive {
return builder.getPrimitive()
}
let triangularPyramid: Primitive
export function TriangularPyramid() {
if (!triangularPyramid) triangularPyramid = Pyramid(polygon(3, true))
return triangularPyramid
}
let octagonalPyramid: Primitive
export function OctagonalPyramid() {
if (!octagonalPyramid) octagonalPyramid = Pyramid(polygon(8, true))

View File

@@ -23,7 +23,7 @@ attribute mat4 aTransform;
attribute float aInstance;
attribute float aGroup;
#ifndef dFlatShaded
#if !defined(dFlatShaded) || !defined(enabledStandardDerivatives)
#ifdef dGeoTexture
uniform sampler2D tNormal;
#else
@@ -38,7 +38,7 @@ void main(){
#include assign_marker_varying
#include assign_position
#ifndef dFlatShaded
#if !defined(dFlatShaded) || !defined(enabledStandardDerivatives)
#ifdef dGeoTexture
vec3 normal = readFromTexture(tNormal, aGroup, uGeoTexDim).xyz;
#else

View File

@@ -5,6 +5,7 @@
*/
import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture } from './compat';
import { isDebugMode } from '../../mol-util/debug';
export type WebGLExtensions = {
instancedArrays: COMPAT_instanced_arrays
@@ -37,43 +38,45 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
}
const standardDerivatives = getStandardDerivatives(gl)
if (standardDerivatives === null) {
// TODO handle non-support downstream (e.g. no flat shading)
// throw new Error('Could not find support for "standard_derivatives"')
if (isDebugMode && standardDerivatives === null) {
// - non-support handled downstream (flat shading option is ignored)
// - can't be a required extension because it is not supported by `headless-gl`
console.log('Could not find support for "standard_derivatives"')
}
const textureFloatLinear = getTextureFloatLinear(gl)
if (textureFloatLinear === null) {
if (isDebugMode && textureFloatLinear === null) {
// TODO handle non-support downstream (no gpu gaussian calc, no gpu mc???)
// throw new Error('Could not find support for "texture_float_linear"')
// - can't be a required extension because it is not supported by `headless-gl`
console.log('Could not find support for "texture_float_linear"')
}
const depthTexture = getDepthTexture(gl)
if (depthTexture === null) {
if (isDebugMode && depthTexture === null) {
console.log('Could not find support for "depth_texture"')
}
const blendMinMax = getBlendMinMax(gl)
if (blendMinMax === null) {
if (isDebugMode && blendMinMax === null) {
// TODO handle non-support downstream (e.g. no gpu gaussian calc)
// - can't be a required extension because it is not supported by `headless-gl`
console.log('Could not find support for "blend_minmax"')
}
const vertexArrayObject = getVertexArrayObject(gl)
if (vertexArrayObject === null) {
if (isDebugMode && vertexArrayObject === null) {
console.log('Could not find support for "vertex_array_object"')
}
const fragDepth = getFragDepth(gl)
if (fragDepth === null) {
if (isDebugMode && fragDepth === null) {
console.log('Could not find support for "frag_depth"')
}
const colorBufferFloat = getColorBufferFloat(gl)
if (colorBufferFloat === null) {
if (isDebugMode && colorBufferFloat === null) {
console.log('Could not find support for "color_buffer_float"')
}
const drawBuffers = getDrawBuffers(gl)
if (drawBuffers === null) {
if (isDebugMode && drawBuffers === null) {
console.log('Could not find support for "draw_buffers"')
}
const shaderTextureLod = getShaderTextureLod(gl)
if (shaderTextureLod === null) {
if (isDebugMode && shaderTextureLod === null) {
console.log('Could not find support for "shader_texture_lod"')
}

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.314, IHM 1.01, CARB draft.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.317, IHM 1.04, CARB draft.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.314, IHM 1.01, CARB draft.
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.317, IHM 1.04, CARB draft.
*
* @author molstar/ciftools package
*/

View File

@@ -1,7 +1,7 @@
/**
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.314, IHM 1.01, CARB draft.
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.317, IHM 1.04, CARB draft.
*
* @author molstar/ciftools package
*/
@@ -142,12 +142,12 @@ export const mmCIF_Schema = {
*/
id: int,
/**
* A component of the identifier for this atom site.
* For further details, see the definition of the ATOM_SITE_ALT
* category.
*
* This data item is a pointer to _atom_sites_alt.id in the
* ATOM_SITES_ALT category.
* A place holder to indicate alternate conformation. The alternate conformation
* can be an entire polymer chain, or several residues or
* partial residue (several atoms within one residue). If
* an atom is provided in more than one position, then a
* non-blank alternate location indicator must be used for
* each of the atomic positions.
*/
label_alt_id: str,
/**
@@ -2215,15 +2215,18 @@ export const mmCIF_Schema = {
* This data item is a pointer to _entity.id in the ENTITY category.
*/
entity_id: str,
/**
* Scientific name of the organism of the natural source.
*/
pdbx_organism_scientific: str,
/**
* The plasmid containing the gene.
*/
pdbx_plasmid_name: str,
/**
* This data item is an ordinal identifier for entity_src_nat data records.
*/
pdbx_src_id: int,
/**
* This data item identifies cases in which an alternative source
* modeled.
*/
pdbx_alt_source_flag: Aliased<'sample' | 'model'>(str),
/**
* The beginning polymer sequence position for the polymer section corresponding
* to this source.
@@ -2257,19 +2260,22 @@ export const mmCIF_Schema = {
* Identifies the gene.
*/
pdbx_gene_src_gene: List(',', x => x),
/**
* Scientific name of the organism.
*/
pdbx_gene_src_scientific_name: str,
/**
* The name of the plasmid that produced the entity in the host
* organism. Where full details of the protein production are available
* it would be expected that this item would be derived from
* _pdbx_construct.name of the construct pointed to from
* _entity_src_gen_express.plasmid_id.
*/
plasmid_name: str,
/**
* This data item is an ordinal identifier for entity_src_gen data records.
*/
pdbx_src_id: int,
/**
* This data item identifies cases in which an alternative source
* modeled.
*/
pdbx_alt_source_flag: Aliased<'sample' | 'model'>(str),
/**
* This data item povides additional information about the sequence type.
*/
pdbx_seq_type: Aliased<'N-terminal tag' | 'C-terminal tag' | 'Biological sequence' | 'Linker'>(str),
/**
* The beginning polymer sequence position for the polymer section corresponding
* to this source.
@@ -2290,6 +2296,11 @@ export const mmCIF_Schema = {
* about chemically synthesized molecules.
*/
pdbx_entity_src_syn: {
/**
* The scientific name of the organism from which the sequence of
* the synthetic entity was derived.
*/
organism_scientific: str,
/**
* This data item is a pointer to _entity.id in the ENTITY category.
*/
@@ -2298,11 +2309,6 @@ export const mmCIF_Schema = {
* This data item is an ordinal identifier for pdbx_entity_src_syn data records.
*/
pdbx_src_id: int,
/**
* This data item identifies cases in which an alternative source
* modeled.
*/
pdbx_alt_source_flag: Aliased<'sample' | 'model'>(str),
/**
* The beginning polymer sequence position for the polymer section corresponding
* to this source.
@@ -3050,7 +3056,7 @@ export const mmCIF_Schema = {
/**
* The type of data held in the dataset.
*/
data_type: Aliased<'NMR data' | '3DEM volume' | '2DEM class average' | 'EM raw micrographs' | 'SAS data' | 'CX-MS data' | 'Mass Spectrometry data' | 'EPR data' | 'H/D exchange data' | 'Single molecule FRET data' | 'Experimental model' | 'Comparative model' | 'Integrative model' | 'De Novo model' | 'Predicted contacts' | 'Mutagenesis data' | 'DNA footprinting data' | 'Hydroxyl radical footprinting data' | 'Yeast two-hybrid screening data' | 'Other'>(str),
data_type: Aliased<'NMR data' | '3DEM volume' | '2DEM class average' | 'EM raw micrographs' | 'SAS data' | 'CX-MS data' | 'Mass Spectrometry data' | 'EPR data' | 'H/D exchange data' | 'Single molecule FRET data' | 'Experimental model' | 'Comparative model' | 'Integrative model' | 'De Novo model' | 'Predicted contacts' | 'Mutagenesis data' | 'DNA footprinting data' | 'Hydroxyl radical footprinting data' | 'Yeast two-hybrid screening data' | 'Quantitative measurements of genetic interactions' | 'Other'>(str),
/**
* A flag that indicates whether the dataset is archived in
* an IHM related database or elsewhere.
@@ -3225,7 +3231,7 @@ export const mmCIF_Schema = {
*/
file_size_bytes: float,
/**
* Textual description of what the external file is.
* Additional textual details regarding the external file.
*/
details: str,
},
@@ -3459,7 +3465,7 @@ export const mmCIF_Schema = {
/**
* The type of crosslinker used.
*/
linker_type: Aliased<'EDC' | 'DSS' | 'EGS' | 'BS3' | 'BS2G' | 'DST' | 'sulfo-SDA' | 'sulfo-SMCC' | 'DSSO' | 'Other'>(str),
linker_type: Aliased<'EDC' | 'DSS' | 'EGS' | 'BS3' | 'BS2G' | 'DST' | 'sulfo-SDA' | 'sulfo-SMCC' | 'DSSO' | 'DSG' | 'BSP' | 'Other'>(str),
/**
* Identifier to the crosslinking dataset.
* This data item is a pointer to the _ihm_dataset_list.id in the

View File

@@ -6,11 +6,11 @@
*/
import { Vec3, Mat4 } from '../../linear-algebra'
import { SpacegroupName, TransformData, GroupData, getSpacegroupIndex, OperatorData, SpacegroupNames } from './tables'
import { SpacegroupName, TransformData, GroupData, getSpacegroupIndex, OperatorData, SpacegroupNumber } from './tables'
import { SymmetryOperator } from '../../geometry';
interface SpacegroupCell {
/** Zero based spacegroup number */
/** Index into spacegroup data table */
readonly index: number,
readonly size: Vec3,
readonly volume: number,
@@ -22,7 +22,10 @@ interface SpacegroupCell {
}
interface Spacegroup {
/** Hermann-Mauguin spacegroup name */
readonly name: string,
/** Spacegroup number from International Tables for Crystallography */
readonly num: number,
readonly cell: SpacegroupCell,
readonly operators: ReadonlyArray<Mat4>
}
@@ -72,14 +75,15 @@ namespace SpacegroupCell {
}
}
namespace Spacegroup {
/** P1 with [1, 1, 1] cell */
export const ZeroP1 = create(SpacegroupCell.Zero);
export function create(cell: SpacegroupCell): Spacegroup {
const operators = GroupData[cell.index].map(i => getOperatorMatrix(OperatorData[i]));
return { name: SpacegroupNames[cell.index], cell, operators };
const name = SpacegroupName[cell.index]
const num = SpacegroupNumber[cell.index]
return { name, num, cell, operators };
}
const _ijkVec = Vec3();

View File

@@ -1,7 +1,8 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
export const TransformData = [
@@ -31,26 +32,26 @@ export const TransformData = [
[0.0, 0.0, -1.0, 0.75],
[1.0, -1.0, 0.0, 0.0],
[-1.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.333333333333333],
[0.0, 0.0, 1.0, 0.666666666666667],
[1.0, 0.0, 0.0, 0.666666666666667],
[0.0, 1.0, 0.0, 0.333333333333333],
[0.0, -1.0, 0.0, 0.666666666666667],
[1.0, -1.0, 0.0, 0.333333333333333],
[-1.0, 1.0, 0.0, 0.666666666666667],
[-1.0, 0.0, 0.0, 0.333333333333333],
[1.0, 0.0, 0.0, 0.333333333333333],
[0.0, 1.0, 0.0, 0.666666666666667],
[0.0, -1.0, 0.0, 0.333333333333333],
[1.0, -1.0, 0.0, 0.666666666666667],
[-1.0, 1.0, 0.0, 0.333333333333333],
[-1.0, 0.0, 0.0, 0.666666666666667],
[0.0, 0.0, -1.0, 0.333333333333333],
[0.0, 0.0, -1.0, 0.666666666666667],
[0.0, 0.0, 1.0, 0.833333333333333],
[0.0, 0.0, 1.0, 0.166666666666667],
[0.0, 0.0, -1.0, 0.833333333333333],
[0.0, 0.0, -1.0, 0.166666666666667],
[0.0, 0.0, 1.0, 1/3],
[0.0, 0.0, 1.0, 2/3],
[1.0, 0.0, 0.0, 2/3],
[0.0, 1.0, 0.0, 1/3],
[0.0, -1.0, 0.0, 2/3],
[1.0, -1.0, 0.0, 1/3],
[-1.0, 1.0, 0.0, 2/3],
[-1.0, 0.0, 0.0, 1/3],
[1.0, 0.0, 0.0, 1/3],
[0.0, 1.0, 0.0, 2/3],
[0.0, -1.0, 0.0, 1/3],
[1.0, -1.0, 0.0, 2/3],
[-1.0, 1.0, 0.0, 1/3],
[-1.0, 0.0, 0.0, 2/3],
[0.0, 0.0, -1.0, 1/3],
[0.0, 0.0, -1.0, 2/3],
[0.0, 0.0, 1.0, 5/6],
[0.0, 0.0, 1.0, 1/6],
[0.0, 0.0, -1.0, 5/6],
[0.0, 0.0, -1.0, 1/6],
];
export const OperatorData = [
@@ -970,6 +971,16 @@ export const GroupData = [
[0, 52, 16, 1, 26, 59, 20, 65],
[0, 31, 1, 63],
[0, 1, 24, 62],
[0, 15, 1, 9], // 'P 1 21/n 1'
// X,Y,Z
// -X+1/2,Y+1/2,-Z+1/2
// -X,-Y,-Z
// X+1/2,-Y+1/2,Z+1/2
[0, 5, 1, 8], // 'P 1 21/a 1'
// X,Y,Z
// -X+1/2,Y+1/2,-Z
// -X,-Y,-Z
// X+1/2,-Y+1/2,Z
[0, 31, 1, 63, 26, 21, 65, 54],
[0, 2, 57, 56],
[0, 60, 3, 16],
@@ -985,7 +996,7 @@ export const GroupData = [
[0, 22, 57, 3, 159, 279, 654, 655, 158, 274, 656, 657, 29, 18, 25, 27, 284, 658, 262, 269, 280, 659, 257, 267],
];
export const ZeroBasedSpacegroupNumbers = {
export const SpacegroupNameToIndexMap = {
'P 1': 0,
'P -1': 1,
'P 1 2 1': 2,
@@ -1318,44 +1329,164 @@ export const ZeroBasedSpacegroupNumbers = {
'B 1 1 2/m': 250,
'P 1 1 2/b': 251,
'P 1 1 21/b': 252,
'B 1 1 2/b': 253,
'P 21 2 2': 254,
'P 2 21 2': 255,
'P 21 21 2 (a)': 256,
'P 21 2 21': 257,
'P 2 21 21': 258,
'C 2 2 21a)': 259,
'C 2 2 2a': 260,
'F 2 2 2a': 261,
'I 2 2 2a': 262,
'P 21/m 21/m 2/n a': 263,
'P 42 21 2a': 264,
'I 2 3a': 265,
'P 1 21/n 1': 253,
'P 1 21/a 1': 254,
'B 1 1 2/b': 255,
'P 21 2 2': 256,
'P 2 21 2': 257,
'P 21 21 2 (a)': 258,
'P 21 2 21': 259,
'P 2 21 21': 260,
'C 2 2 21a)': 261,
'C 2 2 2a': 262,
'F 2 2 2a': 263,
'I 2 2 2a': 264,
'P 21/m 21/m 2/n a': 265,
'P 42 21 2a': 266,
'I 2 3a': 267,
};
export type SpacegroupName = keyof typeof ZeroBasedSpacegroupNumbers
export function getSpacegroupIndexFromNumber(num: number) {
// 38 spacegroup variants as given CCP4s symop.lib
switch (num) {
case 1146: return 146
case 1148: return 149
case 1155: return 157
case 1160: return 163
case 1161: return 165
case 1166: return 171
case 1167: return 173
export const SpacegroupNames: { [num: number]: SpacegroupName } = (function () {
case 1003: return 237 // 'P 1 1 2' !(dyad along z)
case 1004: return 238 // 'P 1 1 21' !(unique axis c)
case 1005: return 239 // 'B 1 1 2' 'B 2'
case 2005: return 240 // 'A 1 2 1'
case 3005: return 241 // 'C 1 21 1' ! (Origin on screw at 1/4X)
case 4005: return 242 // 'I 1 2 1' 'I 2' !!! GJK @ 2003-06-02
case 5005: return 243 // 'I 1 21 1'
case 1006: return 244 // 'P 1 1 m'
case 1007: return 245 // 'P 1 1 b'
case 1008: return 246 // 'B 1 1 m'
case 1009: return 247 // 'B 1 1 b'
case 1010: return 248 // 'P 1 1 2/m'
case 1011: return 249 // 'P 1 1 21/m'
case 1012: return 250 // 'B 1 1 2/m'
case 1013: return 251 // 'P 1 1 2/b'
case 1014: return 252 // 'P 1 1 21/b'
case 2014: return 253 // 'P 1 21/n 1'
case 3014: return 254 // 'P 1 21/a 1'
case 1015: return 255 // 'B 1 1 2/b'
case 1017: return 256 // 'P 21 2 2' !(unique axis a)
case 2017: return 257 // 'P 2 21 2' !(unique axis b)
case 1018: return 258 // 'P 21 21 2 (a)' ! origin on 21 21, shift (1/4,1/4,0)
case 2018: return 259 // 'P 21 2 21' !(unique axis b)
case 3018: return 260 // 'P 2 21 21' !(unique axis a)
case 1020: return 261 // 'C 2 2 21a)' ! P212121 with C centring, shift(1/4,0,0)
case 1021: return 262 // 'C 2 2 2a' ! C21212a origin on 21 21
case 1022: return 263 // 'F 2 2 2a' ! same as 1018 with face centring shift (1/4,0,0)
case 1023: return 264 // 'I 2 2 2a' ! as 1018 with origin shift (1/4,1/4,1/4)
case 1059: return 265 // 'P 21/m 21/m 2/n a'
case 1094: return 266 // 'P 42 21 2a' ! (as P21212a) origin on 21 21 ie Shift 1/4,1/4,1/4
case 1197: return 267 // 'I 2 3a' ! Expansion of 1023 which is an expansion of 1018
}
let offset = 0
if (num > 146) ++offset
if (num > 148) ++offset
if (num > 155) ++offset
if (num > 160) ++offset
if (num > 161) ++offset
if (num > 166) ++offset
if (num > 167) ++offset
return num - 1 + offset
}
export function getSpacegroupNumberFromIndex(idx: number) {
if (idx < 146) return idx + 1
if (idx === 146) return 1146
if (idx < 149) return idx + 1 - 1
if (idx === 149) return 1148
if (idx < 157) return idx + 1 - 2
if (idx === 157) return 1155
if (idx < 163) return idx + 1 - 3
if (idx === 163) return 1160
if (idx < 165) return idx + 1 - 4
if (idx === 165) return 1161
if (idx < 171) return idx + 1 - 5
if (idx === 171) return 1166
if (idx < 173) return idx + 1 - 6
if (idx === 173) return 1167
if (idx < 237) return idx + 1 - 7
if (idx === 237) return 1003
if (idx === 238) return 1004
if (idx === 239) return 1005
if (idx === 240) return 2005
if (idx === 241) return 3005
if (idx === 242) return 4005
if (idx === 243) return 5005
if (idx === 244) return 1006
if (idx === 245) return 1007
if (idx === 246) return 1008
if (idx === 247) return 1009
if (idx === 248) return 1010
if (idx === 249) return 1011
if (idx === 250) return 1012
if (idx === 251) return 1013
if (idx === 252) return 1014
if (idx === 253) return 2014
if (idx === 254) return 3014
if (idx === 255) return 1015
if (idx === 256) return 1017
if (idx === 257) return 2017
if (idx === 258) return 1018
if (idx === 259) return 2018
if (idx === 260) return 3018
if (idx === 261) return 1020
if (idx === 262) return 1021
if (idx === 263) return 1022
if (idx === 264) return 1023
if (idx === 265) return 1059
if (idx === 266) return 1094
if (idx === 267) return 1197
throw new Error(`unknown spacegroup index '${idx}'`)
}
export type SpacegroupName = keyof typeof SpacegroupNameToIndexMap
/** Maps spacegroup index to Hermann-Mauguin spacegroup name */
export const SpacegroupName: { [idx: number]: SpacegroupName } = (function () {
const names = Object.create(null);
for (const n of Object.keys(ZeroBasedSpacegroupNumbers)) {
names[(ZeroBasedSpacegroupNumbers as any)[n]] = n;
for (const n of Object.keys(SpacegroupNameToIndexMap)) {
names[(SpacegroupNameToIndexMap as any)[n]] = n;
}
return names;
}());
// return -1 if the spacegroup does not exist.
export function getSpacegroupIndex(nameOrNumber: number | string | SpacegroupName): number {
let index: number
if (typeof nameOrNumber === 'number') {
// used by CCP4, see http://www.ccp4.ac.uk/html/pointless.html#setting
if (nameOrNumber === 1017) index = 254
else if (nameOrNumber === 2017) index = 255
else if (nameOrNumber === 2018) index = 257
else if (nameOrNumber === 3018) index = 258
else index = nameOrNumber - 1
} else {
index = ZeroBasedSpacegroupNumbers[nameOrNumber as SpacegroupName];
/** Maps spacegroup index to spacegroup number from International Tables for Crystallography */
export const SpacegroupNumber: { [idx: number]: number } = (function () {
const numbers = Object.create(null);
for (const n of Object.keys(SpacegroupNameToIndexMap)) {
const idx = (SpacegroupNameToIndexMap as any)[n]
numbers[idx] = getSpacegroupNumberFromIndex(idx);
}
if (typeof index === 'undefined' || typeof SpacegroupNames[index] === 'undefined') return -1;
return numbers;
}());
/** return -1 if the spacegroup does not exist */
export function getSpacegroupIndex(nameOrNumber: number | string | SpacegroupName): number {
const index = typeof nameOrNumber === 'number'
? getSpacegroupIndexFromNumber(nameOrNumber)
: SpacegroupNameToIndexMap[nameOrNumber as SpacegroupName];
if (typeof index === 'undefined' || typeof SpacegroupName[index] === 'undefined') return -1;
return index;
}

View File

@@ -15,7 +15,6 @@ import { AtomicConformation, AtomicData, AtomicHierarchy, AtomicSegments, AtomsS
import { getAtomicIndex } from '../../../mol-model/structure/model/properties/utils/atomic-index';
import { ElementSymbol } from '../../../mol-model/structure/model/types';
import { Entities } from '../../../mol-model/structure/model/properties/common';
import { getAtomicRanges } from '../../../mol-model/structure/model/properties/utils/atomic-ranges';
import { getAtomicDerivedData } from '../../../mol-model/structure/model/properties/utils/atomic-derived';
import { FormatData } from './parser';
@@ -100,7 +99,6 @@ export function getAtomicHierarchyAndConformation(atom_site: AtomSite, sourceInd
const index = getAtomicIndex(hierarchyData, entities, hierarchySegments);
const derived = getAtomicDerivedData(hierarchyData, index, formatData.chemicalComponentMap);
const hierarchyRanges = getAtomicRanges(hierarchyData, hierarchySegments, conformation, index, derived.residue.moleculeType);
const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, ...hierarchyRanges, index, derived };
const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, index, derived };
return { sameAsPrevious: false, hierarchy, conformation };
}

View File

@@ -30,6 +30,7 @@ import mmCIF_Format = ModelFormat.mmCIF
import { memoize1 } from '../../../mol-util/memoize';
import { ElementIndex, EntityIndex } from '../../../mol-model/structure/model';
import { AtomSiteAnisotrop } from './anisotropic';
import { getAtomicRanges } from '../../../mol-model/structure/model/properties/utils/atomic-ranges';
export async function _parse_mmCif(format: mmCIF_Format, ctx: RuntimeContext) {
const formatData = getFormatData(format)
@@ -53,11 +54,19 @@ function checkNonStandardCrystalFrame(format: mmCIF_Format, spacegroup: Spacegro
return false;
}
function getSpacegroupNameOrNumber(symmetry: mmCIF_Format['data']['symmetry']) {
const groupNumber = symmetry['Int_Tables_number'].value(0);
const groupName = symmetry['space_group_name_H-M'].value(0);
if (!symmetry['Int_Tables_number'].isDefined) return groupName
if (!symmetry['space_group_name_H-M'].isDefined) return groupNumber
return groupName
}
function getSpacegroup(format: mmCIF_Format): Spacegroup {
const { symmetry, cell } = format.data;
if (symmetry._rowCount === 0 || cell._rowCount === 0) return Spacegroup.ZeroP1;
const groupName = symmetry['space_group_name_H-M'].value(0);
const spaceCell = SpacegroupCell.create(groupName,
const nameOrNumber = getSpacegroupNameOrNumber(symmetry)
const spaceCell = SpacegroupCell.create(nameOrNumber,
Vec3.create(cell.length_a.value(0), cell.length_b.value(0), cell.length_c.value(0)),
Vec3.scale(Vec3.zero(), Vec3.create(cell.angle_alpha.value(0), cell.angle_beta.value(0), cell.angle_gamma.value(0)), Math.PI / 180));
@@ -224,6 +233,9 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, sourceIn
}
const coarse = EmptyIHMCoarse;
const sequence = getSequence(format.data, entities, atomic.hierarchy, coarse.hierarchy, formatData.modifiedResidues.parentId)
const atomicRanges = getAtomicRanges(atomic.hierarchy, entities, atomic.conformation, sequence)
const entry = format.data.entry.id.valueKind(0) === Column.ValueKind.Present
? format.data.entry.id.value(0)
: format.data._name;
@@ -241,9 +253,10 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, sourceIn
modelNum,
entities,
symmetry: getSymmetry(format),
sequence: getSequence(format.data, entities, atomic.hierarchy, coarse.hierarchy, formatData.modifiedResidues.parentId),
sequence,
atomicHierarchy: atomic.hierarchy,
atomicConformation: atomic.conformation,
atomicRanges,
coarseHierarchy: coarse.hierarchy,
coarseConformation: coarse.conformation,
properties: {
@@ -259,6 +272,9 @@ function createStandardModel(format: mmCIF_Format, atom_site: AtomSite, sourceIn
function createModelIHM(format: mmCIF_Format, data: IHMData, formatData: FormatData): Model {
const atomic = getAtomicHierarchyAndConformation(data.atom_site, data.atom_site_sourceIndex, data.entities, formatData);
const coarse = getIHMCoarse(data, formatData);
const sequence = getSequence(format.data, data.entities, atomic.hierarchy, coarse.hierarchy, formatData.modifiedResidues.parentId)
const atomicRanges = getAtomicRanges(atomic.hierarchy, data.entities, atomic.conformation, sequence)
const entry = format.data.entry.id.valueKind(0) === Column.ValueKind.Present
? format.data.entry.id.value(0)
: format.data._name;
@@ -278,9 +294,10 @@ function createModelIHM(format: mmCIF_Format, data: IHMData, formatData: FormatD
modelNum: data.model_id,
entities: data.entities,
symmetry: getSymmetry(format),
sequence: getSequence(format.data, data.entities, atomic.hierarchy, coarse.hierarchy, formatData.modifiedResidues.parentId),
sequence,
atomicHierarchy: atomic.hierarchy,
atomicConformation: atomic.conformation,
atomicRanges,
coarseHierarchy: coarse.hierarchy,
coarseConformation: coarse.conformation,
properties: {

View File

@@ -117,7 +117,7 @@ namespace CustomElementProperty {
function LabelProvider(loci: Loci): string | undefined {
if (loci.kind === 'element-loci') {
const e = loci.elements[0];
if (!has(e.unit.model)) return void 0;
if (!e || !has(e.unit.model)) return void 0;
return params.format!(get(StructureElement.Location.create(e.unit, e.unit.elements[OrderedSet.getAt(e.indices, 0)])));
}
return void 0;

View File

@@ -16,14 +16,14 @@ import { Structure } from './structure/structure';
/** A Loci that includes every loci */
export const EveryLoci = { kind: 'every-loci' as 'every-loci' }
export type EveryLoci = typeof EveryLoci
export function isEveryLoci(x: any): x is EveryLoci {
export function isEveryLoci(x?: Loci): x is EveryLoci {
return !!x && x.kind === 'every-loci';
}
/** A Loci that is empty */
export const EmptyLoci = { kind: 'empty-loci' as 'empty-loci' }
export type EmptyLoci = typeof EmptyLoci
export function isEmptyLoci(x: any): x is EmptyLoci {
export function isEmptyLoci(x?: Loci): x is EmptyLoci {
return !!x && x.kind === 'empty-loci';
}
@@ -34,7 +34,7 @@ export interface DataLoci {
readonly tag: string
readonly indices: OrderedSet<number>
}
export function isDataLoci(x: any): x is DataLoci {
export function isDataLoci(x?: Loci): x is DataLoci {
return !!x && x.kind === 'data-loci';
}
export function areDataLociEqual(a: DataLoci, b: DataLoci) {
@@ -76,7 +76,11 @@ namespace Loci {
return false
}
export function isEmpty(loci: Loci): boolean {
export function isEvery(loci?: Loci): loci is EveryLoci {
return !!loci && loci.kind === 'every-loci';
}
export function isEmpty(loci: Loci): loci is EmptyLoci {
if (isEveryLoci(loci)) return false
if (isEmptyLoci(loci)) return true
if (isDataLoci(loci)) return isDataLociEmpty(loci)

View File

@@ -7,7 +7,7 @@
import UUID from '../../../mol-util/uuid';
import StructureSequence from './properties/sequence';
import { AtomicHierarchy, AtomicConformation } from './properties/atomic';
import { AtomicHierarchy, AtomicConformation, AtomicRanges } from './properties/atomic';
import { ModelSymmetry } from './properties/symmetry';
import { CoarseHierarchy, CoarseConformation } from './properties/coarse';
import { Entities, ChemicalComponentMap, MissingResidues } from './properties/common';
@@ -42,6 +42,7 @@ export interface Model extends Readonly<{
atomicHierarchy: AtomicHierarchy,
atomicConformation: AtomicConformation,
atomicRanges: AtomicRanges,
properties: {
/** secondary structure provided by the input file */

View File

@@ -222,7 +222,7 @@ export interface AtomicRanges {
cyclicPolymerMap: Map<ResidueIndex, ResidueIndex>
}
type _Hierarchy = AtomicData & AtomicSegments & AtomicRanges
type _Hierarchy = AtomicData & AtomicSegments
export interface AtomicHierarchy extends _Hierarchy {
index: AtomicIndex
derived: AtomicDerivedData

View File

@@ -21,7 +21,7 @@ export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemi
const moleculeTypeMap = new Map<string, MoleculeType>()
for (let i = 0; i < n; ++i) {
for (let i = 0 as ResidueIndex; i < n; ++i) {
const compId = label_comp_id.value(i)
const chemCompMap = chemicalComponentMap
let molType: MoleculeType
@@ -39,18 +39,18 @@ export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemi
moleculeType[i] = molType
const traceAtomId = getAtomIdForAtomRole(molType, 'trace')
let traceIndex = index.findAtomsOnResidue(i as ResidueIndex, traceAtomId)
let traceIndex = index.findAtomsOnResidue(i, traceAtomId)
if (traceIndex === -1) {
const coarseAtomId = getAtomIdForAtomRole(molType, 'coarseBackbone')
traceIndex = index.findAtomsOnResidue(i as ResidueIndex, coarseAtomId)
traceIndex = index.findAtomsOnResidue(i, coarseAtomId)
}
traceElementIndex[i] = traceIndex
const directionFromAtomId = getAtomIdForAtomRole(molType, 'directionFrom')
directionFromElementIndex[i] = index.findAtomsOnResidue(i as ResidueIndex, directionFromAtomId)
directionFromElementIndex[i] = index.findAtomsOnResidue(i, directionFromAtomId)
const directionToAtomId = getAtomIdForAtomRole(molType, 'directionTo')
directionToElementIndex[i] = index.findAtomsOnResidue(i as ResidueIndex, directionToAtomId)
directionToElementIndex[i] = index.findAtomsOnResidue(i, directionToAtomId)
}
return {

View File

@@ -1,25 +1,26 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { AtomicSegments } from '../atomic';
import { AtomicData, AtomicRanges, AtomicIndex } from '../atomic/hierarchy';
import { AtomicRanges, AtomicIndex, AtomicHierarchy, AtomicDerivedData } from '../atomic/hierarchy';
import { Segmentation, Interval } from '../../../../../mol-data/int';
import SortedRanges from '../../../../../mol-data/int/sorted-ranges';
import { MoleculeType, isPolymer } from '../../types';
import { isPolymer } from '../../types';
import { ElementIndex, ResidueIndex } from '../../indexing';
import { getAtomIdForAtomRole } from '../../../util';
import { AtomicConformation } from '../atomic/conformation';
import { Vec3 } from '../../../../../mol-math/linear-algebra';
import { Entities } from '../common';
import StructureSequence from '../sequence';
// TODO add gaps at the ends of the chains by comparing to the polymer sequence data
function areBackboneConnected(riStart: ResidueIndex, riEnd: ResidueIndex, data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, index: AtomicIndex, moleculeType: ArrayLike<MoleculeType>) {
function areBackboneConnected(riStart: ResidueIndex, riEnd: ResidueIndex, conformation: AtomicConformation, index: AtomicIndex, derived: AtomicDerivedData) {
const { moleculeType, traceElementIndex, directionFromElementIndex, directionToElementIndex } = derived.residue
const mtStart = moleculeType[riStart]
const mtEnd = moleculeType[riEnd]
if (!isPolymer(mtStart) || !isPolymer(mtEnd)) return false
if (traceElementIndex[riStart] === -1 || traceElementIndex[riEnd] === -1) return false
let eiStart = index.findAtomsOnResidue(riStart, getAtomIdForAtomRole(mtStart, 'backboneStart'))
let eiEnd = index.findAtomsOnResidue(riEnd, getAtomIdForAtomRole(mtEnd, 'backboneEnd'))
@@ -32,16 +33,20 @@ function areBackboneConnected(riStart: ResidueIndex, riEnd: ResidueIndex, data:
const { x, y, z } = conformation
const pStart = Vec3.create(x[eiStart], y[eiStart], z[eiStart])
const pEnd = Vec3.create(x[eiEnd], y[eiEnd], z[eiEnd])
return Vec3.distance(pStart, pEnd) < 10 // TODO better distance check, take into account if protein/nucleic and if coarse
const isCoarse = directionFromElementIndex[riStart] === -1 || directionToElementIndex[riStart] === -1 || directionFromElementIndex[riEnd] === -1 || directionToElementIndex[riEnd] === -1
return Vec3.distance(pStart, pEnd) < (isCoarse ? 10 : 3)
}
export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, index: AtomicIndex, moleculeType: ArrayLike<MoleculeType>): AtomicRanges {
export function getAtomicRanges(hierarchy: AtomicHierarchy, entities: Entities, conformation: AtomicConformation, sequence: StructureSequence): AtomicRanges {
const polymerRanges: number[] = []
const gapRanges: number[] = []
const cyclicPolymerMap = new Map<ResidueIndex, ResidueIndex>()
const chainIt = Segmentation.transientSegments(segments.chainAtomSegments, Interval.ofBounds(0, data.atoms._rowCount))
const residueIt = Segmentation.transientSegments(segments.residueAtomSegments, Interval.ofBounds(0, data.atoms._rowCount))
const { label_seq_id } = data.residues
const chainIt = Segmentation.transientSegments(hierarchy.chainAtomSegments, Interval.ofBounds(0, hierarchy.atoms._rowCount))
const residueIt = Segmentation.transientSegments(hierarchy.residueAtomSegments, Interval.ofBounds(0, hierarchy.atoms._rowCount))
const { index, derived } = hierarchy
const { label_seq_id } = hierarchy.residues
const { label_entity_id } = hierarchy.chains
const { moleculeType, traceElementIndex } = derived.residue
let prevSeqId: number
let prevStart: number
@@ -56,9 +61,16 @@ export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conf
prevEnd = -1
startIndex = -1
const riStart = segments.residueAtomSegments.index[chainSegment.start]
const riEnd = segments.residueAtomSegments.index[chainSegment.end - 1]
if (areBackboneConnected(riStart, riEnd, data, segments, conformation, index, moleculeType)) {
const eI = entities.getEntityIndex(label_entity_id.value(chainSegment.index))
const seq = sequence.byEntityKey[eI]
const maxSeqId = seq ? seq.sequence.seqId.value(seq.sequence.seqId.rowCount - 1) : -1
// check cyclic peptides, seqIds and distance must be compatible
const riStart = hierarchy.residueAtomSegments.index[chainSegment.start]
const riEnd = hierarchy.residueAtomSegments.index[chainSegment.end - 1]
const seqIdStart = label_seq_id.value(riStart)
const seqIdEnd = label_seq_id.value(riEnd)
if (seqIdStart === 1 && seqIdEnd === maxSeqId && areBackboneConnected(riStart, riEnd, conformation, index, derived)) {
cyclicPolymerMap.set(riStart, riEnd)
cyclicPolymerMap.set(riEnd, riStart)
}
@@ -67,7 +79,8 @@ export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conf
const residueSegment = residueIt.move();
const residueIndex = residueSegment.index
const seqId = label_seq_id.value(residueIndex)
if (isPolymer(moleculeType[residueIndex])) {
// treat polymers residues that don't have a trace element resolved as gaps
if (isPolymer(moleculeType[residueIndex]) && traceElementIndex[residueIndex] !== -1) {
if (startIndex !== -1) {
if (seqId !== prevSeqId + 1) {
polymerRanges.push(startIndex, prevEnd - 1)
@@ -75,16 +88,24 @@ export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conf
startIndex = residueSegment.start
} else if (!residueIt.hasNext) {
polymerRanges.push(startIndex, residueSegment.end - 1)
// TODO
// if (seqId !== maxSeqId) {
// gapRanges.push(residueSegment.end - 1, residueSegment.end - 1)
// }
} else {
const riStart = segments.residueAtomSegments.index[residueSegment.start]
const riEnd = segments.residueAtomSegments.index[prevEnd - 1]
if (!areBackboneConnected(riStart, riEnd, data, segments, conformation, index, moleculeType)) {
const riStart = hierarchy.residueAtomSegments.index[residueSegment.start]
const riEnd = hierarchy.residueAtomSegments.index[prevEnd - 1]
if (!areBackboneConnected(riStart, riEnd, conformation, hierarchy.index, derived)) {
polymerRanges.push(startIndex, prevEnd - 1)
startIndex = residueSegment.start
}
}
} else {
startIndex = residueSegment.start // start polymer
// TODO
// if (seqId !== 1) {
// gapRanges.push(residueSegment.start, residueSegment.start)
// }
}
} else {
if (startIndex !== -1) {

View File

@@ -9,6 +9,7 @@ import { now } from '../../../mol-util/now';
import { ElementIndex } from '../model';
import { Link } from '../structure/unit/links';
import { LinkType } from '../model/types';
import { StructureSelection } from './selection';
export interface QueryContextView {
readonly element: StructureElement.Location;
@@ -33,6 +34,9 @@ export class QueryContext implements QueryContextView {
/** Current link between atoms */
readonly atomicLink = QueryContextLinkInfo.empty<Unit.Atomic>();
/** Supply this from the outside. Used by the internal.generator.current symbol */
currentSelection: StructureSelection | undefined = void 0;
setElement(unit: Unit, e: ElementIndex) {
this.element.unit = unit;
this.element.element = e;
@@ -88,10 +92,21 @@ export class QueryContext implements QueryContextView {
}
}
constructor(structure: Structure, timeoutMs = 0) {
this.inputStructure = structure;
this.timeoutMs = timeoutMs;
tryGetCurrentSelection() {
if (!this.currentSelection) throw new Error('The current selection is not assigned.');
return this.currentSelection;
}
constructor(structure: Structure, options?: QueryContextOptions) {
this.inputStructure = structure;
this.timeoutMs = (options && options.timeoutMs) || 0;
this.currentSelection = options && options.currentSelection;
}
}
export interface QueryContextOptions {
timeoutMs?: number,
currentSelection?: StructureSelection
}
export interface QueryPredicate { (ctx: QueryContext): boolean }

View File

@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Segmentation } from '../../../../mol-data/int';
import { Segmentation, SortedArray } from '../../../../mol-data/int';
import StructureElement from '../../../../mol-model/structure/structure/element';
import { StructureProperties as P, Unit } from '../../structure';
import Structure from '../../structure/structure';
@@ -12,6 +12,8 @@ import { StructureQuery } from '../query';
import { StructureSelection } from '../selection';
import { QueryContext } from '../context';
import { LinkType } from '../../model/types';
import { BundleElement, Bundle } from '../../structure/element/bundle';
import { UnitIndex } from '../../structure/element/element';
export function defaultLinkTest(ctx: QueryContext) {
return LinkType.isCovalent(ctx.atomicLink.type);
@@ -105,4 +107,23 @@ export function spheres(): StructureQuery {
}
return StructureSelection.Singletons(inputStructure, new Structure(units, { parent: inputStructure }));
};
}
export function bundleElementImpl(groupedUnits: number[][], ranges: number[], set: number[]): BundleElement {
return {
groupedUnits: groupedUnits as any as SortedArray<number>[],
ranges: ranges as any as SortedArray<UnitIndex>,
set: set as any as SortedArray<UnitIndex>
};
}
export function bundleGenerator(elements: BundleElement[]): StructureQuery {
return ctx => {
const bundle: Bundle = {
hash: ctx.inputStructure.hashCode,
elements
};
return StructureSelection.Sequence(ctx.inputStructure, [Bundle.toStructure(bundle, ctx.inputStructure)]);
};
}

View File

@@ -6,12 +6,12 @@
import { Structure } from '../structure'
import { StructureSelection } from './selection'
import { QueryContext, QueryFn } from './context';
import { QueryContext, QueryFn, QueryContextOptions } from './context';
interface StructureQuery extends QueryFn<StructureSelection> { }
namespace StructureQuery {
export function run(query: StructureQuery, structure: Structure, timeoutMs = 0) {
return query(new QueryContext(structure, timeoutMs));
export function run(query: StructureQuery, structure: Structure, options?: QueryContextOptions) {
return query(new QueryContext(structure, options));
}
}

View File

@@ -35,7 +35,8 @@ namespace StructureSelection {
return structureUnion(sel.source, sel.structures);
}
export function toLoci(sel: StructureSelection): StructureElement.Loci {
/** Convert selection to loci and use "current structure units" in Loci elements */
export function toLociWithCurrentUnits(sel: StructureSelection): StructureElement.Loci {
const elements: { unit: Unit, indices: OrderedSet<StructureElement.UnitIndex> }[] = [];
const { unitMap } = sel.source;
@@ -57,7 +58,7 @@ namespace StructureSelection {
}
/** use source unit in loci.elements */
export function toLoci2(sel: StructureSelection): StructureElement.Loci {
export function toLociWithSourceUnits(sel: StructureSelection): StructureElement.Loci {
const elements: { unit: Unit, indices: OrderedSet<StructureElement.UnitIndex> }[] = [];
const { unitMap } = sel.source;

View File

@@ -12,8 +12,10 @@ import { hashFnv32a, hash2 } from '../../../../mol-data/util';
import SortedRanges from '../../../../mol-data/int/sorted-ranges';
import { UnitIndex } from './element';
import { Loci } from './loci';
import Expression from '../../../../mol-script/language/expression';
import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
interface BundleElement {
export interface BundleElement {
/**
* Array (sorted by first element in sub-array) of
* arrays of `Unit.id`s that share the same `Unit.invariantId`
@@ -192,6 +194,21 @@ export namespace Bundle {
return Structure.create(units, { parent })
}
function elementToExpression(e: BundleElement): Expression {
return MS.internal.generator.bundleElement({
groupedUnits: MS.core.type.list(e.groupedUnits.map(u => MS.core.type.list(u))),
ranges: MS.core.type.list(e.ranges),
set: MS.core.type.list(e.set),
})
}
export function toExpression(bundle: Bundle): Expression {
return MS.internal.generator.bundle({
elements: MS.core.type.list(bundle.elements.map(elementToExpression))
});
}
export function areEqual(a: Bundle, b: Bundle) {
if (a.elements.length !== b.elements.length) return false
for (let i = 0, il = a.elements.length; i < il; ++i) {

View File

@@ -17,6 +17,7 @@ import { sortArray, hashFnv32a, hash2 } from '../../../../mol-data/util';
import Expression from '../../../../mol-script/language/expression';
import { ElementIndex } from '../../model';
import { UnitIndex } from './element';
import { Location } from './location';
/** Represents multiple element index locations */
export interface Loci {
@@ -78,6 +79,29 @@ export namespace Loci {
return Loci(structure, []);
}
export function getFirstLocation(loci: Loci, e?: Location): Location | undefined {
if (isEmpty(loci)) return void 0;
const unit = loci.elements[0].unit;
const element = unit.elements[OrderedSet.getAt(loci.elements[0].indices, 0)];
if (e) {
e.unit = loci.elements[0].unit;
e.element = element;
return e;
}
return Location.create(unit, element);
}
export function toStructure(loci: Loci): Structure {
const units: Unit[] = []
for (const e of loci.elements) {
const { unit, indices } = e
const elements = new Int32Array(OrderedSet.size(indices))
OrderedSet.forEach(indices, (v, i) => elements[i] = unit.elements[v])
units.push(unit.getChild(SortedArray.ofSortedArray(elements)))
}
return Structure.create(units, { parent: loci.structure.parent })
}
export function remap(loci: Loci, structure: Structure): Loci {
if (structure === loci.structure) return loci

View File

@@ -52,36 +52,26 @@ export namespace Stats {
const { index, offsets } = unit.model.atomicHierarchy.residueAtomSegments
let i = 0
while (i < size) {
let j = 0
const eI = elements[OrderedSet.getAt(indices, i)]
const rI = index[eI]
if (offsets[rI] !== eI) {
// partial residue, start missing
while (i < size && index[elements[OrderedSet.getAt(indices, i)]] === rI) {
++i
stats.elementCount += 1
while (i < size && index[elements[OrderedSet.getAt(indices, i)]] === rI) {
++i
stats.elementCount += 1
++j
}
if (offsets[rI + 1] - offsets[rI] === j) {
// full residue
stats.residueCount += 1
if (stats.residueCount === 1) {
Location.set(stats.firstResidueLoc, unit, elements[OrderedSet.start(indices)])
}
} else {
++i
while (i < size && index[elements[OrderedSet.getAt(indices, i)]] === rI) {
++i
}
if (offsets[rI + 1] - 1 === elements[OrderedSet.getAt(indices, i - 1)]) {
// full residue
stats.residueCount += 1
if (stats.residueCount === 1) {
Location.set(stats.firstResidueLoc, unit, elements[OrderedSet.start(indices)])
}
} else {
// partial residue, end missing
stats.elementCount += offsets[rI + 1] - 1 - elements[OrderedSet.getAt(indices, i - 1)]
}
// partial residue
stats.elementCount += j
}
}
} else {
// TODO
stats.elementCount += size
if (stats.elementCount === 1) {
Location.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)])

View File

@@ -143,8 +143,9 @@ namespace Unit {
readonly model: Model;
readonly conformation: SymmetryOperator.ArrayMapping<ElementIndex>;
/** Reference some commonly accessed things for faster access. */
/** Reference `residueIndex` from `model` for faster access. */
readonly residueIndex: ArrayLike<ResidueIndex>;
/** Reference `chainIndex` from `model` for faster access. */
readonly chainIndex: ArrayLike<ChainIndex>;
private props: AtomicProperties;

View File

@@ -14,7 +14,7 @@ export function getAtomicPolymerElements(unit: Unit.Atomic) {
const { elements, model } = unit
const { residueAtomSegments } = unit.model.atomicHierarchy
const { traceElementIndex } = model.atomicHierarchy.derived.residue
const polymerIt = SortedRanges.transientSegments(unit.model.atomicHierarchy.polymerRanges, elements)
const polymerIt = SortedRanges.transientSegments(unit.model.atomicRanges.polymerRanges, elements)
const residueIt = Segmentation.transientSegments(residueAtomSegments, elements)
while (polymerIt.hasNext) {
const polymerSegment = polymerIt.move()
@@ -51,7 +51,7 @@ export function getAtomicGapElements(unit: Unit.Atomic) {
const { elements, model, residueIndex } = unit
const { residueAtomSegments } = unit.model.atomicHierarchy
const { traceElementIndex } = model.atomicHierarchy.derived.residue
const gapIt = SortedRanges.transientSegments(unit.model.atomicHierarchy.gapRanges, unit.elements);
const gapIt = SortedRanges.transientSegments(unit.model.atomicRanges.gapRanges, unit.elements);
while (gapIt.hasNext) {
const gapSegment = gapIt.move();
const indexStart = residueIndex[elements[gapSegment.start]]

View File

@@ -16,7 +16,7 @@ const M = ModifiersKeys
const Trigger = Binding.Trigger
const DefaultFocusLociBindings = {
clickCenterFocus: Binding(Trigger(B.Flag.Primary, M.create()), 'Center and focus the clicked element using ${trigger}.'),
clickCenterFocus: Binding([Trigger(B.Flag.Primary, M.create())], 'Center and focus the clicked element using ${triggers}.'),
}
const FocusLociParams = {
minRadius: PD.Numeric(8, { min: 1, max: 50, step: 1 }),

View File

@@ -37,6 +37,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
switch (loci.kind) {
case 'element-loci':
if (loci.elements.length === 0) return void 0;
const e = loci.elements[0];
const u = e.unit;
if (!u.model.customProperties.has(StructureQualityReport.Descriptor)) return void 0;

View File

@@ -16,7 +16,7 @@ import { StateSelection } from '../../../mol-state';
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
import { Binding } from '../../../mol-util/binding';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { EmptyLoci } from '../../../mol-model/loci';
import { EmptyLoci, Loci } from '../../../mol-model/loci';
const B = ButtonsType
const M = ModifiersKeys
@@ -25,8 +25,8 @@ const Trigger = Binding.Trigger
//
const DefaultHighlightLociBindings = {
hoverHighlightOnly: Binding(Trigger(B.Flag.None), 'Highlight hovered element using ${trigger}'),
hoverHighlightOnlyExtend: Binding(Trigger(B.Flag.None, M.create({ shift: true })), 'Extend highlight from selected to hovered element along polymer using ${trigger}'),
hoverHighlightOnly: Binding([Trigger(B.Flag.None)], 'Highlight hovered element using ${triggers}'),
hoverHighlightOnlyExtend: Binding([Trigger(B.Flag.None, M.create({ shift: true }))], 'Extend highlight from selected to hovered element along polymer using ${triggers}'),
}
const HighlightLociParams = {
bindings: PD.Value(DefaultHighlightLociBindings, { isHidden: true }),
@@ -74,10 +74,11 @@ export const HighlightLoci = PluginBehavior.create({
const DefaultSelectLociBindings = {
clickSelect: Binding.Empty,
clickSelectExtend: Binding(Trigger(B.Flag.Primary, M.create({ shift: true })), 'Extend selection to clicked element along polymer using ${trigger}.'),
clickSelectOnly: Binding(Trigger(B.Flag.Secondary, M.create({ control: true })), 'Select only the clicked element using ${trigger}.'),
clickSelectToggle: Binding(Trigger(B.Flag.Primary, M.create({ control: true })), 'Toggle selection of clicked element using ${trigger}.'),
clickSelectExtend: Binding([Trigger(B.Flag.Primary, M.create({ shift: true }))], 'Extend selection to clicked element along polymer using ${triggers}.'),
clickSelectOnly: Binding([Trigger(B.Flag.Primary, M.create({ alt: true, shift: true }))], 'Select only the clicked element using ${triggers}.'),
clickSelectToggle: Binding([Trigger(B.Flag.Primary, M.create({ alt: true }))], 'Toggle selection of clicked element using ${triggers}.'),
clickDeselect: Binding.Empty,
clickDeselectAllOnEmpty: Binding.Empty,
}
const SelectLociParams = {
bindings: PD.Value(DefaultSelectLociBindings, { isHidden: true }),
@@ -127,6 +128,10 @@ export const SelectLoci = PluginBehavior.create({
if (Binding.match(this.params.bindings.clickDeselect, buttons, modifiers)) {
this.ctx.interactivity.lociSelects.deselect(current)
}
if (Binding.match(this.params.bindings.clickDeselectAllOnEmpty, buttons, modifiers)) {
if (Loci.isEmpty(current.loci)) this.ctx.interactivity.lociSelects.deselect(current)
}
});
this.ctx.interactivity.lociSelects.addProvider(this.lociMarkProvider)

View File

@@ -26,7 +26,7 @@ const M = ModifiersKeys
const Trigger = Binding.Trigger
const DefaultStructureRepresentationInteractionBindings = {
clickInteractionAroundOnly: Binding(Trigger(B.Flag.Secondary, M.create()), 'Show the structure interaction around only the clicked element using ${trigger}.'),
clickInteractionAroundOnly: Binding([Trigger(B.Flag.Secondary, M.create()), Trigger(B.Flag.Primary, M.create({ control: true }))], 'Show the structure interaction around only the clicked element using ${triggers}.'),
}
const StructureRepresentationInteractionParams = {
bindings: PD.Value(DefaultStructureRepresentationInteractionBindings, { isHidden: true }),

View File

@@ -34,12 +34,20 @@ const Trigger = Binding.Trigger
export class VolumeStreaming extends PluginStateObject.CreateBehavior<VolumeStreaming.Behavior>({ name: 'Volume Streaming' }) { }
export namespace VolumeStreaming {
function channelParam(label: string, color: Color, defaultValue: VolumeIsoValue, stats: VolumeData['dataStats']) {
return PD.Group({
isoValue: createIsoValueParam(defaultValue, stats),
color: PD.Color(color),
wireframe: PD.Boolean(false),
opacity: PD.Numeric(0.3, { min: 0, max: 1, step: 0.01 })
export interface ChannelParams {
isoValue: VolumeIsoValue,
color: Color,
wireframe: boolean,
opacity: number
}
function channelParam(label: string, color: Color, defaultValue: VolumeIsoValue, stats: VolumeData['dataStats'], defaults: Partial<ChannelParams> = {}) {
return PD.Group<ChannelParams>({
isoValue: createIsoValueParam(typeof defaults.isoValue !== 'undefined' ? defaults.isoValue : defaultValue, stats),
color: PD.Color(typeof defaults.color !== 'undefined' ? defaults.color : color),
wireframe: PD.Boolean(typeof defaults.wireframe !== 'undefined' ? defaults.wireframe : false),
opacity: PD.Numeric(typeof defaults.opacity !== 'undefined' ? defaults.opacity : 0.3, { min: 0, max: 1, step: 0.01 })
}, { label, isExpanded: true });
}
@@ -51,54 +59,67 @@ export namespace VolumeStreaming {
};
export const DefaultBindings = {
clickVolumeAroundOnly: Binding(Trigger(B.Flag.Secondary, M.create()), 'Show the volume around only the clicked element using ${trigger}.'),
clickVolumeAroundOnly: Binding([Trigger(B.Flag.Secondary, M.create()), Trigger(B.Flag.Primary, M.create({ control: true }))], 'Show the volume around only the clicked element using ${triggers}.'),
}
export function createParams(data?: VolumeServerInfo.Data, defaultView?: ViewTypes, binding?: typeof DefaultBindings) {
export function createParams(options: { data?: VolumeServerInfo.Data, defaultView?: ViewTypes, binding?: typeof DefaultBindings, channelParams?: DefaultChannelParams } = { }) {
const { data, defaultView, binding, channelParams } = options;
const map = new Map<string, VolumeServerInfo.EntryData>()
if (data) data.entries.forEach(d => map.set(d.dataId, d))
const names = data ? data.entries.map(d => [d.dataId, d.dataId] as [string, string]) : []
const defaultKey = data ? data.entries[0].dataId : ''
return {
entry: PD.Mapped<EntryParams>(defaultKey, names, name => PD.Group(createEntryParams({ entryData: map.get(name)!, defaultView, structure: data && data.structure, channelParams }))),
bindings: PD.Value(binding || DefaultBindings, { isHidden: true }),
}
}
export type EntryParamDefinition = typeof createEntryParams extends (...args: any[]) => (infer T) ? T : never
export type EntryParams = EntryParamDefinition extends PD.Params ? PD.Values<EntryParamDefinition> : {}
export function createEntryParams(options: { entryData?: VolumeServerInfo.EntryData, defaultView?: ViewTypes, structure?: Structure, channelParams?: DefaultChannelParams }) {
const { entryData, defaultView, structure, channelParams = { } } = options;
// fake the info
const info = data || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: VolumeIsoValue.relative(0) };
const box = (data && data.structure.boundary.box) || Box3D.empty();
const info = entryData || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: VolumeIsoValue.relative(0) };
const box = (structure && structure.boundary.box) || Box3D.empty();
return {
view: PD.MappedStatic(defaultView || (info.kind === 'em' ? 'cell' : 'selection-box'), {
'off': PD.Group({}),
'off': PD.Group<{}>({}),
'box': PD.Group({
bottomLeft: PD.Vec3(box.min),
topRight: PD.Vec3(box.max),
}, { description: 'Static box defined by cartesian coords.', isFlat: true }),
'selection-box': PD.Group({
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }),
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
bottomLeft: PD.Vec3(Vec3.create(0, 0, 0), { isHidden: true }),
topRight: PD.Vec3(Vec3.create(0, 0, 0), { isHidden: true }),
}, { description: 'Box around last-interacted element.', isFlat: true }),
'cell': PD.Group({}),
// 'auto': PD.Group({ }), // TODO based on camera distance/active selection/whatever, show whole structure or slice.
}, { options: ViewTypeOptions as any }),
}, { options: ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Interaction" shows the volume around the element/atom last interacted with. "Whole Structure" shows the volume for the whole structure.' }),
detailLevel: PD.Select<number>(Math.min(3, info.header.availablePrecisions.length - 1),
info.header.availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string])),
info.header.availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]), { description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 0 (0.52M voxels) to 6 (25.17M voxels).' }),
channels: info.kind === 'em'
? PD.Group({
'em': channelParam('EM', Color(0x638F8F), info.emDefaultContourLevel || VolumeIsoValue.relative(1), info.header.sampling[0].valuesInfo[0])
'em': channelParam('EM', Color(0x638F8F), info.emDefaultContourLevel || VolumeIsoValue.relative(1), info.header.sampling[0].valuesInfo[0], channelParams['em'])
}, { isFlat: true })
: PD.Group({
'2fo-fc': channelParam('2Fo-Fc', Color(0x3362B2), VolumeIsoValue.relative(1.5), info.header.sampling[0].valuesInfo[0]),
'fo-fc(+ve)': channelParam('Fo-Fc(+ve)', Color(0x33BB33), VolumeIsoValue.relative(3), info.header.sampling[0].valuesInfo[1]),
'fo-fc(-ve)': channelParam('Fo-Fc(-ve)', Color(0xBB3333), VolumeIsoValue.relative(-3), info.header.sampling[0].valuesInfo[1]),
'2fo-fc': channelParam('2Fo-Fc', Color(0x3362B2), VolumeIsoValue.relative(1.5), info.header.sampling[0].valuesInfo[0], channelParams['2fo-fc']),
'fo-fc(+ve)': channelParam('Fo-Fc(+ve)', Color(0x33BB33), VolumeIsoValue.relative(3), info.header.sampling[0].valuesInfo[1], channelParams['fo-fc(+ve)']),
'fo-fc(-ve)': channelParam('Fo-Fc(-ve)', Color(0xBB3333), VolumeIsoValue.relative(-3), info.header.sampling[0].valuesInfo[1], channelParams['fo-fc(-ve)']),
}, { isFlat: true }),
bindings: PD.Value(binding || DefaultBindings, { isHidden: true }),
};
}
export const ViewTypeOptions = [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Surroundings'], ['cell', 'Whole Structure']];
export const ViewTypeOptions = [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Around Interaction'], ['cell', 'Whole Structure']] as [ViewTypes, string][];
export type ViewTypes = 'off' | 'box' | 'selection-box' | 'cell'
export type ParamDefinition = typeof createParams extends (...args: any[]) => (infer T) ? T : never
export type Params = ParamDefinition extends PD.Params ? PD.Values<ParamDefinition> : {}
type CT = typeof channelParam extends (...args: any[]) => (infer T) ? T : never
export type ChannelParams = CT extends PD.Group<infer T> ? T : {}
type ChannelsInfo = { [name in ChannelType]?: { isoValue: VolumeIsoValue, color: Color, wireframe: boolean, opacity: number } }
type ChannelsData = { [name in 'EM' | '2FO-FC' | 'FO-FC']?: VolumeData }
@@ -113,16 +134,23 @@ export namespace VolumeStreaming {
}
export type Channels = { [name in ChannelType]?: ChannelInfo }
export type DefaultChannelParams = { [name in ChannelType]?: Partial<ChannelParams> }
export class Behavior extends PluginBehavior.WithSubscribers<Params> {
private cache = LRUCache.create<ChannelsData>(25);
public params: Params = {} as any;
private lastLoci: StructureElement.Loci | EmptyLoci = EmptyLoci;
private ref: string = '';
public infoMap: Map<string, VolumeServerInfo.EntryData>
channels: Channels = {}
public get info () {
return this.infoMap.get(this.params.entry.name)!
}
private async queryData(box?: Box3D) {
let url = urlCombine(this.info.serverUrl, `${this.info.kind}/${this.info.dataId.toLowerCase()}`);
let url = urlCombine(this.data.serverUrl, `${this.info.kind}/${this.info.dataId.toLowerCase()}`);
if (box) {
const { min: a, max: b } = box;
@@ -132,7 +160,7 @@ export namespace VolumeStreaming {
} else {
url += `/cell`;
}
url += `?detail=${this.params.detailLevel}`;
url += `?detail=${this.params.entry.params.detailLevel}`;
let data = LRUCache.get(this.cache, url);
if (data) {
@@ -165,24 +193,30 @@ export namespace VolumeStreaming {
const block = parsed.result.blocks[i];
const densityServerCif = CIF.schema.densityServer(block);
const volume = await this.plugin.runTask(await volumeFromDensityServerData(densityServerCif));
const volume = await this.plugin.runTask(volumeFromDensityServerData(densityServerCif));
(ret as any)[block.header as any] = volume;
}
return ret;
}
private updateDynamicBox(box: Box3D) {
if (this.params.view.name !== 'selection-box') return;
if (this.params.entry.params.view.name !== 'selection-box') return;
const state = this.plugin.state.dataState;
const newParams: Params = {
...this.params,
view: {
name: 'selection-box' as 'selection-box',
entry: {
name: this.params.entry.name,
params: {
radius: this.params.view.params.radius,
bottomLeft: box.min,
topRight: box.max
...this.params.entry.params,
view: {
name: 'selection-box' as 'selection-box',
params: {
radius: this.params.entry.params.view.params.radius,
bottomLeft: box.min,
topRight: box.max
}
}
}
}
};
@@ -214,7 +248,7 @@ export namespace VolumeStreaming {
this.subscribeObservable(this.plugin.behaviors.interaction.click, ({ current, buttons, modifiers }) => {
if (!Binding.match((this.params.bindings && this.params.bindings.clickVolumeAroundOnly) || DefaultBindings.clickVolumeAroundOnly, buttons, modifiers)) return;
if (this.params.view.name !== 'selection-box') {
if (this.params.entry.params.view.name !== 'selection-box') {
this.lastLoci = this.getNormalizedLoci(current.loci);
} else {
this.updateInteraction(current);
@@ -235,7 +269,7 @@ export namespace VolumeStreaming {
}
private getBoxFromLoci(loci: StructureElement.Loci | EmptyLoci): Box3D {
if (isEmptyLoci(loci) || StructureElement.Loci.isEmpty(loci)) {
if (Loci.isEmpty(loci)) {
return Box3D.empty();
}
@@ -272,34 +306,34 @@ export namespace VolumeStreaming {
}
async update(params: Params) {
const switchedToSelection = params.view.name === 'selection-box' && this.params && this.params.view && this.params.view.name !== 'selection-box';
const switchedToSelection = params.entry.params.view.name === 'selection-box' && this.params && this.params.entry && this.params.entry.params && this.params.entry.params.view && this.params.entry.params.view.name !== 'selection-box';
this.params = params;
let box: Box3D | undefined = void 0, emptyData = false;
switch (params.view.name) {
switch (params.entry.params.view.name) {
case 'off':
emptyData = true;
break;
case 'box':
box = Box3D.create(params.view.params.bottomLeft, params.view.params.topRight);
box = Box3D.create(params.entry.params.view.params.bottomLeft, params.entry.params.view.params.topRight);
emptyData = Box3D.volume(box) < 0.0001;
break;
case 'selection-box': {
if (switchedToSelection) {
box = this.getBoxFromLoci(this.lastLoci) || Box3D.empty();
} else {
box = Box3D.create(Vec3.clone(params.view.params.bottomLeft), Vec3.clone(params.view.params.topRight));
box = Box3D.create(Vec3.clone(params.entry.params.view.params.bottomLeft), Vec3.clone(params.entry.params.view.params.topRight));
}
const r = params.view.params.radius;
const r = params.entry.params.view.params.radius;
emptyData = Box3D.volume(box) < 0.0001;
Box3D.expand(box, box, Vec3.create(r, r, r));
break;
}
case 'cell':
box = this.info.kind === 'x-ray'
? this.info.structure.boundary.box
? this.data.structure.boundary.box
: void 0;
break;
}
@@ -308,7 +342,7 @@ export namespace VolumeStreaming {
if (!data) return false;
const info = params.channels as ChannelsInfo;
const info = params.entry.params.channels as ChannelsInfo;
if (this.info.kind === 'x-ray') {
this.channels['2fo-fc'] = this.createChannel(data['2FO-FC'] || VolumeData.One, info['2fo-fc'], this.info.header.sampling[0].valuesInfo[0]);
@@ -333,14 +367,17 @@ export namespace VolumeStreaming {
}
getDescription() {
if (this.params.view.name === 'selection-box') return 'Selection';
if (this.params.view.name === 'box') return 'Static Box';
if (this.params.view.name === 'cell') return 'Cell';
if (this.params.entry.params.view.name === 'selection-box') return 'Selection';
if (this.params.entry.params.view.name === 'box') return 'Static Box';
if (this.params.entry.params.view.name === 'cell') return 'Cell';
return '';
}
constructor(public plugin: PluginContext, public info: VolumeServerInfo.Data) {
constructor(public plugin: PluginContext, public data: VolumeServerInfo.Data) {
super(plugin, {} as any);
this.infoMap = new Map<string, VolumeServerInfo.EntryData>()
this.data.entries.forEach(info => this.infoMap.set(info.dataId, info))
}
}
}

View File

@@ -2,6 +2,7 @@
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { PluginStateObject } from '../../../state/objects';
@@ -12,13 +13,16 @@ export class VolumeServerInfo extends PluginStateObject.Create<VolumeServerInfo.
export namespace VolumeServerInfo {
export type Kind = 'x-ray' | 'em'
export interface Data {
serverUrl: string,
export interface EntryData {
kind: Kind,
// for em, the EMDB access code, for x-ray, the PDB id
dataId: string,
header: VolumeServerHeader,
emDefaultContourLevel?: VolumeIsoValue,
}
export interface Data {
serverUrl: string,
entries: EntryData[],
structure: Structure
}
}

View File

@@ -14,7 +14,7 @@ import { urlCombine } from '../../../../mol-util/url';
import { createIsoValueParam } from '../../../../mol-repr/volume/isosurface';
import { VolumeIsoValue } from '../../../../mol-model/volume';
import { StateAction, StateObject, StateTransformer } from '../../../../mol-state';
import { getStreamingMethod, getId, getContourLevel, getEmdbId } from './util';
import { getStreamingMethod, getIds, getContourLevel, getEmdbIds } from './util';
import { VolumeStreaming } from './behavior';
import { VolumeRepresentation3DHelpers } from '../../../../mol-plugin/state/transforms/representation';
import { BuiltInVolumeRepresentations } from '../../../../mol-repr/volume/registry';
@@ -22,48 +22,72 @@ import { createTheme } from '../../../../mol-theme/theme';
import { Box3D } from '../../../../mol-math/geometry';
import { Vec3 } from '../../../../mol-math/linear-algebra';
function addEntry(entries: InfoEntryProps[], method: VolumeServerInfo.Kind, dataId: string, emDefaultContourLevel: number) {
entries.push({
source: method === 'em'
? { name: 'em', params: { isoValue: VolumeIsoValue.absolute(emDefaultContourLevel || 0) } }
: { name: 'x-ray', params: { } },
dataId
})
}
export const InitVolumeStreaming = StateAction.build({
display: { name: 'Volume Streaming' },
from: SO.Molecule.Structure,
params(a) {
const method = getStreamingMethod(a && a.data);
const id = getId(a && a.data);
const ids = getIds(method, a && a.data);
return {
method: PD.Select<VolumeServerInfo.Kind>(method, [['em', 'EM'], ['x-ray', 'X-Ray']]),
id: PD.Text(id),
serverUrl: PD.Text('https://ds.litemol.org'),
entries: PD.ObjectList({ id: PD.Text(ids[0] || '') }, ({ id }) => id, { defaultValue: ids.map(id => ({ id })) }),
defaultView: PD.Select<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box', VolumeStreaming.ViewTypeOptions as any),
behaviorRef: PD.Text('', { isHidden: true }),
emContourProvider: PD.Select<'wwpdb' | 'pdbe'>('wwpdb', [['wwpdb', 'wwPDB'], ['pdbe', 'PDBe']], { isHidden: true }),
bindings: PD.Value(VolumeStreaming.DefaultBindings, { isHidden: true }),
options: PD.Group({
serverUrl: PD.Text('https://ds.litemol.org'),
behaviorRef: PD.Text('', { isHidden: true }),
emContourProvider: PD.Select<'wwpdb' | 'pdbe'>('wwpdb', [['wwpdb', 'wwPDB'], ['pdbe', 'PDBe']], { isHidden: true }),
bindings: PD.Value(VolumeStreaming.DefaultBindings, { isHidden: true }),
channelParams: PD.Value<VolumeStreaming.DefaultChannelParams>({}, { isHidden: true })
})
};
},
isApplicable: (a) => a.data.models.length === 1
})(({ ref, state, params }, plugin: PluginContext) => Task.create('Volume Streaming', async taskCtx => {
let dataId = params.id.toLowerCase(), emDefaultContourLevel: number | undefined;
if (params.method === 'em') {
await taskCtx.update('Getting EMDB info...');
if (!dataId.toUpperCase().startsWith('EMD')) {
dataId = await getEmdbId(plugin, taskCtx, dataId)
const entries: InfoEntryProps[] = []
for (let i = 0, il = params.entries.length; i < il; ++i) {
let dataId = params.entries[i].id.toLowerCase()
let emDefaultContourLevel: number | undefined;
if (params.method === 'em') {
// if pdb ids are given for method 'em', get corresponding emd ids
// and continue the loop
if (!dataId.toUpperCase().startsWith('EMD')) {
await taskCtx.update('Getting EMDB info...');
const emdbIds = await getEmdbIds(plugin, taskCtx, dataId)
for (let j = 0, jl = emdbIds.length; j < jl; ++j) {
const emdbId = emdbIds[j]
const contourLevel = await getContourLevel(params.options.emContourProvider, plugin, taskCtx, emdbId)
addEntry(entries, params.method, emdbId, contourLevel || 0)
}
continue;
}
emDefaultContourLevel = await getContourLevel(params.options.emContourProvider, plugin, taskCtx, dataId);
}
const contourLevel = await getContourLevel(params.emContourProvider, plugin, taskCtx, dataId);
emDefaultContourLevel = contourLevel || 0;
addEntry(entries, params.method, dataId, emDefaultContourLevel || 0)
}
const infoTree = state.build().to(ref)
.apply(CreateVolumeStreamingInfo, {
serverUrl: params.serverUrl,
source: params.method === 'em'
? { name: 'em', params: { isoValue: VolumeIsoValue.absolute(emDefaultContourLevel || 0) } }
: { name: 'x-ray', params: { } },
dataId
serverUrl: params.options.serverUrl,
entries
});
const infoObj = await state.updateTree(infoTree).runInContext(taskCtx);
const behTree = state.build().to(infoTree.ref).apply(CreateVolumeStreamingBehavior,
PD.getDefaultValues(VolumeStreaming.createParams(infoObj.data, params.defaultView, params.bindings)),
{ ref: params.behaviorRef ? params.behaviorRef : void 0 });
PD.getDefaultValues(VolumeStreaming.createParams({ data: infoObj.data, defaultView: params.defaultView, binding: params.options.bindings, channelParams: params.options.channelParams })),
{ ref: params.options.behaviorRef ? params.options.behaviorRef : void 0 });
if (params.method === 'em') {
behTree.apply(VolumeStreamingVisual, { channel: 'em' }, { state: { isGhost: true } });
@@ -78,26 +102,43 @@ export const InitVolumeStreaming = StateAction.build({
export const BoxifyVolumeStreaming = StateAction.build({
display: { name: 'Boxify Volume Streaming', description: 'Make the current box permanent.' },
from: VolumeStreaming,
isApplicable: (a) => a.data.params.view.name === 'selection-box'
isApplicable: (a) => a.data.params.entry.params.view.name === 'selection-box'
})(({ a, ref, state }, plugin: PluginContext) => {
const params = a.data.params;
if (params.view.name !== 'selection-box') return;
const box = Box3D.create(Vec3.clone(params.view.params.bottomLeft), Vec3.clone(params.view.params.topRight));
const r = params.view.params.radius;
if (params.entry.params.view.name !== 'selection-box') return;
const box = Box3D.create(Vec3.clone(params.entry.params.view.params.bottomLeft), Vec3.clone(params.entry.params.view.params.topRight));
const r = params.entry.params.view.params.radius;
Box3D.expand(box, box, Vec3.create(r, r, r));
const newParams: VolumeStreaming.Params = {
...params,
view: {
name: 'box' as 'box',
entry: {
name: params.entry.name,
params: {
bottomLeft: box.min,
topRight: box.max
...params.entry.params,
view: {
name: 'box' as 'box',
params: {
bottomLeft: box.min,
topRight: box.max
}
}
}
}
};
return state.updateTree(state.build().to(ref).update(newParams));
});
const InfoEntryParams = {
dataId: PD.Text(''),
source: PD.MappedStatic('x-ray', {
'em': PD.Group({
isoValue: createIsoValueParam(VolumeIsoValue.relative(1))
}),
'x-ray': PD.Group({ })
})
}
type InfoEntryProps = PD.Values<typeof InfoEntryParams>
export { CreateVolumeStreamingInfo }
type CreateVolumeStreamingInfo = typeof CreateVolumeStreamingInfo
const CreateVolumeStreamingInfo = PluginStateTransform.BuiltIn({
@@ -108,30 +149,34 @@ const CreateVolumeStreamingInfo = PluginStateTransform.BuiltIn({
params(a) {
return {
serverUrl: PD.Text('https://ds.litemol.org'),
source: PD.MappedStatic('x-ray', {
'em': PD.Group({
isoValue: createIsoValueParam(VolumeIsoValue.relative(1))
}),
'x-ray': PD.Group({ })
entries: PD.ObjectList<InfoEntryProps>(InfoEntryParams, ({ dataId }) => dataId, {
defaultValue: [{ dataId: '', source: { name: 'x-ray', params: {} } }]
}),
dataId: PD.Text('')
};
}
})({
apply: ({ a, params }, plugin: PluginContext) => Task.create('', async taskCtx => {
const dataId = params.dataId;
const emDefaultContourLevel = params.source.name === 'em' ? params.source.params.isoValue : VolumeIsoValue.relative(1);
await taskCtx.update('Getting server header...');
const header = await plugin.fetch<VolumeServerHeader>({ url: urlCombine(params.serverUrl, `${params.source.name}/${dataId.toLocaleLowerCase()}`), type: 'json' }).runInContext(taskCtx);
const entries: VolumeServerInfo.EntryData[] = []
for (let i = 0, il = params.entries.length; i < il; ++i) {
const e = params.entries[i]
const dataId = e.dataId;
const emDefaultContourLevel = e.source.name === 'em' ? e.source.params.isoValue : VolumeIsoValue.relative(1);
await taskCtx.update('Getting server header...');
const header = await plugin.fetch<VolumeServerHeader>({ url: urlCombine(params.serverUrl, `${e.source.name}/${dataId.toLocaleLowerCase()}`), type: 'json' }).runInContext(taskCtx);
entries.push({
dataId,
kind: e.source.name,
header,
emDefaultContourLevel
})
}
const data: VolumeServerInfo.Data = {
serverUrl: params.serverUrl,
dataId,
kind: params.source.name,
header,
emDefaultContourLevel,
entries,
structure: a.data
};
return new VolumeServerInfo(data, { label: `Volume Server: ${dataId}` });
return new VolumeServerInfo(data, { label: 'Volume Server', description: `${entries.map(e => e.dataId). join(', ')}` });
})
});
@@ -143,21 +188,29 @@ const CreateVolumeStreamingBehavior = PluginStateTransform.BuiltIn({
from: VolumeServerInfo,
to: VolumeStreaming,
params(a) {
return VolumeStreaming.createParams(a && a.data);
return VolumeStreaming.createParams({ data: a && a.data });
}
})({
canAutoUpdate: ({ oldParams, newParams }) => {
return oldParams.view === newParams.view
|| newParams.view.name === 'selection-box'
|| newParams.view.name === 'off';
return oldParams.entry.params.view === newParams.entry.params.view
|| newParams.entry.params.view.name === 'selection-box'
|| newParams.entry.params.view.name === 'off';
},
apply: ({ a, params }, plugin: PluginContext) => Task.create('Volume streaming', async _ => {
const behavior = new VolumeStreaming.Behavior(plugin, a.data);
await behavior.update(params);
return new VolumeStreaming(behavior, { label: 'Volume Streaming', description: behavior.getDescription() });
}),
update({ b, newParams }) {
update({ a, b, oldParams, newParams }) {
return Task.create('Update Volume Streaming', async _ => {
if (oldParams.entry.name !== newParams.entry.name) {
if ('em' in newParams.entry.params.channels) {
const { emDefaultContourLevel } = b.data.infoMap.get(newParams.entry.name)!
if (emDefaultContourLevel) {
newParams.entry.params.channels['em'].isoValue = emDefaultContourLevel
}
}
}
const ret = await b.data.update(newParams) ? StateTransformer.UpdateResult.Updated : StateTransformer.UpdateResult.Unchanged;
b.description = b.data.getDescription();
return ret;

View File

@@ -5,7 +5,7 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Structure } from '../../../../mol-model/structure';
import { Structure, Model } from '../../../../mol-model/structure';
import { VolumeServerInfo } from './model';
import { PluginContext } from '../../../../mol-plugin/context';
import { RuntimeContext } from '../../../../mol-task';
@@ -25,20 +25,34 @@ export function getStreamingMethod(s?: Structure, defaultKind: VolumeServerInfo.
return 'x-ray';
}
export function getId(s?: Structure): string {
if (!s) return ''
/** Returns EMD ID when available, otherwise falls back to PDB ID */
export function getEmIds(model: Model): string[] {
const ids: string[] = []
if (model.sourceData.kind !== 'mmCIF') return [ model.entryId ]
const model = s.models[0]
if (model.sourceData.kind !== 'mmCIF') return ''
const { db_id, db_name, content_type } = model.sourceData.data.pdbx_database_related
if (!db_name.isDefined) return [ model.entryId ]
const d = model.sourceData.data
for (let i = 0, il = d.pdbx_database_related._rowCount; i < il; ++i) {
if (d.pdbx_database_related.db_name.value(i).toUpperCase() === 'EMDB') {
return d.pdbx_database_related.db_id.value(i)
for (let i = 0, il = db_name.rowCount; i < il; ++i) {
if (db_name.value(i).toUpperCase() === 'EMDB' && content_type.value(i) === 'associated EM volume') {
ids.push(db_id.value(i))
}
}
return s.models.length > 0 ? s.models[0].entryId : ''
return ids
}
export function getXrayIds(model: Model): string[] {
return [ model.entryId ]
}
export function getIds(method: VolumeServerInfo.Kind, s?: Structure): string[] {
if (!s || !s.models.length) return []
const model = s.models[0]
switch (method) {
case 'em': return getEmIds(model)
case 'x-ray': return getXrayIds(model)
}
}
export async function getContourLevel(provider: 'wwpdb' | 'pdbe', plugin: PluginContext, taskCtx: RuntimeContext, emdbId: string) {
@@ -71,21 +85,21 @@ export async function getContourLevelPdbe(plugin: PluginContext, taskCtx: Runtim
return contourLevel;
}
export async function getEmdbId(plugin: PluginContext, taskCtx: RuntimeContext, pdbId: string) {
export async function getEmdbIds(plugin: PluginContext, taskCtx: RuntimeContext, pdbId: string) {
// TODO: parametrize to a differnt URL? in plugin settings perhaps
const summary = await plugin.fetch({ url: `https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary/${pdbId}`, type: 'json' }).runInContext(taskCtx);
const summaryEntry = summary && summary[pdbId];
let emdbId: string;
let emdbIds: string[] = [];
if (summaryEntry && summaryEntry[0] && summaryEntry[0].related_structures) {
const emdb = summaryEntry[0].related_structures.filter((s: any) => s.resource === 'EMDB');
const emdb = summaryEntry[0].related_structures.filter((s: any) => s.resource === 'EMDB' && s.relationship === 'associated EM volume');
if (!emdb.length) {
throw new Error(`No related EMDB entry found for '${pdbId}'.`);
}
emdbId = emdb[0].accession;
emdbIds.push(...emdb.map((e: { accession: string }) => e.accession));
} else {
throw new Error(`No related EMDB entry found for '${pdbId}'.`);
}
return emdbId
return emdbIds
}

View File

@@ -10,13 +10,12 @@ import { PluginComponent } from './component';
import { PluginContext } from './context';
import { PluginCommands } from './command';
// TODO: support collapsed state control orientation
export type PluginLayoutControlsDisplay = 'outside' | 'portrait' | 'landscape' | 'reactive'
export const PluginLayoutStateParams = {
isExpanded: PD.Boolean(false),
showControls: PD.Boolean(true),
outsideControls: PD.Boolean(true, { isHidden: true })
controlsDisplay: PD.Value<PluginLayoutControlsDisplay>('outside', { isHidden: true })
}
export type PluginLayoutStateProps = PD.Values<typeof PluginLayoutStateParams>
interface RootState {

View File

@@ -12,6 +12,10 @@
}
}
.msp-btn-collapse {
padding: 0 $control-spacing / 2;
}
.msp-btn, .msp-btn:active, .msp-btn-link:focus, .msp-btn:hover {
outline: none !important;
}

View File

@@ -13,13 +13,15 @@
}
.msp-sequence-wrapper {
word-break: break-word;
padding: $info-vertical-padding $control-spacing $info-vertical-padding $control-spacing;
word-break: break-all;
// use $control-spacing for top to have space for sequence numebrs
padding: $control-spacing $control-spacing $info-vertical-padding $control-spacing;
user-select: none;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
font-size: 90%;
line-height: 180%;
}
.msp-sequence-wrapper-non-empty {
@@ -30,4 +32,28 @@
span {
cursor: pointer;
}
.msp-sequence-residue-long {
margin: 0em 0.2em 0em 0.2em;
}
.msp-sequence-residue-long-begin {
margin: 0em 0.2em 0em 0em;
}
.msp-sequence-number {
color: $sequence-number-color;
word-break: keep-all;
cursor: default;
position: relative;
top: -1.1em;
left: 3.1em;
padding: 0px;
margin-left: -3em;
font-size: 80%;
}
.msp-sequence-number-long {
left: 3.3em;
}
}

View File

@@ -62,7 +62,7 @@
height: $row-height;
text-align-last: center;
background: none !important;
background: none;
padding: 0 $control-spacing;
> option[value = _] {

View File

@@ -3,27 +3,39 @@
.msp-layout-standard-outside {
position: absolute;
@import 'layout/controls-outside';
@import 'layout/controls-outside';
}
.msp-layout-standard-landscape {
position: absolute;
@import 'layout/controls-landscape';
@import 'layout/controls-landscape';
}
.msp-layout-standard-portrait {
position: absolute;
@import 'layout/controls-portrait';
@import 'layout/controls-portrait';
}
.msp-layout-expanded {
position: fixed;
.msp-layout-standard-reactive {
position: absolute;
@media (orientation:landscape) {
@import 'layout/controls-landscape';
};
};
@media (orientation:portrait) {
@import 'layout/controls-portrait';
} ;
};
}
.msp-layout-expanded {
position: fixed;
@media (orientation:landscape) {
@import 'layout/controls-landscape';
};
@media (orientation:portrait) {
@import 'layout/controls-portrait';
};
}

View File

@@ -80,4 +80,5 @@ $entity-tag-color: color-lower-contrast($font-color, 20%);
// sequence
$sequence-background: $default-background;
$sequence-number-color: $hover-font-color;
$sequence-select-width: 300px;

View File

@@ -28,7 +28,7 @@ import { ensureSecondaryStructure } from './helpers';
import { Script } from '../../../mol-script/script';
import { parse3DG } from '../../../mol-io/reader/3dg/parser';
import { trajectoryFrom3DG } from '../../../mol-model-formats/structure/3dg';
import { CompiledStructureSelectionQueries } from '../../util/structure-selection-helper';
import { StructureSelectionQueries } from '../../util/structure-selection-helper';
export { TrajectoryFromBlob };
export { TrajectoryFromMmCif };
@@ -564,18 +564,18 @@ const StructureComplexElement = PluginStateTransform.BuiltIn({
let query: StructureQuery, label: string;
switch (params.type) {
case 'protein-and-nucleic': query = CompiledStructureSelectionQueries.proteinAndNucleic; label = 'Sequence'; break;
case 'protein-and-nucleic': query = StructureSelectionQueries.proteinAndNucleic.query; label = 'Sequence'; break;
case 'protein': query = CompiledStructureSelectionQueries.protein; label = 'Protein'; break;
case 'nucleic': query = CompiledStructureSelectionQueries.nucleic; label = 'Nucleic'; break;
case 'protein': query = StructureSelectionQueries.protein.query; label = 'Protein'; break;
case 'nucleic': query = StructureSelectionQueries.nucleic.query; label = 'Nucleic'; break;
case 'water': query = Queries.internal.water(); label = 'Water'; break;
case 'branched': query = CompiledStructureSelectionQueries.branchedPlusConnected; label = 'Branched'; break;
case 'ligand': query = CompiledStructureSelectionQueries.ligandPlusConnected; label = 'Ligand'; break;
case 'branched': query = StructureSelectionQueries.branchedPlusConnected.query; label = 'Branched'; break;
case 'ligand': query = StructureSelectionQueries.ligandPlusConnected.query; label = 'Ligand'; break;
case 'modified': query = CompiledStructureSelectionQueries.modified; label = 'Modified'; break;
case 'modified': query = StructureSelectionQueries.modified.query; label = 'Modified'; break;
case 'coarse': query = CompiledStructureSelectionQueries.coarse; label = 'Coarse'; break;
case 'coarse': query = StructureSelectionQueries.coarse.query; label = 'Coarse'; break;
case 'atomic-sequence': query = Queries.internal.atomicSequence(); label = 'Sequence'; break;
case 'atomic-het': query = Queries.internal.atomicHet(); label = 'HET Groups/Ligands'; break;

View File

@@ -84,7 +84,7 @@ export abstract class CollapsableControls<P extends CollapsableProps = Collapsab
return <div className={wrapClass}>
<div className='msp-transform-header'>
<button className='msp-btn msp-btn-block' onClick={this.toggleCollapsed}>
<button className='msp-btn msp-btn-block msp-btn-collapse' onClick={this.toggleCollapsed}>
<span className={`msp-icon msp-icon-${this.state.isCollapsed ? 'expand' : 'collapse'}`} />
{this.state.header}
</button>

View File

@@ -260,7 +260,7 @@ export class ExpandableGroup extends React.Component<{
}
}
export class ButtonSelect extends React.PureComponent<{ label: string, onChange: (value: string) => void }> {
export class ButtonSelect extends React.PureComponent<{ label: string, onChange: (value: string) => void, disabled?: boolean }> {
onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
e.preventDefault()
@@ -269,7 +269,7 @@ export class ButtonSelect extends React.PureComponent<{ label: string, onChange:
}
render() {
return <select value='_' onChange={this.onChange}>
return <select value='_' onChange={this.onChange} disabled={this.props.disabled}>
<option key='_' value='_'>{this.props.label}</option>
{this.props.children}
</select>

View File

@@ -34,7 +34,7 @@ export class ParameterControls<P extends PD.Params> extends React.PureComponent<
const params = this.props.params;
const values = this.props.values;
const keys = Object.keys(params);
if (keys.length === 0) return null;
if (keys.length === 0 || values === undefined) return null;
return <>
{keys.map(key => {
const param = params[key];

View File

@@ -14,11 +14,12 @@ import { ParameterControls, ParamOnChange } from '../controls/parameters';
import { Slider } from '../controls/slider';
import { VolumeIsoValue, VolumeData } from '../../../mol-model/volume';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { ColorNames } from '../../../mol-util/color/names';
const ChannelParams = {
color: PD.Color(0 as any),
wireframe: PD.Boolean(false),
opacity: PD.Numeric(0.3, { min: 0, max: 1, step: 0.01 })
color: PD.Color(ColorNames.black, { description: 'Display color of the volume.' }),
wireframe: PD.Boolean(false, { description: 'Control display of the volume as a wireframe.' }),
opacity: PD.Numeric(0.3, { min: 0, max: 1, step: 0.01 }, { description: 'Opacity of the volume.' })
};
type ChannelParams = PD.Values<typeof ChannelParams>
@@ -61,14 +62,20 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
}
changeIso = (name: string, value: number, isRelative: boolean) => {
const old = this.props.params;
const old = this.props.params as VolumeStreaming.Params
this.newParams({
...old,
channels: {
...old.channels,
[name]: {
...old.channels[name],
isoValue: isRelative ? VolumeIsoValue.relative(value) : VolumeIsoValue.absolute(value)
entry: {
name: old.entry.name,
params: {
...old.entry.params,
channels: {
...old.entry.params.channels,
[name]: {
...(old.entry.params.channels as any)[name],
isoValue: isRelative ? VolumeIsoValue.relative(value) : VolumeIsoValue.absolute(value)
}
}
}
}
});
@@ -78,11 +85,17 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
const old = this.props.params;
this.newParams({
...old,
channels: {
...old.channels,
[name]: {
...old.channels[name],
[param]: value
entry: {
name: old.entry.name,
params: {
...old.entry.params,
channels: {
...old.entry.params.channels,
[name]: {
...(old.entry.params.channels as any)[name],
[param]: value
}
}
}
}
});
@@ -94,41 +107,62 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
: VolumeIsoValue.toAbsolute(channel.isoValue, stats) }
}
changeOption: ParamOnChange = ({ value }) => {
const b = (this.props.b as VolumeStreaming).data;
const isEM = b.info.kind === 'em';
changeOption: ParamOnChange = ({ name, value }) => {
const old = this.props.params as VolumeStreaming.Params
const isRelative = value.params.isRelative;
const sampling = b.info.header.sampling[0];
const old = this.props.params as VolumeStreaming.Params, oldChannels = old.channels as any;
const oldView = old.view.name === value.name
? old.view.params
: (this.props.info.params as VolumeStreaming.ParamDefinition).view.map(value.name).defaultValue;
const viewParams = { ...oldView };
if (value.name === 'selection-box') {
viewParams.radius = value.params.radius;
} else if (value.name === 'box') {
viewParams.bottomLeft = value.params.bottomLeft;
viewParams.topRight = value.params.topRight;
}
this.newParams({
...old,
view: {
name: value.name,
params: viewParams
},
detailLevel: value.params.detailLevel,
channels: isEM
? { em: this.convert(oldChannels.em, sampling.valuesInfo[0], isRelative) }
: {
'2fo-fc': this.convert(oldChannels['2fo-fc'], sampling.valuesInfo[0], isRelative),
'fo-fc(+ve)': this.convert(oldChannels['fo-fc(+ve)'], sampling.valuesInfo[1], isRelative),
'fo-fc(-ve)': this.convert(oldChannels['fo-fc(-ve)'], sampling.valuesInfo[1], isRelative)
if (name === 'entry') {
this.newParams({
...old,
entry: {
name: value,
params: old.entry.params,
}
});
});
} else {
const b = (this.props.b as VolumeStreaming).data;
const isEM = b.info.kind === 'em';
const isRelative = value.params.isRelative;
const sampling = b.info.header.sampling[0];
const oldChannels = old.entry.params.channels as any;
const oldView = old.entry.params.view.name === value.name
? old.entry.params.view.params
: (((this.props.info.params as VolumeStreaming.ParamDefinition)
.entry.map(old.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>)
.params as VolumeStreaming.EntryParamDefinition)
.view.map(value.name).defaultValue;
const viewParams = { ...oldView };
if (value.name === 'selection-box') {
viewParams.radius = value.params.radius;
} else if (value.name === 'box') {
viewParams.bottomLeft = value.params.bottomLeft;
viewParams.topRight = value.params.topRight;
}
this.newParams({
...old,
entry: {
name: old.entry.name,
params: {
...old.entry.params,
view: {
name: value.name,
params: viewParams
},
detailLevel: value.params.detailLevel,
channels: isEM
? { em: this.convert(oldChannels.em, sampling.valuesInfo[0], isRelative) }
: {
'2fo-fc': this.convert(oldChannels['2fo-fc'], sampling.valuesInfo[0], isRelative),
'fo-fc(+ve)': this.convert(oldChannels['fo-fc(+ve)'], sampling.valuesInfo[1], isRelative),
'fo-fc(-ve)': this.convert(oldChannels['fo-fc(-ve)'], sampling.valuesInfo[1], isRelative)
}
}
}
});
}
};
render() {
@@ -139,50 +173,54 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
const pivot = isEM ? 'em' : '2fo-fc';
const params = this.props.params as VolumeStreaming.Params;
const isRelative = ((params.channels as any)[pivot].isoValue as VolumeIsoValue).kind === 'relative';
const detailLevel = ((this.props.info.params as VolumeStreaming.ParamDefinition)
.entry.map(params.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>).params.detailLevel
const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as VolumeIsoValue).kind === 'relative';
const sampling = b.info.header.sampling[0];
// TODO: factor common things out
const OptionsParams = {
view: PD.MappedStatic(params.view.name, {
entry: PD.Select(params.entry.name, b.data.entries.map(info => [info.dataId, info.dataId] as [string, string]), { description: 'Which entry with volume data to display.' }),
view: PD.MappedStatic(params.entry.params.view.name, {
'off': PD.Group({}, { description: 'Display off.' }),
'box': PD.Group({
bottomLeft: PD.Vec3(Vec3.zero()),
topRight: PD.Vec3(Vec3.zero()),
detailLevel: this.props.info.params.detailLevel,
detailLevel,
isRelative: PD.Boolean(isRelative, { description: 'Use relative or absolute iso values.' })
}, { description: 'Static box defined by cartesian coords.' }),
'selection-box': PD.Group({
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }),
detailLevel: this.props.info.params.detailLevel,
radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }),
detailLevel,
isRelative: PD.Boolean(isRelative, { description: 'Use relative or absolute iso values.' })
}, { description: 'Box around last-interacted element.' }),
'cell': PD.Group({
detailLevel: this.props.info.params.detailLevel,
detailLevel,
isRelative: PD.Boolean(isRelative, { description: 'Use relative or absolute iso values.' })
}, { description: 'Box around the structure\'s bounding box.' }),
// 'auto': PD.Group({ }), // TODO based on camera distance/active selection/whatever, show whole structure or slice.
}, { options: [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Surroundings'], ['cell', 'Whole Structure']] })
}, { options: VolumeStreaming.ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Interaction" shows the volume around the element/atom last interacted with. "Whole Structure" shows the volume for the whole structure.' })
};
const options = {
entry: params.entry.name,
view: {
name: params.view.name,
name: params.entry.params.view.name,
params: {
detailLevel: params.detailLevel,
radius: (params.view.params as any).radius,
bottomLeft: (params.view.params as any).bottomLeft,
topRight: (params.view.params as any).topRight,
detailLevel: params.entry.params.detailLevel,
radius: (params.entry.params.view.params as any).radius,
bottomLeft: (params.entry.params.view.params as any).bottomLeft,
topRight: (params.entry.params.view.params as any).topRight,
isRelative
}
}
};
return <>
{!isEM && <Channel label='2Fo-Fc' name='2fo-fc' channels={params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} />}
{!isEM && <Channel label='Fo-Fc(+ve)' name='fo-fc(+ve)' channels={params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} />}
{!isEM && <Channel label='Fo-Fc(-ve)' name='fo-fc(-ve)' channels={params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} />}
{isEM && <Channel label='EM' name='em' channels={params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} />}
{!isEM && <Channel label='2Fo-Fc' name='2fo-fc' channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} />}
{!isEM && <Channel label='Fo-Fc(+ve)' name='fo-fc(+ve)' channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} />}
{!isEM && <Channel label='Fo-Fc(-ve)' name='fo-fc(-ve)' channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} />}
{isEM && <Channel label='EM' name='em' channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} />}
<ParameterControls onChange={this.changeOption} params={OptionsParams} values={options} onEnter={this.props.events.onEnter} />
</>

View File

@@ -19,6 +19,8 @@ interface ImageControlsState extends CollapsableState {
size: 'canvas' | 'custom'
width: number
height: number
isDisabled: boolean
}
const maxWidthUi = 260
@@ -64,7 +66,9 @@ export class ImageControls<P, S extends ImageControlsState> extends CollapsableC
}
setCanvasSize(this.canvas, w, h)
const { pixelRatio } = this.plugin.canvas3d.webgl
const imageData = this.imagePass.getImageData(w * pixelRatio, h * pixelRatio)
const pw = Math.round(w * pixelRatio)
const ph = Math.round(h * pixelRatio)
const imageData = this.imagePass.getImageData(pw, ph)
this.canvasContext.putImageData(imageData, 0, 0)
}
@@ -85,8 +89,8 @@ export class ImageControls<P, S extends ImageControlsState> extends CollapsableC
canvasCtx.putImageData(imageData, 0, 0)
await ctx.update('Downloading image...')
const blob = await canvasToBlob(canvas)
download(blob, 'molstar-image')
const blob = await canvasToBlob(canvas, 'png')
download(blob, 'molstar-image.png')
})
}
@@ -132,7 +136,11 @@ export class ImageControls<P, S extends ImageControlsState> extends CollapsableC
this.handlePreview()
})
this.subscribe(this.plugin.canvas3d.didDraw, () => this.handlePreview())
this.subscribe(this.plugin.canvas3d.didDraw, () => {
this.handlePreview()
})
this.subscribe(this.plugin.state.dataState.events.isUpdating, v => this.setState({ isDisabled: v }))
}
private togglePreview = () => this.setState({ showPreview: !this.state.showPreview })
@@ -154,8 +162,8 @@ export class ImageControls<P, S extends ImageControlsState> extends CollapsableC
size: PD.MappedStatic('custom', {
canvas: PD.Group({}),
custom: PD.Group({
width: PD.Numeric(width, { min: 1, max, step: 1 }),
height: PD.Numeric(height, { min: 1, max, step: 1 }),
width: PD.Numeric(width, { min: 128, max, step: 1 }),
height: PD.Numeric(height, { min: 128, max, step: 1 }),
}, { isFlat: true })
}, { options: [['canvas', 'Canvas'], ['custom', 'Custom']] })
}
@@ -176,16 +184,18 @@ export class ImageControls<P, S extends ImageControlsState> extends CollapsableC
size: 'canvas',
width: 1920,
height: 1080
height: 1080,
isDisabled: false
} as S
}
protected renderControls() {
return <div>
<div className='msp-control-row'>
<button className='msp-btn msp-btn-block' onClick={this.download}>Download</button>
<button className='msp-btn msp-btn-block' onClick={this.download} disabled={this.state.isDisabled}>Download</button>
</div>
<ParameterControls params={this.params} values={this.values} onChange={this.setProps} />
<ParameterControls params={this.params} values={this.values} onChange={this.setProps} isDisabled={this.state.isDisabled} />
<div className='msp-control-group-wrapper'>
<div className='msp-control-group-header'>
<button className='msp-btn msp-btn-block' onClick={this.togglePreview}>

View File

@@ -93,12 +93,7 @@ class Layout extends PluginUIComponent {
if (layout.isExpanded) {
classList.push('msp-layout-expanded')
} else {
classList.push('msp-layout-standard')
if (layout.outsideControls) {
classList.push('msp-layout-standard-outside')
} else {
classList.push('msp-layout-standard-landscape')
}
classList.push('msp-layout-standard', `msp-layout-standard-${layout.controlsDisplay}`)
}
return classList.join(' ')
@@ -254,7 +249,7 @@ export class CurrentObject extends PluginUIComponent {
return <>
{(cell.status === 'ok' || cell.status === 'error') && <UpdateTransformControl state={current.state} transform={transform} /> }
{cell.status === 'ok' && <StateObjectActions state={current.state} nodeRef={ref} initiallyColapsed />}
{cell.status === 'ok' && <StateObjectActions state={current.state} nodeRef={ref} initiallyCollapsed />}
</>;
}
}

View File

@@ -58,7 +58,7 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
getLoci(seqIdx: number) {
const query = createResidueQuery(this.data.unit.id, this.seqId(seqIdx));
return StructureSelection.toLoci2(StructureQuery.run(query, this.data.structure));
return StructureSelection.toLociWithSourceUnits(StructureQuery.run(query, this.data.structure));
}
constructor(data: StructureUnit) {

View File

@@ -11,12 +11,19 @@ import { Interactivity } from '../../util/interactivity';
import { MarkerAction } from '../../../mol-util/marker-action';
import { ButtonsType, ModifiersKeys, getButtons, getModifiers } from '../../../mol-util/input/input-observer';
import { SequenceWrapper } from './wrapper';
import { StructureElement } from '../../../mol-model/structure';
import { StructureElement, StructureProperties, Unit } from '../../../mol-model/structure';
import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { Color } from '../../../mol-util/color';
type SequenceProps = { sequenceWrapper: SequenceWrapper.Any }
type SequenceProps = {
sequenceWrapper: SequenceWrapper.Any,
sequenceNumberPeriod?: number,
hideSequenceNumbers?: boolean
}
/** Note, if this is changed, the CSS for `msp-sequence-number` needs adjustment too */
const MaxSequenceNumberSize = 5
// TODO: this is somewhat inefficient and should be done using a canvas.
export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
@@ -34,6 +41,12 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
if (changed) this.updateMarker();
}
private get sequenceNumberPeriod() {
return this.props.sequenceNumberPeriod !== undefined
? this.props.sequenceNumberPeriod as number
: (this.props.sequenceWrapper.length > 10 ? 10 : 1)
}
componentDidMount() {
this.plugin.interactivity.lociHighlights.addProvider(this.lociHighlightProvider)
this.plugin.interactivity.lociSelects.addProvider(this.lociSelectionProvider)
@@ -92,19 +105,55 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
return marker === 0 ? '' : marker % 2 === 0 ? 'rgb(51, 255, 25)' /* selected */ : 'rgb(255, 102, 153)' /* highlighted */;
}
private getResidueClass(seqIdx: number, label: string) {
return label.length > 1
? (seqIdx === 0 ? 'msp-sequence-residue-long-begin' : 'msp-sequence-residue-long')
: void 0
}
private residue(seqIdx: number, label: string, marker: number, color: Color) {
const margin = label.length > 1 ? (seqIdx === 0 ? `0px 2px 0px 0px` : `0px 2px 0px 2px`) : void 0
return <span key={seqIdx} data-seqid={seqIdx} style={{ color: Color.toStyle(color), backgroundColor: this.getBackgroundColor(marker), margin }}>{label}</span>;
return <span key={seqIdx} data-seqid={seqIdx} style={{ color: Color.toStyle(color), backgroundColor: this.getBackgroundColor(marker) }} className={this.getResidueClass(seqIdx, label)}>{label}</span>;
}
private getSequenceNumberClass(seqIdx: number, label: string) {
return label.length > 1 && seqIdx > 0
? 'msp-sequence-number msp-sequence-number-long'
: 'msp-sequence-number'
}
private location = StructureElement.Location.create();
private padSeqNum(n: string) {
if (n.length < MaxSequenceNumberSize) return n + new Array(MaxSequenceNumberSize - n.length + 1).join('\u00A0');
return n;
}
private getSequenceNumber(seqIdx: number, label: string) {
let sequenceNumber = ''
const loci = this.props.sequenceWrapper.getLoci(seqIdx)
const l = StructureElement.Loci.getFirstLocation(loci, this.location);
if (l) {
if (Unit.isAtomic(l.unit)) {
const seqId = StructureProperties.residue.auth_seq_id(l)
const insCode = StructureProperties.residue.pdbx_PDB_ins_code(l)
sequenceNumber = `${seqId}${insCode ? insCode : ''}`
} else if (Unit.isCoarse(l.unit)) {
sequenceNumber = `${seqIdx + 1}`
}
}
return <span key={`marker-${seqIdx}`} className={this.getSequenceNumberClass(seqIdx, label)}>{this.padSeqNum(sequenceNumber)}</span>
}
private updateMarker() {
if (!this.parentDiv.current) return;
const xs = this.parentDiv.current.children;
const { markerArray } = this.props.sequenceWrapper;
const hasNumbers = !this.props.hideSequenceNumbers, period = this.sequenceNumberPeriod;
for (let i = 0, _i = markerArray.length; i < _i; i++) {
const span = xs[i] as HTMLSpanElement;
if (!span) continue;
let o = 0;
for (let i = 0, il = markerArray.length; i < il; i++) {
if (hasNumbers && i % period === 0 && i < il) o++;
const span = xs[o] as HTMLSpanElement;
if (!span) return;
o++;
const backgroundColor = this.getBackgroundColor(markerArray[i]);
if (span.style.backgroundColor !== backgroundColor) span.style.backgroundColor = backgroundColor;
@@ -147,9 +196,15 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
const sw = this.props.sequenceWrapper
const elems: JSX.Element[] = [];
const hasNumbers = !this.props.hideSequenceNumbers, period = this.sequenceNumberPeriod;
for (let i = 0, il = sw.length; i < il; ++i) {
elems[elems.length] = this.residue(i, sw.residueLabel(i), sw.markerArray[i], sw.residueColor(i));
// TODO: add seq idx markers every N residues? Would need to modify "updateMarker"
const label = sw.residueLabel(i)
// add sequence number before name so the html element do not get separated by a line-break
if (hasNumbers && i % period === 0 && i < il) {
elems[elems.length] = this.getSequenceNumber(i, label)
}
elems[elems.length] = this.residue(i, label, sw.markerArray[i], sw.residueColor(i));
}
// calling .updateMarker here is neccesary to ensure existing

View File

@@ -10,7 +10,7 @@ import { ApplyActionControl } from './apply-action';
import { State } from '../../../mol-state';
import { Icon } from '../controls/common';
export class StateObjectActions extends PluginUIComponent<{ state: State, nodeRef: string, hideHeader?: boolean, initiallyColapsed?: boolean }> {
export class StateObjectActions extends PluginUIComponent<{ state: State, nodeRef: string, hideHeader?: boolean, initiallyCollapsed?: boolean }> {
get current() {
return this.plugin.state.behavior.currentObject.value;
}
@@ -38,7 +38,7 @@ export class StateObjectActions extends PluginUIComponent<{ state: State, nodeRe
return <div className='msp-state-actions'>
{!this.props.hideHeader && <div className='msp-section-header'><Icon name='code' /> {`Actions (${display})`}</div> }
{actions.map((act, i) => <ApplyActionControl plugin={this.plugin} key={`${act.id}`} state={state} action={act} nodeRef={ref} initiallyCollapsed={this.props.initiallyColapsed} />)}
{actions.map((act, i) => <ApplyActionControl plugin={this.plugin} key={`${act.id}`} state={state} action={act} nodeRef={ref} initiallyCollapsed={this.props.initiallyCollapsed} />)}
</div>;
}
}

View File

@@ -186,7 +186,7 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
const { a, b } = this.getSourceAndTarget();
return <div className={wrapClass}>
<div className='msp-transform-header'>
<button className='msp-btn msp-btn-block' onClick={this.toggleExpanded} title={display.description}>
<button className='msp-btn msp-btn-block msp-btn-collapse' onClick={this.toggleExpanded} title={display.description}>
<span className={`msp-icon msp-icon-${this.state.isCollapsed ? 'expand' : 'collapse'}`} />
{display.name}
</button>

View File

@@ -5,7 +5,7 @@
*/
import * as React from 'react';
import { PluginUIComponent } from '../base';
import { PluginUIComponent, CollapsableState, CollapsableProps } from '../base';
import { Structure, StructureElement } from '../../../mol-model/structure';
import { isEmptyLoci } from '../../../mol-model/loci';
import { ColorOptions, ParameterControls } from '../controls/parameters';
@@ -16,15 +16,52 @@ import { VisualQuality, VisualQualityOptions } from '../../../mol-geo/geometry/b
import { StructureRepresentationPresets as P } from '../../util/structure-representation-helper';
import { camelCaseToWords } from '../../../mol-util/string';
import { CollapsableControls } from '../base';
import { StateSelection, StateObject } from '../../../mol-state';
import { PluginStateObject } from '../../state/objects';
abstract class BaseStructureRepresentationControls extends PluginUIComponent {
onChange = (value: string) => {
console.log('onChange', value)
}
interface BaseStructureRepresentationControlsState {
isDisabled: boolean
}
abstract class BaseStructureRepresentationControls extends PluginUIComponent<{}, BaseStructureRepresentationControlsState> {
abstract label: string
abstract lociGetter(structure: Structure): StructureElement.Loci
state = {
isDisabled: false
}
/** root structures */
protected get structures() {
return this.plugin.state.dataState.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure)).map(s => s.obj!.data)
}
/** applicable types */
private get types() {
const types: [string, string][] = []
const structures = this.structures
for (const e of this.plugin.structureRepresentation.registry.list) {
if (structures.some(s => e.provider.isApplicable(s))) {
types.push([e.name, e.provider.label])
}
}
return types
}
private forceUpdateIfStructure = (obj?: StateObject) => {
if (obj && obj.type === PluginStateObject.Molecule.Structure.type) {
this.forceUpdate()
}
}
componentDidMount() {
this.subscribe(this.plugin.events.state.object.created, ({ obj }) => this.forceUpdateIfStructure(obj))
this.subscribe(this.plugin.events.state.object.removed, ({ obj }) => this.forceUpdateIfStructure(obj))
this.subscribe(this.plugin.state.dataState.events.isUpdating, v => this.setState({ isDisabled: v }))
}
show = (value: string) => {
this.plugin.helpers.structureRepresentation.set('add', value, this.lociGetter)
}
@@ -46,21 +83,21 @@ abstract class BaseStructureRepresentationControls extends PluginUIComponent {
}
render() {
const { types } = this.plugin.structureRepresentation.registry
const TypeOptions = Options(this.types)
return <div className='msp-control-row'>
<span title={this.label}>{this.label}</span>
<div className='msp-select-row'>
<ButtonSelect label='Show' onChange={this.show}>
<optgroup label='Show'>{Options(types)}</optgroup>
<ButtonSelect label='Show' onChange={this.show} disabled={this.state.isDisabled}>
<optgroup label='Show'>{TypeOptions}</optgroup>
</ButtonSelect>
<ButtonSelect label='Hide' onChange={this.hide}>
<ButtonSelect label='Hide' onChange={this.hide} disabled={this.state.isDisabled}>
<optgroup label='Clear'>
<option key={'__all__'} value={'__all__'}>All</option>
</optgroup>
<optgroup label='Hide'>{Options(types)}</optgroup>
<optgroup label='Hide'>{TypeOptions}</optgroup>
</ButtonSelect>
<ButtonSelect label='Color' onChange={this.color}>
<ButtonSelect label='Color' onChange={this.color} disabled={this.state.isDisabled}>
<optgroup label='Clear'>
<option key={-1} value={-1}>Theme</option>
</optgroup>
@@ -86,7 +123,15 @@ class SelectionStructureRepresentationControls extends BaseStructureRepresentati
}
}
export class StructureRepresentationControls extends CollapsableControls {
interface StructureRepresentationControlsState extends CollapsableState {
isDisabled: boolean
}
export class StructureRepresentationControls extends CollapsableControls<CollapsableProps, StructureRepresentationControlsState> {
componentDidMount() {
this.subscribe(this.plugin.state.dataState.events.isUpdating, v => this.setState({ isDisabled: v }))
}
preset = async (value: string) => {
const presetFn = P[value as keyof typeof P]
if (presetFn) {
@@ -125,7 +170,9 @@ export class StructureRepresentationControls extends CollapsableControls {
defaultState() {
return {
isCollapsed: false,
header: 'Representation'
header: 'Representation',
isDisabled: false
}
}
@@ -145,7 +192,7 @@ export class StructureRepresentationControls extends CollapsableControls {
<EverythingStructureRepresentationControls />
<SelectionStructureRepresentationControls />
<ParameterControls params={this.params} values={this.values} onChange={this.onChange} />
<ParameterControls params={this.params} values={this.values} onChange={this.onChange} isDisabled={this.state.isDisabled} />
</div>
}
}

View File

@@ -12,7 +12,6 @@ import { PluginCommands } from '../../command';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Interactivity } from '../../util/interactivity';
import { ParameterControls } from '../controls/parameters';
import { camelCaseToWords } from '../../../mol-util/string';
const StructureSelectionParams = {
granularity: Interactivity.Params.granularity,
@@ -21,7 +20,9 @@ const StructureSelectionParams = {
interface StructureSelectionControlsState extends CollapsableState {
minRadius: number,
extraRadius: number,
durationMs: number
durationMs: number,
isDisabled: boolean
}
export class StructureSelectionControls<P, S extends StructureSelectionControlsState> extends CollapsableControls<P, S> {
@@ -33,6 +34,8 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
this.subscribe(this.plugin.events.interactivity.propsUpdated, () => {
this.forceUpdate()
});
this.subscribe(this.plugin.state.dataState.events.isUpdating, v => this.setState({ isDisabled: v }))
}
get stats() {
@@ -46,8 +49,8 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
focus = () => {
const { extraRadius, minRadius, durationMs } = this.state
if (this.plugin.helpers.structureSelectionManager.stats.elementCount === 0) return
const { sphere } = this.plugin.helpers.structureSelectionManager.getBoundary();
if (sphere.radius === 0) return
const radius = Math.max(sphere.radius + extraRadius, minRadius);
this.plugin.canvas3d.camera.focus(sphere.center, radius, durationMs);
}
@@ -66,7 +69,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
set = (modifier: SelectionModifier, value: string) => {
const query = StructureSelectionQueries[value as keyof typeof StructureSelectionQueries]
this.plugin.helpers.structureSelection.set(modifier, query)
this.plugin.helpers.structureSelection.set(modifier, query.query)
}
add = (value: string) => this.set('add', value)
@@ -80,36 +83,38 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
minRadius: 8,
extraRadius: 4,
durationMs: 250
durationMs: 250,
isDisabled: false
} as S
}
renderControls() {
const queries = Object.keys(StructureSelectionQueries).map(name => {
return [name, camelCaseToWords(name)] as [string, string]
return [name, StructureSelectionQueries[name as keyof typeof StructureSelectionQueries].label] as [string, string]
})
return <div>
<div className='msp-control-row msp-row-text'>
<button className='msp-btn msp-btn-block' onClick={this.focus}>
<span className={`msp-icon msp-icon-focus-on-visual`} style={{ position: 'absolute', left: '10px' }} />
<span className={`msp-icon msp-icon-focus-on-visual`} style={{ position: 'absolute', left: '5px' }} />
{this.stats}
</button>
</div>
<ParameterControls params={StructureSelectionParams} values={this.values} onChange={this.setProps} />
<ParameterControls params={StructureSelectionParams} values={this.values} onChange={this.setProps} isDisabled={this.state.isDisabled} />
<div className='msp-control-row'>
<div className='msp-select-row'>
<ButtonSelect label='Add' onChange={this.add}>
<ButtonSelect label='Add' onChange={this.add} disabled={this.state.isDisabled}>
<optgroup label='Add'>
{Options(queries)}
</optgroup>
</ButtonSelect>
<ButtonSelect label='Remove' onChange={this.remove}>
<ButtonSelect label='Remove' onChange={this.remove} disabled={this.state.isDisabled}>
<optgroup label='Remove'>
{Options(queries)}
</optgroup>
</ButtonSelect>
<ButtonSelect label='Only' onChange={this.only}>
<ButtonSelect label='Only' onChange={this.only} disabled={this.state.isDisabled}>
<optgroup label='Only'>
{Options(queries)}
</optgroup>

View File

@@ -66,8 +66,8 @@ export async function getUnitcellRepresentation(ctx: RuntimeContext, model: Mode
}
function getUnitcellLabel(data: UnitcellData) {
const { cell, name } = data.symmetry.spacegroup
const { index, size, anglesInRadians } = cell
const { cell, name, num } = data.symmetry.spacegroup
const { size, anglesInRadians } = cell
const a = size[0].toFixed(2)
const b = size[1].toFixed(2)
const c = size[2].toFixed(2)
@@ -76,7 +76,7 @@ function getUnitcellLabel(data: UnitcellData) {
const gamma = radToDeg(anglesInRadians[2]).toFixed(2)
const label: string[] = []
// name
label.push(`${name} #${index + 1}`)
label.push(`${name} #${num}`)
// sizes
label.push(`${a}\u00D7${b}\u00D7${c} \u212B`)
// angles

View File

@@ -161,6 +161,7 @@ class StructureElementSelectionManager {
if (!entry) return;
let xs = loci.elements[0];
if (!xs) return;
let e: StructureElement.Loci['elements'][0] | undefined;
for (const _e of entry.selection.elements) {
if (xs.unit === _e.unit) {

View File

@@ -100,7 +100,7 @@ export class StructureRepresentationHelper {
return this.set(modifier, type, (structure) => {
const compiled = compile<StructureSelection>(expression)
const result = compiled(new QueryContext(structure))
return StructureSelection.toLoci2(result)
return StructureSelection.toLociWithSourceUnits(result)
}, props)
}
@@ -199,34 +199,42 @@ export class StructureRepresentationHelper {
async function polymerAndLigand(r: StructureRepresentationHelper) {
await r.clear()
await r.setFromExpression('add', 'cartoon', Q.all)
await r.setFromExpression('add', 'carbohydrate', Q.all)
await r.setFromExpression('add', 'cartoon', Q.all.expression)
await r.setFromExpression('add', 'carbohydrate', Q.all.expression)
await r.setFromExpression('add', 'ball-and-stick', MS.struct.modifier.union([
MS.struct.combinator.merge([ Q.ligandPlusConnected, Q.branchedConnectedOnly, Q.water ])
MS.struct.combinator.merge([
Q.ligandPlusConnected.expression,
Q.branchedConnectedOnly.expression,
Q.water.expression
])
]))
}
async function proteinAndNucleic(r: StructureRepresentationHelper) {
await r.clear()
await r.setFromExpression('add', 'cartoon', Q.protein)
await r.setFromExpression('add', 'gaussian-surface', Q.nucleic)
await r.setFromExpression('add', 'cartoon', Q.protein.expression)
await r.setFromExpression('add', 'gaussian-surface', Q.nucleic.expression)
await r.setFromExpression('add', 'carbohydrate', Q.all)
await r.setFromExpression('add', 'carbohydrate', Q.all.expression)
await r.setFromExpression('add', 'ball-and-stick', MS.struct.modifier.union([
MS.struct.combinator.merge([ Q.ligandPlusConnected, Q.branchedConnectedOnly, Q.water ])
MS.struct.combinator.merge([
Q.ligandPlusConnected.expression,
Q.branchedConnectedOnly.expression,
Q.water.expression
])
]))
}
async function capsid(r: StructureRepresentationHelper) {
await r.clear()
await r.setFromExpression('add', 'gaussian-surface', Q.polymer, {
await r.setFromExpression('add', 'gaussian-surface', Q.polymer.expression, {
smoothness: 0.5,
})
}
async function coarseCapsid(r: StructureRepresentationHelper) {
await r.clear()
await r.setFromExpression('add', 'gaussian-surface', Q.trace, {
await r.setFromExpression('add', 'gaussian-surface', Q.trace.expression, {
radiusOffset: 1,
smoothness: 0.5,
visuals: ['structure-gaussian-surface-mesh']

View File

@@ -7,21 +7,32 @@
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
import { StateSelection } from '../../mol-state';
import { PluginStateObject } from '../state/objects';
import { QueryContext, StructureSelection, StructureQuery } from '../../mol-model/structure';
import { QueryContext, StructureSelection, StructureQuery, StructureElement } from '../../mol-model/structure';
import { compile } from '../../mol-script/runtime/query/compiler';
import { Loci } from '../../mol-model/loci';
import { PluginContext } from '../context';
import Expression from '../../mol-script/language/expression';
const all = MS.struct.generator.all()
export interface StructureSelectionQuery {
label: string
query: StructureQuery
expression: Expression
description: string
}
const polymer = MS.struct.modifier.union([
export function StructureSelectionQuery(label: string, expression: Expression, description = ''): StructureSelectionQuery {
return { label, expression, query: compile<StructureSelection>(expression), description }
}
const all = StructureSelectionQuery('All', MS.struct.generator.all())
const polymer = StructureSelectionQuery('Polymer', MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer'])
})
])
]))
const trace = MS.struct.modifier.union([
const trace = StructureSelectionQuery('Trace', MS.struct.modifier.union([
MS.struct.combinator.merge([
MS.struct.modifier.union([
MS.struct.generator.atomGroups({
@@ -39,9 +50,9 @@ const trace = MS.struct.modifier.union([
})
])
])
])
]))
const protein = MS.struct.modifier.union([
const protein = StructureSelectionQuery('Protein', MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
@@ -51,9 +62,9 @@ const protein = MS.struct.modifier.union([
])
])
})
])
]))
const nucleic = MS.struct.modifier.union([
const nucleic = StructureSelectionQuery('Nucleic', MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
@@ -63,9 +74,9 @@ const nucleic = MS.struct.modifier.union([
])
])
})
])
]))
const proteinAndNucleic = MS.struct.modifier.union([
const proteinAndNucleic = StructureSelectionQuery('Protein | Nucleic', MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
@@ -75,15 +86,15 @@ const proteinAndNucleic = MS.struct.modifier.union([
])
])
})
])
]))
const water = MS.struct.modifier.union([
const water = StructureSelectionQuery('Water', MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'water'])
})
])
]))
const branched = MS.struct.modifier.union([
const branched = StructureSelectionQuery('Carbohydrate', MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.logic.or([
MS.core.rel.eq([MS.ammp('entityType'), 'branched']),
@@ -96,65 +107,112 @@ const branched = MS.struct.modifier.union([
])
])
})
])
]))
const branchedPlusConnected = MS.struct.modifier.union([
const branchedPlusConnected = StructureSelectionQuery('Carbohydrate with Connected', MS.struct.modifier.union([
MS.struct.modifier.includeConnected({
0: branched, 'layer-count': 1, 'as-whole-residues': true
0: branched.expression, 'layer-count': 1, 'as-whole-residues': true
})
])
]))
const branchedConnectedOnly = MS.struct.modifier.union([
const branchedConnectedOnly = StructureSelectionQuery('Connected to Carbohydrate', MS.struct.modifier.union([
MS.struct.modifier.exceptBy({
0: branchedPlusConnected,
by: branched
0: branchedPlusConnected.expression,
by: branched.expression
})
])
]))
const ligand = MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp('entityType'), 'non-polymer']),
MS.core.logic.not([MS.core.str.match([
MS.re('oligosaccharide', 'i'),
MS.ammp('entitySubtype')
])])
const ligand = StructureSelectionQuery('Ligand', MS.struct.modifier.union([
MS.struct.combinator.merge([
MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.logic.and([
MS.core.rel.eq([MS.ammp('entityType'), 'non-polymer']),
MS.core.logic.not([MS.core.str.match([
MS.re('oligosaccharide', 'i'),
MS.ammp('entitySubtype')
])])
]),
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
'residue-test': MS.core.logic.not([
MS.core.str.match([MS.re('saccharide', 'i'), MS.ammp('chemCompType')])
])
})
]),
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
'residue-test': MS.core.logic.not([
MS.core.str.match([MS.re('saccharide', 'i'), MS.ammp('chemCompType')])
// this is to get non-polymer components in polymer entities, e.g. PXZ in 4HIV
// one option to optimize this is to expose `_entity_poly.nstd_monomer` in molql
// and only check those entities residue by residue
MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
'residue-test': MS.core.str.match([MS.re('non-polymer', 'i'), MS.ammp('chemCompType')])
})
])
})
])
]),
]))
// don't innclude branched entities as they have their own link representation
const ligandPlusConnected = MS.struct.modifier.union([
// don't include branched entities as they have their own link representation
const ligandPlusConnected = StructureSelectionQuery('Ligand with Connected', MS.struct.modifier.union([
MS.struct.modifier.exceptBy({
0: MS.struct.modifier.union([
MS.struct.modifier.includeConnected({
0: ligand,
0: ligand.expression,
'layer-count': 1,
'as-whole-residues': true
})
]),
by: branched
by: branched.expression
})
])
]))
const modified = MS.struct.modifier.union([
const ligandConnectedOnly = StructureSelectionQuery('Connected to Ligand', MS.struct.modifier.union([
MS.struct.modifier.exceptBy({
0: ligandPlusConnected.expression,
by: ligand.expression
})
]))
// residues connected to ligands or branched entities
const connectedOnly = StructureSelectionQuery('Connected to Ligand | Carbohydrate', MS.struct.modifier.union([
MS.struct.combinator.merge([
branchedConnectedOnly.expression,
ligandConnectedOnly.expression
]),
]))
const modified = StructureSelectionQuery('Modified Residues', MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
'residue-test': MS.ammp('isModified')
})
])
]))
const coarse = MS.struct.modifier.union([
const coarse = StructureSelectionQuery('Coarse Elements', MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'chain-test': MS.core.set.has([
MS.set('sphere', 'gaussian'), MS.ammp('objectPrimitive')
])
})
])
]))
const surroundings = StructureSelectionQuery('Surrounding Residues (5 \u212B)', MS.struct.modifier.union([
MS.struct.modifier.exceptBy({
0: MS.struct.modifier.includeSurroundings({
0: MS.internal.generator.current(),
radius: 5,
'as-whole-residues': true
}),
by: MS.internal.generator.current()
})
]), 'Select residues within 5 \u212B of the current selection.')
const complement = StructureSelectionQuery('Inverse / Complement', MS.struct.modifier.union([
MS.struct.modifier.exceptBy({
0: MS.struct.generator.all(),
by: MS.internal.generator.current()
})
]), 'Select everything not in the current selection.')
export const StructureSelectionQueries = {
all,
@@ -169,26 +227,21 @@ export const StructureSelectionQueries = {
branchedConnectedOnly,
ligand,
ligandPlusConnected,
ligandConnectedOnly,
connectedOnly,
modified,
coarse,
surroundings,
complement,
}
export const CompiledStructureSelectionQueries = (function () {
const ret: { [K in keyof typeof StructureSelectionQueries]: StructureQuery } = Object.create(null);
for (const k of Object.keys(StructureSelectionQueries)) {
(ret as any)[k] = compile<StructureSelection>((StructureSelectionQueries as any)[k]);
}
return ret;
})();
//
export type SelectionModifier = 'add' | 'remove' | 'only'
export class StructureSelectionHelper {
private get structures() {
const state = this.plugin.state.dataState
return state.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure))
return this.plugin.state.dataState.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure)).map(s => s.obj!.data)
}
private _set(modifier: SelectionModifier, loci: Loci) {
@@ -205,13 +258,15 @@ export class StructureSelectionHelper {
}
}
set(modifier: SelectionModifier, query: Expression) {
const compiled = compile<StructureSelection>(query)
set(modifier: SelectionModifier, query: StructureQuery) {
for (const s of this.structures) {
const current = this.plugin.helpers.structureSelectionManager.get(s)
const currentSelection = Loci.isEmpty(current)
? StructureSelection.Empty(s)
: StructureSelection.Singletons(s, StructureElement.Loci.toStructure(current))
for (const so of this.structures) {
const s = so.obj!.data
const result = compiled(new QueryContext(s))
const loci = StructureSelection.toLoci2(result)
const result = query(new QueryContext(s, { currentSelection }))
const loci = StructureSelection.toLociWithSourceUnits(result)
this._set(modifier, loci)
}
}

View File

@@ -14,10 +14,10 @@ import { ThemeRegistryContext } from '../../../mol-theme/theme';
import { Structure } from '../../../mol-model/structure';
const GaussianSurfaceVisuals = {
'gaussian-surface-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, GaussianSurfaceMeshParams>) => UnitsRepresentation('Gaussian surface', ctx, getParams, GaussianSurfaceMeshVisual),
'structure-gaussian-surface-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, StructureGaussianSurfaceMeshParams>) => ComplexRepresentation('Structure-Gaussian surface', ctx, getParams, StructureGaussianSurfaceMeshVisual),
'gaussian-surface-texture-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, GaussianSurfaceMeshParams>) => UnitsRepresentation('Gaussian surface', ctx, getParams, GaussianSurfaceTextureMeshVisual),
'gaussian-wireframe': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, GaussianWireframeParams>) => UnitsRepresentation('Gaussian wireframe', ctx, getParams, GaussianWireframeVisual),
'gaussian-surface-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, GaussianSurfaceMeshParams>) => UnitsRepresentation('Gaussian surface mesh', ctx, getParams, GaussianSurfaceMeshVisual),
'structure-gaussian-surface-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, StructureGaussianSurfaceMeshParams>) => ComplexRepresentation('Structure-Gaussian surface mesh', ctx, getParams, StructureGaussianSurfaceMeshVisual),
'gaussian-surface-texture-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, GaussianSurfaceMeshParams>) => UnitsRepresentation('Gaussian surface texture-mesh', ctx, getParams, GaussianSurfaceTextureMeshVisual),
'gaussian-surface-wireframe': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, GaussianWireframeParams>) => UnitsRepresentation('Gaussian surface wireframe', ctx, getParams, GaussianWireframeVisual),
}
type GaussianSurfaceVisualName = keyof typeof GaussianSurfaceVisuals
const GaussianSurfaceVisualOptions = Object.keys(GaussianSurfaceVisuals).map(name => [name, name] as [GaussianSurfaceVisualName, string])

View File

@@ -11,15 +11,18 @@ import { StructureRepresentation, StructureRepresentationProvider, StructureRepr
import { Representation, RepresentationParamsGetter, RepresentationContext } from '../../../mol-repr/representation';
import { ThemeRegistryContext } from '../../../mol-theme/theme';
import { Structure } from '../../../mol-model/structure';
import { MolecularSurfaceWireframeParams, MolecularSurfaceWireframeVisual } from '../visual/molecular-surface-wireframe';
const MolecularSurfaceVisuals = {
'molecular-surface-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, MolecularSurfaceMeshParams>) => UnitsRepresentation('Molecular surface', ctx, getParams, MolecularSurfaceMeshVisual),
'molecular-surface-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, MolecularSurfaceMeshParams>) => UnitsRepresentation('Molecular surface mesh', ctx, getParams, MolecularSurfaceMeshVisual),
'molecular-surface-wireframe': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, MolecularSurfaceWireframeParams>) => UnitsRepresentation('Molecular surface wireframe', ctx, getParams, MolecularSurfaceWireframeVisual),
}
type MolecularSurfaceVisualName = keyof typeof MolecularSurfaceVisuals
const MolecularSurfaceVisualOptions = Object.keys(MolecularSurfaceVisuals).map(name => [name, name] as [MolecularSurfaceVisualName, string])
export const MolecularSurfaceParams = {
...MolecularSurfaceMeshParams,
...MolecularSurfaceWireframeParams,
visuals: PD.MultiSelect<MolecularSurfaceVisualName>(['molecular-surface-mesh'], MolecularSurfaceVisualOptions),
}
export type MolecularSurfaceParams = typeof MolecularSurfaceParams

View File

@@ -34,6 +34,7 @@ async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure
export const GaussianWireframeParams = {
...UnitsLinesParams,
...GaussianDensityParams,
sizeFactor: PD.Numeric(1.5, { min: 0, max: 10, step: 0.1 }),
lineSizeAttenuation: PD.Boolean(false),
ignoreHydrogens: PD.Boolean(false),
}

View File

@@ -5,7 +5,7 @@
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { UnitsMeshParams, UnitsTextureMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
import { MolecularSurfaceCalculationParams } from '../../../mol-math/geometry/molecular-surface';
import { VisualContext } from '../../visual';
import { Unit, Structure } from '../../../mol-model/structure';
@@ -18,7 +18,6 @@ import { VisualUpdateState } from '../../util';
export const MolecularSurfaceMeshParams = {
...UnitsMeshParams,
...UnitsTextureMeshParams,
...MolecularSurfaceCalculationParams,
ignoreHydrogens: PD.Boolean(false),
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { UnitsVisual, UnitsLinesVisual, UnitsLinesParams } from '../units-visual';
import { MolecularSurfaceCalculationParams } from '../../../mol-math/geometry/molecular-surface';
import { VisualContext } from '../../visual';
import { Unit, Structure } from '../../../mol-model/structure';
import { Theme } from '../../../mol-theme/theme';
import { Lines } from '../../../mol-geo/geometry/lines/lines';
import { computeUnitMolecularSurface, MolecularSurfaceProps } from './util/molecular-surface';
import { computeMarchingCubesLines } from '../../../mol-geo/util/marching-cubes/algorithm';
import { ElementIterator, getElementLoci, eachElement } from './util/element';
import { VisualUpdateState } from '../../util';
export const MolecularSurfaceWireframeParams = {
...UnitsLinesParams,
...MolecularSurfaceCalculationParams,
sizeFactor: PD.Numeric(1.5, { min: 0, max: 10, step: 0.1 }),
ignoreHydrogens: PD.Boolean(false),
}
export type MolecularSurfaceWireframeParams = typeof MolecularSurfaceWireframeParams
//
async function createMolecularSurfaceWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: MolecularSurfaceProps, lines?: Lines): Promise<Lines> {
const { transform, field, idField } = await computeUnitMolecularSurface(unit, props).runInContext(ctx.runtime)
const params = {
isoLevel: props.probeRadius,
scalarField: field,
idField
}
const wireframe = await computeMarchingCubesLines(params, lines).runAsChild(ctx.runtime)
Lines.transformImmediate(wireframe, transform)
return wireframe
}
export function MolecularSurfaceWireframeVisual(materialId: number): UnitsVisual<MolecularSurfaceWireframeParams> {
return UnitsLinesVisual<MolecularSurfaceWireframeParams>({
defaultProps: PD.getDefaultValues(MolecularSurfaceWireframeParams),
createGeometry: createMolecularSurfaceWireframe,
createLocationIterator: ElementIterator.fromGroup,
getLoci: getElementLoci,
eachLocation: eachElement,
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<MolecularSurfaceWireframeParams>, currentProps: PD.Values<MolecularSurfaceWireframeParams>) => {
if (newProps.resolution !== currentProps.resolution) state.createGeometry = true
if (newProps.probeRadius !== currentProps.probeRadius) state.createGeometry = true
if (newProps.probePositions !== currentProps.probePositions) state.createGeometry = true
if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true
}
}, materialId)
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -62,7 +62,7 @@ function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structure: St
const chainIt = Segmentation.transientSegments(chainAtomSegments, elements)
const residueIt = Segmentation.transientSegments(residueAtomSegments, elements)
const cylinderProps: CylinderProps = { radiusTop: 1 * sizeFactor, radiusBottom: 1 * sizeFactor, radialSegments }
const cylinderProps: CylinderProps = { radiusTop: 1 * sizeFactor, radiusBottom: 1 * sizeFactor, radialSegments, bottomCap: true }
let i = 0
while (chainIt.hasNext) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -13,11 +13,11 @@ import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { CylinderProps } from '../../../mol-geo/primitive/cylinder';
import { PolymerGapIterator, PolymerGapLocationIterator, getPolymerGapElementLoci, eachPolymerGapElement } from './util/polymer';
import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
import { addFixedCountDashedCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
import { LinkCylinderParams } from './util/link';
import { VisualUpdateState } from '../../util';
// import { TriangularPyramid } from '../../../mol-geo/primitive/pyramid';
const segmentCount = 10
@@ -28,6 +28,10 @@ export const PolymerGapCylinderParams = {
export const DefaultPolymerGapCylinderProps = PD.getDefaultValues(PolymerGapCylinderParams)
export type PolymerGapCylinderProps = typeof DefaultPolymerGapCylinderProps
// const triangularPyramid = TriangularPyramid()
// const t = Mat4.identity()
// const pd = Vec3.zero()
function createPolymerGapCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerGapCylinderProps, mesh?: Mesh) {
const polymerGapCount = unit.gapElements.length
if (!polymerGapCount) return Mesh.createEmpty(mesh)
@@ -49,9 +53,14 @@ function createPolymerGapCylinderMesh(ctx: VisualContext, unit: Unit, structure:
while (polymerGapIt.hasNext) {
const { centerA, centerB } = polymerGapIt.move()
if (centerA.element === centerB.element) {
builderState.currentGroup = i
pos(centerA.element, pA)
addSphere(builderState, pA, 0.6, 0)
// TODO
// builderState.currentGroup = i
// pos(centerA.element, pA)
// Vec3.add(pd, pA, Vec3.create(1, 0, 0))
// Mat4.targetTo(t, pA, pd, Vec3.create(0, 1, 0))
// Mat4.setTranslation(t, pA)
// Mat4.scale(t, t, Vec3.create(0.7, 0.7, 2.5))
// MeshBuilder.addPrimitive(builderState, t, triangularPyramid)
} else {
pos(centerA.element, pA)
pos(centerB.element, pB)

View File

@@ -10,7 +10,7 @@ import { Unit, Structure } from '../../../mol-model/structure';
import { Theme } from '../../../mol-theme/theme';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
import { createCurveSegmentState, PolymerTraceIterator, interpolateCurveSegment, interpolateSizes, PolymerLocationIterator, getPolymerElementLoci, eachPolymerElement } from './util/polymer';
import { createCurveSegmentState, PolymerTraceIterator, interpolateCurveSegment, interpolateSizes, PolymerLocationIterator, getPolymerElementLoci, eachPolymerElement, HelixTension, NucleicShift, StandardShift, StandardTension, OverhangFactor } from './util/polymer';
import { isNucleic, SecondaryStructureType } from '../../../mol-model/structure/model/types';
import { addSheet } from '../../../mol-geo/geometry/mesh/builder/sheet';
import { addTube } from '../../../mol-geo/geometry/mesh/builder/tube';
@@ -18,9 +18,12 @@ import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '.
import { VisualUpdateState } from '../../util';
import { ComputedSecondaryStructure } from '../../../mol-model-props/computed/secondary-structure';
import { addRibbon } from '../../../mol-geo/geometry/mesh/builder/ribbon';
import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
import { Vec3 } from '../../../mol-math/linear-algebra';
export const PolymerTraceMeshParams = {
sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }),
linearSegments: PD.Numeric(8, { min: 1, max: 48, step: 1 }),
radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }),
aspectRatio: PD.Numeric(5, { min: 0.1, max: 10, step: 0.1 }),
@@ -29,13 +32,13 @@ export const PolymerTraceMeshParams = {
export const DefaultPolymerTraceMeshProps = PD.getDefaultValues(PolymerTraceMeshParams)
export type PolymerTraceMeshProps = typeof DefaultPolymerTraceMeshProps
// TODO handle polymer ends properly
const tmpV1 = Vec3()
function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerTraceMeshProps, mesh?: Mesh) {
const polymerElementCount = unit.polymerElements.length
if (!polymerElementCount) return Mesh.createEmpty(mesh)
const { sizeFactor, linearSegments, radialSegments, aspectRatio, arrowFactor } = props
const { sizeFactor, detail, linearSegments, radialSegments, aspectRatio, arrowFactor } = props
const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 10, mesh)
@@ -53,8 +56,8 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
const isNucleicType = isNucleic(v.moleculeType)
const isSheet = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Beta)
const isHelix = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Helix)
const tension = isHelix ? 0.9 : 0.5
const shift = isNucleicType ? 0.3 : 0.5
const tension = isHelix ? HelixTension : StandardTension
const shift = isNucleicType ? NucleicShift : StandardShift
interpolateCurveSegment(state, v, tension, shift)
@@ -70,7 +73,28 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
const startCap = v.secStrucFirst || v.coarseBackboneFirst || v.first
const endCap = v.secStrucLast || v.coarseBackboneLast || v.last
if (isSheet) {
let segmentCount = linearSegments
if (v.initial) {
segmentCount = Math.max(Math.round(linearSegments * shift), 1)
const offset = linearSegments - segmentCount
curvePoints.copyWithin(0, offset * 3)
binormalVectors.copyWithin(0, offset * 3)
normalVectors.copyWithin(0, offset * 3)
Vec3.fromArray(tmpV1, curvePoints, 3)
Vec3.normalize(tmpV1, Vec3.sub(tmpV1, v.p2, tmpV1))
Vec3.scaleAndAdd(tmpV1, v.p2, tmpV1, w1 * OverhangFactor)
Vec3.toArray(tmpV1, curvePoints, 0)
} else if (v.final) {
segmentCount = Math.max(Math.round(linearSegments * (1 - shift)), 1)
Vec3.fromArray(tmpV1, curvePoints, segmentCount * 3 - 3)
Vec3.normalize(tmpV1, Vec3.sub(tmpV1, v.p2, tmpV1))
Vec3.scaleAndAdd(tmpV1, v.p2, tmpV1, w1 * OverhangFactor)
Vec3.toArray(tmpV1, curvePoints, segmentCount * 3)
}
if (v.initial === true && v.final === true) {
addSphere(builderState, v.p2, w1 * 2, detail)
} else if (isSheet) {
const h0 = w0 * aspectRatio
const h1 = w1 * aspectRatio
const h2 = w2 * aspectRatio
@@ -79,9 +103,9 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
interpolateSizes(state, w0, w1, w2, h0, h1, h2, shift)
if (radialSegments === 2) {
addRibbon(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, widthValues, heightValues, arrowHeight)
addRibbon(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, arrowHeight)
} else {
addSheet(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, widthValues, heightValues, arrowHeight, startCap, endCap)
addSheet(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, arrowHeight, startCap, endCap)
}
} else {
let h0: number, h1: number, h2: number
@@ -108,14 +132,14 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
if (isNucleicType && !v.isCoarseBackbone) {
// TODO find a cleaner way to swap normal and binormal for nucleic types
for (let i = 0, il = binormalVectors.length; i < il; i++) binormalVectors[i] *= -1
addRibbon(builderState, curvePoints, binormalVectors, normalVectors, linearSegments, heightValues, widthValues, 0)
addRibbon(builderState, curvePoints, binormalVectors, normalVectors, segmentCount, heightValues, widthValues, 0)
} else {
addRibbon(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, widthValues, heightValues, 0)
addRibbon(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0)
}
} else if (radialSegments === 4) {
addSheet(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, widthValues, heightValues, 0, startCap, endCap)
addSheet(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0, startCap, endCap)
} else {
addTube(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, radialSegments, widthValues, heightValues, 1, startCap, endCap)
addTube(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, radialSegments, widthValues, heightValues, 1, startCap, endCap)
}
}
@@ -141,6 +165,7 @@ export function PolymerTraceVisual(materialId: number): UnitsVisual<PolymerTrace
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerTraceParams>, currentProps: PD.Values<PolymerTraceParams>, newTheme: Theme, currentTheme: Theme, newStructureGroup: StructureGroup, currentStructureGroup: StructureGroup) => {
state.createGeometry = (
newProps.sizeFactor !== currentProps.sizeFactor ||
newProps.detail !== currentProps.detail ||
newProps.linearSegments !== currentProps.linearSegments ||
newProps.radialSegments !== currentProps.radialSegments ||
newProps.aspectRatio !== currentProps.aspectRatio ||

View File

@@ -10,29 +10,32 @@ import { Unit, Structure } from '../../../mol-model/structure';
import { Theme } from '../../../mol-theme/theme';
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
import { createCurveSegmentState, PolymerTraceIterator, interpolateCurveSegment, interpolateSizes, PolymerLocationIterator, getPolymerElementLoci, eachPolymerElement } from './util/polymer';
import { isNucleic } from '../../../mol-model/structure/model/types';
import { createCurveSegmentState, PolymerTraceIterator, interpolateCurveSegment, interpolateSizes, PolymerLocationIterator, getPolymerElementLoci, eachPolymerElement, HelixTension, StandardTension, StandardShift, NucleicShift, OverhangFactor } from './util/polymer';
import { isNucleic, SecondaryStructureType } from '../../../mol-model/structure/model/types';
import { addTube } from '../../../mol-geo/geometry/mesh/builder/tube';
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
import { VisualUpdateState } from '../../util';
import { addSheet } from '../../../mol-geo/geometry/mesh/builder/sheet';
import { addRibbon } from '../../../mol-geo/geometry/mesh/builder/ribbon';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
export const PolymerTubeMeshParams = {
sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }),
linearSegments: PD.Numeric(8, { min: 1, max: 48, step: 1 }),
radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }),
}
export const DefaultPolymerTubeMeshProps = PD.getDefaultValues(PolymerTubeMeshParams)
export type PolymerTubeMeshProps = typeof DefaultPolymerTubeMeshProps
// TODO handle polymer ends properly
const tmpV1 = Vec3()
function createPolymerTubeMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerTubeMeshProps, mesh?: Mesh) {
const polymerElementCount = unit.polymerElements.length
if (!polymerElementCount) return Mesh.createEmpty(mesh)
const { sizeFactor, linearSegments, radialSegments } = props
const { sizeFactor, detail, linearSegments, radialSegments } = props
const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 10, mesh)
@@ -47,8 +50,9 @@ function createPolymerTubeMesh(ctx: VisualContext, unit: Unit, structure: Struct
builderState.currentGroup = i
const isNucleicType = isNucleic(v.moleculeType)
const tension = isNucleicType ? 0.5 : 0.9
const shift = isNucleicType ? 0.3 : 0.5
const isHelix = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Helix)
const tension = isHelix ? HelixTension : StandardTension
const shift = isNucleicType ? NucleicShift : StandardShift
interpolateCurveSegment(state, v, tension, shift)
@@ -61,12 +65,35 @@ function createPolymerTubeMesh(ctx: VisualContext, unit: Unit, structure: Struct
interpolateSizes(state, s0, s1, s2, s0, s1, s2, shift)
if (radialSegments === 2) {
addRibbon(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, widthValues, heightValues, 0)
let segmentCount = linearSegments
if (v.initial) {
segmentCount = Math.max(Math.round(linearSegments * shift), 1)
const offset = linearSegments - segmentCount
curvePoints.copyWithin(0, offset * 3)
binormalVectors.copyWithin(0, offset * 3)
normalVectors.copyWithin(0, offset * 3)
widthValues.copyWithin(0, offset * 3)
heightValues.copyWithin(0, offset * 3)
Vec3.fromArray(tmpV1, curvePoints, 3)
Vec3.normalize(tmpV1, Vec3.sub(tmpV1, v.p2, tmpV1))
Vec3.scaleAndAdd(tmpV1, v.p2, tmpV1, s1 * OverhangFactor)
Vec3.toArray(tmpV1, curvePoints, 0)
} else if (v.final) {
segmentCount = Math.max(Math.round(linearSegments * (1 - shift)), 1)
Vec3.fromArray(tmpV1, curvePoints, segmentCount * 3 - 3)
Vec3.normalize(tmpV1, Vec3.sub(tmpV1, v.p2, tmpV1))
Vec3.scaleAndAdd(tmpV1, v.p2, tmpV1, s1 * OverhangFactor)
Vec3.toArray(tmpV1, curvePoints, segmentCount * 3)
}
if (v.initial === true && v.final === true) {
addSphere(builderState, v.p2, s1 * 2, detail)
} else if (radialSegments === 2) {
addRibbon(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0)
} else if (radialSegments === 4) {
addSheet(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, widthValues, heightValues, 0, startCap, endCap)
addSheet(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, widthValues, heightValues, 0, startCap, endCap)
} else {
addTube(builderState, curvePoints, normalVectors, binormalVectors, linearSegments, radialSegments, widthValues, heightValues, 1, startCap, endCap)
addTube(builderState, curvePoints, normalVectors, binormalVectors, segmentCount, radialSegments, widthValues, heightValues, 1, startCap, endCap)
}
++i
@@ -91,6 +118,7 @@ export function PolymerTubeVisual(materialId: number): UnitsVisual<PolymerTubePa
setUpdateState: (state: VisualUpdateState, newProps: PD.Values<PolymerTubeParams>, currentProps: PD.Values<PolymerTubeParams>) => {
state.createGeometry = (
newProps.sizeFactor !== currentProps.sizeFactor ||
newProps.detail !== currentProps.detail ||
newProps.linearSegments !== currentProps.linearSegments ||
newProps.radialSegments !== currentProps.radialSegments
)

View File

@@ -35,7 +35,7 @@ export function getNucleotideElementLoci(pickingId: PickingId, structureGroup: S
const { structure, group } = structureGroup
const unit = group.units[instanceId]
if (Unit.isAtomic(unit)) {
return getResidueLoci(structure, unit, unit.polymerElements[groupId])
return getResidueLoci(structure, unit, unit.nucleotideElements[groupId])
}
}
return EmptyLoci

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -18,9 +18,15 @@ export * from './polymer/gap-iterator'
export * from './polymer/trace-iterator'
export * from './polymer/curve-segment'
export const StandardTension = 0.5
export const HelixTension = 0.9
export const StandardShift = 0.5
export const NucleicShift = 0.3
export const OverhangFactor = 2
export function getPolymerRanges(unit: Unit): SortedRanges<ElementIndex> {
switch (unit.kind) {
case Unit.Kind.Atomic: return unit.model.atomicHierarchy.polymerRanges
case Unit.Kind.Atomic: return unit.model.atomicRanges.polymerRanges
case Unit.Kind.Spheres: return unit.model.coarseHierarchy.spheres.polymerRanges
case Unit.Kind.Gaussians: return unit.model.coarseHierarchy.gaussians.polymerRanges
}
@@ -28,7 +34,7 @@ export function getPolymerRanges(unit: Unit): SortedRanges<ElementIndex> {
export function getGapRanges(unit: Unit): SortedRanges<ElementIndex> {
switch (unit.kind) {
case Unit.Kind.Atomic: return unit.model.atomicHierarchy.gapRanges
case Unit.Kind.Atomic: return unit.model.atomicRanges.gapRanges
case Unit.Kind.Spheres: return unit.model.coarseHierarchy.spheres.gapRanges
case Unit.Kind.Gaussians: return unit.model.coarseHierarchy.gaussians.gapRanges
}

View File

@@ -61,7 +61,7 @@ export class AtomicPolymerBackboneIterator implements Iterator<PolymerBackbonePa
this.value.centerA.element = this.value.centerB.element
this.value.centerB.element = this.traceElementIndex[this.residueSegment.index]
if (!this.residueIt.hasNext) {
if (this.unit.model.atomicHierarchy.cyclicPolymerMap.has(this.residueSegment.index)) {
if (this.unit.model.atomicRanges.cyclicPolymerMap.has(this.residueSegment.index)) {
this.state = AtomicPolymerBackboneIteratorState.cycle
} else {
// TODO need to advance to a polymer that has two or more residues (can't assume it has)
@@ -69,7 +69,7 @@ export class AtomicPolymerBackboneIterator implements Iterator<PolymerBackbonePa
}
}
} else if (this.state === AtomicPolymerBackboneIteratorState.cycle) {
const { cyclicPolymerMap } = this.unit.model.atomicHierarchy
const { cyclicPolymerMap } = this.unit.model.atomicRanges
this.value.centerA.element = this.value.centerB.element
this.value.centerB.element = this.traceElementIndex[cyclicPolymerMap.get(this.residueSegment.index)!]
// TODO need to advance to a polymer that has two or more residues (can't assume it has)

View File

@@ -34,6 +34,7 @@ interface PolymerTraceElement {
centerPrev: StructureElement.Location
centerNext: StructureElement.Location
first: boolean, last: boolean
initial: boolean, final: boolean
secStrucFirst: boolean, secStrucLast: boolean
secStrucType: SecondaryStructureType
moleculeType: MoleculeType
@@ -52,6 +53,7 @@ function createPolymerTraceElement (unit: Unit): PolymerTraceElement {
centerPrev: StructureElement.Location.create(unit),
centerNext: StructureElement.Location.create(unit),
first: false, last: false,
initial: false, final: false,
secStrucFirst: false, secStrucLast: false,
secStrucType: SecStrucTypeNA,
moleculeType: MoleculeType.unknown,
@@ -223,6 +225,9 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
const residueIndexNext2 = this.getResidueIndex(residueIndex + 2)
const residueIndexNext3 = this.getResidueIndex(residueIndex + 3)
value.initial = residueIndex === residueIndexPrev1
value.final = residueIndex === residueIndexNext1
value.centerPrev.element = this.traceElementIndex[residueIndexPrev1]
value.center.element = this.traceElementIndex[residueIndex]
value.centerNext.element = this.traceElementIndex[residueIndexNext1]
@@ -291,12 +296,12 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
constructor(private unit: Unit.Atomic, structure: Structure) {
this.atomicConformation = unit.model.atomicConformation
this.residueAtomSegments = unit.model.atomicHierarchy.residueAtomSegments
this.polymerRanges = unit.model.atomicHierarchy.polymerRanges
this.polymerRanges = unit.model.atomicRanges.polymerRanges
this.traceElementIndex = unit.model.atomicHierarchy.derived.residue.traceElementIndex as ArrayLike<ElementIndex> // can assume it won't be -1 for polymer residues
this.directionFromElementIndex = unit.model.atomicHierarchy.derived.residue.directionFromElementIndex
this.directionToElementIndex = unit.model.atomicHierarchy.derived.residue.directionToElementIndex
this.moleculeType = unit.model.atomicHierarchy.derived.residue.moleculeType
this.cyclicPolymerMap = unit.model.atomicHierarchy.cyclicPolymerMap
this.cyclicPolymerMap = unit.model.atomicRanges.cyclicPolymerMap
this.polymerIt = SortedRanges.transientSegments(this.polymerRanges, unit.elements)
this.residueIt = Segmentation.transientSegments(this.residueAtomSegments, unit.elements);
this.value = createPolymerTraceElement(unit)

View File

@@ -120,6 +120,7 @@ export async function createVolumeIsosurfaceWireframe(ctx: VisualContext, volume
export const IsosurfaceWireframeParams = {
...Lines.Params,
sizeFactor: PD.Numeric(1.5, { min: 0, max: 10, step: 0.1 }),
...VolumeIsosurfaceParams
}
export type IsosurfaceWireframeParams = typeof IsosurfaceWireframeParams

View File

@@ -11,6 +11,7 @@ import { MolScriptSymbolTable as SymbolTable } from './symbol-table'
export namespace MolScriptBuilder {
export const core = SymbolTable.core;
export const struct = SymbolTable.structureQuery;
export const internal = SymbolTable.internal;
/** Atom-name constructor */
export function atomName(s: string) { return struct.type.atomName([s]); }

View File

@@ -6,10 +6,11 @@
import core from './symbol-table/core'
import structureQuery from './symbol-table/structure-query'
import internal from './symbol-table/internal'
import { normalizeTable, symbolList } from './helpers'
import { MSymbol } from './symbol'
const MolScriptSymbolTable = { core, structureQuery };
const MolScriptSymbolTable = { core, structureQuery, internal };
normalizeTable(MolScriptSymbolTable);

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2019 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Type from '../type'
import * as Struct from './structure-query'
import { Arguments, Argument } from '../symbol'
import { symbol } from '../helpers'
const generator = {
'@header': 'Generators',
bundleElement: symbol(Arguments.Dictionary({
// TODO: should we use more universal unit keys? (i.e. based on chain and "operator name")
groupedUnits: Argument(Type.Any), // SortedArray<number>[],
set: Argument(Type.Any), // SortedArray<UnitIndex>
ranges: Argument(Type.Any) // SortedArray<UnitIndex>
}), Type.Any), // returns BundleElement
bundle: symbol(Arguments.Dictionary({
elements: Argument(Type.Any) // BundleElement[]
}), Struct.Types.ElementSelectionQuery, 'A selection with single structure containing represented by the bundle.'),
// Use with caution as this is not "state saveable"
// This query should never be used in any State Transform!
current: symbol(Arguments.None, Struct.Types.ElementSelectionQuery, 'Current selection provided by the query context. Avoid using this in State Transforms.')
}
export default {
'@header': 'Internal Queries',
generator
}

View File

@@ -15,6 +15,7 @@ import { VdwRadius, AtomWeight, AtomNumber } from '../../../mol-model/structure/
import { cantorPairing } from '../../../mol-data/util';
import C = QuerySymbolRuntime.Const
import D = QuerySymbolRuntime.Dynamic
import { bundleElementImpl, bundleGenerator } from '../../../mol-model/structure/query/queries/internal';
const symbols = [
// ============= TYPES =============
@@ -320,6 +321,13 @@ const symbols = [
// ============= BOND PROPERTIES ================
D(MolScript.structureQuery.linkProperty.order, (ctx, xs) => ctx.atomicLink.order),
D(MolScript.structureQuery.linkProperty.flags, (ctx, xs) => ctx.atomicLink.type),
////////////////////////////////////
// Internal
D(MolScript.internal.generator.bundleElement, (ctx, xs) => bundleElementImpl(xs.groupedUnits(ctx), xs.ranges(ctx), xs.set(ctx))),
D(MolScript.internal.generator.bundle, (ctx, xs) => bundleGenerator(xs.elements(ctx))),
D(MolScript.internal.generator.current, (ctx, xs) => ctx.tryGetCurrentSelection()),
];
function atomProp(p: (e: StructureElement.Location) => any): (ctx: QueryContext, _: any) => any {

View File

@@ -7,8 +7,9 @@
import { transpileMolScript } from './script/mol-script/symbols';
import { parseMolScript } from './language/parser';
import Expression from './language/expression';
import { StructureElement, QueryContext, StructureSelection, Structure, QueryFn } from '../mol-model/structure';
import { StructureElement, QueryContext, StructureSelection, Structure, QueryFn, QueryContextOptions } from '../mol-model/structure';
import { compile } from './runtime/query/compiler';
import { MolScriptBuilder } from './language/builder';
export { Script }
@@ -43,6 +44,12 @@ namespace Script {
export function toLoci(script: Script, structure: Structure): StructureElement.Loci {
const query = toQuery(script)
const result = query(new QueryContext(structure))
return StructureSelection.toLoci2(result)
return StructureSelection.toLociWithSourceUnits(result)
}
export function getStructureSelection(expr: Expression | ((builder: typeof MolScriptBuilder) => Expression), structure: Structure, options?: QueryContextOptions) {
const e = typeof expr === 'function' ? expr(MolScriptBuilder) : expr;
const query = compile<StructureSelection>(e);
return query(new QueryContext(structure, options));
}
}

View File

@@ -17,7 +17,9 @@ export { StateAction };
interface StateAction<A extends StateObject = StateObject, T = any, P extends {} = {}> {
create(params: P): StateAction.Instance,
readonly id: UUID,
readonly definition: StateAction.Definition<A, T, P>
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
}
namespace StateAction {
@@ -59,7 +61,8 @@ namespace StateAction {
const action: StateAction<A, T, P> = {
create(params) { return { action, params }; },
id: UUID.create22(),
definition
definition,
createDefaultParams(a, globalCtx) { return definition.params ? PD.getDefaultValues( definition.params(a, globalCtx)) : {} as any; }
};
return action;
}

View File

@@ -19,7 +19,9 @@ interface Transformer<A extends StateObject = StateObject, B extends StateObject
toAction(): StateAction<A, void, P>,
readonly namespace: string,
readonly id: Transformer.Id,
readonly definition: Transformer.Definition<A, B, P>
readonly definition: Transformer.Definition<A, B, P>,
/** create a fresh copy of the params which can be edited in place */
createDefaultParams(a: A, globalCtx: unknown): P
}
namespace Transformer {
@@ -143,7 +145,8 @@ namespace Transformer {
toAction() { return StateAction.fromTransformer(t); },
namespace,
id,
definition
definition,
createDefaultParams(a, globalCtx) { return definition.params ? PD.getDefaultValues( definition.params(a, globalCtx)) : {} as any; }
};
registry.set(id, t);
_index(t);

View File

@@ -18,7 +18,7 @@ const DefaultColor = Color(0xFAFAFA)
const Description = 'Gives every chain a color based on its `asym_id` value.'
export const ChainIdColorThemeParams = {
...getPaletteParams({ type: 'set', setList: 'set-3' }),
...getPaletteParams({ type: 'set', setList: 'dark-2' }),
}
export type ChainIdColorThemeParams = typeof ChainIdColorThemeParams
export function getChainIdColorThemeParams(ctx: ThemeDataContext) {

View File

@@ -14,12 +14,13 @@ import { Table, Column } from '../../mol-data/db';
import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
import { getPaletteParams, getPalette } from '../../mol-util/color/palette';
import { TableLegend, ScaleLegend } from '../../mol-util/legend';
import { isInteger } from '../../mol-util/number';
const DefaultColor = Color(0xFAFAFA)
const Description = 'Gives ranges of a polymer chain a color based on the entity source it originates from. Genes get the same color per entity.'
const Description = 'Gives ranges of a polymer chain a color based on the entity source it originates from (e.g. gene, plasmid, organism).'
export const EntitySourceColorThemeParams = {
...getPaletteParams({ type: 'set', setList: 'set-3' }),
...getPaletteParams({ type: 'set', setList: 'dark-2' }),
}
export type EntitySourceColorThemeParams = typeof EntitySourceColorThemeParams
export function getEntitySourceColorThemeParams(ctx: ThemeDataContext) {
@@ -46,13 +47,15 @@ type EntitySrc = Table<{
pdbx_beg_seq_num: mmCIF_Schema['entity_src_gen']['pdbx_beg_seq_num'],
pdbx_end_seq_num: mmCIF_Schema['entity_src_gen']['pdbx_end_seq_num'],
}>
type ScientificName = Column<mmCIF_Schema['entity_src_gen']['pdbx_gene_src_scientific_name']['T']>
type GeneSrcGene = Column<mmCIF_Schema['entity_src_gen']['pdbx_gene_src_gene']['T']>
type PlasmidName = Column<mmCIF_Schema['entity_src_gen']['plasmid_name']['T']>
function srcKey(modelIndex: number, entityId: string, srcId: number, gene: string) {
return `${modelIndex}|${entityId}|${gene ? gene : srcId}`
function srcKey(modelIndex: number, entityId: string, organism: string, srcId: number, plasmid: string, gene: string) {
return `${modelIndex}|${entityId}|${organism}|${gene ? gene : (plasmid ? plasmid : srcId)}`
}
function addSrc(seqToSrcByModelEntity: Map<string, Int16Array>, srcKeySerialMap: Map<string, number>, modelIndex: number, model: Model, entity_src: EntitySrc, gene_src_gene?: GeneSrcGene) {
function addSrc(seqToSrcByModelEntity: Map<string, Int16Array>, srcKeySerialMap: Map<string, number>, modelIndex: number, model: Model, entity_src: EntitySrc, scientific_name: ScientificName, plasmid_name?: PlasmidName, gene_src_gene?: GeneSrcGene) {
const { entity_id, pdbx_src_id, pdbx_beg_seq_num, pdbx_end_seq_num } = entity_src
for (let j = 0, jl = entity_src._rowCount; j < jl; ++j) {
const entityId = entity_id.value(j)
@@ -66,7 +69,10 @@ function addSrc(seqToSrcByModelEntity: Map<string, Int16Array>, srcKeySerialMap:
} else {
seqToSrc = seqToSrcByModelEntity.get(mK)!
}
const sK = srcKey(modelIndex, entityId, pdbx_src_id.value(j), gene_src_gene ? gene_src_gene.value(j).join(',') : '')
const plasmid = plasmid_name ? plasmid_name.value(j) : ''
const gene = gene_src_gene ? gene_src_gene.value(j)[0] : ''
const sK = srcKey(modelIndex, entityId, scientific_name.value(j), pdbx_src_id.value(j), plasmid, gene)
// may not be given (= 0) indicating src is for the whole seq
const beg = pdbx_beg_seq_num.valueKind(j) === Column.ValueKind.Present ? pdbx_beg_seq_num.value(j) : 1
@@ -94,14 +100,24 @@ function getMaps(models: ReadonlyArray<Model>) {
const m = models[i]
if (m.sourceData.kind !== 'mmCIF') continue
const { entity_src_gen, entity_src_nat, pdbx_entity_src_syn } = m.sourceData.data
addSrc(seqToSrcByModelEntity, srcKeySerialMap, i, m, entity_src_gen, entity_src_gen.pdbx_gene_src_gene)
addSrc(seqToSrcByModelEntity, srcKeySerialMap, i, m, entity_src_nat)
addSrc(seqToSrcByModelEntity, srcKeySerialMap, i, m, pdbx_entity_src_syn)
addSrc(seqToSrcByModelEntity, srcKeySerialMap, i, m, entity_src_gen, entity_src_gen.pdbx_gene_src_scientific_name, entity_src_gen.plasmid_name, entity_src_gen.pdbx_gene_src_gene)
addSrc(seqToSrcByModelEntity, srcKeySerialMap, i, m, entity_src_nat, entity_src_nat.pdbx_organism_scientific, entity_src_nat.pdbx_plasmid_name)
addSrc(seqToSrcByModelEntity, srcKeySerialMap, i, m, pdbx_entity_src_syn, pdbx_entity_src_syn.organism_scientific)
}
return { seqToSrcByModelEntity, srcKeySerialMap }
}
function getLabelTable(srcKeySerialMap: Map<string, number>) {
let unnamedCount = 0
return Array.from(srcKeySerialMap.keys()).map(v => {
const vs = v.split('|')
const organism = vs[2]
const name = isInteger(vs[3]) ? `Unnamed ${++unnamedCount}` : vs[3]
return `${name}${organism ? ` (${organism})` : ''}`
})
}
export function EntitySourceColorTheme(ctx: ThemeDataContext, props: PD.Values<EntitySourceColorThemeParams>): ColorTheme<EntitySourceColorThemeParams> {
let color: LocationColor
let legend: ScaleLegend | TableLegend | undefined
@@ -111,14 +127,10 @@ export function EntitySourceColorTheme(ctx: ThemeDataContext, props: PD.Values<E
const { models } = ctx.structure.root
const { seqToSrcByModelEntity, srcKeySerialMap } = getMaps(models)
const labelTable = Array.from(srcKeySerialMap.keys()).map(v => {
const l = v.split('|')[2]
return l === '1' ? 'Unnamed' : l
})
labelTable.push('Unknown')
const labelTable = getLabelTable(srcKeySerialMap)
props.palette.params.valueLabel = (i: number) => labelTable[i]
const palette = getPalette(srcKeySerialMap.size + 1, props)
const palette = getPalette(srcKeySerialMap.size, props)
legend = palette.legend
const getSrcColor = (location: StructureElement.Location) => {
@@ -128,7 +140,7 @@ export function EntitySourceColorTheme(ctx: ThemeDataContext, props: PD.Values<E
const seqToSrc = seqToSrcByModelEntity.get(mK)
if (seqToSrc) {
// minus 1 to convert seqId to array index
return palette.color(seqToSrc[StructureProperties.residue.label_seq_id(location) - 1])
return palette.color(seqToSrc[StructureProperties.residue.label_seq_id(location) - 1] - 1)
} else {
return DefaultColor
}

View File

@@ -19,7 +19,7 @@ const DefaultColor = Color(0xCCCCCC)
const Description = `Assigns a color based on the operator HKL value of a transformed chain.`
export const OperatorHklColorThemeParams = {
...getPaletteParams({ type: 'set', setList: 'set-3' }),
...getPaletteParams({ type: 'set', setList: 'dark-2' }),
}
export type OperatorHklColorThemeParams = typeof OperatorHklColorThemeParams
export function getOperatorHklColorThemeParams(ctx: ThemeDataContext) {

View File

@@ -17,7 +17,7 @@ const DefaultColor = Color(0xCCCCCC)
const Description = `Assigns a color based on the operator name of a transformed chain.`
export const OperatorNameColorThemeParams = {
...getPaletteParams({ type: 'set', setList: 'set-3' }),
...getPaletteParams({ type: 'set', setList: 'dark-2' }),
}
export type OperatorNameColorThemeParams = typeof OperatorNameColorThemeParams
export function getOperatorNameColorThemeParams(ctx: ThemeDataContext) {

View File

@@ -19,7 +19,7 @@ const DefaultColor = Color(0xFAFAFA)
const Description = 'Gives every polymer chain a color based on its `asym_id` value.'
export const PolymerIdColorThemeParams = {
...getPaletteParams({ type: 'set', setList: 'set-3' }),
...getPaletteParams({ type: 'set', setList: 'dark-2' }),
}
export type PolymerIdColorThemeParams = typeof PolymerIdColorThemeParams
export function getPolymerIdColorThemeParams(ctx: ThemeDataContext) {

View File

@@ -17,7 +17,7 @@ const DefaultColor = Color(0xCCCCCC)
const Description = 'Gives every polymer a unique color based on the position (index) of the polymer in the list of polymers in the structure.'
export const PolymerIndexColorThemeParams = {
...getPaletteParams({ type: 'set', setList: 'set-3' }),
...getPaletteParams({ type: 'set', setList: 'dark-2' }),
}
export type PolymerIndexColorThemeParams = typeof PolymerIndexColorThemeParams
export function getPolymerIndexColorThemeParams(ctx: ThemeDataContext) {

View File

@@ -17,7 +17,7 @@ const DefaultColor = Color(0xCCCCCC)
const Description = 'Gives every unit (single chain or collection of single elements) a unique color based on the position (index) of the unit in the list of units in the structure.'
export const UnitIndexColorThemeParams = {
...getPaletteParams({ type: 'set', setList: 'set-3' }),
...getPaletteParams({ type: 'set', setList: 'dark-2' }),
}
export type UnitIndexColorThemeParams = typeof UnitIndexColorThemeParams
export function getUnitIndexColorThemeParams(ctx: ThemeDataContext) {

View File

@@ -54,7 +54,7 @@ function getResidueCount(unit: Unit.Atomic) {
return residueAtomSegments.index[elementEnd] - residueAtomSegments.index[elementStart]
}
export function structureElementStatsLabel(stats: StructureElement.Stats, countsOnly = false) {
export function structureElementStatsLabel(stats: StructureElement.Stats, countsOnly = false): string {
const { unitCount, residueCount, elementCount } = stats
if (!countsOnly && elementCount === 1 && residueCount === 0 && unitCount === 0) {
@@ -74,34 +74,45 @@ export function structureElementStatsLabel(stats: StructureElement.Stats, counts
}
}
export function linkLabel(link: Link.Location) {
export function linkLabel(link: Link.Location): string {
if (!elementLocA) elementLocA = StructureElement.Location.create()
if (!elementLocB) elementLocB = StructureElement.Location.create()
setElementLocation(elementLocA, link.aUnit, link.aIndex)
setElementLocation(elementLocB, link.bUnit, link.bIndex)
return `${elementLabel(elementLocA)} - ${elementLabel(elementLocB)}`
const labelA = _elementLabel(elementLocA)
const labelB = _elementLabel(elementLocB)
let offset = 0
for (let i = 0, il = Math.min(labelA.length, labelB.length); i < il; ++i) {
if (labelA[i] === labelB[i]) offset += 1
else break
}
return `${labelA.join(' | ')} \u2014 ${labelB.slice(offset).join(' | ')}`
}
export type LabelGranularity = 'element' | 'residue' | 'chain' | 'structure'
export function elementLabel(location: StructureElement.Location, granularity: LabelGranularity = 'element') {
export function elementLabel(location: StructureElement.Location, granularity: LabelGranularity = 'element'): string {
return _elementLabel(location, granularity).join(' | ')
}
function _elementLabel(location: StructureElement.Location, granularity: LabelGranularity = 'element'): string[] {
const entry = location.unit.model.entry
const model = `Model ${location.unit.model.modelNum}`
const instance = location.unit.conformation.operator.name
const label = [entry, model, instance]
if (Unit.isAtomic(location.unit)) {
label.push(atomicElementLabel(location as StructureElement.Location<Unit.Atomic>, granularity))
label.push(..._atomicElementLabel(location as StructureElement.Location<Unit.Atomic>, granularity))
} else if (Unit.isCoarse(location.unit)) {
label.push(coarseElementLabel(location as StructureElement.Location<Unit.Spheres | Unit.Gaussians>, granularity))
label.push(..._coarseElementLabel(location as StructureElement.Location<Unit.Spheres | Unit.Gaussians>, granularity))
} else {
label.push('Unknown')
}
return label.join(' | ')
return label
}
export function atomicElementLabel(location: StructureElement.Location<Unit.Atomic>, granularity: LabelGranularity) {
function _atomicElementLabel(location: StructureElement.Location<Unit.Atomic>, granularity: LabelGranularity): string[] {
const label_asym_id = Props.chain.label_asym_id(location)
const auth_asym_id = Props.chain.auth_asym_id(location)
const seq_id = location.unit.model.atomicHierarchy.residues.auth_seq_id.isDefined ? Props.residue.auth_seq_id(location) : Props.residue.label_seq_id(location)
@@ -126,10 +137,10 @@ export function atomicElementLabel(location: StructureElement.Location<Unit.Atom
label.push(`Chain ${label_asym_id}:${auth_asym_id}`)
}
return label.reverse().join(' | ')
return label.reverse()
}
export function coarseElementLabel(location: StructureElement.Location<Unit.Spheres | Unit.Gaussians>, granularity: LabelGranularity) {
function _coarseElementLabel(location: StructureElement.Location<Unit.Spheres | Unit.Gaussians>, granularity: LabelGranularity): string[] {
const asym_id = Props.coarse.asym_id(location)
const seq_id_begin = Props.coarse.seq_id_begin(location)
const seq_id_end = Props.coarse.seq_id_end(location)
@@ -151,5 +162,5 @@ export function coarseElementLabel(location: StructureElement.Location<Unit.Sphe
label.push(`Chain ${asym_id}`)
}
return label.reverse().join(' | ')
return label.reverse()
}

View File

@@ -10,31 +10,32 @@ import { interpolate, stringToWords } from './string';
export { Binding }
interface Binding {
trigger: Binding.Trigger
triggers: Binding.Trigger[]
description: string
}
function Binding(trigger: Binding.Trigger, description = '') {
return Binding.create(trigger, description)
function Binding(triggers: Binding.Trigger[], description = '') {
return Binding.create(triggers, description)
}
namespace Binding {
export function create(trigger: Trigger, description = ''): Binding {
return { trigger, description }
export function create(triggers: Trigger[], description = ''): Binding {
return { triggers, description }
}
export const Empty: Binding = { trigger: {}, description: '' }
export const Empty: Binding = { triggers: [], description: '' }
export function isEmpty(binding: Binding) {
return binding.trigger.buttons === undefined && binding.trigger.modifiers === undefined
return binding.triggers.length === 0 ||
binding.triggers.every(t => t.buttons === undefined && t.modifiers === undefined)
}
export function match(binding: Binding, buttons: ButtonsType, modifiers: ModifiersKeys) {
return Trigger.match(binding.trigger, buttons, modifiers)
return binding.triggers.some(t => Trigger.match(t, buttons, modifiers))
}
export function format(binding: Binding, name = '') {
const help = binding.description || stringToWords(name)
return interpolate(help, { trigger: Trigger.format(binding.trigger) })
return interpolate(help, { triggers: binding.triggers.map(t => Trigger.format(t)).join(' or ') })
}
export interface Trigger {

View File

@@ -80,7 +80,7 @@ export function getPalette(count: number, props: PaletteProps) {
count = Math.min(count, props.palette.params.maxCount)
colors = distinctColors(count, props.palette.params)
}
const { valueLabel } = props.palette.params
const valueLabel = props.palette.params.valueLabel || (i => '' + i);
const colorsLength = colors.length
const table: [string, Color][] = []
for (let i = 0; i < count; ++i) {

View File

@@ -46,4 +46,10 @@ export function getArrayDigitCount(xs: ArrayLike<number>, maxDigits: number, del
}
}
return { mantissaDigits, integerDigits };
}
export function isInteger(s: string) {
s = s.trim()
const n = parseInt(s, 10)
return isNaN(n) ? false : n.toString() === s
}

View File

@@ -57,7 +57,7 @@ export async function resolveJob(job: Job): Promise<CifWriter.Encoder<any>> {
const queries = structures.map(s => job.queryDefinition.query(job.normalizedParams, s));
const result: Structure[] = [];
for (let i = 0; i < structures.length; i++) {
const s = await StructureSelection.unionStructure(StructureQuery.run(queries[i], structures[i], Config.maxQueryTimeInMs))
const s = await StructureSelection.unionStructure(StructureQuery.run(queries[i], structures[i], { timeoutMs: Config.maxQueryTimeInMs }))
if (s.elementCount > 0) result.push(s);
}
perf.end('query');