Bladeren bron

Merge branch 'master' of https://github.com/molstar/molstar into forkdev

# Conflicts:
#	src/extensions/cellpack/model.ts
autin 5 jaren geleden
bovenliggende
commit
7e3cca5780

+ 78 - 49
src/extensions/cellpack/model.ts

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

+ 7 - 1
src/mol-plugin-state/builder/structure/representation-preset.ts

@@ -237,7 +237,13 @@ const atomicDetail = StructureRepresentationPresetProvider({
 
         const components = {
             all: await presetStaticComponent(plugin, structureCell, 'all'),
+            branched: undefined
         };
+        if (params.showCarbohydrateSymbol) {
+            Object.assign(components, {
+                branched: await presetStaticComponent(plugin, structureCell, 'branched', { label: 'Carbohydrate' }),
+            });
+        }
 
         const { update, builder, typeParams, color } = reprBuilder(plugin, params);
         const representations = {
@@ -245,7 +251,7 @@ const atomicDetail = StructureRepresentationPresetProvider({
         };
         if (params.showCarbohydrateSymbol) {
             Object.assign(representations, {
-                snfg3d: builder.buildRepresentation(update, components.all, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color }, { tag: 'snfg-3d' }),
+                snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color }, { tag: 'snfg-3d' }),
             });
         }
 

+ 3 - 3
src/mol-plugin-state/formats/trajectory.ts

@@ -114,10 +114,10 @@ export const Provider3dg: TrajectoryFormatProvider = {
 };
 
 export const MolProvider: TrajectoryFormatProvider = {
-    label: 'MOL',
-    description: 'MOL',
+    label: 'MOL/SDF',
+    description: 'MOL/SDF',
     category: Category,
-    stringExtensions: ['mol', 'sdf'],
+    stringExtensions: ['mol', 'sdf', 'sd'],
     parse: directTrajectory(StateTransforms.Model.TrajectoryFromMOL),
     visuals: defaultVisuals
 };

+ 42 - 37
src/mol-plugin-state/helpers/structure-selection-query.ts

@@ -394,63 +394,68 @@ const wholeResidues = StructureSelectionQuery('Whole Residues of Selection', MS.
 });
 
 const StandardAminoAcids = [
-    [['HIS'], 'HISTIDINE'],
-    [['ARG'], 'ARGININE'],
-    [['LYS'], 'LYSINE'],
-    [['ILE'], 'ISOLEUCINE'],
-    [['PHE'], 'PHENYLALANINE'],
-    [['LEU'], 'LEUCINE'],
-    [['TRP'], 'TRYPTOPHAN'],
-    [['ALA'], 'ALANINE'],
-    [['MET'], 'METHIONINE'],
-    [['PRO'], 'PROLINE'],
-    [['CYS'], 'CYSTEINE'],
-    [['ASN'], 'ASPARAGINE'],
-    [['VAL'], 'VALINE'],
-    [['GLY'], 'GLYCINE'],
-    [['SER'], 'SERINE'],
-    [['GLN'], 'GLUTAMINE'],
-    [['TYR'], 'TYROSINE'],
-    [['ASP'], 'ASPARTIC ACID'],
-    [['GLU'], 'GLUTAMIC ACID'],
-    [['THR'], 'THREONINE'],
-    [['SEC'], 'SELENOCYSTEINE'],
-    [['PYL'], 'PYRROLYSINE'],
-    [['UNK'], 'UNKNOWN'],
+    [['HIS'], 'Histidine'],
+    [['ARG'], 'Arginine'],
+    [['LYS'], 'Lysine'],
+    [['ILE'], 'Isoleucine'],
+    [['PHE'], 'Phenylalanine'],
+    [['LEU'], 'Leucine'],
+    [['TRP'], 'Tryptophan'],
+    [['ALA'], 'Alanine'],
+    [['MET'], 'Methionine'],
+    [['PRO'], 'Proline'],
+    [['CYS'], 'Cysteine'],
+    [['ASN'], 'Asparagine'],
+    [['VAL'], 'Valine'],
+    [['GLY'], 'Glycine'],
+    [['SER'], 'Serine'],
+    [['GLN'], 'Glutamine'],
+    [['TYR'], 'Tyrosine'],
+    [['ASP'], 'Aspartic Acid'],
+    [['GLU'], 'Glutamic Acid'],
+    [['THR'], 'Threonine'],
+    [['SEC'], 'Selenocysteine'],
+    [['PYL'], 'Pyrrolysine'],
+    [['UNK'], 'Unknown'],
 ].sort((a, b) => a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0) as [string[], string][];
 
 const StandardNucleicBases = [
-    [['A', 'DA'], 'ADENOSINE'],
-    [['C', 'DC'], 'CYTIDINE'],
-    [['T', 'DT'], 'THYMIDINE'],
-    [['G', 'DG'], 'GUANOSINE'],
-    [['I', 'DI'], 'INOSINE'],
-    [['U', 'DU'], 'URIDINE'],
-    [['N', 'DN'], 'UNKNOWN'],
+    [['A', 'DA'], 'Adenosine'],
+    [['C', 'DC'], 'Cytidine'],
+    [['T', 'DT'], 'Thymidine'],
+    [['G', 'DG'], 'Guanosine'],
+    [['I', 'DI'], 'Inosine'],
+    [['U', 'DU'], 'Uridine'],
+    [['N', 'DN'], 'Unknown'],
 ].sort((a, b) => a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0) as [string[], string][];
 
 export function ResidueQuery([names, label]: [string[], string], category: string, priority = 0) {
-    return StructureSelectionQuery(`${label} (${names.join(', ')})`, MS.struct.modifier.union([
+    const description = names.length === 1 && !StandardResidues.has(names[0])
+        ? `[${names[0]}] ${label}`
+        : `${label} (${names.join(', ')})`;
+    return StructureSelectionQuery(description, MS.struct.modifier.union([
         MS.struct.generator.atomGroups({
             'residue-test': MS.core.set.has([MS.set(...names), MS.ammp('auth_comp_id')])
         })
-    ]), { category, priority, description: label });
+    ]), { category, priority, description });
 }
 
 export function ElementSymbolQuery([names, label]: [string[], string], category: string, priority: number) {
-    return StructureSelectionQuery(`${label} (${names.join(', ')})`, MS.struct.modifier.union([
+    const description = `${label} (${names.join(', ')})`;
+    return StructureSelectionQuery(description, MS.struct.modifier.union([
         MS.struct.generator.atomGroups({
             'atom-test': MS.core.set.has([MS.set(...names), MS.acp('elementSymbol')])
         })
-    ]), { category, priority });
+    ]), { category, priority, description });
 }
 
