Compare commits

...

44 Commits

Author SHA1 Message Date
David Sehnal
d42c9a6e15 0.7.0-dev.2 2020-04-22 13:09:14 +02:00
David Sehnal
da4dabc3f5 fix package.json bin path 2020-04-22 13:08:17 +02:00
David Sehnal
92217905f8 0.7.0-dev.1 2020-04-22 12:27:57 +02:00
Alexander Rose
598441a727 Merge branch 'master' of https://github.com/molstar/molstar 2020-04-21 18:18:01 -07:00
Alexander Rose
f707cb19a4 volume loci, picking and repr improvements
- Volume.Loci
- Volume.Isosurface.Loci
- Volume.Cell.Loci
- picking
- wip: slice
2020-04-21 18:17:51 -07:00
David Sehnal
63fc408be6 ajaxGet: support custom http headers 2020-04-21 23:36:56 +02:00
David Sehnal
69dedd8c22 Merge branch 'master' of https://github.com/molstar/molstar 2020-04-21 22:51:22 +02:00
David Sehnal
5480805754 AssignColorVolume
+ UI bugfix
2020-04-21 22:50:49 +02:00
David Sehnal
655ae65b8d dxbin support 2020-04-21 21:59:27 +02:00
David Sehnal
1a23cb672e dx parser 2020-04-21 21:24:59 +02:00
Alexander Rose
19e18b4089 MC: ensure winding-order and normals dir are same for neg/pos iso-level 2020-04-21 11:36:41 -07:00
Alexander Rose
3d909d5012 fix, register Dsn6Provider as dsn6 2020-04-21 11:12:38 -07:00
David Sehnal
ad521948b6 VolumeData.transform 2020-04-21 20:10:22 +02:00
Alexander Rose
2f3b6a28c1 support older REMARK 350 format, #34 2020-04-21 10:42:05 -07:00
Alexander Rose
c779da674c Merge branch 'master' of https://github.com/molstar/molstar 2020-04-21 10:18:57 -07:00
Alexander Rose
d9b140f9f2 added Model.probablyHasDensityMap 2020-04-21 10:18:39 -07:00
David Sehnal
052648023e VolumeSourceControls
- initial version, needs more work
2020-04-21 19:07:08 +02:00
David Sehnal
f5d12d440e DownloadFile state action 2020-04-21 17:24:56 +02:00
David Sehnal
99d7a90863 Basic cube format support
- TODO: non-orthogonal frames
2020-04-21 17:07:22 +02:00
Alexander Rose
901d5c86e6 wip, stub for volume-slice representation 2020-04-20 19:09:39 -07:00
Alexander Rose
df9efd05e6 image geometry 2020-04-20 19:04:55 -07:00
David Sehnal
26b8adaec4 extensions/cellpack: use plugin.runTask instead of Task.run 2020-04-21 02:43:09 +02:00
David Sehnal
fc6f5a0336 Merge pull request #33 from corredD/forkdev
Forkdev
2020-04-21 02:34:04 +02:00
Alexander Rose
f823a887b7 Merge branch 'master' of https://github.com/molstar/molstar 2020-04-20 10:25:26 -07:00
Alexander Rose
b346d4d85d tweaked selection/focus remove icon 2020-04-20 10:21:43 -07:00
Alexander Rose
70bd035898 shader refactoring
- add dRenderVariant
- add convenince defines
2020-04-20 10:21:04 -07:00
David Sehnal
7e5cdd8e06 mol-plugin-ui: improved selection controls 2020-04-20 18:55:28 +02:00
David Sehnal
a21dac60e0 Rename "full state" to Session
+ ProteopediaWrapper.snapshot tweaks
2020-04-20 18:11:38 +02:00
David Sehnal
9bd2e0d96e typo fix 2020-04-20 17:52:21 +02:00
autin
dd15a000e1 clean the console.log 2020-04-20 13:39:01 +02:00
autin
ebcfa44f22 OPM support 2020-04-20 13:19:21 +02:00
David Sehnal
43845adb71 import materialui icons separately
- avoids loading extra ~5k modules in webpack
2020-04-19 17:02:52 +02:00
David Sehnal
9e3fff65a7 eslint config fix 2020-04-19 16:08:20 +02:00
David Sehnal
05c35a3a3a mol-model: cif export copyAllCategories option
- support in model-server
- moved servers build config
- fixed swagger template not working with the separate server build
2020-04-19 16:01:47 +02:00
David Sehnal
6f46965344 model-server: change response header for query-many exceeded limit 2020-04-19 14:02:05 +02:00
Alexander Rose
27ee576340 improved .hasDensityMap and .isFromPdbArchive helpers 2020-04-18 13:39:44 -07:00
David Sehnal
58492328df max height for Screenshot / State Snapshot panel 2020-04-18 16:01:17 +02:00
David Sehnal
fd102bede1 mol-plugin-ui: close Screenshot/snapshot panel after saving/opening state 2020-04-18 15:54:50 +02:00
David Sehnal
3d8c47eefa proteopedia-wrapper: support zipped state loading 2020-04-18 15:51:25 +02:00
David Sehnal
c4e43228a2 add Component: option to check if an equivalent component already exists 2020-04-18 15:31:51 +02:00
David Sehnal
6526090b8b mol-plugin-ui: added "add representation" to selection mode 2020-04-18 15:03:49 +02:00
David Sehnal
094a018b5b mol-plugin: global state save options 2020-04-18 14:28:58 +02:00
Alexander Rose
a0a9c994b2 basic asset support for volume streaming 2020-04-17 20:37:14 -07:00
Alexander Rose
524ed90e3f lru cache set returns removed entry 2020-04-17 20:36:20 -07:00
126 changed files with 3127 additions and 675 deletions

View File

@@ -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": [

2
package-lock.json generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -65,6 +65,7 @@ export interface Ingredient {
principalAxis?: Vec3;
/** offset along membrane */
offset?: Vec3;
ingtype?: string;
}
export interface IngredientSource {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,211 @@
/**
* 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<Uint8Array | 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), { isEssential: true }),
};
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>) {
ValueCell.updateIfChanged(values.uAlpha, props.alpha);
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);
}

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,134 @@
/**
* 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
#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.01)
discard;
#ifdef enabledFragDepth
gl_FragColor = packDepthToRGBA(gl_FragDepthEXT);
#else
gl_FragColor = packDepthToRGBA(gl_FragCoord.z);
#endif
#elif defined(dRenderVariant_color)
if (imageData.a < 0.01)
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
}
`;

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,140 @@
/**
* 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 {
header: CubeFile.Header,
atoms: CubeFile.Atoms,
values: Float64Array
}
export namespace CubeFile {
export interface Header {
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 = { 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) {
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 });
});
}

View File

@@ -0,0 +1,128 @@
/**
* 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 {
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) {
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 });
}
async function parseBinary(taskCtx: RuntimeContext, data: Uint8Array) {
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 });
}
export function parseDx(data: string | Uint8Array) {
return Task.create<Result<DxFile>>('Parse Cube', taskCtx => {
if (typeof data === 'string') return parseText(taskCtx, data);
return parseBinary(taskCtx, data);
});
}

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

@@ -16,6 +16,8 @@ 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';
import { VolumeData } from './volume';
/** A Loci that includes every loci */
export const EveryLoci = { kind: 'every-loci' as 'every-loci' };
@@ -62,7 +64,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 +100,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 +125,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 +161,13 @@ namespace Loci {
return ShapeGroup.getBoundingSphere(loci, boundingSphere);
} else if (loci.kind === 'data-loci') {
return loci.getBoundingSphere(boundingSphere);
} else if (loci.kind === 'volume-loci') {
return VolumeData.getBoundingSphere(loci.volume, boundingSphere);
} else if (loci.kind === 'isosurface-loci') {
return VolumeData.getBoundingSphere(loci.volume, boundingSphere);
} else if (loci.kind === 'cell-loci') {
// TODO
return VolumeData.getBoundingSphere(loci.volume, boundingSphere);
}
}
@@ -175,6 +196,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;
}
}

View File

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

View File

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

View File

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

View File

@@ -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,95 @@ 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 {
console.log('default');
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);

View File

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

View File

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

View File

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

View File

@@ -5,14 +5,14 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
import { SpacegroupCell, Box3D, Sphere3D } from '../../mol-math/geometry';
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,25 +22,42 @@ 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() },
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) {
return volA === volB;
}
export function getBoundingSphere(volume: VolumeData, boundingSphere?: Sphere3D) {
if (!boundingSphere) boundingSphere = Sphere3D();
// TODO
return boundingSphere;
}
}
type VolumeIsoValue = VolumeIsoValue.Absolute | VolumeIsoValue.Relative

