Browse Source

Merge pull request #252 from corredD/forkdev

binary model loading support and latest mycoplasma model.
Alexander Rose 3 years ago
parent
commit
46d5442dc5

+ 4 - 0
CHANGELOG.md

@@ -10,6 +10,10 @@ Note that since we don't clearly distinguish between a public and private interf
 - Improve aromatic bond visuals (add ``aromaticScale``, ``aromaticSpacing``, ``aromaticDashCount`` params)
 - [Breaking] Change ``adjustCylinderLength`` default to ``false`` (set to true for focus representation)
 - Fix marker highlight color overriding select color
+- CellPack extension update
+    - add binary model support
+    - add compartment (including membrane) geometry support
+    - add latest mycoplasma model example
 
 ## [v2.3.5] - 2021-10-19
 

+ 2 - 2
README.md

@@ -122,9 +122,9 @@ and navigate to `build/viewer`
 
 **Convert any CIF to BinaryCIF**
 
-    node lib/servers/model/preprocess -i file.cif -ob file.bcif
+    node lib/commonjs/servers/model/preprocess -i file.cif -ob file.bcif
 
-To see all available commands, use ``node lib/servers/model/preprocess -h``.
+To see all available commands, use ``node lib/commonjs/servers/model/preprocess -h``.
 
 Or
 

+ 1 - 1
src/extensions/cellpack/color/generate.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */

+ 1 - 1
src/extensions/cellpack/color/provided.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */

+ 2 - 2
src/extensions/cellpack/curve.ts

@@ -1,7 +1,7 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
- * @author Ludovic Autin <autin@scripps.edu>
+ * @author Ludovic Autin <ludovic.autin@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 

+ 36 - 2
src/extensions/cellpack/data.ts

@@ -13,16 +13,27 @@ export interface CellPack {
 
 export interface CellPacking {
     name: string,
-    location: 'surface' | 'interior' | 'cytoplasme',
+    location: 'surface' | 'interior' | 'cytoplasme'
     ingredients: Packing['ingredients']
+    compartment?: CellCompartment
 }
 
-//
+export interface CellCompartment {
+    filename?: string
+    geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
+    compartment_primitives?: CompartmentPrimitives
+}
 
 export interface Cell {
     recipe: Recipe
+    options?: RecipeOptions
     cytoplasme?: Packing
     compartments?: { [key: string]: Compartment }
+    mapping_ids?: { [key: number]: [number, string] }
+}
+
+export interface RecipeOptions {
+    resultfile?: string
 }
 
 export interface Recipe {
@@ -35,8 +46,29 @@ export interface Recipe {
 export interface Compartment {
     surface?: Packing
     interior?: Packing
+    geom?: unknown
+    geom_type?: 'raw' | 'file' | 'sphere' | 'mb' | 'None'
+    mb?: CompartmentPrimitives
+}
+
+// Primitives discribing a compartment
+export const enum CompartmentPrimitiveType {
+    MetaBall = 0,
+    Sphere = 1,
+    Cube = 2,
+    Cylinder = 3,
+    Cone = 4,
+    Plane = 5,
+    None = 6
 }
 
+export interface CompartmentPrimitives{
+    positions?: number[];
+    radii?: number[];
+    types?: CompartmentPrimitiveType[];
+}
+
+
 export interface Packing {
     ingredients: { [key: string]: Ingredient }
 }
@@ -64,11 +96,13 @@ export interface Ingredient {
     [curveX: string]: unknown;
     /** the orientation in the membrane */
     principalAxis?: Vec3;
+    principalVector?: Vec3;
     /** offset along membrane */
     offset?: Vec3;
     ingtype?: string;
     color?: Vec3;
     confidence?: number;
+    Type?: string;
 }
 
 export interface IngredientSource {

+ 1 - 1
src/extensions/cellpack/index.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */

+ 168 - 69
src/extensions/cellpack/model.ts

@@ -2,13 +2,14 @@
  * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Ludovic Autin <ludovic.autin@gmail.com>
  */
 
 import { StateAction, StateBuilder, StateTransformer, State } from '../../mol-state';
 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 { Ingredient, CellPacking, CompartmentPrimitives } from './data';
 import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean, getFromOPM } from './util';
 import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit, Trajectory } from '../../mol-model/structure';
 import { trajectoryFromMmCIF, MmcifFormat } from '../../mol-model-formats/structure/mmcif';
@@ -17,7 +18,7 @@ import { Mat4, Vec3, Quat } from '../../mol-math/linear-algebra';
 import { SymmetryOperator } from '../../mol-math/geometry';
 import { Task, RuntimeContext } from '../../mol-task';
 import { StateTransforms } from '../../mol-plugin-state/transforms';
-import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies } from './state';
+import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies, CreateCompartmentSphere } from './state';
 import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
 import { getMatFromResamplePoints } from './curve';
 import { compile } from '../../mol-script/runtime/query/compiler';
