mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 22:31:26 +08:00
Compare commits
25 Commits
v0.7.0-dev
...
v0.7.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8c2355d3e | ||
|
|
3d1366024d | ||
|
|
f6964d2a66 | ||
|
|
de60f70af5 | ||
|
|
8f2e619162 | ||
|
|
fbcef01c55 | ||
|
|
641e0639d4 | ||
|
|
5048573976 | ||
|
|
78e4d8536d | ||
|
|
a01d088205 | ||
|
|
4b1d1a045d | ||
|
|
e2857d00b4 | ||
|
|
a55a71d31a | ||
|
|
acf793f112 | ||
|
|
2d58ea28ea | ||
|
|
b21de78eb5 | ||
|
|
376d4b4ee1 | ||
|
|
e39304c7cf | ||
|
|
7cba9cda0c | ||
|
|
04c690e8f9 | ||
|
|
170d0fbc9d | ||
|
|
40d632a7b1 | ||
|
|
2e754d23f4 | ||
|
|
5639a4b37c | ||
|
|
8c959f8a60 |
1
.npmignore
Normal file
1
.npmignore
Normal file
@@ -0,0 +1 @@
|
||||
tsconfig.servers.buildinfo
|
||||
81
package-lock.json
generated
81
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.7.0-dev.2",
|
||||
"version": "0.7.0-dev.6",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -3143,7 +3143,7 @@
|
||||
},
|
||||
"browserify-aes": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
||||
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -3180,7 +3180,7 @@
|
||||
},
|
||||
"browserify-rsa": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
|
||||
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -3374,7 +3374,7 @@
|
||||
},
|
||||
"camelcase-keys": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
|
||||
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -3608,7 +3608,7 @@
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -4193,7 +4193,7 @@
|
||||
},
|
||||
"create-hash": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
||||
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -4206,7 +4206,7 @@
|
||||
},
|
||||
"create-hmac": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
|
||||
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
|
||||
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -4580,7 +4580,7 @@
|
||||
},
|
||||
"diffie-hellman": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
|
||||
"integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -4709,6 +4709,12 @@
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"ejs": {
|
||||
"version": "2.5.9",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.9.tgz",
|
||||
"integrity": "sha512-GJCAeDBKfREgkBtgrYSf9hQy9kTb3helv0zGdzqhM7iAkW8FA/ZF97VQDbwFiwIT8MQLLOe5VlPZOEvZAqtUAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"elegant-spinner": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz",
|
||||
@@ -5963,6 +5969,12 @@
|
||||
"readable-stream": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fs": {
|
||||
"version": "0.0.1-security",
|
||||
"resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
|
||||
"integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=",
|
||||
"dev": true
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz",
|
||||
@@ -6096,7 +6108,7 @@
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -9133,7 +9145,7 @@
|
||||
},
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -9171,7 +9183,7 @@
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -9668,7 +9680,7 @@
|
||||
},
|
||||
"meow": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
|
||||
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -10022,7 +10034,7 @@
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
|
||||
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
|
||||
"dev": true
|
||||
}
|
||||
@@ -10156,7 +10168,7 @@
|
||||
},
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -10188,7 +10200,7 @@
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -12004,7 +12016,7 @@
|
||||
"dependencies": {
|
||||
"convert-source-map": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz",
|
||||
"integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=",
|
||||
"dev": true
|
||||
}
|
||||
@@ -12294,7 +12306,7 @@
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -12442,7 +12454,7 @@
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
|
||||
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -12570,7 +12582,7 @@
|
||||
},
|
||||
"sha.js": {
|
||||
"version": "2.4.11",
|
||||
"resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
||||
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
||||
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -12650,7 +12662,7 @@
|
||||
},
|
||||
"slice-ansi": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
|
||||
"integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
|
||||
"dev": true
|
||||
},
|
||||
@@ -13761,6 +13773,12 @@
|
||||
"integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
|
||||
"integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=",
|
||||
"dev": true
|
||||
},
|
||||
"union": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
|
||||
@@ -14429,6 +14447,7 @@
|
||||
"version": "2.9.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
@@ -14447,6 +14466,7 @@
|
||||
"version": "0.5.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
@@ -14550,6 +14570,7 @@
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -14627,7 +14648,8 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -14727,12 +14749,14 @@
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"bundled": true,
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -15278,6 +15302,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"webpack-version-file-plugin": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-version-file-plugin/-/webpack-version-file-plugin-0.4.0.tgz",
|
||||
"integrity": "sha512-0+lgy3t04EAR3pIWDVKckytGlPKNmbGxPlB5DZ3vF3atC8DWtHwimUOuvdljEnGaTIpiTWitFJPaMLUvKCqO9g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ejs": "~> 2.5.5",
|
||||
"fs": "^0.0.1-security",
|
||||
"underscore": "~1.6.0"
|
||||
}
|
||||
},
|
||||
"whatwg-encoding": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.7.0-dev.2",
|
||||
"version": "0.7.0-dev.6",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -115,7 +115,8 @@
|
||||
"ts-jest": "^25.4.0",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11"
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-version-file-plugin": "^0.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.9.10",
|
||||
|
||||
@@ -225,6 +225,7 @@ function getSymbolScale(symbol: string) {
|
||||
function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.RotationAxes, size: number, structure: Structure) {
|
||||
const eye = Vec3();
|
||||
const target = Vec3();
|
||||
const dir = Vec3();
|
||||
const up = Vec3();
|
||||
let pair: Mutable<AssemblySymmetry.RotationAxes> | undefined = undefined;
|
||||
|
||||
@@ -246,8 +247,8 @@ function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.Rota
|
||||
const a5dir = Vec3.sub(Vec3(), a5.end, a5.start);
|
||||
pair = [a5];
|
||||
for (const a of axes.filter(a => a.order === 3)) {
|
||||
let d = radToDeg(Vec3.angle(Vec3.sub(up, a.end, a.start), a5dir));
|
||||
if (equalEps(d, 100.81, 0.1) || equalEps(d, 79.19, 0.1)) {
|
||||
const d = radToDeg(Vec3.angle(Vec3.sub(up, a.end, a.start), a5dir));
|
||||
if (!pair[1] && (equalEps(d, 100.81, 0.1) || equalEps(d, 79.19, 0.1))) {
|
||||
pair[1] = a;
|
||||
break;
|
||||
}
|
||||
@@ -263,8 +264,8 @@ function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.Rota
|
||||
Vec3.copy(target, aA.end);
|
||||
if (aB) {
|
||||
Vec3.sub(up, aB.end, aB.start);
|
||||
const d = Vec3.dot(eye, up);
|
||||
if (d < 0) Vec3.negate(up, up);
|
||||
Vec3.sub(dir, eye, target);
|
||||
if (Vec3.dot(dir, up) < 0) Vec3.negate(up, up);
|
||||
Mat4.targetTo(t, eye, target, up);
|
||||
Mat4.scaleUniformly(t, t, size * getSymbolScale(symbol));
|
||||
} else {
|
||||
|
||||
@@ -71,8 +71,7 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean,
|
||||
}
|
||||
|
||||
unregister() {
|
||||
// TODO
|
||||
// DefaultQueryRuntimeTable.removeCustomProp(this.provider.descriptor);
|
||||
DefaultQueryRuntimeTable.removeCustomProp(this.provider.descriptor);
|
||||
|
||||
this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
|
||||
|
||||
@@ -341,7 +340,7 @@ export const ValidationReportDensityFitPreset = StructureRepresentationPresetPro
|
||||
description: 'Color structure based on density fit. Data from wwPDB Validation Report, obtained via RCSB PDB.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]) && Model.hasXrayMap(a.data.models[0]);
|
||||
return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]) && Model.isFromXray(a.data.models[0]) && Model.probablyHasDensityMap(a.data.models[0]);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
|
||||
@@ -67,7 +67,7 @@ export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationRep
|
||||
factory: DensityFitColorTheme,
|
||||
getParams: () => ({}),
|
||||
defaultValues: PD.getDefaultValues({}),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.hasXrayMap(ctx.structure.models[0]),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.isFromXray(ctx.structure.models[0]) && Model.probablyHasDensityMap(ctx.structure.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
|
||||
@@ -132,12 +132,12 @@ namespace Canvas3D {
|
||||
if (webgl.isContextLost) return;
|
||||
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return;
|
||||
|
||||
console.log('lose context');
|
||||
if (isDebugMode) console.log('lose context');
|
||||
loseContextExt.loseContext();
|
||||
|
||||
setTimeout(() => {
|
||||
if (!webgl.isContextLost) return;
|
||||
console.log('restore context');
|
||||
if (isDebugMode) console.log('restore context');
|
||||
loseContextExt.restoreContext();
|
||||
}, 1000);
|
||||
}, false);
|
||||
|
||||
@@ -159,5 +159,5 @@ const instanceMaterialId = getNextMaterialId();
|
||||
|
||||
function createBoundingSphereRenderObject(mesh: Mesh, color: Color, materialId: number, transform?: TransformData) {
|
||||
const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform);
|
||||
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, opaque: false }, materialId);
|
||||
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, opaque: false, writeDepth: false }, materialId);
|
||||
}
|
||||
@@ -72,15 +72,18 @@ export namespace BaseGeometry {
|
||||
}
|
||||
|
||||
export function createRenderableState(props: Partial<PD.Values<Params>> = {}): RenderableState {
|
||||
const opaque = props.alpha === undefined ? true : props.alpha === 1;
|
||||
return {
|
||||
visible: true,
|
||||
alphaFactor: 1,
|
||||
pickable: true,
|
||||
opaque: props.alpha === undefined ? true : props.alpha === 1
|
||||
opaque,
|
||||
writeDepth: opaque,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateRenderableState(state: RenderableState, props: PD.Values<Params>) {
|
||||
state.opaque = props.alpha * state.alphaFactor >= 1;
|
||||
state.writeDepth = state.opaque;
|
||||
}
|
||||
}
|
||||
@@ -98,8 +98,7 @@ namespace Image {
|
||||
return image;
|
||||
}
|
||||
|
||||
function update(imageTexture: TextureImage<Uint8Array | Float32Array>, corners: Float32Array, groupTexture: TextureImage<Float32Array>, image: Image): Image {
|
||||
|
||||
function update(imageTexture: TextureImage<Float32Array>, corners: Float32Array, groupTexture: TextureImage<Float32Array>, image: Image): Image {
|
||||
const width = imageTexture.width;
|
||||
const height = imageTexture.height;
|
||||
|
||||
@@ -116,7 +115,7 @@ namespace Image {
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
interpolation: PD.Select('bspline', PD.objectToOptions(InterpolationTypes), { isEssential: true }),
|
||||
interpolation: PD.Select('bspline', PD.objectToOptions(InterpolationTypes)),
|
||||
};
|
||||
export type Params = typeof Params
|
||||
|
||||
@@ -176,7 +175,7 @@ namespace Image {
|
||||
}
|
||||
|
||||
function updateValues(values: ImageValues, props: PD.Values<Params>) {
|
||||
ValueCell.updateIfChanged(values.uAlpha, props.alpha);
|
||||
BaseGeometry.updateValues(values, props);
|
||||
ValueCell.updateIfChanged(values.dInterpolation, props.interpolation);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ export function addTube(state: MeshBuilder.State, controlPoints: ArrayLike<numbe
|
||||
if (radialSegments === 2) {
|
||||
// add2AndScale2(normalVector, u, v, w * Math.cos(t), h * Math.sin(t))
|
||||
Vec3.copy(normalVector, v);
|
||||
console.log(i, t);
|
||||
Vec3.normalize(normalVector, normalVector);
|
||||
if (t !== 0 || i % 2 === 0) Vec3.negate(normalVector, normalVector);
|
||||
} else {
|
||||
|
||||
@@ -86,7 +86,8 @@ function createPoints() {
|
||||
visible: true,
|
||||
alphaFactor: 1,
|
||||
pickable: true,
|
||||
opaque: true
|
||||
opaque: true,
|
||||
writeDepth: true
|
||||
};
|
||||
|
||||
return createRenderObject('points', values, state, -1);
|
||||
|
||||
@@ -18,6 +18,7 @@ export type RenderableState = {
|
||||
alphaFactor: number
|
||||
pickable: boolean
|
||||
opaque: boolean
|
||||
writeDepth: boolean,
|
||||
}
|
||||
|
||||
export interface Renderable<T extends RenderableValues> {
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface TextureImage<T extends Uint8Array | Float32Array> {
|
||||
readonly array: T
|
||||
readonly width: number
|
||||
readonly height: number
|
||||
readonly flipY?: boolean
|
||||
}
|
||||
|
||||
export interface TextureVolume<T extends Uint8Array | Float32Array> {
|
||||
|
||||
@@ -212,6 +212,10 @@ namespace Renderer {
|
||||
state.cullFace(gl.BACK);
|
||||
}
|
||||
|
||||
if (variant === 'color') {
|
||||
state.depthMask(r.state.writeDepth);
|
||||
}
|
||||
|
||||
r.render(variant);
|
||||
}
|
||||
};
|
||||
@@ -245,11 +249,11 @@ namespace Renderer {
|
||||
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.disable(gl.BLEND);
|
||||
state.depthMask(true);
|
||||
state.colorMask(true, true, true, true);
|
||||
state.enable(gl.DEPTH_TEST);
|
||||
|
||||
if (clear) {
|
||||
state.depthMask(true);
|
||||
if (variant === 'color') {
|
||||
state.clearColor(bgColor[0], bgColor[1], bgColor[2], transparentBackground ? 0 : 1);
|
||||
} else {
|
||||
@@ -268,10 +272,11 @@ namespace Renderer {
|
||||
state.enable(gl.BLEND);
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
if (!r.state.opaque) {
|
||||
state.depthMask(false);
|
||||
renderObject(r, variant);
|
||||
}
|
||||
if (!r.state.opaque && r.state.writeDepth) renderObject(r, variant);
|
||||
}
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
const r = renderables[i];
|
||||
if (!r.state.opaque && !r.state.writeDepth) renderObject(r, variant);
|
||||
}
|
||||
} else { // picking & depth
|
||||
for (let i = 0, il = renderables.length; i < il; ++i) {
|
||||
|
||||
@@ -50,9 +50,11 @@ function renderableSort(a: Renderable<RenderableValues & BaseValues>, b: Rendera
|
||||
const materialIdB = b.materialId;
|
||||
|
||||
if (drawProgramIdA !== drawProgramIdB) {
|
||||
return drawProgramIdA - drawProgramIdB; // sort by program id to minimize gl state changes
|
||||
// sort by program id to minimize gl state changes
|
||||
return drawProgramIdA - drawProgramIdB;
|
||||
} else if (materialIdA !== materialIdB) {
|
||||
return materialIdA - materialIdB; // sort by material id to minimize gl state changes
|
||||
// sort by material id to minimize gl state changes
|
||||
return materialIdA - materialIdB;
|
||||
} else {
|
||||
return a.id - b.id;
|
||||
}
|
||||
|
||||
@@ -118,6 +118,14 @@ export function getAttachment(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
throw new Error('unknown texture attachment');
|
||||
}
|
||||
|
||||
function isTexture2d(x: TextureImage<any> | TextureVolume<any>, target: number, gl: GLRenderingContext): x is TextureImage<any> {
|
||||
return target === gl.TEXTURE_2D;
|
||||
}
|
||||
|
||||
function isTexture3d(x: TextureImage<any> | TextureVolume<any>, target: number, gl: WebGL2RenderingContext): x is TextureImage<any> {
|
||||
return target === gl.TEXTURE_3D;
|
||||
}
|
||||
|
||||
export interface Texture {
|
||||
readonly id: number
|
||||
readonly target: number
|
||||
@@ -214,14 +222,13 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
||||
gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
|
||||
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
|
||||
if (target === gl.TEXTURE_2D) {
|
||||
const { array, width: _width, height: _height } = data as TextureImage<any>;
|
||||
width = _width, height = _height;
|
||||
gl.texImage2D(target, 0, internalFormat, width, height, 0, format, type, array);
|
||||
} else if (isWebGL2(gl) && target === gl.TEXTURE_3D) {
|
||||
const { array, width: _width, height: _height, depth: _depth } = data as TextureVolume<any>;
|
||||
width = _width, height = _height, depth = _depth;
|
||||
gl.texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, array);
|
||||
if (isTexture2d(data, target, gl)) {
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !!data.flipY);
|
||||
width = data.width, height = data.height;
|
||||
gl.texImage2D(target, 0, internalFormat, width, height, 0, format, type, data.array);
|
||||
} else if (isWebGL2(gl) && isTexture3d(data, target, gl)) {
|
||||
width = data.width, height = data.height, depth = data.depth;
|
||||
gl.texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, data.array);
|
||||
} else {
|
||||
throw new Error('unknown texture target');
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { SimpleBuffer } from './simple-buffer';
|
||||
const fs = typeof document === 'undefined' ? require('fs') as typeof import('fs') : void 0;
|
||||
|
||||
export interface FileHandle {
|
||||
name: string
|
||||
/**
|
||||
* Asynchronously reads data, returning buffer and number of bytes read
|
||||
*
|
||||
@@ -44,8 +45,9 @@ export interface FileHandle {
|
||||
}
|
||||
|
||||
export namespace FileHandle {
|
||||
export function fromBuffer(buffer: SimpleBuffer): FileHandle {
|
||||
export function fromBuffer(buffer: SimpleBuffer, name: string): FileHandle {
|
||||
return {
|
||||
name,
|
||||
readBuffer: (position: number, sizeOrBuffer: SimpleBuffer | number, size?: number, byteOffset?: number) => {
|
||||
let bytesRead: number;
|
||||
let outBuffer: SimpleBuffer;
|
||||
@@ -82,9 +84,10 @@ export namespace FileHandle {
|
||||
};
|
||||
}
|
||||
|
||||
export function fromDescriptor(file: number): FileHandle {
|
||||
export function fromDescriptor(file: number, name: string): FileHandle {
|
||||
if (fs === undefined) throw new Error('fs module not available');
|
||||
return {
|
||||
name,
|
||||
readBuffer: (position: number, sizeOrBuffer: SimpleBuffer | number, length?: number, byteOffset?: number) => {
|
||||
return new Promise((res, rej) => {
|
||||
let outBuffer: SimpleBuffer;
|
||||
|
||||
@@ -31,7 +31,7 @@ function createCcp4Data() {
|
||||
describe('ccp4 reader', () => {
|
||||
it('basic', async () => {
|
||||
const data = createCcp4Data();
|
||||
const parsed = await CCP4.parse(data).run();
|
||||
const parsed = await CCP4.parse(data, 'test.ccp4').run();
|
||||
|
||||
if (parsed.isError) {
|
||||
throw new Error(parsed.message);
|
||||
|
||||
@@ -8,10 +8,14 @@ import { parseFloat as fastParseFloat, parseInt as fastParseInt, getNumberType,
|
||||
|
||||
describe('common', () => {
|
||||
it('number-parser fastParseFloat', () => {
|
||||
// ignore suffix numbers in parentheses
|
||||
expect(fastParseFloat('11.0829(23)', 0, 11)).toBe(11.0829);
|
||||
// scientific with no space between consecutive values
|
||||
expect(fastParseFloat('-5.1E-01-6.1E-01', 0, 11)).toBe(-0.51);
|
||||
});
|
||||
|
||||
it('number-parser fastParseInt', () => {
|
||||
// ignore suffix numbers in parentheses
|
||||
expect(fastParseInt('11(23)', 0, 11)).toBe(11);
|
||||
});
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ async function parseInternal(file: FileHandle, size: number, ctx: RuntimeContext
|
||||
const buffer = createTypedArrayBufferContext(count, valueType);
|
||||
readCcp4Slices(header, buffer, file, offset, byteCount, littleEndian);
|
||||
|
||||
const result: Ccp4File = { header, values: buffer.values };
|
||||
const result: Ccp4File = { header, values: buffer.values, name: file.name };
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -174,6 +174,6 @@ export function parseFile(file: FileHandle, size: number) {
|
||||
});
|
||||
}
|
||||
|
||||
export function parse(buffer: Uint8Array) {
|
||||
return parseFile(FileHandle.fromBuffer(SimpleBuffer.fromUint8Array(buffer)), buffer.length);
|
||||
export function parse(buffer: Uint8Array, name: string) {
|
||||
return parseFile(FileHandle.fromBuffer(SimpleBuffer.fromUint8Array(buffer), name), buffer.length);
|
||||
}
|
||||
@@ -114,6 +114,7 @@ export interface Ccp4Header {
|
||||
* CCP4 format does not use the ORIGIN header records (words 50-52)
|
||||
*/
|
||||
export interface Ccp4File {
|
||||
name: string
|
||||
header: Ccp4Header
|
||||
values: Float32Array | Int16Array | Int8Array | Uint16Array
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { parseFloat as fastParseFloat } from '../common/text/number-parser';
|
||||
// https://h5cube-spec.readthedocs.io/en/latest/cubeformat.html
|
||||
|
||||
export interface CubeFile {
|
||||
name: string,
|
||||
header: CubeFile.Header,
|
||||
atoms: CubeFile.Atoms,
|
||||
values: Float64Array
|
||||
@@ -24,6 +25,7 @@ export interface CubeFile {
|
||||
|
||||
export namespace CubeFile {
|
||||
export interface Header {
|
||||
orbitals: boolean,
|
||||
comment1: string,
|
||||
comment2: string,
|
||||
atomCount: number,
|
||||
@@ -79,8 +81,7 @@ function readHeader(tokenizer: Tokenizer) {
|
||||
for (let i = 0, _i = +counts[0]; i < _i; i++) dataSetIds.push(+counts[i + 1]);
|
||||
}
|
||||
|
||||
const header: CubeFile.Header = { comment1, comment2, atomCount, origin, dim: Vec3.create(NVX, NVY, NVZ), basisX, basisY, basisZ, dataSetIds };
|
||||
|
||||
const header: CubeFile.Header = { orbitals: rawAtomCount < 0, comment1, comment2, atomCount, origin, dim: Vec3.create(NVX, NVY, NVZ), basisX, basisY, basisZ, dataSetIds };
|
||||
return { header, atoms };
|
||||
}
|
||||
|
||||
@@ -129,12 +130,12 @@ function readValues(ctx: RuntimeContext, tokenizer: Tokenizer, header: CubeFile.
|
||||
}, (ctx, _, i) => ctx.update({ current: Math.min(i, N), max: N }));
|
||||
}
|
||||
|
||||
export function parseCube(data: string) {
|
||||
export function parseCube(data: string, name: string) {
|
||||
return Task.create<Result<CubeFile>>('Parse Cube', async taskCtx => {
|
||||
await taskCtx.update('Reading header...');
|
||||
const tokenizer = Tokenizer(data);
|
||||
const { header, atoms } = readHeader(tokenizer);
|
||||
const values = await readValues(taskCtx, tokenizer, header);
|
||||
return Result.success({ header, atoms, values });
|
||||
return Result.success({ header, atoms, values, name });
|
||||
});
|
||||
}
|
||||
@@ -134,7 +134,7 @@ async function parseInternal(file: FileHandle, size: number, ctx: RuntimeContext
|
||||
const values = new Float32Array(valueCount);
|
||||
await parseDsn6Values(header, buffer, values, littleEndian);
|
||||
|
||||
const result: Dsn6File = { header, values };
|
||||
const result: Dsn6File = { header, values, name: file.name };
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -148,6 +148,6 @@ export function parseFile(file: FileHandle, size: number) {
|
||||
});
|
||||
}
|
||||
|
||||
export function parse(buffer: Uint8Array) {
|
||||
return parseFile(FileHandle.fromBuffer(SimpleBuffer.fromUint8Array(buffer)), buffer.length);
|
||||
export function parse(buffer: Uint8Array, name: string) {
|
||||
return parseFile(FileHandle.fromBuffer(SimpleBuffer.fromUint8Array(buffer), name), buffer.length);
|
||||
}
|
||||
@@ -39,6 +39,7 @@ export interface Dsn6Header {
|
||||
* BRIX http://svn.cgl.ucsf.edu/svn/chimera/trunk/libs/VolumeData/dsn6/brix-1.html
|
||||
*/
|
||||
export interface Dsn6File {
|
||||
name: string
|
||||
header: Dsn6Header
|
||||
values: Float32Array
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { utf8Read } from '../../common/utf8';
|
||||
// http://apbs-pdb2pqr.readthedocs.io/en/latest/formats/opendx.html
|
||||
|
||||
export interface DxFile {
|
||||
name: string,
|
||||
header: DxFile.Header,
|
||||
values: Float64Array
|
||||
}
|
||||
@@ -88,16 +89,16 @@ function readValuesText(ctx: RuntimeContext, tokenizer: Tokenizer, header: DxFil
|
||||
}, (ctx, _, i) => ctx.update({ current: Math.min(i, N), max: N }));
|
||||
}
|
||||
|
||||
async function parseText(taskCtx: RuntimeContext, data: string) {
|
||||
async function parseText(taskCtx: RuntimeContext, data: string, name: string) {
|
||||
await taskCtx.update('Reading header...');
|
||||
const tokenizer = Tokenizer(data as string);
|
||||
const { header } = readHeader(tokenizer);
|
||||
await taskCtx.update('Reading values...');
|
||||
const values = await readValuesText(taskCtx, tokenizer, header);
|
||||
return Result.success({ header, values });
|
||||
return Result.success({ header, values, name });
|
||||
}
|
||||
|
||||
async function parseBinary(taskCtx: RuntimeContext, data: Uint8Array) {
|
||||
async function parseBinary(taskCtx: RuntimeContext, data: Uint8Array, name: string) {
|
||||
await taskCtx.update('Reading header...');
|
||||
|
||||
const headerString = utf8Read(data, 0, 1000);
|
||||
@@ -117,12 +118,12 @@ async function parseBinary(taskCtx: RuntimeContext, data: Uint8Array) {
|
||||
|
||||
// TODO: why doesnt this work? throw "attempting to construct out-of-bounds TypedArray"
|
||||
// const values = new Float64Array(data.buffer, data.byteOffset + headerByteCount, header.dim[0] * header.dim[1] * header.dim[2]);
|
||||
return Result.success({ header, values });
|
||||
return Result.success({ header, values, name });
|
||||
}
|
||||
|
||||
export function parseDx(data: string | Uint8Array) {
|
||||
export function parseDx(data: string | Uint8Array, name: string) {
|
||||
return Task.create<Result<DxFile>>('Parse Cube', taskCtx => {
|
||||
if (typeof data === 'string') return parseText(taskCtx, data);
|
||||
return parseBinary(taskCtx, data);
|
||||
if (typeof data === 'string') return parseText(taskCtx, data, name);
|
||||
return parseBinary(taskCtx, data, name);
|
||||
});
|
||||
}
|
||||
@@ -263,4 +263,59 @@ describe('tensor', () => {
|
||||
|
||||
expect(data).toEqual(exp);
|
||||
});
|
||||
|
||||
it('indexing', () => {
|
||||
function permutations<T>(inputArr: T[]): T[][] {
|
||||
let result: T[][] = [];
|
||||
function permute(arr: any, m: any = []) {
|
||||
if (arr.length === 0) {
|
||||
result.push(m);
|
||||
} else {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
let curr = arr.slice();
|
||||
let next = curr.splice(i, 1);
|
||||
permute(curr.slice(), m.concat(next));
|
||||
}
|
||||
}
|
||||
}
|
||||
permute(inputArr);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
for (let dim = 1; dim <= 5; dim++) {
|
||||
const axes = [], dims: number[] = [];
|
||||
const u: number[] = [], v: number[] = [];
|
||||
|
||||
for (let i = 0; i < dim; i++) {
|
||||
axes.push(i);
|
||||
dims.push(3);
|
||||
u.push(0);
|
||||
v.push(0);
|
||||
}
|
||||
|
||||
const forEachDim = (space: T.Space, d: number): boolean => {
|
||||
if (d === dim) {
|
||||
const o = space.dataOffset(...u);
|
||||
space.getCoords(o, v);
|
||||
|
||||
for (let e = 0; e < dims.length; e++) {
|
||||
expect(u[e]).toEqual(v[e]);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < dims[d]; i++) {
|
||||
u[d] = i;
|
||||
if (!forEachDim(space, d + 1)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
for (const ao of permutations(axes)) {
|
||||
const space = T.Space(dims, ao);
|
||||
if (!forEachDim(space, 0)) break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mat4, Vec3, Vec4, Mat3 } from './3d';
|
||||
@@ -21,6 +22,8 @@ export namespace Tensor {
|
||||
get(data: Tensor.Data, ...coords: number[]): number
|
||||
set(data: Tensor.Data, ...coordsAndValue: number[]): number
|
||||
add(data: Tensor.Data, ...coordsAndValue: number[]): number
|
||||
dataOffset(...coords: number[]): number,
|
||||
getCoords(dataOffset: number, coords: { [i: number]: number }): number[]
|
||||
}
|
||||
|
||||
interface Layout {
|
||||
@@ -46,8 +49,8 @@ export namespace Tensor {
|
||||
|
||||
export function Space(dimensions: number[], axisOrderSlowToFast: number[], ctor?: ArrayCtor): Space {
|
||||
const layout = Layout(dimensions, axisOrderSlowToFast, ctor);
|
||||
const { get, set, add } = accessors(layout);
|
||||
return { rank: dimensions.length, dimensions, axisOrderSlowToFast, create: creator(layout), get, set, add };
|
||||
const { get, set, add, dataOffset, getCoords } = accessors(layout);
|
||||
return { rank: dimensions.length, dimensions, axisOrderSlowToFast, create: creator(layout), get, set, add, dataOffset, getCoords };
|
||||
}
|
||||
|
||||
export function Data1(values: ArrayLike<number>): Data { return values as Data; }
|
||||
@@ -95,13 +98,15 @@ export namespace Tensor {
|
||||
return true;
|
||||
}
|
||||
|
||||
function accessors(layout: Layout): { get: Space['get'], set: Space['set'], add: Space['add'] } {
|
||||
function accessors(layout: Layout): { get: Space['get'], set: Space['set'], add: Space['add'], dataOffset: Space['dataOffset'], getCoords: Space['getCoords'] } {
|
||||
const { dimensions, axisOrderFastToSlow: ao } = layout;
|
||||
switch (dimensions.length) {
|
||||
case 1: return {
|
||||
get: (t, d) => t[d],
|
||||
set: (t, d, x) => t[d] = x,
|
||||
add: (t, d, x) => t[d] += x
|
||||
add: (t, d, x) => t[d] += x,
|
||||
dataOffset: (d) => d,
|
||||
getCoords: (o, c) => { c[0] = o; return c as number[]; }
|
||||
};
|
||||
case 2: {
|
||||
// column major
|
||||
@@ -110,7 +115,9 @@ export namespace Tensor {
|
||||
return {
|
||||
get: (t, i, j) => t[j * rows + i],
|
||||
set: (t, i, j, x) => t[j * rows + i] = x,
|
||||
add: (t, i, j, x) => t[j * rows + i] += x
|
||||
add: (t, i, j, x) => t[j * rows + i] += x,
|
||||
dataOffset: (i, j) => j * rows + i,
|
||||
getCoords: (o, c) => { c[0] = o % rows; c[1] = Math.floor(o / rows) ; return c as number[]; }
|
||||
};
|
||||
}
|
||||
if (ao[0] === 1 && ao[1] === 0) {
|
||||
@@ -118,7 +125,9 @@ export namespace Tensor {
|
||||
return {
|
||||
get: (t, i, j) => t[i * cols + j],
|
||||
set: (t, i, j, x) => t[i * cols + j] = x,
|
||||
add: (t, i, j, x) => t[i * cols + j] += x
|
||||
add: (t, i, j, x) => t[i * cols + j] += x,
|
||||
dataOffset: (i, j) => i * cols + j,
|
||||
getCoords: (o, c) => { c[0] = Math.floor(o / cols); c[1] = o % cols; return c as number[]; }
|
||||
};
|
||||
}
|
||||
throw new Error('bad axis order');
|
||||
@@ -129,7 +138,15 @@ export namespace Tensor {
|
||||
return {
|
||||
get: (t, i, j, k) => t[i + j * u + k * uv],
|
||||
set: (t, i, j, k, x ) => t[i + j * u + k * uv] = x,
|
||||
add: (t, i, j, k, x ) => t[i + j * u + k * uv] += x
|
||||
add: (t, i, j, k, x ) => t[i + j * u + k * uv] += x,
|
||||
dataOffset: (i, j, k) => i + j * u + k * uv,
|
||||
getCoords: (o, c) => {
|
||||
const p = Math.floor(o / u);
|
||||
c[0] = o % u;
|
||||
c[1] = p % v;
|
||||
c[2] = Math.floor(p / v);
|
||||
return c as number[];
|
||||
}
|
||||
};
|
||||
}
|
||||
if (ao[0] === 0 && ao[1] === 2 && ao[2] === 1) { // 021 ikj
|
||||
@@ -137,7 +154,15 @@ export namespace Tensor {
|
||||
return {
|
||||
get: (t, i, j, k) => t[i + k * u + j * uv],
|
||||
set: (t, i, j, k, x ) => t[i + k * u + j * uv] = x,
|
||||
add: (t, i, j, k, x ) => t[i + k * u + j * uv] += x
|
||||
add: (t, i, j, k, x ) => t[i + k * u + j * uv] += x,
|
||||
dataOffset: (i, j, k) => i + k * u + j * uv,
|
||||
getCoords: (o, c) => {
|
||||
const p = Math.floor(o / u);
|
||||
c[0] = o % u;
|
||||
c[1] = Math.floor(p / v);
|
||||
c[2] = p % v;
|
||||
return c as number[];
|
||||
}
|
||||
};
|
||||
}
|
||||
if (ao[0] === 1 && ao[1] === 0 && ao[2] === 2) { // 102 jik
|
||||
@@ -145,7 +170,15 @@ export namespace Tensor {
|
||||
return {
|
||||
get: (t, i, j, k) => t[j + i * u + k * uv],
|
||||
set: (t, i, j, k, x ) => t[j + i * u + k * uv] = x,
|
||||
add: (t, i, j, k, x ) => t[j + i * u + k * uv] += x
|
||||
add: (t, i, j, k, x ) => t[j + i * u + k * uv] += x,
|
||||
dataOffset: (i, j, k) => j + i * u + k * uv,
|
||||
getCoords: (o, c) => {
|
||||
const p = Math.floor(o / u);
|
||||
c[0] = p % v;
|
||||
c[1] = o % u;
|
||||
c[2] = Math.floor(p / v);
|
||||
return c as number[];
|
||||
}
|
||||
};
|
||||
}
|
||||
if (ao[0] === 1 && ao[1] === 2 && ao[2] === 0) { // 120 jki
|
||||
@@ -153,7 +186,15 @@ export namespace Tensor {
|
||||
return {
|
||||
get: (t, i, j, k) => t[j + k * u + i * uv],
|
||||
set: (t, i, j, k, x ) => t[j + k * u + i * uv] = x,
|
||||
add: (t, i, j, k, x ) => t[j + k * u + i * uv] += x
|
||||
add: (t, i, j, k, x ) => t[j + k * u + i * uv] += x,
|
||||
dataOffset: (i, j, k) => j + k * u + i * uv,
|
||||
getCoords: (o, c) => {
|
||||
const p = Math.floor(o / u);
|
||||
c[0] = Math.floor(p / v);
|
||||
c[1] = o % u;
|
||||
c[2] = p % v;
|
||||
return c as number[];
|
||||
}
|
||||
};
|
||||
}
|
||||
if (ao[0] === 2 && ao[1] === 0 && ao[2] === 1) { // 201 kij
|
||||
@@ -161,7 +202,15 @@ export namespace Tensor {
|
||||
return {
|
||||
get: (t, i, j, k) => t[k + i * u + j * uv],
|
||||
set: (t, i, j, k, x ) => t[k + i * u + j * uv] = x,
|
||||
add: (t, i, j, k, x ) => t[k + i * u + j * uv] += x
|
||||
add: (t, i, j, k, x ) => t[k + i * u + j * uv] += x,
|
||||
dataOffset: (i, j, k) => k + i * u + j * uv,
|
||||
getCoords: (o, c) => {
|
||||
const p = Math.floor(o / u);
|
||||
c[0] = p % v;
|
||||
c[1] = Math.floor(p / v);
|
||||
c[2] = o % u;
|
||||
return c as number[];
|
||||
}
|
||||
};
|
||||
}
|
||||
if (ao[0] === 2 && ao[1] === 1 && ao[2] === 0) { // 210 kji
|
||||
@@ -169,7 +218,15 @@ export namespace Tensor {
|
||||
return {
|
||||
get: (t, i, j, k) => t[k + j * u + i * uv],
|
||||
set: (t, i, j, k, x ) => t[k + j * u + i * uv] = x,
|
||||
add: (t, i, j, k, x ) => t[k + j * u + i * uv] += x
|
||||
add: (t, i, j, k, x ) => t[k + j * u + i * uv] += x,
|
||||
dataOffset: (i, j, k) => k + j * u + i * uv,
|
||||
getCoords: (o, c) => {
|
||||
const p = Math.floor(o / u);
|
||||
c[0] = Math.floor(p / v);
|
||||
c[1] = p % v;
|
||||
c[2] = o % u;
|
||||
return c as number[];
|
||||
}
|
||||
};
|
||||
}
|
||||
throw new Error('bad axis order');
|
||||
@@ -177,7 +234,9 @@ export namespace Tensor {
|
||||
default: return {
|
||||
get: (t, ...c) => t[dataOffset(layout, c)],
|
||||
set: (t, ...c) => t[dataOffset(layout, c)] = c[c.length - 1],
|
||||
add: (t, ...c) => t[dataOffset(layout, c)] += c[c.length - 1]
|
||||
add: (t, ...c) => t[dataOffset(layout, c)] += c[c.length - 1],
|
||||
dataOffset: (...c) => dataOffset(layout, c),
|
||||
getCoords: (o, c) => getCoords(layout, o, c as number[]),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -199,6 +258,21 @@ export namespace Tensor {
|
||||
return o;
|
||||
}
|
||||
|
||||
function getCoords(layout: Layout, o: number, coords: number[]) {
|
||||
const { dimensions: dim, axisOrderFastToSlow: ao } = layout;
|
||||
const d = dim.length;
|
||||
|
||||
let c = o;
|
||||
for (let i = 0; i < d; i++) {
|
||||
const d = dim[ao[i]];
|
||||
coords[ao[i]] = c % d;
|
||||
c = Math.floor(c / d);
|
||||
}
|
||||
coords[ao[d + 1]] = c;
|
||||
|
||||
return coords;
|
||||
}
|
||||
|
||||
// Convers "slow to fast" axis order to "fast to slow" and vice versa.
|
||||
export function invertAxisOrder(v: number[]) {
|
||||
const ret: number[] = [];
|
||||
|
||||
@@ -17,7 +17,6 @@ import { FiniteArray } from '../mol-util/type-helpers';
|
||||
import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
|
||||
import { stringToWords } from '../mol-util/string';
|
||||
import { Volume } from './volume/volume';
|
||||
import { VolumeData } from './volume';
|
||||
|
||||
/** A Loci that includes every loci */
|
||||
export const EveryLoci = { kind: 'every-loci' as 'every-loci' };
|
||||
@@ -162,12 +161,11 @@ namespace Loci {
|
||||
} else if (loci.kind === 'data-loci') {
|
||||
return loci.getBoundingSphere(boundingSphere);
|
||||
} else if (loci.kind === 'volume-loci') {
|
||||
return VolumeData.getBoundingSphere(loci.volume, boundingSphere);
|
||||
return Volume.getBoundingSphere(loci.volume, boundingSphere);
|
||||
} else if (loci.kind === 'isosurface-loci') {
|
||||
return VolumeData.getBoundingSphere(loci.volume, boundingSphere);
|
||||
return Volume.Isosurface.getBoundingSphere(loci.volume, loci.isoValue, boundingSphere);
|
||||
} else if (loci.kind === 'cell-loci') {
|
||||
// TODO
|
||||
return VolumeData.getBoundingSphere(loci.volume, boundingSphere);
|
||||
return Volume.Cell.getBoundingSphere(loci.volume, loci.indices, boundingSphere);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -146,7 +146,6 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures:
|
||||
if (params?.copyAllCategories && MmcifFormat.is(models[0].sourceData)) {
|
||||
encode_mmCIF_categories_copyAll(encoder, ctx);
|
||||
} else {
|
||||
console.log('default');
|
||||
encode_mmCIF_categories_default(encoder, ctx, params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,15 @@ export const enum Elements {
|
||||
H = 'H', D = 'D', T = 'T', HE = 'HE', LI = 'LI', BE = 'BE', B = 'B', C = 'C', N = 'N', O = 'O', F = 'F', NE = 'NE', NA = 'NA', MG = 'MG', AL = 'AL', SI = 'SI', P = 'P', S = 'S', CL = 'CL', AR = 'AR', K = 'K', CA = 'CA', SC = 'SC', TI = 'TI', V = 'V', CR = 'CR', MN = 'MN', FE = 'FE', CO = 'CO', NI = 'NI', CU = 'CU', ZN = 'ZN', GA = 'GA', GE = 'GE', AS = 'AS', SE = 'SE', BR = 'BR', KR = 'KR', RB = 'RB', SR = 'SR', Y = 'Y', ZR = 'ZR', NB = 'NB', MO = 'MO', TC = 'TC', RU = 'RU', RH = 'RH', PD = 'PD', AG = 'AG', CD = 'CD', IN = 'IN', SN = 'SN', SB = 'SB', TE = 'TE', I = 'I', XE = 'XE', CS = 'CS', BA = 'BA', LA = 'LA', CE = 'CE', PR = 'PR', ND = 'ND', PM = 'PM', SM = 'SM', EU = 'EU', GD = 'GD', TB = 'TB', DY = 'DY', HO = 'HO', ER = 'ER', TM = 'TM', YB = 'YB', LU = 'LU', HF = 'HF', TA = 'TA', W = 'W', RE = 'RE', OS = 'OS', IR = 'IR', PT = 'PT', AU = 'AU', HG = 'HG', TL = 'TL', PB = 'PB', BI = 'BI', PO = 'PO', AT = 'AT', RN = 'RN', FR = 'FR', RA = 'RA', AC = 'AC', TH = 'TH', PA = 'PA', U = 'U', NP = 'NP', PU = 'PU', AM = 'AM', CM = 'CM', BK = 'BK', CF = 'CF', ES = 'ES', FM = 'FM', MD = 'MD', NO = 'NO', LR = 'LR', RF = 'RF', DB = 'DB', SG = 'SG', BH = 'BH', HS = 'HS', MT = 'MT', DS = 'DS', RG = 'RG', CN = 'CN', NH = 'NH', FL = 'FL', MC = 'MC', LV = 'LV', TS = 'TS', OG = 'OG'
|
||||
}
|
||||
|
||||
export const ElementNames: { [k: string]: string } = {
|
||||
'H': 'Hydrogen',
|
||||
'C': 'Carbon',
|
||||
'N': 'Nitrogen',
|
||||
'O': 'Oxygen',
|
||||
'S': 'Sulfur',
|
||||
'FE': 'Iron',
|
||||
};
|
||||
|
||||
export const AlkaliMetals = new Set<ElementSymbol>(['LI', 'NA', 'K', 'RB', 'CS', 'FR'] as ElementSymbol[]);
|
||||
export function isAlkaliMetal(element: ElementSymbol) { return AlkaliMetals.has(element); }
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import { CustomProperties } from '../common/custom-property';
|
||||
import { AtomicHierarchy } from '../model/properties/atomic';
|
||||
import { StructureSelection } from '../query/selection';
|
||||
import { getBoundary } from '../../../mol-math/geometry/boundary';
|
||||
import { ElementSymbol } from '../model/types';
|
||||
|
||||
class Structure {
|
||||
/** Maps unit.id to unit */
|
||||
@@ -50,6 +51,7 @@ class Structure {
|
||||
masterModel?: Model,
|
||||
representativeModel?: Model,
|
||||
uniqueResidueNames?: Set<string>,
|
||||
uniqueElementSymbols?: Set<ElementSymbol>,
|
||||
entityIndices?: ReadonlyArray<EntityIndex>,
|
||||
uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
|
||||
serialMapping?: SerialMapping,
|
||||
@@ -265,6 +267,11 @@ class Structure {
|
||||
|| (this._props.uniqueResidueNames = getUniqueResidueNames(this));
|
||||
}
|
||||
|
||||
get uniqueElementSymbols() {
|
||||
return this._props.uniqueElementSymbols
|
||||
|| (this._props.uniqueElementSymbols = getUniqueElementSymbols(this));
|
||||
}
|
||||
|
||||
get entityIndices() {
|
||||
return this._props.entityIndices
|
||||
|| (this._props.entityIndices = getEntityIndices(this));
|
||||
@@ -403,7 +410,8 @@ function getUniqueResidueNames(s: Structure) {
|
||||
const prop = StructureProperties.residue.label_comp_id;
|
||||
const names = new Set<string>();
|
||||
const loc = StructureElement.Location.create(s);
|
||||
for (const unit of s.units) {
|
||||
for (const unitGroup of s.unitSymmetryGroups) {
|
||||
const unit = unitGroup.units[0];
|
||||
// TODO: support coarse unit?
|
||||
if (!Unit.isAtomic(unit)) continue;
|
||||
const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
|
||||
@@ -417,6 +425,21 @@ function getUniqueResidueNames(s: Structure) {
|
||||
return names;
|
||||
}
|
||||
|
||||
function getUniqueElementSymbols(s: Structure) {
|
||||
const prop = StructureProperties.atom.type_symbol;
|
||||
const symbols = new Set<ElementSymbol>();
|
||||
const loc = StructureElement.Location.create(s);
|
||||
for (const unit of s.units) {
|
||||
if (!Unit.isAtomic(unit)) continue;
|
||||
loc.unit = unit;
|
||||
for (let i = 0, il = unit.elements.length; i < il; ++i) {
|
||||
loc.element = unit.elements[i];
|
||||
symbols.add(prop(loc));
|
||||
}
|
||||
}
|
||||
return symbols;
|
||||
}
|
||||
|
||||
function getEntityIndices(structure: Structure): ReadonlyArray<EntityIndex> {
|
||||
const { units } = structure;
|
||||
const l = StructureElement.Location.create(structure);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { SpacegroupCell, Box3D, Sphere3D } from '../../mol-math/geometry';
|
||||
import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
|
||||
import { Tensor, Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
|
||||
|
||||
@@ -28,7 +28,7 @@ interface VolumeData extends VolumeDataBase {
|
||||
|
||||
namespace VolumeData {
|
||||
export const One: VolumeData = {
|
||||
transform: { kind: 'matrix', matrix: Mat4() },
|
||||
transform: { kind: 'matrix', matrix: Mat4.identity() },
|
||||
data: Tensor.create(Tensor.Space([1, 1, 1], [0, 1, 2]), Tensor.Data1([0])),
|
||||
dataStats: { min: 0, max: 0, mean: 0, sigma: 0 }
|
||||
};
|
||||
@@ -52,12 +52,6 @@ namespace VolumeData {
|
||||
export function areEquivalent(volA: VolumeData, volB: VolumeData) {
|
||||
return volA === volB;
|
||||
}
|
||||
|
||||
export function getBoundingSphere(volume: VolumeData, boundingSphere?: Sphere3D) {
|
||||
if (!boundingSphere) boundingSphere = Sphere3D();
|
||||
// TODO
|
||||
return boundingSphere;
|
||||
}
|
||||
}
|
||||
|
||||
type VolumeIsoValue = VolumeIsoValue.Absolute | VolumeIsoValue.Relative
|
||||
@@ -91,8 +85,8 @@ namespace VolumeIsoValue {
|
||||
|
||||
export function toString(value: VolumeIsoValue) {
|
||||
return value.kind === 'relative'
|
||||
? `${value.relativeValue} σ`
|
||||
: `${value.absoluteValue}`;
|
||||
? `${value.relativeValue.toFixed(2)} σ`
|
||||
: `${value.absoluteValue.toPrecision(4)}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
import { VolumeData, VolumeIsoValue } from './data';
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
|
||||
|
||||
export namespace Volume {
|
||||
export type CellIndex = { readonly '@type': 'cell-index' } & number
|
||||
@@ -16,12 +19,43 @@ export namespace Volume {
|
||||
export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume; }
|
||||
export function isLociEmpty(loci: Loci) { return loci.volume.data.data.length === 0; }
|
||||
|
||||
export function getBoundingSphere(volume: VolumeData, boundingSphere?: Sphere3D) {
|
||||
if (!boundingSphere) boundingSphere = Sphere3D();
|
||||
|
||||
const transform = VolumeData.getGridToCartesianTransform(volume);
|
||||
const [x, y, z] = volume.data.space.dimensions;
|
||||
|
||||
const cpA = Vec3.create(0, 0, 0); Vec3.transformMat4(cpA, cpA, transform);
|
||||
const cpB = Vec3.create(x, y, z); Vec3.transformMat4(cpB, cpB, transform);
|
||||
const cpC = Vec3.create(x, 0, 0); Vec3.transformMat4(cpC, cpC, transform);
|
||||
const cpD = Vec3.create(0, y, z); Vec3.transformMat4(cpD, cpC, transform);
|
||||
|
||||
const cpE = Vec3.create(0, 0, z); Vec3.transformMat4(cpE, cpE, transform);
|
||||
const cpF = Vec3.create(x, 0, z); Vec3.transformMat4(cpF, cpF, transform);
|
||||
const cpG = Vec3.create(x, y, 0); Vec3.transformMat4(cpG, cpG, transform);
|
||||
const cpH = Vec3.create(0, y, 0); Vec3.transformMat4(cpH, cpH, transform);
|
||||
|
||||
const center = Vec3();
|
||||
Vec3.add(center, cpA, cpB);
|
||||
Vec3.scale(center, center, 0.5);
|
||||
const d = Math.max(Vec3.distance(cpA, cpB), Vec3.distance(cpC, cpD));
|
||||
Sphere3D.set(boundingSphere, center, d / 2);
|
||||
Sphere3D.setExtrema(boundingSphere, [cpA, cpB, cpC, cpD, cpE, cpF, cpG, cpH]);
|
||||
|
||||
return boundingSphere;
|
||||
}
|
||||
|
||||
export namespace Isosurface {
|
||||
export interface Loci { readonly kind: 'isosurface-loci', readonly volume: VolumeData, readonly isoValue: VolumeIsoValue }
|
||||
export function Loci(volume: VolumeData, isoValue: VolumeIsoValue): Loci { return { kind: 'isosurface-loci', volume, isoValue }; }
|
||||
export function isLoci(x: any): x is Loci { return !!x && x.kind === 'isosurface-loci'; }
|
||||
export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume && VolumeIsoValue.areSame(a.isoValue, b.isoValue, a.volume.dataStats); }
|
||||
export function isLociEmpty(loci: Loci) { return loci.volume.data.data.length === 0; }
|
||||
|
||||
export function getBoundingSphere(volume: VolumeData, isoValue: VolumeIsoValue, boundingSphere?: Sphere3D) {
|
||||
// TODO get bounding sphere for subgrid with values >= isoValue
|
||||
return Volume.getBoundingSphere(volume, boundingSphere);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Cell {
|
||||
@@ -30,5 +64,29 @@ export namespace Volume {
|
||||
export function isLoci(x: any): x is Loci { return !!x && x.kind === 'cell-loci'; }
|
||||
export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume && OrderedSet.areEqual(a.indices, b.indices); }
|
||||
export function isLociEmpty(loci: Loci) { return OrderedSet.size(loci.indices) === 0; }
|
||||
|
||||
const boundaryHelper = new BoundaryHelper('98');
|
||||
const tmpBoundaryPos = Vec3();
|
||||
export function getBoundingSphere(volume: VolumeData, indices: OrderedSet<CellIndex>, boundingSphere?: Sphere3D) {
|
||||
boundaryHelper.reset();
|
||||
const transform = VolumeData.getGridToCartesianTransform(volume);
|
||||
const { getCoords } = volume.data.space;
|
||||
|
||||
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
|
||||
const o = OrderedSet.getAt(indices, i);
|
||||
getCoords(o, tmpBoundaryPos);
|
||||
Vec3.transformMat4(tmpBoundaryPos, tmpBoundaryPos, transform);
|
||||
boundaryHelper.includePosition(tmpBoundaryPos);
|
||||
}
|
||||
boundaryHelper.finishedIncludeStep();
|
||||
for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
|
||||
const o = OrderedSet.getAt(indices, i);
|
||||
getCoords(o, tmpBoundaryPos);
|
||||
Vec3.transformMat4(tmpBoundaryPos, tmpBoundaryPos, transform);
|
||||
boundaryHelper.radiusPosition(tmpBoundaryPos);
|
||||
}
|
||||
|
||||
return boundaryHelper.getSphere(boundingSphere);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ const DownloadStructure = StateAction.build({
|
||||
source: PD.MappedStatic('pdb', {
|
||||
'pdb': PD.Group({
|
||||
provider: PD.Group({
|
||||
id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
|
||||
id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma/space separated PDB ids.' }),
|
||||
server: PD.MappedStatic('pdbe', {
|
||||
'rcsb': PD.Group({
|
||||
encoding: PD.Select('bcif', [['cif', 'cif'], ['bcif', 'bcif']] as ['cif' | 'bcif', string][]),
|
||||
@@ -50,19 +50,19 @@ const DownloadStructure = StateAction.build({
|
||||
options
|
||||
}, { isFlat: true, label: 'PDB' }),
|
||||
'pdb-dev': PD.Group({
|
||||
id: PD.Text('PDBDEV_00000001', { label: 'PDBDev Id(s)', description: 'One or more comma separated ids.' }),
|
||||
id: PD.Text('PDBDEV_00000001', { label: 'PDBDev Id(s)', description: 'One or more comma/space separated ids.' }),
|
||||
options
|
||||
}, { isFlat: true, label: 'PDBDEV' }),
|
||||
'bcif-static': PD.Group({
|
||||
id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
|
||||
id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma/space separated PDB ids.' }),
|
||||
options
|
||||
}, { isFlat: true, label: 'BinaryCIF (static PDBe Updated)' }),
|
||||
'swissmodel': PD.Group({
|
||||
id: PD.Text('Q9Y2I8', { label: 'UniProtKB AC(s)', description: 'One or more comma separated ACs.' }),
|
||||
id: PD.Text('Q9Y2I8', { label: 'UniProtKB AC(s)', description: 'One or more comma/space separated ACs.' }),
|
||||
options
|
||||
}, { isFlat: true, label: 'SWISS-MODEL', description: 'Loads the best homology model or experimental structure' }),
|
||||
'pubchem': PD.Group({
|
||||
id: PD.Text('2244,2245', { label: 'PubChem ID', description: 'One or more comma separated IDs.' }),
|
||||
id: PD.Text('2244,2245', { label: 'PubChem ID', description: 'One or more comma/space separated IDs.' }),
|
||||
options
|
||||
}, { isFlat: true, label: 'PubChem', description: 'Loads 3D conformer from PubChem.' }),
|
||||
'url': PD.Group({
|
||||
@@ -160,7 +160,7 @@ const DownloadStructure = StateAction.build({
|
||||
}));
|
||||
|
||||
function getDownloadParams(src: string, url: (id: string) => string, label: (id: string) => string, isBinary: boolean): StateTransformer.Params<Download>[] {
|
||||
const ids = src.split(',').map(id => id.trim()).filter(id => !!id && (id.length >= 4 || /^[1-9][0-9]*$/.test(id)));
|
||||
const ids = src.split(/[,\s]/).map(id => id.trim()).filter(id => !!id && (id.length >= 4 || /^[1-9][0-9]*$/.test(id)));
|
||||
const ret: StateTransformer.Params<Download>[] = [];
|
||||
for (const id of ids) {
|
||||
ret.push({ url: Asset.Url(url(id)), isBinary, label: label(id) });
|
||||
|
||||
@@ -68,9 +68,11 @@ export const DxProvider = DataFormatProvider({
|
||||
stringExtensions: ['dx'],
|
||||
binaryExtensions: ['dxbin'],
|
||||
parse: async (plugin, data) => {
|
||||
const volume = plugin.build()
|
||||
const format = plugin.build()
|
||||
.to(data)
|
||||
.apply(StateTransforms.Volume.VolumeFromDx);
|
||||
.apply(StateTransforms.Data.ParseDx, {}, { state: { isGhost: true } });
|
||||
|
||||
const volume = format.apply(StateTransforms.Volume.VolumeFromDx);
|
||||
|
||||
await volume.commit({ revertOnError: true });
|
||||
|
||||
@@ -107,13 +109,13 @@ export const CubeProvider = DataFormatProvider({
|
||||
type: 'isosurface',
|
||||
typeParams: { isoValue: VolumeIsoValue.relative(1), alpha: 0.4 },
|
||||
color: 'uniform',
|
||||
colorParams: { value: ColorNames.red }
|
||||
colorParams: { value: ColorNames.blue }
|
||||
}));
|
||||
const volumeNeg = surfaces.to(data.volume).apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(plugin, volumeData, {
|
||||
type: 'isosurface',
|
||||
typeParams: { isoValue: VolumeIsoValue.relative(-1), alpha: 0.4 },
|
||||
color: 'uniform',
|
||||
colorParams: { value: ColorNames.blue }
|
||||
colorParams: { value: ColorNames.red }
|
||||
}));
|
||||
|
||||
const structure = await plugin.builders.structure.representation.applyPreset(data.structure, 'auto');
|
||||
|
||||
@@ -229,14 +229,14 @@ const branchedPlusConnected = StructureSelectionQuery('Carbohydrate with Connect
|
||||
MS.struct.modifier.includeConnected({
|
||||
0: branched.expression, 'layer-count': 1, 'as-whole-residues': true
|
||||
})
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
const branchedConnectedOnly = StructureSelectionQuery('Connected to Carbohydrate', MS.struct.modifier.union([
|
||||
MS.struct.modifier.exceptBy({
|
||||
0: branchedPlusConnected.expression,
|
||||
by: branched.expression
|
||||
})
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
const ligand = StructureSelectionQuery('Ligand', MS.struct.modifier.union([
|
||||
MS.struct.combinator.merge([
|
||||
@@ -264,7 +264,7 @@ const ligand = StructureSelectionQuery('Ligand', MS.struct.modifier.union([
|
||||
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
|
||||
'residue-test': MS.core.str.match([
|
||||
MS.re('non-polymer|(amino|carboxy) terminus', 'i'),
|
||||
MS.re('non-polymer|(amino|carboxy) terminus|peptide-like', 'i'),
|
||||
MS.ammp('chemCompType')
|
||||
])
|
||||
})
|
||||
@@ -290,14 +290,14 @@ const ligandPlusConnected = StructureSelectionQuery('Ligand with Connected', MS.
|
||||
]),
|
||||
by: branched.expression
|
||||
})
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
const ligandConnectedOnly = StructureSelectionQuery('Connected to Ligand', MS.struct.modifier.union([
|
||||
MS.struct.modifier.exceptBy({
|
||||
0: ligandPlusConnected.expression,
|
||||
by: ligand.expression
|
||||
})
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
// residues connected to ligands or branched entities
|
||||
const connectedOnly = StructureSelectionQuery('Connected to Ligand or Carbohydrate', MS.struct.modifier.union([
|
||||
@@ -305,7 +305,7 @@ const connectedOnly = StructureSelectionQuery('Connected to Ligand or Carbohydra
|
||||
branchedConnectedOnly.expression,
|
||||
ligandConnectedOnly.expression
|
||||
]),
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
const disulfideBridges = StructureSelectionQuery('Disulfide Bridges', MS.struct.modifier.union([
|
||||
MS.struct.modifier.wholeResidues([
|
||||
@@ -380,6 +380,16 @@ const bonded = StructureSelectionQuery('Residues Bonded to Selection', MS.struct
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const wholeResidues = StructureSelectionQuery('Whole Residues of Selection', MS.struct.modifier.union([
|
||||
MS.struct.modifier.wholeResidues({
|
||||
0: MS.internal.generator.current()
|
||||
})
|
||||
]), {
|
||||
description: 'Expand current selection to whole residues.',
|
||||
category: StructureSelectionCategory.Manipulate,
|
||||
referencesCurrent: true
|
||||
});
|
||||
|
||||
const StandardAminoAcids = [
|
||||
[['HIS'], 'HISTIDINE'],
|
||||
[['ARG'], 'ARGININE'],
|
||||
@@ -390,6 +400,7 @@ const StandardAminoAcids = [
|
||||
[['TRP'], 'TRYPTOPHAN'],
|
||||
[['ALA'], 'ALANINE'],
|
||||
[['MET'], 'METHIONINE'],
|
||||
[['PRO'], 'PROLINE'],
|
||||
[['CYS'], 'CYSTEINE'],
|
||||
[['ASN'], 'ASPARAGINE'],
|
||||
[['VAL'], 'VALINE'],
|
||||
@@ -402,6 +413,7 @@ const StandardAminoAcids = [
|
||||
[['THR'], 'THREONINE'],
|
||||
[['SEC'], 'SELENOCYSTEINE'],
|
||||
[['PYL'], 'PYRROLYSINE'],
|
||||
[['UNK'], 'UNKNOWN'],
|
||||
].sort((a, b) => a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0) as [string[], string][];
|
||||
|
||||
const StandardNucleicBases = [
|
||||
@@ -411,9 +423,10 @@ const StandardNucleicBases = [
|
||||
[['G', 'DG'], 'GUANOSINE'],
|
||||
[['I', 'DI'], 'INOSINE'],
|
||||
[['U', 'DU'], 'URIDINE'],
|
||||
[['N', 'DN'], 'UNKNOWN'],
|
||||
].sort((a, b) => a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0) as [string[], string][];
|
||||
|
||||
function ResidueQuery([names, label]: [string[], string], category: string) {
|
||||
export function ResidueQuery([names, label]: [string[], string], category: string) {
|
||||
return StructureSelectionQuery(`${stringToWords(label)} (${names.join(', ')})`, MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'residue-test': MS.core.set.has([MS.set(...names), MS.ammp('auth_comp_id')])
|
||||
@@ -421,6 +434,14 @@ function ResidueQuery([names, label]: [string[], string], category: string) {
|
||||
]), { category });
|
||||
}
|
||||
|
||||
export function ElementSymbolQuery([names, label]: [string[], string], category: string) {
|
||||
return StructureSelectionQuery(`${stringToWords(label)} (${names.join(', ')})`, MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'atom-test': MS.core.set.has([MS.set(...names), MS.acp('elementSymbol')])
|
||||
})
|
||||
]), { category });
|
||||
}
|
||||
|
||||
export const StructureSelectionQueries = {
|
||||
all,
|
||||
current,
|
||||
@@ -447,6 +468,7 @@ export const StructureSelectionQueries = {
|
||||
surroundings,
|
||||
complement,
|
||||
bonded,
|
||||
wholeResidues,
|
||||
};
|
||||
|
||||
export function applyBuiltInSelection(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, query: keyof typeof StructureSelectionQueries, customTag?: string) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -22,6 +22,7 @@ import { StructureRepresentation, StructureRepresentationState } from '../mol-re
|
||||
import { VolumeRepresentation } from '../mol-repr/volume/representation';
|
||||
import { StateObject, StateTransformer } from '../mol-state';
|
||||
import { CubeFile } from '../mol-io/reader/cube/parser';
|
||||
import { DxFile } from '../mol-io/reader/dx/parser';
|
||||
|
||||
export type TypeClass = 'root' | 'data' | 'prop'
|
||||
|
||||
@@ -73,6 +74,7 @@ export namespace PluginStateObject {
|
||||
export class Ply extends Create<PlyFile>({ name: 'PLY File', typeClass: 'Data' }) { }
|
||||
export class Ccp4 extends Create<Ccp4File>({ name: 'CCP4/MRC/MAP File', typeClass: 'Data' }) { }
|
||||
export class Dsn6 extends Create<Dsn6File>({ name: 'DSN6/BRIX File', typeClass: 'Data' }) { }
|
||||
export class Dx extends Create<DxFile>({ name: 'DX File', typeClass: 'Data' }) { }
|
||||
|
||||
export type BlobEntry = { id: string } & (
|
||||
{ kind: 'json', data: unknown } |
|
||||
@@ -85,6 +87,7 @@ export namespace PluginStateObject {
|
||||
{ kind: 'dcd', data: DcdFile } |
|
||||
{ kind: 'ccp4', data: Ccp4File } |
|
||||
{ kind: 'dsn6', data: Dsn6File } |
|
||||
{ kind: 'dx', data: DxFile } |
|
||||
{ kind: 'ply', data: PlyFile } |
|
||||
// For non-build in extensions
|
||||
{ kind: 'custom', data: unknown, tag: string }
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../objects';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { parseCube } from '../../mol-io/reader/cube/parser';
|
||||
import { parseDx } from '../../mol-io/reader/dx/parser';
|
||||
|
||||
export { Download };
|
||||
export { DownloadBlob };
|
||||
@@ -31,6 +32,7 @@ export { ParsePsf };
|
||||
export { ParsePly };
|
||||
export { ParseCcp4 };
|
||||
export { ParseDsn6 };
|
||||
export { ParseDx };
|
||||
export { ImportString };
|
||||
export { ImportJson };
|
||||
export { ParseJson };
|
||||
@@ -266,7 +268,7 @@ const ParseCube = PluginStateTransform.BuiltIn({
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse Cube', async ctx => {
|
||||
const parsed = await parseCube(a.data).runInContext(ctx);
|
||||
const parsed = await parseCube(a.data, a.label).runInContext(ctx);
|
||||
if (parsed.isError) throw new Error(parsed.message);
|
||||
return new SO.Format.Cube(parsed.result);
|
||||
});
|
||||
@@ -314,7 +316,7 @@ const ParseCcp4 = PluginStateTransform.BuiltIn({
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse CCP4/MRC/MAP', async ctx => {
|
||||
const parsed = await CCP4.parse(a.data).runInContext(ctx);
|
||||
const parsed = await CCP4.parse(a.data, a.label).runInContext(ctx);
|
||||
if (parsed.isError) throw new Error(parsed.message);
|
||||
return new SO.Format.Ccp4(parsed.result);
|
||||
});
|
||||
@@ -330,13 +332,29 @@ const ParseDsn6 = PluginStateTransform.BuiltIn({
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse DSN6/BRIX', async ctx => {
|
||||
const parsed = await DSN6.parse(a.data).runInContext(ctx);
|
||||
const parsed = await DSN6.parse(a.data, a.label).runInContext(ctx);
|
||||
if (parsed.isError) throw new Error(parsed.message);
|
||||
return new SO.Format.Dsn6(parsed.result);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
type ParseDx = typeof ParseDx
|
||||
const ParseDx = PluginStateTransform.BuiltIn({
|
||||
name: 'parse-dx',
|
||||
display: { name: 'Parse DX', description: 'Parse DX from Binary/String data' },
|
||||
from: [SO.Data.Binary, SO.Data.String],
|
||||
to: SO.Format.Dx
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse DX', async ctx => {
|
||||
const parsed = await parseDx(a.data, a.label).runInContext(ctx);
|
||||
if (parsed.isError) throw new Error(parsed.message);
|
||||
return new SO.Format.Dx(parsed.result);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
type ImportString = typeof ImportString
|
||||
const ImportString = PluginStateTransform.BuiltIn({
|
||||
name: 'import-string',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -14,7 +14,6 @@ import { Task } from '../../mol-task';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { PluginStateObject as SO, PluginStateTransform } from '../objects';
|
||||
import { volumeFromCube } from '../../mol-model-formats/volume/cube';
|
||||
import { parseDx } from '../../mol-io/reader/dx/parser';
|
||||
import { volumeFromDx } from '../../mol-model-formats/volume/dx';
|
||||
import { VolumeData } from '../../mol-model/volume';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
@@ -42,7 +41,7 @@ const VolumeFromCcp4 = PluginStateTransform.BuiltIn({
|
||||
})({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Create volume from CCP4/MRC/MAP', async ctx => {
|
||||
const volume = await volumeFromCcp4(a.data, { ...params, label: a.label }).runInContext(ctx);
|
||||
const volume = await volumeFromCcp4(a.data, { ...params, label: a.data.name || a.label }).runInContext(ctx);
|
||||
const props = { label: volume.label || 'Volume', description: 'Volume' };
|
||||
return new SO.Volume.Data(volume, props);
|
||||
});
|
||||
@@ -63,7 +62,7 @@ const VolumeFromDsn6 = PluginStateTransform.BuiltIn({
|
||||
})({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Create volume from DSN6/BRIX', async ctx => {
|
||||
const volume = await volumeFromDsn6(a.data, { ...params, label: a.label }).runInContext(ctx);
|
||||
const volume = await volumeFromDsn6(a.data, { ...params, label: a.data.name || a.label }).runInContext(ctx);
|
||||
const props = { label: volume.label || 'Volume', description: 'Volume' };
|
||||
return new SO.Volume.Data(volume, props);
|
||||
});
|
||||
@@ -85,7 +84,7 @@ const VolumeFromCube = PluginStateTransform.BuiltIn({
|
||||
})({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Create volume from Cube', async ctx => {
|
||||
const volume = await volumeFromCube(a.data, { ...params, label: a.label }).runInContext(ctx);
|
||||
const volume = await volumeFromCube(a.data, { ...params, label: a.data.name || a.label }).runInContext(ctx);
|
||||
const props = { label: volume.label || 'Volume', description: 'Volume' };
|
||||
return new SO.Volume.Data(volume, props);
|
||||
});
|
||||
@@ -95,15 +94,15 @@ const VolumeFromCube = PluginStateTransform.BuiltIn({
|
||||
type VolumeFromDx = typeof VolumeFromDx
|
||||
const VolumeFromDx = PluginStateTransform.BuiltIn({
|
||||
name: 'volume-from-dx',
|
||||
display: { name: 'Parse PX', description: 'Parse DX string/binary and create volume.' },
|
||||
from: [SO.Data.String, SO.Data.Binary],
|
||||
display: { name: 'Parse DX', description: 'Create volume from DX data.' },
|
||||
from: SO.Format.Dx,
|
||||
to: SO.Volume.Data
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse DX', async ctx => {
|
||||
const parsed = await parseDx(a.data).runInContext(ctx);
|
||||
if (parsed.isError) throw new Error(parsed.message);
|
||||
const volume = await volumeFromDx(parsed.result, { label: a.label }).runInContext(ctx);
|
||||
console.log(a);
|
||||
const volume = await volumeFromDx(a.data, { label: a.data.name || a.label }).runInContext(ctx);
|
||||
console.log(volume);
|
||||
const props = { label: volume.label || 'Volume', description: 'Volume' };
|
||||
return new SO.Volume.Data(volume, props);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { PluginUIComponent } from '../base';
|
||||
import { StateTransformParameters } from '../state/common';
|
||||
import * as React from 'react';
|
||||
import { VolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/behavior';
|
||||
import { ExpandableControlRow } from '../controls/common';
|
||||
import { ExpandableControlRow, IconButton } from '../controls/common';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ParameterControls, ParamOnChange } from '../controls/parameters';
|
||||
import { Slider } from '../controls/slider';
|
||||
@@ -16,6 +16,10 @@ import { VolumeIsoValue, VolumeData } from '../../mol-model/volume';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { toPrecision } from '../../mol-util/number';
|
||||
import VisibilityOffOutlined from '@material-ui/icons/VisibilityOffOutlined';
|
||||
import VisibilityOutlined from '@material-ui/icons/VisibilityOutlined';
|
||||
import { StateSelection, StateObjectCell } from '../../mol-state';
|
||||
import { setSubtreeVisibility } from '../../mol-plugin/behavior/static/state';
|
||||
|
||||
const ChannelParams = {
|
||||
color: PD.Color(ColorNames.black, { description: 'Display color of the volume.' }),
|
||||
@@ -24,36 +28,69 @@ const ChannelParams = {
|
||||
};
|
||||
type ChannelParams = PD.Values<typeof ChannelParams>
|
||||
|
||||
function Channel(props: {
|
||||
class Channel extends PluginUIComponent<{
|
||||
label: string,
|
||||
name: VolumeStreaming.ChannelType,
|
||||
channels: { [k: string]: VolumeStreaming.ChannelParams },
|
||||
isRelative: boolean,
|
||||
params: StateTransformParameters.Props,
|
||||
stats: VolumeData['dataStats'],
|
||||
changeIso: (name: string, value: number, isRelative: boolean) => void
|
||||
changeParams: (name: string, param: string, value: any) => void
|
||||
}) {
|
||||
const { isRelative, stats } = props;
|
||||
const channel = props.channels[props.name]!;
|
||||
changeIso: (name: string, value: number, isRelative: boolean) => void,
|
||||
changeParams: (name: string, param: string, value: any) => void,
|
||||
bCell: StateObjectCell,
|
||||
isDisabled?: boolean
|
||||
}> {
|
||||
private ref = StateSelection.findTagInSubtree(this.plugin.state.data.tree, this.props.bCell!.transform.ref, this.props.name);
|
||||
|
||||
const { min, max, mean, sigma } = stats;
|
||||
const value = Math.round(100 * (channel.isoValue.kind === 'relative' ? channel.isoValue.relativeValue : channel.isoValue.absoluteValue)) / 100;
|
||||
const relMin = (min - mean) / sigma;
|
||||
const relMax = (max - mean) / sigma;
|
||||
const step = toPrecision(isRelative ? Math.round(((max - min) / sigma)) / 100 : sigma / 100, 2);
|
||||
componentDidUpdate() {
|
||||
this.ref = StateSelection.findTagInSubtree(this.plugin.state.data.tree, this.props.bCell!.transform.ref, this.props.name);
|
||||
}
|
||||
|
||||
return <ExpandableControlRow
|
||||
label={props.label + (props.isRelative ? ' \u03C3' : '')}
|
||||
colorStripe={channel.color}
|
||||
pivot={<Slider value={value} min={isRelative ? relMin : min} max={isRelative ? relMax : max} step={step}
|
||||
onChange={v => props.changeIso(props.name, v, isRelative)} disabled={props.params.isDisabled} onEnter={props.params.events.onEnter} />}
|
||||
controls={<ParameterControls onChange={({ name, value }) => props.changeParams(props.name, name, value)} params={ChannelParams} values={channel} onEnter={props.params.events.onEnter} />}
|
||||
/>;
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.state.data.events.cell.stateUpdated, e => {
|
||||
if (this.ref === e.ref) this.forceUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
getVisible = () => {
|
||||
const state = this.plugin.state.data;
|
||||
const ref = this.ref;
|
||||
if (!ref) return false;
|
||||
return !state.cells.get(ref)!.state.isHidden;
|
||||
};
|
||||
|
||||
toggleVisible = () => {
|
||||
const state = this.plugin.state.data;
|
||||
const ref = this.ref;
|
||||
if (!ref) return;
|
||||
setSubtreeVisibility(state, ref, !state.cells.get(ref)!.state.isHidden);
|
||||
};
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
const { isRelative, stats } = props;
|
||||
const channel = props.channels[props.name]!;
|
||||
|
||||
const { min, max, mean, sigma } = stats;
|
||||
const value = Math.round(100 * (channel.isoValue.kind === 'relative' ? channel.isoValue.relativeValue : channel.isoValue.absoluteValue)) / 100;
|
||||
const relMin = (min - mean) / sigma;
|
||||
const relMax = (max - mean) / sigma;
|
||||
const step = toPrecision(isRelative ? Math.round(((max - min) / sigma)) / 100 : sigma / 100, 2);
|
||||
|
||||
return <ExpandableControlRow
|
||||
label={props.label + (props.isRelative ? ' \u03C3' : '')}
|
||||
colorStripe={channel.color}
|
||||
pivot={<div className='msp-volume-channel-inline-controls'>
|
||||
<Slider value={value} min={isRelative ? relMin : min} max={isRelative ? relMax : max} step={step}
|
||||
onChange={v => props.changeIso(props.name, v, isRelative)} disabled={props.params.isDisabled} onEnter={props.params.events.onEnter} />
|
||||
<IconButton svg={this.getVisible() ? VisibilityOutlined : VisibilityOffOutlined} onClick={this.toggleVisible} toggleState={false} disabled={props.params.isDisabled} />
|
||||
</div>}
|
||||
controls={<ParameterControls onChange={({ name, value }) => props.changeParams(props.name, name, value)} params={ChannelParams} values={channel} onEnter={props.params.events.onEnter} isDisabled={props.params.isDisabled} />}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransformParameters.Props> {
|
||||
|
||||
private areInitial(params: any) {
|
||||
return PD.areEqual(this.props.info.params, params, this.props.info.initialValues);
|
||||
}
|
||||
@@ -170,8 +207,8 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
|
||||
render() {
|
||||
if (!this.props.b) return null;
|
||||
|
||||
const b = (this.props.b as VolumeStreaming).data;
|
||||
|
||||
const isEM = b.info.kind === 'em';
|
||||
const pivot = isEM ? 'em' : '2fo-fc';
|
||||
|
||||
@@ -225,16 +262,16 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
|
||||
};
|
||||
|
||||
if (isOff) {
|
||||
return <ParameterControls onChange={this.changeOption} params={OptionsParams} values={options} onEnter={this.props.events.onEnter} />;
|
||||
return <ParameterControls onChange={this.changeOption} params={OptionsParams} values={options} onEnter={this.props.events.onEnter} isDisabled={this.props.isDisabled} />;
|
||||
}
|
||||
|
||||
return <>
|
||||
{!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]} />}
|
||||
{!isEM && <Channel label='2Fo-Fc' name='2fo-fc' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} />}
|
||||
{!isEM && <Channel label='Fo-Fc(+ve)' name='fo-fc(+ve)' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} />}
|
||||
{!isEM && <Channel label='Fo-Fc(-ve)' name='fo-fc(-ve)' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[1]} />}
|
||||
{isEM && <Channel label='EM' name='em' bCell={this.props.bCell!} channels={params.entry.params.channels} changeIso={this.changeIso} changeParams={this.changeParams} isRelative={isRelative} params={this.props} stats={sampling.valuesInfo[0]} />}
|
||||
|
||||
<ParameterControls onChange={this.changeOption} params={OptionsParams} values={options} onEnter={this.props.events.onEnter} />
|
||||
<ParameterControls onChange={this.changeOption} params={OptionsParams} values={options} onEnter={this.props.events.onEnter} isDisabled={this.props.isDisabled} />
|
||||
</>;
|
||||
}
|
||||
}
|
||||
@@ -579,4 +579,35 @@
|
||||
@include accent('green', $color-accent-green);
|
||||
@include accent('purple', $color-accent-purple);
|
||||
@include accent('blue', $color-accent-blue);
|
||||
@include accent('orange', $color-accent-orange);
|
||||
@include accent('orange', $color-accent-orange);
|
||||
|
||||
.msp-volume-channel-inline-controls {
|
||||
> :first-child {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: $row-height;
|
||||
right: 32px;
|
||||
}
|
||||
|
||||
.msp-slider {
|
||||
> div:first-child() {
|
||||
right: 42px;
|
||||
}
|
||||
> div:last-child() {
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
> button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 32px;
|
||||
top: 0;
|
||||
padding: 0;
|
||||
|
||||
.msp-material-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { State, StateTransform, StateTransformer, StateAction, StateObject } from '../../mol-state';
|
||||
import { State, StateTransform, StateTransformer, StateAction, StateObject, StateObjectCell } from '../../mol-state';
|
||||
import * as React from 'react';
|
||||
import { PurePluginUIComponent } from '../base';
|
||||
import { ParameterControls, ParamOnChange } from '../controls/parameters';
|
||||
@@ -56,7 +56,8 @@ namespace StateTransformParameters {
|
||||
params: any,
|
||||
isDisabled?: boolean,
|
||||
a?: StateObject,
|
||||
b?: StateObject
|
||||
b?: StateObject,
|
||||
bCell?: StateObjectCell
|
||||
}
|
||||
|
||||
export type Class = React.ComponentClass<Props>
|
||||
@@ -125,7 +126,7 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
|
||||
abstract canAutoApply(newParams: any): boolean;
|
||||
abstract applyText(): string;
|
||||
abstract isUpdate(): boolean;
|
||||
abstract getSourceAndTarget(): { a?: StateObject, b?: StateObject };
|
||||
abstract getSourceAndTarget(): { a?: StateObject, b?: StateObject, bCell?: StateObjectCell };
|
||||
abstract state: S;
|
||||
|
||||
private busy: Subject<boolean> = new Subject();
|
||||
@@ -224,10 +225,10 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
|
||||
|
||||
let params = null;
|
||||
if (!isEmpty && !this.state.isCollapsed) {
|
||||
const { a, b } = this.getSourceAndTarget();
|
||||
const { a, b, bCell } = this.getSourceAndTarget();
|
||||
const applyControl = this.renderApply();
|
||||
params = <>
|
||||
<ParamEditor info={info} a={a} b={b} events={this.events} params={this.state.params} isDisabled={this.state.busy} />
|
||||
<ParamEditor info={info} a={a} b={b} bCell={bCell} events={this.events} params={this.state.params} isDisabled={this.state.busy} />
|
||||
{applyControl}
|
||||
</>;
|
||||
}
|
||||
@@ -266,11 +267,11 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
|
||||
const ParamEditor: StateTransformParameters.Class = this.plugin.customParamEditors.has(tId)
|
||||
? this.plugin.customParamEditors.get(tId)!
|
||||
: StateTransformParameters;
|
||||
const { a, b } = this.getSourceAndTarget();
|
||||
const { a, b, bCell } = this.getSourceAndTarget();
|
||||
|
||||
return <>
|
||||
{apply}
|
||||
<ParamEditor info={info} a={a} b={b} events={this.events} params={this.state.params} isDisabled={this.state.busy} />
|
||||
<ParamEditor info={info} a={a} b={b} bCell={bCell} events={this.events} params={this.state.params} isDisabled={this.state.busy} />
|
||||
</>;
|
||||
}
|
||||
|
||||
|
||||
@@ -175,7 +175,6 @@ class LocalStateSnapshotList extends PluginUIComponent<{}, {}> {
|
||||
return <ul style={{ listStyle: 'none', marginTop: '10px' }} className='msp-state-list'>
|
||||
{this.plugin.managers.snapshot.state.entries.map(e => <li key={e!.snapshot.id} className='msp-flex-row'>
|
||||
<Button data-id={e!.snapshot.id} onClick={this.apply} className='msp-no-overflow'>
|
||||
{(console.log(e!.snapshot.durationInMs), false)}
|
||||
<span style={{ fontWeight: e!.snapshot.id === current ? 'bold' : void 0 }}>
|
||||
{e!.name || new Date(e!.timestamp).toLocaleString()}</span> <small>
|
||||
{`${e!.snapshot.durationInMs ? formatTimespan(e!.snapshot.durationInMs, false) + `${e!.description ? ', ' : ''}` : ''}${e!.description ? e!.description : ''}`}
|
||||
|
||||
@@ -49,9 +49,11 @@ class UpdateTransformControl extends TransformControlBase<UpdateTransformControl
|
||||
applyText() { return this.canApply() ? 'Update' : 'Nothing to Update'; }
|
||||
isUpdate() { return true; }
|
||||
getSourceAndTarget() {
|
||||
const bCell = this.props.state.cells.get(this.props.transform.ref);
|
||||
return {
|
||||
a: this.props.state.cells.get(this.props.transform.parent)!.obj,
|
||||
b: this.props.state.cells.has(this.props.transform.ref)! ? this.props.state.cells.get(this.props.transform.ref)!.obj : void 0
|
||||
b: bCell?.obj,
|
||||
bCell
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +198,6 @@ class MeasurementsOptions extends PurePluginUIComponent<{}, { isDisabled: boolea
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, v => {
|
||||
console.log('isBusy', 'measurement opt', v);
|
||||
this.setState({ isDisabled: v });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import Brush from '@material-ui/icons/Brush';
|
||||
import Restore from '@material-ui/icons/Restore';
|
||||
import Remove from '@material-ui/icons/Remove';
|
||||
import * as React from 'react';
|
||||
import { StructureSelectionQueries, StructureSelectionQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { StructureSelectionQueries, StructureSelectionQuery, ResidueQuery, ElementSymbolQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { InteractivityManager } from '../../mol-plugin-state/manager/interactivity';
|
||||
import { StructureComponentManager } from '../../mol-plugin-state/manager/structure/component';
|
||||
import { StructureRef, StructureComponentRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
@@ -25,6 +25,9 @@ import { Button, ControlGroup, IconButton, ToggleButton } from '../controls/comm
|
||||
import { ParameterControls, ParamOnChange, PureSelectControl } from '../controls/parameters';
|
||||
import { Union, Subtract, Intersect, SetSvg as SetSvg, CubeSvg } from '../controls/icons';
|
||||
import { AddComponentControls } from './components';
|
||||
import { SetUtils } from '../../mol-util/set';
|
||||
import { AminoAcidNamesL, RnaBaseNames, DnaBaseNames, WaterNames, ElementSymbol } from '../../mol-model/structure/model/types';
|
||||
import { ElementNames } from '../../mol-model/structure/model/properties/atomic/types';
|
||||
|
||||
const StructureSelectionParams = {
|
||||
granularity: InteractivityManager.Params.granularity,
|
||||
@@ -45,6 +48,10 @@ const ActionHeader = new Map<StructureSelectionModifier, string>([
|
||||
['set', 'Set Selection']
|
||||
] as const);
|
||||
|
||||
const StandardResidues = SetUtils.unionMany(
|
||||
AminoAcidNamesL, RnaBaseNames, DnaBaseNames, WaterNames
|
||||
);
|
||||
|
||||
export class StructureSelectionActionsControls extends PluginUIComponent<{}, StructureSelectionActionsControlsState> {
|
||||
state = {
|
||||
action: void 0 as StructureSelectionActionsControlsState['action'],
|
||||
@@ -60,6 +67,9 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
if (this.state.isEmpty !== isEmpty) {
|
||||
this.setState({ isEmpty });
|
||||
}
|
||||
// trigger elementQueries and nonStandardResidueQueries recalculation
|
||||
this.queriesVersion = -1;
|
||||
this.forceUpdate();
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, v => {
|
||||
@@ -83,15 +93,60 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
this.plugin.managers.structure.selection.fromSelectionQuery(modifier, selectionQuery, false);
|
||||
}
|
||||
|
||||
selectQuery: ActionMenu.OnSelect = item => {
|
||||
selectQuery: ActionMenu.OnSelect = (item, e) => {
|
||||
if (!item || !this.state.action) {
|
||||
this.setState({ action: void 0 });
|
||||
return;
|
||||
}
|
||||
const q = this.state.action! as StructureSelectionModifier;
|
||||
this.setState({ action: void 0 }, () => {
|
||||
if (e?.shiftKey) {
|
||||
this.set(q, item.value as StructureSelectionQuery);
|
||||
} else {
|
||||
this.setState({ action: void 0 }, () => {
|
||||
this.set(q, item.value as StructureSelectionQuery);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get elementQueries () {
|
||||
const uniqueElements = new Set<ElementSymbol>();
|
||||
for (const s of this.plugin.managers.structure.hierarchy.selection.structures) {
|
||||
const structure = s.cell.obj?.data;
|
||||
if (!structure) continue;
|
||||
|
||||
structure.uniqueElementSymbols.forEach(e => uniqueElements.add(e));
|
||||
}
|
||||
|
||||
const queries: StructureSelectionQuery[] = [];
|
||||
uniqueElements.forEach(e => {
|
||||
const label = ElementNames[e] || e;
|
||||
queries.push(ElementSymbolQuery([[e], label], 'Element Symbol'));
|
||||
});
|
||||
return queries;
|
||||
}
|
||||
|
||||
get nonStandardResidueQueries () {
|
||||
const residueLabels = new Map<string, string>();
|
||||
const uniqueResidues = new Set<string>();
|
||||
for (const s of this.plugin.managers.structure.hierarchy.selection.structures) {
|
||||
const structure = s.cell.obj?.data;
|
||||
if (!structure) continue;
|
||||
|
||||
structure.uniqueResidueNames.forEach(r => uniqueResidues.add(r));
|
||||
for (const m of structure.models) {
|
||||
structure.uniqueResidueNames.forEach(r => {
|
||||
const comp = m.properties.chemicalComponentMap.get(r);
|
||||
if (comp) residueLabels.set(r, comp.name);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const queries: StructureSelectionQuery[] = [];
|
||||
SetUtils.difference(uniqueResidues, StandardResidues).forEach(r => {
|
||||
const label = residueLabels.get(r) || r;
|
||||
queries.push(ResidueQuery([[r], label], 'Ligand/Non-standard Residue'));
|
||||
});
|
||||
return queries;
|
||||
}
|
||||
|
||||
private queriesItems: ActionMenu.Items[] = []
|
||||
@@ -99,8 +154,9 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
get queries () {
|
||||
const { registry } = this.plugin.query.structure;
|
||||
if (registry.version !== this.queriesVersion) {
|
||||
this.queriesItems = ActionMenu.createItems(registry.list, {
|
||||
filter: q => q !== StructureSelectionQueries.current,
|
||||
const queries = [...registry.list, ...this.nonStandardResidueQueries, ...this.elementQueries];
|
||||
this.queriesItems = ActionMenu.createItems(queries, {
|
||||
filter: q => q !== StructureSelectionQueries.current && !q.isHidden,
|
||||
label: q => q.label,
|
||||
category: q => q.category,
|
||||
description: q => q.description
|
||||
|
||||
@@ -11,6 +11,7 @@ import { PluginBehavior } from '../behavior';
|
||||
import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observer';
|
||||
import { Binding } from '../../../mol-util/binding';
|
||||
import { PluginCommands } from '../../commands';
|
||||
import { Structure, StructureElement, Bond } from '../../../mol-model/structure';
|
||||
|
||||
const B = ButtonsType;
|
||||
const M = ModifiersKeys;
|
||||
@@ -53,9 +54,13 @@ export const FocusLoci = PluginBehavior.create<FocusLociProps>({
|
||||
}
|
||||
|
||||
// The focus is handled in structure-focus-representation
|
||||
// TODO: is there a better solution?
|
||||
// const loci = Loci.normalize(current.loci, this.ctx.managers.interactivity.props.granularity);
|
||||
// this.ctx.managers.camera.focusLoci(loci, this.params);
|
||||
// TODO: is there a better solution for structure-based loci?
|
||||
|
||||
const loci = Loci.normalize(current.loci, this.ctx.managers.interactivity.props.granularity);
|
||||
|
||||
if (!Structure.isLoci(loci) && !StructureElement.Loci.is(loci) && !Bond.isLoci(loci)) {
|
||||
this.ctx.managers.camera.focusLoci(loci, this.params);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -51,8 +51,7 @@ export const AccessibleSurfaceArea = PluginBehavior.create<{ autoAttach: boolean
|
||||
}
|
||||
|
||||
unregister() {
|
||||
// TODO
|
||||
// DefaultQueryRuntimeTable.removeCustomProp(this.provider.descriptor);
|
||||
DefaultQueryRuntimeTable.removeCustomProp(this.provider.descriptor);
|
||||
|
||||
this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(AccessibleSurfaceAreaColorThemeProvider);
|
||||
|
||||
@@ -115,11 +115,11 @@ export const InitVolumeStreaming = StateAction.build({
|
||||
{ ref: params.options.behaviorRef ? params.options.behaviorRef : void 0 });
|
||||
|
||||
if (params.method === 'em') {
|
||||
behTree.apply(VolumeStreamingVisual, { channel: 'em' }, { state: { isGhost: true } });
|
||||
behTree.apply(VolumeStreamingVisual, { channel: 'em' }, { state: { isGhost: true }, tags: 'em' });
|
||||
} else {
|
||||
behTree.apply(VolumeStreamingVisual, { channel: '2fo-fc' }, { state: { isGhost: true } });
|
||||
behTree.apply(VolumeStreamingVisual, { channel: 'fo-fc(+ve)' }, { state: { isGhost: true } });
|
||||
behTree.apply(VolumeStreamingVisual, { channel: 'fo-fc(-ve)' }, { state: { isGhost: true } });
|
||||
behTree.apply(VolumeStreamingVisual, { channel: '2fo-fc' }, { state: { isGhost: true }, tags: '2fo-fc' });
|
||||
behTree.apply(VolumeStreamingVisual, { channel: 'fo-fc(+ve)' }, { state: { isGhost: true }, tags: 'fo-fc(+ve)' });
|
||||
behTree.apply(VolumeStreamingVisual, { channel: 'fo-fc(-ve)' }, { state: { isGhost: true }, tags: 'fo-fc(-ve)' });
|
||||
}
|
||||
await state.updateTree(behTree).runInContext(taskCtx);
|
||||
}));
|
||||
|
||||
@@ -5,11 +5,8 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
/** version from package.json, to be filled in at bundle build time */
|
||||
declare const __VERSION__: string;
|
||||
export const PLUGIN_VERSION = __VERSION__;
|
||||
/** version from package.json, to be filled in at build time */
|
||||
export const PLUGIN_VERSION = '';
|
||||
|
||||
/** unix time stamp, to be filled in at bundle build time */
|
||||
declare const __VERSION_TIMESTAMP__: number;
|
||||
export const PLUGIN_VERSION_TIMESTAMP = __VERSION_TIMESTAMP__;
|
||||
export const PLUGIN_VERSION_DATE = new Date(PLUGIN_VERSION_TIMESTAMP);
|
||||
/** to be filled in at build time */
|
||||
export const PLUGIN_VERSION_DATE = +new Date();
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -122,12 +122,12 @@ export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: Vol
|
||||
const transform = VolumeData.getGridToCartesianTransform(volume);
|
||||
ctx.runtime.update({ message: 'Transforming mesh...' });
|
||||
Mesh.transform(surface, transform);
|
||||
console.log(surface, Tensor.create(volume.data.space, Tensor.Data1(ids)));
|
||||
return surface;
|
||||
}
|
||||
|
||||
export const IsosurfaceMeshParams = {
|
||||
...Mesh.Params,
|
||||
quality: { ...Mesh.Params.quality, isEssential: false },
|
||||
...VolumeIsosurfaceParams
|
||||
};
|
||||
export type IsosurfaceMeshParams = typeof IsosurfaceMeshParams
|
||||
@@ -167,6 +167,7 @@ export async function createVolumeIsosurfaceWireframe(ctx: VisualContext, volume
|
||||
|
||||
export const IsosurfaceWireframeParams = {
|
||||
...Lines.Params,
|
||||
quality: { ...Lines.Params.quality, isEssential: false },
|
||||
sizeFactor: PD.Numeric(1.5, { min: 0, max: 10, step: 0.1 }),
|
||||
...VolumeIsosurfaceParams
|
||||
};
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Image } from '../../mol-geo/geometry/image/image';
|
||||
import { BaseGeometry } from '../../mol-geo/geometry/base';
|
||||
import { ThemeRegistryContext, Theme } from '../../mol-theme/theme';
|
||||
import { VolumeData } from '../../mol-model/volume';
|
||||
import { VolumeData, VolumeIsoValue } from '../../mol-model/volume';
|
||||
import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider } from './representation';
|
||||
import { LocationIterator } from '../../mol-geo/util/location-iterator';
|
||||
import { VisualUpdateState } from '../util';
|
||||
@@ -18,57 +17,118 @@ import { VisualContext } from '../visual';
|
||||
import { Volume } from '../../mol-model/volume/volume';
|
||||
import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { EmptyLoci, Loci } from '../../mol-model/loci';
|
||||
import { Interval, OrderedSet } from '../../mol-data/int';
|
||||
import { fillSerial } from '../../mol-util/array';
|
||||
import { Interval, OrderedSet, SortedArray } from '../../mol-data/int';
|
||||
import { transformPositionArray } from '../../mol-geo/util';
|
||||
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
|
||||
import { RenderableState } from '../../mol-gl/renderable';
|
||||
import { createIsoValueParam, IsoValueParam } from './isosurface';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { ColorTheme } from '../../mol-theme/color';
|
||||
|
||||
export async function createImage(ctx: VisualContext, volume: VolumeData, theme: Theme, props: PD.Values<SliceParams>, image?: Image) {
|
||||
const dim = parseInt(props.dimension.name.toString());
|
||||
// const index = props.dimension.params;
|
||||
const { dimension: { name: dim }, isoValue } = props;
|
||||
|
||||
const { space } = volume.data;
|
||||
const { space, data: data } = volume.data;
|
||||
const { min, max } = volume.dataStats;
|
||||
const isoVal = VolumeIsoValue.toAbsolute(isoValue, volume.dataStats).absoluteValue;
|
||||
|
||||
let width: number, height: number;
|
||||
if (dim === 0) {
|
||||
width = space.dimensions[1];
|
||||
height = space.dimensions[2];
|
||||
} else if (dim === 1) {
|
||||
width = space.dimensions[0];
|
||||
height = space.dimensions[2];
|
||||
} else {
|
||||
width = space.dimensions[0];
|
||||
height = space.dimensions[1];
|
||||
// TODO more color themes
|
||||
const color = theme.color.color(NullLocation, false);
|
||||
const [r, g, b] = Color.toRgbNormalized(color);
|
||||
|
||||
const {
|
||||
width, height,
|
||||
x, y, z,
|
||||
x0, y0, z0,
|
||||
nx, ny, nz
|
||||
} = getSliceInfo(volume, props);
|
||||
|
||||
const corners = new Float32Array(
|
||||
dim === 'x' ? [x, 0, 0, x, y, 0, x, 0, z, x, y, z] :
|
||||
dim === 'y' ? [0, y, 0, x, y, 0, 0, y, z, x, y, z] :
|
||||
[0, 0, z, 0, y, z, x, 0, z, x, y, z]
|
||||
);
|
||||
|
||||
const imageArray = new Float32Array(width * height * 4);
|
||||
const groupArray = getGroupArray(volume, props);
|
||||
|
||||
let i = 0;
|
||||
for (let iy = y0; iy < ny; ++iy) {
|
||||
for (let ix = x0; ix < nx; ++ix) {
|
||||
for (let iz = z0; iz < nz; ++iz) {
|
||||
const val = space.get(data, ix, iy, iz);
|
||||
const normVal = (val - min) / (max - min);
|
||||
|
||||
imageArray[i] = r * normVal * 2;
|
||||
imageArray[i + 1] = g * normVal * 2;
|
||||
imageArray[i + 2] = b * normVal * 2;
|
||||
imageArray[i + 3] = val >= isoVal ? 1 : 0;
|
||||
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const n = width * height;
|
||||
const imageTexture = { width, height, array: imageArray, flipY: true };
|
||||
const groupTexture = { width, height, array: groupArray, flipY: true };
|
||||
|
||||
// TODO fill with volume data values
|
||||
const imageTexture = { width, height, array: new Float32Array(n * 4) };
|
||||
|
||||
for (let i = 0, il = n * 4; i < il; i += 4) {
|
||||
imageTexture.array[i] = 0;
|
||||
imageTexture.array[i + 1] = Math.random();
|
||||
imageTexture.array[i + 2] = Math.random();
|
||||
imageTexture.array[i + 3] = 1;
|
||||
}
|
||||
|
||||
// TODO fill with linearized index into volume
|
||||
// (to be used for picking which needs a volume location/loci)
|
||||
const groupTexture = { width, height, array: fillSerial(new Float32Array(n)) };
|
||||
|
||||
// TODO four corners of a plane
|
||||
const corners = new Float32Array([
|
||||
0, 0, 0,
|
||||
0, 50, 0,
|
||||
0, 0, 50,
|
||||
0, 50, 50
|
||||
]);
|
||||
const transform = VolumeData.getGridToCartesianTransform(volume);
|
||||
transformPositionArray(transform, corners, 0, 4);
|
||||
|
||||
return Image.create(imageTexture, corners, groupTexture, image);
|
||||
}
|
||||
|
||||
function getSliceInfo(volume: VolumeData, props: PD.Values<SliceParams>) {
|
||||
const { dimension: { name: dim, params: index } } = props;
|
||||
const { space } = volume.data;
|
||||
|
||||
let width, height;
|
||||
let x, y, z;
|
||||
let x0 = 0, y0 = 0, z0 = 0;
|
||||
let [nx, ny, nz] = space.dimensions;
|
||||
|
||||
if (dim === 'x') {
|
||||
x = index, y = ny - 1, z = nz - 1;
|
||||
width = nz, height = ny;
|
||||
x0 = x, nx = x0 + 1;
|
||||
} else if (dim === 'y') {
|
||||
x = nx - 1, y = index, z = nz - 1;
|
||||
width = nz, height = nx;
|
||||
y0 = y, ny = y0 + 1;
|
||||
} else {
|
||||
x = nx - 1, y = ny - 1, z = index;
|
||||
width = nx, height = ny;
|
||||
z0 = z, nz = z0 + 1;
|
||||
}
|
||||
return {
|
||||
width, height,
|
||||
x, y, z,
|
||||
x0, y0, z0,
|
||||
nx, ny, nz
|
||||
};
|
||||
}
|
||||
|
||||
function getGroupArray(volume: VolumeData, props: PD.Values<SliceParams>) {
|
||||
const { space } = volume.data;
|
||||
const { width, height, x0, y0, z0, nx, ny, nz } = getSliceInfo(volume, props);
|
||||
const groupArray = new Float32Array(width * height);
|
||||
|
||||
let j = 0;
|
||||
for (let iy = y0; iy < ny; ++iy) {
|
||||
for (let ix = x0; ix < nx; ++ix) {
|
||||
for (let iz = z0; iz < nz; ++iz) {
|
||||
groupArray[j] = space.dataOffset(ix, iy, iz);
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return groupArray;
|
||||
}
|
||||
|
||||
function getLoci(volume: VolumeData, props: PD.Values<SliceParams>) {
|
||||
// TODO only slice indices
|
||||
return Volume.Loci(volume);
|
||||
// TODO cache somehow?
|
||||
const groupArray = getGroupArray(volume, props);
|
||||
return Volume.Cell.Loci(volume, SortedArray.ofUnsortedArray(groupArray));
|
||||
}
|
||||
|
||||
function getSliceLoci(pickingId: PickingId, volume: VolumeData, props: PD.Values<SliceParams>, id: number) {
|
||||
@@ -86,8 +146,15 @@ function eachSlice(loci: Loci, volume: VolumeData, props: PD.Values<SliceParams>
|
||||
if (apply(Interval.ofLength(volume.data.data.length))) changed = true;
|
||||
} else if (Volume.Isosurface.isLoci(loci)) {
|
||||
if (!VolumeData.areEquivalent(loci.volume, volume)) return false;
|
||||
// TODO check isoValue
|
||||
if (apply(Interval.ofLength(volume.data.data.length))) changed = true;
|
||||
// TODO find a cheaper way?
|
||||
const { dataStats, data: { data } } = volume;
|
||||
const eps = dataStats.sigma;
|
||||
const v = VolumeIsoValue.toAbsolute(loci.isoValue, dataStats).absoluteValue;
|
||||
for (let i = 0, il = data.length; i < il; ++i) {
|
||||
if (equalEps(v, data[i], eps)) {
|
||||
if (apply(Interval.ofSingleton(i))) changed = true;
|
||||
}
|
||||
}
|
||||
} else if (Volume.Cell.isLoci(loci)) {
|
||||
if (!VolumeData.areEquivalent(loci.volume, volume)) return false;
|
||||
if (Interval.is(loci.indices)) {
|
||||
@@ -104,22 +171,25 @@ function eachSlice(loci: Loci, volume: VolumeData, props: PD.Values<SliceParams>
|
||||
//
|
||||
|
||||
export const SliceParams = {
|
||||
...BaseGeometry.Params,
|
||||
...Image.Params,
|
||||
dimension: PD.MappedStatic(0, {
|
||||
0: PD.Numeric(0, { min: 0, max: 0, step: 1 }),
|
||||
1: PD.Numeric(0, { min: 0, max: 0, step: 1 }),
|
||||
2: PD.Numeric(0, { min: 0, max: 0, step: 1 }),
|
||||
quality: { ...Image.Params.quality, isEssential: false },
|
||||
dimension: PD.MappedStatic('x', {
|
||||
x: PD.Numeric(0, { min: 0, max: 0, step: 1 }),
|
||||
y: PD.Numeric(0, { min: 0, max: 0, step: 1 }),
|
||||
z: PD.Numeric(0, { min: 0, max: 0, step: 1 }),
|
||||
}, { isEssential: true }),
|
||||
isoValue: IsoValueParam,
|
||||
};
|
||||
export type SliceParams = typeof SliceParams
|
||||
export function getSliceParams(ctx: ThemeRegistryContext, volume: VolumeData) {
|
||||
const p = PD.clone(SliceParams);
|
||||
p.dimension = PD.MappedStatic(0, {
|
||||
0: PD.Numeric(0, { min: 0, max: volume.data.space.dimensions[0], step: 1 }),
|
||||
1: PD.Numeric(0, { min: 0, max: volume.data.space.dimensions[1], step: 1 }),
|
||||
2: PD.Numeric(0, { min: 0, max: volume.data.space.dimensions[2], step: 1 }),
|
||||
const dim = volume.data.space.dimensions;
|
||||
p.dimension = PD.MappedStatic('x', {
|
||||
x: PD.Numeric(0, { min: 0, max: dim[0] - 1, step: 1 }),
|
||||
y: PD.Numeric(0, { min: 0, max: dim[1] - 1, step: 1 }),
|
||||
z: PD.Numeric(0, { min: 0, max: dim[2] - 1, step: 1 }),
|
||||
}, { isEssential: true });
|
||||
p.isoValue = createIsoValueParam(VolumeIsoValue.absolute(volume.dataStats.min), volume.dataStats);
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -130,16 +200,32 @@ export function SliceVisual(materialId: number): VolumeVisual<SliceParams> {
|
||||
createLocationIterator: (volume: VolumeData) => LocationIterator(volume.data.data.length, 1, () => NullLocation),
|
||||
getLoci: getSliceLoci,
|
||||
eachLocation: eachSlice,
|
||||
setUpdateState: (state: VisualUpdateState, volume: VolumeData, newProps: PD.Values<SliceParams>, currentProps: PD.Values<SliceParams>) => {
|
||||
setUpdateState: (state: VisualUpdateState, volume: VolumeData, newProps: PD.Values<SliceParams>, currentProps: PD.Values<SliceParams>, newTheme: Theme, currentTheme: Theme) => {
|
||||
state.createGeometry = (
|
||||
newProps.dimension.name !== currentProps.dimension.name ||
|
||||
newProps.dimension.params !== currentProps.dimension.params
|
||||
newProps.dimension.params !== currentProps.dimension.params ||
|
||||
!VolumeIsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.dataStats) ||
|
||||
!ColorTheme.areEqual(newTheme.color, currentTheme.color)
|
||||
);
|
||||
},
|
||||
geometryUtils: Image.Utils
|
||||
geometryUtils: {
|
||||
...Image.Utils,
|
||||
createRenderableState: (props: PD.Values<SliceParams>) => {
|
||||
const state = Image.Utils.createRenderableState(props);
|
||||
updateRenderableState(state, props);
|
||||
return state;
|
||||
},
|
||||
updateRenderableState
|
||||
}
|
||||
}, materialId);
|
||||
}
|
||||
|
||||
function updateRenderableState(state: RenderableState, props: PD.Values<SliceParams>) {
|
||||
Image.Utils.updateRenderableState(state, props);
|
||||
state.opaque = false;
|
||||
state.writeDepth = true;
|
||||
}
|
||||
|
||||
export function SliceRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, SliceParams>): VolumeRepresentation<SliceParams> {
|
||||
return VolumeRepresentation('Slice', ctx, getParams, SliceVisual, getLoci);
|
||||
}
|
||||
|
||||
@@ -11,9 +11,13 @@ import { MSymbol } from '../../language/symbol';
|
||||
export class QueryRuntimeTable {
|
||||
private map = new Map<string, QuerySymbolRuntime>();
|
||||
|
||||
removeSymbol(runtime: QuerySymbolRuntime) {
|
||||
this.map.delete(runtime.symbol.id);
|
||||
}
|
||||
|
||||
addSymbol(runtime: QuerySymbolRuntime) {
|
||||
if (this.map.has(runtime.symbol.id)) {
|
||||
throw new Error(`Symbol '${runtime.symbol.id}' already added.`);
|
||||
console.warn(`Symbol '${runtime.symbol.id}' already added. Call removeSymbol/removeCustomProps re-adding the symbol.`);
|
||||
}
|
||||
this.map.set(runtime.symbol.id, runtime);
|
||||
}
|
||||
@@ -26,6 +30,14 @@ export class QueryRuntimeTable {
|
||||
}
|
||||
}
|
||||
|
||||
removeCustomProp(desc: CustomPropertyDescriptor<any>) {
|
||||
if (!desc.symbols) return;
|
||||
|
||||
for (const k of Object.keys(desc.symbols)) {
|
||||
this.removeSymbol((desc.symbols as any)[k]);
|
||||
}
|
||||
}
|
||||
|
||||
getRuntime(id: string) {
|
||||
return this.map.get(id);
|
||||
}
|
||||
|
||||
@@ -56,10 +56,16 @@ export function lociLabel(loci: Loci, options: Partial<LabelOptions> = {}): stri
|
||||
case 'cell-loci':
|
||||
const size = OrderedSet.size(loci.indices);
|
||||
const start = OrderedSet.start(loci.indices);
|
||||
return [
|
||||
const absVal = VolumeIsoValue.absolute(loci.volume.data.data[start]);
|
||||
const relVal = VolumeIsoValue.toRelative(absVal, loci.volume.dataStats);
|
||||
const label = [
|
||||
`${loci.volume.label || 'Volume'}`,
|
||||
`${size === 1 ? `Cell #${start}` : `${size} Cells`}`
|
||||
].join(' | ');
|
||||
];
|
||||
if (size === 1) {
|
||||
label.push(`${VolumeIsoValue.toString(absVal)} (${VolumeIsoValue.toString(relVal)})`);
|
||||
}
|
||||
return label.join(' | ');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ export function getProviderFromType(type: Type): Provider {
|
||||
export async function open(name: string, filename: string, type: Type): Promise<Context> {
|
||||
const provider = getProviderFromType(type);
|
||||
const descriptor = await File.openRead(filename);
|
||||
const file = FileHandle.fromDescriptor(descriptor);
|
||||
const file = FileHandle.fromDescriptor(descriptor, filename);
|
||||
const header = await provider.readHeader(name, file);
|
||||
const data = { header, file, slices: void 0 as any };
|
||||
return { data, provider };
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function createContext(filename: string, channels: Format.Context[]
|
||||
}
|
||||
|
||||
const ctx: Data.Context = {
|
||||
file: FileHandle.fromDescriptor(await File.createFile(filename)),
|
||||
file: FileHandle.fromDescriptor(await File.createFile(filename), filename),
|
||||
isPeriodic,
|
||||
channels,
|
||||
valueType,
|
||||
|
||||
@@ -66,7 +66,7 @@ async function readHeader(filename: string | undefined, sourceId: string) {
|
||||
let file: FileHandle | undefined;
|
||||
try {
|
||||
if (!filename) return void 0;
|
||||
file = FileHandle.fromDescriptor(await File.openRead(filename));
|
||||
file = FileHandle.fromDescriptor(await File.openRead(filename), filename);
|
||||
const header = await DataFormat.readHeader(file);
|
||||
return header.header;
|
||||
} catch (e) {
|
||||
|
||||
@@ -34,7 +34,7 @@ export default async function execute(params: Data.QueryParams, outputProvider:
|
||||
|
||||
let sourceFile: FileHandle | undefined;
|
||||
try {
|
||||
sourceFile = FileHandle.fromDescriptor(await File.openRead(params.sourceFilename));
|
||||
sourceFile = FileHandle.fromDescriptor(await File.openRead(params.sourceFilename), params.sourceFilename);
|
||||
await _execute(sourceFile, params, guid, outputProvider);
|
||||
return true;
|
||||
} catch (e) {
|
||||
|
||||
@@ -2,6 +2,7 @@ const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const VersionFile = require('webpack-version-file-plugin');
|
||||
// const CircularDependencyPlugin = require('circular-dependency-plugin');
|
||||
|
||||
const sharedConfig = {
|
||||
@@ -38,11 +39,17 @@ const sharedConfig = {
|
||||
],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
__VERSION__: webpack.DefinePlugin.runtimeValue(() => JSON.stringify(require('./package.json').version), true),
|
||||
__VERSION_TIMESTAMP__: webpack.DefinePlugin.runtimeValue(() => `${new Date().valueOf()}`, true),
|
||||
// __VERSION__: webpack.DefinePlugin.runtimeValue(() => JSON.stringify(require('./package.json').version), true),
|
||||
// __VERSION_TIMESTAMP__: webpack.DefinePlugin.runtimeValue(() => `${new Date().valueOf()}`, true),
|
||||
'process.env.DEBUG': JSON.stringify(process.env.DEBUG)
|
||||
}),
|
||||
new MiniCssExtractPlugin({ filename: 'app.css' })
|
||||
new MiniCssExtractPlugin({ filename: 'app.css' }),
|
||||
new VersionFile({
|
||||
extras: { timestamp: `${new Date().valueOf()}` },
|
||||
packageFile: path.resolve(__dirname, 'package.json'),
|
||||
templateString: `export const PLUGIN_VERSION = '<%= package.version %>';\nexport const PLUGIN_VERSION_DATE = new Date(<%= extras.timestamp %>);`,
|
||||
outputFile: path.resolve(__dirname, 'lib/mol-plugin/version.js')
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
modules: [
|
||||
|
||||
Reference in New Issue
Block a user