View File

@@ -0,0 +1,34 @@
/**
* 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';
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 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 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; }
}
}

View File

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

View File

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

View File

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

View File

@@ -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,74 @@ 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 volume = plugin.build()
.to(data)
.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.red }
}));
const volumeNeg = surfaces.to(data.volume).apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(plugin, volumeData, {
type: 'isosurface',
typeParams: { isoValue: VolumeIsoValue.relative(-1), alpha: 0.4 },
color: 'uniform',
colorParams: { value: ColorNames.blue }
}));
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 +181,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;

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

@@ -21,6 +21,7 @@ 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';
export type TypeClass = 'root' | 'data' | 'prop'
@@ -67,6 +68,7 @@ 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' }) { }

View File

@@ -18,6 +18,7 @@ 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';
export { Download };
export { DownloadBlob };
@@ -25,6 +26,7 @@ export { RawData };
export { ReadFile };
export { ParseBlob };
export { ParseCif };
export { ParseCube };
export { ParsePsf };
export { ParsePly };
export { ParseCcp4 };
@@ -255,6 +257,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).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',

View File

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

View File

@@ -13,10 +13,20 @@ import { volumeFromDsn6 } from '../../mol-model-formats/volume/dsn6';
import { Task } from '../../mol-task';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { PluginStateObject as SO, PluginStateTransform } from '../objects';
import { volumeFromCube } from '../../mol-model-formats/volume/cube';
import { parseDx } from '../../mol-io/reader/dx/parser';
import { volumeFromDx } from '../../mol-model-formats/volume/dx';
import { VolumeData } from '../../mol-model/volume';
import { PluginContext } from '../../mol-plugin/context';
import { StateSelection } from '../../mol-state';
export { VolumeFromCcp4 };
export { VolumeFromDsn6 };
export { VolumeFromCube };
export { VolumeFromDx };
export { AssignColorVolume };
export { VolumeFromDensityServerCif };
type VolumeFromCcp4 = typeof VolumeFromCcp4
const VolumeFromCcp4 = PluginStateTransform.BuiltIn({
name: 'volume-from-ccp4',
@@ -32,8 +42,8 @@ const VolumeFromCcp4 = PluginStateTransform.BuiltIn({
})({
apply({ a, params }) {
return Task.create('Create volume from CCP4/MRC/MAP', async ctx => {
const volume = await volumeFromCcp4(a.data, params).runInContext(ctx);
const props = { label: 'Volume' };
const volume = await volumeFromCcp4(a.data, { ...params, label: a.label }).runInContext(ctx);
const props = { label: volume.label || 'Volume', description: 'Volume' };
return new SO.Volume.Data(volume, props);
});
}
@@ -53,8 +63,48 @@ const VolumeFromDsn6 = PluginStateTransform.BuiltIn({
})({
apply({ a, params }) {
return Task.create('Create volume from DSN6/BRIX', async ctx => {
const volume = await volumeFromDsn6(a.data, params).runInContext(ctx);
const props = { label: 'Volume' };
const volume = await volumeFromDsn6(a.data, { ...params, label: a.label }).runInContext(ctx);
const props = { label: volume.label || 'Volume', description: 'Volume' };
return new SO.Volume.Data(volume, props);
});
}
});
type VolumeFromCube = typeof VolumeFromCube
const VolumeFromCube = PluginStateTransform.BuiltIn({
name: 'volume-from-cube',
display: { name: 'Volume from Cube', description: 'Create Volume from Cube data' },
from: SO.Format.Cube,
to: SO.Volume.Data,
params(a) {
if (!a) return { dataIndex: PD.Numeric(0) };
return {
dataIndex: PD.Select(0, a.data.header.dataSetIds.map((id, i) => [i, `${id}`] as const))
};
}
})({
apply({ a, params }) {
return Task.create('Create volume from Cube', async ctx => {
const volume = await volumeFromCube(a.data, { ...params, label: a.label }).runInContext(ctx);
const props = { label: volume.label || 'Volume', description: 'Volume' };
return new SO.Volume.Data(volume, props);
});
}
});
type VolumeFromDx = typeof VolumeFromDx
const VolumeFromDx = PluginStateTransform.BuiltIn({
name: 'volume-from-dx',
display: { name: 'Parse PX', description: 'Parse DX string/binary and create volume.' },
from: [SO.Data.String, SO.Data.Binary],
to: SO.Volume.Data
})({
apply({ a }) {
return Task.create('Parse DX', async ctx => {
const parsed = await parseDx(a.data).runInContext(ctx);
if (parsed.isError) throw new Error(parsed.message);
const volume = await volumeFromDx(parsed.result, { label: a.label }).runInContext(ctx);
const props = { label: volume.label || 'Volume', description: 'Volume' };
return new SO.Volume.Data(volume, props);
});
}
@@ -90,4 +140,34 @@ const VolumeFromDensityServerCif = PluginStateTransform.BuiltIn({
return new SO.Volume.Data(volume, props);
});
}
});
});
type AssignColorVolume = typeof AssignColorVolume
const AssignColorVolume = PluginStateTransform.BuiltIn({
name: 'assign-color-volume',
display: { name: 'Assign Color Volume', description: 'Assigns another volume to be available for coloring.' },
from: SO.Volume.Data,
to: SO.Volume.Data,
isDecorator: true,
params(a, plugin: PluginContext) {
if (!a) return { ref: PD.Text() };
const cells = plugin.state.data.select(StateSelection.Generators.root.subtree().ofType(SO.Volume.Data).filter(cell => !!cell.obj && !cell.obj?.data.colorVolume && cell.obj !== a));
if (cells.length === 0) return { ref: PD.Text('', { isHidden: true }) };
return { ref: PD.Select(cells[0].transform.ref, cells.map(c => [c.transform.ref, c.obj!.label])) };
}
})({
apply({ a, params, dependencies }) {
return Task.create('Assign Color Volume', async ctx => {
if (!dependencies || !dependencies[params.ref]) {
throw new Error('Dependency not available.');
}
const colorVolume = dependencies[params.ref].data as VolumeData;
const volume: VolumeData = {
...a.data,
colorVolume
};
const props = { label: a.label, description: 'Volume + Colors' };
return new SO.Volume.Data(volume, props);
});
}
});

View File

@@ -8,8 +8,9 @@
import * as React from 'react';
import { Observable, Subscription } from 'rxjs';
import { PluginContext } from '../mol-plugin/context';
import { Button } from './controls/common';
import { ArrowRight, ArrowDropDown } from '@material-ui/icons';
import { Button, ColorAccent } from './controls/common';
import ArrowRight from '@material-ui/icons/ArrowRight';
import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
import { Icon } from './controls/icons';
export const PluginReactContext = React.createContext(void 0 as any as PluginContext);
@@ -77,7 +78,7 @@ export type CollapsableState = {
header: string,
description?: string,
isHidden?: boolean,
brand?: { svg?: React.FC, accent: 'cyan' | 'red' | 'gray' | 'green' | 'purple' | 'blue' | 'orange' }
brand?: { svg?: React.FC, accent: ColorAccent }
}
export abstract class CollapsableControls<P = {}, S = {}, SS = {}> extends PluginUIComponent<P & CollapsableProps, S & CollapsableState, SS> {

View File

@@ -5,7 +5,13 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Build, NavigateBefore, NavigateNext, PlayArrow, SkipPrevious, Stop, SubscriptionsOutlined } from '@material-ui/icons';
import Build from '@material-ui/icons/Build';
import NavigateBefore from '@material-ui/icons/NavigateBefore';
import NavigateNext from '@material-ui/icons/NavigateNext';
import PlayArrow from '@material-ui/icons/PlayArrow';
import SkipPrevious from '@material-ui/icons/SkipPrevious';
import Stop from '@material-ui/icons/Stop';
import SubscriptionsOutlined from '@material-ui/icons/SubscriptionsOutlined';
import * as React from 'react';
import { UpdateTrajectory } from '../mol-plugin-state/actions/structure';
import { LociLabel } from '../mol-plugin-state/manager/loci-label';
@@ -22,7 +28,7 @@ import { StructureComponentControls } from './structure/components';
import { StructureMeasurementsControls } from './structure/measurements';
import { StructureSelectionActionsControls } from './structure/selection';
import { StructureSourceControls } from './structure/source';
import { VolumeStreamingControls } from './structure/volume';
import { VolumeStreamingControls, VolumeSourceControls } from './structure/volume';
export class TrajectoryViewportControls extends PluginUIComponent<{}, { show: boolean, label: string }> {
state = { show: false, label: '' }
@@ -292,6 +298,7 @@ export class DefaultStructureTools extends PluginUIComponent {
<StructureMeasurementsControls />
<StructureComponentControls />
<VolumeStreamingControls />
<VolumeSourceControls />
<CustomStructureControls />
</>;

View File

@@ -8,7 +8,10 @@
import * as React from 'react';
import { ParamDefinition } from '../../mol-util/param-definition';
import { Button, ControlGroup } from './common';
import { ArrowRight, Check, Close, ArrowDropDown } from '@material-ui/icons';
import ArrowRight from '@material-ui/icons/ArrowRight';
import Check from '@material-ui/icons/Check';
import Close from '@material-ui/icons/Close';
import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
export class ActionMenu extends React.PureComponent<ActionMenu.Props> {
hide = () => this.props.onSelect(void 0)

View File

@@ -7,7 +7,12 @@
import * as React from 'react';
import { Color } from '../../mol-util/color';
import { Icon } from './icons';
import { ArrowRight, ArrowDropDown, Remove, Add } from '@material-ui/icons';
import ArrowRight from '@material-ui/icons/ArrowRight';
import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
import Remove from '@material-ui/icons/Remove';
import Add from '@material-ui/icons/Add';
export type ColorAccent = 'cyan' | 'red' | 'gray' | 'green' | 'purple' | 'blue' | 'orange'
export class ControlGroup extends React.Component<{
header: string,
@@ -211,8 +216,8 @@ export class ExpandableControlRow extends React.Component<{
}
}
export function SectionHeader(props: { icon?: React.FC, title: string | JSX.Element, desc?: string }) {
return <div className='msp-section-header'>
export function SectionHeader(props: { icon?: React.FC, title: string | JSX.Element, desc?: string, accent?: ColorAccent }) {
return <div className={`msp-section-header${props.accent ? ' msp-transform-header-brand-' + props.accent : ''}` }>
{props.icon && <Icon svg={props.icon} />}
{props.title} <small>{props.desc}</small>
</div>;
@@ -224,6 +229,7 @@ export type ButtonProps = {
disabled?: boolean,
title?: string,
icon?: React.FC,
commit?: boolean | 'on' | 'off'
children?: React.ReactNode,
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void,
onContextMenu?: (e: React.MouseEvent<HTMLButtonElement>) => void,
@@ -241,6 +247,8 @@ export function Button(props: ButtonProps) {
if (!props.inline) className += ' msp-btn-block';
if (props.noOverflow) className += ' msp-no-overflow';
if (props.flex) className += ' msp-flex-item';
if (props.commit === 'on' || props.commit) className += ' msp-btn-commit msp-btn-commit-on';
if (props.commit === 'off') className += ' msp-btn-commit msp-btn-commit-off';
if (!props.children) className += ' msp-btn-childless';
if (props.className) className += ' ' + props.className;

View File

@@ -29,8 +29,8 @@ export function MoleculeSvg() { return _molecule; }
export function RulerSvg() { return _ruler; }
export function CubeSvg() { return _cube; }
const circleLeft = <circle r="6px" id="circle-left" cy="12px" cx="8px" strokeWidth="0.5" />;
const circleRight = <circle r="6px" id="circle-right" cy="12px" cx="16px" strokeWidth="0.5" />;
const circleLeft = <circle r="6px" id="circle-left" cy="12px" cx="8px" strokeWidth="1" />;
const circleRight = <circle r="6px" id="circle-right" cy="12px" cx="16px" strokeWidth="1" />;
const _union = <svg width="24px" height="24px" viewBox="0 0 24 24">
<defs>

View File

@@ -5,7 +5,16 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ArrowDownward, ArrowDropDown, ArrowRight, ArrowUpward, BookmarksOutlined, Check, Clear, DeleteOutlined, HelpOutline, MoreHoriz } from '@material-ui/icons';
import ArrowDownward from '@material-ui/icons/ArrowDownward';
import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
import ArrowRight from '@material-ui/icons/ArrowRight';
import ArrowUpward from '@material-ui/icons/ArrowUpward';
import BookmarksOutlined from '@material-ui/icons/BookmarksOutlined';
import Check from '@material-ui/icons/Check';
import Clear from '@material-ui/icons/Clear';
import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
import HelpOutline from '@material-ui/icons/HelpOutline';
import MoreHoriz from '@material-ui/icons/MoreHoriz';
import * as React from 'react';
import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra';
import { PluginContext } from '../../mol-plugin/context';

View File

@@ -4,7 +4,12 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { AccountTreeOutlined, DeleteOutlined, HelpOutline, HomeOutlined, SaveOutlined, Tune } from '@material-ui/icons';
import AccountTreeOutlined from '@material-ui/icons/AccountTreeOutlined';
import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
import HelpOutline from '@material-ui/icons/HelpOutline';
import HomeOutlined from '@material-ui/icons/HomeOutlined';
import SaveOutlined from '@material-ui/icons/SaveOutlined';
import Tune from '@material-ui/icons/Tune';
import * as React from 'react';
import { Canvas3DParams } from '../mol-canvas3d/canvas3d';
import { PluginCommands } from '../mol-plugin/commands';

View File

@@ -22,7 +22,7 @@ import { ElementSequenceWrapper } from './sequence/element';
import { elementLabel } from '../mol-theme/label';
import { Icon } from './controls/icons';
import { StructureSelectionManager } from '../mol-plugin-state/manager/structure/selection';
import { HelpOutline } from '@material-ui/icons';
import HelpOutline from '@material-ui/icons/HelpOutline';
const MaxDisplaySequenceLength = 5000;

View File

@@ -427,7 +427,7 @@
position: relative;
// display: inline-block;
margin: $control-spacing auto 0 auto;
width: 332px;
width: 400px;
line-height: $row-height;
&-actions {
@@ -444,7 +444,7 @@
select.msp-form-control {
padding: 0 5px;
text-align: center;
background: none;
background: $msp-form-control-background;
flex: 0 0 80px;
text-overflow: ellipsis;
}

View File

@@ -9,7 +9,7 @@ import { State } from '../../mol-state';
import { PluginUIComponent } from '../base';
import { Icon } from '../controls/icons';
import { ApplyActionControl } from './apply-action';
import { Code } from '@material-ui/icons';
import Code from '@material-ui/icons/Code';
export class StateObjectActions extends PluginUIComponent<{ state: State, nodeRef: string, hideHeader?: boolean, initiallyCollapsed?: boolean, alwaysExpandFirst?: boolean }> {
get current() {

View File

@@ -8,7 +8,7 @@ import * as React from 'react';
import { PluginUIComponent } from '../base';
import { ParameterControls, ParamOnChange } from '../controls/parameters';
import { Button } from '../controls/common';
import { PlayArrow } from '@material-ui/icons';
import PlayArrow from '@material-ui/icons/PlayArrow';
export class AnimationControls extends PluginUIComponent<{ onStart?: () => void }> {
componentDidMount() {

View File

@@ -13,7 +13,11 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Subject } from 'rxjs';
import { Icon } from '../controls/icons';
import { ExpandGroup, ToggleButton, Button, IconButton } from '../controls/common';
import { Refresh, ArrowRight, ArrowDropDown, Check, Tune } from '@material-ui/icons';
import Refresh from '@material-ui/icons/Refresh';
import ArrowRight from '@material-ui/icons/ArrowRight';
import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
import Check from '@material-ui/icons/Check';
import Tune from '@material-ui/icons/Tune';
export { StateTransformParameters, TransformControlBase };

View File

@@ -4,7 +4,16 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { ArrowDownward, ArrowUpward, CloudUpload, DeleteOutlined, GetApp, OpenInBrowser, SaveOutlined, SwapHoriz } from '@material-ui/icons';
import Add from '@material-ui/icons/Refresh';
import ArrowDownward from '@material-ui/icons/ArrowDownward';
import ArrowUpward from '@material-ui/icons/ArrowUpward';
import CloudUpload from '@material-ui/icons/CloudUpload';
import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
import GetApp from '@material-ui/icons/GetApp';
import OpenInBrowser from '@material-ui/icons/OpenInBrowser';
import SaveOutlined from '@material-ui/icons/SaveOutlined';
import SwapHoriz from '@material-ui/icons/SwapHoriz';
import Refresh from '@material-ui/icons/Refresh';
import { OrderedMap } from 'immutable';
import * as React from 'react';
import { PluginCommands } from '../../mol-plugin/commands';
@@ -15,7 +24,7 @@ import { formatTimespan } from '../../mol-util/now';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { urlCombine } from '../../mol-util/url';
import { PluginUIComponent, PurePluginUIComponent } from '../base';
import { Button, IconButton, SectionHeader } from '../controls/common';
import { Button, ExpandGroup, IconButton, SectionHeader } from '../controls/common';
import { Icon } from '../controls/icons';
import { ParameterControls } from '../controls/parameters';
@@ -23,23 +32,33 @@ export class StateSnapshots extends PluginUIComponent<{}> {
render() {
return <div>
<SectionHeader icon={SaveOutlined} title='Plugin State' />
<div style={{ marginBottom: '10px' }}>
<ExpandGroup header='Save Options' initiallyExpanded={false}>
<LocalStateSnapshotParams />
</ExpandGroup>
</div>
<LocalStateSnapshots />
<LocalStateSnapshotList />
<SectionHeader title='Save as File' accent='blue' />
<StateExportImportControls />
{this.plugin.spec.components?.remoteState !== 'none' && <RemoteStateSnapshots />}
<div style={{ marginTop: '10px' }}>
<StateExportImportControls />
</div>
</div>;
}
}
export class StateExportImportControls extends PluginUIComponent {
export class StateExportImportControls extends PluginUIComponent<{ onAction?: () => void }> {
downloadToFileJson = () => {
this.props.onAction?.();
PluginCommands.State.Snapshots.DownloadToFile(this.plugin, { type: 'json' });
}
downloadToFileZip = () => {
this.props.onAction?.();
PluginCommands.State.Snapshots.DownloadToFile(this.plugin, { type: 'zip' });
}
@@ -49,16 +68,17 @@ export class StateExportImportControls extends PluginUIComponent {
return;
}
this.props.onAction?.();
PluginCommands.State.Snapshots.OpenFile(this.plugin, { file: e.target.files[0] });
}
render() {
return <div className='msp-flex-row'>
<Button icon={GetApp} onClick={this.downloadToFileJson} title='Save the state description. Input data are loaded using the provided sources. Does not work if local files are used as input.'>
Base
State
</Button>
<Button icon={GetApp} onClick={this.downloadToFileZip} title='Save the state including the input data.'>
Full
Session
</Button>
<div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file'>
<Icon svg={OpenInBrowser} inline /> Open <input onChange={this.open} type='file' multiple={false} accept='.molx,.molj' />
@@ -67,37 +87,35 @@ export class StateExportImportControls extends PluginUIComponent {
}
}
export class LocalStateSnapshotParams extends PluginUIComponent {
componentDidMount() {
this.subscribe(this.plugin.state.snapshotParams, () => this.forceUpdate());
}
render() {
return <ParameterControls params={PluginState.SnapshotParams} values={this.plugin.state.snapshotParams.value} onChangeValues={this.plugin.state.setSnapshotParams} />;
}
}
class LocalStateSnapshots extends PluginUIComponent<
{},
{ params: PD.Values<typeof LocalStateSnapshots.Params> }> {
state = { params: PD.getDefaultValues(LocalStateSnapshots.Params) };
static Params = {
name: PD.Text(),
options: PD.Group({
description: PD.Text(),
...PluginState.GetSnapshotParams
})
description: PD.Text()
};
add = () => {
PluginCommands.State.Snapshots.Add(this.plugin, {
name: this.state.params.name,
description: this.state.params.options.description,
params: this.state.params.options
});
this.setState({
params: {
name: '',
options: {
...this.state.params.options,
description: ''
}
}
description: this.state.params.description
});
}
updateParams = (params: PD.Values<typeof LocalStateSnapshots.Params>) => this.setState({ params });
clear = () => {
PluginCommands.State.Snapshots.Clear(this.plugin, {});
}
@@ -107,17 +125,11 @@ class LocalStateSnapshots extends PluginUIComponent<
}
render() {
// TODO: proper styling
return <div>
<ParameterControls params={LocalStateSnapshots.Params} values={this.state.params} onEnter={this.add} onChange={p => {
const params = { ...this.state.params, [p.name]: p.value };
this.setState({ params } as any);
this.plugin.managers.snapshot.currentGetSnapshotParams = params.options;
}} />
<ParameterControls params={LocalStateSnapshots.Params} values={this.state.params} onEnter={this.add} onChangeValues={this.updateParams} />
<div className='msp-flex-row'>
<Button onClick={this.add}>Save</Button>
<Button onClick={this.clear}>Clear</Button>
<IconButton onClick={this.clear} svg={DeleteOutlined} title='Remove All' />
<Button onClick={this.add} icon={Add} style={{ textAlign: 'right' }} commit>Add</Button>
</div>
</div>;
}
@@ -155,12 +167,12 @@ class LocalStateSnapshotList extends PluginUIComponent<{}, {}> {
replace = (e: React.MouseEvent<HTMLElement>) => {
const id = e.currentTarget.getAttribute('data-id');
if (!id) return;
PluginCommands.State.Snapshots.Replace(this.plugin, { id, params: this.plugin.managers.snapshot.currentGetSnapshotParams });
PluginCommands.State.Snapshots.Replace(this.plugin, { id });
}
render() {
const current = this.plugin.managers.snapshot.state.current;
return <ul style={{ listStyle: 'none', marginTop: '1px' }} className='msp-state-list'>
return <ul style={{ listStyle: 'none', marginTop: '10px' }} className='msp-state-list'>
{this.plugin.managers.snapshot.state.entries.map(e => <li key={e!.snapshot.id} className='msp-flex-row'>
<Button data-id={e!.snapshot.id} onClick={this.apply} className='msp-no-overflow'>
{(console.log(e!.snapshot.durationInMs), false)}
@@ -246,8 +258,7 @@ export class RemoteStateSnapshots extends PluginUIComponent<
name: this.state.params.name,
description: this.state.params.options.description,
playOnLoad: this.state.params.options.playOnLoad,
serverUrl: this.state.params.options.serverUrl,
params: this.plugin.managers.snapshot.currentGetSnapshotParams
serverUrl: this.state.params.options.serverUrl
});
this.setState({ isBusy: false });
@@ -283,29 +294,29 @@ export class RemoteStateSnapshots extends PluginUIComponent<
render() {
return <>
<SectionHeader title='Remote States' />
<SectionHeader title='Remote States' accent='blue' />
{!this.props.listOnly && <>
<ParameterControls params={this.Params} values={this.state.params} onEnter={this.upload} onChange={p => {
this.setState({ params: { ...this.state.params, [p.name]: p.value } } as any);
}} isDisabled={this.state.isBusy} />
<div className='msp-flex-row'>
<Button icon={CloudUpload} onClick={this.upload} disabled={this.state.isBusy}>Upload</Button>
<Button onClick={this.refresh} disabled={this.state.isBusy}>Refresh</Button>
<IconButton onClick={this.refresh} disabled={this.state.isBusy} svg={Refresh} />
<Button icon={CloudUpload} onClick={this.upload} disabled={this.state.isBusy} commit>Upload</Button>
</div>
</>}
<RemoteStateSnapshotList entries={this.state.entries} isBusy={this.state.isBusy} serverUrl={this.state.params.options.serverUrl}
fetch={this.fetch} remove={this.props.listOnly ? void 0 : this.remove} />
{this.props.listOnly && <>
{this.props.listOnly && <div style={{ marginTop: '10px' }}>
<ParameterControls params={this.ListOnlyParams} values={this.state.params} onEnter={this.upload} onChange={p => {
this.setState({ params: { ...this.state.params, [p.name]: p.value } } as any);
}} isDisabled={this.state.isBusy} />
<div className='msp-flex-row'>
<Button onClick={this.refresh} disabled={this.state.isBusy}>Refresh</Button>
<Button onClick={this.refresh} disabled={this.state.isBusy} icon={Refresh}>Refresh</Button>
</div>
</>}
</div>}
</>;
}
}
@@ -328,7 +339,7 @@ class RemoteStateSnapshotList extends PurePluginUIComponent<
}
render() {
return <ul style={{ listStyle: 'none' }} className='msp-state-list'>
return <ul style={{ listStyle: 'none', marginTop: '10px' }} className='msp-state-list'>
{this.props.entries.valueSeq().map(e => <li key={e!.id} className='msp-flex-row'>
<Button data-id={e!.id} onClick={this.props.fetch}
disabled={this.props.isBusy} onContextMenu={this.open} title='Click to download, right-click to open in a new tab.'>

View File

@@ -4,7 +4,13 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { ArrowDropDown, ArrowRight, Close, DeleteOutlined, HomeOutlined, VisibilityOffOutlined, VisibilityOutlined } from '@material-ui/icons';
import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
import ArrowRight from '@material-ui/icons/ArrowRight';
import Close from '@material-ui/icons/Close';
import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
import HomeOutlined from '@material-ui/icons/HomeOutlined';
import VisibilityOffOutlined from '@material-ui/icons/VisibilityOffOutlined';
import VisibilityOutlined from '@material-ui/icons/VisibilityOutlined';
import * as React from 'react';
import { debounceTime, filter } from 'rxjs/operators';
import { PluginStateObject } from '../../mol-plugin-state/objects';
@@ -239,7 +245,9 @@ class StateTreeNodeLabel extends PluginUIComponent<{ cell: StateObjectCell, dept
e.currentTarget.blur();
}
hideApply = () => this.setState({ action: 'options', currentAction: void 0 });
hideApply = () => {
this.setCurrentRoot();
}
get actions() {
const cell = this.props.cell;

View File

@@ -4,19 +4,27 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Add, BookmarksOutlined, Delete, DeleteOutlined, MoreHoriz, Tune, Restore, VisibilityOutlined, VisibilityOffOutlined } from '@material-ui/icons';
import Add from '@material-ui/icons/Add';
import BookmarksOutlined from '@material-ui/icons/BookmarksOutlined';
import Delete from '@material-ui/icons/Delete';
import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
import MoreHoriz from '@material-ui/icons/MoreHoriz';
import Restore from '@material-ui/icons/Restore';
import Tune from '@material-ui/icons/Tune';
import VisibilityOffOutlined from '@material-ui/icons/VisibilityOffOutlined';
import VisibilityOutlined from '@material-ui/icons/VisibilityOutlined';
import * as React from 'react';
import { getStructureThemeTypes } from '../../mol-plugin-state/helpers/structure-representation-params';
import { StructureComponentManager } from '../../mol-plugin-state/manager/structure/component';
import { StructureHierarchyManager } from '../../mol-plugin-state/manager/structure/hierarchy';
import { StructureComponentRef, StructureRef, StructureRepresentationRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { StructureComponentRef, StructureRepresentationRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { PluginCommands } from '../../mol-plugin/commands';
import { State } from '../../mol-state';
import { ParamDefinition } from '../../mol-util/param-definition';
import { CollapsableControls, CollapsableState, PurePluginUIComponent } from '../base';
import { ActionMenu } from '../controls/action-menu';
import { Button, ExpandGroup, IconButton, ToggleButton } from '../controls/common';
import { Intersect, SetSvg, Subtract, Union, CubeSvg } from '../controls/icons';
import { CubeSvg, Intersect, SetSvg, Subtract, Union } from '../controls/icons';
import { ParameterControls } from '../controls/parameters';
import { UpdateTransformControl } from '../state/update-transform';
import { GenericEntryListControls } from './generic';
@@ -129,7 +137,7 @@ class ComponentEditorControls extends PurePluginUIComponent<{}, ComponentEditorC
</div>
{this.state.action === 'preset' && this.presetControls}
{this.state.action === 'add' && <div className='msp-control-offset'>
<AddComponentControls structures={this.plugin.managers.structure.component.currentStructures} onApply={this.hideAction} />
<AddComponentControls onApply={this.hideAction} />
</div>}
{this.state.action === 'options' && <div className='msp-control-offset'><ComponentOptionsControls isDisabled={this.isDisabled} /></div>}
</>;
@@ -142,31 +150,32 @@ interface AddComponentControlsState {
}
interface AddComponentControlsProps {
structures: ReadonlyArray<StructureRef>,
forSelection?: boolean,
onApply: () => void
}
class AddComponentControls extends PurePluginUIComponent<AddComponentControlsProps, AddComponentControlsState> {
export class AddComponentControls extends PurePluginUIComponent<AddComponentControlsProps, AddComponentControlsState> {
createState(): AddComponentControlsState {
const params = StructureComponentManager.getAddParams(this.plugin);
const params = StructureComponentManager.getAddParams(this.plugin, this.props.forSelection
? { allowNone: false, hideSelection: true, checkExisting: this.props.forSelection }
: void 0);
return { params, values: ParamDefinition.getDefaultValues(params) };
}
state = this.createState();
get selectedStructures() {
return this.plugin.managers.structure.component.currentStructures;
}
apply = () => {
this.plugin.managers.structure.component.add(this.state.values, this.props.structures);
const structures = this.props.forSelection ? this.plugin.managers.structure.hierarchy.getStructuresWithSelection() : this.selectedStructures;
this.props.onApply();
this.plugin.managers.structure.component.add(this.state.values, structures);
}
paramsChanged = (values: any) => this.setState({ values })
componentDidUpdate(prevProps: AddComponentControlsProps) {
if (this.props.structures !== prevProps.structures) {
this.setState(this.createState());
}
}
render() {
return <>
<ParameterControls params={this.state.params} values={this.state.values} onChangeValues={this.paramsChanged} />
@@ -206,8 +215,6 @@ class ComponentListControls extends PurePluginUIComponent {
}
}
type StructureComponentEntryActions = 'action' | 'remove'
class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureComponentRef[] }, { action?: StructureComponentEntryActions }> {

View File

@@ -4,7 +4,8 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { CenterFocusStrong, Clear } from '@material-ui/icons';
import CenterFocusStrong from '@material-ui/icons/CenterFocusStrong';
import CancelOutlined from '@material-ui/icons/CancelOutlined';
import * as React from 'react';
import { OrderedSet, SortedArray } from '../../mol-data/int';
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
@@ -237,7 +238,7 @@ export class StructureFocusControls extends PluginUIComponent<{}, StructureFocus
style={{ textAlignLast: current ? 'left' : void 0 }}>
{label}
</Button>
{current && <IconButton svg={Clear} onClick={this.clear} title='Clear' className='msp-form-control' flex disabled={this.isDisabled} />}
{current && <IconButton svg={CancelOutlined} onClick={this.clear} title='Clear' className='msp-form-control' flex disabled={this.isDisabled} />}
<ToggleButton icon={CenterFocusStrong} title='Select a focus target to center on an show its surroundings. Hold shift to focus on multiple targets.' toggle={this.toggleAction} isSelected={this.state.showAction} disabled={this.isDisabled} style={{ flex: '0 0 40px', padding: 0 }} />
</div>
{this.state.showAction && <ActionMenu items={this.actionItems} onSelect={this.selectAction} />}

View File

@@ -5,9 +5,11 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { MoreHoriz, VisibilityOutlined, VisibilityOffOutlined } from '@material-ui/icons';
import MoreHoriz from '@material-ui/icons/MoreHoriz';
import VisibilityOutlined from '@material-ui/icons/VisibilityOutlined';
import VisibilityOffOutlined from '@material-ui/icons/VisibilityOffOutlined';
import * as React from 'react';
import { HierarchyRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { StructureHierarchyRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { PluginCommands } from '../../mol-plugin/commands';
import { State } from '../../mol-state';
import { PurePluginUIComponent } from '../base';
@@ -60,7 +62,7 @@ export class GenericEntryListControls extends PurePluginUIComponent {
}
}
export class GenericEntry<T extends HierarchyRef> extends PurePluginUIComponent<{ refs: T[], labelMultiple?: string }, { showOptions: boolean }> {
export class GenericEntry<T extends StructureHierarchyRef> extends PurePluginUIComponent<{ refs: T[], labelMultiple?: string }, { showOptions: boolean }> {
state = { showOptions: false }
componentDidMount() {

View File

@@ -5,7 +5,16 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Add, ArrowDownward, ArrowUpward, DeleteOutlined, HelpOutline, MoreHoriz, RemoveOutlined, Tune, VisibilityOutlined, VisibilityOffOutlined } from '@material-ui/icons';
import Add from '@material-ui/icons/Add';
import ArrowDownward from '@material-ui/icons/ArrowDownward';
import ArrowUpward from '@material-ui/icons/ArrowUpward';
import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
import HelpOutline from '@material-ui/icons/HelpOutline';
import MoreHoriz from '@material-ui/icons/MoreHoriz';
import RemoveOutlined from '@material-ui/icons/RemoveOutlined';
import Tune from '@material-ui/icons/Tune';
import VisibilityOutlined from '@material-ui/icons/VisibilityOutlined';
import VisibilityOffOutlined from '@material-ui/icons/VisibilityOffOutlined';
import * as React from 'react';
import { Loci } from '../../mol-model/loci';
import { StructureElement } from '../../mol-model/structure';

View File

@@ -5,12 +5,16 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Close, Clear, Brush } from '@material-ui/icons';
import Close from '@material-ui/icons/Close';
import CancelOutlined from '@material-ui/icons/CancelOutlined';
import Brush from '@material-ui/icons/Brush';
import Restore from '@material-ui/icons/Restore';
import Remove from '@material-ui/icons/Remove';
import * as React from 'react';
import { StructureSelectionQueries, StructureSelectionQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
import { InteractivityManager } from '../../mol-plugin-state/manager/interactivity';
import { StructureComponentManager } from '../../mol-plugin-state/manager/structure/component';
import { StructureRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { StructureRef, StructureComponentRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { StructureSelectionModifier } from '../../mol-plugin-state/manager/structure/selection';
import { memoizeLatest } from '../../mol-util/memoize';
import { ParamDefinition } from '../../mol-util/param-definition';
@@ -19,84 +23,26 @@ import { PluginUIComponent, PurePluginUIComponent } from '../base';
import { ActionMenu } from '../controls/action-menu';
import { Button, ControlGroup, IconButton, ToggleButton } from '../controls/common';
import { ParameterControls, ParamOnChange, PureSelectControl } from '../controls/parameters';
import { Union, Subtract, Intersect, SetSvg as SetSvg } from '../controls/icons';
import { Union, Subtract, Intersect, SetSvg as SetSvg, CubeSvg } from '../controls/icons';
import { AddComponentControls } from './components';
const StructureSelectionParams = {
granularity: InteractivityManager.Params.granularity,
};
// interface StructureSelectionControlsState extends CollapsableState {
// isEmpty: boolean,
// isBusy: boolean,
// }
// export class StructureSelectionControls<P, S extends StructureSelectionControlsState> extends CollapsableControls<P, S> {
// componentDidMount() {
// this.subscribe(this.plugin.managers.structure.selection.events.changed, () => {
// this.forceUpdate()
// });
// this.subscribe(this.plugin.managers.interactivity.events.propsUpdated, () => {
// this.forceUpdate()
// });
// this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, c => {
// const isEmpty = c.structures.length === 0;
// if (this.state.isEmpty !== isEmpty) {
// this.setState({ isEmpty });
// }
// });
// }
// get isDisabled() {
// return this.state.isBusy || this.state.isEmpty
// }
// setProps = (props: any) => {
// this.plugin.managers.interactivity.setProps(props);
// }
// get values () {
// return {
// granularity: this.plugin.managers.interactivity.props.granularity,
// }
// }
// defaultState() {
// return {
// isCollapsed: false,
// header: 'Selection',
// isEmpty: true,
// isBusy: false,
// brand: { name: 'Sel', accent: 'red' }
// } as S
// }
// renderControls() {
// return <>
// {/* <ParameterControls params={StructureSelectionParams} values={this.values} onChangeValues={this.setProps} />
// <StructureSelectionActionsControls /> */}
// <StructureSelectionStatsControls />
// {/* <div style={{ margin: '6px 0' }}>
// </div> */}
// </>
// }
// }
interface StructureSelectionActionsControlsState {
isEmpty: boolean,
isBusy: boolean,
canUndo: boolean,
action?: StructureSelectionModifier | 'color'
action?: StructureSelectionModifier | 'color' | 'add-repr'
}
const ActionHeader = new Map<StructureSelectionModifier, string>([
['add', 'Add/Union'],
['remove', 'Remove/Subtract'],
['intersect', 'Intersect'],
['set', 'Set']
['add', 'Add/Union Selection'],
['remove', 'Remove/Subtract Selection'],
['intersect', 'Intersect Selection'],
['set', 'Set Selection']
] as const);
export class StructureSelectionActionsControls extends PluginUIComponent<{}, StructureSelectionActionsControlsState> {
@@ -105,11 +51,12 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
isEmpty: true,
isBusy: false,
canUndo: false,
}
componentDidMount() {
this.subscribe(this.plugin.managers.structure.hierarchy.behaviors.selection, c => {
const isEmpty = c.structures.length === 0;
const isEmpty = c.hierarchy.structures.length === 0;
if (this.state.isEmpty !== isEmpty) {
this.setState({ isEmpty });
}
@@ -122,6 +69,10 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
this.subscribe(this.plugin.managers.interactivity.events.propsUpdated, () => {
this.forceUpdate();
});
this.subscribe(this.plugin.state.data.events.historyUpdated, ({ state }) => {
this.setState({ canUndo: state.canUndo });
});
}
get isDisabled() {
@@ -168,6 +119,7 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
toggleIntersect = this.showAction('intersect')
toggleSet = this.showAction('set')
toggleColor = this.showAction('color')
toggleAddRepr = this.showAction('add-repr')
setGranuality: ParamOnChange = ({ value }) => {
this.plugin.managers.interactivity.setProps({ granularity: value });
@@ -175,24 +127,51 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
turnOff = () => this.plugin.selectionMode = false;
undo = () => {
const task = this.plugin.state.data.undo();
if (task) this.plugin.runTask(task);
}
subtract = () => {
const sel = this.plugin.managers.structure.hierarchy.getStructuresWithSelection();
const components: StructureComponentRef[] = [];
for (const s of sel) components.push(...s.components);
if (components.length === 0) return;
this.plugin.managers.structure.component.modifyByCurrentSelection(components, 'subtract');
}
render() {
const granularity = this.plugin.managers.interactivity.props.granularity;
const undoTitle = this.state.canUndo
? `Undo ${this.plugin.state.data.latestUndoLabel}`
: 'Some mistakes of the past can be undone.';
return <>
<div className='msp-flex-row'>
<div className='msp-flex-row' style={{ background: 'none' }}>
<PureSelectControl title={`Picking Level`} param={StructureSelectionParams.granularity} name='granularity' value={granularity} onChange={this.setGranuality} isDisabled={this.isDisabled} />
<ToggleButton icon={Union} title={ActionHeader.get('add')} toggle={this.toggleAdd} isSelected={this.state.action === 'add'} disabled={this.isDisabled} />
<ToggleButton icon={Subtract} title={ActionHeader.get('remove')} toggle={this.toggleRemove} isSelected={this.state.action === 'remove'} disabled={this.isDisabled} />
<ToggleButton icon={Intersect} title={ActionHeader.get('intersect')} toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} />
<ToggleButton icon={SetSvg} title={ActionHeader.get('set')} toggle={this.toggleSet} isSelected={this.state.action === 'set'} disabled={this.isDisabled} />
<ToggleButton icon={Brush} title='Color' toggle={this.toggleColor} isSelected={this.state.action === 'color'} disabled={this.isDisabled} />
<PureSelectControl title={`Picking Level`} param={StructureSelectionParams.granularity} name='granularity' value={granularity} onChange={this.setGranuality} isDisabled={this.isDisabled} />
<IconButton svg={Close} title='Turn selection mode off' onClick={this.turnOff} />
<ToggleButton icon={Brush} title='Color' toggle={this.toggleColor} isSelected={this.state.action === 'color'} disabled={this.isDisabled} style={{ marginLeft: '10px' }} />
<ToggleButton icon={CubeSvg} title='Create Representation' toggle={this.toggleAddRepr} isSelected={this.state.action === 'add-repr'} disabled={this.isDisabled} />
<IconButton svg={Remove} title='Subtract from Representations' onClick={this.subtract} disabled={this.isDisabled} />
<IconButton svg={Restore} onClick={this.undo} disabled={!this.state.canUndo || this.isDisabled} title={undoTitle} />
<IconButton svg={CancelOutlined} title='Turn selection mode off' onClick={this.turnOff} style={{ marginLeft: '10px' }} />
</div>
{(this.state.action && this.state.action !== 'color') && <div className='msp-selection-viewport-controls-actions'>
{(this.state.action && this.state.action !== 'color' && this.state.action !== 'add-repr') && <div className='msp-selection-viewport-controls-actions'>
<ActionMenu header={ActionHeader.get(this.state.action as StructureSelectionModifier)} items={this.queries} onSelect={this.selectQuery} noOffset />
</div>}
{this.state.action === 'color' && <div className='msp-selection-viewport-controls-actions'>
<ControlGroup header='Color' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleColor} topRightIcon={Close}>
<ApplyColorControls />
<ApplyColorControls onApply={this.toggleColor} />
</ControlGroup>
</div>}
{this.state.action === 'add-repr' && <div className='msp-selection-viewport-controls-actions'>
<ControlGroup header='Add Representation' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleAddRepr} topRightIcon={Close}>
<AddComponentControls onApply={this.toggleAddRepr} forSelection />
</ControlGroup>
</div>}
</>;
@@ -266,7 +245,7 @@ export class StructureSelectionStatsControls extends PluginUIComponent<{ hideOnE
style={{ textAlignLast: !empty ? 'left' : void 0 }}>
{this.stats}
</Button>
{!empty && <IconButton svg={Clear} onClick={this.clear} title='Clear' className='msp-form-control' flex />}
{!empty && <IconButton svg={CancelOutlined} onClick={this.clear} title='Clear' className='msp-form-control' flex />}
</div>
</>;
}

View File

@@ -6,7 +6,7 @@
*/
import * as React from 'react';
import { HierarchyRef, ModelRef, TrajectoryRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { StructureHierarchyRef, ModelRef, TrajectoryRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { CollapsableControls, CollapsableState } from '../base';
import { ActionMenu } from '../controls/action-menu';
@@ -16,7 +16,7 @@ import { StructureFocusControls } from './focus';
import { UpdateTransformControl } from '../state/update-transform';
import { StructureSelectionStatsControls } from './selection';
import { StateSelection } from '../../mol-state';
import { BookmarksOutlined } from '@material-ui/icons';
import BookmarksOutlined from '@material-ui/icons/BookmarksOutlined';
import { MoleculeSvg } from '../controls/icons';
interface StructureSourceControlState extends CollapsableState {
@@ -41,7 +41,7 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
});
}
private item = (ref: HierarchyRef) => {
private item = (ref: StructureHierarchyRef) => {
const selected = this.plugin.managers.structure.hierarchy.seletionSet;
let label;
@@ -179,11 +179,11 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
}
selectHierarchy: ActionMenu.OnSelectMany = (items) => {
if (!items || items.length === 0) return 0;
if (!items || items.length === 0) return;
const refs: HierarchyRef[] = [];
const refs: StructureHierarchyRef[] = [];
for (const i of items) {
for (const r of (i.value as HierarchyRef[])) refs.push(r);
for (const r of (i.value as StructureHierarchyRef[])) refs.push(r);
}
this.plugin.managers.structure.hierarchy.updateCurrent(refs, items[0].selected ? 'remove' : 'add');

View File

@@ -5,18 +5,29 @@
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import Add from '@material-ui/icons/Add';
import BlurOn from '@material-ui/icons/BlurOn';
import Check from '@material-ui/icons/Check';
import ErrorSvg from '@material-ui/icons/Error';
import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
import MoreHoriz from '@material-ui/icons/MoreHoriz';
import VisibilityOffOutlined from '@material-ui/icons/VisibilityOffOutlined';
import VisibilityOutlined from '@material-ui/icons/VisibilityOutlined';
import * as React from 'react';
import { StructureHierarchyManager } from '../../mol-plugin-state/manager/structure/hierarchy';
import { VolumeHierarchyManager } from '../../mol-plugin-state/manager/volume/hierarchy';
import { VolumeRef, VolumeRepresentationRef } from '../../mol-plugin-state/manager/volume/hierarchy-state';
import { FocusLoci } from '../../mol-plugin/behavior/dynamic/representation';
import { VolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/behavior';
import { InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
import { CollapsableControls, CollapsableState } from '../base';
import { State, StateSelection, StateTransform } from '../../mol-state';
import { CollapsableControls, CollapsableState, PurePluginUIComponent } from '../base';
import { ActionMenu } from '../controls/action-menu';
import { Button, ExpandGroup, IconButton } from '../controls/common';
import { ApplyActionControl } from '../state/apply-action';
import { UpdateTransformControl } from '../state/update-transform';
import { BindingsHelp } from '../viewport/help';
import { ExpandGroup } from '../controls/common';
import { StructureHierarchyManager } from '../../mol-plugin-state/manager/structure/hierarchy';
import { FocusLoci } from '../../mol-plugin/behavior/dynamic/representation';
import { StateSelection, StateTransform } from '../../mol-state';
import { VolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/behavior';
import { Check, Error as ErrorSvg, BlurOn } from '@material-ui/icons';
import { PluginCommands } from '../../mol-plugin/commands';
interface VolumeStreamingControlState extends CollapsableState {
isBusy: boolean
@@ -96,4 +107,164 @@ export class VolumeStreamingControls extends CollapsableControls<{}, VolumeStrea
if (!pivot.volumeStreaming) return this.renderEnable();
return this.renderParams();
}
}
interface VolumeSourceControlState extends CollapsableState {
isBusy: boolean,
show?: 'hierarchy' | 'add-repr'
}
export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceControlState> {
protected defaultState(): VolumeSourceControlState {
return {
header: 'Volume',
isCollapsed: false,
isBusy: false,
isHidden: true,
brand: { accent: 'purple', svg: BlurOn }
};
}
componentDidMount() {
this.subscribe(this.plugin.managers.volume.hierarchy.behaviors.selection, sel => {
this.setState({ isHidden: sel.hierarchy.volumes.length === 0 });
});
this.subscribe(this.plugin.behaviors.state.isBusy, v => {
this.setState({ isBusy: v });
});
}
private item = (ref: VolumeRef) => {
const selected = this.plugin.managers.volume.hierarchy.selection;
const label = ref.cell.obj?.data.label || 'Volume';
const item: ActionMenu.Item = { kind: 'item', label: label || ref.kind, selected: selected === ref, value: ref };
return item;
}
get hierarchyItems() {
const mng = this.plugin.managers.volume.hierarchy;
const { current } = mng;
const ret: ActionMenu.Items = [];
for (let ref of current.volumes) {
ret.push(this.item(ref));
}
return ret;
}
get addActions(): ActionMenu.Items {
const mng = this.plugin.managers.volume.hierarchy;
const current = mng.selection;
const ret: ActionMenu.Items = [
...VolumeHierarchyManager.getRepresentationTypes(this.plugin, current)
.map(t => ActionMenu.Item(t[1], () => mng.addRepresentation(current!, t[0])))
];
return ret;
}
get isEmpty() {
const { volumes } = this.plugin.managers.volume.hierarchy.current;
return volumes.length === 0;
}
get label() {
const selected = this.plugin.managers.volume.hierarchy.selection;
if (!selected) return 'Nothing Selected';
return selected?.cell.obj?.data.label || 'Volume';
}
selectCurrent: ActionMenu.OnSelect = (item) => {
this.toggleHierarchy();
if (!item) return;
this.plugin.managers.volume.hierarchy.setCurrent(item.value as VolumeRef);
}
selectAdd: ActionMenu.OnSelect = (item) => {
if (!item) return;
this.setState({ show: void 0 });
(item.value as any)();
}
toggleHierarchy = () => this.setState({ show: this.state.show !== 'hierarchy' ? 'hierarchy' : void 0 });
toggleAddRepr = () => this.setState({ show: this.state.show !== 'add-repr' ? 'add-repr' : void 0 });
renderControls() {
const disabled = this.state.isBusy || this.isEmpty;
const label = this.label;
const selected = this.plugin.managers.volume.hierarchy.selection;
return <>
<div className='msp-flex-row' style={{ marginTop: '1px' }}>
<Button noOverflow flex onClick={this.toggleHierarchy} disabled={disabled} title={label}>{label}</Button>
{!this.isEmpty && <IconButton svg={Add} onClick={this.toggleAddRepr} title='Apply a structure presets to the current hierarchy.' toggleState={this.state.show === 'add-repr'} disabled={disabled} />}
</div>
{this.state.show === 'hierarchy' && <ActionMenu items={this.hierarchyItems} onSelect={this.selectCurrent} />}
{this.state.show === 'add-repr' && <ActionMenu items={this.addActions} onSelect={this.selectAdd} />}
{selected && selected.representations.length > 0 && <div style={{ marginTop: '6px' }}>
{selected.representations.map(r => <VolumeRepresentationControls key={r.cell.transform.ref} representation={r} />)}
</div>}
</>;
}
}
type VolumeRepresentationEntryActions = 'update'
class VolumeRepresentationControls extends PurePluginUIComponent<{ representation: VolumeRepresentationRef }, { action?: VolumeRepresentationEntryActions }> {
state = { action: void 0 as VolumeRepresentationEntryActions | undefined }
componentDidMount() {
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
if (State.ObjectEvent.isCell(e, this.props.representation.cell)) this.forceUpdate();
});
}
remove = () => this.plugin.managers.volume.hierarchy.remove([ this.props.representation ], true);
toggleVisible = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
e.currentTarget.blur();
this.plugin.managers.volume.hierarchy.toggleVisibility([ this.props.representation ]);
}
toggleUpdate = () => this.setState({ action: this.state.action === 'update' ? void 0 : 'update' });
highlight = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
if (!this.props.representation.cell.parent) return;
PluginCommands.Interactivity.Object.Highlight(this.plugin, { state: this.props.representation.cell.parent!, ref: this.props.representation.cell.transform.ref });
}
clearHighlight = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
PluginCommands.Interactivity.ClearHighlights(this.plugin);
}
focus = () => {
const repr = this.props.representation;
const objects = this.props.representation.cell.obj?.data.repr.renderObjects;
if (repr.cell.state.isHidden) this.plugin.managers.volume.hierarchy.toggleVisibility([this.props.representation], 'show');
this.plugin.managers.camera.focusRenderObjects(objects, { extraRadius: 1 });
}
render() {
const repr = this.props.representation.cell;
return <>
<div className='msp-flex-row'>
<Button noOverflow className='msp-control-button-label' title={`${repr.obj?.label}. Click to focus.`} onClick={this.focus} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight} style={{ textAlign: 'left' }}>
{repr.obj?.label}
<small className='msp-25-lower-contrast-text' style={{ float: 'right' }}>{repr.obj?.description}</small>
</Button>
<IconButton svg={repr.state.isHidden ? VisibilityOffOutlined : VisibilityOutlined} toggleState={false} onClick={this.toggleVisible} title={`${repr.state.isHidden ? 'Show' : 'Hide'} component`} small className='msp-form-control' flex />
<IconButton svg={DeleteOutlined} onClick={this.remove} title='Remove' small />
<IconButton svg={MoreHoriz} onClick={this.toggleUpdate} title='Actions' toggleState={this.state.action === 'update'} />
</div>
{this.state.action === 'update' && !!repr.parent && <div style={{ marginBottom: '6px' }} className='msp-accent-offset'>
<UpdateTransformControl state={repr.parent} transform={repr.transform} customHeader='none' noMargin />
</div>}
</>;
}
}

View File

@@ -11,7 +11,7 @@ import { TaskManager } from '../mol-plugin/util/task-manager';
import { filter } from 'rxjs/operators';
import { Progress } from '../mol-task';
import { IconButton } from './controls/common';
import { Cancel } from '@material-ui/icons';
import Cancel from '@material-ui/icons/Cancel';
export class BackgroundTaskProgress extends PluginUIComponent<{ }, { tracked: OrderedMap<number, TaskManager.ProgressEvent> }> {
componentDidMount() {

View File

@@ -10,7 +10,7 @@ import * as React from 'react';
import { PluginUIComponent } from './base';
import { PluginToastManager } from '../mol-plugin/util/toast';
import { IconButton } from './controls/common';
import { Cancel } from '@material-ui/icons';
import Cancel from '@material-ui/icons/Cancel';
class ToastEntry extends PluginUIComponent<{ entry: PluginToastManager.Entry }> {
private hide = () => {

View File

@@ -5,7 +5,13 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Autorenew, BuildOutlined, CameraOutlined, Close, Crop, Fullscreen, Tune } from '@material-ui/icons';
import Autorenew from '@material-ui/icons/Autorenew';
import BuildOutlined from '@material-ui/icons/BuildOutlined';
import CameraOutlined from '@material-ui/icons/CameraOutlined';
import Close from '@material-ui/icons/Close';
import Crop from '@material-ui/icons/Crop';
import Fullscreen from '@material-ui/icons/Fullscreen';
import Tune from '@material-ui/icons/Tune';
import * as React from 'react';
import { resizeCanvas } from '../mol-canvas3d/util';
import { PluginCommands } from '../mol-plugin/commands';
@@ -108,8 +114,8 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
</div>}
</div>
{this.state.isScreenshotExpanded && <div className='msp-viewport-controls-panel'>
<ControlGroup header='Screenshot / State Snapshot' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleScreenshotExpanded}
topRightIcon={Close} noTopMargin>
<ControlGroup header='Screenshot / State' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleScreenshotExpanded}
topRightIcon={Close} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
<DownloadScreenshotControls close={this.toggleScreenshotExpanded} />
</ControlGroup>
</div>}

View File

@@ -12,7 +12,9 @@ import { SelectLoci } from '../../mol-plugin/behavior/dynamic/representation';
import { FocusLoci } from '../../mol-plugin/behavior/dynamic/representation';
import { Icon } from '../controls/icons';
import { Button } from '../controls/common';
import { ArrowRight, ArrowDropDown, Camera } from '@material-ui/icons';
import ArrowRight from '@material-ui/icons/ArrowRight';
import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
import Camera from '@material-ui/icons/Camera';
function getBindingsList(bindings: { [k: string]: Binding }) {
return Object.keys(bindings).map(k => [k, bindings[k]] as [string, Binding]);

View File

@@ -14,10 +14,12 @@ import { Subject } from 'rxjs';
import { ViewportScreenshotHelper } from '../../mol-plugin/util/viewport-screenshot';
import { Button, ExpandGroup } from '../controls/common';
import { CameraHelperProps } from '../../mol-canvas3d/helper/camera-helper';
import { GetApp, Launch, Warning } from '@material-ui/icons';
import GetApp from '@material-ui/icons/GetApp';
import Launch from '@material-ui/icons/Launch';
import Warning from '@material-ui/icons/Warning';
import { PluginCommands } from '../../mol-plugin/commands';
import { Icon } from '../controls/icons';
import { StateExportImportControls } from '../state/snapshots';
import { StateExportImportControls, LocalStateSnapshotParams } from '../state/snapshots';
interface ImageControlsState {
showPreview: boolean
@@ -145,8 +147,11 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
<Button icon={Launch} onClick={this.openTab} disabled={this.state.isDisabled}>Open in new Tab</Button>
</div>
<ParameterControls params={this.plugin.helpers.viewportScreenshot!.params} values={this.plugin.helpers.viewportScreenshot!.values} onChange={this.setProps} isDisabled={this.state.isDisabled} />
<ExpandGroup header='State Snapshot'>
<StateExportImportControls />
<ExpandGroup header='State'>
<StateExportImportControls onAction={this.props.close} />
<ExpandGroup header='Save Options' initiallyExpanded={false} noOffset>
<LocalStateSnapshotParams />
</ExpandGroup>
<div className='msp-help-text' style={{ padding: '10px'}}>
<Icon svg={Warning} /> This is an experimental feature and stored states might not be openable in a future version.
</div>

View File

@@ -23,6 +23,7 @@ import { StateSelection } from '../../../../mol-state';
import { StructureElement, Structure } from '../../../../mol-model/structure';
import { PluginContext } from '../../../context';
import { EmptyLoci, Loci, isEmptyLoci } from '../../../../mol-model/loci';
import { Asset } from '../../../../mol-util/assets';
export class VolumeStreaming extends PluginStateObject.CreateBehavior<VolumeStreaming.Behavior>({ name: 'Volume Streaming' }) { }
@@ -126,7 +127,7 @@ export namespace VolumeStreaming {
export type DefaultChannelParams = { [name in ChannelType]?: Partial<ChannelParams> }
export class Behavior extends PluginBehavior.WithSubscribers<Params> {
private cache = LRUCache.create<ChannelsData>(25);
private cache = LRUCache.create<{ data: ChannelsData, asset: Asset.Wrapper }>(25);
public params: Params = {} as any;
private lastLoci: StructureElement.Loci | EmptyLoci = EmptyLoci;
private ref: string = '';
@@ -151,18 +152,16 @@ export namespace VolumeStreaming {
}
url += `?detail=${this.params.entry.params.detailLevel}`;
let data = LRUCache.get(this.cache, url);
if (data) {
return data;
}
const entry = LRUCache.get(this.cache, url);
if (entry) return entry.data;
const cif = await this.plugin.runTask(this.plugin.fetch({ url, type: 'binary' }));
data = await this.parseCif(cif as Uint8Array);
if (!data) {
return;
}
const urlAsset = Asset.getUrlAsset(this.plugin.managers.asset, url);
const asset = await this.plugin.runTask(this.plugin.managers.asset.resolve(urlAsset, 'binary'));
const data = await this.parseCif(asset.data);
if (!data) return;
LRUCache.set(this.cache, url, data);
const removed = LRUCache.set(this.cache, url, { data, asset });
if (removed) removed.asset.dispose();
return data;
}
@@ -246,6 +245,14 @@ export namespace VolumeStreaming {
});
}
unregister() {
let entry = this.cache.entries.first;
while (entry) {
entry.value.data.asset.dispose();
entry = entry.next;
}
}
private getBoxFromLoci(loci: StructureElement.Loci | EmptyLoci): Box3D {
if (Loci.isEmpty(loci)) {
return Box3D.empty();

View File

@@ -54,7 +54,7 @@ export const InitVolumeStreaming = StateAction.build({
isApplicable: (a, _, plugin: PluginContext) => {
const canStreamTest = plugin.config.get(PluginConfig.VolumeStreaming.CanStream);
if (canStreamTest) return canStreamTest(a.data, plugin);
return a.data.models.length === 1 && Model.hasDensityMap(a.data.models[0]);
return a.data.models.length === 1 && Model.probablyHasDensityMap(a.data.models[0]);
}
})(({ ref, state, params }, plugin: PluginContext) => Task.create('Volume Streaming', async taskCtx => {
const entries: InfoEntryProps[] = [];

Some files were not shown because too many files have changed in this diff Show More