@@ -28,8 +29,9 @@ 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';
+import { readFromFile } from '../../mol-util/data-source';
+import { ColorNames } from '../../mol-util/color/names';
 
 function getCellPackModelUrl(fileName: string, baseUrl: string) {
     return `${baseUrl}/results/${fileName}`;
@@ -41,10 +43,14 @@ class TrajectoryCache {
     get(id: string) { return this.map.get(id); }
 }
 
-async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, baseUrl: string, trajCache: TrajectoryCache, file?: Asset.File) {
+async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient,
+    baseUrl: string, trajCache: TrajectoryCache, location: string,
+    file?: Asset.File
+) {
     const assetManager = plugin.managers.asset;
     const modelIndex = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
-    const surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
+    let surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
+    if (location === 'surface') surface = true;
     let trajectory = trajCache.get(id);
     const assets: Asset.Wrapper[] = [];
     if (!trajectory) {
@@ -72,6 +78,7 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien
                 try {
                     const data = await getFromOPM(plugin, id, assetManager);
                     assets.push(data.asset);
+                    data.pdb.id! = id.toUpperCase();
                     trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
                 } catch (e) {
                     // fallback to getFromPdb
@@ -100,7 +107,7 @@ async function getModel(plugin: PluginContext, id: string, ingredient: Ingredien
     return { model, assets };
 }
 
-async function getStructure(plugin: PluginContext, model: Model, source: IngredientSource, props: { assembly?: string } = {}) {
+async function getStructure(plugin: PluginContext, model: Model, source: Ingredient, props: { assembly?: string } = {}) {
     let structure = Structure.ofModel(model);
     const { assembly } = props;
 
@@ -108,11 +115,12 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi
         structure = await plugin.runTask(StructureSymmetry.buildAssembly(structure, assembly));
     }
     let query;
-    if (source.selection) {
-        const asymIds: string[] = source.selection.replace(' ', '').replace(':', '').split('or');
+    if (source.source.selection) {
+        const sel = source.source.selection;
+        // selection can have the model ID as well. remove it
+        const asymIds: string[] = sel.replace(/ /g, '').replace(/:/g, '').split('or').slice(1);
         query = MS.struct.modifier.union([
             MS.struct.generator.atomGroups({
-                'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
                 'chain-test': MS.core.set.has([MS.set(...asymIds), MS.ammp('auth_asym_id')])
             })
         ]);
@@ -123,11 +131,11 @@ async function getStructure(plugin: PluginContext, model: Model, source: Ingredi
             })
         ]);
     }
-
     const compiled = compile<StructureSelection>(query);
     const result = compiled(new QueryContext(structure));
     structure = StructureSelection.unionStructure(result);
-
+    // change here if possible the label ?
+    // structure.label =  source.name;
     return structure;
 }
 
@@ -141,9 +149,9 @@ function getTransformLegacy(trans: Vec3, rot: Quat) {
 }
 
 function getTransform(trans: Vec3, rot: Quat) {
-    const q: Quat = Quat.create(rot[0], rot[1], rot[2], rot[3]);
+    const q: Quat = Quat.create(-rot[0], rot[1], rot[2], -rot[3]);
     const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
-    const p: Vec3 = Vec3.create(trans[0], trans[1], trans[2]);
+    const p: Vec3 = Vec3.create(-trans[0], trans[1], trans[2]);
     Mat4.setTranslation(m, p);
     return m;
 }
@@ -168,7 +176,7 @@ function getCurveTransforms(ingredient: Ingredient) {
     for (let i = 0; i < n; ++i) {
         const cname = `curve${i}`;
         if (!(cname in ingredient)) {
-            // console.warn(`Expected '${cname}' in ingredient`)
+            console.warn(`Expected '${cname}' in ingredient`);
             continue;
         }
         const _points = ingredient[cname] as Vec3[];
@@ -179,7 +187,7 @@ function getCurveTransforms(ingredient: Ingredient) {
         // test for resampling
         const distance: number = Vec3.distance(_points[0], _points[1]);
         if (distance >= segmentLength + 2.0) {
-            console.info(distance);
+            // console.info(distance);
             resampling = true;
         }
         const points = new Float32Array(_points.length * 3);
@@ -190,8 +198,8 @@ function getCurveTransforms(ingredient: Ingredient) {
     return instances;
 }
 
-function getAssembly(transforms: Mat4[], structure: Structure) {
-    const builder = Structure.Builder();
+function getAssembly(name: string, transforms: Mat4[], structure: Structure) {
+    const builder = Structure.Builder({ label: name });
     const { units } = structure;
 
     for (let i = 0, il = transforms.length; i < il; ++i) {
@@ -307,13 +315,13 @@ async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredi
     });
 
     const curveModel = await plugin.runTask(curveModelTask);
-    return getStructure(plugin, curveModel, ingredient.source);
+    // ingredient.source.selection = undefined;
+    return getStructure(plugin, curveModel, ingredient);
 }
 
-async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache) {
+async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache, location: 'surface' | 'interior' | 'cytoplasme') {
     const { name, source, results, nbCurve } = ingredient;
     if (source.pdb === 'None') return;
-
     const file = ingredientFiles[source.pdb];
     if (!file) {
         // TODO can these be added to the library?
@@ -325,13 +333,13 @@ 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, trajCache, file);
+    const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, location, file);
     if (!model) return;
-
     let structure: Structure;
     if (nbCurve) {
         structure = await getCurve(plugin, name, ingredient, getCurveTransforms(ingredient), model);
     } else {
+        if ((!results || results.length === 0)) return;
         let bu: string|undefined = source.bu ? source.bu : undefined;
         if (bu) {
             if (bu === 'AU') {
@@ -340,10 +348,11 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
                 bu = bu.slice(2);
             }
         }
-        structure = await getStructure(plugin, model, source, { assembly: bu });
+        structure = await getStructure(plugin, model, ingredient, { assembly: bu });
         // transform with offset and pcp
         let legacy: boolean = true;
-        if (ingredient.offset || ingredient.principalAxis) {
+        const pcp = ingredient.principalVector ? ingredient.principalVector : ingredient.principalAxis;
+        if (pcp) {
             legacy = false;
             const structureMean = getStructureMean(structure);
             Vec3.negate(structureMean, structureMean);
@@ -351,38 +360,44 @@ async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredi
             Mat4.setTranslation(m1, structureMean);
             structure = Structure.transform(structure, m1);
             if (ingredient.offset) {
-                if (!Vec3.exactEquals(ingredient.offset, Vec3.zero())) {
+                const o: Vec3 = Vec3.create(ingredient.offset[0], ingredient.offset[1], ingredient.offset[2]);
+                if (!Vec3.exactEquals(o, Vec3.zero())) { // -1, 1, 4e-16 ??
+                    if (location !== 'surface') {
+                        Vec3.negate(o, o);
+                    }
                     const m: Mat4 = Mat4.identity();
-                    Mat4.setTranslation(m, ingredient.offset);
+                    Mat4.setTranslation(m, o);
                     structure = Structure.transform(structure, m);
                 }
             }
-            if (ingredient.principalAxis) {
-                if (!Vec3.exactEquals(ingredient.principalAxis, Vec3.unitZ)) {
+            if (pcp) {
+                const p: Vec3 = Vec3.create(pcp[0], pcp[1], pcp[2]);
+                if (!Vec3.exactEquals(p, Vec3.unitZ)) {
                     const q: Quat = Quat.identity();
-                    Quat.rotationTo(q, ingredient.principalAxis, Vec3.unitZ);
+                    Quat.rotationTo(q, p, Vec3.unitZ);
                     const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
                     structure = Structure.transform(structure, m);
                 }
             }
         }
-        structure = getAssembly(getResultTransforms(results, legacy), structure);
+
+        structure = getAssembly(name, getResultTransforms(results, legacy), structure);
     }
 
     return { structure, assets };
 }
 
+
 export function createStructureFromCellPack(plugin: PluginContext, packing: CellPacking, baseUrl: string, ingredientFiles: IngredientFiles) {
     return Task.create('Create Packing Structure', async ctx => {
-        const { ingredients, name } = packing;
+        const { ingredients, location, name } = packing;
         const assets: Asset.Wrapper[] = [];
         const trajCache = new TrajectoryCache();
         const structures: Structure[] = [];
         const colors: Color[] = [];
-        let skipColors: boolean = false;
         for (const iName in ingredients) {
             if (ctx.shouldUpdate) await ctx.update(iName);
-            const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache);
+            const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache, location);
             if (ingredientStructure) {
                 structures.push(ingredientStructure.structure);
                 assets.push(...ingredientStructure.assets);
@@ -390,7 +405,7 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
                 if (c) {
                     colors.push(Color.fromNormalizedRgb(c[0], c[1], c[2]));
                 } else {
-                    skipColors = true;
+                    colors.push(Color.fromNormalizedRgb(1, 0, 0));
                 }
             }
         }
@@ -414,21 +429,20 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
         }
 
         if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
-        const structure = Structure.create(units);
+        const structure = Structure.create(units, { label: name + '.' + location });
         for (let i = 0, il = structure.models.length; i < il; ++i) {
             Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
         }
-        return { structure, assets, colors: skipColors ? undefined : colors };
+        return { structure, assets, colors: colors };
     });
 }
 
 async function handleHivRna(plugin: PluginContext, packings: CellPacking[], baseUrl: string) {
     for (let i = 0, il = packings.length; i < il; ++i) {
-        if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
+        if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0' || packings[i].name === 'HIV_capsid') {
             const url = Asset.getUrlAsset(plugin.managers.asset, `${baseUrl}/extras/rna_allpoints.json`);
             const json = await plugin.runTask(plugin.managers.asset.resolve(url, 'json', false));
             const points = json.data.points as number[];
-
             const curve0: Vec3[] = [];
             for (let j = 0, jl = points.length; j < jl; j += 3) {
                 curve0.push(Vec3.fromArray(Vec3(), points, j));
@@ -465,7 +479,8 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
             }
         }
     }
-
+    let legacy_membrane: boolean = false; // temporary variable until all membrane are converted to the new correct cif format
+    let geometry_membrane: boolean = false; // membrane can be a mesh geometry
     let b = state.build().toRoot();
     if (file) {
         if (file.name.endsWith('.cif')) {
@@ -474,27 +489,82 @@ async function loadMembrane(plugin: PluginContext, name: string, state: State, p
             b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } });
         }
     } else {
-        const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
-        b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
+        if (name.toLowerCase().endsWith('.bcif')) {
+            const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
+            b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
+        } else if (name.toLowerCase().endsWith('.cif')) {
+            const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
+            b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
+        } else if (name.toLowerCase().endsWith('.ply')) {
+            const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/geometries/${name}`);
+            b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
+            geometry_membrane = true;
+        } else {
+            const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
+            b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
+            legacy_membrane = true;
+        }
     }
-
-    const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
-        .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
-        .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
-        .apply(StructureFromAssemblies, undefined, { state: { isGhost: true } })
-        .commit({ revertOnError: true });
-
-    const membraneParams = {
-        representation: params.preset.representation,
+    const props = {
+        type: {
+            name: 'assembly' as const,
+            params: { id: '1' }
+        }
     };
+    if (legacy_membrane) {
+        // old membrane
+        const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
+            .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
+            .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
+            .apply(StructureFromAssemblies, undefined, { state: { isGhost: true } })
+            .commit({ revertOnError: true });
+        const membraneParams = {
+            representation: params.preset.representation,
+        };
+        await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
+    } else if (geometry_membrane) {
+        await b.apply(StateTransforms.Data.ParsePly, undefined, { state: { isGhost: true } })
+            .apply(StateTransforms.Model.ShapeFromPly)
+            .apply(StateTransforms.Representation.ShapeRepresentation3D, { xrayShaded: true,
+                doubleSided: true, coloring: { name: 'uniform', params: { color: ColorNames.orange } } })
+            .commit({ revertOnError: true });
+    } else {
+        const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
+            .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
+            .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
+            .apply(StateTransforms.Model.StructureFromModel, props, { state: { isGhost: true } })
+            .commit({ revertOnError: true });
+        const membraneParams = {
+            representation: params.preset.representation,
+        };
+        await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
+    }
+}
 
-    await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
+async function handleMembraneSpheres(state: State, primitives: CompartmentPrimitives) {
+    const nSpheres = primitives.positions!.length / 3;
+    // console.log('ok mb ', nSpheres);
+    // TODO : take in account the type of the primitives.
+    for (let j = 0; j < nSpheres; j++) {
+        await state.build()
+            .toRoot()
+            .apply(CreateCompartmentSphere, {
+                center: Vec3.create(
+                    primitives.positions![j * 3 + 0],
+                    primitives.positions![j * 3 + 1],
+                    primitives.positions![j * 3 + 2]
+                ),
+                radius: primitives!.radii![j]
+            })
+            .commit();
+    }
 }
 
 async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
     const ingredientFiles = params.ingredients || [];
 
     let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
+    let resultsFile: Asset.File | null = params.results;
     if (params.source.name === 'id') {
         const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl));
         cellPackJson = state.build().toRoot()
@@ -506,29 +576,36 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
             return;
         }
 
-        let jsonFile: Asset.File;
+        let modelFile: 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'));
+            if (data['model.json']) {
+                modelFile = Asset.File(new File([data['model.json']], 'model.json'));
+            } else {
+                throw new Error('model.json missing from zip file');
+            }
+            if (data['results.bin']) {
+                resultsFile = Asset.File(new File([data['results.bin']], 'results.bin'));
+            }
             objectForEach(data, (v, k) => {
                 if (k === 'model.json') return;
+                if (k === 'results.bin') return;
                 ingredientFiles.push(Asset.File(new File([v], k)));
             });
         } else {
-            jsonFile = file;
+            modelFile = file;
         }
-
         cellPackJson = state.build().toRoot()
-            .apply(StateTransforms.Data.ReadFile, { file: jsonFile, isBinary: false, label: jsonFile.name }, { state: { isGhost: true } });
+            .apply(StateTransforms.Data.ReadFile, { file: modelFile, isBinary: false, label: modelFile.name }, { state: { isGhost: true } });
     }
 
     const cellPackBuilder = cellPackJson
         .apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
-        .apply(ParseCellPack);
+        .apply(ParseCellPack, { resultsFile, baseUrl: params.baseUrl });
 
     const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime);
-    const { packings } = cellPackObject.obj!.data;
 
+    const { packings } = cellPackObject.obj!.data;
     await handleHivRna(plugin, packings, params.baseUrl);
 
     for (let i = 0, il = packings.length; i < il; ++i) {
@@ -544,8 +621,30 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat
             representation: params.preset.representation,
         };
         await CellpackPackingPreset.apply(packing, packingParams, plugin);
-        if (packings[i].location === 'surface' && params.membrane) {
-            await loadMembrane(plugin, packings[i].name, state, params);
+        if (packings[i].compartment) {
+            if (params.membrane === 'lipids') {
+                if (packings[i].compartment!.geom_type) {
+                    if (packings[i].compartment!.geom_type === 'file') {
+                        // TODO: load mesh files or vertex,faces data
+                        await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
+                    } else if (packings[i].compartment!.compartment_primitives) {
+                        await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
+                    }
+                } else {
+                    // try loading membrane from repo as a bcif file or from the given list of files.
+                    if (params.membrane === 'lipids') {
+                        await loadMembrane(plugin, packings[i].name, state, params);
+                    }
+                }
+            } else if (params.membrane === 'geometry') {
+                if (packings[i].compartment!.compartment_primitives) {
+                    await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
+                } else if (packings[i].compartment!.geom_type === 'file') {
+                    if (packings[i].compartment!.filename!.toLowerCase().endsWith('.ply')) {
+                        await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
+                    }
+                }
+            }
         }
     }
 }
@@ -555,19 +654,19 @@ const LoadCellPackModelParams = {
         'id': PD.Select('InfluenzaModel2.json', [
             ['blood_hiv_immature_inside.json', 'Blood HIV immature'],
             ['HIV_immature_model.json', 'HIV immature'],
-            ['BloodHIV1.0_mixed_fixed_nc1.cpr', 'Blood HIV'],
-            ['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV'],
+            ['Blood_HIV.json', 'Blood HIV'],
+            ['HIV-1_0.1.6-8_mixed_radii_pdb.json', 'HIV'],
             ['influenza_model1.json', 'Influenza envelope'],
-            ['InfluenzaModel2.json', 'Influenza Complete'],
+            ['InfluenzaModel2.json', 'Influenza complete'],
             ['ExosomeModel.json', 'Exosome Model'],
-            ['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma simple'],
-            ['MycoplasmaModel.json', 'Mycoplasma WholeCell model'],
+            ['MycoplasmaGenitalium.json', 'Mycoplasma Genitalium curated 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.' }),
+        'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.', label: 'Recipe file' }),
     }, { options: [['id', 'Id'], ['file', 'File']] }),
     baseUrl: PD.Text(DefaultCellPackBaseUrl),
-    membrane: PD.Boolean(true),
-    ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredients' }),
+    results: PD.File({ accept: '.bin', description: 'open results file in binary format from cellpackgpu for the specified recipe', label: 'Results file' }),
+    membrane: PD.Select('lipids', PD.arrayToOptions(['lipids', 'geometry', 'none'])),
+    ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredient files' }),
     preset: PD.Group({
         traceOnly: PD.Boolean(false),
         representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation']))
@@ -581,4 +680,4 @@ export const LoadCellPackModel = StateAction.build({
     from: PSO.Root
 })(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
     await loadPackings(ctx, taskCtx, state, params);
-}));
+}));

+ 5 - 6
src/extensions/cellpack/preset.ts

@@ -1,7 +1,8 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Ludovic Autin <ludovic.autin@gmail.com>
  */
 
 import { StateObjectRef } from '../../mol-state';
@@ -9,8 +10,6 @@ import { StructureRepresentationPresetProvider, presetStaticComponent } from '..
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ColorNames } from '../../mol-util/color/names';
 import { CellPackGenerateColorThemeProvider } from './color/generate';
-import { CellPackInfoProvider } from './property';
-import { CellPackProvidedColorThemeProvider } from './color/provided';
 
 export const CellpackPackingPresetParams = {
     traceOnly: PD.Boolean(true),
@@ -42,8 +41,8 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
             Object.assign(reprProps, { sizeFactor: 2 });
         }
 
-        const info = structureCell.obj?.data && CellPackInfoProvider.get(structureCell.obj?.data).value;
-        const color = info?.colors ? CellPackProvidedColorThemeProvider.name : CellPackGenerateColorThemeProvider.name;
+        // default is generated
+        const color = CellPackGenerateColorThemeProvider.name;
 
         const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
         const representations = {
@@ -92,4 +91,4 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({
 
         return { components, representations };
     }
-});
+});

+ 2 - 2
src/extensions/cellpack/property.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -34,4 +34,4 @@ export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellP
             value: { ...CellPackInfoParams.info.defaultValue, ...props.info }
         };
     }
-});
+});

+ 70 - 0
src/extensions/cellpack/representation.ts

@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Ludovic Autin <ludovic.autin@gmail.com>
+ */
+
+import { ShapeRepresentation } from '../../mol-repr/shape/representation';
+import { Shape } from '../../mol-model/shape';
+import { ColorNames } from '../../mol-util/color/names';
+import { RuntimeContext } from '../../mol-task';
+import { ParamDefinition as PD } from '../../mol-util/param-definition';
+import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
+import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
+// import { Polyhedron, DefaultPolyhedronProps } from '../../mol-geo/primitive/polyhedron';
+// import { Icosahedron } from '../../mol-geo/primitive/icosahedron';
+import { Sphere } from '../../mol-geo/primitive/sphere';
+import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
+import { RepresentationParamsGetter, Representation, RepresentationContext } from '../../mol-repr/representation';
+
+
+interface MembraneSphereData {
+    radius: number
+    center: Vec3
+}
+
+
+const MembraneSphereParams = {
+    ...Mesh.Params,
+    cellColor: PD.Color(ColorNames.orange),
+    cellScale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
+    radius: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
+    center: PD.Vec3(Vec3.create(0, 0, 0)),
+    quality: { ...Mesh.Params.quality, isEssential: false },
+};
+
+type MeshParams = typeof MembraneSphereParams
+
+const MembraneSphereVisuals = {
+    'mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MeshParams>) => ShapeRepresentation(getMBShape, Mesh.Utils),
+};
+
+export const MBParams = {
+    ...MembraneSphereParams
+};
+export type MBParams = typeof MBParams
+export type UnitcellProps = PD.Values<MBParams>
+
+function getMBMesh(data: MembraneSphereData, props: UnitcellProps, mesh?: Mesh) {
+    const state = MeshBuilder.createState(256, 128, mesh);
+    const radius = props.radius;
+    const asphere = Sphere(3);
+    const trans: Mat4 = Mat4.identity();
+    Mat4.fromScaling(trans, Vec3.create(radius, radius, radius));
+    state.currentGroup = 1;
+    MeshBuilder.addPrimitive(state, trans, asphere);
+    const m = MeshBuilder.getMesh(state);
+    return m;
+}
+
+function getMBShape(ctx: RuntimeContext, data: MembraneSphereData, props: UnitcellProps, shape?: Shape<Mesh>) {
+    const geo = getMBMesh(data, props, shape && shape.geometry);
+    const label = 'mb';
+    return Shape.create(label, data, geo, () => props.cellColor, () => 1, () => label);
+}
+
+export type MBRepresentation = Representation<MembraneSphereData, MBParams>
+export function MBRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneSphereData, MBParams>): MBRepresentation {
+    return Representation.createMulti('MB', ctx, getParams, Representation.StateBuilder, MembraneSphereVisuals as unknown as Representation.Def<MembraneSphereData, MBParams>);
+}

+ 189 - 13
src/extensions/cellpack/state.ts

@@ -1,7 +1,8 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Ludovic Autin <ludovic.autin@gmail.com>
  */
 
 import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
@@ -15,9 +16,13 @@ import { PluginContext } from '../../mol-plugin/context';
 import { CellPackInfoProvider } from './property';
 import { Structure, StructureSymmetry, Unit, Model } from '../../mol-model/structure';
 import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
+import { Vec3, Quat } from '../../mol-math/linear-algebra';
+import { StateTransformer } from '../../mol-state';
+import { MBRepresentation, MBParams } from './representation';
+import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';;
+import { getFloatValue } from './util';
 
-export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/';
-
+export const DefaultCellPackBaseUrl = 'https://raw.githubusercontent.com/mesoscope/cellPACK_data/master/cellPACK_database_1.1.0';
 export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
 
 export { ParseCellPack };
@@ -26,26 +31,173 @@ const ParseCellPack = PluginStateTransform.BuiltIn({
     name: 'parse-cellpack',
     display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
     from: PSO.Format.Json,
-    to: CellPack
+    to: CellPack,
+    params: a => {
+        return {
+            resultsFile: PD.File({ accept: '.bin' }),
+            baseUrl: PD.Text(DefaultCellPackBaseUrl)
+        };
+    }
 })({
-    apply({ a }) {
+    apply({ a, params, cache }, plugin: PluginContext) {
         return Task.create('Parse CellPack', async ctx => {
             const cell = a.data as Cell;
-
+            let counter_id = 0;
+            let fiber_counter_id = 0;
+            let comp_counter = 0;
             const packings: CellPacking[] = [];
             const { compartments, cytoplasme } = cell;
+            if (!cell.mapping_ids) cell.mapping_ids = {};
+            if (cytoplasme) {
+                packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
+                for (const iName in cytoplasme.ingredients) {
+                    if (cytoplasme.ingredients[iName].ingtype === 'fiber') {
+                        cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
+                        if (!cytoplasme.ingredients[iName].nbCurve) cytoplasme.ingredients[iName].nbCurve = 0;
+                        fiber_counter_id++;
+                    } else {
+                        cell.mapping_ids[counter_id] = [comp_counter, iName];
+                        if (!cytoplasme.ingredients[iName].results) { cytoplasme.ingredients[iName].results = []; }
+                        counter_id++;
+                    }
+                }
+                comp_counter++;
+            }
             if (compartments) {
                 for (const name in compartments) {
                     const { surface, interior } = compartments[name];
-                    if (surface) packings.push({ name, location: 'surface', ingredients: surface.ingredients });
-                    if (interior) packings.push({ name, location: 'interior', ingredients: interior.ingredients });
+                    let filename = '';
+                    if (compartments[name].geom_type === 'file') {
+                        filename = (compartments[name].geom) ? compartments[name].geom as string : '';
+                    }
+                    const compartment = { filename: filename, geom_type: compartments[name].geom_type, compartment_primitives: compartments[name].mb };
+                    if (surface) {
+                        packings.push({ name, location: 'surface', ingredients: surface.ingredients, compartment: compartment });
+                        for (const iName in surface.ingredients) {
+                            if (surface.ingredients[iName].ingtype === 'fiber') {
+                                cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
+                                if (!surface.ingredients[iName].nbCurve) surface.ingredients[iName].nbCurve = 0;
+                                fiber_counter_id++;
+                            } else {
+                                cell.mapping_ids[counter_id] = [comp_counter, iName];
+                                if (!surface.ingredients[iName].results) { surface.ingredients[iName].results = []; }
+                                counter_id++;
+                            }
+                        }
+                        comp_counter++;
+                    }
+                    if (interior) {
+                        if (!surface) packings.push({ name, location: 'interior', ingredients: interior.ingredients, compartment: compartment });
+                        else packings.push({ name, location: 'interior', ingredients: interior.ingredients });
+                        for (const iName in interior.ingredients) {
+                            if (interior.ingredients[iName].ingtype === 'fiber') {
+                                cell.mapping_ids[-(fiber_counter_id + 1)] = [comp_counter, iName];
+                                if (!interior.ingredients[iName].nbCurve) interior.ingredients[iName].nbCurve = 0;
+                                fiber_counter_id++;
+                            } else {
+                                cell.mapping_ids[counter_id] = [comp_counter, iName];
+                                if (!interior.ingredients[iName].results) { interior.ingredients[iName].results = []; }
+                                counter_id++;
+                            }
+                        }
+                        comp_counter++;
+                    }
                 }
             }
-            if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
+            const { options } = cell;
+            let resultsAsset: Asset.Wrapper<'binary'> | undefined;
+            if (params.resultsFile) {
+                resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(params.resultsFile, 'binary', true));
+            } else if (options?.resultfile) {
+                const url = `${params.baseUrl}/results/${options.resultfile}`;
+                resultsAsset = await plugin.runTask(plugin.managers.asset.resolve(Asset.getUrlAsset(plugin.managers.asset, url), 'binary', true));
+            }
+            if (resultsAsset) {
+                (cache as any).asset = resultsAsset;
+                const results = resultsAsset.data;
+                // flip the byte order if needed
+                const buffer = IsNativeEndianLittle ? results.buffer : flipByteOrder(results, 4);
+                const numbers = new DataView(buffer);
+                const ninst = getFloatValue(numbers, 0);
+                const npoints = getFloatValue(numbers, 4);
+                const ncurve = getFloatValue(numbers, 8);
+
+                let offset = 12;
+
+                if (ninst !== 0) {
+                    const pos = new Float32Array(buffer, offset, ninst * 4);
+                    offset += ninst * 4 * 4;
+                    const quat = new Float32Array(buffer, offset, ninst * 4);
+                    offset += ninst * 4 * 4;
+
+                    for (let i = 0; i < ninst; i++) {
+                        const x: number = pos[i * 4 + 0];
+                        const y: number = pos[i * 4 + 1];
+                        const z: number = pos[i * 4 + 2];
+                        const ingr_id = pos[i * 4 + 3] as number;
+                        const pid = cell.mapping_ids![ingr_id];
+                        if (!packings[pid[0]].ingredients[pid[1]].results) {
+                            packings[pid[0]].ingredients[pid[1]].results = [];
+                        }
+                        packings[pid[0]].ingredients[pid[1]].results.push([Vec3.create(x, y, z),
+                            Quat.create(quat[i * 4 + 0], quat[i * 4 + 1], quat[i * 4 + 2], quat[i * 4 + 3])]);
+                    }
+                }
 
+                if (npoints !== 0) {
+                    const ctr_pos = new Float32Array(buffer, offset, npoints * 4);
+                    offset += npoints * 4 * 4;
+                    offset += npoints * 4 * 4;
+                    const ctr_info = new Float32Array(buffer, offset, npoints * 4);
+                    offset += npoints * 4 * 4;
+                    const curve_ids = new Float32Array(buffer, offset, ncurve * 4);
+                    offset += ncurve * 4 * 4;
+
+                    let counter = 0;
+                    let ctr_points: Vec3[] = [];
+                    let prev_ctype = 0;
+                    let prev_cid = 0;
+
+                    for (let i = 0; i < npoints; i++) {
+                        const x: number = -ctr_pos[i * 4 + 0];
+                        const y: number = ctr_pos[i * 4 + 1];
+                        const z: number = ctr_pos[i * 4 + 2];
+                        const cid: number = ctr_info[i * 4 + 0]; // curve id
+                        const ctype: number = curve_ids[cid * 4 + 0]; // curve type
+                        // cid  148 165 -1 0
+                        // console.log("cid ",cid,ctype,prev_cid,prev_ctype);//165,148
+                        if (prev_ctype !== ctype) {
+                            const pid = cell.mapping_ids![-prev_ctype - 1];
+                            const cname = `curve${counter}`;
+                            packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
+                            packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
+                            ctr_points = [];
+                            counter = 0;
+                        } else if (prev_cid !== cid) {
+                            ctr_points = [];
+                            const pid = cell.mapping_ids![-prev_ctype - 1];
+                            const cname = `curve${counter}`;
+                            packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
+                            counter += 1;
+                        }
+                        ctr_points.push(Vec3.create(x, y, z));
+                        prev_ctype = ctype;
+                        prev_cid = cid;
+                    }
+
+                    // do the last one
+                    const pid = cell.mapping_ids![-prev_ctype - 1];
+                    const cname = `curve${counter}`;
+                    packings[pid[0]].ingredients[pid[1]].nbCurve = counter + 1;
+                    packings[pid[0]].ingredients[pid[1]][cname] = ctr_points;
+                }
+            }
             return new CellPack({ cell, packings });
         });
-    }
+    },
+    dispose({ cache }) {
+        ((cache as any)?.asset as Asset.Wrapper | undefined)?.dispose();
+    },
 });
 
 export { StructureFromCellpack };
@@ -77,9 +229,8 @@ const StructureFromCellpack = PluginStateTransform.BuiltIn({
             await CellPackInfoProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, structure, {
                 info: { packingsCount: a.data.packings.length, packingIndex: params.packing, colors }
             });
-
             (cache as any).assets = assets;
-            return new PSO.Molecule.Structure(structure, { label: packing.name });
+            return new PSO.Molecule.Structure(structure, { label: packing.name + '.' + packing.location });
         });
     },
     dispose({ b, cache }) {
@@ -125,7 +276,7 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
                     const s = await StructureSymmetry.buildAssembly(initial_structure, a.id).runInContext(ctx);
                     structures.push(s);
                 }
-                const builder = Structure.Builder();
+                const builder = Structure.Builder({ label: 'Membrane' });
                 let offsetInvariantId = 0;
                 for (const s of structures) {
                     let maxInvariantId = 0;
@@ -148,3 +299,28 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
         b?.data.customPropertyDescriptors.dispose();
     }
 });
+
+const CreateTransformer = StateTransformer.builderFactory('cellPACK');
+export const CreateCompartmentSphere = CreateTransformer({
+    name: 'create-compartment-sphere',
+    display: 'CompartmentSphere',
+    from: PSO.Root, // or whatever data source
+    to: PSO.Shape.Representation3D,
+    params: {
+        center: PD.Vec3(Vec3()),
+        radius: PD.Numeric(1),
+        label: PD.Text(`Compartment Sphere`)
+    }
+})({
+    canAutoUpdate({ oldParams, newParams }) {
+        return true;
+    },
+    apply({ a, params }, plugin: PluginContext) {
+        return Task.create('Compartment Sphere', async ctx => {
+            const data = params;
+            const repr = MBRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => (MBParams));
+            await repr.createOrUpdate({ ...params, quality: 'custom', xrayShaded: true, doubleSided: true }, data).runInContext(ctx);
+            return new PSO.Shape.Representation3D({ repr, sourceData: a }, { label: data.label });
+        });
+    }
+});

+ 34 - 2
src/extensions/cellpack/util.ts

@@ -1,7 +1,8 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Ludovic Autin <ludovic.autin@gmail.com>
  */
 
 import { CIF } from '../../mol-io/reader/cif';
@@ -37,7 +38,7 @@ async function downloadPDB(plugin: PluginContext, url: string, id: string, asset
 }
 
 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);
+    const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId}.bcif`, true, assetManager);
     return { mmcif: cif.blocks[0], asset };
 }
 
@@ -74,4 +75,35 @@ export function getStructureMean(structure: Structure) {
     }
     const { elementCount } = structure;
     return Vec3.create(xSum / elementCount, ySum / elementCount, zSum / elementCount);
+}
+
+export function getFloatValue(value: DataView, offset: number) {
+    // if the last byte is a negative value (MSB is 1), the final
+    // float should be too
+    const negative = value.getInt8(offset + 2) >>> 31;
+
+    // this is how the bytes are arranged in the byte array/DataView
+    // buffer
+    const [b0, b1, b2, exponent] = [
+        // get first three bytes as unsigned since we only care
+        // about the last 8 bits of 32-bit js number returned by
+        // getUint8().
+        // Should be the same as: getInt8(offset) & -1 >>> 24
+        value.getUint8(offset),
+        value.getUint8(offset + 1),
+        value.getUint8(offset + 2),
+
+        // get the last byte, which is the exponent, as a signed int
+        // since it's already correct
+        value.getInt8(offset + 3)
+    ];
+
+    let mantissa = b0 | (b1 << 8) | (b2 << 16);
+    if (negative) {
+        // need to set the most significant 8 bits to 1's since a js
+        // number is 32 bits but our mantissa is only 24.
+        mantissa |= 255 << 24;
+    }
+
+    return mantissa * Math.pow(10, exponent);
 }

