|
@@ -28,61 +28,73 @@ import { createModels } from '../../mol-model-formats/structure/basic/parser';
|
|
|
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
|
|
|
import { Asset } from '../../mol-util/assets';
|
|
|
import { Color } from '../../mol-util/color';
|
|
|
+import { readFromFile } from '../../mol-util/data-source';
|
|
|
+import { objectForEach } from '../../mol-util/object';
|
|
|
|
|
|
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
|
|
return `${baseUrl}/results/${fileName}`;
|
|
|
}
|
|
|
|
|
|
-async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, baseUrl: string, file?: Asset.File) {
|
|
|
+class TrajectoryCache {
|
|
|
+ private map = new Map<string, Model.Trajectory>();
|
|
|
+ set(id: string, trajectory: Model.Trajectory) { this.map.set(id, trajectory); }
|
|
|
+ get(id: string) { return this.map.get(id); }
|
|
|
+}
|
|
|
+
|
|
|
+async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, baseUrl: string, trajCache: TrajectoryCache, file?: Asset.File) {
|
|
|
const assetManager = plugin.managers.asset;
|
|
|
- const model_id = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
|
|
|
+ const modelIndex = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
|
|
|
const surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
|
|
|
- let model: Model;
|
|
|
+ let trajectory = trajCache.get(id);
|
|
|
let assets: Asset.Wrapper[] = [];
|
|
|
- if (file) {
|
|
|
- if (file.name.endsWith('.cif')) {
|
|
|
- const text = await plugin.runTask(assetManager.resolve(file, 'string'));
|
|
|
- assets.push(text);
|
|
|
- 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 plugin.runTask(assetManager.resolve(file, 'binary'));
|
|
|
- assets.push(binary);
|
|
|
- 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 plugin.runTask(assetManager.resolve(file, 'string'));
|
|
|
- assets.push(text);
|
|
|
- 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)) {
|
|
|
- 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];
|
|
|
+ if (!trajectory) {
|
|
|
+ if (file) {
|
|
|
+ if (file.name.endsWith('.cif')) {
|
|
|
+ const text = await plugin.runTask(assetManager.resolve(file, 'string'));
|
|
|
+ assets.push(text);
|
|
|
+ const cif = (await parseCif(plugin, text.data)).blocks[0];
|
|
|
+ trajectory = await plugin.runTask(trajectoryFromMmCIF(cif));
|
|
|
+ } else if (file.name.endsWith('.bcif')) {
|
|
|
+ const binary = await plugin.runTask(assetManager.resolve(file, 'binary'));
|
|
|
+ assets.push(binary);
|
|
|
+ const cif = (await parseCif(plugin, binary.data)).blocks[0];
|
|
|
+ trajectory = await plugin.runTask(trajectoryFromMmCIF(cif));
|
|
|
+ } else if (file.name.endsWith('.pdb')) {
|
|
|
+ const text = await plugin.runTask(assetManager.resolve(file, 'string'));
|
|
|
+ assets.push(text);
|
|
|
+ const pdb = await parsePDBfile(plugin, text.data, id);
|
|
|
+ trajectory = await plugin.runTask(trajectoryFromPDB(pdb));
|
|
|
+ } else {
|
|
|
+ throw new Error(`unsupported file type '${file.name}'`);
|
|
|
+ }
|
|
|
+ } else if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
|
|
|
+ if (surface){
|
|
|
+ const data = await getFromOPM(plugin, id, assetManager);
|
|
|
+ if (data.asset){
|
|
|
+ assets.push(data.asset);
|
|
|
+ trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
|
|
|
+ } else {
|
|
|
+ const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
|
|
|
+ assets.push(asset);
|
|
|
+ trajectory = await plugin.runTask(trajectoryFromMmCIF(mmcif));
|
|
|
+ }
|
|
|
} else {
|
|
|
const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
|
|
|
assets.push(asset);
|
|
|
- model = (await plugin.runTask(trajectoryFromMmCIF(mmcif)))[model_id];
|
|
|
+ trajectory = await plugin.runTask(trajectoryFromMmCIF(mmcif));
|
|
|
}
|
|
|
} 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(plugin, id, baseUrl, assetManager);
|
|
|
- assets.push(data.asset);
|
|
|
- if ('pdb' in data) {
|
|
|
- model = (await plugin.runTask(trajectoryFromPDB(data.pdb)))[model_id];
|
|
|
- } else {
|
|
|
- model = (await plugin.runTask(trajectoryFromMmCIF(data.mmcif)))[model_id];
|
|
|
+ const data = await getFromCellPackDB(plugin, id, baseUrl, assetManager);
|
|
|
+ assets.push(data.asset);
|
|
|
+ if ('pdb' in data) {
|
|
|
+ trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
|
|
|
+ } else {
|
|
|
+ trajectory = await plugin.runTask(trajectoryFromMmCIF(data.mmcif));
|
|
|
+ }
|
|
|
}
|
|
|
+ trajCache.set(id, trajectory);
|
|
|
}
|
|
|
+ const model = trajectory[modelIndex];
|
|
|
return { model, assets };
|
|
|
}
|
|
|
|
|
@@ -134,7 +146,6 @@ function getTransform(trans: Vec3, rot: Quat) {
|
|
|
return m;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
function getResultTransforms(results: Ingredient['results'], legacy: boolean) {
|
|
|
if (legacy) return results.map((r: Ingredient['results'][0]) => getTransformLegacy(r[0], r[1]));
|
|
|
else return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1]));
|
|
@@ -292,7 +303,7 @@ async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredi
|
|
|
return getStructure(plugin, curveModel, ingredient.source);
|
|
|
}
|
|
|
|
|
|
-async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles) {
|
|
|
+async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache) {
|
|
|
const { name, source, results, nbCurve } = ingredient;
|
|
|
if (source.pdb === 'None') return;
|
|
|
|
|
@@ -307,7 +318,7 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
|
|
|
}
|
|
|
|
|
|
// model id in case structure is NMR
|
|
|
- const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, file);
|
|
|
+ const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, file);
|
|
|
if (!model) return;
|
|
|
|
|
|
let structure: Structure;
|
|
@@ -358,12 +369,13 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
|
|
|
return Task.create('Create Packing Structure', async ctx => {
|
|
|
const { ingredients, name } = packing;
|
|
|
const assets: Asset.Wrapper[] = [];
|
|
|
+ const trajCache = new TrajectoryCache();
|
|
|
const structures: Structure[] = [];
|
|
|
let colors: Color[] = [];
|
|
|
let skip_color: boolean = false;
|
|
|
for (const iName in ingredients) {
|
|
|
if (ctx.shouldUpdate) await ctx.update(iName);
|
|
|
- const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles);
|
|
|
+ const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache);
|
|
|
if (ingredientStructure) {
|
|
|
structures.push(ingredientStructure.structure);
|
|
|
assets.push(...ingredientStructure.assets);
|
|
@@ -476,6 +488,8 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
|
|
|
}
|
|
|
|
|
|
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
|
|
|
+ const ingredientFiles = params.ingredients.files || [];
|
|
|
+
|
|
|
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
|
|
|
if (params.source.name === 'id') {
|
|
|
const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl));
|
|
@@ -483,12 +497,25 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
|
|
|
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } });
|
|
|
} else {
|
|
|
const file = params.source.params;
|
|
|
- if (file === null) {
|
|
|
+ if (!file?.file) {
|
|
|
plugin.log.error('No file selected');
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
+ let jsonFile: Asset.File;
|
|
|
+ if (file.name.toLowerCase().endsWith('.zip')) {
|
|
|
+ const data = await readFromFile(file.file, 'zip').runInContext(runtime);
|
|
|
+ jsonFile = Asset.File(new File([data['model.json']], 'model.json'));
|
|
|
+ objectForEach(data, (v, k) => {
|
|
|
+ if (k === 'model.json') return;
|
|
|
+ ingredientFiles.push(Asset.File(new File([v], k)));
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ jsonFile = file;
|
|
|
+ }
|
|
|
+
|
|
|
cellPackJson = state.build().toRoot()
|
|
|
- .apply(StateTransforms.Data.ReadFile, { file, isBinary: false, label: file.name }, { state: { isGhost: true } });
|
|
|
+ .apply(StateTransforms.Data.ReadFile, { file: jsonFile, isBinary: false, label: jsonFile.name }, { state: { isGhost: true } });
|
|
|
}
|
|
|
|
|
|
const cellPackBuilder = cellPackJson
|
|
@@ -501,7 +528,7 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
|
|
|
await handleHivRna(plugin, packings, params.baseUrl);
|
|
|
|
|
|
for (let i = 0, il = packings.length; i < il; ++i) {
|
|
|
- const p = { packing: i, baseUrl: params.baseUrl, ingredientFiles: params.ingredients.files };
|
|
|
+ const p = { packing: i, baseUrl: params.baseUrl, ingredientFiles };
|
|
|
|
|
|
const packing = await state.build()
|
|
|
.to(cellPackBuilder.ref)
|
|
@@ -523,6 +550,7 @@ const LoadCellPackModelParams = {
|
|
|
source: PD.MappedStatic('id', {
|
|
|
'id': PD.Select('influenza_model1.json', [
|
|
|
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
|
|
|
+ ['HIV_immature_model.json', 'HIV immature'],
|
|
|
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
|
|
|
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
|
|
['hiv_lipids.bcif', 'hiv_lipids'],
|
|
@@ -530,8 +558,9 @@ const LoadCellPackModelParams = {
|
|
|
['InfluenzaModel2.json', 'Influenza Complete'],
|
|
|
['ExosomeModel.json', 'ExosomeModel'],
|
|
|
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
|
|
- ] as const),
|
|
|
- 'file': PD.File({ accept: 'id' }),
|
|
|
+ ['MycoplasmaModel.json', 'Mycoplasma WholeCell model'],
|
|
|
+ ] as const, { description: 'Download the model definition with `id` from the server at `baseUrl.`' }),
|
|
|
+ 'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.' }),
|
|
|
}, { options: [['id', 'Id'], ['file', 'File']] }),
|
|
|
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
|
|
ingredients : PD.Group({
|