-export function EntityDescriptionQuery([description, label]: [string[], string], category: string, priority: number) {
+export function EntityDescriptionQuery([names, label]: [string[], string], category: string, priority: number) {
+    const description = `${label}`;
     return StructureSelectionQuery(`${label}`, MS.struct.modifier.union([
         MS.struct.generator.atomGroups({
-            'entity-test': MS.core.list.equal([MS.list(...description), MS.ammp('entityDescription')])
+            'entity-test': MS.core.list.equal([MS.list(...names), MS.ammp('entityDescription')])
         })
-    ]), { category, priority, description: description.join(', ') });
+    ]), { category, priority, description });
 }
 
 const StandardResidues = SetUtils.unionMany(

+ 1 - 1
src/mol-theme/label.ts

@@ -173,7 +173,7 @@ export function _bundleLabel(bundle: Loci.Bundle<any>, options: LabelOptions) {
         }
 
         let offset = 0;
-        for (let i = 0, il = Math.min(...labels.map(l => l.length)); i < il; ++i) {
+        for (let i = 0, il = Math.min(...labels.map(l => l.length)) - 1; i < il; ++i) {
             let areIdentical = true;
             for (let j = 1, jl = labels.length; j < jl; ++j) {
                 if (labels[0][i] !== labels[j][i]) {