+ 21 - 5
src/mol-plugin-ui/sequence.tsx

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
@@ -25,6 +25,9 @@ import { StructureSelectionManager } from '../mol-plugin-state/manager/structure
 import { arrayEqual } from '../mol-util/array';
 
 const MaxDisplaySequenceLength = 5000;
+// TODO: add virtualized Select controls (at best with a search box)?
+const MaxSelectOptionsCount = 1000;
+const MaxSequenceWrappersCount = 30;
 
 function opKey(l: StructureElement.Location) {
     const ids = SP.unit.pdbx_struct_oper_list_ids(l);
@@ -94,7 +97,7 @@ function getSequenceWrapper(state: { structure: Structure, modelEntityId: string
     }
 }
 
-function getModelEntityOptions(structure: Structure, polymersOnly = false) {
+function getModelEntityOptions(structure: Structure, polymersOnly = false): [string, string][] {
     const options: [string, string][] = [];
     const l = StructureElement.Location.create(structure);
     const seen = new Set<string>();
@@ -118,13 +121,17 @@ function getModelEntityOptions(structure: Structure, polymersOnly = false) {
         const label = `${id}: ${description}`;
         options.push([key, label]);
         seen.add(key);
+
+        if (options.length > MaxSelectOptionsCount) {
+            return [['', 'Too many entities']];
+        }
     }
 
     if (options.length === 0) options.push(['', 'No entities']);
     return options;
 }
 
-function getChainOptions(structure: Structure, modelEntityId: string) {
+function getChainOptions(structure: Structure, modelEntityId: string): [number, string][] {
     const options: [number, string][] = [];
     const l = StructureElement.Location.create(structure);
     const seen = new Set<number>();
@@ -144,13 +151,17 @@ function getChainOptions(structure: Structure, modelEntityId: string) {
 
         options.push([id, label]);
         seen.add(id);
+
+        if (options.length > MaxSelectOptionsCount) {
+            return [[-1, 'Too many chains']];
+        }
     }
 
-    if (options.length === 0) options.push([-1, 'No units']);
+    if (options.length === 0) options.push([-1, 'No chains']);
     return options;
 }
 
-function getOperatorOptions(structure: Structure, modelEntityId: string, chainGroupId: number) {
+function getOperatorOptions(structure: Structure, modelEntityId: string, chainGroupId: number): [string, string][] {
     const options: [string, string][] = [];
     const l = StructureElement.Location.create(structure);
     const seen = new Set<string>();
@@ -168,6 +179,10 @@ function getOperatorOptions(structure: Structure, modelEntityId: string, chainGr
         const label = unit.conformation.operator.name;
         options.push([id, label]);
         seen.add(id);
+
+        if (options.length > MaxSelectOptionsCount) {
+            return [['', 'Too many operators']];
+        }
     }
 
     if (options.length === 0) options.push(['', 'No operators']);
@@ -266,6 +281,7 @@ export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceView
                         }, this.plugin.managers.structure.selection),
                         label: `${cLabel} | ${eLabel}`
                     });
+                    if (wrappers.length > MaxSequenceWrappersCount) return [];
                 }
             }
         }