mirror of
https://github.com/molstar/molstar.git
synced 2026-06-06 06:34:23 +08:00
Compare commits
83 Commits
v0.7.0-dev
...
v0.7.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88369158c9 | ||
|
|
8926575283 | ||
|
|
15b0288ce4 | ||
|
|
28853ec19d | ||
|
|
e3480a076a | ||
|
|
abf6452124 | ||
|
|
2cdd811dd3 | ||
|
|
be6fea39bf | ||
|
|
ed1bc2cd07 | ||
|
|
28d3d5861a | ||
|
|
95d3ef491f | ||
|
|
5c37ddfc6d | ||
|
|
4d9e2d9c91 | ||
|
|
0b1c18913d | ||
|
|
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 | ||
|
|
d42c9a6e15 | ||
|
|
da4dabc3f5 | ||
|
|
92217905f8 | ||
|
|
598441a727 | ||
|
|
f707cb19a4 | ||
|
|
63fc408be6 | ||
|
|
69dedd8c22 | ||
|
|
5480805754 | ||
|
|
655ae65b8d | ||
|
|
1a23cb672e | ||
|
|
19e18b4089 | ||
|
|
3d909d5012 | ||
|
|
ad521948b6 | ||
|
|
2f3b6a28c1 | ||
|
|
c779da674c | ||
|
|
d9b140f9f2 | ||
|
|
052648023e | ||
|
|
f5d12d440e | ||
|
|
99d7a90863 | ||
|
|
901d5c86e6 | ||
|
|
df9efd05e6 | ||
|
|
26b8adaec4 | ||
|
|
fc6f5a0336 | ||
|
|
f823a887b7 | ||
|
|
b346d4d85d | ||
|
|
70bd035898 | ||
|
|
7e5cdd8e06 | ||
|
|
a21dac60e0 | ||
|
|
9bd2e0d96e | ||
|
|
dd15a000e1 | ||
|
|
ebcfa44f22 | ||
|
|
43845adb71 | ||
|
|
9e3fff65a7 | ||
|
|
05c35a3a3a | ||
|
|
6f46965344 | ||
|
|
27ee576340 | ||
|
|
58492328df | ||
|
|
fd102bede1 | ||
|
|
3d8c47eefa | ||
|
|
c4e43228a2 | ||
|
|
6526090b8b | ||
|
|
094a018b5b | ||
|
|
a0a9c994b2 | ||
|
|
524ed90e3f |
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": ["tsconfig.json", "src/servers/tsconfig.json"],
|
||||
"project": ["tsconfig.json", "tsconfig.servers.json"],
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
|
||||
1
.npmignore
Normal file
1
.npmignore
Normal file
@@ -0,0 +1 @@
|
||||
tsconfig.servers.buildinfo
|
||||
2688
package-lock.json
generated
2688
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.7.0-dev.0",
|
||||
"version": "0.7.0-dev.8",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -16,7 +16,7 @@
|
||||
"test": "npm run lint && jest",
|
||||
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
|
||||
"build-viewer": "npm run build-tsc && npm run build-extra && npm run build-webpack-viewer",
|
||||
"build-tsc": "tsc --incremental && tsc --build src/servers --incremental",
|
||||
"build-tsc": "tsc --incremental && tsc --build tsconfig.servers.json --incremental",
|
||||
"build-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/",
|
||||
"build-webpack": "webpack --mode production --config ./webpack.config.production.js",
|
||||
"build-webpack-viewer": "webpack --mode production --config ./webpack.config.viewer.js",
|
||||
@@ -24,7 +24,7 @@
|
||||
"watch-viewer": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer\"",
|
||||
"watch-viewer-debug": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer-debug\"",
|
||||
"watch-tsc": "tsc --watch --incremental",
|
||||
"watch-servers": "tsc --build src/servers --watch --incremental",
|
||||
"watch-servers": "tsc --build tsconfig.servers.json --watch --incremental",
|
||||
"watch-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/ --watch",
|
||||
"watch-webpack": "webpack -w --mode development --display minimal",
|
||||
"watch-webpack-viewer": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.js",
|
||||
@@ -45,7 +45,7 @@
|
||||
"cif2bcif": "lib/apps/cif2bcif/index.js",
|
||||
"cifschema": "lib/apps/cifschema/index.js",
|
||||
"model-server": "lib/servers/servers/model/server.js",
|
||||
"model-server-query": "lib/servers/servers/model/local.js",
|
||||
"model-server-query": "lib/servers/servers/model/query.js",
|
||||
"model-server-preprocess": "lib/servers/servers/model/preprocess.js",
|
||||
"volume-server": "lib/servers/servers/volume/server.js",
|
||||
"volume-server-query": "lib/servers/servers/volume/query.js",
|
||||
@@ -82,51 +82,52 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^1.13.2",
|
||||
"@graphql-codegen/cli": "^1.13.2",
|
||||
"@graphql-codegen/time": "^1.13.2",
|
||||
"@graphql-codegen/typescript": "^1.13.2",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.13.2",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.13.2",
|
||||
"@graphql-codegen/typescript-operations": "^1.13.2",
|
||||
"@graphql-codegen/add": "^1.13.3",
|
||||
"@graphql-codegen/cli": "^1.13.3",
|
||||
"@graphql-codegen/time": "^1.13.3",
|
||||
"@graphql-codegen/typescript": "^1.13.3",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.13.3",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.13.3",
|
||||
"@graphql-codegen/typescript-operations": "^1.13.3",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@typescript-eslint/eslint-plugin": "^2.28.0",
|
||||
"@typescript-eslint/parser": "^2.28.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.29.0",
|
||||
"@typescript-eslint/parser": "^2.29.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"circular-dependency-plugin": "^5.2.0",
|
||||
"concurrently": "^5.1.0",
|
||||
"cpx2": "^2.0.0",
|
||||
"css-loader": "^3.5.2",
|
||||
"css-loader": "^3.5.3",
|
||||
"eslint": "^6.8.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.0.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"graphql": "^15.0.0",
|
||||
"http-server": "^0.12.1",
|
||||
"jest": "^25.3.0",
|
||||
"jest": "^25.4.0",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"node-sass": "^4.14.0",
|
||||
"raw-loader": "^4.0.1",
|
||||
"resolve-url-loader": "^3.1.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"simple-git": "^1.132.0",
|
||||
"style-loader": "^1.1.4",
|
||||
"style-loader": "^1.2.0",
|
||||
"ts-jest": "^25.4.0",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11"
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-version-file-plugin": "^0.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.9.10",
|
||||
"@material-ui/core": "^4.9.11",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@types/argparse": "^1.0.38",
|
||||
"@types/benchmark": "^1.0.31",
|
||||
"@types/compression": "1.7.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/node": "^13.13.0",
|
||||
"@types/node-fetch": "^2.5.6",
|
||||
"@types/node": "^13.13.2",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/react": "^16.9.34",
|
||||
"@types/react-dom": "^16.9.6",
|
||||
"@types/swagger-ui-dist": "3.0.5",
|
||||
|
||||
@@ -34,9 +34,8 @@ function print(data: Volume) {
|
||||
const { volume_data_3d_info } = data.source;
|
||||
const row = Table.getRow(volume_data_3d_info, 0);
|
||||
console.log(row);
|
||||
console.log(data.volume.cell);
|
||||
if (data.volume.transform) console.log(data.volume.transform);
|
||||
console.log(data.volume.dataStats);
|
||||
console.log(data.volume.fractionalBox);
|
||||
}
|
||||
|
||||
async function doMesh(data: Volume, filename: string) {
|
||||
|
||||
@@ -176,16 +176,19 @@
|
||||
|
||||
var snapshot;
|
||||
addControl('Set Snapshot', () => {
|
||||
snapshot = PluginWrapper.snapshot.get();
|
||||
// could use JSON.stringify(snapshot) and upload the data
|
||||
// const options = { data: true, behavior: false, animation: false, interactivity: false, canvas3d: false, camera: false, cameraTransition: false };
|
||||
snapshot = PluginWrapper.plugin.state.getSnapshot(/** options */);
|
||||
// console.log(JSON.stringify(snapshot, null, 2));
|
||||
});
|
||||
addControl('Restore Snapshot', () => {
|
||||
if (!snapshot) return;
|
||||
PluginWrapper.snapshot.set(snapshot);
|
||||
});
|
||||
addControl('Download Snapshot', () => {
|
||||
snapshot = PluginWrapper.snapshot.download();
|
||||
addControl('Download State', () => {
|
||||
snapshot = PluginWrapper.snapshot.download('molj');
|
||||
});
|
||||
addControl('Download Session', () => {
|
||||
snapshot = PluginWrapper.snapshot.download('molx');
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
@@ -33,7 +33,7 @@ require('../../mol-plugin-ui/skin/light.scss');
|
||||
|
||||
class MolStarProteopediaWrapper {
|
||||
static VERSION_MAJOR = 5;
|
||||
static VERSION_MINOR = 2;
|
||||
static VERSION_MINOR = 4;
|
||||
|
||||
private _ev = RxEventHelper.create();
|
||||
|
||||
@@ -195,6 +195,7 @@ class MolStarProteopediaWrapper {
|
||||
return PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
}
|
||||
|
||||
private emptyLoadedParams: LoadParams = { url: '', format: 'cif', isBinary: false, assemblyId: '' };
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', isBinary: false, assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = 'deposited', isBinary = false, representationStyle }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
@@ -265,26 +266,7 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
camera = {
|
||||
toggleSpin: () => this.toggleSpin(),
|
||||
resetPosition: () => PluginCommands.Camera.Reset(this.plugin, { }),
|
||||
// setClip: (options?: { distance?: number, near?: number, far?: number }) => {
|
||||
// if (!options) {
|
||||
// PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
// settings: {
|
||||
// cameraClipDistance: DefaultCanvas3DParams.cameraClipDistance,
|
||||
// clip: DefaultCanvas3DParams.clip
|
||||
// }
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// options = options || { };
|
||||
// const props = this.plugin.canvas3d.props;
|
||||
// const clipNear = typeof options.near === 'undefined' ? props.clip[0] : options.near;
|
||||
// const clipFar = typeof options.far === 'undefined' ? props.clip[1] : options.far;
|
||||
// PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
// settings: { cameraClipDistance: options.distance, clip: [clipNear, clipFar] }
|
||||
// });
|
||||
// }
|
||||
resetPosition: () => PluginCommands.Camera.Reset(this.plugin, { })
|
||||
}
|
||||
|
||||
animate = {
|
||||
@@ -305,11 +287,6 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
const state = this.state;
|
||||
|
||||
// const visuals = state.selectQ(q => q.ofType(PluginStateObject.Molecule.Structure.Representation3D).filter(c => c.transform.transformer === StateTransforms.Representation.StructureRepresentation3D));
|
||||
// for (const v of visuals) {
|
||||
// }
|
||||
|
||||
const tree = state.build();
|
||||
const colorTheme = { name: EvolutionaryConservation.propertyProvider.descriptor.name, params: this.plugin.representation.structure.themes.colorThemeRegistry.get(EvolutionaryConservation.propertyProvider.descriptor.name).defaultValues };
|
||||
|
||||
@@ -416,22 +393,21 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
snapshot = {
|
||||
get: () => {
|
||||
return this.plugin.state.getSnapshot();
|
||||
get: (params?: PluginState.SnapshotParams) => {
|
||||
return this.plugin.state.getSnapshot(params);
|
||||
},
|
||||
set: (snapshot: PluginState.Snapshot) => {
|
||||
return this.plugin.state.setSnapshot(snapshot);
|
||||
},
|
||||
download: () => {
|
||||
const json = JSON.stringify(this.plugin.state.getSnapshot(), null, 2);
|
||||
const blob = new Blob([json], {type : 'application/json;charset=utf-8'});
|
||||
download(blob, `mol-star_state_${(name || getFormattedTime())}.json`);
|
||||
download: async (type: 'molj' | 'molx' = 'molj', params?: PluginState.SnapshotParams) => {
|
||||
const data = await this.plugin.managers.snapshot.serialize({ type, params });
|
||||
download(data, `mol-star_state_${(name || getFormattedTime())}.${type}`);
|
||||
},
|
||||
fetch: async (url: string) => {
|
||||
fetch: async (url: string, type: 'molj' | 'molx' = 'molj') => {
|
||||
try {
|
||||
const snapshot = await this.plugin.runTask(this.plugin.fetch({ url, type: 'json' }));
|
||||
// TODO: is this OK to test for snapshots from server?
|
||||
await this.plugin.state.setSnapshot(snapshot?.data?.entries?.[0]?.snapshot || snapshot);
|
||||
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'binary' }));
|
||||
this.loadedParams = { ...this.emptyLoadedParams };
|
||||
await this.plugin.managers.snapshot.open(new File([data], `state.${type}`));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ export interface Ingredient {
|
||||
principalAxis?: Vec3;
|
||||
/** offset along membrane */
|
||||
offset?: Vec3;
|
||||
ingtype?: string;
|
||||
}
|
||||
|
||||
export interface IngredientSource {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Ingredient, IngredientSource, CellPacking } from './data';
|
||||
import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean } from './util';
|
||||
import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean, getFromOPM } from './util';
|
||||
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../mol-model/structure';
|
||||
import { trajectoryFromMmCIF, MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { trajectoryFromPDB } from '../../mol-model-formats/structure/pdb';
|
||||
@@ -26,56 +26,71 @@ import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Column } from '../../mol-data/db';
|
||||
import { createModels } from '../../mol-model-formats/structure/basic/parser';
|
||||
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
|
||||
import { Asset, AssetManager } from '../../mol-util/assets';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/results/${fileName}`;
|
||||
}
|
||||
|
||||
async function getModel(assetManager: AssetManager, id: string, model_id: number, baseUrl: string, file?: Asset.File) {
|
||||
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, baseUrl: string, file?: Asset.File) {
|
||||
const assetManager = plugin.managers.asset;
|
||||
const model_id = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
|
||||
const surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
|
||||
let model: Model;
|
||||
let assets: Asset.Wrapper[] = [];
|
||||
if (file) {
|
||||
if (file.name.endsWith('.cif')) {
|
||||
const text = await assetManager.resolve(file, 'string').run();
|
||||
const text = await plugin.runTask(assetManager.resolve(file, 'string'));
|
||||
assets.push(text);
|
||||
const cif = (await parseCif(text.data)).blocks[0];
|
||||
model = (await trajectoryFromMmCIF(cif).run())[model_id];
|
||||
const cif = (await parseCif(plugin, text.data)).blocks[0];
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(cif)))[model_id];
|
||||
} else if (file.name.endsWith('.bcif')) {
|
||||
const binary = await assetManager.resolve(file, 'binary').run();
|
||||
const binary = await plugin.runTask(assetManager.resolve(file, 'binary'));
|
||||
assets.push(binary);
|
||||
const cif = (await parseCif(binary.data)).blocks[0];
|
||||
model = (await trajectoryFromMmCIF(cif).run())[model_id];
|
||||
const cif = (await parseCif(plugin, binary.data)).blocks[0];
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(cif)))[model_id];
|
||||
} else if (file.name.endsWith('.pdb')) {
|
||||
const text = await assetManager.resolve(file, 'string').run();
|
||||
const text = await plugin.runTask(assetManager.resolve(file, 'string'));
|
||||
assets.push(text);
|
||||
const pdb = await parsePDBfile(text.data, id);
|
||||
model = (await trajectoryFromPDB(pdb).run())[model_id];
|
||||
const pdb = await parsePDBfile(plugin, text.data, id);
|
||||
model = (await plugin.runTask(trajectoryFromPDB(pdb)))[model_id];
|
||||
} else {
|
||||
throw new Error(`unsupported file type '${file.name}'`);
|
||||
}
|
||||
} else if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
|
||||
const { mmcif, asset } = await getFromPdb(id, assetManager);
|
||||
assets.push(asset);
|
||||
model = (await trajectoryFromMmCIF(mmcif).run())[model_id];
|
||||
if (surface){
|
||||
const data = await getFromOPM(plugin, id, assetManager);
|
||||
if (data.asset){
|
||||
assets.push(data.asset);
|
||||
model = (await plugin.runTask(trajectoryFromPDB(data.pdb)))[model_id];
|
||||
} else {
|
||||
const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
|
||||
assets.push(asset);
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(mmcif)))[model_id];
|
||||
}
|
||||
} else {
|
||||
const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
|
||||
assets.push(asset);
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(mmcif)))[model_id];
|
||||
}
|
||||
} else {
|
||||
const data = await getFromCellPackDB(id, baseUrl, assetManager);
|
||||
const data = await getFromCellPackDB(plugin, id, baseUrl, assetManager);
|
||||
assets.push(data.asset);
|
||||
if ('pdb' in data) {
|
||||
model = (await trajectoryFromPDB(data.pdb).run())[model_id];
|
||||
model = (await plugin.runTask(trajectoryFromPDB(data.pdb)))[model_id];
|
||||
} else {
|
||||
model = (await trajectoryFromMmCIF(data.mmcif).run())[model_id];
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(data.mmcif)))[model_id];
|
||||
}
|
||||
}
|
||||
return { model, assets };
|
||||
}
|
||||
|
||||
async function getStructure(model: Model, source: IngredientSource, props: { assembly?: string } = {}) {
|
||||
async function getStructure(plugin: PluginContext, model: Model, source: IngredientSource, props: { assembly?: string } = {}) {
|
||||
let structure = Structure.ofModel(model);
|
||||
const { assembly } = props;
|
||||
|
||||
if (assembly) {
|
||||
structure = await StructureSymmetry.buildAssembly(structure, assembly).run();
|
||||
structure = await plugin.runTask(StructureSymmetry.buildAssembly(structure, assembly));
|
||||
}
|
||||
let query;
|
||||
if (source.selection){
|
||||
@@ -260,7 +275,7 @@ function getCifCurve(name: string, transforms: Mat4[], model: Model) {
|
||||
};
|
||||
}
|
||||
|
||||
async function getCurve(name: string, ingredient: Ingredient, transforms: Mat4[], model: Model) {
|
||||
async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredient, transforms: Mat4[], model: Model) {
|
||||
const cif = getCifCurve(name, transforms, model);
|
||||
|
||||
const curveModelTask = Task.create('Curve Model', async ctx => {
|
||||
@@ -269,11 +284,11 @@ async function getCurve(name: string, ingredient: Ingredient, transforms: Mat4[]
|
||||
return models[0];
|
||||
});
|
||||
|
||||
const curveModel = await curveModelTask.run();
|
||||
return getStructure(curveModel, ingredient.source);
|
||||
const curveModel = await plugin.runTask(curveModelTask);
|
||||
return getStructure(plugin, curveModel, ingredient.source);
|
||||
}
|
||||
|
||||
async function getIngredientStructure(assetManager: AssetManager, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles) {
|
||||
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles) {
|
||||
const { name, source, results, nbCurve } = ingredient;
|
||||
if (source.pdb === 'None') return;
|
||||
|
||||
@@ -288,13 +303,12 @@ async function getIngredientStructure(assetManager: AssetManager, ingredient: In
|
||||
}
|
||||
|
||||
// model id in case structure is NMR
|
||||
const model_id = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
|
||||
const { model, assets } = await getModel(assetManager, source.pdb || name, model_id, baseUrl, file);
|
||||
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, file);
|
||||
if (!model) return;
|
||||
|
||||
let structure: Structure;
|
||||
if (nbCurve) {
|
||||
structure = await getCurve(name, ingredient, getCurveTransforms(ingredient), model);
|
||||
structure = await getCurve(plugin, name, ingredient, getCurveTransforms(ingredient), model);
|
||||
} else {
|
||||
let bu: string|undefined = source.bu ? source.bu : undefined;
|
||||
if (bu){
|
||||
@@ -304,7 +318,7 @@ async function getIngredientStructure(assetManager: AssetManager, ingredient: In
|
||||
bu = bu.slice(2);
|
||||
}
|
||||
}
|
||||
structure = await getStructure(model, source, { assembly: bu });
|
||||
structure = await getStructure(plugin, model, source, { assembly: bu });
|
||||
// transform with offset and pcp
|
||||
let legacy: boolean = true;
|
||||
if (ingredient.offset || ingredient.principalAxis){
|
||||
@@ -336,14 +350,14 @@ async function getIngredientStructure(assetManager: AssetManager, ingredient: In
|
||||
return { structure, assets };
|
||||
}
|
||||
|
||||
export function createStructureFromCellPack(assetManager: AssetManager, packing: CellPacking, baseUrl: string, ingredientFiles: IngredientFiles) {
|
||||
export function createStructureFromCellPack(plugin: PluginContext, packing: CellPacking, baseUrl: string, ingredientFiles: IngredientFiles) {
|
||||
return Task.create('Create Packing Structure', async ctx => {
|
||||
const { ingredients, name } = packing;
|
||||
const assets: Asset.Wrapper[] = [];
|
||||
const structures: Structure[] = [];
|
||||
for (const iName in ingredients) {
|
||||
if (ctx.shouldUpdate) await ctx.update(iName);
|
||||
const ingredientStructure = await getIngredientStructure(assetManager, ingredients[iName], baseUrl, ingredientFiles);
|
||||
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles);
|
||||
if (ingredientStructure) {
|
||||
structures.push(ingredientStructure.structure);
|
||||
assets.push(...ingredientStructure.assets);
|
||||
|
||||
@@ -71,7 +71,7 @@ const StructureFromCellpack = PluginStateTransform.BuiltIn({
|
||||
ingredientFiles[file.name] = file;
|
||||
}
|
||||
}
|
||||
const { structure, assets } = await createStructureFromCellPack(plugin.managers.asset, packing, params.baseUrl, ingredientFiles).runInContext(ctx);
|
||||
const { structure, assets } = await createStructureFromCellPack(plugin, packing, params.baseUrl, ingredientFiles).runInContext(ctx);
|
||||
|
||||
await CellPackInfoProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, structure, {
|
||||
info: { packingsCount: a.data.packings.length, packingIndex: params.packing }
|
||||
|
||||
@@ -9,52 +9,56 @@ import { parsePDB } from '../../mol-io/reader/pdb/parser';
|
||||
import { AssetManager, Asset } from '../../mol-util/assets';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
|
||||
export async function parseCif(data: string|Uint8Array) {
|
||||
export async function parseCif(plugin: PluginContext, data: string | Uint8Array) {
|
||||
const comp = CIF.parse(data);
|
||||
const parsed = await comp.run();
|
||||
const parsed = await plugin.runTask(comp);
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
export async function parsePDBfile(data: string, id: string) {
|
||||
export async function parsePDBfile(plugin: PluginContext, data: string, id: string) {
|
||||
const comp = parsePDB(data, id);
|
||||
const parsed = await comp.run();
|
||||
const parsed = await plugin.runTask(comp);
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
async function downloadCif(url: string, isBinary: boolean, assetManager: AssetManager) {
|
||||
async function downloadCif(plugin: PluginContext, url: string, isBinary: boolean, assetManager: AssetManager) {
|
||||
const type = isBinary ? 'binary' : 'string';
|
||||
const asset = await assetManager.resolve(Asset.getUrlAsset(assetManager, url), type).run();
|
||||
return { cif: await parseCif(asset.data), asset };
|
||||
const asset = await plugin.runTask(assetManager.resolve(Asset.getUrlAsset(assetManager, url), type));
|
||||
return { cif: await parseCif(plugin, asset.data), asset };
|
||||
}
|
||||
|
||||
async function downloadPDB(url: string, id: string, assetManager: AssetManager) {
|
||||
async function downloadPDB(plugin: PluginContext, url: string, id: string, assetManager: AssetManager) {
|
||||
const asset = await assetManager.resolve(Asset.getUrlAsset(assetManager, url), 'string').run();
|
||||
return { pdb: await parsePDBfile(asset.data, id), asset };
|
||||
return { pdb: await parsePDBfile(plugin, asset.data, id), asset };
|
||||
}
|
||||
|
||||
export async function getFromPdb(pdbId: string, assetManager: AssetManager) {
|
||||
const { cif, asset } = await downloadCif(`https://models.rcsb.org/${pdbId.toUpperCase()}.bcif`, true, assetManager);
|
||||
export async function getFromPdb(plugin: PluginContext, pdbId: string, assetManager: AssetManager) {
|
||||
const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId.toUpperCase()}.bcif`, true, assetManager);
|
||||
return { mmcif: cif.blocks[0], asset };
|
||||
}
|
||||
|
||||
export async function getFromCellPackDB(id: string, baseUrl: string, assetManager: AssetManager) {
|
||||
export async function getFromOPM(plugin: PluginContext, pdbId: string, assetManager: AssetManager){
|
||||
const asset = await plugin.runTask(assetManager.resolve(Asset.getUrlAsset(assetManager, `https://opm-assets.storage.googleapis.com/pdb/${pdbId.toLowerCase()}.pdb`), 'string'));
|
||||
return { pdb: await parsePDBfile(plugin, asset.data, pdbId), asset };
|
||||
}
|
||||
|
||||
export async function getFromCellPackDB(plugin: PluginContext, id: string, baseUrl: string, assetManager: AssetManager) {
|
||||
if (id.toLowerCase().endsWith('.cif') || id.toLowerCase().endsWith('.bcif')) {
|
||||
const isBinary = id.toLowerCase().endsWith('.bcif');
|
||||
const { cif, asset } = await downloadCif(`${baseUrl}/other/${id}`, isBinary, assetManager);
|
||||
const { cif, asset } = await downloadCif(plugin, `${baseUrl}/other/${id}`, isBinary, assetManager);
|
||||
return { mmcif: cif.blocks[0], asset };
|
||||
} else {
|
||||
const name = id.endsWith('.pdb') ? id.substring(0, id.length - 4) : id;
|
||||
return await downloadPDB(`${baseUrl}/other/${name}.pdb`, name, assetManager);
|
||||
return await downloadPDB(plugin, `${baseUrl}/other/${name}.pdb`, name, assetManager);
|
||||
}
|
||||
}
|
||||
|
||||
export type IngredientFiles = { [name: string]: Asset.File }
|
||||
|
||||
//
|
||||
|
||||
export function getStructureMean(structure: Structure) {
|
||||
let xSum = 0, ySum = 0, zSum = 0;
|
||||
for (let i = 0, il = structure.units.length; i < il; ++i) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -16,7 +16,8 @@ import { StateAction, StateSelection } from '../../../mol-state';
|
||||
import { PluginStateObject } from '../../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { Check, Extension } from '@material-ui/icons';
|
||||
import Check from '@material-ui/icons/Check';
|
||||
import Extension from '@material-ui/icons/Extension';
|
||||
|
||||
interface AssemblySymmetryControlState extends CollapsableState {
|
||||
isBusy: boolean
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,9 @@ import { TransformData } from './transform-data';
|
||||
import { Theme } from '../../mol-theme/theme';
|
||||
import { RenderObjectValues } from '../../mol-gl/render-object';
|
||||
import { TextureMesh } from './texture-mesh/texture-mesh';
|
||||
import { Image } from './image/image';
|
||||
|
||||
export type GeometryKind = 'mesh' | 'points' | 'spheres' | 'text' | 'lines' | 'direct-volume' | 'texture-mesh'
|
||||
export type GeometryKind = 'mesh' | 'points' | 'spheres' | 'text' | 'lines' | 'direct-volume' | 'image' | 'texture-mesh'
|
||||
|
||||
export type Geometry<T extends GeometryKind = GeometryKind> =
|
||||
T extends 'mesh' ? Mesh :
|
||||
@@ -31,7 +32,8 @@ export type Geometry<T extends GeometryKind = GeometryKind> =
|
||||
T extends 'text' ? Text :
|
||||
T extends 'lines' ? Lines :
|
||||
T extends 'direct-volume' ? DirectVolume :
|
||||
T extends 'texture-mesh' ? TextureMesh : never
|
||||
T extends 'image' ? Image :
|
||||
T extends 'texture-mesh' ? TextureMesh : never
|
||||
|
||||
type GeometryParams<T extends GeometryKind> =
|
||||
T extends 'mesh' ? Mesh.Params :
|
||||
@@ -40,7 +42,8 @@ type GeometryParams<T extends GeometryKind> =
|
||||
T extends 'text' ? Text.Params :
|
||||
T extends 'lines' ? Lines.Params :
|
||||
T extends 'direct-volume' ? DirectVolume.Params :
|
||||
T extends 'texture-mesh' ? TextureMesh.Params : never
|
||||
T extends 'image' ? Image.Params :
|
||||
T extends 'texture-mesh' ? TextureMesh.Params : never
|
||||
|
||||
export interface GeometryUtils<G extends Geometry, P extends PD.Params = GeometryParams<G['kind']>, V = RenderObjectValues<G['kind']>> {
|
||||
Params: P
|
||||
@@ -64,6 +67,7 @@ export namespace Geometry {
|
||||
case 'text': return geometry.charCount * 2 * 3;
|
||||
case 'lines': return geometry.lineCount * 2 * 3;
|
||||
case 'direct-volume': return 12 * 3;
|
||||
case 'image': return 2 * 3;
|
||||
case 'texture-mesh': return geometry.vertexCount;
|
||||
}
|
||||
}
|
||||
@@ -78,6 +82,8 @@ export namespace Geometry {
|
||||
return getDrawCount(geometry) === 0 ? 0 : (arrayMax(geometry.groupBuffer.ref.value) + 1);
|
||||
case 'direct-volume':
|
||||
return 1;
|
||||
case 'image':
|
||||
return arrayMax(geometry.groupTexture.ref.value.array) + 1;
|
||||
case 'texture-mesh':
|
||||
return geometry.groupCount;
|
||||
}
|
||||
@@ -92,6 +98,7 @@ export namespace Geometry {
|
||||
case 'text': return Text.Utils as any;
|
||||
case 'lines': return Lines.Utils as any;
|
||||
case 'direct-volume': return DirectVolume.Utils as any;
|
||||
case 'image': return Image.Utils as any;
|
||||
case 'texture-mesh': return TextureMesh.Utils as any;
|
||||
}
|
||||
}
|
||||
|
||||
210
src/mol-geo/geometry/image/image.ts
Normal file
210
src/mol-geo/geometry/image/image.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { hashFnv32a } from '../../../mol-data/util';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { RenderableState } from '../../../mol-gl/renderable';
|
||||
import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere, TextureImage } from '../../../mol-gl/renderable/util';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { Vec2 } from '../../../mol-math/linear-algebra';
|
||||
import { Theme } from '../../../mol-theme/theme';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { BaseGeometry } from '../base';
|
||||
import { createColors } from '../color-data';
|
||||
import { GeometryUtils } from '../geometry';
|
||||
import { createMarkers } from '../marker-data';
|
||||
import { createEmptyOverpaint } from '../overpaint-data';
|
||||
import { TransformData } from '../transform-data';
|
||||
import { createEmptyTransparency } from '../transparency-data';
|
||||
import { ImageValues } from '../../../mol-gl/renderable/image';
|
||||
import { fillSerial } from '../../../mol-util/array';
|
||||
|
||||
const QuadIndices = new Uint32Array([
|
||||
0, 1, 2,
|
||||
1, 3, 2
|
||||
]);
|
||||
|
||||
const QuadUvs = new Float32Array([
|
||||
0, 1,
|
||||
0, 0,
|
||||
1, 1,
|
||||
1, 0
|
||||
]);
|
||||
|
||||
export const InterpolationTypes = {
|
||||
'nearest': 'Nearest',
|
||||
'catmulrom': 'Catmulrom (Cubic)',
|
||||
'mitchell': 'Mitchell (Cubic)',
|
||||
'bspline': 'B-Spline (Cubic)'
|
||||
};
|
||||
export type InterpolationTypes = keyof typeof InterpolationTypes;
|
||||
export const InterpolationTypeNames = Object.keys(InterpolationTypes) as InterpolationTypes[];
|
||||
|
||||
export { Image };
|
||||
|
||||
interface Image {
|
||||
readonly kind: 'image',
|
||||
|
||||
readonly imageTexture: ValueCell<TextureImage<Float32Array>>,
|
||||
readonly imageTextureDim: ValueCell<Vec2>,
|
||||
readonly cornerBuffer: ValueCell<Float32Array>,
|
||||
readonly groupTexture: ValueCell<TextureImage<Float32Array>>,
|
||||
|
||||
/** Bounding sphere of the image */
|
||||
boundingSphere: Sphere3D
|
||||
}
|
||||
|
||||
namespace Image {
|
||||
export function create(imageTexture: TextureImage<Float32Array>, corners: Float32Array, groupTexture: TextureImage<Float32Array>, image?: Image): Image {
|
||||
return image ?
|
||||
update(imageTexture, corners, groupTexture, image) :
|
||||
fromData(imageTexture, corners, groupTexture);
|
||||
}
|
||||
|
||||
function hashCode(image: Image) {
|
||||
return hashFnv32a([
|
||||
image.cornerBuffer.ref.version
|
||||
]);
|
||||
}
|
||||
|
||||
function fromData(imageTexture: TextureImage<Float32Array>, corners: Float32Array, groupTexture: TextureImage<Float32Array>): Image {
|
||||
const boundingSphere = Sphere3D();
|
||||
let currentHash = -1;
|
||||
|
||||
const width = imageTexture.width;
|
||||
const height = imageTexture.height;
|
||||
|
||||
const image = {
|
||||
kind: 'image' as const,
|
||||
imageTexture: ValueCell.create(imageTexture),
|
||||
imageTextureDim: ValueCell.create(Vec2.create(width, height)),
|
||||
cornerBuffer: ValueCell.create(corners),
|
||||
groupTexture: ValueCell.create(groupTexture),
|
||||
get boundingSphere() {
|
||||
const newHash = hashCode(image);
|
||||
if (newHash !== currentHash) {
|
||||
const b = getBoundingSphere(image.cornerBuffer.ref.value);
|
||||
Sphere3D.copy(boundingSphere, b);
|
||||
currentHash = newHash;
|
||||
}
|
||||
return boundingSphere;
|
||||
},
|
||||
};
|
||||
return image;
|
||||
}
|
||||
|
||||
function update(imageTexture: TextureImage<Float32Array>, corners: Float32Array, groupTexture: TextureImage<Float32Array>, image: Image): Image {
|
||||
const width = imageTexture.width;
|
||||
const height = imageTexture.height;
|
||||
|
||||
ValueCell.update(image.imageTexture, imageTexture);
|
||||
ValueCell.update(image.imageTextureDim, Vec2.set(image.imageTextureDim.ref.value, width, height));
|
||||
ValueCell.update(image.cornerBuffer, corners);
|
||||
ValueCell.update(image.groupTexture, groupTexture);
|
||||
return image;
|
||||
}
|
||||
|
||||
export function createEmpty(image?: Image): Image {
|
||||
return {} as Image; // TODO
|
||||
}
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
interpolation: PD.Select('bspline', PD.objectToOptions(InterpolationTypes)),
|
||||
};
|
||||
export type Params = typeof Params
|
||||
|
||||
export const Utils: GeometryUtils<Image, Params> = {
|
||||
Params,
|
||||
createEmpty,
|
||||
createValues,
|
||||
createValuesSimple,
|
||||
updateValues,
|
||||
updateBoundingSphere,
|
||||
createRenderableState,
|
||||
updateRenderableState
|
||||
};
|
||||
|
||||
function createValues(image: Image, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): ImageValues {
|
||||
|
||||
const { instanceCount, groupCount } = locationIt;
|
||||
const color = createColors(locationIt, theme.color);
|
||||
const marker = createMarkers(instanceCount * groupCount);
|
||||
const overpaint = createEmptyOverpaint();
|
||||
const transparency = createEmptyTransparency();
|
||||
|
||||
const counts = { drawCount: QuadIndices.length, groupCount, instanceCount };
|
||||
|
||||
const invariantBoundingSphere = Sphere3D.clone(image.boundingSphere);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
|
||||
|
||||
return {
|
||||
...color,
|
||||
...marker,
|
||||
...overpaint,
|
||||
...transparency,
|
||||
...transform,
|
||||
...BaseGeometry.createValues(props, counts),
|
||||
|
||||
aPosition: image.cornerBuffer,
|
||||
aUv: ValueCell.create(QuadUvs),
|
||||
elements: ValueCell.create(QuadIndices),
|
||||
|
||||
// aGroup is used as a vertex index here, group id is in tGroupTex
|
||||
aGroup: ValueCell.create(fillSerial(new Float32Array(4))),
|
||||
boundingSphere: ValueCell.create(boundingSphere),
|
||||
invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
|
||||
|
||||
dInterpolation: ValueCell.create(props.interpolation),
|
||||
|
||||
uImageTexDim: image.imageTextureDim,
|
||||
tImageTex: image.imageTexture,
|
||||
tGroupTex: image.groupTexture,
|
||||
};
|
||||
}
|
||||
|
||||
function createValuesSimple(image: Image, props: Partial<PD.Values<Params>>, colorValue: Color, sizeValue: number, transform?: TransformData) {
|
||||
const s = BaseGeometry.createSimple(colorValue, sizeValue, transform);
|
||||
const p = { ...PD.getDefaultValues(Params), ...props };
|
||||
return createValues(image, s.transform, s.locationIterator, s.theme, p);
|
||||
}
|
||||
|
||||
function updateValues(values: ImageValues, props: PD.Values<Params>) {
|
||||
BaseGeometry.updateValues(values, props);
|
||||
ValueCell.updateIfChanged(values.dInterpolation, props.interpolation);
|
||||
}
|
||||
|
||||
function updateBoundingSphere(values: ImageValues, image: Image) {
|
||||
const invariantBoundingSphere = Sphere3D.clone(image.boundingSphere);
|
||||
const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, values.aTransform.ref.value, values.instanceCount.ref.value);
|
||||
|
||||
if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {
|
||||
ValueCell.update(values.boundingSphere, boundingSphere);
|
||||
}
|
||||
if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) {
|
||||
ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere);
|
||||
}
|
||||
}
|
||||
|
||||
function createRenderableState(props: PD.Values<Params>): RenderableState {
|
||||
const state = BaseGeometry.createRenderableState(props);
|
||||
state.opaque = false;
|
||||
return state;
|
||||
}
|
||||
|
||||
function updateRenderableState(state: RenderableState, props: PD.Values<Params>) {
|
||||
BaseGeometry.updateRenderableState(state, props);
|
||||
state.opaque = false;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
function getBoundingSphere(corners: Float32Array) {
|
||||
return calculateInvariantBoundingSphere(corners, corners.length / 3, 1);
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -196,11 +196,16 @@ class MarchingCubesState {
|
||||
const n1y = sfg(sf, hi, Math.max(0, hj - 1), hk) - sfg(sf, hi, Math.min(this.nY - 1, hj + 1), hk);
|
||||
const n1z = sfg(sf, hi, hj, Math.max(0, hk - 1)) - sfg(sf, hi, hj, Math.min(this.nZ - 1, hk + 1));
|
||||
|
||||
this.builder.addNormal(
|
||||
n0x + t * (n0x - n1x),
|
||||
n0y + t * (n0y - n1y),
|
||||
n0z + t * (n0z - n1z)
|
||||
);
|
||||
const nx = n0x + t * (n0x - n1x);
|
||||
const ny = n0y + t * (n0y - n1y);
|
||||
const nz = n0z + t * (n0z - n1z);
|
||||
|
||||
// ensure normal-direction is the same for negative and positive iso-levels
|
||||
if (this.isoLevel >= 0) {
|
||||
this.builder.addNormal(nx, ny, nz);
|
||||
} else {
|
||||
this.builder.addNormal(-nx, -ny, -nz);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
@@ -255,7 +260,13 @@ class MarchingCubesState {
|
||||
|
||||
const triInfo = TriTable[tableIndex];
|
||||
for (let t = 0; t < triInfo.length; t += 3) {
|
||||
this.builder.addTriangle(this.vertList, triInfo[t], triInfo[t + 1], triInfo[t + 2], edgeFilter);
|
||||
const l = triInfo[t], m = triInfo[t + 1], n = triInfo[t + 2];
|
||||
// ensure winding-order is the same for negative and positive iso-levels
|
||||
if (this.isoLevel >= 0) {
|
||||
this.builder.addTriangle(this.vertList, l, m, n, edgeFilter);
|
||||
} else {
|
||||
this.builder.addTriangle(this.vertList, n, m, l, edgeFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,8 @@ function createPoints() {
|
||||
visible: true,
|
||||
alphaFactor: 1,
|
||||
pickable: true,
|
||||
opaque: true
|
||||
opaque: true,
|
||||
writeDepth: true
|
||||
};
|
||||
|
||||
return createRenderObject('points', values, state, -1);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { LinesValues, LinesRenderable } from './renderable/lines';
|
||||
import { SpheresValues, SpheresRenderable } from './renderable/spheres';
|
||||
import { TextValues, TextRenderable } from './renderable/text';
|
||||
import { TextureMeshValues, TextureMeshRenderable } from './renderable/texture-mesh';
|
||||
import { ImageValues, ImageRenderable } from './renderable/image';
|
||||
|
||||
const getNextId = idFactory(0, 0x7FFFFFFF);
|
||||
|
||||
@@ -27,7 +28,7 @@ export interface GraphicsRenderObject<T extends RenderObjectType = RenderObjectT
|
||||
readonly materialId: number
|
||||
}
|
||||
|
||||
export type RenderObjectType = 'mesh' | 'points' | 'spheres' | 'text' | 'lines' | 'direct-volume' | 'texture-mesh'
|
||||
export type RenderObjectType = 'mesh' | 'points' | 'spheres' | 'text' | 'lines' | 'direct-volume' | 'image' | 'texture-mesh'
|
||||
|
||||
export type RenderObjectValues<T extends RenderObjectType> =
|
||||
T extends 'mesh' ? MeshValues :
|
||||
@@ -36,7 +37,8 @@ export type RenderObjectValues<T extends RenderObjectType> =
|
||||
T extends 'text' ? TextValues :
|
||||
T extends 'lines' ? LinesValues :
|
||||
T extends 'direct-volume' ? DirectVolumeValues :
|
||||
T extends 'texture-mesh' ? TextureMeshValues : never
|
||||
T extends 'image' ? ImageValues :
|
||||
T extends 'texture-mesh' ? TextureMeshValues : never
|
||||
|
||||
//
|
||||
|
||||
@@ -52,6 +54,7 @@ export function createRenderable<T extends RenderObjectType>(ctx: WebGLContext,
|
||||
case 'text': return TextRenderable(ctx, o.id, o.values as TextValues, o.state, o.materialId);
|
||||
case 'lines': return LinesRenderable(ctx, o.id, o.values as LinesValues, o.state, o.materialId);
|
||||
case 'direct-volume': return DirectVolumeRenderable(ctx, o.id, o.values as DirectVolumeValues, o.state, o.materialId);
|
||||
case 'image': return ImageRenderable(ctx, o.id, o.values as ImageValues, o.state, o.materialId);
|
||||
case 'texture-mesh': return TextureMeshRenderable(ctx, o.id, o.values as TextureMeshValues, o.state, o.materialId);
|
||||
}
|
||||
throw new Error('unsupported type');
|
||||
|
||||
@@ -18,6 +18,7 @@ export type RenderableState = {
|
||||
alphaFactor: number
|
||||
pickable: boolean
|
||||
opaque: boolean
|
||||
writeDepth: boolean,
|
||||
}
|
||||
|
||||
export interface Renderable<T extends RenderableValues> {
|
||||
|
||||
41
src/mol-gl/renderable/image.ts
Normal file
41
src/mol-gl/renderable/image.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Renderable, RenderableState, createRenderable } from '../renderable';
|
||||
import { WebGLContext } from '../webgl/context';
|
||||
import { createGraphicsRenderItem } from '../webgl/render-item';
|
||||
import { AttributeSpec, Values, GlobalUniformSchema, InternalSchema, TextureSpec, ElementsSpec, DefineSpec, InternalValues, BaseSchema, UniformSpec } from './schema';
|
||||
import { ImageShaderCode } from '../shader-code';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { InterpolationTypeNames } from '../../mol-geo/geometry/image/image';
|
||||
|
||||
export const ImageSchema = {
|
||||
...BaseSchema,
|
||||
|
||||
aPosition: AttributeSpec('float32', 3, 0),
|
||||
aUv: AttributeSpec('float32', 2, 0),
|
||||
|
||||
elements: ElementsSpec('uint32'),
|
||||
|
||||
uImageTexDim: UniformSpec('v2'),
|
||||
tImageTex: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
|
||||
tGroupTex: TextureSpec('image-float32', 'alpha', 'float', 'nearest'),
|
||||
|
||||
dInterpolation: DefineSpec('string', InterpolationTypeNames),
|
||||
};
|
||||
export type ImageSchema = typeof ImageSchema
|
||||
export type ImageValues = Values<ImageSchema>
|
||||
|
||||
export function ImageRenderable(ctx: WebGLContext, id: number, values: ImageValues, state: RenderableState, materialId: number): Renderable<ImageValues> {
|
||||
const schema = { ...GlobalUniformSchema, ...InternalSchema, ...ImageSchema };
|
||||
const internalValues: InternalValues = {
|
||||
uObjectId: ValueCell.create(id),
|
||||
uPickable: ValueCell.create(state.pickable ? 1 : 0),
|
||||
};
|
||||
const shaderCode = ImageShaderCode;
|
||||
const renderItem = createGraphicsRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }, materialId);
|
||||
return createRenderable(renderItem, values, state);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -126,6 +126,10 @@ import direct_volume_vert from './shader/direct-volume.vert';
|
||||
import direct_volume_frag from './shader/direct-volume.frag';
|
||||
export const DirectVolumeShaderCode = ShaderCode('direct-volume', direct_volume_vert, direct_volume_frag, { fragDepth: true });
|
||||
|
||||
import image_vert from './shader/image.vert';
|
||||
import image_frag from './shader/image.frag';
|
||||
export const ImageShaderCode = ShaderCode('image', image_vert, image_frag, { fragDepth: true });
|
||||
|
||||
//
|
||||
|
||||
export type ShaderDefines = {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
export default `
|
||||
float depth = length(vViewPosition);
|
||||
float fogFactor = smoothstep(uFogNear, uFogFar, depth);
|
||||
float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
|
||||
if (uTransparentBackground == 0) {
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
|
||||
if (gl_FragColor.a < 1.0)
|
||||
gl_FragColor.a = fogAlpha;
|
||||
} else {
|
||||
float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
|
||||
gl_FragColor.a = fogAlpha;
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
export default `
|
||||
#if defined(dColorType_attribute)
|
||||
vColor.rgb = aColor;
|
||||
#elif defined(dColorType_instance)
|
||||
vColor.rgb = readFromTexture(tColor, aInstance, uColorTexDim).rgb;
|
||||
#elif defined(dColorType_group)
|
||||
vColor.rgb = readFromTexture(tColor, group, uColorTexDim).rgb;
|
||||
#elif defined(dColorType_groupInstance)
|
||||
vColor.rgb = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb;
|
||||
#elif defined(dColorType_objectPicking)
|
||||
vColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);
|
||||
#elif defined(dColorType_instancePicking)
|
||||
vColor = vec4(encodeFloatRGB(aInstance), 1.0);
|
||||
#elif defined(dColorType_groupPicking)
|
||||
vColor = vec4(encodeFloatRGB(group), 1.0);
|
||||
#endif
|
||||
#if defined(dRenderVariant_color)
|
||||
#if defined(dColorType_attribute)
|
||||
vColor.rgb = aColor;
|
||||
#elif defined(dColorType_instance)
|
||||
vColor.rgb = readFromTexture(tColor, aInstance, uColorTexDim).rgb;
|
||||
#elif defined(dColorType_group)
|
||||
vColor.rgb = readFromTexture(tColor, group, uColorTexDim).rgb;
|
||||
#elif defined(dColorType_groupInstance)
|
||||
vColor.rgb = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb;
|
||||
#endif
|
||||
|
||||
#ifdef dOverpaint
|
||||
vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
|
||||
#endif
|
||||
#ifdef dOverpaint
|
||||
vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
|
||||
#endif
|
||||
|
||||
#ifdef dTransparency
|
||||
vGroup = group;
|
||||
vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a;
|
||||
#ifdef dTransparency
|
||||
vGroup = group;
|
||||
vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a;
|
||||
#endif
|
||||
#elif defined(dRenderVariant_pick)
|
||||
#if defined(dRenderVariant_pickObject)
|
||||
vColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);
|
||||
#elif defined(dRenderVariant_pickInstance)
|
||||
vColor = vec4(encodeFloatRGB(aInstance), 1.0);
|
||||
#elif defined(dRenderVariant_pickGroup)
|
||||
vColor = vec4(encodeFloatRGB(group), 1.0);
|
||||
#endif
|
||||
#endif
|
||||
`;
|
||||
@@ -1,43 +1,45 @@
|
||||
export default `
|
||||
#if defined(dColorType_uniform)
|
||||
vec4 material = vec4(uColor, uAlpha);
|
||||
#elif defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance)
|
||||
vec4 material = vec4(vColor.rgb, uAlpha);
|
||||
#elif defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
|
||||
#if defined(dRenderVariant_color)
|
||||
#if defined(dColorType_uniform)
|
||||
vec4 material = vec4(uColor, uAlpha);
|
||||
#elif defined(dColorType_varying)
|
||||
vec4 material = vec4(vColor.rgb, uAlpha);
|
||||
#endif
|
||||
|
||||
// mix material with overpaint
|
||||
#if defined(dOverpaint)
|
||||
material.rgb = mix(material.rgb, vOverpaint.rgb, vOverpaint.a);
|
||||
#endif
|
||||
|
||||
// apply screendoor transparency
|
||||
#if defined(dTransparency)
|
||||
float ta = 1.0 - vTransparency;
|
||||
float at = 0.0;
|
||||
|
||||
// shift by view-offset during multi-sample rendering to allow for blending
|
||||
vec2 coord = gl_FragCoord.xy + uViewOffset * 0.25;
|
||||
|
||||
#if defined(dTransparencyVariant_single)
|
||||
const mat4 thresholdMatrix = mat4(
|
||||
1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0,
|
||||
13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0,
|
||||
4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0,
|
||||
16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0
|
||||
);
|
||||
at = thresholdMatrix[int(intMod(coord.x, 4.0))][int(intMod(coord.y, 4.0))];
|
||||
#elif defined(dTransparencyVariant_multi)
|
||||
at = fract(dot(vec3(coord, vGroup + 0.5), vec3(2.0, 7.0, 23.0) / 17.0f));
|
||||
#endif
|
||||
|
||||
if (ta < 0.99 && (ta < 0.01 || ta < at)) discard;
|
||||
#endif
|
||||
#elif defined(dRenderVariant_pick)
|
||||
vec4 material = uPickable == 1 ? vColor : vec4(0.0, 0.0, 0.0, 1.0); // set to empty picking id
|
||||
#elif defined(dColorType_depth)
|
||||
#elif defined(dRenderVariant_depth)
|
||||
#ifdef enabledFragDepth
|
||||
vec4 material = packDepthToRGBA(gl_FragDepthEXT);
|
||||
#else
|
||||
vec4 material = packDepthToRGBA(gl_FragCoord.z);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// mix material with overpaint
|
||||
#if defined(dOverpaint) && (defined(dColorType_uniform) || defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance))
|
||||
material.rgb = mix(material.rgb, vOverpaint.rgb, vOverpaint.a);
|
||||
#endif
|
||||
|
||||
// apply screendoor transparency
|
||||
#if defined(dTransparency) && (defined(dColorType_uniform) || defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance))
|
||||
float ta = 1.0 - vTransparency;
|
||||
float at = 0.0;
|
||||
|
||||
// shift by view-offset during multi-sample rendering to allow for blending
|
||||
vec2 coord = gl_FragCoord.xy + uViewOffset * 0.25;
|
||||
|
||||
#if defined(dTransparencyVariant_single)
|
||||
const mat4 thresholdMatrix = mat4(
|
||||
1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0,
|
||||
13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0,
|
||||
4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0,
|
||||
16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0
|
||||
);
|
||||
at = thresholdMatrix[int(intMod(coord.x, 4.0))][int(intMod(coord.y, 4.0))];
|
||||
#elif defined(dTransparencyVariant_multi)
|
||||
at = fract(dot(vec3(coord, vGroup + 0.5), vec3(2.0, 7.0, 23.0) / 17.0f));
|
||||
#endif
|
||||
|
||||
if (ta < 0.99 && (ta < 0.01 || ta < at)) discard;
|
||||
#endif
|
||||
`;
|
||||
@@ -1,22 +1,24 @@
|
||||
export default `
|
||||
#if defined(dColorType_uniform)
|
||||
uniform vec3 uColor;
|
||||
#elif defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance)
|
||||
varying vec4 vColor;
|
||||
#elif defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
|
||||
#if defined(dRenderVariant_color)
|
||||
#if defined(dColorType_uniform)
|
||||
uniform vec3 uColor;
|
||||
#elif defined(dColorType_varying)
|
||||
varying vec4 vColor;
|
||||
#endif
|
||||
|
||||
#ifdef dOverpaint
|
||||
varying vec4 vOverpaint;
|
||||
#endif
|
||||
|
||||
#ifdef dTransparency
|
||||
varying float vGroup;
|
||||
varying float vTransparency;
|
||||
#endif
|
||||
#elif defined(dRenderVariant_pick)
|
||||
#if __VERSION__ != 300
|
||||
varying vec4 vColor;
|
||||
#else
|
||||
flat in vec4 vColor;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef dOverpaint
|
||||
varying vec4 vOverpaint;
|
||||
#endif
|
||||
|
||||
#ifdef dTransparency
|
||||
varying float vGroup;
|
||||
varying float vTransparency;
|
||||
#endif
|
||||
`;
|
||||
@@ -1,31 +1,33 @@
|
||||
export default `
|
||||
#if defined(dColorType_uniform)
|
||||
uniform vec3 uColor;
|
||||
#elif defined(dColorType_attribute)
|
||||
varying vec4 vColor;
|
||||
attribute vec3 aColor;
|
||||
#elif defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance)
|
||||
varying vec4 vColor;
|
||||
uniform vec2 uColorTexDim;
|
||||
uniform sampler2D tColor;
|
||||
#elif defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
|
||||
#if defined(dRenderVariant_color)
|
||||
#if defined(dColorType_uniform)
|
||||
uniform vec3 uColor;
|
||||
#elif defined(dColorType_attribute)
|
||||
varying vec4 vColor;
|
||||
attribute vec3 aColor;
|
||||
#elif defined(dColorType_texture)
|
||||
varying vec4 vColor;
|
||||
uniform vec2 uColorTexDim;
|
||||
uniform sampler2D tColor;
|
||||
#endif
|
||||
|
||||
#ifdef dOverpaint
|
||||
varying vec4 vOverpaint;
|
||||
uniform vec2 uOverpaintTexDim;
|
||||
uniform sampler2D tOverpaint;
|
||||
#endif
|
||||
|
||||
#ifdef dTransparency
|
||||
varying float vGroup;
|
||||
varying float vTransparency;
|
||||
uniform vec2 uTransparencyTexDim;
|
||||
uniform sampler2D tTransparency;
|
||||
#endif
|
||||
#elif defined(dRenderVariant_pick)
|
||||
#if __VERSION__ != 300
|
||||
varying vec4 vColor;
|
||||
#else
|
||||
flat out vec4 vColor;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef dOverpaint
|
||||
varying vec4 vOverpaint;
|
||||
uniform vec2 uOverpaintTexDim;
|
||||
uniform sampler2D tOverpaint;
|
||||
#endif
|
||||
|
||||
#ifdef dTransparency
|
||||
varying float vGroup;
|
||||
varying float vTransparency;
|
||||
uniform vec2 uTransparencyTexDim;
|
||||
uniform sampler2D tTransparency;
|
||||
#endif
|
||||
`;
|
||||
@@ -1,4 +1,20 @@
|
||||
export default `
|
||||
// TODO find a better place for these convenience defines
|
||||
|
||||
#if defined(dRenderVariant_pickObject) || defined(dRenderVariant_pickInstance) || defined(dRenderVariant_pickGroup)
|
||||
#define dRenderVariant_pick
|
||||
#endif
|
||||
|
||||
#if defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance)
|
||||
#define dColorType_texture
|
||||
#endif
|
||||
|
||||
#if defined(dColorType_attribute) || defined(dColorType_texture)
|
||||
#define dColorType_varying
|
||||
#endif
|
||||
|
||||
//
|
||||
|
||||
#define PI 3.14159265
|
||||
#define RECIPROCAL_PI 0.31830988618
|
||||
#define EPSILON 1e-6
|
||||
|
||||
@@ -41,7 +41,7 @@ uniform int uPickable;
|
||||
|
||||
#if defined(dColorType_uniform)
|
||||
uniform vec3 uColor;
|
||||
#elif defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance)
|
||||
#elif defined(dColorType_varying)
|
||||
uniform vec2 uColorTexDim;
|
||||
uniform sampler2D tColor;
|
||||
#endif
|
||||
@@ -114,23 +114,23 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
|
||||
tmp = ((prevValue - uIsoValue) / ((prevValue - uIsoValue) - (value - uIsoValue)));
|
||||
isoPos = mix(pos - step, pos, tmp);
|
||||
|
||||
#if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
|
||||
#if defined(dRenderVariant_pick)
|
||||
if (uAlpha < uPickingAlphaThreshold)
|
||||
discard; // ignore so the element below can be picked
|
||||
if (uPickable == 0)
|
||||
return vec4(0.0, 0.0, 0.0, 1.0); // set to empty picking id
|
||||
#endif
|
||||
|
||||
#if defined(dColorType_objectPicking)
|
||||
#if defined(dRenderVariant_pickObject)
|
||||
return vec4(encodeFloatRGB(float(uObjectId)), 1.0);
|
||||
#elif defined(dColorType_instancePicking)
|
||||
#elif defined(dRenderVariant_pickInstance)
|
||||
return vec4(encodeFloatRGB(instance), 1.0);
|
||||
#elif defined(dColorType_groupPicking)
|
||||
#elif defined(dRenderVariant_pickGroup)
|
||||
float group = floor(decodeFloatRGB(textureGroup(isoPos).rgb) + 0.5);
|
||||
return vec4(encodeFloatRGB(group), 1.0);
|
||||
#elif defined(dColorType_depth)
|
||||
#elif defined(dRenderVariant_depth)
|
||||
return packDepthToRGBA(gl_FragCoord.z); // TODO calculate isosurface depth
|
||||
#else
|
||||
#elif defined(dRenderVariant_color)
|
||||
// compute gradient by central differences
|
||||
gradient.x = textureVal(isoPos - dx).a - textureVal(isoPos + dx).a;
|
||||
gradient.y = textureVal(isoPos - dy).a - textureVal(isoPos + dy).a;
|
||||
|
||||
136
src/mol-gl/shader/image.frag.ts
Normal file
136
src/mol-gl/shader/image.frag.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
|
||||
#include common
|
||||
#include read_from_texture
|
||||
#include common_frag_params
|
||||
|
||||
uniform vec2 uImageTexDim;
|
||||
uniform sampler2D tImageTex;
|
||||
uniform sampler2D tGroupTex;
|
||||
|
||||
uniform vec2 uMarkerTexDim;
|
||||
uniform sampler2D tMarker;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying float vInstance;
|
||||
|
||||
#if defined(dInterpolation_catmulrom) || defined(dInterpolation_mitchell) || defined(dInterpolation_bspline)
|
||||
#define dInterpolation_cubic
|
||||
#endif
|
||||
|
||||
#if defined(dInterpolation_cubic)
|
||||
#if defined(dInterpolation_catmulrom) || defined(dInterpolation_mitchell)
|
||||
#if defined(dInterpolation_catmulrom)
|
||||
const float B = 0.0;
|
||||
const float C = 0.5;
|
||||
#elif defined(dInterpolation_mitchell)
|
||||
const float B = 0.333;
|
||||
const float C = 0.333;
|
||||
#endif
|
||||
|
||||
float cubicFilter(float x){
|
||||
float f = x;
|
||||
if (f < 0.0) {
|
||||
f = -f;
|
||||
}
|
||||
if (f < 1.0) {
|
||||
return ((12.0 - 9.0 * B - 6.0 * C) * (f * f * f) +
|
||||
(-18.0 + 12.0 * B + 6.0 * C) * (f * f) +
|
||||
(6.0 - 2.0 * B)) / 6.0;
|
||||
}else if (f >= 1.0 && f < 2.0){
|
||||
return ((-B - 6.0 * C) * ( f * f * f)
|
||||
+ (6.0 * B + 30.0 * C) * (f * f) +
|
||||
(-(12.0 * B) - 48.0 * C) * f +
|
||||
8.0 * B + 24.0 * C) / 6.0;
|
||||
}else{
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
#elif defined(dInterpolation_bspline)
|
||||
float cubicFilter(float x) {
|
||||
float f = x;
|
||||
if (f < 0.0) {
|
||||
f = -f;
|
||||
}
|
||||
if (f >= 0.0 && f <= 1.0){
|
||||
return (2.0 / 3.0) + (0.5) * (f * f * f) - (f * f);
|
||||
} else if (f > 1.0 && f <= 2.0) {
|
||||
return 1.0 / 6.0 * pow((2.0 - f), 3.0);
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
vec4 biCubic(sampler2D tex, vec2 texCoord) {
|
||||
vec2 texelSize = 1.0 / uImageTexDim;
|
||||
texCoord -= texelSize / 2.0;
|
||||
vec4 nSum = vec4(0.0);
|
||||
float nDenom = 0.0;
|
||||
vec2 cell = fract(texCoord * uImageTexDim);
|
||||
for (float m = -1.0; m <= 2.0; ++m) {
|
||||
for (float n = -1.0; n <= 2.0; ++n) {
|
||||
vec4 vecData = texture2D(tex, texCoord + texelSize * vec2(m, n));
|
||||
float c = cubicFilter(m - cell.x) * cubicFilter(-n + cell.y);
|
||||
nSum += vecData * c;
|
||||
nDenom += c;
|
||||
}
|
||||
}
|
||||
return nSum / nDenom;
|
||||
}
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
#if defined(dInterpolation_cubic)
|
||||
vec4 imageData = biCubic(tImageTex, vUv);
|
||||
#else
|
||||
vec4 imageData = texture2D(tImageTex, vUv);
|
||||
#endif
|
||||
imageData.a = clamp(imageData.a, 0.0, 1.0);
|
||||
if (imageData.a > 0.9) imageData.a = 1.0;
|
||||
|
||||
#if defined(dRenderVariant_pick)
|
||||
if (imageData.a < 0.3)
|
||||
discard;
|
||||
|
||||
if (uPickable == 1) {
|
||||
#if defined(dRenderVariant_pickObject)
|
||||
gl_FragColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);
|
||||
#elif defined(dRenderVariant_pickInstance)
|
||||
gl_FragColor = vec4(encodeFloatRGB(vInstance), 1.0);
|
||||
#elif defined(dRenderVariant_pickGroup)
|
||||
float group = texture2D(tGroupTex, vUv).r;
|
||||
gl_FragColor = vec4(encodeFloatRGB(group), 1.0);
|
||||
#endif
|
||||
} else {
|
||||
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); // set to empty picking id
|
||||
}
|
||||
#elif defined(dRenderVariant_depth)
|
||||
if (imageData.a < 0.05)
|
||||
discard;
|
||||
|
||||
#ifdef enabledFragDepth
|
||||
gl_FragColor = packDepthToRGBA(gl_FragDepthEXT);
|
||||
#else
|
||||
gl_FragColor = packDepthToRGBA(gl_FragCoord.z);
|
||||
#endif
|
||||
#elif defined(dRenderVariant_color)
|
||||
if (imageData.a < 0.05)
|
||||
discard;
|
||||
|
||||
gl_FragColor = imageData;
|
||||
gl_FragColor.a *= uAlpha;
|
||||
|
||||
float group = texture2D(tGroupTex, vUv).r;
|
||||
float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#endif
|
||||
}
|
||||
`;
|
||||
27
src/mol-gl/shader/image.vert.ts
Normal file
27
src/mol-gl/shader/image.vert.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
export default `
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
|
||||
#include common
|
||||
#include common_vert_params
|
||||
|
||||
attribute vec3 aPosition;
|
||||
attribute vec2 aUv;
|
||||
attribute mat4 aTransform;
|
||||
attribute float aInstance;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying float vInstance;
|
||||
|
||||
void main() {
|
||||
#include assign_position
|
||||
|
||||
vUv = aUv;
|
||||
vInstance = aInstance;
|
||||
}
|
||||
`;
|
||||
@@ -15,12 +15,12 @@ precision highp int;
|
||||
void main(){
|
||||
#include assign_material_color
|
||||
|
||||
#if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
|
||||
#if defined(dRenderVariant_pick)
|
||||
#include check_picking_alpha
|
||||
gl_FragColor = material;
|
||||
#elif defined(dColorType_depth)
|
||||
#elif defined(dRenderVariant_depth)
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
#elif defined(dRenderVariant_color)
|
||||
gl_FragColor = material;
|
||||
|
||||
#include apply_marker_color
|
||||
|
||||
@@ -33,12 +33,12 @@ void main() {
|
||||
|
||||
#include assign_material_color
|
||||
|
||||
#if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
|
||||
#if defined(dRenderVariant_pick)
|
||||
#include check_picking_alpha
|
||||
gl_FragColor = material;
|
||||
#elif defined(dColorType_depth)
|
||||
#elif defined(dRenderVariant_depth)
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
#elif defined(dRenderVariant_color)
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
|
||||
@@ -22,12 +22,12 @@ const float radius = 0.5;
|
||||
void main(){
|
||||
#include assign_material_color
|
||||
|
||||
#if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
|
||||
#if defined(dRenderVariant_pick)
|
||||
#include check_picking_alpha
|
||||
gl_FragColor = material;
|
||||
#elif defined(dColorType_depth)
|
||||
#elif defined(dRenderVariant_depth)
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
#elif defined(dRenderVariant_color)
|
||||
gl_FragColor = material;
|
||||
|
||||
#ifdef dPointFilledCircle
|
||||
|
||||
@@ -58,7 +58,7 @@ bool Impostor(out vec3 cameraPos, out vec3 cameraNormal){
|
||||
|
||||
cameraPos = rayDirection * negT + rayOrigin;
|
||||
|
||||
|
||||
|
||||
if (calcDepth(cameraPos) <= 0.0) {
|
||||
cameraPos = rayDirection * posT + rayOrigin;
|
||||
interior = true;
|
||||
@@ -94,12 +94,12 @@ void main(void){
|
||||
|
||||
#include assign_material_color
|
||||
|
||||
#if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
|
||||
#if defined(dRenderVariant_pick)
|
||||
#include check_picking_alpha
|
||||
gl_FragColor = material;
|
||||
#elif defined(dColorType_depth)
|
||||
#elif defined(dRenderVariant_depth)
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
#elif defined(dRenderVariant_color)
|
||||
#ifdef dIgnoreLight
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
|
||||
@@ -56,11 +56,11 @@ void main(){
|
||||
gl_FragColor = material;
|
||||
}
|
||||
|
||||
#if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
|
||||
#if defined(dRenderVariant_pick)
|
||||
#include check_picking_alpha
|
||||
#elif defined(dColorType_depth)
|
||||
#elif defined(dRenderVariant_depth)
|
||||
gl_FragColor = material;
|
||||
#else
|
||||
#elif defined(dRenderVariant_color)
|
||||
#include apply_marker_color
|
||||
#include apply_fog
|
||||
#endif
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
import { createAttributeBuffers, ElementsBuffer, AttributeKind } from './buffer';
|
||||
import { createTextures, Texture } from './texture';
|
||||
import { WebGLContext, checkError } from './context';
|
||||
import { ShaderCode } from '../shader-code';
|
||||
import { ShaderCode, DefineValues } from '../shader-code';
|
||||
import { Program } from './program';
|
||||
import { RenderableSchema, RenderableValues, AttributeSpec, getValueVersions, splitValues, Values } from '../renderable/schema';
|
||||
import { RenderableSchema, RenderableValues, AttributeSpec, getValueVersions, splitValues, DefineSpec } from '../renderable/schema';
|
||||
import { idFactory } from '../../mol-util/id-factory';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { TextureImage, TextureVolume } from '../../mol-gl/renderable/util';
|
||||
@@ -46,21 +46,19 @@ export interface RenderItem<T extends string> {
|
||||
|
||||
//
|
||||
|
||||
const GraphicsRenderVariantDefines = {
|
||||
'color': {},
|
||||
'pickObject': { dColorType: ValueCell.create('objectPicking') },
|
||||
'pickInstance': { dColorType: ValueCell.create('instancePicking') },
|
||||
'pickGroup': { dColorType: ValueCell.create('groupPicking') },
|
||||
'depth': { dColorType: ValueCell.create('depth') }
|
||||
};
|
||||
export type GraphicsRenderVariant = keyof typeof GraphicsRenderVariantDefines
|
||||
const GraphicsRenderVariant = { 'color': '', 'pickObject': '', 'pickInstance': '', 'pickGroup': '', 'depth': '' };
|
||||
export type GraphicsRenderVariant = keyof typeof GraphicsRenderVariant
|
||||
const GraphicsRenderVariants = Object.keys(GraphicsRenderVariant) as GraphicsRenderVariant[];
|
||||
|
||||
const ComputeRenderVariantDefines = {
|
||||
'compute': {},
|
||||
};
|
||||
export type ComputeRenderVariant = keyof typeof ComputeRenderVariantDefines
|
||||
const ComputeRenderVariant = { 'compute': '' };
|
||||
export type ComputeRenderVariant = keyof typeof ComputeRenderVariant
|
||||
const ComputeRenderVariants = Object.keys(ComputeRenderVariant) as ComputeRenderVariant[];
|
||||
|
||||
type RenderVariantDefines = typeof GraphicsRenderVariantDefines | typeof ComputeRenderVariantDefines
|
||||
function createProgramVariant(ctx: WebGLContext, variant: string, defineValues: DefineValues, shaderCode: ShaderCode, schema: RenderableSchema) {
|
||||
defineValues = { ...defineValues, dRenderVariant: ValueCell.create(variant) };
|
||||
schema = { ...schema, dRenderVariant: DefineSpec('string') };
|
||||
return ctx.resources.program(defineValues, shaderCode, schema);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -90,14 +88,14 @@ function resetValueChanges(valueChanges: ValueChanges) {
|
||||
|
||||
//
|
||||
|
||||
export type GraphicsRenderItem = RenderItem<keyof typeof GraphicsRenderVariantDefines & string>
|
||||
export type GraphicsRenderItem = RenderItem<GraphicsRenderVariant>
|
||||
export function createGraphicsRenderItem(ctx: WebGLContext, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues, materialId: number) {
|
||||
return createRenderItem(ctx, drawMode, shaderCode, schema, values, materialId, GraphicsRenderVariantDefines);
|
||||
return createRenderItem(ctx, drawMode, shaderCode, schema, values, materialId, GraphicsRenderVariants);
|
||||
}
|
||||
|
||||
export type ComputeRenderItem = RenderItem<keyof typeof ComputeRenderVariantDefines & string>
|
||||
export type ComputeRenderItem = RenderItem<ComputeRenderVariant>
|
||||
export function createComputeRenderItem(ctx: WebGLContext, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues, materialId = -1) {
|
||||
return createRenderItem(ctx, drawMode, shaderCode, schema, values, materialId, ComputeRenderVariantDefines);
|
||||
return createRenderItem(ctx, drawMode, shaderCode, schema, values, materialId, ComputeRenderVariants);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +103,7 @@ export function createComputeRenderItem(ctx: WebGLContext, drawMode: DrawMode, s
|
||||
*
|
||||
* - assumes that `values.drawCount` and `values.instanceCount` exist
|
||||
*/
|
||||
export function createRenderItem<T extends RenderVariantDefines, S extends keyof T & string>(ctx: WebGLContext, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues, materialId: number, renderVariantDefines: T): RenderItem<S> {
|
||||
export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues, materialId: number, renderVariants: T[]): RenderItem<T> {
|
||||
const id = getNextRenderItemId();
|
||||
const { stats, state, resources } = ctx;
|
||||
const { instancedArrays, vertexArrayObject } = ctx.extensions;
|
||||
@@ -121,10 +119,9 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
|
||||
const glDrawMode = getDrawMode(ctx, drawMode);
|
||||
|
||||
const programs: ProgramVariants = {};
|
||||
Object.keys(renderVariantDefines).forEach(k => {
|
||||
const variantDefineValues: Values<RenderableSchema> = (renderVariantDefines as any)[k];
|
||||
programs[k] = resources.program({ ...defineValues, ...variantDefineValues }, shaderCode, schema);
|
||||
});
|
||||
for (const k of renderVariants) {
|
||||
programs[k] = createProgramVariant(ctx, k, defineValues, shaderCode, schema);
|
||||
}
|
||||
|
||||
const textures = createTextures(ctx, schema, textureValues);
|
||||
const attributeBuffers = createAttributeBuffers(ctx, schema, attributeValues);
|
||||
@@ -136,9 +133,9 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
|
||||
}
|
||||
|
||||
const vertexArrays: VertexArrayVariants = {};
|
||||
Object.keys(renderVariantDefines).forEach(k => {
|
||||
for (const k of renderVariants) {
|
||||
vertexArrays[k] = vertexArrayObject ? resources.vertexArray(programs[k], attributeBuffers, elementsBuffer) : null;
|
||||
});
|
||||
}
|
||||
|
||||
let drawCount = values.drawCount.ref.value;
|
||||
let instanceCount = values.instanceCount.ref.value;
|
||||
@@ -155,9 +152,9 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
|
||||
return {
|
||||
id,
|
||||
materialId,
|
||||
getProgram: (variant: S) => programs[variant],
|
||||
getProgram: (variant: T) => programs[variant],
|
||||
|
||||
render: (variant: S) => {
|
||||
render: (variant: T) => {
|
||||
if (drawCount === 0 || instanceCount === 0 || ctx.isContextLost) return;
|
||||
const program = programs[variant];
|
||||
if (program.id === currentProgramId && state.currentRenderItemId === id) {
|
||||
@@ -216,11 +213,10 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
|
||||
|
||||
if (valueChanges.defines) {
|
||||
// console.log('some defines changed, need to rebuild programs')
|
||||
Object.keys(renderVariantDefines).forEach(k => {
|
||||
const variantDefineValues: Values<RenderableSchema> = (renderVariantDefines as any)[k];
|
||||
for (const k of renderVariants) {
|
||||
programs[k].destroy();
|
||||
programs[k] = resources.program({ ...defineValues, ...variantDefineValues }, shaderCode, schema);
|
||||
});
|
||||
programs[k] = createProgramVariant(ctx, k, defineValues, shaderCode, schema);
|
||||
}
|
||||
}
|
||||
|
||||
if (values.drawCount.ref.version !== versions.drawCount) {
|
||||
@@ -271,11 +267,11 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
|
||||
|
||||
if (valueChanges.attributes || valueChanges.defines || valueChanges.elements) {
|
||||
// console.log('program/defines or buffers changed, update vaos')
|
||||
Object.keys(renderVariantDefines).forEach(k => {
|
||||
for (const k of renderVariants) {
|
||||
const vertexArray = vertexArrays[k];
|
||||
if (vertexArray) vertexArray.destroy();
|
||||
vertexArrays[k] = vertexArrayObject ? resources.vertexArray(programs[k], attributeBuffers, elementsBuffer) : null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, il = textures.length; i < il; ++i) {
|
||||
@@ -298,11 +294,11 @@ export function createRenderItem<T extends RenderVariantDefines, S extends keyof
|
||||
},
|
||||
destroy: () => {
|
||||
if (!destroyed) {
|
||||
Object.keys(renderVariantDefines).forEach(k => {
|
||||
for (const k of renderVariants) {
|
||||
programs[k].destroy();
|
||||
const vertexArray = vertexArrays[k];
|
||||
if (vertexArray) vertexArray.destroy();
|
||||
});
|
||||
}
|
||||
textures.forEach(([k, texture]) => {
|
||||
// lifetime of textures with kind 'texture' is defined externally
|
||||
if (schema[k].kind !== 'texture') {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -107,13 +107,22 @@ namespace Tokenizer {
|
||||
return read;
|
||||
}
|
||||
|
||||
/** Advance the state by the given number of lines and return line starts/ends as tokens. */
|
||||
export function readLines(state: Tokenizer, count: number): Tokens {
|
||||
/** Advance the state by the given number of lines and return them*/
|
||||
export function markLines(state: Tokenizer, count: number): Tokens {
|
||||
const lineTokens = TokenBuilder.create(state.data, count * 2);
|
||||
readLinesChunk(state, count, lineTokens);
|
||||
return lineTokens;
|
||||
}
|
||||
|
||||
/** Advance the state by the given number of lines and return them */
|
||||
export function readLines(state: Tokenizer, count: number): string[] {
|
||||
const ret: string[] = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
ret.push(Tokenizer.readLine(state));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Advance the state by the given number of lines and return line starts/ends as tokens. */
|
||||
export async function readLinesAsync(state: Tokenizer, count: number, ctx: RuntimeContext, initialLineCount = 100000): Promise<Tokens> {
|
||||
const { length } = state;
|
||||
|
||||
141
src/mol-io/reader/cube/parser.ts
Normal file
141
src/mol-io/reader/cube/parser.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Adapted from NGL.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { Tokenizer } from '../common/text/tokenizer';
|
||||
import { Column } from '../../../mol-data/db';
|
||||
import { Task, chunkedSubtask, RuntimeContext } from '../../../mol-task';
|
||||
import { ReaderResult as Result } from '../result';
|
||||
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
|
||||
}
|
||||
|
||||
export namespace CubeFile {
|
||||
export interface Header {
|
||||
orbitals: boolean,
|
||||
comment1: string,
|
||||
comment2: string,
|
||||
atomCount: number,
|
||||
origin: Vec3,
|
||||
dim: Vec3,
|
||||
basisX: Vec3,
|
||||
basisY: Vec3,
|
||||
basisZ: Vec3,
|
||||
dataSetIds: number[]
|
||||
}
|
||||
|
||||
export interface Atoms {
|
||||
count: number,
|
||||
number: Column<number>,
|
||||
nuclearCharge: Column<number>,
|
||||
x: Column<number>,
|
||||
y: Column<number>,
|
||||
z: Column<number>
|
||||
}
|
||||
}
|
||||
|
||||
const bohrToAngstromFactor = 0.529177210859;
|
||||
|
||||
function readHeader(tokenizer: Tokenizer) {
|
||||
const headerLines = Tokenizer.readLines(tokenizer, 6);
|
||||
const h = (k: number, l: number) => {
|
||||
const field = +headerLines[k].trim().split(/\s+/g)[ l ];
|
||||
return Number.isNaN(field) ? 0 : field;
|
||||
};
|
||||
const basis = (i: number) => {
|
||||
const n = h(i + 2, 0);
|
||||
const s = bohrToAngstromFactor;
|
||||
return [Math.abs(n), Vec3.create(h(i + 2, 1) * s, h(i + 2, 2) * s, h(i + 2, 3) * s), n] as const;
|
||||
};
|
||||
|
||||
const comment1 = headerLines[0].trim();
|
||||
const comment2 = headerLines[1].trim();
|
||||
|
||||
const [atomCount, origin, rawAtomCount] = basis(0);
|
||||
const [NVX, basisX] = basis(1);
|
||||
const [NVY, basisY] = basis(2);
|
||||
const [NVZ, basisZ] = basis(3);
|
||||
|
||||
const atoms = readAtoms(tokenizer, atomCount, bohrToAngstromFactor);
|
||||
|
||||
const dataSetIds: number[] = [];
|
||||
if (rawAtomCount >= 0) {
|
||||
let nVal = h(2, 4);
|
||||
if (nVal === 0) nVal = 1;
|
||||
for (let i = 0; i < nVal; i++) dataSetIds.push(i);
|
||||
} else {
|
||||
const counts = Tokenizer.readLine(tokenizer).trim().split(/\s+/g);
|
||||
for (let i = 0, _i = +counts[0]; i < _i; i++) dataSetIds.push(+counts[i + 1]);
|
||||
}
|
||||
|
||||
const header: CubeFile.Header = { orbitals: rawAtomCount < 0, comment1, comment2, atomCount, origin, dim: Vec3.create(NVX, NVY, NVZ), basisX, basisY, basisZ, dataSetIds };
|
||||
return { header, atoms };
|
||||
}
|
||||
|
||||
function readAtoms(tokenizer: Tokenizer, count: number, scaleFactor: number): CubeFile.Atoms {
|
||||
const number = new Int32Array(count);
|
||||
const value = new Float64Array(count);
|
||||
const x = new Float32Array(count);
|
||||
const y = new Float32Array(count);
|
||||
const z = new Float32Array(count);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const fields = Tokenizer.readLine(tokenizer).trim().split(/\s+/g);
|
||||
number[i] = +fields[0];
|
||||
value[i] = +fields[1];
|
||||
x[i] = +fields[2] * scaleFactor;
|
||||
y[i] = +fields[3] * scaleFactor;
|
||||
z[i] = +fields[4] * scaleFactor;
|
||||
}
|
||||
|
||||
return {
|
||||
count,
|
||||
number: Column.ofArray({ array: number, schema: Column.Schema.int }),
|
||||
nuclearCharge: Column.ofArray({ array: value, schema: Column.Schema.float }),
|
||||
x: Column.ofArray({ array: x, schema: Column.Schema.float }),
|
||||
y: Column.ofArray({ array: y, schema: Column.Schema.float }),
|
||||
z: Column.ofArray({ array: z, schema: Column.Schema.float })
|
||||
};
|
||||
}
|
||||
|
||||
function readValues(ctx: RuntimeContext, tokenizer: Tokenizer, header: CubeFile.Header) {
|
||||
const N = header.dim[0] * header.dim[1] * header.dim[2] * header.dataSetIds.length;
|
||||
const chunkSize = 100 * 100 * 100;
|
||||
const data = new Float64Array(N);
|
||||
let offset = 0;
|
||||
|
||||
return chunkedSubtask(ctx, chunkSize, data, (count, data) => {
|
||||
const max = Math.min(N, offset + count);
|
||||
for (let i = offset; i < max; i++) {
|
||||
Tokenizer.skipWhitespace(tokenizer);
|
||||
tokenizer.tokenStart = tokenizer.position;
|
||||
Tokenizer.eatValue(tokenizer);
|
||||
data[i] = fastParseFloat(tokenizer.data, tokenizer.tokenStart, tokenizer.tokenEnd);
|
||||
}
|
||||
offset = max;
|
||||
return max === N ? 0 : chunkSize;
|
||||
}, (ctx, _, i) => ctx.update({ current: Math.min(i, N), max: N }));
|
||||
}
|
||||
|
||||
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, 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
|
||||
}
|
||||
129
src/mol-io/reader/dx/parser.ts
Normal file
129
src/mol-io/reader/dx/parser.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Adapted from NGL.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { chunkedSubtask, RuntimeContext, Task } from '../../../mol-task';
|
||||
import { parseFloat as fastParseFloat } from '../common/text/number-parser';
|
||||
import { Tokenizer } from '../common/text/tokenizer';
|
||||
import { ReaderResult as Result } from '../result';
|
||||
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
|
||||
}
|
||||
|
||||
export namespace DxFile {
|
||||
export interface Header {
|
||||
dim: Vec3,
|
||||
min: Vec3,
|
||||
h: Vec3
|
||||
}
|
||||
}
|
||||
|
||||
function readHeader(tokenizer: Tokenizer): { header: DxFile.Header, headerByteCount: number } {
|
||||
const header: Partial<DxFile.Header> = { h: Vec3() };
|
||||
let headerByteCount = 0;
|
||||
let deltaLineCount = 0;
|
||||
|
||||
const reWhitespace = /\s+/g;
|
||||
|
||||
while (true) {
|
||||
const line = Tokenizer.readLine(tokenizer);
|
||||
let ls;
|
||||
|
||||
if (line.startsWith('object 1')) {
|
||||
ls = line.split(reWhitespace);
|
||||
header.dim = Vec3.create(parseInt(ls[5]), parseInt(ls[6]), parseInt(ls[7]));
|
||||
} else if (line.startsWith('origin')) {
|
||||
ls = line.split(reWhitespace);
|
||||
header.min = Vec3.create(parseFloat(ls[1]), parseFloat(ls[2]), parseFloat(ls[3]));
|
||||
} else if (line.startsWith('delta')) {
|
||||
ls = line.split(reWhitespace);
|
||||
|
||||
if (deltaLineCount === 0) {
|
||||
(header.h as any)[0] = parseFloat(ls[1]);
|
||||
} else if (deltaLineCount === 1) {
|
||||
(header.h as any)[1] = parseFloat(ls[2]);
|
||||
} else if (deltaLineCount === 2) {
|
||||
(header.h as any)[2] = parseFloat(ls[3]);
|
||||
}
|
||||
|
||||
deltaLineCount += 1;
|
||||
} else if (line.startsWith('object 3')) {
|
||||
headerByteCount += line.length + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
headerByteCount += line.length + 1;
|
||||
}
|
||||
|
||||
return { header: header as DxFile.Header, headerByteCount };
|
||||
}
|
||||
|
||||
function readValuesText(ctx: RuntimeContext, tokenizer: Tokenizer, header: DxFile.Header) {
|
||||
const N = header.dim[0] * header.dim[1] * header.dim[2];
|
||||
const chunkSize = 100 * 100 * 100;
|
||||
const data = new Float64Array(N);
|
||||
let offset = 0;
|
||||
|
||||
return chunkedSubtask(ctx, chunkSize, data, (count, data) => {
|
||||
const max = Math.min(N, offset + count);
|
||||
for (let i = offset; i < max; i++) {
|
||||
Tokenizer.skipWhitespace(tokenizer);
|
||||
tokenizer.tokenStart = tokenizer.position;
|
||||
Tokenizer.eatValue(tokenizer);
|
||||
data[i] = fastParseFloat(tokenizer.data, tokenizer.tokenStart, tokenizer.tokenEnd);
|
||||
}
|
||||
offset = max;
|
||||
return max === N ? 0 : chunkSize;
|
||||
}, (ctx, _, i) => ctx.update({ current: Math.min(i, N), max: N }));
|
||||
}
|
||||
|
||||
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, name });
|
||||
}
|
||||
|
||||
async function parseBinary(taskCtx: RuntimeContext, data: Uint8Array, name: string) {
|
||||
await taskCtx.update('Reading header...');
|
||||
|
||||
const headerString = utf8Read(data, 0, 1000);
|
||||
|
||||
const tokenizer = Tokenizer(headerString);
|
||||
const { header, headerByteCount } = readHeader(tokenizer);
|
||||
|
||||
await taskCtx.update('Reading values...');
|
||||
|
||||
const size = header.dim[0] * header.dim[1] * header.dim[2];
|
||||
const dv = new DataView(data.buffer, data.byteOffset + headerByteCount);
|
||||
const values = new Float64Array(size);
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
values[i] = dv.getFloat64(i * 8, true);
|
||||
}
|
||||
|
||||
// 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, name });
|
||||
}
|
||||
|
||||
export function parseDx(data: string | Uint8Array, name: string) {
|
||||
return Task.create<Result<DxFile>>('Parse Cube', taskCtx => {
|
||||
if (typeof data === 'string') return parseText(taskCtx, data, name);
|
||||
return parseBinary(taskCtx, data, name);
|
||||
});
|
||||
}
|
||||
@@ -108,6 +108,11 @@ export namespace Field {
|
||||
return this;
|
||||
}
|
||||
|
||||
add(field: Field<K, D>) {
|
||||
this.fields.push(field);
|
||||
return this;
|
||||
}
|
||||
|
||||
getFields() { return this.fields; }
|
||||
}
|
||||
|
||||
@@ -222,6 +227,8 @@ export namespace Category {
|
||||
}
|
||||
|
||||
export interface Encoder<T = string | Uint8Array> extends EncoderBase {
|
||||
readonly isBinary: boolean,
|
||||
|
||||
setFilter(filter?: Category.Filter): void,
|
||||
isCategoryIncluded(name: string): boolean,
|
||||
setFormatter(formatter?: Category.Formatter): void,
|
||||
|
||||
@@ -28,6 +28,8 @@ export default class BinaryEncoder implements Encoder<Uint8Array> {
|
||||
private filter: Category.Filter = Category.DefaultFilter;
|
||||
private formatter: Category.Formatter = Category.DefaultFormatter;
|
||||
|
||||
readonly isBinary = true;
|
||||
|
||||
binaryEncodingProvider: BinaryEncodingProvider | undefined = void 0;
|
||||
|
||||
setFilter(filter?: Category.Filter) {
|
||||
|
||||
@@ -19,6 +19,8 @@ export default class TextEncoder implements Encoder<string> {
|
||||
private filter: Category.Filter = Category.DefaultFilter;
|
||||
private formatter: Category.Formatter = Category.DefaultFormatter;
|
||||
|
||||
readonly isBinary = false;
|
||||
|
||||
binaryEncodingProvider = void 0;
|
||||
|
||||
setFilter(filter?: Category.Filter) {
|
||||
|
||||
@@ -164,6 +164,21 @@ namespace Mat4 {
|
||||
return a;
|
||||
}
|
||||
|
||||
export function fromBasis(a: Mat4, x: Vec3, y: Vec3, z: Vec3) {
|
||||
Mat4.setZero(a);
|
||||
Mat4.setValue(a, 0, 0, x[0]);
|
||||
Mat4.setValue(a, 1, 0, x[1]);
|
||||
Mat4.setValue(a, 2, 0, x[2]);
|
||||
Mat4.setValue(a, 0, 1, y[0]);
|
||||
Mat4.setValue(a, 1, 1, y[1]);
|
||||
Mat4.setValue(a, 2, 1, y[2]);
|
||||
Mat4.setValue(a, 0, 2, z[0]);
|
||||
Mat4.setValue(a, 1, 2, z[1]);
|
||||
Mat4.setValue(a, 2, 2, z[2]);
|
||||
Mat4.setValue(a, 3, 3, 1);
|
||||
return a;
|
||||
}
|
||||
|
||||
export function copy(out: Mat4, a: Mat4) {
|
||||
out[0] = a[0];
|
||||
out[1] = a[1];
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
83
src/mol-model-formats/structure/cube.ts
Normal file
83
src/mol-model-formats/structure/cube.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { Model } from '../../mol-model/structure/model';
|
||||
import { MoleculeType, getElementFromAtomicNumber, ElementSymbol } from '../../mol-model/structure/model/types';
|
||||
import { RuntimeContext, Task } from '../../mol-task';
|
||||
import { createModels } from './basic/parser';
|
||||
import { BasicSchema, createBasic } from './basic/schema';
|
||||
import { ComponentBuilder } from './common/component';
|
||||
import { EntityBuilder } from './common/entity';
|
||||
import { ModelFormat } from './format';
|
||||
import { CubeFile } from '../../mol-io/reader/cube/parser';
|
||||
|
||||
async function getModels(cube: CubeFile, ctx: RuntimeContext): Promise<Model[]> {
|
||||
const { atoms } = cube;
|
||||
|
||||
const MOL = Column.ofConst('MOL', cube.atoms.count, Column.Schema.str);
|
||||
const A = Column.ofConst('A', cube.atoms.count, Column.Schema.str);
|
||||
const type_symbol = Column.ofArray({ array: Column.mapToArray(atoms.number, n => getElementFromAtomicNumber(n)), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) });
|
||||
const seq_id = Column.ofConst(1, atoms.count, Column.Schema.int);
|
||||
|
||||
const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, {
|
||||
auth_asym_id: A,
|
||||
auth_atom_id: type_symbol,
|
||||
auth_comp_id: MOL,
|
||||
auth_seq_id: seq_id,
|
||||
Cartn_x: Column.asArrayColumn(atoms.x, Float32Array),
|
||||
Cartn_y: Column.asArrayColumn(atoms.y, Float32Array),
|
||||
Cartn_z: Column.asArrayColumn(atoms.z, Float32Array),
|
||||
id: Column.range(0, atoms.count - 1),
|
||||
|
||||
label_asym_id: A,
|
||||
label_atom_id: type_symbol,
|
||||
label_comp_id: MOL,
|
||||
label_seq_id: seq_id,
|
||||
label_entity_id: Column.ofConst('1', atoms.count, Column.Schema.str),
|
||||
|
||||
occupancy: Column.ofConst(1, atoms.count, Column.Schema.float),
|
||||
type_symbol,
|
||||
|
||||
pdbx_PDB_model_num: Column.ofConst(1, atoms.count, Column.Schema.int),
|
||||
}, atoms.count);
|
||||
|
||||
const entityBuilder = new EntityBuilder();
|
||||
entityBuilder.setNames([['MOL', 'Unknown Entity']]);
|
||||
entityBuilder.getEntityId('MOL', MoleculeType.Unknown, 'A');
|
||||
|
||||
const componentBuilder = new ComponentBuilder(seq_id, type_symbol);
|
||||
componentBuilder.setNames([['MOL', 'Unknown Molecule']]);
|
||||
componentBuilder.add('MOL', 0);
|
||||
|
||||
const basics = createBasic({
|
||||
entity: entityBuilder.getEntityTable(),
|
||||
chem_comp: componentBuilder.getChemCompTable(),
|
||||
atom_site
|
||||
});
|
||||
|
||||
return await createModels(basics, MolFormat.create(cube), ctx);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export { CubeFormat };
|
||||
|
||||
type CubeFormat = ModelFormat<CubeFile>
|
||||
|
||||
namespace MolFormat {
|
||||
export function is(x: ModelFormat): x is CubeFormat {
|
||||
return x.kind === 'cube';
|
||||
}
|
||||
|
||||
export function create(cube: CubeFile): CubeFormat {
|
||||
return { kind: 'cube', name: cube.header.comment1, data: cube };
|
||||
}
|
||||
}
|
||||
|
||||
export function trajectoryFromCube(cube: CubeFile): Task<Model.Trajectory> {
|
||||
return Task.create('Parse Cube', ctx => getModels(cube, ctx));
|
||||
}
|
||||
@@ -59,13 +59,13 @@ export function parseRemark350(lines: Tokens, lineStart: number, lineEnd: number
|
||||
const assemblies: PdbAssembly[] = [];
|
||||
|
||||
// Read the assemblies
|
||||
let current: PdbAssembly, group: PdbAssembly['groups'][0], matrix: Mat4, operId = 1;
|
||||
let current: PdbAssembly, group: PdbAssembly['groups'][0], matrix: Mat4, operId = 1, asmId = 1;
|
||||
const getLine = (n: number) => lines.data.substring(lines.indices[2 * n], lines.indices[2 * n + 1]);
|
||||
for (let i = lineStart; i < lineEnd; i++) {
|
||||
let line = getLine(i);
|
||||
if (line.substr(11, 12) === 'BIOMOLECULE:') {
|
||||
const id = line.substr(23).trim();
|
||||
let details: string = `Biomolecule ` + id;
|
||||
let details = `Biomolecule ${id}`;
|
||||
line = getLine(i + 1);
|
||||
if (line.substr(11, 30) !== 'APPLY THE FOLLOWING TO CHAINS:') {
|
||||
i++;
|
||||
@@ -100,6 +100,23 @@ export function parseRemark350(lines: Tokens, lineStart: number, lineEnd: number
|
||||
const c = chainList[j].trim();
|
||||
if (c) group!.chains.push(c);
|
||||
}
|
||||
} else if (line.substr(11, 33) === 'APPLYING THE FOLLOWING TO CHAINS:') {
|
||||
// variant in older PDB format version
|
||||
current = PdbAssembly(`${asmId}`, `Biomolecule ${asmId}`);
|
||||
assemblies.push(current);
|
||||
asmId += 1;
|
||||
|
||||
group = { chains: [], operators: [] };
|
||||
current!.groups.push(group);
|
||||
|
||||
i++;
|
||||
line = getLine(i);
|
||||
|
||||
const chainList = line.substr(11, 69).split(',');
|
||||
for (let j = 0, jl = chainList.length; j < jl; ++j) {
|
||||
const c = chainList[j].trim();
|
||||
if (c) group!.chains.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ function getTypedArrayCtor(header: Ccp4Header) {
|
||||
throw Error(`${valueType} is not a supported value format.`);
|
||||
}
|
||||
|
||||
export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, offset?: Vec3 }): Task<VolumeData> {
|
||||
export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, offset?: Vec3, label?: string }): Task<VolumeData> {
|
||||
return Task.create<VolumeData>('Create Volume Data', async ctx => {
|
||||
const { header, values } = source;
|
||||
const size = Vec3.create(header.xLength, header.yLength, header.zLength);
|
||||
@@ -67,8 +67,8 @@ export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, of
|
||||
// These, however, calculate sigma, so no data on that.
|
||||
|
||||
return {
|
||||
cell,
|
||||
fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)),
|
||||
label: params?.label,
|
||||
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
|
||||
data,
|
||||
dataStats: {
|
||||
min: isNaN(header.AMIN) ? arrayMin(values) : header.AMIN,
|
||||
|
||||
57
src/mol-model-formats/volume/cube.ts
Normal file
57
src/mol-model-formats/volume/cube.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { CubeFile } from '../../mol-io/reader/cube/parser';
|
||||
import { Mat4, Tensor } from '../../mol-math/linear-algebra';
|
||||
import { VolumeData } from '../../mol-model/volume/data';
|
||||
import { Task } from '../../mol-task';
|
||||
import { arrayMax, arrayMean, arrayMin, arrayRms } from '../../mol-util/array';
|
||||
|
||||
export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number, label?: string }): Task<VolumeData> {
|
||||
return Task.create<VolumeData>('Create Volume Data', async () => {
|
||||
const { header, values: sourceValues } = source;
|
||||
const space = Tensor.Space(header.dim, [0, 1, 2], Float64Array);
|
||||
|
||||
let values: Float64Array;
|
||||
if (header.dataSetIds.length === 0) {
|
||||
values = sourceValues;
|
||||
} else {
|
||||
// get every nth value from the source values
|
||||
const [h, k, l] = header.dim;
|
||||
const nth = (params?.dataIndex || 0) + 1;
|
||||
|
||||
let o = 0, s = 0;
|
||||
|
||||
values = new Float64Array(h * k * l);
|
||||
for (let u = 0; u < h; u++) {
|
||||
for (let v = 0; v < k; v++) {
|
||||
for (let w = 0; w < l; w++) {
|
||||
values[o++] = sourceValues[s];
|
||||
s += nth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const data = Tensor.create(space, Tensor.Data1(values));
|
||||
|
||||
const matrix = Mat4.fromTranslation(Mat4(), header.origin);
|
||||
const basis = Mat4.fromBasis(Mat4(), header.basisX, header.basisY, header.basisZ);
|
||||
Mat4.mul(matrix, matrix, basis);
|
||||
|
||||
return {
|
||||
label: params?.label,
|
||||
transform: { kind: 'matrix', matrix },
|
||||
data,
|
||||
dataStats: {
|
||||
min: arrayMin(values),
|
||||
max: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: arrayRms(values)
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -34,8 +34,7 @@ function volumeFromDensityServerData(source: DensityServer_Data_Database): Task<
|
||||
const dimensions = Vec3.ofArray(normalizeOrder(info.dimensions.value(0)));
|
||||
|
||||
return {
|
||||
cell,
|
||||
fractionalBox: Box3D.create(origin, Vec3.add(Vec3.zero(), origin, dimensions)),
|
||||
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin, Vec3.add(Vec3.zero(), origin, dimensions)) },
|
||||
data,
|
||||
dataStats: {
|
||||
min: info.min_sampled.value(0),
|
||||
|
||||
@@ -12,7 +12,7 @@ import { degToRad } from '../../mol-math/misc';
|
||||
import { Dsn6File } from '../../mol-io/reader/dsn6/schema';
|
||||
import { arrayMin, arrayMax, arrayMean, arrayRms } from '../../mol-util/array';
|
||||
|
||||
function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3 }): Task<VolumeData> {
|
||||
function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3, label?: string }): Task<VolumeData> {
|
||||
return Task.create<VolumeData>('Create Volume Data', async ctx => {
|
||||
const { header, values } = source;
|
||||
const size = Vec3.create(header.xlen, header.ylen, header.zlen);
|
||||
@@ -32,8 +32,8 @@ function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3 }): Task<V
|
||||
const data = Tensor.create(space, Tensor.Data1(values));
|
||||
|
||||
return {
|
||||
cell,
|
||||
fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)),
|
||||
label: params?.label,
|
||||
transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
|
||||
data,
|
||||
dataStats: {
|
||||
min: arrayMin(values),
|
||||
|
||||
34
src/mol-model-formats/volume/dx.ts
Normal file
34
src/mol-model-formats/volume/dx.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { DxFile } from '../../mol-io/reader/dx/parser';
|
||||
import { Mat4, Tensor } from '../../mol-math/linear-algebra';
|
||||
import { VolumeData } from '../../mol-model/volume/data';
|
||||
import { Task } from '../../mol-task';
|
||||
import { arrayMax, arrayMean, arrayMin, arrayRms } from '../../mol-util/array';
|
||||
|
||||
export function volumeFromDx(source: DxFile, params?: { label?: string }): Task<VolumeData> {
|
||||
return Task.create<VolumeData>('Create Volume Data', async () => {
|
||||
const { header, values } = source;
|
||||
const space = Tensor.Space(header.dim, [0, 1, 2], Float64Array);
|
||||
const data = Tensor.create(space, Tensor.Data1(values));
|
||||
const matrix = Mat4.fromTranslation(Mat4(), header.min);
|
||||
const basis = Mat4.fromScaling(Mat4(), header.h);
|
||||
Mat4.mul(matrix, matrix, basis);
|
||||
|
||||
return {
|
||||
label: params?.label,
|
||||
transform: { kind: 'matrix', matrix },
|
||||
data,
|
||||
dataStats: {
|
||||
min: arrayMin(values),
|
||||
max: arrayMax(values),
|
||||
mean: arrayMean(values),
|
||||
sigma: arrayRms(values)
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { shallowEqual } from '../mol-util';
|
||||
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';
|
||||
|
||||
/** A Loci that includes every loci */
|
||||
export const EveryLoci = { kind: 'every-loci' as 'every-loci' };
|
||||
@@ -62,7 +63,7 @@ export function DataLoci<T = unknown, E = unknown>(tag: string, data: T, element
|
||||
|
||||
export { Loci };
|
||||
|
||||
type Loci = StructureElement.Loci | Structure.Loci | Bond.Loci | EveryLoci | EmptyLoci | DataLoci | Shape.Loci | ShapeGroup.Loci
|
||||
type Loci = StructureElement.Loci | Structure.Loci | Bond.Loci | EveryLoci | EmptyLoci | DataLoci | Shape.Loci | ShapeGroup.Loci | Volume.Loci | Volume.Isosurface.Loci | Volume.Cell.Loci
|
||||
|
||||
namespace Loci {
|
||||
export interface Bundle<L extends number> { loci: FiniteArray<Loci, L> }
|
||||
@@ -98,6 +99,15 @@ namespace Loci {
|
||||
if (ShapeGroup.isLoci(lociA) && ShapeGroup.isLoci(lociB)) {
|
||||
return ShapeGroup.areLociEqual(lociA, lociB);
|
||||
}
|
||||
if (Volume.isLoci(lociA) && Volume.isLoci(lociB)) {
|
||||
return Volume.areLociEqual(lociA, lociB);
|
||||
}
|
||||
if (Volume.Isosurface.isLoci(lociA) && Volume.Isosurface.isLoci(lociB)) {
|
||||
return Volume.Isosurface.areLociEqual(lociA, lociB);
|
||||
}
|
||||
if (Volume.Cell.isLoci(lociA) && Volume.Cell.isLoci(lociB)) {
|
||||
return Volume.Cell.areLociEqual(lociA, lociB);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -114,6 +124,9 @@ namespace Loci {
|
||||
if (Bond.isLoci(loci)) return Bond.isLociEmpty(loci);
|
||||
if (Shape.isLoci(loci)) return Shape.isLociEmpty(loci);
|
||||
if (ShapeGroup.isLoci(loci)) return ShapeGroup.isLociEmpty(loci);
|
||||
if (Volume.isLoci(loci)) return Volume.isLociEmpty(loci);
|
||||
if (Volume.Isosurface.isLoci(loci)) return Volume.Isosurface.isLociEmpty(loci);
|
||||
if (Volume.Cell.isLoci(loci)) return Volume.Cell.isLociEmpty(loci);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -147,6 +160,12 @@ namespace Loci {
|
||||
return ShapeGroup.getBoundingSphere(loci, boundingSphere);
|
||||
} else if (loci.kind === 'data-loci') {
|
||||
return loci.getBoundingSphere(boundingSphere);
|
||||
} else if (loci.kind === 'volume-loci') {
|
||||
return Volume.getBoundingSphere(loci.volume, boundingSphere);
|
||||
} else if (loci.kind === 'isosurface-loci') {
|
||||
return Volume.Isosurface.getBoundingSphere(loci.volume, loci.isoValue, boundingSphere);
|
||||
} else if (loci.kind === 'cell-loci') {
|
||||
return Volume.Cell.getBoundingSphere(loci.volume, loci.indices, boundingSphere);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +194,15 @@ namespace Loci {
|
||||
} else if (loci.kind === 'data-loci') {
|
||||
// TODO maybe add loci.getPrincipalAxes()???
|
||||
return void 0;
|
||||
} else if (loci.kind === 'volume-loci') {
|
||||
// TODO
|
||||
return void 0;
|
||||
} else if (loci.kind === 'isosurface-loci') {
|
||||
// TODO
|
||||
return void 0;
|
||||
} else if (loci.kind === 'cell-loci') {
|
||||
// TODO
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ export namespace Shape {
|
||||
export function Loci(shape: Shape): Loci { return { kind: 'shape-loci', shape }; }
|
||||
export function isLoci(x: any): x is Loci { return !!x && x.kind === 'shape-loci'; }
|
||||
export function areLociEqual(a: Loci, b: Loci) { return a.shape === b.shape; }
|
||||
export function isLociEmpty(loci: Loci) { return loci.shape.groupCount === 0 ? true : false; }
|
||||
export function isLociEmpty(loci: Loci) { return loci.shape.groupCount === 0; }
|
||||
}
|
||||
|
||||
export namespace ShapeGroup {
|
||||
|
||||
@@ -6,18 +6,21 @@
|
||||
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { CifExportContext } from '../mmcif';
|
||||
import { StructureElement, StructureProperties as P } from '../../structure';
|
||||
import { StructureElement, StructureProperties as P, CifExportCategoryInfo } from '../../structure';
|
||||
import Unit from '../../structure/unit';
|
||||
import { Segmentation } from '../../../../mol-data/int';
|
||||
import { CifWriter } from '../../../../mol-io/writer/cif';
|
||||
import { Column } from '../../../../mol-data/db';
|
||||
|
||||
export function atom_site_operator_mapping(encoder: CifWriter.Encoder, ctx: CifExportContext) {
|
||||
|
||||
export function atom_site_operator_mapping(ctx: CifExportContext): CifExportCategoryInfo | undefined {
|
||||
const entries = getEntries(ctx);
|
||||
if (entries.length === 0) return;
|
||||
encoder.writeCategory(Category, entries, { ignoreFilter: true });
|
||||
return [Category, entries, { ignoreFilter: true }];
|
||||
}
|
||||
|
||||
export const AtomSiteOperatorMappingCategoryName = 'molstar_atom_site_operator_mapping';
|
||||
|
||||
export const AtomSiteOperatorMappingSchema = {
|
||||
molstar_atom_site_operator_mapping: {
|
||||
label_asym_id: Column.Schema.Str(),
|
||||
|
||||
@@ -14,6 +14,7 @@ import { sortArray } from '../../../../mol-data/util';
|
||||
import { CifWriter } from '../../../../mol-io/writer/cif';
|
||||
import { CifExportContext } from '../mmcif';
|
||||
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { CifCategory, CifField, getCifFieldType } from '../../../../mol-io/reader/cif';
|
||||
|
||||
export function getModelMmCifCategory<K extends keyof mmCIF_Schema>(model: Model, name: K): mmCIF_Database[K] | undefined {
|
||||
if (!MmcifFormat.is(model.sourceData)) return;
|
||||
@@ -58,4 +59,41 @@ export function copy_mmCif_category(name: keyof mmCIF_Schema, condition?: (struc
|
||||
return CifWriter.Category.ofTable(table);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function copy_source_mmCifCategory(encoder: CifWriter.Encoder, ctx: CifExportContext, category: CifCategory): CifWriter.Category<CifExportContext> | undefined {
|
||||
if (!MmcifFormat.is(ctx.firstModel.sourceData)) return;
|
||||
|
||||
const fs = CifWriter.fields<number, undefined>();
|
||||
if (encoder.isBinary) {
|
||||
for (const f of category.fieldNames) {
|
||||
// TODO: this could be optimized
|
||||
const field = classifyField(f, category.getField(f)!);
|
||||
fs.add(field);
|
||||
}
|
||||
} else {
|
||||
for (const f of category.fieldNames) {
|
||||
const field = category.getField(f)!;
|
||||
fs.str(f, row => field.str(row));
|
||||
}
|
||||
}
|
||||
|
||||
const fields = fs.getFields();
|
||||
return {
|
||||
name: category.name,
|
||||
instance() {
|
||||
return { fields, source: [{ data: void 0, rowCount: category.rowCount }] };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function classifyField(name: string, field: CifField): CifWriter.Field {
|
||||
const type = getCifFieldType(field);
|
||||
if (type['@type'] === 'str') {
|
||||
return { name, type: CifWriter.Field.Type.Str, value: field.str, valueKind: field.valueKind };
|
||||
} else if (type['@type'] === 'float') {
|
||||
return CifWriter.Field.float(name, field.float, { valueKind: field.valueKind, typedArray: Float64Array });
|
||||
} else {
|
||||
return CifWriter.Field.int(name, field.int, { valueKind: field.valueKind, typedArray: Int32Array });
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,11 @@ import CifCategory = CifWriter.Category
|
||||
import { _struct_conf, _struct_sheet_range } from './categories/secondary-structure';
|
||||
import { _chem_comp, _pdbx_chem_comp_identifier, _pdbx_nonpoly_scheme } from './categories/misc';
|
||||
import { Model } from '../model';
|
||||
import { getUniqueEntityIndicesFromStructures, copy_mmCif_category } from './categories/utils';
|
||||
import { getUniqueEntityIndicesFromStructures, copy_mmCif_category, copy_source_mmCifCategory } from './categories/utils';
|
||||
import { _struct_asym, _entity_poly, _entity_poly_seq } from './categories/sequence';
|
||||
import { CustomPropertyDescriptor } from '../common/custom-property';
|
||||
import { atom_site_operator_mapping } from './categories/atom_site_operator_mapping';
|
||||
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
|
||||
export interface CifExportContext {
|
||||
structures: Structure[],
|
||||
@@ -24,6 +25,10 @@ export interface CifExportContext {
|
||||
cache: any
|
||||
}
|
||||
|
||||
export type CifExportCategoryInfo =
|
||||
| [CifWriter.Category, any /** context */, CifWriter.Encoder.WriteCategoryOptions]
|
||||
| [CifWriter.Category, any /** context */]
|
||||
|
||||
export namespace CifExportContext {
|
||||
export function create(structures: Structure | Structure[]): CifExportContext {
|
||||
const structureArray = Array.isArray(structures) ? structures : [structures];
|
||||
@@ -99,8 +104,8 @@ export const mmCIF_Export_Filters = {
|
||||
}
|
||||
};
|
||||
|
||||
function encodeCustomProp(customProp: CustomPropertyDescriptor, ctx: CifExportContext, encoder: CifWriter.Encoder, params: encode_mmCIF_categories_Params) {
|
||||
if (!customProp.cifExport || customProp.cifExport.categories.length === 0) return;
|
||||
function getCustomPropCategories(customProp: CustomPropertyDescriptor, ctx: CifExportContext, params?: encode_mmCIF_categories_Params): CifExportCategoryInfo[] {
|
||||
if (!customProp.cifExport || customProp.cifExport.categories.length === 0) return [];
|
||||
|
||||
const prefix = customProp.cifExport.prefix;
|
||||
const cats = customProp.cifExport.categories;
|
||||
@@ -114,14 +119,21 @@ function encodeCustomProp(customProp: CustomPropertyDescriptor, ctx: CifExportCo
|
||||
ctx.cache[propId + '__ctx'] = propCtx;
|
||||
}
|
||||
}
|
||||
|
||||
const ret: CifExportCategoryInfo[] = [];
|
||||
for (const cat of cats) {
|
||||
if (params.skipCategoryNames && params.skipCategoryNames.has(cat.name)) continue;
|
||||
if (params?.skipCategoryNames?.has(cat.name)) continue;
|
||||
if (cat.name.indexOf(prefix) !== 0) throw new Error(`Custom category '${cat.name}' name must start with prefix '${prefix}.'`);
|
||||
encoder.writeCategory(cat, propCtx);
|
||||
ret.push([cat, propCtx]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
type encode_mmCIF_categories_Params = { skipCategoryNames?: Set<string>, exportCtx?: CifExportContext }
|
||||
type encode_mmCIF_categories_Params = {
|
||||
skipCategoryNames?: Set<string>,
|
||||
exportCtx?: CifExportContext,
|
||||
copyAllCategories?: boolean
|
||||
}
|
||||
|
||||
/** Doesn't start a data block */
|
||||
export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures: Structure | Structure[], params?: encode_mmCIF_categories_Params) {
|
||||
@@ -129,30 +141,94 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures:
|
||||
const models = first.models;
|
||||
if (models.length !== 1) throw 'Can\'t export stucture composed from multiple models.';
|
||||
|
||||
const _params = params || { };
|
||||
const ctx: CifExportContext = params && params.exportCtx ? params.exportCtx : CifExportContext.create(structures);
|
||||
const ctx: CifExportContext = params?.exportCtx || CifExportContext.create(structures);
|
||||
|
||||
if (params?.copyAllCategories && MmcifFormat.is(models[0].sourceData)) {
|
||||
encode_mmCIF_categories_copyAll(encoder, ctx);
|
||||
} else {
|
||||
encode_mmCIF_categories_default(encoder, ctx, params);
|
||||
}
|
||||
}
|
||||
|
||||
function encode_mmCIF_categories_default(encoder: CifWriter.Encoder, ctx: CifExportContext, params?: encode_mmCIF_categories_Params) {
|
||||
for (const cat of Categories) {
|
||||
if (_params.skipCategoryNames && _params.skipCategoryNames.has(cat.name)) continue;
|
||||
if (params?.skipCategoryNames && params?.skipCategoryNames.has(cat.name)) continue;
|
||||
encoder.writeCategory(cat, ctx);
|
||||
}
|
||||
|
||||
if ((!_params.skipCategoryNames || !_params.skipCategoryNames.has('atom_site')) && encoder.isCategoryIncluded('atom_site')) {
|
||||
atom_site_operator_mapping(encoder, ctx);
|
||||
if (!params?.skipCategoryNames?.has('atom_site') && encoder.isCategoryIncluded('atom_site')) {
|
||||
const info = atom_site_operator_mapping(ctx);
|
||||
if (info) encoder.writeCategory(info[0], info[1], info[2]);
|
||||
}
|
||||
|
||||
for (const customProp of models[0].customProperties.all) {
|
||||
encodeCustomProp(customProp, ctx, encoder, _params);
|
||||
const _params = params || { };
|
||||
for (const customProp of ctx.firstModel.customProperties.all) {
|
||||
for (const [cat, propCtx] of getCustomPropCategories(customProp, ctx, _params)) {
|
||||
encoder.writeCategory(cat, propCtx);
|
||||
}
|
||||
}
|
||||
|
||||
const structureCustomProps = new Set<CustomPropertyDescriptor>();
|
||||
for (const s of ctx.structures) {
|
||||
if (!s.hasCustomProperties) continue;
|
||||
for (const p of s.customPropertyDescriptors.all) structureCustomProps.add(p);
|
||||
for (const customProp of s.customPropertyDescriptors.all) {
|
||||
for (const [cat, propCtx] of getCustomPropCategories(customProp, ctx, _params)) {
|
||||
encoder.writeCategory(cat, propCtx);
|
||||
}
|
||||
}
|
||||
}
|
||||
structureCustomProps.forEach(customProp => encodeCustomProp(customProp, ctx, encoder, _params));
|
||||
}
|
||||
|
||||
function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExportContext) {
|
||||
const providedCategories = new Map<string, CifExportCategoryInfo>();
|
||||
|
||||
for (const cat of Categories) {
|
||||
providedCategories.set(cat.name, [cat, ctx]);
|
||||
}
|
||||
|
||||
const mapping = atom_site_operator_mapping(ctx);
|
||||
if (mapping) providedCategories.set(mapping[0].name, mapping);
|
||||
|
||||
for (const customProp of ctx.firstModel.customProperties.all) {
|
||||
for (const info of getCustomPropCategories(customProp, ctx)) {
|
||||
providedCategories.set(info[0].name, info);
|
||||
}
|
||||
}
|
||||
|
||||
for (const s of ctx.structures) {
|
||||
if (!s.hasCustomProperties) continue;
|
||||
for (const customProp of s.customPropertyDescriptors.all) {
|
||||
for (const info of getCustomPropCategories(customProp, ctx)) {
|
||||
providedCategories.set(info[0].name, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handled = new Set<string>();
|
||||
|
||||
const data = (ctx.firstModel.sourceData as MmcifFormat).data;
|
||||
for (const catName of data.frame.categoryNames) {
|
||||
handled.add(catName);
|
||||
|
||||
if (providedCategories.has(catName)) {
|
||||
const info = providedCategories.get(catName)!;
|
||||
encoder.writeCategory(info[0], info[1], info[2]);
|
||||
} else {
|
||||
if ((data.db as any)[catName]) {
|
||||
const cat = copy_mmCif_category(catName as any);
|
||||
encoder.writeCategory(cat, ctx);
|
||||
} else {
|
||||
const cat = copy_source_mmCifCategory(encoder, ctx, data.frame.categories[catName]);
|
||||
if (cat) encoder.writeCategory(cat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
providedCategories.forEach((info, name) => {
|
||||
if (!handled.has(name)) encoder.writeCategory(info[0], info[1], info[2]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function to_mmCIF(name: string, structure: Structure, asBinary = false) {
|
||||
const enc = CifWriter.createEncoder({ binary: asBinary });
|
||||
enc.startDataBlock(name);
|
||||
|
||||
@@ -25,6 +25,7 @@ import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
import { ChainIndex } from './indexing';
|
||||
import { SymmetryOperator } from '../../../mol-math/geometry';
|
||||
import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
|
||||
import { Column } from '../../../mol-data/db';
|
||||
|
||||
/**
|
||||
* Interface to the "source data" of the molecule.
|
||||
@@ -143,16 +144,19 @@ export namespace Model {
|
||||
|
||||
//
|
||||
|
||||
export function isFromPdbArchive(model: Model) {
|
||||
export function isFromPdbArchive(model: Model): boolean {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false;
|
||||
const { db } = model.sourceData.data;
|
||||
return (
|
||||
db.database_2.database_id.isDefined ||
|
||||
model.entryId.length === 4
|
||||
// 4 character PDB id
|
||||
model.entryId.match(/^[1-9][a-z0-9]{3,3}$/i) !== null ||
|
||||
// long PDB id
|
||||
model.entryId.match(/^pdb_[0-9]{4,4}[1-9][a-z0-9]{3,3}$/i) !== null
|
||||
);
|
||||
}
|
||||
|
||||
export function hasSecondaryStructure(model: Model) {
|
||||
export function hasSecondaryStructure(model: Model): boolean {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false;
|
||||
const { db } = model.sourceData.data;
|
||||
return (
|
||||
@@ -163,7 +167,7 @@ export namespace Model {
|
||||
|
||||
const tmpAngles90 = Vec3.create(1.5707963, 1.5707963, 1.5707963); // in radians
|
||||
const tmpLengths1 = Vec3.create(1, 1, 1);
|
||||
export function hasCrystalSymmetry(model: Model) {
|
||||
export function hasCrystalSymmetry(model: Model): boolean {
|
||||
const spacegroup = ModelSymmetry.Provider.get(model)?.spacegroup;
|
||||
return !!spacegroup && !(
|
||||
spacegroup.num === 1 &&
|
||||
@@ -172,7 +176,7 @@ export namespace Model {
|
||||
);
|
||||
}
|
||||
|
||||
export function isFromXray(model: Model) {
|
||||
export function isFromXray(model: Model): boolean {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false;
|
||||
const { db } = model.sourceData.data;
|
||||
for (let i = 0; i < db.exptl.method.rowCount; i++) {
|
||||
@@ -182,7 +186,7 @@ export namespace Model {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isFromEm(model: Model) {
|
||||
export function isFromEm(model: Model): boolean {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false;
|
||||
const { db } = model.sourceData.data;
|
||||
for (let i = 0; i < db.exptl.method.rowCount; i++) {
|
||||
@@ -192,7 +196,7 @@ export namespace Model {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isFromNmr(model: Model) {
|
||||
export function isFromNmr(model: Model): boolean {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false;
|
||||
const { db } = model.sourceData.data;
|
||||
for (let i = 0; i < db.exptl.method.rowCount; i++) {
|
||||
@@ -202,7 +206,7 @@ export namespace Model {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function hasXrayMap(model: Model) {
|
||||
export function hasXrayMap(model: Model): boolean {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false;
|
||||
// Check exprimental method to exclude models solved with
|
||||
// 'ELECTRON CRYSTALLOGRAPHY' which also have structure factors
|
||||
@@ -217,7 +221,7 @@ export namespace Model {
|
||||
* like 6TEK which are solved with 'X-RAY DIFFRACTION' but have an related
|
||||
* EMDB entry of type 'other EM volume'.
|
||||
*/
|
||||
export function hasEmMap(model: Model) {
|
||||
export function hasEmMap(model: Model): boolean {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false;
|
||||
const { db } = model.sourceData.data;
|
||||
const { db_name, content_type } = db.pdbx_database_related;
|
||||
@@ -229,7 +233,26 @@ export namespace Model {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function hasDensityMap(model: Model) {
|
||||
export function hasDensityMap(model: Model): boolean {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false;
|
||||
return hasXrayMap(model) || hasEmMap(model);
|
||||
}
|
||||
|
||||
export function probablyHasDensityMap(model: Model): boolean {
|
||||
if (!MmcifFormat.is(model.sourceData)) return false;
|
||||
const { db } = model.sourceData.data;
|
||||
return hasDensityMap(model) || (
|
||||
// check if from pdb archive but missing relevant meta data
|
||||
isFromPdbArchive(model) && (
|
||||
!db.exptl.method.isDefined ||
|
||||
(isFromXray(model) && (
|
||||
!db.pdbx_database_status.status_code_sf.isDefined ||
|
||||
db.pdbx_database_status.status_code_sf.valueKind(0) === Column.ValueKind.Unknown
|
||||
)) ||
|
||||
(isFromEm(model) && (
|
||||
!db.pdbx_database_related.db_name.isDefined
|
||||
))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,10 @@ export const enum Elements {
|
||||
H = 'H', D = 'D', T = 'T', HE = 'HE', LI = 'LI', BE = 'BE', B = 'B', C = 'C', N = 'N', O = 'O', F = 'F', NE = 'NE', NA = 'NA', MG = 'MG', AL = 'AL', SI = 'SI', P = 'P', S = 'S', CL = 'CL', AR = 'AR', K = 'K', CA = 'CA', SC = 'SC', TI = 'TI', V = 'V', CR = 'CR', MN = 'MN', FE = 'FE', CO = 'CO', NI = 'NI', CU = 'CU', ZN = 'ZN', GA = 'GA', GE = 'GE', AS = 'AS', SE = 'SE', BR = 'BR', KR = 'KR', RB = 'RB', SR = 'SR', Y = 'Y', ZR = 'ZR', NB = 'NB', MO = 'MO', TC = 'TC', RU = 'RU', RH = 'RH', PD = 'PD', AG = 'AG', CD = 'CD', IN = 'IN', SN = 'SN', SB = 'SB', TE = 'TE', I = 'I', XE = 'XE', CS = 'CS', BA = 'BA', LA = 'LA', CE = 'CE', PR = 'PR', ND = 'ND', PM = 'PM', SM = 'SM', EU = 'EU', GD = 'GD', TB = 'TB', DY = 'DY', HO = 'HO', ER = 'ER', TM = 'TM', YB = 'YB', LU = 'LU', HF = 'HF', TA = 'TA', W = 'W', RE = 'RE', OS = 'OS', IR = 'IR', PT = 'PT', AU = 'AU', HG = 'HG', TL = 'TL', PB = 'PB', BI = 'BI', PO = 'PO', AT = 'AT', RN = 'RN', FR = 'FR', RA = 'RA', AC = 'AC', TH = 'TH', PA = 'PA', U = 'U', NP = 'NP', PU = 'PU', AM = 'AM', CM = 'CM', BK = 'BK', CF = 'CF', ES = 'ES', FM = 'FM', MD = 'MD', NO = 'NO', LR = 'LR', RF = 'RF', DB = 'DB', SG = 'SG', BH = 'BH', HS = 'HS', MT = 'MT', DS = 'DS', RG = 'RG', CN = 'CN', NH = 'NH', FL = 'FL', MC = 'MC', LV = 'LV', TS = 'TS', OG = 'OG'
|
||||
}
|
||||
|
||||
export const ElementNames: { [k: string]: string } = {
|
||||
H: 'Hydrogen', HE: 'Helium', LI: 'Lithium', BE: 'Beryllium', B: 'Boron', C: 'Carbon', N: 'Nitrogen', O: 'Oxygen', F: 'Fluorine', NE: 'Neon', NA: 'Sodium', MG: 'Magnesium', AL: 'Aluminum', SI: 'Silicon', P: 'Phosphorus', S: 'Sulfur', CL: 'Chlorine', AR: 'Argon', K: 'Potassium', CA: 'Calcium', SC: 'Scandium', TI: 'Titanium', V: 'Vanadium', CR: 'Chromium', MN: 'Manganese', FE: 'Iron', CO: 'Cobalt', NI: 'Nickel', CU: 'Copper', ZN: 'Zinc', GA: 'Gallium', GE: 'Germanium', AS: 'Arsenic', SE: 'Selenium', BR: 'Bromine', KR: 'Krypton', RB: 'Rubidium', SR: 'Strontium', Y: 'Yttrium', ZR: 'Zirconium', NB: 'Niobium', MO: 'Molybdenum', TC: 'Technetium', RU: 'Ruthenium', RH: 'Rhodium', PD: 'Palladium', AG: 'Silver', CD: 'Cadmium', IN: 'Indium', SN: 'Tin', SB: 'Antimony', TE: 'Tellurium', I: 'Iodine', XE: 'Xenon', CS: 'Cesium', BA: 'Barium', LA: 'Lanthanum', CE: 'Cerium', PR: 'Praseodymium', ND: 'Neodymium', PM: 'Promethium', SM: 'Samarium', EU: 'Europium', GD: 'Gadolinium', TB: 'Terbium', DY: 'Dysprosium', HO: 'Holmium', ER: 'Erbium', TM: 'Thulium', YB: 'Ytterbium', LU: 'Lutetium', HF: 'Hafnium', TA: 'Tantalum', W: 'Wolfram', RE: 'Rhenium', OS: 'Osmium', IR: 'Iridium', PT: 'Platinum', AU: 'Gold', HG: 'Mercury', TL: 'Thallium', PB: 'Lead', BI: 'Bismuth', PO: 'Polonium', AT: 'Astatine', RN: 'Radon', FR: 'Francium', RA: 'Radium', AC: 'Actinium', TH: 'Thorium', PA: 'Protactinium', U: 'Uranium', NP: 'Neptunium', PU: 'Plutonium', AM: 'Americium', CM: 'Curium', BK: 'Berkelium', CF: 'Californium', ES: 'Einsteinium', FM: 'Fermium', MD: 'Mendelevium', NO: 'Nobelium', LR: 'Lawrencium', RF: 'Rutherfordium', DB: 'Dubnium', SG: 'Seaborgium', BH: 'Bohrium', HS: 'Hassium', MT: 'Meitnerium', DS: 'Darmstadtium', RG: 'Roentgenium', CN: 'Copernicium', NH: 'Nihonium', FL: 'Flerovium', MC: 'Moscovium', LV: 'Livermorium', TS: 'Tennessine', OG: 'Oganesson'
|
||||
};
|
||||
|
||||
export const AlkaliMetals = new Set<ElementSymbol>(['LI', 'NA', 'K', 'RB', 'CS', 'FR'] as ElementSymbol[]);
|
||||
export function isAlkaliMetal(element: ElementSymbol) { return AlkaliMetals.has(element); }
|
||||
|
||||
|
||||
@@ -34,6 +34,15 @@ export function ElementSymbol(s: string): ElementSymbol {
|
||||
return _esCache[s] || s.toUpperCase();
|
||||
}
|
||||
|
||||
const _elementByAtomicNumber = new Map(
|
||||
([[1, 'H'], [2, 'He'], [3, 'Li'], [4, 'Be'], [5, 'B'], [6, 'C'], [7, 'N'], [8, 'O'], [9, 'F'], [10, 'Ne'], [11, 'Na'], [12, 'Mg'], [13, 'Al'], [14, 'Si'], [15, 'P'], [16, 'S'], [17, 'Cl'], [18, 'Ar'], [19, 'K'], [20, 'Ca'], [21, 'Sc'], [22, 'Ti'], [23, 'V'], [24, 'Cr'], [25, 'Mn'], [26, 'Fe'], [27, 'Co'], [28, 'Ni'], [29, 'Cu'], [30, 'Zn'], [31, 'Ga'], [32, 'Ge'], [33, 'As'], [34, 'Se'], [35, 'Br'], [36, 'Kr'], [37, 'Rb'], [38, 'Sr'], [39, 'Y'], [40, 'Zr'], [41, 'Nb'], [42, 'Mo'], [43, 'Tc'], [44, 'Ru'], [45, 'Rh'], [46, 'Pd'], [47, 'Ag'], [48, 'Cd'], [49, 'In'], [50, 'Sn'], [51, 'Sb'], [52, 'Te'], [53, 'I'], [54, 'Xe'], [55, 'Cs'], [56, 'Ba'], [57, 'La'], [58, 'Ce'], [59, 'Pr'], [60, 'Nd'], [61, 'Pm'], [62, 'Sm'], [63, 'Eu'], [64, 'Gd'], [65, 'Tb'], [66, 'Dy'], [67, 'Ho'], [68, 'Er'], [69, 'Tm'], [70, 'Yb'], [71, 'Lu'], [72, 'Hf'], [73, 'Ta'], [74, 'W'], [75, 'Re'], [76, 'Os'], [77, 'Ir'], [78, 'Pt'], [79, 'Au'], [80, 'Hg'], [81, 'Tl'], [82, 'Pb'], [83, 'Bi'], [84, 'Po'], [85, 'At'], [86, 'Rn'], [87, 'Fr'], [88, 'Ra'], [89, 'Ac'], [90, 'Th'], [91, 'Pa'], [92, 'U'], [93, 'Np'], [94, 'Pu'], [95, 'Am'], [96, 'Cm'], [97, 'Bk'], [98, 'Cf'], [99, 'Es'], [100, 'Fm'], [101, 'Md'], [102, 'No'], [103, 'Lr'], [104, 'Rf'], [105, 'Db'], [106, 'Sg'], [107, 'Bh'], [108, 'Hs'], [109, 'Mt'], [110, 'Ds'], [111, 'Rg'], [112, 'Cn'], [113, 'Uut'], [114, 'Fl'], [115, 'Uup'], [116, 'Lv'], [117, 'Uus'], [118, 'Uuo']] as const)
|
||||
.map(e => [e[0], ElementSymbol(e[1])]));
|
||||
|
||||
export function getElementFromAtomicNumber(n: number) {
|
||||
if (_elementByAtomicNumber.has(n as any)) return _elementByAtomicNumber.get(n as any)!;
|
||||
return ElementSymbol('H');
|
||||
}
|
||||
|
||||
/** Entity types as defined in the mmCIF dictionary */
|
||||
export const enum EntityType {
|
||||
'unknown', 'polymer', 'non-polymer', 'macrolide', 'water', 'branched'
|
||||
@@ -256,9 +265,9 @@ export const DnaBaseNames = new Set([
|
||||
'DA', 'DC', 'DT', 'DG', 'DI', 'DU',
|
||||
'DN' // unknown DNA base from CCD
|
||||
]);
|
||||
export const PeptideBaseNames = new Set([ 'APN', 'CPN', 'TPN', 'GPN' ]);
|
||||
export const PurineBaseNames = new Set([ 'A', 'G', 'I', 'DA', 'DG', 'DI', 'APN', 'GPN' ]);
|
||||
export const PyrimidineBaseNames = new Set([ 'C', 'T', 'U', 'DC', 'DT', 'DU', 'CPN', 'TPN' ]);
|
||||
export const PeptideBaseNames = new Set(['APN', 'CPN', 'TPN', 'GPN']);
|
||||
export const PurineBaseNames = new Set(['A', 'G', 'I', 'DA', 'DG', 'DI', 'APN', 'GPN']);
|
||||
export const PyrimidineBaseNames = new Set(['C', 'T', 'U', 'DC', 'DT', 'DU', 'CPN', 'TPN']);
|
||||
export const BaseNames = SetUtils.unionMany(RnaBaseNames, DnaBaseNames, PeptideBaseNames);
|
||||
|
||||
export const isPurineBase = (compId: string) => PurineBaseNames.has(compId.toUpperCase());
|
||||
@@ -596,13 +605,13 @@ export type BondType = BitFlags<BondType.Flag>
|
||||
export namespace BondType {
|
||||
export const is: (b: BondType, f: Flag) => boolean = BitFlags.has;
|
||||
export const enum Flag {
|
||||
None = 0x0,
|
||||
Covalent = 0x1,
|
||||
None = 0x0,
|
||||
Covalent = 0x1,
|
||||
MetallicCoordination = 0x2,
|
||||
HydrogenBond = 0x4,
|
||||
Disulfide = 0x8,
|
||||
Aromatic = 0x10,
|
||||
Computed = 0x20
|
||||
HydrogenBond = 0x4,
|
||||
Disulfide = 0x8,
|
||||
Aromatic = 0x10,
|
||||
Computed = 0x20
|
||||
// currently at most 16 flags are supported!!
|
||||
}
|
||||
|
||||
@@ -656,28 +665,28 @@ export namespace BondType {
|
||||
*/
|
||||
export const ResidueHydrophobicity = {
|
||||
// AA DGwif DGwoct Oct-IF
|
||||
'ALA': [ 0.17, 0.50, 0.33 ],
|
||||
'ARG': [ 0.81, 1.81, 1.00 ],
|
||||
'ASN': [ 0.42, 0.85, 0.43 ],
|
||||
'ASP': [ 1.23, 3.64, 2.41 ],
|
||||
'ASH': [ -0.07, 0.43, 0.50 ],
|
||||
'CYS': [ -0.24, -0.02, 0.22 ],
|
||||
'GLN': [ 0.58, 0.77, 0.19 ],
|
||||
'GLU': [ 2.02, 3.63, 1.61 ],
|
||||
'GLH': [ -0.01, 0.11, 0.12 ],
|
||||
'GLY': [ 0.01, 1.15, 1.14 ],
|
||||
'ALA': [0.17, 0.50, 0.33],
|
||||
'ARG': [0.81, 1.81, 1.00],
|
||||
'ASN': [0.42, 0.85, 0.43],
|
||||
'ASP': [1.23, 3.64, 2.41],
|
||||
'ASH': [-0.07, 0.43, 0.50],
|
||||
'CYS': [-0.24, -0.02, 0.22],
|
||||
'GLN': [0.58, 0.77, 0.19],
|
||||
'GLU': [2.02, 3.63, 1.61],
|
||||
'GLH': [-0.01, 0.11, 0.12],
|
||||
'GLY': [0.01, 1.15, 1.14],
|
||||
// "His+": [ 0.96, 2.33, 1.37 ],
|
||||
'HIS': [ 0.17, 0.11, -0.06 ],
|
||||
'ILE': [ -0.31, -1.12, -0.81 ],
|
||||
'LEU': [ -0.56, -1.25, -0.69 ],
|
||||
'LYS': [ 0.99, 2.80, 1.81 ],
|
||||
'MET': [ -0.23, -0.67, -0.44 ],
|
||||
'PHE': [ -1.13, -1.71, -0.58 ],
|
||||
'PRO': [ 0.45, 0.14, -0.31 ],
|
||||
'SER': [ 0.13, 0.46, 0.33 ],
|
||||
'THR': [ 0.14, 0.25, 0.11 ],
|
||||
'TRP': [ -1.85, -2.09, -0.24 ],
|
||||
'TYR': [ -0.94, -0.71, 0.23 ],
|
||||
'VAL': [ 0.07, -0.46, -0.53 ]
|
||||
'HIS': [0.17, 0.11, -0.06],
|
||||
'ILE': [-0.31, -1.12, -0.81],
|
||||
'LEU': [-0.56, -1.25, -0.69],
|
||||
'LYS': [0.99, 2.80, 1.81],
|
||||
'MET': [-0.23, -0.67, -0.44],
|
||||
'PHE': [-1.13, -1.71, -0.58],
|
||||
'PRO': [0.45, 0.14, -0.31],
|
||||
'SER': [0.13, 0.46, 0.33],
|
||||
'THR': [0.14, 0.25, 0.11],
|
||||
'TRP': [-1.85, -2.09, -0.24],
|
||||
'TYR': [-0.94, -0.71, 0.23],
|
||||
'VAL': [0.07, -0.46, -0.53]
|
||||
};
|
||||
export const DefaultResidueHydrophobicity = [ 0.00, 0.00, 0.00 ];
|
||||
export const DefaultResidueHydrophobicity = [0.00, 0.00, 0.00];
|
||||
@@ -40,6 +40,22 @@ function buildUnion(this: StructureSubsetBuilder, elements: StructureElement.Set
|
||||
this.setUnit(id, elements);
|
||||
}
|
||||
|
||||
export function structureAreEqual(sA: Structure, sB: Structure): boolean {
|
||||
if (sA === sB) return true;
|
||||
|
||||
if (sA.units.length !== sB.units.length) return false;
|
||||
|
||||
const aU = sA.units, bU = sB.unitMap;
|
||||
for (let i = 0, _i = aU.length; i < _i; i++) {
|
||||
const u = aU[i];
|
||||
if (!bU.has(u.id)) return false;
|
||||
const v = bU.get(u.id);
|
||||
if (!SortedArray.areEqual(u.elements, v.elements)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function structureAreIntersecting(sA: Structure, sB: Structure): boolean {
|
||||
if (sA === sB) return true;
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import { CustomProperties } from '../common/custom-property';
|
||||
import { AtomicHierarchy } from '../model/properties/atomic';
|
||||
import { StructureSelection } from '../query/selection';
|
||||
import { getBoundary } from '../../../mol-math/geometry/boundary';
|
||||
import { ElementSymbol } from '../model/types';
|
||||
|
||||
class Structure {
|
||||
/** Maps unit.id to unit */
|
||||
@@ -50,6 +51,7 @@ class Structure {
|
||||
masterModel?: Model,
|
||||
representativeModel?: Model,
|
||||
uniqueResidueNames?: Set<string>,
|
||||
uniqueElementSymbols?: Set<ElementSymbol>,
|
||||
entityIndices?: ReadonlyArray<EntityIndex>,
|
||||
uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
|
||||
serialMapping?: SerialMapping,
|
||||
@@ -265,6 +267,11 @@ class Structure {
|
||||
|| (this._props.uniqueResidueNames = getUniqueResidueNames(this));
|
||||
}
|
||||
|
||||
get uniqueElementSymbols() {
|
||||
return this._props.uniqueElementSymbols
|
||||
|| (this._props.uniqueElementSymbols = getUniqueElementSymbols(this));
|
||||
}
|
||||
|
||||
get entityIndices() {
|
||||
return this._props.entityIndices
|
||||
|| (this._props.entityIndices = getEntityIndices(this));
|
||||
@@ -403,7 +410,8 @@ function getUniqueResidueNames(s: Structure) {
|
||||
const prop = StructureProperties.residue.label_comp_id;
|
||||
const names = new Set<string>();
|
||||
const loc = StructureElement.Location.create(s);
|
||||
for (const unit of s.units) {
|
||||
for (const unitGroup of s.unitSymmetryGroups) {
|
||||
const unit = unitGroup.units[0];
|
||||
// TODO: support coarse unit?
|
||||
if (!Unit.isAtomic(unit)) continue;
|
||||
const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
|
||||
@@ -417,6 +425,22 @@ function getUniqueResidueNames(s: Structure) {
|
||||
return names;
|
||||
}
|
||||
|
||||
function getUniqueElementSymbols(s: Structure) {
|
||||
const prop = StructureProperties.atom.type_symbol;
|
||||
const symbols = new Set<ElementSymbol>();
|
||||
const loc = StructureElement.Location.create(s);
|
||||
for (const unitGroup of s.unitSymmetryGroups) {
|
||||
const unit = unitGroup.units[0];
|
||||
if (!Unit.isAtomic(unit)) continue;
|
||||
loc.unit = unit;
|
||||
for (let i = 0, il = unit.elements.length; i < il; ++i) {
|
||||
loc.element = unit.elements[i];
|
||||
symbols.add(prop(loc));
|
||||
}
|
||||
}
|
||||
return symbols;
|
||||
}
|
||||
|
||||
function getEntityIndices(structure: Structure): ReadonlyArray<EntityIndex> {
|
||||
const { units } = structure;
|
||||
const l = StructureElement.Location.create(structure);
|
||||
|
||||
@@ -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>
|
||||
@@ -10,9 +10,9 @@ import { Tensor, Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
|
||||
|
||||
/** The basic unit cell that contains the data. */
|
||||
interface VolumeData {
|
||||
readonly cell: SpacegroupCell,
|
||||
readonly fractionalBox: Box3D,
|
||||
interface VolumeDataBase {
|
||||
readonly label?: string,
|
||||
readonly transform: { kind: 'spacegroup', cell: SpacegroupCell, fractionalBox: Box3D } | { kind: 'matrix', matrix: Mat4 },
|
||||
readonly data: Tensor,
|
||||
readonly dataStats: Readonly<{
|
||||
min: number,
|
||||
@@ -22,20 +22,31 @@ interface VolumeData {
|
||||
}>
|
||||
}
|
||||
|
||||
interface VolumeData extends VolumeDataBase {
|
||||
readonly colorVolume?: VolumeDataBase
|
||||
}
|
||||
|
||||
namespace VolumeData {
|
||||
export const One: VolumeData = {
|
||||
cell: SpacegroupCell.Zero,
|
||||
fractionalBox: Box3D.empty(),
|
||||
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 }
|
||||
};
|
||||
|
||||
const _scale = Mat4.zero(), _translate = Mat4.zero();
|
||||
export function getGridToCartesianTransform(volume: VolumeData) {
|
||||
const { data: { space } } = volume;
|
||||
const scale = Mat4.fromScaling(_scale, Vec3.div(Vec3.zero(), Box3D.size(Vec3.zero(), volume.fractionalBox), Vec3.ofArray(space.dimensions)));
|
||||
const translate = Mat4.fromTranslation(_translate, volume.fractionalBox.min);
|
||||
return Mat4.mul3(Mat4.zero(), volume.cell.fromFractional, translate, scale);
|
||||
if (volume.transform.kind === 'matrix') {
|
||||
return Mat4.copy(Mat4(), volume.transform.matrix);
|
||||
}
|
||||
|
||||
if (volume.transform.kind === 'spacegroup') {
|
||||
const { data: { space } } = volume;
|
||||
const scale = Mat4.fromScaling(_scale, Vec3.div(Vec3.zero(), Box3D.size(Vec3.zero(), volume.transform.fractionalBox), Vec3.ofArray(space.dimensions)));
|
||||
const translate = Mat4.fromTranslation(_translate, volume.transform.fractionalBox.min);
|
||||
return Mat4.mul3(Mat4.zero(), volume.transform.cell.fromFractional, translate, scale);
|
||||
}
|
||||
|
||||
return Mat4.identity();
|
||||
}
|
||||
|
||||
export function areEquivalent(volA: VolumeData, volB: VolumeData) {
|
||||
@@ -74,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)}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
93
src/mol-model/volume/volume.ts
Normal file
93
src/mol-model/volume/volume.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { VolumeData, VolumeIsoValue } from './data';
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { Sphere3D } from '../../mol-math/geometry';
|
||||
import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
|
||||
|
||||
export namespace Volume {
|
||||
export type CellIndex = { readonly '@type': 'cell-index' } & number
|
||||
|
||||
export interface Loci { readonly kind: 'volume-loci', readonly volume: VolumeData }
|
||||
export function Loci(volume: VolumeData): Loci { return { kind: 'volume-loci', volume }; }
|
||||
export function isLoci(x: any): x is Loci { return !!x && x.kind === 'volume-loci'; }
|
||||
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 {
|
||||
export interface Loci { readonly kind: 'cell-loci', readonly volume: VolumeData, readonly indices: OrderedSet<CellIndex> }
|
||||
export function Loci(volume: VolumeData, indices: OrderedSet<CellIndex>): Loci { return { kind: 'cell-loci', volume, indices }; }
|
||||
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);
|
||||
}
|
||||
|
||||
const bs = boundaryHelper.getSphere(boundingSphere);
|
||||
return Sphere3D.expand(bs, bs, Mat4.getMaxScaleOnAxis(transform) * 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,10 @@ export const OpenFiles = StateAction.build({
|
||||
const { extensions, options } = ctx.dataFormats;
|
||||
return {
|
||||
files: PD.FileList({ accept: Array.from(extensions.values()).map(e => `.${e}`).join(',') + ',.gz,.zip', multiple: true }),
|
||||
format: PD.Select('auto', options),
|
||||
format: PD.MappedStatic('auto', {
|
||||
auto: PD.EmptyGroup(),
|
||||
specific: PD.Select(options[0][0], options)
|
||||
}),
|
||||
visuals: PD.Boolean(true, { description: 'Add default visuals' }),
|
||||
};
|
||||
}
|
||||
@@ -35,9 +38,9 @@ export const OpenFiles = StateAction.build({
|
||||
const info = getFileInfo(file.file!);
|
||||
const isBinary = plugin.dataFormats.binaryExtensions.has(info.ext);
|
||||
const { data } = await plugin.builders.data.readFile({ file, isBinary });
|
||||
const provider = params.format === 'auto'
|
||||
const provider = params.format.name === 'auto'
|
||||
? plugin.dataFormats.auto(info, data.cell?.obj!)
|
||||
: plugin.dataFormats.get(params.format);
|
||||
: plugin.dataFormats.get(params.format.params);
|
||||
|
||||
if (!provider) {
|
||||
plugin.log.warn(`OpenFiles: could not find data provider for '${info.name}.${info.ext}'`);
|
||||
@@ -54,4 +57,38 @@ export const OpenFiles = StateAction.build({
|
||||
}
|
||||
}
|
||||
}).runInContext(taskCtx);
|
||||
}));
|
||||
|
||||
export const DownloadFile = StateAction.build({
|
||||
display: { name: 'Download File', description: 'Load one or more file from an URL' },
|
||||
from: PluginStateObject.Root,
|
||||
params: (a, ctx: PluginContext) => {
|
||||
const { options } = ctx.dataFormats;
|
||||
return {
|
||||
url: PD.Url(''),
|
||||
format: PD.Select(options[0][0], options),
|
||||
isBinary: PD.Boolean(false),
|
||||
visuals: PD.Boolean(true, { description: 'Add default visuals' }),
|
||||
};
|
||||
}
|
||||
})(({ params, state }, plugin: PluginContext) => Task.create('Open Files', async taskCtx => {
|
||||
plugin.behaviors.layout.leftPanelTabName.next('data');
|
||||
|
||||
await state.transaction(async () => {
|
||||
try {
|
||||
const provider = plugin.dataFormats.get(params.format);
|
||||
if (!provider) {
|
||||
plugin.log.warn(`DownloadFile: could not find data provider for '${params.format}'`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await plugin.builders.data.download({ url: params.url, isBinary: params.isBinary });
|
||||
const parsed = await provider.parse(plugin, data);
|
||||
if (params.visuals) {
|
||||
await provider.visuals?.(plugin, parsed);
|
||||
}
|
||||
} catch (e) {
|
||||
plugin.log.error(e);
|
||||
}
|
||||
}).runInContext(taskCtx);
|
||||
}));
|
||||
@@ -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) });
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateAction, StateTransformer } from '../../mol-state';
|
||||
import { StateAction, StateTransformer, StateSelection } from '../../mol-state';
|
||||
import { Task } from '../../mol-task';
|
||||
import { getFileInfo } from '../../mol-util/file-info';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
@@ -14,6 +14,7 @@ import { PluginStateObject } from '../objects';
|
||||
import { Download } from '../transforms/data';
|
||||
import { DataFormatProvider } from '../formats/provider';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { StateTransforms } from '../transforms';
|
||||
|
||||
export { DownloadDensity };
|
||||
type DownloadDensity = typeof DownloadDensity
|
||||
@@ -135,4 +136,17 @@ const DownloadDensity = StateAction.build({
|
||||
|
||||
const volumes = await provider.parse(plugin, data);
|
||||
await provider.visuals?.(plugin, volumes);
|
||||
}));
|
||||
}));
|
||||
|
||||
export const AssignColorVolume = StateAction.build({
|
||||
display: { name: 'Assign Volume Colors', description: 'Assigns another volume to be available for coloring.' },
|
||||
from: PluginStateObject.Volume.Data,
|
||||
isApplicable(a) { return !a.data.colorVolume; },
|
||||
params(a, plugin: PluginContext) {
|
||||
const cells = plugin.state.data.select(StateSelection.Generators.root.subtree().ofType(PluginStateObject.Volume.Data).filter(cell => !!cell.obj && !cell.obj?.data.colorVolume && cell.obj !== a));
|
||||
if (cells.length === 0) return { ref: PD.Text('', { isHidden: true, label: 'Volume' }) };
|
||||
return { ref: PD.Select(cells[0].transform.ref, cells.map(c => [c.transform.ref, c.obj!.label]), { label: 'Volume' }) };
|
||||
}
|
||||
})(({ ref, params, state }, plugin: PluginContext) => {
|
||||
return plugin.build().to(ref).apply(StateTransforms.Volume.AssignColorVolume, { ref: params.ref }, { dependsOn: [ params.ref ] }).commit();
|
||||
});
|
||||
@@ -75,7 +75,8 @@ const auto = StructureRepresentationPresetProvider({
|
||||
case Structure.Size.Medium:
|
||||
return polymerAndLigand.apply(ref, params, plugin);
|
||||
case Structure.Size.Small:
|
||||
return atomicDetail.apply(ref, params, plugin);
|
||||
// `showCarbohydrateSymbol: true` is nice e.g. for PDB 1aga
|
||||
return atomicDetail.apply(ref, { ...params, showCarbohydrateSymbol: true }, plugin);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -105,7 +106,7 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
|
||||
polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
|
||||
ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
|
||||
nonStandard: await presetStaticComponent(plugin, structureCell, 'non-standard'),
|
||||
branched: await presetStaticComponent(plugin, structureCell, 'branched'),
|
||||
branched: await presetStaticComponent(plugin, structureCell, 'branched', { label: 'Carbohydrate' }),
|
||||
water: await presetStaticComponent(plugin, structureCell, 'water'),
|
||||
coarse: await presetStaticComponent(plugin, structureCell, 'coarse')
|
||||
};
|
||||
@@ -115,9 +116,9 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
|
||||
polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams, color }, { tag: 'polymer' }),
|
||||
ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams, color }, { tag: 'ligand' }),
|
||||
nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color: color || 'polymer-id' }, { tag: 'non-standard' }),
|
||||
branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.15 }, color }, { tag: 'branched-ball-and-stick' }),
|
||||
branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.3 }, color }, { tag: 'branched-ball-and-stick' }),
|
||||
branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }, { tag: 'branched-snfg-3d' }),
|
||||
water: builder.buildRepresentation(update, components.water, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.51 }, color }, { tag: 'water' }),
|
||||
water: builder.buildRepresentation(update, components.water, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.6 }, color }, { tag: 'water' }),
|
||||
coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'polymer-id' }, { tag: 'coarse' })
|
||||
};
|
||||
|
||||
@@ -226,7 +227,10 @@ const atomicDetail = StructureRepresentationPresetProvider({
|
||||
name: 'Atomic Detail', group: BuiltInPresetGroupName,
|
||||
description: 'Shows everything in atomic detail with Ball & Stick.'
|
||||
},
|
||||
params: () => CommonParams,
|
||||
params: () => ({
|
||||
...CommonParams,
|
||||
showCarbohydrateSymbol: PD.Boolean(false)
|
||||
}),
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
if (!structureCell) return {};
|
||||
@@ -237,8 +241,13 @@ const atomicDetail = StructureRepresentationPresetProvider({
|
||||
|
||||
const { update, builder, typeParams, color } = reprBuilder(plugin, params);
|
||||
const representations = {
|
||||
all: builder.buildRepresentation(update, components.all, { type: 'ball-and-stick', typeParams, color }, { tag: 'all' })
|
||||
all: builder.buildRepresentation(update, components.all, { type: 'ball-and-stick', typeParams, color }, { tag: 'all' }),
|
||||
};
|
||||
if (params.showCarbohydrateSymbol) {
|
||||
Object.assign(representations, {
|
||||
snfg3d: builder.buildRepresentation(update, components.all, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color }, { tag: 'snfg-3d' }),
|
||||
});
|
||||
}
|
||||
|
||||
await update.commit({ revertOnError: true });
|
||||
return { components, representations };
|
||||
|
||||
@@ -45,7 +45,7 @@ export class DataFormatRegistry {
|
||||
|
||||
get options() {
|
||||
if (this._options) return this._options;
|
||||
const options: [string, string, string][] = [['auto', 'Automatic', '']];
|
||||
const options: [string, string, string][] = [];
|
||||
this._list.forEach(({ name, provider }) => options.push([ name, provider.label, provider.category || '' ]));
|
||||
this._options = options;
|
||||
return options;
|
||||
|
||||
@@ -13,6 +13,8 @@ import { PluginStateObject } from '../objects';
|
||||
import { VolumeRepresentation3DHelpers } from '../transforms/representation';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { VolumeIsoValue } from '../../mol-model/volume';
|
||||
import { createVolumeRepresentationParams } from '../helpers/volume-representation-params';
|
||||
import { objectForEach } from '../../mol-util/object';
|
||||
|
||||
const Category = 'Volume';
|
||||
|
||||
@@ -59,6 +61,76 @@ export const Dsn6Provider = DataFormatProvider({
|
||||
visuals: defaultVisuals
|
||||
});
|
||||
|
||||
export const DxProvider = DataFormatProvider({
|
||||
label: 'DX',
|
||||
description: 'DX',
|
||||
category: Category,
|
||||
stringExtensions: ['dx'],
|
||||
binaryExtensions: ['dxbin'],
|
||||
parse: async (plugin, data) => {
|
||||
const format = plugin.build()
|
||||
.to(data)
|
||||
.apply(StateTransforms.Data.ParseDx, {}, { state: { isGhost: true } });
|
||||
|
||||
const volume = format.apply(StateTransforms.Volume.VolumeFromDx);
|
||||
|
||||
await volume.commit({ revertOnError: true });
|
||||
|
||||
return { volume: volume.selector };
|
||||
},
|
||||
visuals: defaultVisuals
|
||||
});
|
||||
|
||||
export const CubeProvider = DataFormatProvider({
|
||||
label: 'Cube',
|
||||
description: 'Cube',
|
||||
category: Category,
|
||||
stringExtensions: ['cub', 'cube'],
|
||||
parse: async (plugin, data) => {
|
||||
const format = plugin.build()
|
||||
.to(data)
|
||||
.apply(StateTransforms.Data.ParseCube, {}, { state: { isGhost: true } });
|
||||
|
||||
const volume = format.apply(StateTransforms.Volume.VolumeFromCube);
|
||||
const structure = format
|
||||
.apply(StateTransforms.Model.TrajectoryFromCube, void 0, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory)
|
||||
.apply(StateTransforms.Model.StructureFromModel);
|
||||
|
||||
await format.commit({ revertOnError: true });
|
||||
|
||||
return { format: format.selector, volume: volume.selector, structure: structure.selector };
|
||||
},
|
||||
visuals: async (plugin: PluginContext, data: { volume: StateObjectSelector<PluginStateObject.Volume.Data>, structure: StateObjectSelector<PluginStateObject.Molecule.Structure> }) => {
|
||||
const surfaces = plugin.build();
|
||||
|
||||
const volumeData = data.volume.cell?.obj?.data;
|
||||
const volumePos = surfaces.to(data.volume).apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(plugin, volumeData, {
|
||||
type: 'isosurface',
|
||||
typeParams: { isoValue: VolumeIsoValue.relative(1), alpha: 0.4 },
|
||||
color: 'uniform',
|
||||
colorParams: { value: ColorNames.blue }
|
||||
}));
|
||||
const volumeNeg = surfaces.to(data.volume).apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(plugin, volumeData, {
|
||||
type: 'isosurface',
|
||||
typeParams: { isoValue: VolumeIsoValue.relative(-1), alpha: 0.4 },
|
||||
color: 'uniform',
|
||||
colorParams: { value: ColorNames.red }
|
||||
}));
|
||||
|
||||
const structure = await plugin.builders.structure.representation.applyPreset(data.structure, 'auto');
|
||||
await surfaces.commit();
|
||||
|
||||
const structureReprs: StateObjectSelector<PluginStateObject.Molecule.Structure.Representation3D>[] = [];
|
||||
|
||||
objectForEach(structure?.representations as any, (r: any) => {
|
||||
if (r) structureReprs.push(r);
|
||||
});
|
||||
|
||||
return [volumePos.selector, volumeNeg.selector, ...structureReprs];
|
||||
}
|
||||
});
|
||||
|
||||
export const DscifProvider = DataFormatProvider({
|
||||
label: 'DensityServer CIF',
|
||||
description: 'DensityServer CIF',
|
||||
@@ -111,7 +183,9 @@ export const DscifProvider = DataFormatProvider({
|
||||
|
||||
export const BuiltInVolumeFormats = [
|
||||
['ccp4', Ccp4Provider] as const,
|
||||
['dns6', Dsn6Provider] as const,
|
||||
['dsn6', Dsn6Provider] as const,
|
||||
['cube', CubeProvider] as const,
|
||||
['dx', DxProvider] as const,
|
||||
['dscif', DscifProvider] as const,
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
*/
|
||||
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
import { QueryContext, Structure, StructureQuery, StructureSelection } from '../../mol-model/structure';
|
||||
import { BondType, NucleicBackboneAtoms, ProteinBackboneAtoms, SecondaryStructureType } from '../../mol-model/structure/model/types';
|
||||
import { QueryContext, Structure, StructureQuery, StructureSelection, StructureProperties, StructureElement } from '../../mol-model/structure';
|
||||
import { BondType, NucleicBackboneAtoms, ProteinBackboneAtoms, SecondaryStructureType, AminoAcidNamesL, RnaBaseNames, DnaBaseNames, WaterNames, ElementSymbol } from '../../mol-model/structure/model/types';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import Expression from '../../mol-script/language/expression';
|
||||
@@ -15,9 +15,9 @@ import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
import { StateBuilder } from '../../mol-state';
|
||||
import { RuntimeContext } from '../../mol-task';
|
||||
import { SetUtils } from '../../mol-util/set';
|
||||
import { stringToWords } from '../../mol-util/string';
|
||||
import { PluginStateObject } from '../objects';
|
||||
import { StateTransforms } from '../transforms';
|
||||
import { ElementNames } from '../../mol-model/structure/model/properties/atomic/types';
|
||||
|
||||
export enum StructureSelectionCategory {
|
||||
Type = 'Type',
|
||||
@@ -41,6 +41,7 @@ interface StructureSelectionQuery {
|
||||
readonly description: string
|
||||
readonly category: string
|
||||
readonly isHidden: boolean
|
||||
readonly priority: number
|
||||
readonly referencesCurrent: boolean
|
||||
readonly query: StructureQuery
|
||||
readonly ensureCustomProperties?: (ctx: CustomProperty.Context, structure: Structure) => Promise<void>
|
||||
@@ -51,6 +52,7 @@ interface StructureSelectionQueryProps {
|
||||
description?: string
|
||||
category?: string
|
||||
isHidden?: boolean
|
||||
priority?: number
|
||||
referencesCurrent?: boolean
|
||||
ensureCustomProperties?: (ctx: CustomProperty.Context, structure: Structure) => Promise<void>
|
||||
}
|
||||
@@ -63,6 +65,7 @@ function StructureSelectionQuery(label: string, expression: Expression, props: S
|
||||
description: props.description || '',
|
||||
category: props.category ?? StructureSelectionCategory.Misc,
|
||||
isHidden: !!props.isHidden,
|
||||
priority: props.priority || 0,
|
||||
referencesCurrent: !!props.referencesCurrent,
|
||||
get query() {
|
||||
if (!_query) _query = compile<StructureSelection>(expression);
|
||||
@@ -81,7 +84,7 @@ function StructureSelectionQuery(label: string, expression: Expression, props: S
|
||||
};
|
||||
}
|
||||
|
||||
const all = StructureSelectionQuery('All', MS.struct.generator.all(), { category: '' });
|
||||
const all = StructureSelectionQuery('All', MS.struct.generator.all(), { category: '', priority: 1000 });
|
||||
const current = StructureSelectionQuery('Current Selection', MS.internal.generator.current(), { category: '', referencesCurrent: true });
|
||||
|
||||
const polymer = StructureSelectionQuery('Polymer', MS.struct.modifier.union([
|
||||
@@ -229,14 +232,14 @@ const branchedPlusConnected = StructureSelectionQuery('Carbohydrate with Connect
|
||||
MS.struct.modifier.includeConnected({
|
||||
0: branched.expression, 'layer-count': 1, 'as-whole-residues': true
|
||||
})
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
const branchedConnectedOnly = StructureSelectionQuery('Connected to Carbohydrate', MS.struct.modifier.union([
|
||||
MS.struct.modifier.exceptBy({
|
||||
0: branchedPlusConnected.expression,
|
||||
by: branched.expression
|
||||
})
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
const ligand = StructureSelectionQuery('Ligand', MS.struct.modifier.union([
|
||||
MS.struct.combinator.merge([
|
||||
@@ -264,7 +267,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 +293,14 @@ const ligandPlusConnected = StructureSelectionQuery('Ligand with Connected', MS.
|
||||
]),
|
||||
by: branched.expression
|
||||
})
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
const ligandConnectedOnly = StructureSelectionQuery('Connected to Ligand', MS.struct.modifier.union([
|
||||
MS.struct.modifier.exceptBy({
|
||||
0: ligandPlusConnected.expression,
|
||||
by: ligand.expression
|
||||
})
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
// residues connected to ligands or branched entities
|
||||
const connectedOnly = StructureSelectionQuery('Connected to Ligand or Carbohydrate', MS.struct.modifier.union([
|
||||
@@ -305,7 +308,7 @@ const connectedOnly = StructureSelectionQuery('Connected to Ligand or Carbohydra
|
||||
branchedConnectedOnly.expression,
|
||||
ligandConnectedOnly.expression
|
||||
]),
|
||||
]), { category: StructureSelectionCategory.Internal });
|
||||
]), { category: StructureSelectionCategory.Internal, isHidden: true });
|
||||
|
||||
const disulfideBridges = StructureSelectionQuery('Disulfide Bridges', MS.struct.modifier.union([
|
||||
MS.struct.modifier.wholeResidues([
|
||||
@@ -380,6 +383,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 +403,7 @@ const StandardAminoAcids = [
|
||||
[['TRP'], 'TRYPTOPHAN'],
|
||||
[['ALA'], 'ALANINE'],
|
||||
[['MET'], 'METHIONINE'],
|
||||
[['PRO'], 'PROLINE'],
|
||||
[['CYS'], 'CYSTEINE'],
|
||||
[['ASN'], 'ASPARAGINE'],
|
||||
[['VAL'], 'VALINE'],
|
||||
@@ -402,6 +416,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,14 +426,99 @@ 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) {
|
||||
return StructureSelectionQuery(`${stringToWords(label)} (${names.join(', ')})`, MS.struct.modifier.union([
|
||||
export function ResidueQuery([names, label]: [string[], string], category: string, priority = 0) {
|
||||
return StructureSelectionQuery(`${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')])
|
||||
})
|
||||
]), { category });
|
||||
]), { category, priority, description: label });
|
||||
}
|
||||
|
||||
export function ElementSymbolQuery([names, label]: [string[], string], category: string, priority: number) {
|
||||
return StructureSelectionQuery(`${label} (${names.join(', ')})`, MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'atom-test': MS.core.set.has([MS.set(...names), MS.acp('elementSymbol')])
|
||||
})
|
||||
]), { category, priority });
|
||||
}
|
||||
|
||||
export function EntityDescriptionQuery([description, label]: [string[], string], category: string, priority: number) {
|
||||
return StructureSelectionQuery(`${label}`, MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.list.equal([MS.list(...description), MS.ammp('entityDescription')])
|
||||
})
|
||||
]), { category, priority, description: description.join(', ') });
|
||||
}
|
||||
|
||||
const StandardResidues = SetUtils.unionMany(
|
||||
AminoAcidNamesL, RnaBaseNames, DnaBaseNames, WaterNames
|
||||
);
|
||||
|
||||
export function getElementQueries(structures: Structure[]) {
|
||||
const uniqueElements = new Set<ElementSymbol>();
|
||||
for (const structure of structures) {
|
||||
structure.uniqueElementSymbols.forEach(e => uniqueElements.add(e));
|
||||
}
|
||||
|
||||
const queries: StructureSelectionQuery[] = [];
|
||||
uniqueElements.forEach(e => {
|
||||
const label = ElementNames[e] || e;
|
||||
queries.push(ElementSymbolQuery([[e], label], 'Element Symbol', 0));
|
||||
});
|
||||
return queries;
|
||||
}
|
||||
|
||||
export function getNonStandardResidueQueries(structures: Structure[]) {
|
||||
const residueLabels = new Map<string, string>();
|
||||
const uniqueResidues = new Set<string>();
|
||||
for (const structure of structures) {
|
||||
structure.uniqueResidueNames.forEach(r => uniqueResidues.add(r));
|
||||
for (const m of structure.models) {
|
||||
structure.uniqueResidueNames.forEach(r => {
|
||||
const comp = m.properties.chemicalComponentMap.get(r);
|
||||
if (comp) residueLabels.set(r, comp.name);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const queries: StructureSelectionQuery[] = [];
|
||||
SetUtils.difference(uniqueResidues, StandardResidues).forEach(r => {
|
||||
const label = residueLabels.get(r) || r;
|
||||
queries.push(ResidueQuery([[r], label], 'Ligand/Non-standard Residue', 200));
|
||||
});
|
||||
return queries;
|
||||
}
|
||||
|
||||
export function getPolymerAndBranchedEntityQueries(structures: Structure[]) {
|
||||
const uniqueEntities = new Map<string, string[]>();
|
||||
const l = StructureElement.Location.create();
|
||||
for (const structure of structures) {
|
||||
l.structure = structure;
|
||||
for (const ug of structure.unitSymmetryGroups) {
|
||||
l.unit = ug.units[0];
|
||||
l.element = ug.elements[0];
|
||||
const entityType = StructureProperties.entity.type(l);
|
||||
if (entityType === 'polymer' || entityType === 'branched') {
|
||||
const description = StructureProperties.entity.pdbx_description(l);
|
||||
uniqueEntities.set(description.join(', '), description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const queries: StructureSelectionQuery[] = [];
|
||||
uniqueEntities.forEach((v, k) => {
|
||||
queries.push(EntityDescriptionQuery([v, k], 'Polymer/Carbohydrate Entities', 300));
|
||||
});
|
||||
return queries;
|
||||
}
|
||||
|
||||
export function applyBuiltInSelection(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, query: keyof typeof StructureSelectionQueries, customTag?: string) {
|
||||
return to.apply(StateTransforms.Model.StructureSelectionFromExpression,
|
||||
{ expression: StructureSelectionQueries[query].expression, label: StructureSelectionQueries[query].label },
|
||||
{ tags: customTag ? [query, customTag] : [query] });
|
||||
}
|
||||
|
||||
export const StructureSelectionQueries = {
|
||||
@@ -447,14 +547,9 @@ export const StructureSelectionQueries = {
|
||||
surroundings,
|
||||
complement,
|
||||
bonded,
|
||||
wholeResidues,
|
||||
};
|
||||
|
||||
export function applyBuiltInSelection(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, query: keyof typeof StructureSelectionQueries, customTag?: string) {
|
||||
return to.apply(StateTransforms.Model.StructureSelectionFromExpression,
|
||||
{ expression: StructureSelectionQueries[query].expression, label: StructureSelectionQueries[query].label },
|
||||
{ tags: customTag ? [query, customTag] : [query] });
|
||||
}
|
||||
|
||||
export class StructureSelectionQueryRegistry {
|
||||
list: StructureSelectionQuery[] = []
|
||||
options: [StructureSelectionQuery, string, string][] = []
|
||||
|
||||
121
src/mol-plugin-state/helpers/volume-representation-params.ts
Normal file
121
src/mol-plugin-state/helpers/volume-representation-params.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { VolumeData } from '../../mol-model/volume';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { RepresentationProvider } from '../../mol-repr/representation';
|
||||
import { VolumeRepresentationRegistry } from '../../mol-repr/volume/registry';
|
||||
import { StateTransformer } from '../../mol-state';
|
||||
import { ColorTheme } from '../../mol-theme/color';
|
||||
import { SizeTheme } from '../../mol-theme/size';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { VolumeRepresentation3D } from '../transforms/representation';
|
||||
|
||||
export interface VolumeRepresentationBuiltInProps<
|
||||
R extends VolumeRepresentationRegistry.BuiltIn = VolumeRepresentationRegistry.BuiltIn,
|
||||
C extends ColorTheme.BuiltIn = ColorTheme.BuiltIn,
|
||||
S extends SizeTheme.BuiltIn = SizeTheme.BuiltIn> {
|
||||
/** Using any registered name will work, but code completion will break */
|
||||
type?: R,
|
||||
typeParams?: VolumeRepresentationRegistry.BuiltInParams<R>,
|
||||
/** Using any registered name will work, but code completion will break */
|
||||
color?: C,
|
||||
colorParams?: ColorTheme.BuiltInParams<C>,
|
||||
/** Using any registered name will work, but code completion will break */
|
||||
size?: S,
|
||||
sizeParams?: SizeTheme.BuiltInParams<S>
|
||||
}
|
||||
|
||||
export interface VolumeRepresentationProps<
|
||||
R extends RepresentationProvider<VolumeData> = RepresentationProvider<VolumeData>,
|
||||
C extends ColorTheme.Provider = ColorTheme.Provider,
|
||||
S extends SizeTheme.Provider = SizeTheme.Provider> {
|
||||
type?: R,
|
||||
typeParams?: Partial<RepresentationProvider.ParamValues<R>>,
|
||||
color?: C,
|
||||
colorParams?: Partial<ColorTheme.ParamValues<C>>,
|
||||
size?: S,
|
||||
sizeParams?: Partial<SizeTheme.ParamValues<S>>
|
||||
}
|
||||
|
||||
export function createVolumeRepresentationParams<R extends VolumeRepresentationRegistry.BuiltIn, C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(ctx: PluginContext, volume?: VolumeData, props?: VolumeRepresentationBuiltInProps<R, C, S>): StateTransformer.Params<VolumeRepresentation3D>
|
||||
export function createVolumeRepresentationParams<R extends RepresentationProvider<VolumeData>, C extends ColorTheme.Provider, S extends SizeTheme.Provider>(ctx: PluginContext, volume?: VolumeData, props?: VolumeRepresentationProps<R, C, S>): StateTransformer.Params<VolumeRepresentation3D>
|
||||
export function createVolumeRepresentationParams(ctx: PluginContext, volume?: VolumeData, props: any = {}): StateTransformer.Params<VolumeRepresentation3D> {
|
||||
const p = props as VolumeRepresentationBuiltInProps;
|
||||
if (typeof p.type === 'string' || typeof p.color === 'string' || typeof p.size === 'string') return createParamsByName(ctx, volume || VolumeData.One, props);
|
||||
return createParamsProvider(ctx, volume || VolumeData.One, props);
|
||||
}
|
||||
|
||||
export function getVolumeThemeTypes(ctx: PluginContext, volume?: VolumeData) {
|
||||
const { themes: themeCtx } = ctx.representation.volume;
|
||||
if (!volume) return themeCtx.colorThemeRegistry.types;
|
||||
return themeCtx.colorThemeRegistry.getApplicableTypes({ volume });
|
||||
}
|
||||
|
||||
export function createVolumeColorThemeParams<T extends ColorTheme.BuiltIn>(ctx: PluginContext, volume: VolumeData | undefined, typeName: string | undefined, themeName: T, params?: ColorTheme.BuiltInParams<T>): StateTransformer.Params<VolumeRepresentation3D>['colorTheme']
|
||||
export function createVolumeColorThemeParams(ctx: PluginContext, volume: VolumeData | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['colorTheme']
|
||||
export function createVolumeColorThemeParams(ctx: PluginContext, volume: VolumeData | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['colorTheme'] {
|
||||
const { registry, themes } = ctx.representation.volume;
|
||||
const repr = registry.get(typeName || registry.default.name);
|
||||
const color = themes.colorThemeRegistry.get(themeName || repr.defaultColorTheme.name);
|
||||
const colorDefaultParams = PD.getDefaultValues(color.getParams({ volume: volume || VolumeData.One }));
|
||||
if (color.name === repr.defaultColorTheme.name) Object.assign(colorDefaultParams, repr.defaultColorTheme.props);
|
||||
return { name: color.name, params: Object.assign(colorDefaultParams, params) };
|
||||
}
|
||||
|
||||
export function createVolumeSizeThemeParams<T extends SizeTheme.BuiltIn>(ctx: PluginContext, volume: VolumeData | undefined, typeName: string | undefined, themeName: T, params?: SizeTheme.BuiltInParams<T>): StateTransformer.Params<VolumeRepresentation3D>['sizeTheme']
|
||||
export function createVolumeSizeThemeParams(ctx: PluginContext, volume: VolumeData | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['sizeTheme']
|
||||
export function createVolumeSizeThemeParams(ctx: PluginContext, volume: VolumeData | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['sizeTheme'] {
|
||||
const { registry, themes } = ctx.representation.volume;
|
||||
const repr = registry.get(typeName || registry.default.name);
|
||||
const size = themes.sizeThemeRegistry.get(themeName || repr.defaultSizeTheme.name);
|
||||
const sizeDefaultParams = PD.getDefaultValues(size.getParams({ volume: volume || VolumeData.One }));
|
||||
if (size.name === repr.defaultSizeTheme.name) Object.assign(sizeDefaultParams, repr.defaultSizeTheme.props);
|
||||
return { name: size.name, params: Object.assign(sizeDefaultParams, params) };
|
||||
}
|
||||
|
||||
function createParamsByName(ctx: PluginContext, volume: VolumeData, props: VolumeRepresentationBuiltInProps): StateTransformer.Params<VolumeRepresentation3D> {
|
||||
const typeProvider = (props.type && ctx.representation.volume.registry.get(props.type))
|
||||
|| ctx.representation.volume.registry.default.provider;
|
||||
const colorProvider = (props.color && ctx.representation.volume.themes.colorThemeRegistry.get(props.color))
|
||||
|| ctx.representation.volume.themes.colorThemeRegistry.get(typeProvider.defaultColorTheme.name);
|
||||
const sizeProvider = (props.size && ctx.representation.volume.themes.sizeThemeRegistry.get(props.size))
|
||||
|| ctx.representation.volume.themes.sizeThemeRegistry.get(typeProvider.defaultSizeTheme.name);
|
||||
|
||||
return createParamsProvider(ctx, volume, {
|
||||
type: typeProvider,
|
||||
typeParams: props.typeParams,
|
||||
color: colorProvider,
|
||||
colorParams: props.colorParams,
|
||||
size: sizeProvider,
|
||||
sizeParams: props.sizeParams
|
||||
});
|
||||
}
|
||||
|
||||
function createParamsProvider(ctx: PluginContext, volume: VolumeData, props: VolumeRepresentationProps = {}): StateTransformer.Params<VolumeRepresentation3D> {
|
||||
const { themes: themeCtx } = ctx.representation.volume;
|
||||
const themeDataCtx = { volume };
|
||||
|
||||
const repr = props.type || ctx.representation.volume.registry.default.provider;
|
||||
const reprDefaultParams = PD.getDefaultValues(repr.getParams(themeCtx, volume));
|
||||
const reprParams = Object.assign(reprDefaultParams, props.typeParams);
|
||||
|
||||
const color = props.color || themeCtx.colorThemeRegistry.get(repr.defaultColorTheme.name);
|
||||
const colorDefaultParams = PD.getDefaultValues(color.getParams(themeDataCtx));
|
||||
if (color.name === repr.defaultColorTheme.name) Object.assign(colorDefaultParams, repr.defaultColorTheme.props);
|
||||
const colorParams = Object.assign(colorDefaultParams, props.colorParams);
|
||||
|
||||
const size = props.size || themeCtx.sizeThemeRegistry.get(repr.defaultSizeTheme.name);
|
||||
const sizeDefaultParams = PD.getDefaultValues(size.getParams(themeDataCtx));
|
||||
if (size.name === repr.defaultSizeTheme.name) Object.assign(sizeDefaultParams, repr.defaultSizeTheme.props);
|
||||
const sizeParams = Object.assign(sizeDefaultParams, props.sizeParams);
|
||||
|
||||
return ({
|
||||
type: { name: repr.name, params: reprParams },
|
||||
colorTheme: { name: color.name, params: colorParams },
|
||||
sizeTheme: { name: size.name, params: sizeParams }
|
||||
});
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { PrincipalAxes } from '../../mol-math/linear-algebra/matrix/principal-ax
|
||||
import { Camera } from '../../mol-canvas3d/camera';
|
||||
import { Loci } from '../../mol-model/loci';
|
||||
import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
|
||||
import { GraphicsRenderObject } from '../../mol-gl/render-object';
|
||||
|
||||
// TODO: make this customizable somewhere?
|
||||
const DefaultCameraFocusOptions = {
|
||||
@@ -24,6 +25,19 @@ export type CameraFocusOptions = typeof DefaultCameraFocusOptions
|
||||
export class CameraManager {
|
||||
private boundaryHelper = new BoundaryHelper('98');
|
||||
|
||||
focusRenderObjects(objects?: ReadonlyArray<GraphicsRenderObject>, options?: Partial<CameraFocusOptions>) {
|
||||
if (!objects) return;
|
||||
const spheres: Sphere3D[] = [];
|
||||
|
||||
for (const o of objects) {
|
||||
const s = o.values.boundingSphere.ref.value;
|
||||
if (s.radius === 0) continue;
|
||||
spheres.push(s);
|
||||
}
|
||||
|
||||
this.focusSpheres(spheres, s => s, options);
|
||||
}
|
||||
|
||||
focusLoci(loci: Loci | Loci[], options?: Partial<CameraFocusOptions>) {
|
||||
// TODO: allow computation of principal axes here?
|
||||
// perhaps have an optimized function, that does exact axes small Loci and approximate/sampled from big ones?
|
||||
|
||||
@@ -33,8 +33,6 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
changed: this.ev()
|
||||
};
|
||||
|
||||
currentGetSnapshotParams: PluginState.GetSnapshotParams = PluginState.DefaultGetSnapshotParams as any;
|
||||
|
||||
getIndex(e: PluginStateSnapshotManager.Entry) {
|
||||
return this.state.entries.indexOf(e);
|
||||
}
|
||||
@@ -164,7 +162,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
return next;
|
||||
}
|
||||
|
||||
private syncCurrent(options?: { name?: string, description?: string, params?: PluginState.GetSnapshotParams }) {
|
||||
private syncCurrent(options?: { name?: string, description?: string, params?: PluginState.SnapshotParams }) {
|
||||
const snapshot = this.plugin.state.getSnapshot(options?.params);
|
||||
if (this.state.entries.size === 0 || !this.state.current) {
|
||||
this.add(PluginStateSnapshotManager.Entry(snapshot, { name: options?.name, description: options?.description }));
|
||||
@@ -173,10 +171,8 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
}
|
||||
}
|
||||
|
||||
getStateSnapshot(options?: { name?: string, description?: string, playOnLoad?: boolean, params?: PluginState.GetSnapshotParams }): PluginStateSnapshotManager.StateSnapshot {
|
||||
getStateSnapshot(options?: { name?: string, description?: string, playOnLoad?: boolean, params?: PluginState.SnapshotParams }): PluginStateSnapshotManager.StateSnapshot {
|
||||
// TODO: diffing and all that fancy stuff
|
||||
|
||||
// TODO: the options need to be handled better, particularky options.params
|
||||
this.syncCurrent(options);
|
||||
|
||||
return {
|
||||
@@ -193,10 +189,10 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
};
|
||||
}
|
||||
|
||||
async serialize(type: 'json' | 'zip' = 'json') {
|
||||
const json = JSON.stringify(this.getStateSnapshot(), null, 2);
|
||||
async serialize(options?: { type: 'json' | 'molj' | 'zip' | 'molx', params?: PluginState.SnapshotParams }) {
|
||||
const json = JSON.stringify(this.getStateSnapshot({ params: options?.params }), null, 2);
|
||||
|
||||
if (type === 'json') {
|
||||
if (!options?.type || options.type === 'json' || options.type === 'molj') {
|
||||
return new Blob([json], {type : 'application/json;charset=utf-8'});
|
||||
} else {
|
||||
const state = new Uint8Array(utf8ByteCount(json));
|
||||
@@ -232,7 +228,12 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
|
||||
if (fn.endsWith('json') || fn.endsWith('molj')) {
|
||||
const data = await this.plugin.runTask(readFromFile(file, 'string'));
|
||||
const snapshot = JSON.parse(data);
|
||||
return this.setStateSnapshot(snapshot);
|
||||
|
||||
if (PluginStateSnapshotManager.isStateSnapshot(snapshot)) {
|
||||
return this.setStateSnapshot(snapshot);
|
||||
} else {
|
||||
this.plugin.state.setSnapshot(snapshot);
|
||||
}
|
||||
} else {
|
||||
const data = await this.plugin.runTask(readFromFile(file, 'zip'));
|
||||
const assets = Object.create(null);
|
||||
@@ -333,6 +334,11 @@ namespace PluginStateSnapshotManager {
|
||||
return { timestamp: +new Date(), snapshot, ...params };
|
||||
}
|
||||
|
||||
export function isStateSnapshot(x?: any): x is StateSnapshot {
|
||||
const s = x as StateSnapshot;
|
||||
return !!s && !!s.timestamp && !!s.entries;
|
||||
}
|
||||
|
||||
export interface StateSnapshot {
|
||||
timestamp: number,
|
||||
version: string,
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
import { VisualQualityOptions } from '../../../mol-geo/geometry/base';
|
||||
import { InteractionsProvider } from '../../../mol-model-props/computed/interactions';
|
||||
import { Structure, StructureElement } from '../../../mol-model/structure';
|
||||
import { structureAreIntersecting, structureIntersect, structureSubtract, structureUnion } from '../../../mol-model/structure/query/utils/structure-set';
|
||||
import { Structure, StructureElement, StructureSelection } from '../../../mol-model/structure';
|
||||
import { structureAreEqual, structureAreIntersecting, structureIntersect, structureSubtract, structureUnion } from '../../../mol-model/structure/query/utils/structure-set';
|
||||
import { setSubtreeVisibility } from '../../../mol-plugin/behavior/static/state';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateBuilder, StateTransformer } from '../../../mol-state';
|
||||
import { StateBuilder, StateObjectRef, StateTransformer } from '../../../mol-state';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { ColorTheme } from '../../../mol-theme/color';
|
||||
import { SizeTheme } from '../../../mol-theme/size';
|
||||
@@ -25,7 +25,7 @@ import { clearStructureOverpaint, setStructureOverpaint } from '../../helpers/st
|
||||
import { createStructureColorThemeParams, createStructureSizeThemeParams } from '../../helpers/structure-representation-params';
|
||||
import { StructureSelectionQueries, StructureSelectionQuery } from '../../helpers/structure-selection-query';
|
||||
import { StructureRepresentation3D } from '../../transforms/representation';
|
||||
import { HierarchyRef, StructureComponentRef, StructureRef, StructureRepresentationRef } from './hierarchy-state';
|
||||
import { StructureHierarchyRef, StructureComponentRef, StructureRef, StructureRepresentationRef } from './hierarchy-state';
|
||||
|
||||
export { StructureComponentManager };
|
||||
|
||||
@@ -132,7 +132,7 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
|
||||
let changed = false;
|
||||
const update = this.dataState.build();
|
||||
|
||||
const sync = (r: HierarchyRef) => {
|
||||
const sync = (r: StructureHierarchyRef) => {
|
||||
if (!keptRefs.has(r.cell.transform.ref)) {
|
||||
changed = true;
|
||||
update.delete(r.cell);
|
||||
@@ -169,7 +169,7 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
|
||||
}
|
||||
}
|
||||
|
||||
canBeModified(ref: HierarchyRef) {
|
||||
canBeModified(ref: StructureHierarchyRef) {
|
||||
return this.plugin.builders.structure.isComponentTransform(ref.cell);
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
|
||||
removeRepresentations(components: ReadonlyArray<StructureComponentRef>, pivot?: StructureRepresentationRef) {
|
||||
if (components.length === 0) return;
|
||||
|
||||
const toRemove: HierarchyRef[] = [];
|
||||
const toRemove: StructureHierarchyRef[] = [];
|
||||
if (pivot) {
|
||||
const index = components[0].representations.indexOf(pivot);
|
||||
if (index < 0) return;
|
||||
@@ -308,6 +308,24 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
|
||||
}, { canUndo: 'Add Representation' });
|
||||
}
|
||||
|
||||
private tryFindComponent(structure: StructureRef, selection: StructureSelectionQuery) {
|
||||
if (structure.components.length === 0) return;
|
||||
|
||||
return this.plugin.runTask(Task.create('Find Component', async taskCtx => {
|
||||
|
||||
const data = structure.cell.obj?.data;
|
||||
if (!data) return;
|
||||
const sel = StructureSelection.unionStructure(await selection.getSelection(this.plugin, taskCtx, data));
|
||||
|
||||
for (const c of structure.components) {
|
||||
const comp = c.cell.obj?.data;
|
||||
if (!comp || !c.cell.parent) continue;
|
||||
|
||||
if (structureAreEqual(sel, comp)) return c.cell;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async add(params: StructureComponentManager.AddParams, structures?: ReadonlyArray<StructureRef>) {
|
||||
return this.plugin.dataTransaction(async () => {
|
||||
const xs = structures || this.currentStructures;
|
||||
@@ -319,9 +337,18 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone
|
||||
|
||||
const componentKey = UUID.create22();
|
||||
for (const s of xs) {
|
||||
const component = await this.plugin.builders.structure.tryCreateComponentFromSelection(s.cell, params.selection, componentKey, {
|
||||
label: params.label || (params.selection === StructureSelectionQueries.current ? 'Custom Selection' : ''),
|
||||
});
|
||||
let component: StateObjectRef | undefined = void 0;
|
||||
|
||||
if (params.options.checkExisting) {
|
||||
component = await this.tryFindComponent(s, params.selection);
|
||||
}
|
||||
|
||||
if (!component) {
|
||||
component = await this.plugin.builders.structure.tryCreateComponentFromSelection(s.cell, params.selection, componentKey, {
|
||||
label: params.options.label || (params.selection === StructureSelectionQueries.current ? 'Custom Selection' : ''),
|
||||
});
|
||||
}
|
||||
|
||||
if (params.representation === 'none' || !component) continue;
|
||||
await this.plugin.builders.structure.representation.addRepresentation(component, {
|
||||
type: this.plugin.representation.structure.registry.get(params.representation),
|
||||
@@ -400,15 +427,26 @@ namespace StructureComponentManager {
|
||||
};
|
||||
export type Options = PD.Values<typeof OptionsParams>
|
||||
|
||||
export function getAddParams(plugin: PluginContext) {
|
||||
export function getAddParams(plugin: PluginContext, params?: { pivot?: StructureRef, allowNone: boolean, hideSelection?: boolean, checkExisting?: boolean, defaultSelection?: StructureSelectionQuery }) {
|
||||
const { options } = plugin.query.structure.registry;
|
||||
params = {
|
||||
pivot: plugin.managers.structure.component.pivotStructure,
|
||||
allowNone: true,
|
||||
hideSelection: false,
|
||||
checkExisting: false,
|
||||
defaultSelection: StructureSelectionQueries.current,
|
||||
...params
|
||||
};
|
||||
return {
|
||||
selection: PD.Select(options[1][0], options),
|
||||
representation: getRepresentationTypesSelect(plugin, plugin.managers.structure.component.pivotStructure, [['none', '< Create Later >']]),
|
||||
label: PD.Text('')
|
||||
selection: PD.Select(options[1][0], options, { isHidden: params?.hideSelection }),
|
||||
representation: getRepresentationTypesSelect(plugin, params?.pivot, params?.allowNone ? [['none', '< Create Later >']] : []),
|
||||
options: PD.Group({
|
||||
label: PD.Text(''),
|
||||
checkExisting: PD.Boolean(!!params?.checkExisting, { help: () => ({ description: 'Checks if a selection with the specifield elements already exists to avoid creating duplicate components.' }) }),
|
||||
})
|
||||
};
|
||||
}
|
||||
export type AddParams = { selection: StructureSelectionQuery, label: string, representation: string }
|
||||
export type AddParams = { selection: StructureSelectionQuery, options: { checkExisting: boolean, label: string }, representation: string }
|
||||
|
||||
export function getColorParams(plugin: PluginContext, pivot: StructureRef | StructureComponentRef | undefined) {
|
||||
return {
|
||||
|
||||
@@ -21,7 +21,7 @@ export interface StructureHierarchy {
|
||||
trajectories: TrajectoryRef[],
|
||||
models: ModelRef[],
|
||||
structures: StructureRef[],
|
||||
refs: Map<StateTransform.Ref, HierarchyRef>
|
||||
refs: Map<StateTransform.Ref, StructureHierarchyRef>
|
||||
// TODO: might be needed in the future
|
||||
// decorators: Map<StateTransform.Ref, StateTransform>,
|
||||
}
|
||||
@@ -36,7 +36,7 @@ interface RefBase<K extends string = string, O extends StateObject = StateObject
|
||||
version: StateTransform['version']
|
||||
}
|
||||
|
||||
export type HierarchyRef =
|
||||
export type StructureHierarchyRef =
|
||||
| TrajectoryRef
|
||||
| ModelRef | ModelPropertiesRef | ModelUnitcellRef
|
||||
| StructureRef | StructurePropertiesRef | StructureTransformRef | StructureVolumeStreamingRef | StructureComponentRef | StructureRepresentationRef
|
||||
@@ -140,10 +140,10 @@ function StructureRepresentationRef(cell: StateObjectCell<SO.Molecule.Structure.
|
||||
}
|
||||
|
||||
export interface GenericRepresentationRef extends RefBase<'generic-representation', SO.Any> {
|
||||
parent: HierarchyRef
|
||||
parent: StructureHierarchyRef
|
||||
}
|
||||
|
||||
function GenericRepresentationRef(cell: StateObjectCell<SO.Molecule.Structure.Representation3D>, parent: HierarchyRef): GenericRepresentationRef {
|
||||
function GenericRepresentationRef(cell: StateObjectCell<SO.Molecule.Structure.Representation3D>, parent: StructureHierarchyRef): GenericRepresentationRef {
|
||||
return { kind: 'generic-representation', cell, version: cell.transform.version, parent };
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ function BuildState(state: State, oldHierarchy: StructureHierarchy): BuildState
|
||||
return { state, oldHierarchy, hierarchy: StructureHierarchy(), changed: false, added: new Set() };
|
||||
}
|
||||
|
||||
function createOrUpdateRefList<R extends HierarchyRef, C extends any[]>(state: BuildState, cell: StateObjectCell, list: R[], ctor: (...args: C) => R, ...args: C) {
|
||||
function createOrUpdateRefList<R extends StructureHierarchyRef, C extends any[]>(state: BuildState, cell: StateObjectCell, list: R[], ctor: (...args: C) => R, ...args: C) {
|
||||
const ref: R = ctor(...args);
|
||||
list.push(ref);
|
||||
state.hierarchy.refs.set(cell.transform.ref, ref);
|
||||
@@ -180,7 +180,7 @@ function createOrUpdateRefList<R extends HierarchyRef, C extends any[]>(state: B
|
||||
return ref;
|
||||
}
|
||||
|
||||
function createOrUpdateRef<R extends HierarchyRef, C extends any[]>(state: BuildState, cell: StateObjectCell, ctor: (...args: C) => R, ...args: C) {
|
||||
function createOrUpdateRef<R extends StructureHierarchyRef, C extends any[]>(state: BuildState, cell: StateObjectCell, ctor: (...args: C) => R, ...args: C) {
|
||||
const ref: R = ctor(...args);
|
||||
state.hierarchy.refs.set(cell.transform.ref, ref);
|
||||
const old = state.oldHierarchy.refs.get(cell.transform.ref);
|
||||
@@ -300,7 +300,7 @@ function isValidCell(cell?: StateObjectCell): cell is StateObjectCell {
|
||||
return true;
|
||||
}
|
||||
|
||||
function isRemoved(this: BuildState, ref: HierarchyRef) {
|
||||
function isRemoved(this: BuildState, ref: StructureHierarchyRef) {
|
||||
const { cell } = ref;
|
||||
if (isValidCell(cell)) return;
|
||||
this.changed = true;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { StateTransform, StateTree } from '../../../mol-state';
|
||||
import { SetUtils } from '../../../mol-util/set';
|
||||
import { TrajectoryHierarchyPresetProvider } from '../../builder/structure/hierarchy-preset';
|
||||
import { PluginComponent } from '../../component';
|
||||
import { buildStructureHierarchy, HierarchyRef, ModelRef, StructureComponentRef, StructureHierarchy, StructureRef, TrajectoryRef } from './hierarchy-state';
|
||||
import { buildStructureHierarchy, StructureHierarchyRef, ModelRef, StructureComponentRef, StructureHierarchy, StructureRef, TrajectoryRef } from './hierarchy-state';
|
||||
|
||||
export class StructureHierarchyManager extends PluginComponent {
|
||||
private state = {
|
||||
@@ -68,7 +68,18 @@ export class StructureHierarchyManager extends PluginComponent {
|
||||
return this.state.selection;
|
||||
}
|
||||
|
||||
private syncCurrent<T extends HierarchyRef>(all: ReadonlyArray<T>, added: Set<StateTransform.Ref>): T[] {
|
||||
getStructuresWithSelection() {
|
||||
const xs = this.plugin.managers.structure.hierarchy.current.structures;
|
||||
const ret: StructureRef[] = [];
|
||||
for (const s of xs) {
|
||||
if (this.plugin.managers.structure.selection.structureHasSelection(s)) {
|
||||
ret.push(s);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private syncCurrent<T extends StructureHierarchyRef>(all: ReadonlyArray<T>, added: Set<StateTransform.Ref>): T[] {
|
||||
const current = this.seletionSet;
|
||||
const newCurrent: T[] = [];
|
||||
|
||||
@@ -121,7 +132,7 @@ export class StructureHierarchyManager extends PluginComponent {
|
||||
}
|
||||
}
|
||||
|
||||
updateCurrent(refs: HierarchyRef[], action: 'add' | 'remove') {
|
||||
updateCurrent(refs: StructureHierarchyRef[], action: 'add' | 'remove') {
|
||||
const hierarchy = this.current;
|
||||
const set = action === 'add'
|
||||
? SetUtils.union(this.seletionSet, new Set(refs.map(r => r.cell.transform.ref)))
|
||||
@@ -151,14 +162,14 @@ export class StructureHierarchyManager extends PluginComponent {
|
||||
this.behaviors.selection.next({ hierarchy, trajectories, models, structures });
|
||||
}
|
||||
|
||||
remove(refs: (HierarchyRef | string)[], canUndo?: boolean) {
|
||||
remove(refs: (StructureHierarchyRef | string)[], canUndo?: boolean) {
|
||||
if (refs.length === 0) return;
|
||||
const deletes = this.plugin.state.data.build();
|
||||
for (const r of refs) deletes.delete(typeof r === 'string' ? r : r.cell.transform.ref);
|
||||
return deletes.commit({ canUndo: canUndo ? 'Remove' : false });
|
||||
}
|
||||
|
||||
toggleVisibility(refs: ReadonlyArray<HierarchyRef>, action?: 'show' | 'hide') {
|
||||
toggleVisibility(refs: ReadonlyArray<StructureHierarchyRef>, action?: 'show' | 'hide') {
|
||||
if (refs.length === 0) return;
|
||||
|
||||
const isHidden = action !== void 0
|
||||
|
||||
@@ -21,6 +21,7 @@ import { StatefulPluginComponent } from '../../component';
|
||||
import { StructureSelectionQuery } from '../../helpers/structure-selection-query';
|
||||
import { PluginStateObject } from '../../objects';
|
||||
import { UUID } from '../../../mol-util';
|
||||
import { StructureRef } from './hierarchy-state';
|
||||
|
||||
interface StructureSelectionManagerState {
|
||||
entries: Map<string, SelectionEntry>,
|
||||
@@ -248,6 +249,13 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
return entry.structure;
|
||||
}
|
||||
|
||||
structureHasSelection(structure: StructureRef) {
|
||||
const s = structure.cell?.obj?.data;
|
||||
if (!s) return false;
|
||||
const entry = this.getEntry(s);
|
||||
return !!entry && !StructureElement.Loci.isEmpty(entry.selection);
|
||||
}
|
||||
|
||||
has(loci: Loci) {
|
||||
if (StructureElement.Loci.is(loci)) {
|
||||
const entry = this.getEntry(loci.structure);
|
||||
|
||||
170
src/mol-plugin-state/manager/volume/hierarchy-state.ts
Normal file
170
src/mol-plugin-state/manager/volume/hierarchy-state.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as SO } from '../../objects';
|
||||
import { StateObject, StateTransform, State, StateObjectCell, StateTree, StateTransformer } from '../../../mol-state';
|
||||
import { StateTransforms } from '../../transforms';
|
||||
|
||||
export function buildVolumeHierarchy(state: State, previous?: VolumeHierarchy) {
|
||||
const build = BuildState(state, previous || VolumeHierarchy());
|
||||
doPreOrder(state.tree, build);
|
||||
if (previous) previous.refs.forEach(isRemoved, build);
|
||||
return { hierarchy: build.hierarchy, added: build.added, changed: build.changed };
|
||||
}
|
||||
|
||||
export interface VolumeHierarchy {
|
||||
volumes: VolumeRef[],
|
||||
refs: Map<StateTransform.Ref, VolumeHierarchyRef>
|
||||
// TODO: might be needed in the future
|
||||
// decorators: Map<StateTransform.Ref, StateTransform>,
|
||||
}
|
||||
|
||||
export function VolumeHierarchy(): VolumeHierarchy {
|
||||
return { volumes: [], refs: new Map() };
|
||||
}
|
||||
|
||||
interface RefBase<K extends string = string, O extends StateObject = StateObject, T extends StateTransformer = StateTransformer> {
|
||||
kind: K,
|
||||
cell: StateObjectCell<O, StateTransform<T>>,
|
||||
version: StateTransform['version']
|
||||
}
|
||||
|
||||
export type VolumeHierarchyRef = VolumeRef | VolumeRepresentationRef
|
||||
|
||||
export interface VolumeRef extends RefBase<'volume', SO.Volume.Data> {
|
||||
representations: VolumeRepresentationRef[]
|
||||
}
|
||||
|
||||
function VolumeRef(cell: StateObjectCell<SO.Volume.Data>): VolumeRef {
|
||||
return { kind: 'volume', cell, version: cell.transform.version, representations: [] };
|
||||
}
|
||||
|
||||
export interface VolumeRepresentationRef extends RefBase<'volume-representation', SO.Volume.Representation3D, StateTransforms['Representation']['VolumeRepresentation3D']> {
|
||||
volume: VolumeRef
|
||||
}
|
||||
|
||||
function VolumeRepresentationRef(cell: StateObjectCell<SO.Volume.Representation3D>, volume: VolumeRef): VolumeRepresentationRef {
|
||||
return { kind: 'volume-representation', cell, version: cell.transform.version, volume };
|
||||
}
|
||||
|
||||
interface BuildState {
|
||||
state: State,
|
||||
oldHierarchy: VolumeHierarchy,
|
||||
|
||||
hierarchy: VolumeHierarchy,
|
||||
|
||||
currentVolume?: VolumeRef,
|
||||
|
||||
changed: boolean,
|
||||
added: Set<StateTransform.Ref>
|
||||
}
|
||||
|
||||
function BuildState(state: State, oldHierarchy: VolumeHierarchy): BuildState {
|
||||
return { state, oldHierarchy, hierarchy: VolumeHierarchy(), changed: false, added: new Set() };
|
||||
}
|
||||
|
||||
function createOrUpdateRefList<R extends VolumeHierarchyRef, C extends any[]>(state: BuildState, cell: StateObjectCell, list: R[], ctor: (...args: C) => R, ...args: C) {
|
||||
const ref: R = ctor(...args);
|
||||
list.push(ref);
|
||||
state.hierarchy.refs.set(cell.transform.ref, ref);
|
||||
const old = state.oldHierarchy.refs.get(cell.transform.ref);
|
||||
if (old) {
|
||||
if (old.version !== cell.transform.version) state.changed = true;
|
||||
} else {
|
||||
state.added.add(ref.cell.transform.ref);
|
||||
state.changed = true;
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
type TestCell = (cell: StateObjectCell, state: BuildState) => boolean
|
||||
type ApplyRef = (state: BuildState, cell: StateObjectCell) => boolean | void
|
||||
type LeaveRef = (state: BuildState) => any
|
||||
|
||||
function isTypeRoot(t: StateObject.Ctor, target: (state: BuildState) => any): TestCell {
|
||||
return (cell, state) => !target(state) && t.is(cell.obj);
|
||||
}
|
||||
|
||||
function noop() { }
|
||||
|
||||
const Mapping: [TestCell, ApplyRef, LeaveRef][] = [
|
||||
[isTypeRoot(SO.Volume.Data, t => t.currentVolume), (state, cell) => {
|
||||
state.currentVolume = createOrUpdateRefList(state, cell, state.hierarchy.volumes, VolumeRef, cell);
|
||||
}, state => state.currentVolume = void 0],
|
||||
|
||||
[(cell, state) => {
|
||||
return !cell.state.isGhost && !!state.currentVolume && SO.Volume.Representation3D.is(cell.obj);
|
||||
}, (state, cell) => {
|
||||
if (state.currentVolume) {
|
||||
createOrUpdateRefList(state, cell, state.currentVolume.representations, VolumeRepresentationRef, cell, state.currentVolume);
|
||||
}
|
||||
return false;
|
||||
}, noop]
|
||||
];
|
||||
|
||||
function isValidCell(cell?: StateObjectCell): cell is StateObjectCell {
|
||||
if (!cell || !cell?.parent || !cell.parent.cells.has(cell.transform.ref)) return false;
|
||||
const { obj } = cell;
|
||||
if (!obj || obj === StateObject.Null || (cell.status !== 'ok' && cell.status !== 'error')) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function isRemoved(this: BuildState, ref: VolumeHierarchyRef) {
|
||||
const { cell } = ref;
|
||||
if (isValidCell(cell)) return;
|
||||
this.changed = true;
|
||||
}
|
||||
|
||||
type VisitorCtx = { tree: StateTree, state: BuildState };
|
||||
|
||||
function _preOrderFunc(this: VisitorCtx, c: StateTransform.Ref | undefined) { _doPreOrder(this, this.tree.transforms.get(c!)!); }
|
||||
function _doPreOrder(ctx: VisitorCtx, root: StateTransform) {
|
||||
const { state } = ctx;
|
||||
const cell = state.state.cells.get(root.ref);
|
||||
if (!isValidCell(cell)) return;
|
||||
|
||||
let onLeave: undefined | ((state: BuildState) => any) = void 0;
|
||||
let end = false;
|
||||
for (const [test, f, l] of Mapping) {
|
||||
if (test(cell, state)) {
|
||||
const cont = f(state, cell);
|
||||
if (cont === false) {
|
||||
end = true;
|
||||
break;
|
||||
}
|
||||
onLeave = l;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: might be needed in the future
|
||||
// const { currentComponent, currentModel, currentStructure, currentTrajectory } = ctx.state;
|
||||
// const inTrackedSubtree = currentComponent || currentModel || currentStructure || currentTrajectory;
|
||||
|
||||
// if (inTrackedSubtree && cell.transform.transformer.definition.isDecorator) {
|
||||
// const ref = cell.transform.ref;
|
||||
// const old = ctx.state.oldHierarchy.decorators.get(ref);
|
||||
// if (old && old.version !== cell.transform.version) {
|
||||
// ctx.state.changed = true;
|
||||
// }
|
||||
// ctx.state.hierarchy.decorators.set(cell.transform.ref, cell.transform);
|
||||
// }
|
||||
|
||||
if (end) return;
|
||||
|
||||
const children = ctx.tree.children.get(root.ref);
|
||||
if (children && children.size) {
|
||||
children.forEach(_preOrderFunc, ctx);
|
||||
}
|
||||
|
||||
if (onLeave) onLeave(state);
|
||||
}
|
||||
|
||||
function doPreOrder(tree: StateTree, state: BuildState): BuildState {
|
||||
const ctx: VisitorCtx = { tree, state };
|
||||
_doPreOrder(ctx, tree.root);
|
||||
return ctx.state;
|
||||
}
|
||||
135
src/mol-plugin-state/manager/volume/hierarchy.ts
Normal file
135
src/mol-plugin-state/manager/volume/hierarchy.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { setSubtreeVisibility } from '../../../mol-plugin/behavior/static/state';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { PluginComponent } from '../../component';
|
||||
import { buildVolumeHierarchy, VolumeHierarchy, VolumeHierarchyRef, VolumeRef } from './hierarchy-state';
|
||||
import { createVolumeRepresentationParams } from '../../helpers/volume-representation-params';
|
||||
import { StateTransforms } from '../../transforms';
|
||||
|
||||
export class VolumeHierarchyManager extends PluginComponent {
|
||||
private state = {
|
||||
syncedTree: this.dataState.tree,
|
||||
notified: false,
|
||||
|
||||
hierarchy: VolumeHierarchy(),
|
||||
selection: void 0 as VolumeRef | undefined
|
||||
}
|
||||
|
||||
readonly behaviors = {
|
||||
selection: this.ev.behavior({
|
||||
hierarchy: this.current,
|
||||
volume: this.selection
|
||||
})
|
||||
}
|
||||
|
||||
private get dataState() {
|
||||
return this.plugin.state.data;
|
||||
}
|
||||
|
||||
get current() {
|
||||
this.sync(false);
|
||||
return this.state.hierarchy;
|
||||
}
|
||||
|
||||
get selection() {
|
||||
this.sync(false);
|
||||
return this.state.selection;
|
||||
}
|
||||
|
||||
private sync(notify: boolean) {
|
||||
if (!notify && this.dataState.inUpdate) return;
|
||||
|
||||
if (this.state.syncedTree === this.dataState.tree) {
|
||||
if (notify && !this.state.notified) {
|
||||
this.state.notified = true;
|
||||
this.behaviors.selection.next({ hierarchy: this.state.hierarchy, volume: this.state.selection });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.syncedTree = this.dataState.tree;
|
||||
|
||||
const update = buildVolumeHierarchy(this.plugin.state.data, this.current);
|
||||
if (!update.changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { hierarchy } = update;
|
||||
|
||||
this.state.hierarchy = hierarchy;
|
||||
if (!this.state.selection) {
|
||||
this.state.selection = hierarchy.volumes[0];
|
||||
} else {
|
||||
this.state.selection = hierarchy.refs.has(this.state.selection.cell.transform.ref) ? hierarchy.refs.get(this.state.selection.cell.transform.ref) as VolumeRef : hierarchy.volumes[0];
|
||||
}
|
||||
|
||||
if (notify) {
|
||||
this.state.notified = true;
|
||||
this.behaviors.selection.next({ hierarchy, volume: this.state.selection });
|
||||
} else {
|
||||
this.state.notified = false;
|
||||
}
|
||||
}
|
||||
|
||||
setCurrent(volume?: VolumeRef) {
|
||||
this.state.selection = volume || this.state.hierarchy.volumes[0];
|
||||
this.behaviors.selection.next({ hierarchy: this.state.hierarchy, volume: volume || this.state.hierarchy.volumes[0] });
|
||||
}
|
||||
|
||||
// TODO: have common util
|
||||
remove(refs: (VolumeHierarchyRef | string)[], canUndo?: boolean) {
|
||||
if (refs.length === 0) return;
|
||||
const deletes = this.plugin.state.data.build();
|
||||
for (const r of refs) deletes.delete(typeof r === 'string' ? r : r.cell.transform.ref);
|
||||
return deletes.commit({ canUndo: canUndo ? 'Remove' : false });
|
||||
}
|
||||
|
||||
// TODO: have common util
|
||||
toggleVisibility(refs: ReadonlyArray<VolumeHierarchyRef>, action?: 'show' | 'hide') {
|
||||
if (refs.length === 0) return;
|
||||
|
||||
const isHidden = action !== void 0
|
||||
? (action === 'show' ? false : true)
|
||||
: !refs[0].cell.state.isHidden;
|
||||
for (const c of refs) {
|
||||
setSubtreeVisibility(this.dataState, c.cell.transform.ref, isHidden);
|
||||
}
|
||||
}
|
||||
|
||||
addRepresentation(ref: VolumeRef, type: string) {
|
||||
const update = this.dataState.build()
|
||||
.to(ref.cell)
|
||||
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, ref.cell.obj?.data, {
|
||||
type: type as any,
|
||||
}));
|
||||
|
||||
return update.commit({ canUndo: 'Add Representation' });
|
||||
}
|
||||
|
||||
constructor(private plugin: PluginContext) {
|
||||
super();
|
||||
|
||||
this.subscribe(plugin.state.data.events.changed, e => {
|
||||
if (e.inTransaction || plugin.behaviors.state.isAnimating.value) return;
|
||||
this.sync(true);
|
||||
});
|
||||
|
||||
this.subscribe(plugin.behaviors.state.isAnimating, isAnimating => {
|
||||
if (!isAnimating && !plugin.behaviors.state.isUpdating.value) this.sync(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export namespace VolumeHierarchyManager {
|
||||
export function getRepresentationTypes(plugin: PluginContext, pivot: VolumeRef | undefined) {
|
||||
return pivot?.cell.obj?.data
|
||||
? plugin.representation.volume.registry.getApplicableTypes(pivot.cell.obj?.data!)
|
||||
: plugin.representation.volume.registry.types;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -21,6 +21,8 @@ import { ShapeRepresentation } from '../mol-repr/shape/representation';
|
||||
import { StructureRepresentation, StructureRepresentationState } from '../mol-repr/structure/representation';
|
||||
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'
|
||||
|
||||
@@ -67,10 +69,12 @@ export namespace PluginStateObject {
|
||||
export namespace Format {
|
||||
export class Json extends Create<any>({ name: 'JSON Data', typeClass: 'Data' }) { }
|
||||
export class Cif extends Create<CifFile>({ name: 'CIF File', typeClass: 'Data' }) { }
|
||||
export class Cube extends Create<CubeFile>({ name: 'Cube File', typeClass: 'Data' }) { }
|
||||
export class Psf extends Create<PsfFile>({ name: 'PSF File', typeClass: 'Data' }) { }
|
||||
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 } |
|
||||
@@ -83,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 }
|
||||
|
||||
@@ -18,6 +18,8 @@ import { ajaxGetMany } from '../../mol-util/data-source';
|
||||
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 };
|
||||
@@ -25,10 +27,12 @@ export { RawData };
|
||||
export { ReadFile };
|
||||
export { ParseBlob };
|
||||
export { ParseCif };
|
||||
export { ParseCube };
|
||||
export { ParsePsf };
|
||||
export { ParsePly };
|
||||
export { ParseCcp4 };
|
||||
export { ParseDsn6 };
|
||||
export { ParseDx };
|
||||
export { ImportString };
|
||||
export { ImportJson };
|
||||
export { ParseJson };
|
||||
@@ -255,6 +259,22 @@ const ParseCif = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
});
|
||||
|
||||
type ParseCube = typeof ParseCube
|
||||
const ParseCube = PluginStateTransform.BuiltIn({
|
||||
name: 'parse-cube',
|
||||
display: { name: 'Parse Cube', description: 'Parse Cube from String data' },
|
||||
from: SO.Data.String,
|
||||
to: SO.Format.Cube
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse Cube', async 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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
type ParsePsf = typeof ParsePsf
|
||||
const ParsePsf = PluginStateTransform.BuiltIn({
|
||||
name: 'parse-psf',
|
||||
@@ -296,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);
|
||||
});
|
||||
@@ -312,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',
|
||||
|
||||
@@ -34,6 +34,7 @@ import { PluginStateObject as SO, PluginStateTransform } from '../objects';
|
||||
import { parseMol } from '../../mol-io/reader/mol/parser';
|
||||
import { trajectoryFromMol } from '../../mol-model-formats/structure/mol';
|
||||
import { trajectoryFromCifCore } from '../../mol-model-formats/structure/cif-core';
|
||||
import { trajectoryFromCube } from '../../mol-model-formats/structure/cube';
|
||||
|
||||
export { CoordinatesFromDcd };
|
||||
export { TopologyFromPsf };
|
||||
@@ -43,6 +44,7 @@ export { TrajectoryFromMmCif };
|
||||
export { TrajectoryFromPDB };
|
||||
export { TrajectoryFromGRO };
|
||||
export { TrajectoryFromMOL };
|
||||
export { TrajectoryFromCube };
|
||||
export { TrajectoryFromCifCore };
|
||||
export { TrajectoryFrom3DG };
|
||||
export { ModelFromTrajectory };
|
||||
@@ -233,6 +235,22 @@ const TrajectoryFromMOL = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
});
|
||||
|
||||
type TrajectoryFromCube = typeof TrajectoryFromCube
|
||||
const TrajectoryFromCube = PluginStateTransform.BuiltIn({
|
||||
name: 'trajectory-from-cube',
|
||||
display: { name: 'Parse Cube', description: 'Parse Cube file to create a trajectory.' },
|
||||
from: SO.Format.Cube,
|
||||
to: SO.Molecule.Trajectory
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse MOL', async ctx => {
|
||||
const models = await trajectoryFromCube(a.data).runInContext(ctx);
|
||||
const props = { label: `${models[0].entry}`, description: `${models.length} model${models.length === 1 ? '' : 's'}` };
|
||||
return new SO.Molecule.Trajectory(models, props);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
type TrajectoryFromCifCore = typeof TrajectoryFromCifCore
|
||||
const TrajectoryFromCifCore = PluginStateTransform.BuiltIn({
|
||||
name: 'trajectory-from-cif-core',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user