mirror of
https://github.com/molstar/molstar.git
synced 2026-06-07 23:34:23 +08:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fde5dd1bf | ||
|
|
2f9ecb9396 | ||
|
|
85092279fa | ||
|
|
d0189523e4 | ||
|
|
a26d03205a | ||
|
|
862eda6dd4 | ||
|
|
4a7f0fc206 | ||
|
|
6bbee58d39 | ||
|
|
989faed410 | ||
|
|
198e2f2043 | ||
|
|
6ae3d4110d | ||
|
|
c34e1be43b | ||
|
|
17a440ad9c | ||
|
|
04dcfc7adb | ||
|
|
ca66e334c1 | ||
|
|
54bba4c92f | ||
|
|
9c046b808c | ||
|
|
ca5d1865cc | ||
|
|
d8f1aa5bc9 | ||
|
|
6ac790e083 | ||
|
|
d0870e4bbb | ||
|
|
5138595f36 | ||
|
|
d9aa5684a9 | ||
|
|
8e2521a7a9 | ||
|
|
7dd7a117cb | ||
|
|
defbadf4d7 | ||
|
|
131e88080a | ||
|
|
fb78b886c1 | ||
|
|
aed0b87b16 | ||
|
|
1316cc6a40 |
62
package-lock.json
generated
62
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.4",
|
||||
"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",
|
||||
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.4",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"')
|
||||
}
|
||||
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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)
|
||||
@@ -224,6 +225,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 +245,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 +264,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 +286,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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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)]);
|
||||
};
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -78,6 +78,17 @@ export namespace Loci {
|
||||
return Loci(structure, []);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -54,34 +54,22 @@ export namespace Stats {
|
||||
while (i < size) {
|
||||
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
|
||||
}
|
||||
|
||||
if (offsets[rI + 1] - offsets[rI] === i) {
|
||||
// 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 += i
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO
|
||||
stats.elementCount += size
|
||||
if (stats.elementCount === 1) {
|
||||
Location.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)])
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -51,17 +51,31 @@ 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) {
|
||||
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(map.get(name)!, defaultView, data && data.structure))),
|
||||
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(entryData?: VolumeServerInfo.EntryData, defaultView?: ViewTypes, structure?: Structure) {
|
||||
// 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),
|
||||
@@ -85,7 +99,6 @@ export namespace VolumeStreaming {
|
||||
'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]),
|
||||
}, { isFlat: true }),
|
||||
bindings: PD.Value(binding || DefaultBindings, { isHidden: true }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -118,11 +131,16 @@ export namespace VolumeStreaming {
|
||||
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 +150,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 +183,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 +238,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 +259,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 +296,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 +332,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 +357,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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,15 +22,24 @@ 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),
|
||||
entries: PD.ObjectList({ id: PD.Text(ids[0] || '') }, ({ id }) => id, { defaultValue: ids.map(id => ({ id })) }),
|
||||
serverUrl: PD.Text('https://ds.litemol.org'),
|
||||
defaultView: PD.Select<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box', VolumeStreaming.ViewTypeOptions as any),
|
||||
behaviorRef: PD.Text('', { isHidden: true }),
|
||||
@@ -40,23 +49,35 @@ export const InitVolumeStreaming = StateAction.build({
|
||||
},
|
||||
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.emContourProvider, plugin, taskCtx, emdbId)
|
||||
addEntry(entries, params.method, emdbId, contourLevel || 0)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
emDefaultContourLevel = await getContourLevel(params.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
|
||||
entries
|
||||
});
|
||||
|
||||
const infoObj = await state.updateTree(infoTree).runInContext(taskCtx);
|
||||
@@ -78,26 +99,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 +146,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(', ')}` });
|
||||
})
|
||||
});
|
||||
|
||||
@@ -147,17 +189,25 @@ const CreateVolumeStreamingBehavior = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
})({
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -61,14 +61,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 +84,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 +106,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 +172,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])),
|
||||
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,
|
||||
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']] })
|
||||
};
|
||||
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} />
|
||||
</>
|
||||
|
||||
@@ -64,7 +64,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)
|
||||
}
|
||||
|
||||
@@ -154,8 +156,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']] })
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -16,15 +16,30 @@ 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 } from '../../../mol-state';
|
||||
import { PluginStateObject } from '../../state/objects';
|
||||
|
||||
abstract class BaseStructureRepresentationControls extends PluginUIComponent {
|
||||
onChange = (value: string) => {
|
||||
console.log('onChange', value)
|
||||
}
|
||||
|
||||
abstract label: string
|
||||
abstract lociGetter(structure: Structure): StructureElement.Loci
|
||||
|
||||
/** root structures */
|
||||
protected get structures() {
|
||||
return this.plugin.state.dataState.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure)).map(s => s.obj!.data)
|
||||
}
|
||||
|
||||
/** applicapble 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
|
||||
}
|
||||
|
||||
show = (value: string) => {
|
||||
this.plugin.helpers.structureRepresentation.set('add', value, this.lociGetter)
|
||||
}
|
||||
@@ -46,19 +61,19 @@ 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>
|
||||
<optgroup label='Show'>{TypeOptions}</optgroup>
|
||||
</ButtonSelect>
|
||||
<ButtonSelect label='Hide' onChange={this.hide}>
|
||||
<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}>
|
||||
<optgroup label='Clear'>
|
||||
|
||||
@@ -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,
|
||||
@@ -46,8 +45,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 +65,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)
|
||||
@@ -86,7 +85,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
|
||||
|
||||
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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]); }
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
34
src/mol-script/language/symbol-table/internal.ts
Normal file
34
src/mol-script/language/symbol-table/internal.ts
Normal 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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user