Browse Source

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

# Conflicts:
#	src/extensions/cellpack/property.ts
autin 5 years ago
parent
commit
1d7321cd6f
72 changed files with 817 additions and 548 deletions
  1. 1 1
      package-lock.json
  2. 1 1
      package.json
  3. 1 1
      src/apps/structure-info/model.ts
  4. 10 12
      src/apps/structure-info/volume.ts
  5. 3 3
      src/apps/viewer/index.ts
  6. 2 1
      src/extensions/cellpack/property.ts
  7. 2 1
      src/extensions/pdbe/preferred-assembly.ts
  8. 2 1
      src/extensions/pdbe/struct-ref-domain.ts
  9. 2 1
      src/extensions/pdbe/structure-quality-report/prop.ts
  10. 2 1
      src/extensions/rcsb/assembly-symmetry/prop.ts
  11. 2 1
      src/extensions/rcsb/validation-report/prop.ts
  12. 15 6
      src/mol-canvas3d/canvas3d.ts
  13. 208 0
      src/mol-canvas3d/helper/handle-helper.ts
  14. 6 2
      src/mol-canvas3d/passes/draw.ts
  15. 3 2
      src/mol-canvas3d/passes/image.ts
  16. 7 2
      src/mol-canvas3d/passes/pick.ts
  17. 1 1
      src/mol-model-formats/structure/basic/parser.ts
  18. 2 1
      src/mol-model-formats/structure/common/property.ts
  19. 1 1
      src/mol-model-formats/structure/property/anisotropic.ts
  20. 1 1
      src/mol-model-formats/structure/property/bonds/comp.ts
  21. 1 1
      src/mol-model-formats/structure/property/bonds/index-pair.ts
  22. 1 1
      src/mol-model-formats/structure/property/bonds/struct_conn.ts
  23. 1 1
      src/mol-model-formats/structure/property/secondary-structure.ts
  24. 1 1
      src/mol-model-formats/structure/property/symmetry.ts
  25. 16 11
      src/mol-model-formats/volume/ccp4.ts
  26. 16 11
      src/mol-model-formats/volume/cube.ts
  27. 16 11
      src/mol-model-formats/volume/density-server.ts
  28. 16 11
      src/mol-model-formats/volume/dsn6.ts
  29. 16 11
      src/mol-model-formats/volume/dx.ts
  30. 2 1
      src/mol-model-props/common/custom-element-property.ts
  31. 2 1
      src/mol-model-props/common/custom-model-property.ts
  32. 1 1
      src/mol-model-props/common/custom-property.ts
  33. 2 1
      src/mol-model-props/common/custom-structure-property.ts
  34. 2 1
      src/mol-model-props/computed/accessible-surface-area.ts
  35. 2 1
      src/mol-model-props/computed/interactions.ts
  36. 1 1
      src/mol-model-props/computed/secondary-structure.ts
  37. 2 1
      src/mol-model-props/computed/valence-model.ts
  38. 2 1
      src/mol-model-props/integrative/cross-link-restraint/format.ts
  39. 2 1
      src/mol-model-props/integrative/cross-link-restraint/property.ts
  40. 5 5
      src/mol-model/custom-property.ts
  41. 59 96
      src/mol-model/sequence/sequence.ts
  42. 1 2
      src/mol-model/structure.ts
  43. 1 1
      src/mol-model/structure/export/mmcif.ts
  44. 1 1
      src/mol-model/structure/model/model.ts
  45. 1 1
      src/mol-model/structure/structure/structure.ts
  46. 11 3
      src/mol-model/structure/structure/unit/bonds/intra-compute.ts
  47. 4 2
      src/mol-model/volume.ts
  48. 0 96
      src/mol-model/volume/data.ts
  49. 57 0
      src/mol-model/volume/grid.ts
  50. 91 18
      src/mol-model/volume/volume.ts
  51. 7 8
      src/mol-plugin-state/formats/volume.ts
  52. 18 18
      src/mol-plugin-state/helpers/volume-representation-params.ts
  53. 2 2
      src/mol-plugin-state/objects.ts
  54. 6 6
      src/mol-plugin-state/transforms/representation.ts
  55. 3 3
      src/mol-plugin-state/transforms/volume.ts
  56. 7 7
      src/mol-plugin-ui/custom/volume.tsx
  57. 10 9
      src/mol-plugin-ui/sequence/polymer.ts
  58. 2 2
      src/mol-plugin-ui/structure/volume.tsx
  59. 18 18
      src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts
  60. 3 3
      src/mol-plugin/behavior/dynamic/volume-streaming/model.ts
  61. 4 4
      src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts
  62. 18 21
      src/mol-repr/volume/direct-volume.ts
  63. 48 49
      src/mol-repr/volume/isosurface.ts
  64. 2 2
      src/mol-repr/volume/registry.ts
  65. 18 18
      src/mol-repr/volume/representation.ts
  66. 32 33
      src/mol-repr/volume/slice.ts
  67. 2 1
      src/mol-script/runtime/query/base.ts
  68. 2 2
      src/mol-state/action.ts
  69. 1 0
      src/mol-state/state.ts
  70. 5 5
      src/mol-theme/label.ts
  71. 2 2
      src/mol-theme/theme.ts
  72. 2 1
      src/perf-tests/mol-script.ts

+ 1 - 1
package-lock.json

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

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "molstar",
-  "version": "0.7.0-dev.17",
+  "version": "0.7.0-dev.18",
   "description": "A comprehensive macromolecular library.",
   "homepage": "https://github.com/molstar/molstar#readme",
   "repository": {

+ 1 - 1
src/apps/structure-info/model.ts

@@ -117,7 +117,7 @@ export function printSequence(model: Model) {
     for (const key of Object.keys(byEntityKey)) {
         const { sequence, entityId } = byEntityKey[+key];
         const { seqId, compId } = sequence;
-        console.log(`${entityId} (${sequence.kind} ${seqId.value(0)} (offset ${sequence.offset}), ${seqId.value(seqId.rowCount - 1)}) (${compId.value(0)}, ${compId.value(compId.rowCount - 1)})`);
+        console.log(`${entityId} (${sequence.kind} ${seqId.value(0)}, ${seqId.value(seqId.rowCount - 1)}) (${compId.value(0)}, ${compId.value(compId.rowCount - 1)})`);
         console.log(`${Sequence.getSequenceString(sequence)}`);
     }
     console.log();

+ 10 - 12
src/apps/structure-info/volume.ts

@@ -8,38 +8,36 @@ import * as fs from 'fs';
 import * as argparse from 'argparse';
 import * as util from 'util';
 
-import { VolumeData, VolumeIsoValue } from '../../mol-model/volume';
+import { Volume } from '../../mol-model/volume';
 import { downloadCif } from './helpers';
 import { CIF } from '../../mol-io/reader/cif';
-import { DensityServer_Data_Database } from '../../mol-io/reader/cif/schema/density-server';
 import { Table } from '../../mol-data/db';
 import { StringBuilder } from '../../mol-util';
 import { Task } from '../../mol-task';
 import { createVolumeIsosurfaceMesh } from '../../mol-repr/volume/isosurface';
 import { Theme } from '../../mol-theme/theme';
-import { volumeFromDensityServerData } from '../../mol-model-formats/volume/density-server';
+import { volumeFromDensityServerData, DscifFormat } from '../../mol-model-formats/volume/density-server';
 
 require('util.promisify').shim();
 const writeFileAsync = util.promisify(fs.writeFile);
 
-type Volume = { source: DensityServer_Data_Database, volume: VolumeData }
-
 async function getVolume(url: string): Promise<Volume> {
     const cif = await downloadCif(url, true);
     const data = CIF.schema.densityServer(cif.blocks[1]);
-    return { source: data, volume: await volumeFromDensityServerData(data).run() };
+    return await volumeFromDensityServerData(data).run();
 }
 
-function print(data: Volume) {
-    const { volume_data_3d_info } = data.source;
+function print(volume: Volume) {
+    if (!DscifFormat.is(volume.sourceData)) return;
+    const { volume_data_3d_info } = volume.sourceData.data;
     const row = Table.getRow(volume_data_3d_info, 0);
     console.log(row);
-    if (data.volume.transform) console.log(data.volume.transform);
-    console.log(data.volume.dataStats);
+    console.log(volume.grid.transform);
+    console.log(volume.grid.stats);
 }
 
-async function doMesh(data: Volume, filename: string) {
-    const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, data.volume, Theme.createEmpty(), { isoValue: VolumeIsoValue.absolute(1.5) } )).run();
+async function doMesh(volume: Volume, filename: string) {
+    const mesh = await Task.create('', runtime => createVolumeIsosurfaceMesh({ runtime }, volume, Theme.createEmpty(), { isoValue: Volume.IsoValue.absolute(1.5) } )).run();
     console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
 
     // Export the mesh in OBJ format.

+ 3 - 3
src/apps/viewer/index.ts

@@ -110,7 +110,7 @@ export class Viewer {
     }
 
     async loadStructureFromUrl(url: string, format = 'cif', isBinary = false) {
-        const params = DownloadStructure.createDefaultParams(undefined, this.plugin);
+        const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
         return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
             source: {
                 name: 'url',
@@ -125,7 +125,7 @@ export class Viewer {
     }
 
     async loadPdb(pdb: string) {
-        const params = DownloadStructure.createDefaultParams(undefined, this.plugin);
+        const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
         const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!;
         return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
             source: {
@@ -145,7 +145,7 @@ export class Viewer {
     }
 
     async loadPdbDev(pdbDev: string) {
-        const params = DownloadStructure.createDefaultParams(undefined, this.plugin);
+        const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
         return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
             source: {
                 name: 'pdb-dev' as const,

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

@@ -5,10 +5,11 @@
  */
 
 import { CustomStructureProperty } from '../../mol-model-props/common/custom-structure-property';
-import { Structure, CustomPropertyDescriptor } from '../../mol-model/structure';
+import { Structure } from '../../mol-model/structure';
 import { CustomProperty } from '../../mol-model-props/common/custom-property';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { Color } from '../../mol-util/color';
+import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
 
 export type CellPackInfoValue = {
     packingsCount: number

+ 2 - 1
src/extensions/pdbe/preferred-assembly.ts

@@ -7,9 +7,10 @@
 import { Column, Table } from '../../mol-data/db';
 import { toTable } from '../../mol-io/reader/cif/schema';
 import { CifWriter } from '../../mol-io/writer/cif';
-import { Model, CustomPropertyDescriptor } from '../../mol-model/structure';
+import { Model } from '../../mol-model/structure';
 import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
 import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
+import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
 
 export namespace PDBePreferredAssembly {
     export type Property = string

+ 2 - 1
src/extensions/pdbe/struct-ref-domain.ts

@@ -7,9 +7,10 @@
 import { Column, Table } from '../../mol-data/db';
 import { toTable } from '../../mol-io/reader/cif/schema';
 import { CifWriter } from '../../mol-io/writer/cif';
-import { Model, CustomPropertyDescriptor } from '../../mol-model/structure';
+import { Model } from '../../mol-model/structure';
 import { PropertyWrapper } from '../../mol-model-props/common/wrapper';
 import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
+import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
 
 export namespace PDBeStructRefDomain {
     export type Property = PropertyWrapper<Table<Schema['pdbe_struct_ref_domain']> | undefined>

+ 2 - 1
src/extensions/pdbe/structure-quality-report/prop.ts

@@ -9,7 +9,7 @@ import { Column, Table } from '../../../mol-data/db';
 import { toTable } from '../../../mol-io/reader/cif/schema';
 import { mmCIF_residueId_schema } from '../../../mol-io/reader/cif/schema/mmcif-extras';
 import { CifWriter } from '../../../mol-io/writer/cif';
-import { Model, CustomPropertyDescriptor, ResidueIndex, Unit, IndexedCustomProperty } from '../../../mol-model/structure';
+import { Model, ResidueIndex, Unit, IndexedCustomProperty } from '../../../mol-model/structure';
 import { residueIdFields } from '../../../mol-model/structure/export/categories/atom_site';
 import { StructureElement, CifExportContext, Structure } from '../../../mol-model/structure/structure';
 import { CustomPropSymbol } from '../../../mol-script/language/symbol';
@@ -22,6 +22,7 @@ import { PropertyWrapper } from '../../../mol-model-props/common/wrapper';
 import { CustomProperty } from '../../../mol-model-props/common/custom-property';
 import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
 import { Asset } from '../../../mol-util/assets';
+import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
 
 export { StructureQualityReport };
 

+ 2 - 1
src/extensions/rcsb/assembly-symmetry/prop.ts

@@ -8,7 +8,7 @@ import { AssemblySymmetryQuery, AssemblySymmetryQueryVariables } from '../graphq
 import query from '../graphql/symmetry.gql';
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { CustomPropertyDescriptor, Structure, Model, StructureSelection, QueryContext } from '../../../mol-model/structure';
+import { Structure, Model, StructureSelection, QueryContext } from '../../../mol-model/structure';
 import { Database as _Database, Column } from '../../../mol-data/db';
 import { GraphQLClient } from '../../../mol-util/graphql-client';
 import { CustomProperty } from '../../../mol-model-props/common/custom-property';
@@ -19,6 +19,7 @@ import { ReadonlyVec3 } from '../../../mol-math/linear-algebra/3d/vec3';
 import { SetUtils } from '../../../mol-util/set';
 import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
 import { compile } from '../../../mol-script/runtime/query/compiler';
+import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
 
 const BiologicalAssemblyNames = new Set([
     'author_and_software_defined_assembly',

+ 2 - 1
src/extensions/rcsb/validation-report/prop.ts

@@ -5,7 +5,7 @@
  */
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { CustomPropertyDescriptor, Structure, Unit } from '../../../mol-model/structure';
+import { Structure, Unit } from '../../../mol-model/structure';
 import { CustomProperty } from '../../../mol-model-props/common/custom-property';
 import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
 import { Model, ElementIndex, ResidueIndex } from '../../../mol-model/structure/model';
@@ -21,6 +21,7 @@ import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
 import { CustomPropSymbol } from '../../../mol-script/language/symbol';
 import Type from '../../../mol-script/language/type';
 import { Asset } from '../../../mol-util/assets';
+import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
 
 export { ValidationReport };
 

+ 15 - 6
src/mol-canvas3d/canvas3d.ts

@@ -36,6 +36,7 @@ import { Sphere3D } from '../mol-math/geometry';
 import { isDebugMode } from '../mol-util/debug';
 import { CameraHelperParams } from './helper/camera-helper';
 import { produce } from 'immer';
+import { HandleHelper, HandleHelperParams } from './helper/handle-helper';
 
 export const Canvas3DParams = {
     camera: PD.Group({
@@ -60,7 +61,8 @@ export const Canvas3DParams = {
     postprocessing: PD.Group(PostprocessingParams),
     renderer: PD.Group(RendererParams),
     trackball: PD.Group(TrackballControlsParams),
-    debug: PD.Group(DebugHelperParams)
+    debug: PD.Group(DebugHelperParams),
+    handle: PD.Group(HandleHelperParams),
 };
 export const DefaultCanvas3DParams = PD.getDefaultValues(Canvas3DParams);
 export type Canvas3DProps = PD.Values<typeof Canvas3DParams>
@@ -188,12 +190,13 @@ namespace Canvas3D {
         const controls = TrackballControls.create(input, camera, p.trackball);
         const renderer = Renderer.create(webgl, p.renderer);
         const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
+        const handleHelper = new HandleHelper(webgl, p.handle);
         const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
 
-        const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, {
+        const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, handleHelper, {
             cameraHelper: p.camera.helper
         });
-        const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5);
+        const pickPass = new PickPass(webgl, renderer, scene, camera, handleHelper, 0.5);
         const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing);
         const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample);
 
@@ -205,6 +208,7 @@ namespace Canvas3D {
         function getLoci(pickingId: PickingId) {
             let loci: Loci = EmptyLoci;
             let repr: Representation.Any = Representation.Empty;
+            loci = handleHelper.getLoci(pickingId);
             reprRenderObjects.forEach((_, _repr) => {
                 const _loci = _repr.getLoci(pickingId);
                 if (!isEmptyLoci(_loci)) {
@@ -224,10 +228,12 @@ namespace Canvas3D {
             if (repr) {
                 changed = repr.mark(loci, action);
             } else {
+                changed = handleHelper.mark(loci, action);
                 reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
             }
             if (changed) {
                 scene.update(void 0, true);
+                handleHelper.scene.update(void 0, true);
                 const prevPickDirty = pickPass.pickDirty;
                 draw(true);
                 pickPass.pickDirty = prevPickDirty; // marking does not change picking buffers
@@ -428,7 +434,8 @@ namespace Canvas3D {
                 multiSample: { ...multiSample.props },
                 renderer: { ...renderer.props },
                 trackball: { ...controls.props },
-                debug: { ...debugHelper.props }
+                debug: { ...debugHelper.props },
+                handle: { ...handleHelper.props },
             };
         }
 
@@ -535,11 +542,12 @@ namespace Canvas3D {
                 if (props.renderer) renderer.setProps(props.renderer);
                 if (props.trackball) controls.setProps(props.trackball);
                 if (props.debug) debugHelper.setProps(props.debug);
+                if (props.handle) handleHelper.setProps(props.handle);
 
                 requestDraw(true);
             },
             getImagePass: (props: Partial<ImageProps> = {}) => {
-                return new ImagePass(webgl, renderer, scene, camera, debugHelper, props);
+                return new ImagePass(webgl, renderer, scene, camera, debugHelper, handleHelper, props);
             },
 
             get props() {
@@ -563,7 +571,8 @@ namespace Canvas3D {
                     multiSample: { ...multiSample.props },
                     renderer: { ...renderer.props },
                     trackball: { ...controls.props },
-                    debug: { ...debugHelper.props }
+                    debug: { ...debugHelper.props },
+                    handle: { ...handleHelper.props },
                 };
             },
             get input() {

+ 208 - 0
src/mol-canvas3d/helper/handle-helper.ts

@@ -0,0 +1,208 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { WebGLContext } from '../../mol-gl/webgl/context';
+import Scene from '../../mol-gl/scene';
+import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
+import { Vec3, Mat4, Mat3 } from '../../mol-math/linear-algebra';
+import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
+import { GraphicsRenderObject } from '../../mol-gl/render-object';
+import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
+import { ColorNames } from '../../mol-util/color/names';
+import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
+import { ValueCell } from '../../mol-util';
+import { Sphere3D } from '../../mol-math/geometry';
+import { ParamDefinition as PD } from '../../mol-util/param-definition';
+import produce from 'immer';
+import { Shape } from '../../mol-model/shape';
+import { PickingId } from '../../mol-geo/geometry/picking';
+import { Camera } from '../camera';
+import { DataLoci, EmptyLoci, Loci } from '../../mol-model/loci';
+import { MarkerAction, MarkerActions } from '../../mol-util/marker-action';
+import { Visual } from '../../mol-repr/visual';
+import { Interval } from '../../mol-data/int';
+
+const HandleParams = {
+    ...Mesh.Params,
+    alpha: { ...Mesh.Params.alpha, defaultValue: 1 },
+    ignoreLight: { ...Mesh.Params.ignoreLight, defaultValue: true },
+    colorX: PD.Color(ColorNames.red, { isEssential: true }),
+    colorY: PD.Color(ColorNames.green, { isEssential: true }),
+    colorZ: PD.Color(ColorNames.blue, { isEssential: true }),
+    scale: PD.Numeric(0.33, { min: 0.1, max: 2, step: 0.1 }, { isEssential: true }),
+};
+type HandleParams = typeof HandleParams
+type HandleProps = PD.Values<HandleParams>
+
+export const HandleHelperParams = {
+    handle: PD.MappedStatic('off', {
+        on: PD.Group(HandleParams),
+        off: PD.Group({})
+    }, { cycle: true, description: 'Show handle tool' }),
+};
+export type HandleHelperParams = typeof HandleHelperParams
+export type HandleHelperProps = PD.Values<HandleHelperParams>
+
+export class HandleHelper {
+    scene: Scene
+    props: HandleHelperProps = {
+        handle: { name: 'off', params: {} }
+    }
+
+    private renderObject: GraphicsRenderObject | undefined
+
+    private _transform = Mat4();
+    getBoundingSphere(out: Sphere3D, instanceId: number) {
+        if (this.renderObject) {
+            Sphere3D.copy(out, this.renderObject.values.invariantBoundingSphere.ref.value);
+            Mat4.fromArray(this._transform, this.renderObject.values.aTransform.ref.value, instanceId * 16);
+            Sphere3D.transform(out, out, this._transform);
+        }
+        return out;
+    }
+
+    setProps(props: Partial<HandleHelperProps>) {
+        this.props = produce(this.props, p => {
+            if (props.handle !== undefined) {
+                p.handle.name = props.handle.name;
+                if (props.handle.name === 'on') {
+                    this.scene.clear();
+                    const params = { ...props.handle.params, scale: props.handle.params.scale * this.webgl.pixelRatio };
+                    this.renderObject = createHandleRenderObject(params);
+                    this.scene.add(this.renderObject);
+                    this.scene.commit();
+
+                    p.handle.params = { ...props.handle.params };
+                }
+            }
+        });
+    }
+
+    get isEnabled() {
+        return this.props.handle.name === 'on';
+    }
+
+    // TODO could be a lists of position/rotation if we want to show more than one handle tool,
+    //      they would be distingishable by their instanceId
+    update(camera: Camera, position: Vec3, rotation: Mat3) {
+        if (!this.renderObject) return;
+
+        Mat4.setTranslation(this.renderObject.values.aTransform.ref.value as unknown as Mat4, position);
+        Mat4.fromMat3(this.renderObject.values.aTransform.ref.value as unknown as Mat4, rotation);
+
+        // TODO make invariant to camera scaling by adjusting renderObject transform
+
+        ValueCell.update(this.renderObject.values.aTransform, this.renderObject.values.aTransform.ref.value);
+        this.scene.update([this.renderObject], true);
+    }
+
+    getLoci(pickingId: PickingId) {
+        const { objectId, groupId, instanceId } = pickingId;
+        if (!this.renderObject || objectId !== this.renderObject.id) return EmptyLoci;
+        return HandleLoci(this, groupId, instanceId);
+    }
+
+    private eachGroup = (loci: Loci, apply: (interval: Interval) => boolean): boolean => {
+        if (!this.renderObject) return false;
+        if (!isHandleLoci(loci)) return false;
+        let changed = false;
+        const groupCount = this.renderObject.values.uGroupCount.ref.value;
+        const { elements } = loci;
+        for (const { groupId, instanceId } of elements) {
+            const idx = instanceId * groupCount + groupId;
+            if (apply(Interval.ofSingleton(idx))) changed = true;
+        }
+        return changed;
+    }
+
+    mark(loci: Loci, action: MarkerAction) {
+        if (!MarkerActions.is(MarkerActions.Highlighting, action)) return false;
+        if (!isHandleLoci(loci)) return false;
+        if (loci.data !== this) return false;
+        return Visual.mark(this.renderObject, loci, action, this.eachGroup);
+    }
+
+    constructor(private webgl: WebGLContext, props: Partial<HandleHelperProps> = {}) {
+        this.scene = Scene.create(webgl);
+        this.setProps(props);
+    }
+}
+
+function createHandleMesh(scale: number, mesh?: Mesh) {
+    const state = MeshBuilder.createState(512, 256, mesh);
+    const radius = 0.05 * scale;
+    const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
+    const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
+    const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
+    const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
+
+    state.currentGroup = HandleGroup.TranslateScreenXY;
+    addSphere(state, Vec3.origin, radius * 3, 2);
+
+    state.currentGroup = HandleGroup.TranslateObjectX;
+    addSphere(state, x, radius, 2);
+    addCylinder(state, Vec3.origin, x, 1, cylinderProps);
+
+    state.currentGroup = HandleGroup.TranslateObjectY;
+    addSphere(state, y, radius, 2);
+    addCylinder(state, Vec3.origin, y, 1, cylinderProps);
+
+    state.currentGroup = HandleGroup.TranslateObjectZ;
+    addSphere(state, z, radius, 2);
+    addCylinder(state, Vec3.origin, z, 1, cylinderProps);
+
+    // TODO add more helper geometries for the other HandleGroup options
+    // TODO add props to create subset of geometries
+
+    return MeshBuilder.getMesh(state);
+}
+
+export const HandleGroup = {
+    None: 0,
+    TranslateScreenXY: 1,
+    // TranslateScreenZ: 2,
+    TranslateObjectX: 3,
+    TranslateObjectY: 4,
+    TranslateObjectZ: 5,
+    // TranslateObjectXY: 6,
+    // TranslateObjectXZ: 7,
+    // TranslateObjectYZ: 8,
+
+    // RotateScreenZ: 9,
+    // RotateObjectX: 10,
+    // RotateObjectY: 11,
+    // RotateObjectZ: 12,
+} as const;
+
+function HandleLoci(handleHelper: HandleHelper, groupId: number, instanceId: number) {
+    return DataLoci('handle', handleHelper, [{ groupId, instanceId }],
+        (boundingSphere: Sphere3D) => handleHelper.getBoundingSphere(boundingSphere, instanceId),
+        () => `Handle Helper | Group Id ${groupId} | Instance Id ${instanceId}`);
+}
+export type HandleLoci = ReturnType<typeof HandleLoci>
+export function isHandleLoci(x: Loci): x is HandleLoci {
+    return x.kind === 'data-loci' && x.tag === 'handle';
+}
+
+function getHandleShape(props: HandleProps, shape?: Shape<Mesh>) {
+    const scale = 10 * props.scale;
+    const mesh = createHandleMesh(scale, shape?.geometry);
+    mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4));
+    const getColor = (groupId: number) => {
+        switch (groupId) {
+            case HandleGroup.TranslateObjectX: return props.colorX;
+            case HandleGroup.TranslateObjectY: return props.colorY;
+            case HandleGroup.TranslateObjectZ: return props.colorZ;
+            default: return ColorNames.grey;
+        }
+    };
+    return Shape.create('handle', {}, mesh, getColor, () => 1, () => '');
+}
+
+function createHandleRenderObject(props: HandleProps) {
+    const shape = getHandleShape(props);
+    return Shape.createRenderObject(shape, props);
+}

+ 6 - 2
src/mol-canvas3d/passes/draw.ts

@@ -13,6 +13,7 @@ import { Texture } from '../../mol-gl/webgl/texture';
 import { Camera } from '../camera';
 import { CameraHelper, CameraHelperParams } from '../helper/camera-helper';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
+import { HandleHelper } from '../helper/handle-helper';
 
 export const DrawPassParams = {
     cameraHelper: PD.Group(CameraHelperParams)
@@ -29,7 +30,7 @@ export class DrawPass {
 
     private depthTarget: RenderTarget | null
 
-    constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, props: Partial<DrawPassProps> = {}) {
+    constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, private handleHelper: HandleHelper, props: Partial<DrawPassProps> = {}) {
         const { gl, extensions, resources } = webgl;
         const width = gl.drawingBufferWidth;
         const height = gl.drawingBufferHeight;
@@ -89,12 +90,15 @@ export class DrawPass {
     }
 
     private renderInternal(variant: 'color' | 'depth', transparentBackground: boolean) {
-        const { renderer, scene, camera, debugHelper, cameraHelper } = this;
+        const { renderer, scene, camera, debugHelper, cameraHelper, handleHelper } = this;
         renderer.render(scene, camera, variant, true, transparentBackground);
         if (debugHelper.isEnabled) {
             debugHelper.syncVisibility();
             renderer.render(debugHelper.scene, camera, variant, false, transparentBackground);
         }
+        if (handleHelper.isEnabled) {
+            renderer.render(handleHelper.scene, camera, variant, false, transparentBackground);
+        }
         if (cameraHelper.isEnabled) {
             cameraHelper.update(camera);
             renderer.render(cameraHelper.scene, cameraHelper.camera, variant, false, transparentBackground);

+ 3 - 2
src/mol-canvas3d/passes/image.ts

@@ -15,6 +15,7 @@ import { PostprocessingPass, PostprocessingParams } from './postprocessing';
 import { MultiSamplePass, MultiSampleParams } from './multi-sample';
 import { Camera } from '../camera';
 import { Viewport } from '../camera/util';
+import { HandleHelper } from '../helper/handle-helper';
 
 export const ImageParams = {
     transparentBackground: PD.Boolean(false),
@@ -40,12 +41,12 @@ export class ImagePass {
     get width() { return this._width; }
     get height() { return this._height; }
 
-    constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, props: Partial<ImageProps>) {
+    constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, handleHelper: HandleHelper, props: Partial<ImageProps>) {
         const p = { ...PD.getDefaultValues(ImageParams), ...props };
 
         this._transparentBackground = p.transparentBackground;
 
-        this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, p.drawPass);
+        this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, handleHelper, p.drawPass);
         this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing);
         this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample);
 

+ 7 - 2
src/mol-canvas3d/passes/pick.ts

@@ -11,6 +11,7 @@ import Scene from '../../mol-gl/scene';
 import { PickingId } from '../../mol-geo/geometry/picking';
 import { decodeFloatRGB } from '../../mol-util/float-packing';
 import { Camera } from '../camera';
+import { HandleHelper } from '../helper/handle-helper';
 
 export class PickPass {
     pickDirty = true
@@ -27,7 +28,7 @@ export class PickPass {
     private pickWidth: number
     private pickHeight: number
 
-    constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private pickBaseScale: number) {
+    constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private handleHelper: HandleHelper, private pickBaseScale: number) {
         const { gl } = webgl;
         const width = gl.drawingBufferWidth;
         const height = gl.drawingBufferHeight;
@@ -65,14 +66,18 @@ export class PickPass {
     }
 
     render() {
-        const { renderer, scene, camera } = this;
+        const { renderer, scene, camera, handleHelper: { scene: handleScene } } = this;
         renderer.setViewport(0, 0, this.pickWidth, this.pickHeight);
+
         this.objectPickTarget.bind();
         renderer.render(scene, camera, 'pickObject', true, false);
+        renderer.render(handleScene, camera, 'pickObject', false, false);
         this.instancePickTarget.bind();
         renderer.render(scene, camera, 'pickInstance', true, false);
+        renderer.render(handleScene, camera, 'pickInstance', false, false);
         this.groupPickTarget.bind();
         renderer.render(scene, camera, 'pickGroup', true, false);
+        renderer.render(handleScene, camera, 'pickGroup', false, false);
 
         this.pickDirty = false;
     }

+ 1 - 1
src/mol-model-formats/structure/basic/parser.ts

@@ -10,7 +10,7 @@ import { RuntimeContext } from '../../../mol-task';
 import UUID from '../../../mol-util/uuid';
 import { Model } from '../../../mol-model/structure/model/model';
 import { Entities } from '../../../mol-model/structure/model/properties/common';
-import { CustomProperties } from '../../../mol-model/structure';
+import { CustomProperties } from '../../../mol-model/custom-property';
 import { getAtomicHierarchyAndConformation } from './atomic';
 import { getCoarse, EmptyCoarse, CoarseData } from './coarse';
 import { getSequence } from './sequence';

+ 2 - 1
src/mol-model-formats/structure/common/property.ts

@@ -4,8 +4,9 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { CustomPropertyDescriptor, Model } from '../../../mol-model/structure';
+import { Model } from '../../../mol-model/structure';
 import { ModelFormat } from '../../format';
+import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
 
 class FormatRegistry<T> {
     private map = new Map<ModelFormat['kind'], (model: Model) => T | undefined>()

+ 1 - 1
src/mol-model-formats/structure/property/anisotropic.ts

@@ -5,7 +5,7 @@
  */
 
 import { Table, Column } from '../../../mol-data/db';
-import { CustomPropertyDescriptor } from '../../../mol-model/structure';
+import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
 import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
 import { CifWriter } from '../../../mol-io/writer/cif';
 import { FormatPropertyProvider } from '../common/property';

+ 1 - 1
src/mol-model-formats/structure/property/bonds/comp.ts

@@ -7,7 +7,7 @@
 
 import { Model } from '../../../../mol-model/structure/model/model';
 import { BondType } from '../../../../mol-model/structure/model/types';
-import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
+import { CustomPropertyDescriptor } from '../../../../mol-model/custom-property';
 import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
 import { CifWriter } from '../../../../mol-io/writer/cif';
 import { Table } from '../../../../mol-data/db';

+ 1 - 1
src/mol-model-formats/structure/property/bonds/index-pair.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
+import { CustomPropertyDescriptor } from '../../../../mol-model/custom-property';
 import { IntAdjacencyGraph } from '../../../../mol-math/graph';
 import { Column } from '../../../../mol-data/db';
 import { FormatPropertyProvider } from '../../common/property';

+ 1 - 1
src/mol-model-formats/structure/property/bonds/struct_conn.ts

@@ -9,7 +9,7 @@ import { Model } from '../../../../mol-model/structure/model/model';
 import { Structure } from '../../../../mol-model/structure';
 import { BondType } from '../../../../mol-model/structure/model/types';
 import { Column, Table } from '../../../../mol-data/db';
-import { CustomPropertyDescriptor } from '../../../../mol-model/structure';
+import { CustomPropertyDescriptor } from '../../../../mol-model/custom-property';
 import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
 import { SortedArray } from '../../../../mol-data/int';
 import { CifWriter } from '../../../../mol-io/writer/cif';

+ 1 - 1
src/mol-model-formats/structure/property/secondary-structure.ts

@@ -13,7 +13,7 @@ import { SecondaryStructure } from '../../../mol-model/structure/model/propertie
 import { Column, Table } from '../../../mol-data/db';
 import { ChainIndex, ResidueIndex } from '../../../mol-model/structure/model/indexing';
 import { FormatPropertyProvider } from '../common/property';
-import { CustomPropertyDescriptor } from '../../../mol-model/structure';
+import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
 
 export { ModelSecondaryStructure };
 

+ 1 - 1
src/mol-model-formats/structure/property/symmetry.ts

@@ -10,7 +10,7 @@ import { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/
 import { Tensor, Vec3, Mat3 } from '../../../mol-math/linear-algebra';
 import { Symmetry } from '../../../mol-model/structure/model/properties/symmetry';
 import { createAssemblies } from './assembly';
-import { CustomPropertyDescriptor } from '../../../mol-model/structure';
+import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
 import { FormatPropertyProvider } from '../common/property';
 import { Table } from '../../../mol-data/db';
 

+ 16 - 11
src/mol-model-formats/volume/ccp4.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { VolumeData } from '../../mol-model/volume/data';
+import { Volume } from '../../mol-model/volume';
 import { Task } from '../../mol-task';
 import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
 import { Tensor, Vec3 } from '../../mol-math/linear-algebra';
@@ -14,6 +14,7 @@ import { getCcp4ValueType } from '../../mol-io/reader/ccp4/parser';
 import { TypedArrayValueType } from '../../mol-io/common/typed-array';
 import { arrayMin, arrayRms, arrayMean, arrayMax } from '../../mol-util/array';
 import { ModelFormat } from '../format';
+import { CustomProperties } from '../../mol-model/custom-property';
 
 /** When available (e.g. in MRC files) use ORIGIN records instead of N[CRS]START */
 export function getCcp4Origin(header: Ccp4Header): Vec3 {
@@ -39,8 +40,8 @@ function getTypedArrayCtor(header: Ccp4Header) {
     throw Error(`${valueType} is not a supported value format.`);
 }
 
-export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, offset?: Vec3, label?: string }): Task<VolumeData> {
-    return Task.create<VolumeData>('Create Volume Data', async ctx => {
+export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, offset?: Vec3, label?: string }): Task<Volume> {
+    return Task.create<Volume>('Create Volume', async ctx => {
         const { header, values } = source;
         const size = Vec3.create(header.xLength, header.yLength, header.zLength);
         if (params && params.voxelSize) Vec3.mul(size, size, params.voxelSize);
@@ -69,15 +70,19 @@ export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, of
 
         return {
             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,
-                max: isNaN(header.AMAX) ? arrayMax(values) : header.AMAX,
-                mean: isNaN(header.AMEAN) ? arrayMean(values) : header.AMEAN,
-                sigma: (isNaN(header.ARMS) || header.ARMS === 0) ? arrayRms(values) : header.ARMS
+            grid: {
+                transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
+                cells: data,
+                stats: {
+                    min: isNaN(header.AMIN) ? arrayMin(values) : header.AMIN,
+                    max: isNaN(header.AMAX) ? arrayMax(values) : header.AMAX,
+                    mean: isNaN(header.AMEAN) ? arrayMean(values) : header.AMEAN,
+                    sigma: (isNaN(header.ARMS) || header.ARMS === 0) ? arrayRms(values) : header.ARMS
+                },
             },
-            sourceData: Ccp4Format.create(source)
+            sourceData: Ccp4Format.create(source),
+            customProperties: new CustomProperties(),
+            _propertyData: Object.create(null),
         };
     });
 }

+ 16 - 11
src/mol-model-formats/volume/cube.ts

@@ -6,13 +6,14 @@
 
 import { CubeFile } from '../../mol-io/reader/cube/parser';
 import { Mat4, Tensor } from '../../mol-math/linear-algebra';
-import { VolumeData } from '../../mol-model/volume/data';
+import { Volume } from '../../mol-model/volume';
 import { Task } from '../../mol-task';
 import { arrayMax, arrayMean, arrayMin, arrayRms } from '../../mol-util/array';
 import { ModelFormat } from '../format';
+import { CustomProperties } from '../../mol-model/custom-property';
 
-export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number, label?: string }): Task<VolumeData> {
-    return Task.create<VolumeData>('Create Volume Data', async () => {
+export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number, label?: string }): Task<Volume> {
+    return Task.create<Volume>('Create Volume', async () => {
         const { header, values: sourceValues } = source;
         const space = Tensor.Space(header.dim, [0, 1, 2], Float64Array);
 
@@ -45,15 +46,19 @@ export function volumeFromCube(source: CubeFile, params?: { dataIndex?: number,
 
         return {
             label: params?.label,
-            transform: { kind: 'matrix', matrix },
-            data,
-            dataStats: {
-                min: arrayMin(values),
-                max: arrayMax(values),
-                mean: arrayMean(values),
-                sigma: arrayRms(values)
+            grid: {
+                transform: { kind: 'matrix', matrix },
+                cells: data,
+                stats: {
+                    min: arrayMin(values),
+                    max: arrayMax(values),
+                    mean: arrayMean(values),
+                    sigma: arrayRms(values)
+                },
             },
-            sourceData: CubeFormat.create(source)
+            sourceData: CubeFormat.create(source),
+            customProperties: new CustomProperties(),
+            _propertyData: Object.create(null),
         };
     });
 }

+ 16 - 11
src/mol-model-formats/volume/density-server.ts

@@ -5,14 +5,15 @@
  */
 
 import { DensityServer_Data_Database } from '../../mol-io/reader/cif/schema/density-server';
-import { VolumeData } from '../../mol-model/volume/data';
+import { Volume } from '../../mol-model/volume';
 import { Task } from '../../mol-task';
 import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
 import { Tensor, Vec3 } from '../../mol-math/linear-algebra';
 import { ModelFormat } from '../format';
+import { CustomProperties } from '../../mol-model/custom-property';
 
-export function volumeFromDensityServerData(source: DensityServer_Data_Database): Task<VolumeData> {
-    return Task.create<VolumeData>('Create Volume Data', async ctx => {
+export function volumeFromDensityServerData(source: DensityServer_Data_Database): Task<Volume> {
+    return Task.create<Volume>('Create Volume', async ctx => {
         const { volume_data_3d_info: info, volume_data_3d: values } = source;
         const cell = SpacegroupCell.create(
             info.spacegroup_number.value(0),
@@ -35,15 +36,19 @@ export function volumeFromDensityServerData(source: DensityServer_Data_Database)
         const dimensions = Vec3.ofArray(normalizeOrder(info.dimensions.value(0)));
 
         return {
-            transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin, Vec3.add(Vec3.zero(), origin, dimensions)) },
-            data,
-            dataStats: {
-                min: info.min_sampled.value(0),
-                max: info.max_sampled.value(0),
-                mean: info.mean_sampled.value(0),
-                sigma: info.sigma_sampled.value(0)
+            grid: {
+                transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin, Vec3.add(Vec3.zero(), origin, dimensions)) },
+                cells: data,
+                stats: {
+                    min: info.min_sampled.value(0),
+                    max: info.max_sampled.value(0),
+                    mean: info.mean_sampled.value(0),
+                    sigma: info.sigma_sampled.value(0)
+                },
             },
-            sourceData: DscifFormat.create(source)
+            sourceData: DscifFormat.create(source),
+            customProperties: new CustomProperties(),
+            _propertyData: Object.create(null),
         };
     });
 }

+ 16 - 11
src/mol-model-formats/volume/dsn6.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { VolumeData } from '../../mol-model/volume/data';
+import { Volume } from '../../mol-model/volume';
 import { Task } from '../../mol-task';
 import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
 import { Tensor, Vec3 } from '../../mol-math/linear-algebra';
@@ -12,9 +12,10 @@ import { degToRad } from '../../mol-math/misc';
 import { Dsn6File } from '../../mol-io/reader/dsn6/schema';
 import { arrayMin, arrayMax, arrayMean, arrayRms } from '../../mol-util/array';
 import { ModelFormat } from '../format';
+import { CustomProperties } from '../../mol-model/custom-property';
 
-export function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3, label?: string }): Task<VolumeData> {
-    return Task.create<VolumeData>('Create Volume Data', async ctx => {
+export function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3, label?: string }): Task<Volume> {
+    return Task.create<Volume>('Create Volume', async ctx => {
         const { header, values } = source;
         const size = Vec3.create(header.xlen, header.ylen, header.zlen);
         if (params && params.voxelSize) Vec3.mul(size, size, params.voxelSize);
@@ -34,15 +35,19 @@ export function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3, la
 
         return {
             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),
-                max: arrayMax(values),
-                mean: arrayMean(values),
-                sigma: header.sigma !== undefined ? header.sigma : arrayRms(values)
+            grid: {
+                transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
+                cells: data,
+                stats: {
+                    min: arrayMin(values),
+                    max: arrayMax(values),
+                    mean: arrayMean(values),
+                    sigma: header.sigma !== undefined ? header.sigma : arrayRms(values)
+                },
             },
-            sourceData: Dsn6Format.create(source)
+            sourceData: Dsn6Format.create(source),
+            customProperties: new CustomProperties(),
+            _propertyData: Object.create(null),
         };
     });
 }

+ 16 - 11
src/mol-model-formats/volume/dx.ts

@@ -6,13 +6,14 @@
 
 import { DxFile } from '../../mol-io/reader/dx/parser';
 import { Mat4, Tensor } from '../../mol-math/linear-algebra';
-import { VolumeData } from '../../mol-model/volume/data';
+import { Volume } from '../../mol-model/volume';
 import { Task } from '../../mol-task';
 import { arrayMax, arrayMean, arrayMin, arrayRms } from '../../mol-util/array';
 import { ModelFormat } from '../format';
+import { CustomProperties } from '../../mol-model/custom-property';
 
-export function volumeFromDx(source: DxFile, params?: { label?: string }): Task<VolumeData> {
-    return Task.create<VolumeData>('Create Volume Data', async () => {
+export function volumeFromDx(source: DxFile, params?: { label?: string }): Task<Volume> {
+    return Task.create<Volume>('Create Volume', async () => {
         const { header, values } = source;
         const space = Tensor.Space(header.dim, [0, 1, 2], Float64Array);
         const data = Tensor.create(space, Tensor.Data1(values));
@@ -22,15 +23,19 @@ export function volumeFromDx(source: DxFile, params?: { label?: string }): Task<
 
         return {
             label: params?.label,
-            transform: { kind: 'matrix', matrix },
-            data,
-            dataStats: {
-                min: arrayMin(values),
-                max: arrayMax(values),
-                mean: arrayMean(values),
-                sigma: arrayRms(values)
+            grid: {
+                transform: { kind: 'matrix', matrix },
+                cells: data,
+                stats: {
+                    min: arrayMin(values),
+                    max: arrayMax(values),
+                    mean: arrayMean(values),
+                    sigma: arrayRms(values)
+                },
             },
-            sourceData: DxFormat.create(source)
+            sourceData: DxFormat.create(source),
+            customProperties: new CustomProperties(),
+            _propertyData: Object.create(null),
         };
     });
 }

+ 2 - 1
src/mol-model-props/common/custom-element-property.ts

@@ -5,7 +5,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ElementIndex, Model, CustomPropertyDescriptor } from '../../mol-model/structure';
+import { ElementIndex, Model } from '../../mol-model/structure';
 import { StructureElement } from '../../mol-model/structure/structure';
 import { Location } from '../../mol-model/location';
 import { ThemeDataContext } from '../../mol-theme/theme';
@@ -16,6 +16,7 @@ import { OrderedSet } from '../../mol-data/int';
 import { CustomModelProperty } from './custom-model-property';
 import { CustomProperty } from './custom-property';
 import { LociLabelProvider } from '../../mol-plugin-state/manager/loci-label';
+import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
 
 export { CustomElementProperty };
 

+ 2 - 1
src/mol-model-props/common/custom-model-property.ts

@@ -4,10 +4,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { CustomPropertyDescriptor, Model } from '../../mol-model/structure';
+import { Model } from '../../mol-model/structure';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ValueBox } from '../../mol-util';
 import { CustomProperty } from './custom-property';
+import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
 
 export { CustomModelProperty };
 

+ 1 - 1
src/mol-model-props/common/custom-property.ts

@@ -5,7 +5,7 @@
  */
 
 import { RuntimeContext } from '../../mol-task';
-import { CustomPropertyDescriptor } from '../../mol-model/structure';
+import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ValueBox } from '../../mol-util';
 import { OrderedMap } from 'immutable';

+ 2 - 1
src/mol-model-props/common/custom-structure-property.ts

@@ -4,10 +4,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { CustomPropertyDescriptor, Structure } from '../../mol-model/structure';
+import { Structure } from '../../mol-model/structure';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ValueBox } from '../../mol-util';
 import { CustomProperty } from './custom-property';
+import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
 
 export { CustomStructureProperty };
 

+ 2 - 1
src/mol-model-props/computed/accessible-surface-area.ts

@@ -7,12 +7,13 @@
 
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ShrakeRupleyComputationParams, AccessibleSurfaceArea } from './accessible-surface-area/shrake-rupley';
-import { Structure, CustomPropertyDescriptor, Unit } from '../../mol-model/structure';
+import { Structure, Unit } from '../../mol-model/structure';
 import { CustomStructureProperty } from '../common/custom-structure-property';
 import { CustomProperty } from '../common/custom-property';
 import { QuerySymbolRuntime } from '../../mol-script/runtime/query/compiler';
 import { CustomPropSymbol } from '../../mol-script/language/symbol';
 import Type from '../../mol-script/language/type';
+import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
 
 export const AccessibleSurfaceAreaParams = {
     ...ShrakeRupleyComputationParams

+ 2 - 1
src/mol-model-props/computed/interactions.ts

@@ -4,11 +4,12 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { CustomPropertyDescriptor, Structure } from '../../mol-model/structure';
+import { Structure } from '../../mol-model/structure';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { computeInteractions, Interactions, InteractionsParams as _InteractionsParams } from './interactions/interactions';
 import { CustomStructureProperty } from '../common/custom-structure-property';
 import { CustomProperty } from '../common/custom-property';
+import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
 
 export const InteractionsParams = {
     ..._InteractionsParams

+ 1 - 1
src/mol-model-props/computed/secondary-structure.ts

@@ -12,7 +12,7 @@ import { Unit } from '../../mol-model/structure/structure';
 import { CustomStructureProperty } from '../common/custom-structure-property';
 import { CustomProperty } from '../common/custom-property';
 import { ModelSecondaryStructure } from '../../mol-model-formats/structure/property/secondary-structure';
-import { CustomPropertyDescriptor } from '../../mol-model/structure/common/custom-property';
+import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
 import { Model } from '../../mol-model/structure/model';
 
 function getSecondaryStructureParams(data?: Structure) {

+ 2 - 1
src/mol-model-props/computed/valence-model.ts

@@ -4,11 +4,12 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { CustomPropertyDescriptor, Structure } from '../../mol-model/structure';
+import { Structure } from '../../mol-model/structure';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { calcValenceModel, ValenceModel, ValenceModelParams as _ValenceModelParams } from './chemistry/valence-model';
 import { CustomStructureProperty } from '../common/custom-structure-property';
 import { CustomProperty } from '../common/custom-property';
+import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
 
 export const ValenceModelParams = {
     ..._ValenceModelParams

+ 2 - 1
src/mol-model-props/integrative/cross-link-restraint/format.ts

@@ -7,9 +7,10 @@
 import { Model } from '../../../mol-model/structure/model/model';
 import { Table } from '../../../mol-data/db';
 import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
-import { Unit, CustomPropertyDescriptor } from '../../../mol-model/structure';
+import { Unit } from '../../../mol-model/structure';
 import { ElementIndex } from '../../../mol-model/structure/model/indexing';
 import { FormatPropertyProvider } from '../../../mol-model-formats/structure/common/property';
+import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
 
 export { ModelCrossLinkRestraint };
 

+ 2 - 1
src/mol-model-props/integrative/cross-link-restraint/property.ts

@@ -5,7 +5,7 @@
  */
 
 import { ModelCrossLinkRestraint } from './format';
-import { Unit, StructureElement, Structure, CustomPropertyDescriptor, Bond} from '../../../mol-model/structure';
+import { Unit, StructureElement, Structure, Bond} from '../../../mol-model/structure';
 import { PairRestraints, PairRestraint } from '../pair-restraints';
 import { CustomStructureProperty } from '../../common/custom-structure-property';
 import { CustomProperty } from '../../common/custom-property';
@@ -15,6 +15,7 @@ import { Sphere3D } from '../../../mol-math/geometry';
 import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
 import { bondLabel } from '../../../mol-theme/label';
 import { Vec3 } from '../../../mol-math/linear-algebra';
+import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
 
 export type CrossLinkRestraintValue = PairRestraints<CrossLinkRestraint>
 

+ 5 - 5
src/mol-model/structure/common/custom-property.ts → src/mol-model/custom-property.ts

@@ -5,11 +5,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { CifWriter } from '../../../mol-io/writer/cif';
-import { CifExportContext } from '../export/mmcif';
-import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
-import { UUID } from '../../../mol-util';
-import { Asset } from '../../../mol-util/assets';
+import { CifWriter } from '../mol-io/writer/cif';
+import { CifExportContext } from './structure/export/mmcif';
+import { QuerySymbolRuntime } from '../mol-script/runtime/query/compiler';
+import { UUID } from '../mol-util';
+import { Asset } from '../mol-util/assets';
 
 export { CustomPropertyDescriptor, CustomProperties };
 

+ 59 - 96
src/mol-model/sequence/sequence.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -24,14 +24,18 @@ namespace Sequence {
     export interface Base<K extends Kind, Alphabet extends string> {
         readonly kind: K,
         readonly length: number,
-        readonly offset: number,
 
+        /** One letter code */
         readonly code: Column<Alphabet>
         readonly label: Column<string>
 
         readonly seqId: Column<number>
+        /** Component id */
         readonly compId: Column<string>
 
+        /** returns index for given seqId */
+        readonly index: (seqId: number) => number
+
         /** maps seqId to list of compIds */
         readonly microHet: ReadonlyMap<number, string[]>
     }
@@ -41,11 +45,6 @@ namespace Sequence {
     export interface DNA extends Base<Kind.DNA, NuclecicAlphabet> { }
     export interface Generic extends Base<Kind.Generic, 'X' | '-'> { }
 
-    export function create<K extends Kind, Alphabet extends string>(kind: K, code: Column<Alphabet>, label: Column<string>, seqId: Column<number>, compId: Column<string>, microHet: Map<number, string[]>, offset: number = 0): Base<K, Alphabet> {
-        const length = code.rowCount;
-        return { kind, code, label, seqId, compId, microHet, offset, length };
-    }
-
     export function getSequenceString(seq: Sequence) {
         const array = seq.code.toArray();
         return (array instanceof Array ? array : Array.from(array)).join('');
@@ -88,100 +87,60 @@ namespace Sequence {
     }
 
     class ResidueNamesImpl<K extends Kind, Alphabet extends string> implements Base<K, Alphabet> {
-        private _offset = 0;
-        private _length = 0;
-        private _microHet: ReadonlyMap<number, string[]> | undefined = void 0;
-        private _code: Column<Alphabet> | undefined = undefined
-        private _label: Column<string> | undefined = undefined
-
-        private codeFromName: (name: string) => string
-
-        get code(): Column<Alphabet> {
-            if (this._code !== void 0) return this._code;
-            this.create();
-            return this._code!;
-        }
-
-        get label(): Column<string> {
-            if (this._label !== void 0) return this._label;
-            this.create();
-            return this._label!;
-        }
-
-        get offset() {
-            if (this._code !== void 0) return this._offset;
-            this.create();
-            return this._offset;
-        }
-
-        get length() {
-            if (this._code !== void 0) return this._length;
-            this.create();
-            return this._length;
-        }
+        public length: number
+        public code: Column<Alphabet>
+        public label: Column<string>
+        public seqId: Column<number>
+        public compId: Column<string>
+        public microHet: ReadonlyMap<number, string[]> = new Map()
 
-        get microHet(): ReadonlyMap<number, string[]> {
-            if (this._microHet !== void 0) return this._microHet;
-            this.create();
-            return this._microHet!;
+        private indexMap: Map<number, number>
+        index(seqId: number) {
+            return this.indexMap.get(seqId)!;
         }
 
-        private create() {
-            let maxSeqId = 0, minSeqId = Number.MAX_SAFE_INTEGER;
-            for (let i = 0, _i = this.seqId.rowCount; i < _i; i++) {
-                const id = this.seqId.value(i);
-                if (maxSeqId < id) maxSeqId = id;
-                if (id < minSeqId) minSeqId = id;
-            }
-
-            const count = maxSeqId - minSeqId + 1;
-            const sequenceArray = new Array<string>(maxSeqId + 1);
-            const labels = new Array<string[]>(maxSeqId + 1);
-            for (let i = 0; i < count; i++) {
-                sequenceArray[i] = '-';
-                labels[i] = [];
-            }
-
-            const compIds = new Array<string[]>(maxSeqId + 1);
-            for (let i = minSeqId; i <= maxSeqId; ++i) {
-                compIds[i] = [];
-            }
-
-            for (let i = 0, _i = this.seqId.rowCount; i < _i; i++) {
-                const seqId = this.seqId.value(i);
-                const idx = seqId - minSeqId;
-                const name = this.compId.value(i);
-                const code = this.codeFromName(name);
-                // in case of MICROHETEROGENEITY `sequenceArray[idx]` may already be set
-                if (!sequenceArray[idx] || sequenceArray[idx] === '-') {
-                    sequenceArray[idx] = code;
+        constructor(public kind: K, compId: Column<string>, seqId: Column<number>) {
+            const codeFromName = codeProvider(kind);
+            const codes: string[] = [];
+            const compIds: string[] = [];
+            const seqIds: number[] = [];
+            const microHet = new Map<number, string[]>();
+
+            let idx = 0;
+            const indexMap = new Map<number, number>();
+            for (let i = 0, il = seqId.rowCount; i < il; ++i) {
+                const seq_id = seqId.value(i);
+
+                if (!indexMap.has(seq_id)) {
+                    indexMap.set(seq_id, idx);
+                    const comp_id = compId.value(i);
+                    compIds[idx] = comp_id;
+                    seqIds[idx] = seq_id;
+                    codes[idx] = codeFromName(comp_id);
+                    idx += 1;
+                } else {
+                    // micro-heterogeneity
+                    if (!microHet.has(seq_id)) {
+                        microHet.set(seq_id, [compIds[indexMap.get(seq_id)!], compId.value(i)]);
+                    } else {
+                        microHet.get(seq_id)!.push(compId.value(i));
+                    }
                 }
-                labels[idx].push(code === 'X' ? name : code);
-                compIds[seqId].push(name);
             }
 
-            const microHet = new Map();
-            for (let i = minSeqId; i <= maxSeqId; ++i) {
-                if (compIds[i].length > 1) microHet.set(i, compIds[i]);
+            const labels: string[] = [];
+            for (let i = 0, il = idx; i < il; ++i) {
+                const mh = microHet.get(seqIds[i]);
+                labels[i] = mh ? `(${mh.join('|')})` : codes[i];
             }
 
-            this._code = Column.ofStringArray(sequenceArray) as Column<Alphabet>;
-            this._label = Column.ofLambda({
-                value: i => {
-                    const l = labels[i];
-                    return l.length > 1 ? `(${l.join('|')})` : l.join('');
-                },
-                rowCount: labels.length,
-                schema: Column.Schema.str
-            });
-            this._microHet = microHet;
-            this._offset = minSeqId - 1;
-            this._length = count;
-        }
-
-        constructor(public kind: K, public compId: Column<string>, public seqId: Column<number>) {
-
-            this.codeFromName = codeProvider(kind);
+            this.length = idx;
+            this.code = Column.ofStringArray(codes) as Column<Alphabet>;
+            this.compId = Column.ofStringArray(compIds);
+            this.seqId = Column.ofIntArray(seqIds);
+            this.label = Column.ofStringArray(labels);
+            this.microHet = microHet;
+            this.indexMap = indexMap;
         }
     }
 
@@ -192,13 +151,17 @@ namespace Sequence {
     }
 
     class SequenceRangesImpl<K extends Kind, Alphabet extends string> implements Base<K, Alphabet> {
-        public offset: number
         public length: number
         public code: Column<Alphabet>
         public label: Column<string>
         public seqId: Column<number>
         public compId: Column<string>
-        public microHet: ReadonlyMap<number, string[]>
+        public microHet: ReadonlyMap<number, string[]> = new Map()
+
+        private minSeqId: number
+        index(seqId: number) {
+            return seqId - this.minSeqId;
+        }
 
         constructor(public kind: K, private seqIdStart: Column<number>, private seqIdEnd: Column<number>) {
             let maxSeqId = 0, minSeqId = Number.MAX_SAFE_INTEGER;
@@ -220,8 +183,8 @@ namespace Sequence {
             });
             this.compId = Column.ofConst('', count, Column.Schema.str);
 
-            this.offset = minSeqId - 1;
             this.length = count;
+            this.minSeqId = minSeqId;
         }
     }
 }

+ 1 - 2
src/mol-model/structure.ts

@@ -9,5 +9,4 @@ export * from './structure/coordinates';
 export * from './structure/topology';
 export * from './structure/model';
 export * from './structure/structure';
-export * from './structure/query';
-export * from './structure/common/custom-property';
+export * from './structure/query';

+ 1 - 1
src/mol-model/structure/export/mmcif.ts

@@ -15,7 +15,7 @@ import { _chem_comp, _pdbx_chem_comp_identifier, _pdbx_nonpoly_scheme } from './
 import { Model } from '../model';
 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 { CustomPropertyDescriptor } from '../../custom-property';
 import { atom_site_operator_mapping } from './categories/atom_site_operator_mapping';
 import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
 

+ 1 - 1
src/mol-model/structure/model/model.ts

@@ -10,7 +10,7 @@ import StructureSequence from './properties/sequence';
 import { AtomicHierarchy, AtomicConformation, AtomicRanges } from './properties/atomic';
 import { CoarseHierarchy, CoarseConformation } from './properties/coarse';
 import { Entities, ChemicalComponentMap, MissingResidues, StructAsymMap } from './properties/common';
-import { CustomProperties } from '../common/custom-property';
+import { CustomProperties } from '../../custom-property';
 import { SaccharideComponentMap } from '../structure/carbohydrates/constants';
 import { ModelFormat } from '../../../mol-model-formats/format';
 import { calcModelCenter } from './util';

+ 1 - 1
src/mol-model/structure/structure/structure.ts

@@ -25,7 +25,7 @@ import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
 import { idFactory } from '../../../mol-util/id-factory';
 import { GridLookup3D } from '../../../mol-math/geometry';
 import { UUID } from '../../../mol-util';
-import { CustomProperties } from '../common/custom-property';
+import { CustomProperties } from '../../custom-property';
 import { AtomicHierarchy } from '../model/properties/atomic';
 import { StructureSelection } from '../query/selection';
 import { getBoundary } from '../../../mol-math/geometry/boundary';

+ 11 - 3
src/mol-model/structure/structure/unit/bonds/intra-compute.ts

@@ -37,9 +37,11 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
 
     const { x, y, z } = unit.model.atomicConformation;
     const atomCount = unit.elements.length;
-    const { elements: atoms, residueIndex } = unit;
+    const { elements: atoms, residueIndex, chainIndex } = unit;
     const { type_symbol, label_atom_id, label_alt_id } = unit.model.atomicHierarchy.atoms;
-    const { label_comp_id } = unit.model.atomicHierarchy.residues;
+    const { label_comp_id, label_seq_id } = unit.model.atomicHierarchy.residues;
+    const { index } = unit.model.atomicHierarchy;
+    const { byEntityKey } = unit.model.sequence;
     const query3d = unit.lookup3d;
 
     const structConn = StructConn.Provider.get(unit.model);
@@ -101,7 +103,13 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
 
         if (!props.forceCompute && raI !== lastResidue) {
             if (!!component && component.entries.has(compId)) {
-                componentMap = component.entries.get(compId)!.map;
+                const entitySeq = byEntityKey[index.getEntityFromChain(chainIndex[aI])];
+                if (entitySeq && entitySeq.sequence.microHet.has(label_seq_id.value(raI))) {
+                    // compute for sequence positions with micro-heterogeneity
+                    componentMap = void 0;
+                } else {
+                    componentMap = component.entries.get(compId)!.map;
+                }
             } else {
                 componentMap = void 0;
             }

+ 4 - 2
src/mol-model/volume.ts

@@ -1,7 +1,9 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-export * from './volume/data';
+export * from './volume/volume';
+export * from './volume/grid';

+ 0 - 96
src/mol-model/volume/data.ts

@@ -1,96 +0,0 @@
-/**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
-import { Tensor, Mat4, Vec3 } from '../../mol-math/linear-algebra';
-import { equalEps } from '../../mol-math/linear-algebra/3d/common';
-import { ModelFormat } from '../../mol-model-formats/format';
-
-/** The basic unit cell that contains the data. */
-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,
-        max: number,
-        mean: number,
-        sigma: number
-    }>
-    readonly sourceData: ModelFormat,
-}
-
-interface VolumeData extends VolumeDataBase {
-    readonly colorVolume?: VolumeDataBase
-}
-
-namespace VolumeData {
-    export const One: VolumeData = {
-        transform: { kind: 'matrix', matrix: Mat4.identity() },
-        data: Tensor.create(Tensor.Space([1, 1, 1], [0, 1, 2]), Tensor.Data1([0])),
-        dataStats: { min: 0, max: 0, mean: 0, sigma: 0 },
-        sourceData: { kind: '', data: '', name: '' }
-    };
-
-    const _scale = Mat4.zero(), _translate = Mat4.zero();
-    export function getGridToCartesianTransform(volume: VolumeData) {
-        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;
-    }
-}
-
-type VolumeIsoValue = VolumeIsoValue.Absolute | VolumeIsoValue.Relative
-
-namespace VolumeIsoValue {
-    export type Relative = Readonly<{ kind: 'relative', relativeValue: number }>
-    export type Absolute = Readonly<{ kind: 'absolute', absoluteValue: number }>
-
-    export function areSame(a: VolumeIsoValue, b: VolumeIsoValue, stats: VolumeData['dataStats']) {
-        return equalEps(toAbsolute(a, stats).absoluteValue, toAbsolute(b, stats).absoluteValue, stats.sigma / 100);
-    }
-
-    export function absolute(value: number): Absolute { return { kind: 'absolute', absoluteValue: value }; }
-    export function relative(value: number): Relative { return { kind: 'relative', relativeValue: value }; }
-
-    export function calcAbsolute(stats: VolumeData['dataStats'], relativeValue: number): number {
-        return relativeValue * stats.sigma + stats.mean;
-    }
-
-    export function calcRelative(stats: VolumeData['dataStats'], absoluteValue: number): number {
-        return stats.sigma === 0 ? 0 : ((absoluteValue - stats.mean) / stats.sigma);
-    }
-
-    export function toAbsolute(value: VolumeIsoValue, stats: VolumeData['dataStats']): Absolute {
-        return value.kind === 'absolute' ? value : { kind: 'absolute', absoluteValue: VolumeIsoValue.calcAbsolute(stats, value.relativeValue) };
-    }
-
-    export function toRelative(value: VolumeIsoValue, stats: VolumeData['dataStats']): Relative {
-        return value.kind === 'relative' ? value : { kind: 'relative', relativeValue: VolumeIsoValue.calcRelative(stats, value.absoluteValue) };
-    }
-
-    export function toString(value: VolumeIsoValue) {
-        return value.kind === 'relative'
-            ? `${value.relativeValue.toFixed(2)} σ`
-            : `${value.absoluteValue.toPrecision(4)}`;
-    }
-}
-
-export { VolumeData, VolumeIsoValue };

+ 57 - 0
src/mol-model/volume/grid.ts

@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
+import { Tensor, Mat4, Vec3 } from '../../mol-math/linear-algebra';
+
+/** The basic unit cell that contains the grid data. */
+interface Grid {
+    readonly transform: Grid.Transform,
+    readonly cells: Tensor,
+    readonly stats: Readonly<{
+        min: number,
+        max: number,
+        mean: number,
+        sigma: number
+    }>
+}
+
+namespace Grid {
+    export const One: Grid = {
+        transform: { kind: 'matrix', matrix: Mat4.identity() },
+        cells: Tensor.create(Tensor.Space([1, 1, 1], [0, 1, 2]), Tensor.Data1([0])),
+        stats: { min: 0, max: 0, mean: 0, sigma: 0 },
+    };
+
+    export type Transform = { kind: 'spacegroup', cell: SpacegroupCell, fractionalBox: Box3D } | { kind: 'matrix', matrix: Mat4 }
+
+    const _scale = Mat4.zero(), _translate = Mat4.zero();
+    export function getGridToCartesianTransform(grid: Grid) {
+        if (grid.transform.kind === 'matrix') {
+            return Mat4.copy(Mat4(), grid.transform.matrix);
+        }
+
+        if (grid.transform.kind === 'spacegroup') {
+            const { cells: { space } } = grid;
+            const scale = Mat4.fromScaling(_scale, Vec3.div(Vec3.zero(), Box3D.size(Vec3.zero(), grid.transform.fractionalBox), Vec3.ofArray(space.dimensions)));
+            const translate = Mat4.fromTranslation(_translate, grid.transform.fractionalBox.min);
+            return Mat4.mul3(Mat4.zero(), grid.transform.cell.fromFractional, translate, scale);
+        }
+
+        return Mat4.identity();
+    }
+
+    export function areEquivalent(gridA: Grid, gridB: Grid) {
+        return gridA === gridB;
+    }
+
+    export function isEmpty(grid: Grid) {
+        return grid.cells.data.length === 0;
+    }
+}
+
+export { Grid };

+ 91 - 18
src/mol-model/volume/volume.ts

@@ -4,32 +4,105 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { VolumeData, VolumeIsoValue } from './data';
+import { Grid } from './grid';
 import { OrderedSet } from '../../mol-data/int';
 import { Sphere3D } from '../../mol-math/geometry';
 import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
 import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
 import { CubeFormat } from '../../mol-model-formats/volume/cube';
+import { equalEps } from '../../mol-math/linear-algebra/3d/common';
+import { ModelFormat } from '../../mol-model-formats/format';
+import { CustomProperties } from '../custom-property';
+
+export interface Volume {
+    readonly label?: string
+    readonly grid: Grid
+    readonly sourceData: ModelFormat
+
+    // TODO use...
+    customProperties: CustomProperties
+
+    /**
+     * Not to be accessed directly, each custom property descriptor
+     * defines property accessors that use this field to store the data.
+     */
+    _propertyData: { [name: string]: any }
+
+    // TODO add as customProperty?
+    readonly colorVolume?: Volume
+}
 
 export namespace Volume {
     export type CellIndex = { readonly '@type': 'cell-index' } & number
 
-    export function isOrbitals(volume: VolumeData) {
+    export type IsoValue = IsoValue.Absolute | IsoValue.Relative
+
+    export namespace IsoValue {
+        export type Relative = Readonly<{ kind: 'relative', relativeValue: number }>
+        export type Absolute = Readonly<{ kind: 'absolute', absoluteValue: number }>
+
+        export function areSame(a: IsoValue, b: IsoValue, stats: Grid['stats']) {
+            return equalEps(toAbsolute(a, stats).absoluteValue, toAbsolute(b, stats).absoluteValue, stats.sigma / 100);
+        }
+
+        export function absolute(value: number): Absolute { return { kind: 'absolute', absoluteValue: value }; }
+        export function relative(value: number): Relative { return { kind: 'relative', relativeValue: value }; }
+
+        export function calcAbsolute(stats: Grid['stats'], relativeValue: number): number {
+            return relativeValue * stats.sigma + stats.mean;
+        }
+
+        export function calcRelative(stats: Grid['stats'], absoluteValue: number): number {
+            return stats.sigma === 0 ? 0 : ((absoluteValue - stats.mean) / stats.sigma);
+        }
+
+        export function toAbsolute(value: IsoValue, stats: Grid['stats']): Absolute {
+            return value.kind === 'absolute' ? value : { kind: 'absolute', absoluteValue: IsoValue.calcAbsolute(stats, value.relativeValue) };
+        }
+
+        export function toRelative(value: IsoValue, stats: Grid['stats']): Relative {
+            return value.kind === 'relative' ? value : { kind: 'relative', relativeValue: IsoValue.calcRelative(stats, value.absoluteValue) };
+        }
+
+        export function toString(value: IsoValue) {
+            return value.kind === 'relative'
+                ? `${value.relativeValue.toFixed(2)} σ`
+                : `${value.absoluteValue.toPrecision(4)}`;
+        }
+    }
+
+    export const One: Volume = {
+        label: '',
+        grid: Grid.One,
+        sourceData: { kind: '', name: '', data: {} },
+        customProperties: new CustomProperties(),
+        _propertyData: Object.create(null),
+    };
+
+    export function areEquivalent(volA: Volume, volB: Volume) {
+        return Grid.areEquivalent(volA.grid, volB.grid);
+    }
+
+    export function isEmpty(vol: Volume) {
+        return Grid.isEmpty(vol.grid);
+    }
+
+    export function isOrbitals(volume: Volume) {
         if (!CubeFormat.is(volume.sourceData)) return false;
         return volume.sourceData.data.header.orbitals;
     }
 
-    export interface Loci { readonly kind: 'volume-loci', readonly volume: VolumeData }
-    export function Loci(volume: VolumeData): Loci { return { kind: 'volume-loci', volume }; }
+    export interface Loci { readonly kind: 'volume-loci', readonly volume: Volume }
+    export function Loci(volume: Volume): Loci { return { kind: 'volume-loci', volume }; }
     export function isLoci(x: any): x is Loci { return !!x && x.kind === 'volume-loci'; }
     export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume; }
-    export function isLociEmpty(loci: Loci) { return loci.volume.data.data.length === 0; }
+    export function isLociEmpty(loci: Loci) { return Grid.isEmpty(loci.volume.grid); }
 
-    export function getBoundingSphere(volume: VolumeData, boundingSphere?: Sphere3D) {
+    export function getBoundingSphere(volume: Volume, boundingSphere?: Sphere3D) {
         if (!boundingSphere) boundingSphere = Sphere3D();
 
-        const transform = VolumeData.getGridToCartesianTransform(volume);
-        const [x, y, z] = volume.data.space.dimensions;
+        const transform = Grid.getGridToCartesianTransform(volume.grid);
+        const [x, y, z] = volume.grid.cells.space.dimensions;
 
         const cpA = Vec3.create(0, 0, 0); Vec3.transformMat4(cpA, cpA, transform);
         const cpB = Vec3.create(x, y, z); Vec3.transformMat4(cpB, cpB, transform);
@@ -52,31 +125,31 @@ export namespace Volume {
     }
 
     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 interface Loci { readonly kind: 'isosurface-loci', readonly volume: Volume, readonly isoValue: Volume.IsoValue }
+        export function Loci(volume: Volume, isoValue: Volume.IsoValue): Loci { return { kind: 'isosurface-loci', volume, isoValue }; }
         export function isLoci(x: any): x is Loci { return !!x && x.kind === 'isosurface-loci'; }
-        export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume && VolumeIsoValue.areSame(a.isoValue, b.isoValue, a.volume.dataStats); }
-        export function isLociEmpty(loci: Loci) { return loci.volume.data.data.length === 0; }
+        export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume && Volume.IsoValue.areSame(a.isoValue, b.isoValue, a.volume.grid.stats); }
+        export function isLociEmpty(loci: Loci) { return loci.volume.grid.cells.data.length === 0; }
 
-        export function getBoundingSphere(volume: VolumeData, isoValue: VolumeIsoValue, boundingSphere?: Sphere3D) {
+        export function getBoundingSphere(volume: Volume, isoValue: Volume.IsoValue, boundingSphere?: Sphere3D) {
             // TODO get bounding sphere for subgrid with values >= isoValue
             return Volume.getBoundingSphere(volume, boundingSphere);
         }
     }
 
     export namespace Cell {
-        export interface Loci { readonly kind: 'cell-loci', readonly volume: VolumeData, readonly indices: OrderedSet<CellIndex> }
-        export function Loci(volume: VolumeData, indices: OrderedSet<CellIndex>): Loci { return { kind: 'cell-loci', volume, indices }; }
+        export interface Loci { readonly kind: 'cell-loci', readonly volume: Volume, readonly indices: OrderedSet<CellIndex> }
+        export function Loci(volume: Volume, indices: OrderedSet<CellIndex>): Loci { return { kind: 'cell-loci', volume, indices }; }
         export function isLoci(x: any): x is Loci { return !!x && x.kind === 'cell-loci'; }
         export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume && OrderedSet.areEqual(a.indices, b.indices); }
         export function isLociEmpty(loci: Loci) { return OrderedSet.size(loci.indices) === 0; }
 
         const boundaryHelper = new BoundaryHelper('98');
         const tmpBoundaryPos = Vec3();
-        export function getBoundingSphere(volume: VolumeData, indices: OrderedSet<CellIndex>, boundingSphere?: Sphere3D) {
+        export function getBoundingSphere(volume: Volume, indices: OrderedSet<CellIndex>, boundingSphere?: Sphere3D) {
             boundaryHelper.reset();
-            const transform = VolumeData.getGridToCartesianTransform(volume);
-            const { getCoords } = volume.data.space;
+            const transform = Grid.getGridToCartesianTransform(volume.grid);
+            const { getCoords } = volume.grid.cells.space;
 
             for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
                 const o = OrderedSet.getAt(indices, i);

+ 7 - 8
src/mol-plugin-state/formats/volume.ts

@@ -12,10 +12,9 @@ import { StateObjectSelector } from '../../mol-state';
 import { PluginStateObject } from '../objects';
 import { VolumeRepresentation3DHelpers } from '../transforms/representation';
 import { ColorNames } from '../../mol-util/color/names';
-import { VolumeIsoValue } from '../../mol-model/volume';
+import { Volume } from '../../mol-model/volume';
 import { createVolumeRepresentationParams } from '../helpers/volume-representation-params';
 import { objectForEach } from '../../mol-util/object';
-import { Volume } from '../../mol-model/volume/volume';
 
 const Category = 'Volume';
 
@@ -110,13 +109,13 @@ export const CubeProvider = DataFormatProvider({
         if (volumeData && Volume.isOrbitals(volumeData)) {
             const volumePos = surfaces.to(data.volume).apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(plugin, volumeData, {
                 type: 'isosurface',
-                typeParams: { isoValue: VolumeIsoValue.relative(1), alpha: 0.4 },
+                typeParams: { isoValue: Volume.IsoValue.relative(1), alpha: 0.4 },
                 color: 'uniform',
                 colorParams: { value: ColorNames.blue }
             }));
             const volumeNeg = surfaces.to(data.volume).apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(plugin, volumeData, {
                 type: 'isosurface',
-                typeParams: { isoValue: VolumeIsoValue.relative(-1), alpha: 0.4 },
+                typeParams: { isoValue: Volume.IsoValue.relative(-1), alpha: 0.4 },
                 color: 'uniform',
                 colorParams: { value: ColorNames.red }
             }));
@@ -124,7 +123,7 @@ export const CubeProvider = DataFormatProvider({
         } else {
             const volume = surfaces.to(data.volume).apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(plugin, volumeData, {
                 type: 'isosurface',
-                typeParams: { isoValue: VolumeIsoValue.relative(2), alpha: 0.4 },
+                typeParams: { isoValue: Volume.IsoValue.relative(2), alpha: 0.4 },
                 color: 'uniform',
                 colorParams: { value: ColorNames.grey }
             }));
@@ -176,13 +175,13 @@ export const DscifProvider = DataFormatProvider({
         if (volumes.length > 0) {
             visuals[0] = tree
                 .to(volumes[0])
-                .apply(StateTransforms.Representation.VolumeRepresentation3D, VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: VolumeIsoValue.relative(1.5), alpha: 1 }, 'uniform', { value: ColorNames.teal }))
+                .apply(StateTransforms.Representation.VolumeRepresentation3D, VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: Volume.IsoValue.relative(1.5), alpha: 1 }, 'uniform', { value: ColorNames.teal }))
                 .selector;
         }
 
         if (volumes.length > 1) {
-            const posParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: VolumeIsoValue.relative(3), alpha: 0.3 }, 'uniform', { value: ColorNames.green });
-            const negParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: VolumeIsoValue.relative(-3), alpha: 0.3 }, 'uniform', { value: ColorNames.red });
+            const posParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: Volume.IsoValue.relative(3), alpha: 0.3 }, 'uniform', { value: ColorNames.green });
+            const negParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: Volume.IsoValue.relative(-3), alpha: 0.3 }, 'uniform', { value: ColorNames.red });
             visuals[visuals.length] = tree.to(volumes[1]).apply(StateTransforms.Representation.VolumeRepresentation3D, posParams).selector;
             visuals[visuals.length] = tree.to(volumes[1]).apply(StateTransforms.Representation.VolumeRepresentation3D, negParams).selector;
         }

+ 18 - 18
src/mol-plugin-state/helpers/volume-representation-params.ts

@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { VolumeData } from '../../mol-model/volume';
+import { Volume } from '../../mol-model/volume';
 import { PluginContext } from '../../mol-plugin/context';
 import { RepresentationProvider } from '../../mol-repr/representation';
 import { VolumeRepresentationRegistry } from '../../mol-repr/volume/registry';
@@ -30,7 +30,7 @@ export interface VolumeRepresentationBuiltInProps<
 }
 
 export interface VolumeRepresentationProps<
-    R extends RepresentationProvider<VolumeData> = RepresentationProvider<VolumeData>,
+    R extends RepresentationProvider<Volume> = RepresentationProvider<Volume>,
     C extends ColorTheme.Provider = ColorTheme.Provider,
     S extends SizeTheme.Provider = SizeTheme.Provider> {
     type?: R,
@@ -41,43 +41,43 @@ export interface VolumeRepresentationProps<
     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>  {
+export function createVolumeRepresentationParams<R extends VolumeRepresentationRegistry.BuiltIn, C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(ctx: PluginContext, volume?: Volume, props?: VolumeRepresentationBuiltInProps<R, C, S>): StateTransformer.Params<VolumeRepresentation3D>
+export function createVolumeRepresentationParams<R extends RepresentationProvider<Volume>, C extends ColorTheme.Provider, S extends SizeTheme.Provider>(ctx: PluginContext, volume?: Volume, props?: VolumeRepresentationProps<R, C, S>): StateTransformer.Params<VolumeRepresentation3D>
+export function createVolumeRepresentationParams(ctx: PluginContext, volume?: Volume, 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);
+    if (typeof p.type === 'string' || typeof p.color === 'string' || typeof p.size === 'string') return createParamsByName(ctx, volume || Volume.One, props);
+    return createParamsProvider(ctx, volume || Volume.One, props);
 }
 
-export function getVolumeThemeTypes(ctx: PluginContext, volume?: VolumeData) {
+export function getVolumeThemeTypes(ctx: PluginContext, volume?: Volume) {
     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'] {
+export function createVolumeColorThemeParams<T extends ColorTheme.BuiltIn>(ctx: PluginContext, volume: Volume | undefined, typeName: string | undefined, themeName: T, params?: ColorTheme.BuiltInParams<T>): StateTransformer.Params<VolumeRepresentation3D>['colorTheme']
+export function createVolumeColorThemeParams(ctx: PluginContext, volume: Volume | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['colorTheme']
+export function createVolumeColorThemeParams(ctx: PluginContext, volume: Volume | 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 }));
+    const colorDefaultParams = PD.getDefaultValues(color.getParams({ volume: volume || Volume.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'] {
+export function createVolumeSizeThemeParams<T extends SizeTheme.BuiltIn>(ctx: PluginContext, volume: Volume | undefined, typeName: string | undefined, themeName: T, params?: SizeTheme.BuiltInParams<T>): StateTransformer.Params<VolumeRepresentation3D>['sizeTheme']
+export function createVolumeSizeThemeParams(ctx: PluginContext, volume: Volume | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<VolumeRepresentation3D>['sizeTheme']
+export function createVolumeSizeThemeParams(ctx: PluginContext, volume: Volume | 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 }));
+    const sizeDefaultParams = PD.getDefaultValues(size.getParams({ volume: volume || Volume.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> {
+function createParamsByName(ctx: PluginContext, volume: Volume, 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))
@@ -95,7 +95,7 @@ function createParamsByName(ctx: PluginContext, volume: VolumeData, props: Volum
     });
 }
 
-function createParamsProvider(ctx: PluginContext, volume: VolumeData, props: VolumeRepresentationProps = {}): StateTransformer.Params<VolumeRepresentation3D> {
+function createParamsProvider(ctx: PluginContext, volume: Volume, props: VolumeRepresentationProps = {}): StateTransformer.Params<VolumeRepresentation3D> {
     const { themes: themeCtx } = ctx.representation.volume;
     const themeDataCtx = { volume };
 

+ 2 - 2
src/mol-plugin-state/objects.ts

@@ -14,7 +14,7 @@ import { PlyFile } from '../mol-io/reader/ply/schema';
 import { PsfFile } from '../mol-io/reader/psf/parser';
 import { ShapeProvider } from '../mol-model/shape/provider';
 import { Coordinates as _Coordinates, Model as _Model, Structure as _Structure, StructureElement, Topology as _Topology } from '../mol-model/structure';
-import { VolumeData } from '../mol-model/volume';
+import { Volume as _Volume } from '../mol-model/volume';
 import { PluginBehavior } from '../mol-plugin/behavior/behavior';
 import { Representation } from '../mol-repr/representation';
 import { ShapeRepresentation } from '../mol-repr/shape/representation';
@@ -121,7 +121,7 @@ export namespace PluginStateObject {
     }
 
     export namespace Volume {
-        export class Data extends Create<VolumeData>({ name: 'Volume Data', typeClass: 'Object' }) { }
+        export class Data extends Create<_Volume>({ name: 'Volume', typeClass: 'Object' }) { }
         export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>>({ name: 'Volume 3D' }) { }
     }
 

+ 6 - 6
src/mol-plugin-state/transforms/representation.ts

@@ -6,7 +6,7 @@
  */
 
 import { Structure, StructureElement } from '../../mol-model/structure';
-import { VolumeData, VolumeIsoValue } from '../../mol-model/volume';
+import { Volume } from '../../mol-model/volume';
 import { PluginContext } from '../../mol-plugin/context';
 import { VolumeRepresentationRegistry } from '../../mol-repr/volume/registry';
 import { VolumeParams } from '../../mol-repr/volume/representation';
@@ -441,7 +441,7 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
 //
 
 export namespace VolumeRepresentation3DHelpers {
-    export function getDefaultParams(ctx: PluginContext, name: VolumeRepresentationRegistry.BuiltIn, volume: VolumeData, volumeParams?: Partial<PD.Values<VolumeParams>>): StateTransformer.Params<VolumeRepresentation3D> {
+    export function getDefaultParams(ctx: PluginContext, name: VolumeRepresentationRegistry.BuiltIn, volume: Volume, volumeParams?: Partial<PD.Values<VolumeParams>>): StateTransformer.Params<VolumeRepresentation3D> {
         const type = ctx.representation.volume.registry.get(name);
 
         const themeDataCtx = { volume };
@@ -467,7 +467,7 @@ export namespace VolumeRepresentation3DHelpers {
     }
 
     export function getDescription(props: any) {
-        return props.isoValue && VolumeIsoValue.toString(props.isoValue);
+        return props.isoValue && Volume.IsoValue.toString(props.isoValue);
     }
 }
 type VolumeRepresentation3D = typeof VolumeRepresentation3D
@@ -485,16 +485,16 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
                 type: PD.Mapped<any>(
                     registry.default.name,
                     registry.types,
-                    name => PD.Group<any>(registry.get(name).getParams(themeCtx, VolumeData.One))),
+                    name => PD.Group<any>(registry.get(name).getParams(themeCtx, Volume.One))),
                 colorTheme: PD.Mapped<any>(
                     type.defaultColorTheme.name,
                     themeCtx.colorThemeRegistry.types,
-                    name => PD.Group<any>(themeCtx.colorThemeRegistry.get(name).getParams({ volume: VolumeData.One }))
+                    name => PD.Group<any>(themeCtx.colorThemeRegistry.get(name).getParams({ volume: Volume.One }))
                 ),
                 sizeTheme: PD.Mapped<any>(
                     type.defaultSizeTheme.name,
                     themeCtx.sizeThemeRegistry.types,
-                    name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams({ volume: VolumeData.One }))
+                    name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams({ volume: Volume.One }))
                 )
             };
         }

+ 3 - 3
src/mol-plugin-state/transforms/volume.ts

@@ -15,7 +15,7 @@ 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 { volumeFromDx } from '../../mol-model-formats/volume/dx';
-import { VolumeData } from '../../mol-model/volume';
+import {  Volume } from '../../mol-model/volume';
 import { PluginContext } from '../../mol-plugin/context';
 import { StateSelection } from '../../mol-state';
 
@@ -160,8 +160,8 @@ const AssignColorVolume = PluginStateTransform.BuiltIn({
             if (!dependencies || !dependencies[params.ref]) {
                 throw new Error('Dependency not available.');
             }
-            const colorVolume = dependencies[params.ref].data as VolumeData;
-            const volume: VolumeData = {
+            const colorVolume = dependencies[params.ref].data as Volume;
+            const volume: Volume = {
                 ...a.data,
                 colorVolume
             };

+ 7 - 7
src/mol-plugin-ui/custom/volume.tsx

@@ -12,7 +12,7 @@ import { ExpandableControlRow, IconButton } from '../controls/common';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ParameterControls, ParamOnChange } from '../controls/parameters';
 import { Slider } from '../controls/slider';
-import { VolumeIsoValue, VolumeData } from '../../mol-model/volume';
+import { Volume, Grid } from '../../mol-model/volume';
 import { Vec3 } from '../../mol-math/linear-algebra';
 import { ColorNames } from '../../mol-util/color/names';
 import { toPrecision } from '../../mol-util/number';
@@ -34,7 +34,7 @@ class Channel extends PluginUIComponent<{
     channels: { [k: string]: VolumeStreaming.ChannelParams },
     isRelative: boolean,
     params: StateTransformParameters.Props,
-    stats: VolumeData['dataStats'],
+    stats: Grid['stats'],
     changeIso: (name: string, value: number, isRelative: boolean) => void,
     changeParams: (name: string, param: string, value: any) => void,
     bCell: StateObjectCell,
@@ -111,7 +111,7 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
                         ...old.entry.params.channels,
                         [name]: {
                             ...(old.entry.params.channels as any)[name],
-                            isoValue: isRelative ? VolumeIsoValue.relative(value) : VolumeIsoValue.absolute(value)
+                            isoValue: isRelative ? Volume.IsoValue.relative(value) : Volume.IsoValue.absolute(value)
                         }
                     }
                 }
@@ -139,11 +139,11 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
         });
     };
 
-    convert(channel: any, stats: VolumeData['dataStats'], isRelative: boolean) {
+    convert(channel: any, stats: Grid['stats'], isRelative: boolean) {
         return {
             ...channel, isoValue: isRelative
-                ? VolumeIsoValue.toRelative(channel.isoValue, stats)
-                : VolumeIsoValue.toAbsolute(channel.isoValue, stats)
+                ? Volume.IsoValue.toRelative(channel.isoValue, stats)
+                : Volume.IsoValue.toAbsolute(channel.isoValue, stats)
         };
     }
 
@@ -215,7 +215,7 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf
         const params = this.props.params as VolumeStreaming.Params;
         const detailLevel = ((this.props.info.params as VolumeStreaming.ParamDefinition)
             .entry.map(params.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>).params.detailLevel;
-        const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as VolumeIsoValue).kind === 'relative';
+        const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as Volume.IsoValue).kind === 'relative';
 
         const sampling = b.info.header.sampling[0];
 

+ 10 - 9
src/mol-plugin-ui/sequence/polymer.ts

@@ -44,18 +44,18 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
     mark(loci: Loci, action: MarkerAction): boolean {
         let changed = false;
         const { structure } = this.data;
+        const index = (seqId: number) => this.sequence.index(seqId);
         if (StructureElement.Loci.is(loci)) {
             if (!Structure.areRootsEquivalent(loci.structure, structure)) return false;
             loci = StructureElement.Loci.remap(loci, structure);
 
-            const { offset } = this.sequence;
             for (const e of loci.elements) {
                 if (!this.unitMap.has(e.unit.id)) continue;
 
                 if (Unit.isAtomic(e.unit)) {
-                    changed = applyMarkerAtomic(e, action, this.markerArray, offset) || changed;
+                    changed = applyMarkerAtomic(e, action, this.markerArray, index) || changed;
                 } else {
-                    changed = applyMarkerCoarse(e, action, this.markerArray, offset) || changed;
+                    changed = applyMarkerCoarse(e, action, this.markerArray, index) || changed;
                 }
             }
         } else if (Structure.isLoci(loci)) {
@@ -117,27 +117,28 @@ function createResidueQuery(chainGroupId: number, operatorName: string, label_se
     });
 }
 
-function applyMarkerAtomic(e: StructureElement.Loci.Element, action: MarkerAction, markerArray: Uint8Array, offset: number) {
+function applyMarkerAtomic(e: StructureElement.Loci.Element, action: MarkerAction, markerArray: Uint8Array, index: (seqId: number) => number) {
     const { model, elements } = e.unit;
-    const { index } = model.atomicHierarchy.residueAtomSegments;
+    const { index: residueIndex } = model.atomicHierarchy.residueAtomSegments;
     const { label_seq_id } = model.atomicHierarchy.residues;
 
     let changed = false;
-    OrderedSet.forEachSegment(e.indices, i => index[elements[i]], rI => {
+    OrderedSet.forEachSegment(e.indices, i => residueIndex[elements[i]], rI => {
         const seqId = label_seq_id.value(rI);
-        changed = applyMarkerActionAtPosition(markerArray, seqId - 1 - offset, action) || changed;
+        changed = applyMarkerActionAtPosition(markerArray, index(seqId), action) || changed;
     });
     return changed;
 }
 
-function applyMarkerCoarse(e: StructureElement.Loci.Element, action: MarkerAction, markerArray: Uint8Array, offset: number) {
+function applyMarkerCoarse(e: StructureElement.Loci.Element, action: MarkerAction, markerArray: Uint8Array, index: (seqId: number) => number) {
     const { model, elements } = e.unit;
     const begin = Unit.isSpheres(e.unit) ? model.coarseHierarchy.spheres.seq_id_begin : model.coarseHierarchy.gaussians.seq_id_begin;
     const end = Unit.isSpheres(e.unit) ? model.coarseHierarchy.spheres.seq_id_end : model.coarseHierarchy.gaussians.seq_id_end;
 
     let changed = false;
     OrderedSet.forEach(e.indices, i => {
-        for (let s = begin.value(elements[i]) - 1 - offset, e = end.value(elements[i]) - 1 - offset; s <= e; s++) {
+        const eI = elements[i];
+        for (let s = index(begin.value(eI)), e = index(end.value(eI)); s <= e; s++) {
             changed = applyMarkerActionAtPosition(markerArray, s, action) || changed;
         }
     });

+ 2 - 2
src/mol-plugin-ui/structure/volume.tsx

@@ -137,7 +137,7 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
     private item = (ref: VolumeRef) => {
         const selected = this.plugin.managers.volume.hierarchy.selection;
 
-        const label = ref.cell.obj?.data.label || 'Volume';
+        const label = ref.cell.obj?.label || 'Volume';
         const item: ActionMenu.Item = { kind: 'item', label: label || ref.kind, selected: selected === ref, value: ref };
         return item;
     }
@@ -172,7 +172,7 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
     get label() {
         const selected = this.plugin.managers.volume.hierarchy.selection;
         if (!selected) return 'Nothing Selected';
-        return selected?.cell.obj?.data.label || 'Volume';
+        return selected?.cell.obj?.label || 'Volume';
     }
 
     selectCurrent: ActionMenu.OnSelect = (item) => {

+ 18 - 18
src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts

@@ -7,7 +7,7 @@
 
 import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
 import { PluginStateObject } from '../../../../mol-plugin-state/objects';
-import { VolumeIsoValue, VolumeData } from '../../../../mol-model/volume';
+import { Volume, Grid } from '../../../../mol-model/volume';
 import { createIsoValueParam } from '../../../../mol-repr/volume/isosurface';
 import { VolumeServerHeader, VolumeServerInfo } from './model';
 import { Box3D } from '../../../../mol-math/geometry';
@@ -31,13 +31,13 @@ export namespace VolumeStreaming {
     export const RootTag = 'volume-streaming-info';
 
     export interface ChannelParams {
-        isoValue: VolumeIsoValue,
+        isoValue: Volume.IsoValue,
         color: Color,
         wireframe: boolean,
         opacity: number
     }
 
-    function channelParam(label: string, color: Color, defaultValue: VolumeIsoValue, stats: VolumeData['dataStats'], defaults: Partial<ChannelParams> = {}) {
+    function channelParam(label: string, color: Color, defaultValue: Volume.IsoValue, stats: Grid['stats'], defaults: Partial<ChannelParams> = {}) {
         return PD.Group<ChannelParams>({
             isoValue: createIsoValueParam(typeof defaults.isoValue !== 'undefined' ? defaults.isoValue : defaultValue, stats),
             color: PD.Color(typeof defaults.color !== 'undefined' ? defaults.color : color),
@@ -71,7 +71,7 @@ export namespace VolumeStreaming {
         const { entryData, defaultView, structure, channelParams = { } } = options;
 
         // fake the info
-        const info = entryData || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: VolumeIsoValue.relative(0) };
+        const info = entryData || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: Volume.IsoValue.relative(0) };
         const box = (structure && structure.boundary.box) || Box3D.empty();
 
         return {
@@ -93,12 +93,12 @@ export namespace VolumeStreaming {
                 info.header.availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]), { description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 0 (0.52M voxels) to 6 (25.17M voxels).' }),
             channels: info.kind === 'em'
                 ? PD.Group({
-                    'em': channelParam('EM', Color(0x638F8F), info.emDefaultContourLevel || VolumeIsoValue.relative(1), info.header.sampling[0].valuesInfo[0], channelParams['em'])
+                    'em': channelParam('EM', Color(0x638F8F), info.emDefaultContourLevel || Volume.IsoValue.relative(1), info.header.sampling[0].valuesInfo[0], channelParams['em'])
                 }, { isFlat: true })
                 : PD.Group({
-                    '2fo-fc': channelParam('2Fo-Fc', Color(0x3362B2), VolumeIsoValue.relative(1.5), info.header.sampling[0].valuesInfo[0], channelParams['2fo-fc']),
-                    'fo-fc(+ve)': channelParam('Fo-Fc(+ve)', Color(0x33BB33), VolumeIsoValue.relative(3), info.header.sampling[0].valuesInfo[1], channelParams['fo-fc(+ve)']),
-                    'fo-fc(-ve)': channelParam('Fo-Fc(-ve)', Color(0xBB3333), VolumeIsoValue.relative(-3), info.header.sampling[0].valuesInfo[1], channelParams['fo-fc(-ve)']),
+                    '2fo-fc': channelParam('2Fo-Fc', Color(0x3362B2), Volume.IsoValue.relative(1.5), info.header.sampling[0].valuesInfo[0], channelParams['2fo-fc']),
+                    'fo-fc(+ve)': channelParam('Fo-Fc(+ve)', Color(0x33BB33), Volume.IsoValue.relative(3), info.header.sampling[0].valuesInfo[1], channelParams['fo-fc(+ve)']),
+                    'fo-fc(-ve)': channelParam('Fo-Fc(-ve)', Color(0xBB3333), Volume.IsoValue.relative(-3), info.header.sampling[0].valuesInfo[1], channelParams['fo-fc(-ve)']),
                 }, { isFlat: true }),
         };
     }
@@ -110,16 +110,16 @@ export namespace VolumeStreaming {
     export type ParamDefinition = typeof createParams extends (...args: any[]) => (infer T) ? T : never
     export type Params = ParamDefinition extends PD.Params ? PD.Values<ParamDefinition> : {}
 
-    type ChannelsInfo = { [name in ChannelType]?: { isoValue: VolumeIsoValue, color: Color, wireframe: boolean, opacity: number } }
-    type ChannelsData = { [name in 'EM' | '2FO-FC' | 'FO-FC']?: VolumeData }
+    type ChannelsInfo = { [name in ChannelType]?: { isoValue: Volume.IsoValue, color: Color, wireframe: boolean, opacity: number } }
+    type ChannelsData = { [name in 'EM' | '2FO-FC' | 'FO-FC']?: Volume }
 
     export type ChannelType = 'em' | '2fo-fc' | 'fo-fc(+ve)' | 'fo-fc(-ve)'
     export const ChannelTypeOptions: [ChannelType, string][] = [['em', 'em'], ['2fo-fc', '2fo-fc'], ['fo-fc(+ve)', 'fo-fc(+ve)'], ['fo-fc(-ve)', 'fo-fc(-ve)']];
     export interface ChannelInfo {
-        data: VolumeData,
+        data: Volume,
         color: Color,
         wireframe: boolean,
-        isoValue: VolumeIsoValue.Relative,
+        isoValue: Volume.IsoValue.Relative,
         opacity: number
     }
     export type Channels = { [name in ChannelType]?: ChannelInfo }
@@ -329,24 +329,24 @@ export namespace VolumeStreaming {
             const info = params.entry.params.channels as ChannelsInfo;
 
             if (this.info.kind === 'x-ray') {
-                this.channels['2fo-fc'] = this.createChannel(data['2FO-FC'] || VolumeData.One, info['2fo-fc'], this.info.header.sampling[0].valuesInfo[0]);
-                this.channels['fo-fc(+ve)'] = this.createChannel(data['FO-FC'] || VolumeData.One, info['fo-fc(+ve)'], this.info.header.sampling[0].valuesInfo[1]);
-                this.channels['fo-fc(-ve)'] = this.createChannel(data['FO-FC'] || VolumeData.One, info['fo-fc(-ve)'], this.info.header.sampling[0].valuesInfo[1]);
+                this.channels['2fo-fc'] = this.createChannel(data['2FO-FC'] || Volume.One, info['2fo-fc'], this.info.header.sampling[0].valuesInfo[0]);
+                this.channels['fo-fc(+ve)'] = this.createChannel(data['FO-FC'] || Volume.One, info['fo-fc(+ve)'], this.info.header.sampling[0].valuesInfo[1]);
+                this.channels['fo-fc(-ve)'] = this.createChannel(data['FO-FC'] || Volume.One, info['fo-fc(-ve)'], this.info.header.sampling[0].valuesInfo[1]);
             } else {
-                this.channels['em'] = this.createChannel(data['EM'] || VolumeData.One, info['em'], this.info.header.sampling[0].valuesInfo[0]);
+                this.channels['em'] = this.createChannel(data['EM'] || Volume.One, info['em'], this.info.header.sampling[0].valuesInfo[0]);
             }
 
             return true;
         }
 
-        private createChannel(data: VolumeData, info: ChannelsInfo['em'], stats: VolumeData['dataStats']): ChannelInfo {
+        private createChannel(data: Volume, info: ChannelsInfo['em'], stats: Grid['stats']): ChannelInfo {
             const i = info!;
             return {
                 data,
                 color: i.color,
                 wireframe: i.wireframe,
                 opacity: i.opacity,
-                isoValue: i.isoValue.kind === 'relative' ? i.isoValue : VolumeIsoValue.toRelative(i.isoValue, stats)
+                isoValue: i.isoValue.kind === 'relative' ? i.isoValue : Volume.IsoValue.toRelative(i.isoValue, stats)
             };
         }
 

+ 3 - 3
src/mol-plugin/behavior/dynamic/volume-streaming/model.ts

@@ -1,12 +1,12 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { PluginStateObject } from '../../../../mol-plugin-state/objects';
-import { VolumeIsoValue } from '../../../../mol-model/volume';
+import { Volume } from '../../../../mol-model/volume';
 import { Structure } from '../../../../mol-model/structure';
 
 export class VolumeServerInfo extends PluginStateObject.Create<VolumeServerInfo.Data>({ name: 'Volume Streaming', typeClass: 'Object' }) { }
@@ -18,7 +18,7 @@ export namespace VolumeServerInfo {
         // for em, the EMDB access code, for x-ray, the PDB id
         dataId: string,
         header: VolumeServerHeader,
-        emDefaultContourLevel?: VolumeIsoValue,
+        emDefaultContourLevel?: Volume.IsoValue,
     }
     export interface Data {
         serverUrl: string,

+ 4 - 4
src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts

@@ -12,7 +12,7 @@ import { Task } from '../../../../mol-task';
 import { PluginContext } from '../../../../mol-plugin/context';
 import { urlCombine } from '../../../../mol-util/url';
 import { createIsoValueParam } from '../../../../mol-repr/volume/isosurface';
-import { VolumeIsoValue } from '../../../../mol-model/volume';
+import { Volume } from '../../../../mol-model/volume';
 import { StateAction, StateObject, StateTransformer } from '../../../../mol-state';
 import { getStreamingMethod, getIds, getContourLevel, getEmdbIds } from './util';
 import { VolumeStreaming } from './behavior';
@@ -27,7 +27,7 @@ import { Model } from '../../../../mol-model/structure';
 function addEntry(entries: InfoEntryProps[], method: VolumeServerInfo.Kind, dataId: string, emDefaultContourLevel: number) {
     entries.push({
         source: method === 'em'
-            ? { name: 'em', params: { isoValue: VolumeIsoValue.absolute(emDefaultContourLevel || 0) } }
+            ? { name: 'em', params: { isoValue: Volume.IsoValue.absolute(emDefaultContourLevel || 0) } }
             : { name: 'x-ray', params: { } },
         dataId
     });
@@ -157,7 +157,7 @@ const InfoEntryParams = {
     dataId: PD.Text(''),
     source: PD.MappedStatic('x-ray', {
         'em': PD.Group({
-            isoValue: createIsoValueParam(VolumeIsoValue.relative(1))
+            isoValue: createIsoValueParam(Volume.IsoValue.relative(1))
         }),
         'x-ray': PD.Group({ })
     })
@@ -185,7 +185,7 @@ const CreateVolumeStreamingInfo = PluginStateTransform.BuiltIn({
         for (let i = 0, il = params.entries.length; i < il; ++i) {
             const e = params.entries[i];
             const dataId = e.dataId;
-            const emDefaultContourLevel = e.source.name === 'em' ? e.source.params.isoValue : VolumeIsoValue.relative(1);
+            const emDefaultContourLevel = e.source.name === 'em' ? e.source.params.isoValue : Volume.IsoValue.relative(1);
             await taskCtx.update('Getting server header...');
             const header = await plugin.fetch({ url: urlCombine(params.serverUrl, `${e.source.name}/${dataId.toLocaleLowerCase()}`), type: 'json' }).runInContext(taskCtx) as VolumeServerHeader;
             entries.push({

+ 18 - 21
src/mol-repr/volume/direct-volume.ts

@@ -7,7 +7,7 @@
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
 import { Box3D } from '../../mol-math/geometry';
-import { VolumeData } from '../../mol-model/volume';
+import { Grid, Volume } from '../../mol-model/volume';
 import { RuntimeContext } from '../../mol-task';
 import { WebGLContext } from '../../mol-gl/webgl/context';
 import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume';
@@ -20,7 +20,6 @@ import { NullLocation } from '../../mol-model/location';
 import { EmptyLoci } from '../../mol-model/loci';
 import { VisualUpdateState } from '../util';
 import { RepresentationContext, RepresentationParamsGetter } from '../representation';
-import { Volume } from '../../mol-model/volume/volume';
 
 function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
     const bbox = Box3D.empty();
@@ -49,9 +48,8 @@ function getVolumeTexture2dLayout(dim: Vec3, maxTextureSize: number) {
     return { width, height, columns, rows };
 }
 
-function createVolumeTexture2d(volume: VolumeData, maxTextureSize: number) {
-    const { data: tensor, dataStats: stats } = volume;
-    const { space, data } = tensor;
+function createVolumeTexture2d(volume: Volume, maxTextureSize: number) {
+    const { cells: { space, data }, stats } = volume.grid;
     const dim = space.dimensions as Vec3;
     const { get } = space;
     const { width, height, columns, rows } = getVolumeTexture2dLayout(dim, maxTextureSize);
@@ -85,11 +83,11 @@ function createVolumeTexture2d(volume: VolumeData, maxTextureSize: number) {
     return textureImage;
 }
 
-export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, volume: VolumeData, directVolume?: DirectVolume) {
-    const gridDimension = volume.data.space.dimensions as Vec3;
+export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, volume: Volume, directVolume?: DirectVolume) {
+    const gridDimension = volume.grid.cells.space.dimensions as Vec3;
     const textureImage = createVolumeTexture2d(volume, webgl.maxTextureSize);
     // debugTexture(createImageData(textureImage.array, textureImage.width, textureImage.height), 1/3)
-    const transform = VolumeData.getGridToCartesianTransform(volume);
+    const transform = Grid.getGridToCartesianTransform(volume.grid);
     const bbox = getBoundingBox(gridDimension, transform);
     const dim = Vec3.create(gridDimension[0], gridDimension[1], gridDimension[2]);
     dim[0] += 1; // horizontal padding
@@ -103,9 +101,8 @@ export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, v
 
 // 3d volume texture
 
-function createVolumeTexture3d(volume: VolumeData) {
-    const { data: tensor, dataStats: stats } = volume;
-    const { space, data } = tensor;
+function createVolumeTexture3d(volume: Volume) {
+    const { cells: { space, data }, stats } = volume.grid;
     const [ width, height, depth ] = space.dimensions as Vec3;
     const { get } = space;
 
@@ -128,10 +125,10 @@ function createVolumeTexture3d(volume: VolumeData) {
     return textureVolume;
 }
 
-export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, volume: VolumeData, directVolume?: DirectVolume) {
-    const gridDimension = volume.data.space.dimensions as Vec3;
+export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, volume: Volume, directVolume?: DirectVolume) {
+    const gridDimension = volume.grid.cells.space.dimensions as Vec3;
     const textureVolume = createVolumeTexture3d(volume);
-    const transform = VolumeData.getGridToCartesianTransform(volume);
+    const transform = Grid.getGridToCartesianTransform(volume.grid);
     // Mat4.invert(transform, transform)
     const bbox = getBoundingBox(gridDimension, transform);
 
@@ -143,7 +140,7 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, v
 
 //
 
-export async function createDirectVolume(ctx: VisualContext, volume: VolumeData, theme: Theme, props: PD.Values<DirectVolumeParams>, directVolume?: DirectVolume) {
+export async function createDirectVolume(ctx: VisualContext, volume: Volume, theme: Theme, props: PD.Values<DirectVolumeParams>, directVolume?: DirectVolume) {
     const { runtime, webgl } = ctx;
     if (webgl === undefined) throw new Error('DirectVolumeVisual requires `webgl` in props');
 
@@ -152,7 +149,7 @@ export async function createDirectVolume(ctx: VisualContext, volume: VolumeData,
         createDirectVolume2d(runtime, webgl, volume, directVolume);
 }
 
-function getLoci(volume: VolumeData, props: PD.Values<DirectVolumeParams>) {
+function getLoci(volume: Volume, props: PD.Values<DirectVolumeParams>) {
     return Volume.Loci(volume);
 }
 
@@ -163,7 +160,7 @@ export const DirectVolumeParams = {
     ...DirectVolume.Params
 };
 export type DirectVolumeParams = typeof DirectVolumeParams
-export function getDirectVolumeParams(ctx: ThemeRegistryContext, volume: VolumeData) {
+export function getDirectVolumeParams(ctx: ThemeRegistryContext, volume: Volume) {
     return PD.clone(DirectVolumeParams);
 }
 
@@ -171,16 +168,16 @@ export function DirectVolumeVisual(materialId: number): VolumeVisual<DirectVolum
     return VolumeVisual<DirectVolume, DirectVolumeParams>({
         defaultProps: PD.getDefaultValues(DirectVolumeParams),
         createGeometry: createDirectVolume,
-        createLocationIterator: (volume: VolumeData) => LocationIterator(1, 1, () => NullLocation),
+        createLocationIterator: (volume: Volume) => LocationIterator(1, 1, () => NullLocation),
         getLoci: () => EmptyLoci,
         eachLocation: () => false,
-        setUpdateState: (state: VisualUpdateState, volume: VolumeData, newProps: PD.Values<DirectVolumeParams>, currentProps: PD.Values<DirectVolumeParams>) => {
+        setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<DirectVolumeParams>, currentProps: PD.Values<DirectVolumeParams>) => {
         },
         geometryUtils: DirectVolume.Utils
     }, materialId);
 }
 
-export function DirectVolumeRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, DirectVolumeParams>): VolumeRepresentation<DirectVolumeParams> {
+export function DirectVolumeRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, DirectVolumeParams>): VolumeRepresentation<DirectVolumeParams> {
     return VolumeRepresentation('Direct Volume', ctx, getParams, DirectVolumeVisual, getLoci);
 }
 
@@ -193,5 +190,5 @@ export const DirectVolumeRepresentationProvider = VolumeRepresentationProvider({
     defaultValues: PD.getDefaultValues(DirectVolumeParams),
     defaultColorTheme: { name: 'uniform' },
     defaultSizeTheme: { name: 'uniform' },
-    isApplicable: (volume: VolumeData) => volume.data.data.length > 0
+    isApplicable: (volume: Volume) => !Volume.isEmpty(volume)
 });

+ 48 - 49
src/mol-repr/volume/isosurface.ts

@@ -6,7 +6,7 @@
  */
 
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
-import { VolumeData, VolumeIsoValue } from '../../mol-model/volume';
+import { Grid, Volume } from '../../mol-model/volume';
 import { VisualContext } from '../visual';
 import { Theme, ThemeRegistryContext } from '../../mol-theme/theme';
 import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
@@ -18,15 +18,14 @@ import { VisualUpdateState } from '../util';
 import { Lines } from '../../mol-geo/geometry/lines/lines';
 import { RepresentationContext, RepresentationParamsGetter, Representation } from '../representation';
 import { toPrecision } from '../../mol-util/number';
-import { Volume } from '../../mol-model/volume/volume';
 import { PickingId } from '../../mol-geo/geometry/picking';
 import { EmptyLoci, Loci } from '../../mol-model/loci';
 import { Interval, OrderedSet } from '../../mol-data/int';
 import { Tensor } from '../../mol-math/linear-algebra';
 import { fillSerial } from '../../mol-util/array';
 
-const defaultStats: VolumeData['dataStats'] = { min: -1, max: 1, mean: 0, sigma: 0.1  };
-export function createIsoValueParam(defaultValue: VolumeIsoValue, stats?: VolumeData['dataStats']) {
+const defaultStats: Grid['stats'] = { min: -1, max: 1, mean: 0, sigma: 0.1  };
+export function createIsoValueParam(defaultValue: Volume.IsoValue, stats?: Grid['stats']) {
     const sts = stats || defaultStats;
     const { min, max, mean, sigma } = sts;
 
@@ -36,34 +35,34 @@ export function createIsoValueParam(defaultValue: VolumeIsoValue, stats?: Volume
 
     let def = defaultValue;
     if (defaultValue.kind === 'absolute') {
-        if (defaultValue.absoluteValue < min) def = VolumeIsoValue.absolute(min);
-        else if (defaultValue.absoluteValue > max) def = VolumeIsoValue.absolute(max);
+        if (defaultValue.absoluteValue < min) def = Volume.IsoValue.absolute(min);
+        else if (defaultValue.absoluteValue > max) def = Volume.IsoValue.absolute(max);
     } else {
-        if (defaultValue.relativeValue < relMin) def = VolumeIsoValue.relative(relMin);
-        else if (defaultValue.relativeValue > relMax) def = VolumeIsoValue.relative(relMax);
+        if (defaultValue.relativeValue < relMin) def = Volume.IsoValue.relative(relMin);
+        else if (defaultValue.relativeValue > relMax) def = Volume.IsoValue.relative(relMax);
     }
 
     return PD.Conditioned(
         def,
         {
             'absolute': PD.Converted(
-                (v: VolumeIsoValue) => VolumeIsoValue.toAbsolute(v, VolumeData.One.dataStats).absoluteValue,
-                (v: number) => VolumeIsoValue.absolute(v),
+                (v: Volume.IsoValue) => Volume.IsoValue.toAbsolute(v, Grid.One.stats).absoluteValue,
+                (v: number) => Volume.IsoValue.absolute(v),
                 PD.Numeric(mean, { min, max, step: toPrecision(sigma / 100, 2) })
             ),
             'relative': PD.Converted(
-                (v: VolumeIsoValue) => VolumeIsoValue.toRelative(v, VolumeData.One.dataStats).relativeValue,
-                (v: number) => VolumeIsoValue.relative(v),
+                (v: Volume.IsoValue) => Volume.IsoValue.toRelative(v, Grid.One.stats).relativeValue,
+                (v: number) => Volume.IsoValue.relative(v),
                 PD.Numeric(Math.min(1, relMax), { min: relMin, max: relMax, step: toPrecision(Math.round(((max - min) / sigma)) / 100, 2) })
             )
         },
-        (v: VolumeIsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative',
-        (v: VolumeIsoValue, c: 'absolute' | 'relative') => c === 'absolute' ? VolumeIsoValue.toAbsolute(v, sts) : VolumeIsoValue.toRelative(v, sts),
+        (v: Volume.IsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative',
+        (v: Volume.IsoValue, c: 'absolute' | 'relative') => c === 'absolute' ? Volume.IsoValue.toAbsolute(v, sts) : Volume.IsoValue.toRelative(v, sts),
         { isEssential: true }
     );
 }
 
-export const IsoValueParam = createIsoValueParam(VolumeIsoValue.relative(2));
+export const IsoValueParam = createIsoValueParam(Volume.IsoValue.relative(2));
 type IsoValueParam = typeof IsoValueParam
 
 export const VolumeIsosurfaceParams = {
@@ -72,11 +71,11 @@ export const VolumeIsosurfaceParams = {
 export type VolumeIsosurfaceParams = typeof VolumeIsosurfaceParams
 export type VolumeIsosurfaceProps = PD.Values<VolumeIsosurfaceParams>
 
-function getLoci(volume: VolumeData, props: VolumeIsosurfaceProps) {
+function getLoci(volume: Volume, props: VolumeIsosurfaceProps) {
     return Volume.Isosurface.Loci(volume, props.isoValue);
 }
 
-function getIsosurfaceLoci(pickingId: PickingId, volume: VolumeData, props: VolumeIsosurfaceProps, id: number) {
+function getIsosurfaceLoci(pickingId: PickingId, volume: Volume, props: VolumeIsosurfaceProps, id: number) {
     const { objectId, groupId } = pickingId;
     if (id === objectId) {
         return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
@@ -84,17 +83,17 @@ function getIsosurfaceLoci(pickingId: PickingId, volume: VolumeData, props: Volu
     return EmptyLoci;
 }
 
-function eachIsosurface(loci: Loci, volume: VolumeData, props: VolumeIsosurfaceProps, apply: (interval: Interval) => boolean) {
+function eachIsosurface(loci: Loci, volume: Volume, props: VolumeIsosurfaceProps, apply: (interval: Interval) => boolean) {
     let changed = false;
     if (Volume.isLoci(loci)) {
-        if (!VolumeData.areEquivalent(loci.volume, volume)) return false;
-        if (apply(Interval.ofLength(volume.data.data.length))) changed = true;
+        if (!Volume.areEquivalent(loci.volume, volume)) return false;
+        if (apply(Interval.ofLength(volume.grid.cells.data.length))) changed = true;
     } else if (Volume.Isosurface.isLoci(loci)) {
-        if (!VolumeData.areEquivalent(loci.volume, volume)) return false;
-        if (!VolumeIsoValue.areSame(loci.isoValue, props.isoValue, volume.dataStats)) return false;
-        if (apply(Interval.ofLength(volume.data.data.length))) changed = true;
+        if (!Volume.areEquivalent(loci.volume, volume)) return false;
+        if (!Volume.IsoValue.areSame(loci.isoValue, props.isoValue, volume.grid.stats)) return false;
+        if (apply(Interval.ofLength(volume.grid.cells.data.length))) changed = true;
     } else if (Volume.Cell.isLoci(loci)) {
-        if (!VolumeData.areEquivalent(loci.volume, volume)) return false;
+        if (!Volume.areEquivalent(loci.volume, volume)) return false;
         if (Interval.is(loci.indices)) {
             if (apply(loci.indices)) changed = true;
         } else {
@@ -108,18 +107,18 @@ function eachIsosurface(loci: Loci, volume: VolumeData, props: VolumeIsosurfaceP
 
 //
 
-export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: VolumeData, theme: Theme, props: VolumeIsosurfaceProps, mesh?: Mesh) {
+export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: Volume, theme: Theme, props: VolumeIsosurfaceProps, mesh?: Mesh) {
     ctx.runtime.update({ message: 'Marching cubes...' });
 
-    const ids = fillSerial(new Int32Array(volume.data.data.length));
+    const ids = fillSerial(new Int32Array(volume.grid.cells.data.length));
 
     const surface = await computeMarchingCubesMesh({
-        isoLevel: VolumeIsoValue.toAbsolute(props.isoValue, volume.dataStats).absoluteValue,
-        scalarField: volume.data,
-        idField: Tensor.create(volume.data.space, Tensor.Data1(ids))
+        isoLevel: Volume.IsoValue.toAbsolute(props.isoValue, volume.grid.stats).absoluteValue,
+        scalarField: volume.grid.cells,
+        idField: Tensor.create(volume.grid.cells.space, Tensor.Data1(ids))
     }, mesh).runAsChild(ctx.runtime);
 
-    const transform = VolumeData.getGridToCartesianTransform(volume);
+    const transform = Grid.getGridToCartesianTransform(volume.grid);
     ctx.runtime.update({ message: 'Transforming mesh...' });
     Mesh.transform(surface, transform);
     return surface;
@@ -136,11 +135,11 @@ export function IsosurfaceMeshVisual(materialId: number): VolumeVisual<Isosurfac
     return VolumeVisual<Mesh, IsosurfaceMeshParams>({
         defaultProps: PD.getDefaultValues(IsosurfaceMeshParams),
         createGeometry: createVolumeIsosurfaceMesh,
-        createLocationIterator: (volume: VolumeData) => LocationIterator(volume.data.data.length, 1, () => NullLocation),
+        createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, () => NullLocation),
         getLoci: getIsosurfaceLoci,
         eachLocation: eachIsosurface,
-        setUpdateState: (state: VisualUpdateState, volume: VolumeData, newProps: PD.Values<IsosurfaceMeshParams>, currentProps: PD.Values<IsosurfaceMeshParams>) => {
-            if (!VolumeIsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.dataStats)) state.createGeometry = true;
+        setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<IsosurfaceMeshParams>, currentProps: PD.Values<IsosurfaceMeshParams>) => {
+            if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)) state.createGeometry = true;
         },
         geometryUtils: Mesh.Utils
     }, materialId);
@@ -148,18 +147,18 @@ export function IsosurfaceMeshVisual(materialId: number): VolumeVisual<Isosurfac
 
 //
 
-export async function createVolumeIsosurfaceWireframe(ctx: VisualContext, volume: VolumeData, theme: Theme, props: VolumeIsosurfaceProps, lines?: Lines) {
+export async function createVolumeIsosurfaceWireframe(ctx: VisualContext, volume: Volume, theme: Theme, props: VolumeIsosurfaceProps, lines?: Lines) {
     ctx.runtime.update({ message: 'Marching cubes...' });
 
-    const ids = fillSerial(new Int32Array(volume.data.data.length));
+    const ids = fillSerial(new Int32Array(volume.grid.cells.data.length));
 
     const wireframe = await computeMarchingCubesLines({
-        isoLevel: VolumeIsoValue.toAbsolute(props.isoValue, volume.dataStats).absoluteValue,
-        scalarField: volume.data,
-        idField: Tensor.create(volume.data.space, Tensor.Data1(ids))
+        isoLevel: Volume.IsoValue.toAbsolute(props.isoValue, volume.grid.stats).absoluteValue,
+        scalarField: volume.grid.cells,
+        idField: Tensor.create(volume.grid.cells.space, Tensor.Data1(ids))
     }, lines).runAsChild(ctx.runtime);
 
-    const transform = VolumeData.getGridToCartesianTransform(volume);
+    const transform = Grid.getGridToCartesianTransform(volume.grid);
     Lines.transform(wireframe, transform);
 
     return wireframe;
@@ -177,11 +176,11 @@ export function IsosurfaceWireframeVisual(materialId: number): VolumeVisual<Isos
     return VolumeVisual<Lines, IsosurfaceWireframeParams>({
         defaultProps: PD.getDefaultValues(IsosurfaceWireframeParams),
         createGeometry: createVolumeIsosurfaceWireframe,
-        createLocationIterator: (volume: VolumeData) => LocationIterator(volume.data.data.length, 1, () => NullLocation),
+        createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, () => NullLocation),
         getLoci: getIsosurfaceLoci,
         eachLocation: eachIsosurface,
-        setUpdateState: (state: VisualUpdateState, volume: VolumeData, newProps: PD.Values<IsosurfaceWireframeParams>, currentProps: PD.Values<IsosurfaceWireframeParams>) => {
-            if (!VolumeIsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.dataStats)) state.createGeometry = true;
+        setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<IsosurfaceWireframeParams>, currentProps: PD.Values<IsosurfaceWireframeParams>) => {
+            if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)) state.createGeometry = true;
         },
         geometryUtils: Lines.Utils
     }, materialId);
@@ -190,8 +189,8 @@ export function IsosurfaceWireframeVisual(materialId: number): VolumeVisual<Isos
 //
 
 const IsosurfaceVisuals = {
-    'solid': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, IsosurfaceMeshParams>) => VolumeRepresentation('Isosurface mesh', ctx, getParams, IsosurfaceMeshVisual, getLoci),
-    'wireframe': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, IsosurfaceWireframeParams>) => VolumeRepresentation('Isosurface wireframe', ctx, getParams, IsosurfaceWireframeVisual, getLoci),
+    'solid': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, IsosurfaceMeshParams>) => VolumeRepresentation('Isosurface mesh', ctx, getParams, IsosurfaceMeshVisual, getLoci),
+    'wireframe': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, IsosurfaceWireframeParams>) => VolumeRepresentation('Isosurface wireframe', ctx, getParams, IsosurfaceWireframeVisual, getLoci),
 };
 
 export const IsosurfaceParams = {
@@ -200,15 +199,15 @@ export const IsosurfaceParams = {
     visuals: PD.MultiSelect(['solid'], PD.objectToOptions(IsosurfaceVisuals)),
 };
 export type IsosurfaceParams = typeof IsosurfaceParams
-export function getIsosurfaceParams(ctx: ThemeRegistryContext, volume: VolumeData) {
+export function getIsosurfaceParams(ctx: ThemeRegistryContext, volume: Volume) {
     const p = PD.clone(IsosurfaceParams);
-    p.isoValue = createIsoValueParam(VolumeIsoValue.relative(2), volume.dataStats);
+    p.isoValue = createIsoValueParam(Volume.IsoValue.relative(2), volume.grid.stats);
     return p;
 }
 
 export type IsosurfaceRepresentation = VolumeRepresentation<IsosurfaceParams>
-export function IsosurfaceRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, IsosurfaceParams>): IsosurfaceRepresentation {
-    return Representation.createMulti('Isosurface', ctx, getParams, Representation.StateBuilder, IsosurfaceVisuals as unknown as Representation.Def<VolumeData, IsosurfaceParams>);
+export function IsosurfaceRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, IsosurfaceParams>): IsosurfaceRepresentation {
+    return Representation.createMulti('Isosurface', ctx, getParams, Representation.StateBuilder, IsosurfaceVisuals as unknown as Representation.Def<Volume, IsosurfaceParams>);
 }
 
 export const IsosurfaceRepresentationProvider = VolumeRepresentationProvider({
@@ -220,5 +219,5 @@ export const IsosurfaceRepresentationProvider = VolumeRepresentationProvider({
     defaultValues: PD.getDefaultValues(IsosurfaceParams),
     defaultColorTheme: { name: 'uniform' },
     defaultSizeTheme: { name: 'uniform' },
-    isApplicable: (volume: VolumeData) => volume.data.data.length > 0
+    isApplicable: (volume: Volume) => !Volume.isEmpty(volume)
 });

+ 2 - 2
src/mol-repr/volume/registry.ts

@@ -5,12 +5,12 @@
  */
 
 import { RepresentationRegistry, Representation, RepresentationProvider } from '../representation';
-import { VolumeData } from '../../mol-model/volume';
+import { Volume } from '../../mol-model/volume';
 import { IsosurfaceRepresentationProvider } from './isosurface';
 import { objectForEach } from '../../mol-util/object';
 import { SliceRepresentationProvider } from './slice';
 
-export class VolumeRepresentationRegistry extends RepresentationRegistry<VolumeData, Representation.State> {
+export class VolumeRepresentationRegistry extends RepresentationRegistry<Volume, Representation.State> {
     constructor() {
         super();
         objectForEach(VolumeRepresentationRegistry.BuiltIn, (p, k) => {

+ 18 - 18
src/mol-repr/volume/representation.ts

@@ -6,7 +6,7 @@
 
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { Visual, VisualContext } from '../visual';
-import { VolumeData } from '../../mol-model/volume';
+import { Volume } from '../../mol-model/volume';
 import { Geometry, GeometryUtils } from '../../mol-geo/geometry/geometry';
 import { LocationIterator } from '../../mol-geo/util/location-iterator';
 import { Theme } from '../../mol-theme/theme';
@@ -30,9 +30,9 @@ import { Subject } from 'rxjs';
 import { Task } from '../../mol-task';
 import { SizeValues } from '../../mol-gl/renderable/schema';
 
-export interface VolumeVisual<P extends VolumeParams> extends Visual<VolumeData, P> { }
+export interface VolumeVisual<P extends VolumeParams> extends Visual<Volume, P> { }
 
-function createVolumeRenderObject<G extends Geometry>(volume: VolumeData, geometry: G, locationIt: LocationIterator, theme: Theme, props: PD.Values<Geometry.Params<G>>, materialId: number) {
+function createVolumeRenderObject<G extends Geometry>(volume: Volume, geometry: G, locationIt: LocationIterator, theme: Theme, props: PD.Values<Geometry.Params<G>>, materialId: number) {
     const { createValues, createRenderableState } = Geometry.getUtils(geometry);
     const transform = createIdentityTransform();
     const values = createValues(geometry, transform, locationIt, theme, props);
@@ -42,11 +42,11 @@ function createVolumeRenderObject<G extends Geometry>(volume: VolumeData, geomet
 
 interface VolumeVisualBuilder<P extends VolumeParams, G extends Geometry> {
     defaultProps: PD.Values<P>
-    createGeometry(ctx: VisualContext, volume: VolumeData, theme: Theme, props: PD.Values<P>, geometry?: G): Promise<G> | G
-    createLocationIterator(volume: VolumeData): LocationIterator
-    getLoci(pickingId: PickingId, volume: VolumeData, props: PD.Values<P>, id: number): Loci
-    eachLocation(loci: Loci, volume: VolumeData, props: PD.Values<P>, apply: (interval: Interval) => boolean): boolean
-    setUpdateState(state: VisualUpdateState, volume: VolumeData, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme): void
+    createGeometry(ctx: VisualContext, volume: Volume, theme: Theme, props: PD.Values<P>, geometry?: G): Promise<G> | G
+    createLocationIterator(volume: Volume): LocationIterator
+    getLoci(pickingId: PickingId, volume: Volume, props: PD.Values<P>, id: number): Loci
+    eachLocation(loci: Loci, volume: Volume, props: PD.Values<P>, apply: (interval: Interval) => boolean): boolean
+    setUpdateState(state: VisualUpdateState, volume: Volume, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme): void
 }
 
 interface VolumeVisualGeometryBuilder<P extends VolumeParams, G extends Geometry> extends VolumeVisualBuilder<P, G> {
@@ -62,16 +62,16 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
 
     let newProps: PD.Values<P>;
     let newTheme: Theme;
-    let newVolume: VolumeData;
+    let newVolume: Volume;
 
     let currentProps: PD.Values<P> = Object.assign({}, defaultProps);
     let currentTheme: Theme = Theme.createEmpty();
-    let currentVolume: VolumeData;
+    let currentVolume: Volume;
 
     let geometry: G;
     let locationIt: LocationIterator;
 
-    function prepareUpdate(theme: Theme, props: Partial<PD.Values<P>>, volume: VolumeData) {
+    function prepareUpdate(theme: Theme, props: Partial<PD.Values<P>>, volume: Volume) {
         if (!volume && !currentVolume) {
             throw new Error('missing volume');
         }
@@ -84,7 +84,7 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
 
         if (!renderObject) {
             updateState.createNew = true;
-        } else if (!currentVolume || !VolumeData.areEquivalent(newVolume, currentVolume)) {
+        } else if (!currentVolume || !Volume.areEquivalent(newVolume, currentVolume)) {
             updateState.createNew = true;
         }
 
@@ -158,7 +158,7 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
     return {
         get groupCount() { return locationIt ? locationIt.count : 0; },
         get renderObject () { return renderObject; },
-        async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}, volume?: VolumeData) {
+        async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}, volume?: Volume) {
             prepareUpdate(theme, props, volume || currentVolume);
             if (updateState.createGeometry) {
                 const newGeometry = createGeometry(ctx, newVolume, newTheme, newProps, geometry);
@@ -198,9 +198,9 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
     };
 }
 
-export interface VolumeRepresentation<P extends VolumeParams> extends Representation<VolumeData, P> { }
+export interface VolumeRepresentation<P extends VolumeParams> extends Representation<Volume, P> { }
 
-export type VolumeRepresentationProvider<P extends VolumeParams, Id extends string = string> = RepresentationProvider<VolumeData, P, Representation.State, Id>
+export type VolumeRepresentationProvider<P extends VolumeParams, Id extends string = string> = RepresentationProvider<Volume, P, Representation.State, Id>
 export function VolumeRepresentationProvider<P extends VolumeParams, Id extends string>(p: VolumeRepresentationProvider<P, Id>): VolumeRepresentationProvider<P, Id> { return p; }
 
 //
@@ -210,7 +210,7 @@ export const VolumeParams = {
 };
 export type VolumeParams = typeof VolumeParams
 
-export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, P>, visualCtor: (materialId: number) => VolumeVisual<P>, getLoci: (volume: VolumeData, props: PD.Values<P>) => Loci): VolumeRepresentation<P> {
+export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, P>, visualCtor: (materialId: number) => VolumeVisual<P>, getLoci: (volume: Volume, props: PD.Values<P>) => Loci): VolumeRepresentation<P> {
     let version = 0;
     const updated = new Subject<number>();
     const materialId = getNextMaterialId();
@@ -218,12 +218,12 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
     const _state = Representation.createState();
     let visual: VolumeVisual<P> | undefined;
 
-    let _volume: VolumeData;
+    let _volume: Volume;
     let _params: P;
     let _props: PD.Values<P>;
     let _theme = Theme.createEmpty();
 
-    function createOrUpdate(props: Partial<PD.Values<P>> = {}, volume?: VolumeData) {
+    function createOrUpdate(props: Partial<PD.Values<P>> = {}, volume?: Volume) {
         if (volume && volume !== _volume) {
             _params = getParams(ctx, volume);
             _volume = volume;

+ 32 - 33
src/mol-repr/volume/slice.ts

@@ -7,14 +7,13 @@
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { Image } from '../../mol-geo/geometry/image/image';
 import { ThemeRegistryContext, Theme } from '../../mol-theme/theme';
-import { VolumeData, VolumeIsoValue } from '../../mol-model/volume';
+import { Grid, Volume } from '../../mol-model/volume';
 import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider } from './representation';
 import { LocationIterator } from '../../mol-geo/util/location-iterator';
 import { VisualUpdateState } from '../util';
 import { NullLocation } from '../../mol-model/location';
 import { RepresentationContext, RepresentationParamsGetter } from '../representation';
 import { VisualContext } from '../visual';
-import { Volume } from '../../mol-model/volume/volume';
 import { PickingId } from '../../mol-geo/geometry/picking';
 import { EmptyLoci, Loci } from '../../mol-model/loci';
 import { Interval, OrderedSet, SortedArray } from '../../mol-data/int';
@@ -25,12 +24,12 @@ import { createIsoValueParam, IsoValueParam } from './isosurface';
 import { Color } from '../../mol-util/color';
 import { ColorTheme } from '../../mol-theme/color';
 
-export async function createImage(ctx: VisualContext, volume: VolumeData, theme: Theme, props: PD.Values<SliceParams>, image?: Image) {
+export async function createImage(ctx: VisualContext, volume: Volume, theme: Theme, props: PD.Values<SliceParams>, image?: Image) {
     const { dimension: { name: dim }, isoValue } = props;
 
-    const { space, data: data } = volume.data;
-    const { min, max } = volume.dataStats;
-    const isoVal = VolumeIsoValue.toAbsolute(isoValue, volume.dataStats).absoluteValue;
+    const { space, data } = volume.grid.cells;
+    const { min, max } = volume.grid.stats;
+    const isoVal = Volume.IsoValue.toAbsolute(isoValue, volume.grid.stats).absoluteValue;
 
     // TODO more color themes
     const color = theme.color.color(NullLocation, false);
@@ -41,7 +40,7 @@ export async function createImage(ctx: VisualContext, volume: VolumeData, theme:
         x, y, z,
         x0, y0, z0,
         nx, ny, nz
-    } = getSliceInfo(volume, props);
+    } = getSliceInfo(volume.grid, props);
 
     const corners = new Float32Array(
         dim === 'x' ? [x, 0, 0,  x, y, 0,  x, 0, z,  x, y, z] :
@@ -50,7 +49,7 @@ export async function createImage(ctx: VisualContext, volume: VolumeData, theme:
     );
 
     const imageArray = new Float32Array(width * height * 4);
-    const groupArray = getGroupArray(volume, props);
+    const groupArray = getGroupArray(volume.grid, props);
 
     let i = 0;
     for (let iy = y0; iy < ny; ++iy) {
@@ -72,15 +71,15 @@ export async function createImage(ctx: VisualContext, volume: VolumeData, theme:
     const imageTexture = { width, height, array: imageArray, flipY: true };
     const groupTexture = { width, height, array: groupArray, flipY: true };
 
-    const transform = VolumeData.getGridToCartesianTransform(volume);
+    const transform = Grid.getGridToCartesianTransform(volume.grid);
     transformPositionArray(transform, corners, 0, 4);
 
     return Image.create(imageTexture, corners, groupTexture, image);
 }
 
-function getSliceInfo(volume: VolumeData, props: PD.Values<SliceParams>) {
+function getSliceInfo(grid: Grid, props: PD.Values<SliceParams>) {
     const { dimension: { name: dim, params: index } } = props;
-    const { space } = volume.data;
+    const { space } = grid.cells;
 
     let width, height;
     let x, y, z;
@@ -108,9 +107,9 @@ function getSliceInfo(volume: VolumeData, props: PD.Values<SliceParams>) {
     };
 }
 
-function getGroupArray(volume: VolumeData, props: PD.Values<SliceParams>) {
-    const { space } = volume.data;
-    const { width, height, x0, y0, z0, nx, ny, nz } = getSliceInfo(volume, props);
+function getGroupArray(grid: Grid, props: PD.Values<SliceParams>) {
+    const { space } = grid.cells;
+    const { width, height, x0, y0, z0, nx, ny, nz } = getSliceInfo(grid, props);
     const groupArray = new Float32Array(width * height);
 
     let j = 0;
@@ -125,13 +124,13 @@ function getGroupArray(volume: VolumeData, props: PD.Values<SliceParams>) {
     return groupArray;
 }
 
-function getLoci(volume: VolumeData, props: PD.Values<SliceParams>) {
+function getLoci(volume: Volume, props: PD.Values<SliceParams>) {
     // TODO cache somehow?
-    const groupArray = getGroupArray(volume, props);
+    const groupArray = getGroupArray(volume.grid, props);
     return Volume.Cell.Loci(volume, SortedArray.ofUnsortedArray(groupArray));
 }
 
-function getSliceLoci(pickingId: PickingId, volume: VolumeData, props: PD.Values<SliceParams>, id: number) {
+function getSliceLoci(pickingId: PickingId, volume: Volume, props: PD.Values<SliceParams>, id: number) {
     const { objectId, groupId } = pickingId;
     if (id === objectId) {
         return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
@@ -139,24 +138,24 @@ function getSliceLoci(pickingId: PickingId, volume: VolumeData, props: PD.Values
     return EmptyLoci;
 }
 
-function eachSlice(loci: Loci, volume: VolumeData, props: PD.Values<SliceParams>, apply: (interval: Interval) => boolean) {
+function eachSlice(loci: Loci, volume: Volume, props: PD.Values<SliceParams>, apply: (interval: Interval) => boolean) {
     let changed = false;
     if (Volume.isLoci(loci)) {
-        if (!VolumeData.areEquivalent(loci.volume, volume)) return false;
-        if (apply(Interval.ofLength(volume.data.data.length))) changed = true;
+        if (!Volume.areEquivalent(loci.volume, volume)) return false;
+        if (apply(Interval.ofLength(volume.grid.cells.data.length))) changed = true;
     } else if (Volume.Isosurface.isLoci(loci)) {
-        if (!VolumeData.areEquivalent(loci.volume, volume)) return false;
+        if (!Volume.areEquivalent(loci.volume, volume)) return false;
         // TODO find a cheaper way?
-        const { dataStats, data: { data } } = volume;
-        const eps = dataStats.sigma;
-        const v = VolumeIsoValue.toAbsolute(loci.isoValue, dataStats).absoluteValue;
+        const { stats, cells: { data } } = volume.grid;
+        const eps = stats.sigma;
+        const v = Volume.IsoValue.toAbsolute(loci.isoValue, stats).absoluteValue;
         for (let i = 0, il = data.length; i < il; ++i) {
             if (equalEps(v, data[i], eps)) {
                 if (apply(Interval.ofSingleton(i))) changed = true;
             }
         }
     } else if (Volume.Cell.isLoci(loci)) {
-        if (!VolumeData.areEquivalent(loci.volume, volume)) return false;
+        if (!Volume.areEquivalent(loci.volume, volume)) return false;
         if (Interval.is(loci.indices)) {
             if (apply(loci.indices)) changed = true;
         } else {
@@ -181,15 +180,15 @@ export const SliceParams = {
     isoValue: IsoValueParam,
 };
 export type SliceParams = typeof SliceParams
-export function getSliceParams(ctx: ThemeRegistryContext, volume: VolumeData) {
+export function getSliceParams(ctx: ThemeRegistryContext, volume: Volume) {
     const p = PD.clone(SliceParams);
-    const dim = volume.data.space.dimensions;
+    const dim = volume.grid.cells.space.dimensions;
     p.dimension = PD.MappedStatic('x', {
         x: PD.Numeric(0, { min: 0, max: dim[0] - 1, step: 1 }),
         y: PD.Numeric(0, { min: 0, max: dim[1] - 1, step: 1 }),
         z: PD.Numeric(0, { min: 0, max: dim[2] - 1, step: 1 }),
     }, { isEssential: true });
-    p.isoValue = createIsoValueParam(VolumeIsoValue.absolute(volume.dataStats.min), volume.dataStats);
+    p.isoValue = createIsoValueParam(Volume.IsoValue.absolute(volume.grid.stats.min), volume.grid.stats);
     return p;
 }
 
@@ -197,14 +196,14 @@ export function SliceVisual(materialId: number): VolumeVisual<SliceParams> {
     return VolumeVisual<Image, SliceParams>({
         defaultProps: PD.getDefaultValues(SliceParams),
         createGeometry: createImage,
-        createLocationIterator: (volume: VolumeData) => LocationIterator(volume.data.data.length, 1, () => NullLocation),
+        createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, () => NullLocation),
         getLoci: getSliceLoci,
         eachLocation: eachSlice,
-        setUpdateState: (state: VisualUpdateState, volume: VolumeData, newProps: PD.Values<SliceParams>, currentProps: PD.Values<SliceParams>, newTheme: Theme, currentTheme: Theme) => {
+        setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<SliceParams>, currentProps: PD.Values<SliceParams>, newTheme: Theme, currentTheme: Theme) => {
             state.createGeometry = (
                 newProps.dimension.name !== currentProps.dimension.name ||
                 newProps.dimension.params !== currentProps.dimension.params ||
-                !VolumeIsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.dataStats) ||
+                !Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) ||
                 !ColorTheme.areEqual(newTheme.color, currentTheme.color)
             );
         },
@@ -226,7 +225,7 @@ function updateRenderableState(state: RenderableState, props: PD.Values<SlicePar
     state.writeDepth = true;
 }
 
-export function SliceRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<VolumeData, SliceParams>): VolumeRepresentation<SliceParams> {
+export function SliceRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, SliceParams>): VolumeRepresentation<SliceParams> {
     return VolumeRepresentation('Slice', ctx, getParams, SliceVisual, getLoci);
 }
 
@@ -239,5 +238,5 @@ export const SliceRepresentationProvider = VolumeRepresentationProvider({
     defaultValues: PD.getDefaultValues(SliceParams),
     defaultColorTheme: { name: 'uniform' },
     defaultSizeTheme: { name: 'uniform' },
-    isApplicable: (volume: VolumeData) => volume.data.data.length > 0
+    isApplicable: (volume: Volume) => !Volume.isEmpty(volume)
 });

+ 2 - 1
src/mol-script/runtime/query/base.ts

@@ -5,8 +5,9 @@
  */
 
 import Expression from '../../language/expression';
-import { QueryContext, QueryFn, Structure, CustomPropertyDescriptor } from '../../../mol-model/structure';
+import { QueryContext, QueryFn, Structure } from '../../../mol-model/structure';
 import { MSymbol } from '../../language/symbol';
+import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
 
 export class QueryRuntimeTable {
     private map = new Map<string, QuerySymbolRuntime>();

+ 2 - 2
src/mol-state/action.ts

@@ -19,7 +19,7 @@ interface StateAction<A extends StateObject = StateObject, T = any, P extends {}
     readonly id: UUID,
     readonly definition: StateAction.Definition<A, T, P>,
     /** create a fresh copy of the params which can be edited in place */
-    createDefaultParams(a: A | undefined, globalCtx: unknown): P
+    createDefaultParams(a: A, globalCtx: unknown): P
 }
 
 namespace StateAction {
@@ -54,7 +54,7 @@ namespace StateAction {
     export interface Definition<A extends StateObject = StateObject, T = any, P extends {} = {}> extends DefinitionBase<A, T, P> {
         readonly from: StateObject.Ctor[],
         readonly display: { readonly name: string, readonly description?: string },
-        params?(a: A | undefined, globalCtx: unknown): { [K in keyof P]: PD.Any }
+        params?(a: A, globalCtx: unknown): { [K in keyof P]: PD.Any }
     }
 
     export function create<A extends StateObject, T, P extends {} = {}>(definition: Definition<A, T, P>): StateAction<A, T, P> {

+ 1 - 0
src/mol-state/state.ts

@@ -63,6 +63,7 @@ class State {
     get tree(): StateTree { return this._tree; }
     get transforms() { return (this._tree as StateTree).transforms; }
     get current() { return this.behaviors.currentObject.value.ref; }
+    get root() { return this.cells.get((this._tree as StateTree).root.ref)!; }
 
     build() { return new StateBuilder.Root(this.tree, this); }
 

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

@@ -12,7 +12,7 @@ import { capitalize, stripTags } from '../mol-util/string';
 import { Column } from '../mol-data/db';
 import { Vec3 } from '../mol-math/linear-algebra';
 import { radToDeg } from '../mol-math/misc';
-import { VolumeIsoValue } from '../mol-model/volume';
+import { Volume } from '../mol-model/volume';
 
 export type LabelGranularity = 'element' | 'conformation' | 'residue' | 'chain' | 'structure'
 
@@ -51,19 +51,19 @@ export function lociLabel(loci: Loci, options: Partial<LabelOptions> = {}): stri
         case 'isosurface-loci':
             return [
                 `${loci.volume.label || 'Volume'}`,
-                `Isosurface at ${VolumeIsoValue.toString(loci.isoValue)}`
+                `Isosurface at ${Volume.IsoValue.toString(loci.isoValue)}`
             ].join(' | ');
         case 'cell-loci':
             const size = OrderedSet.size(loci.indices);
             const start = OrderedSet.start(loci.indices);
-            const absVal = VolumeIsoValue.absolute(loci.volume.data.data[start]);
-            const relVal = VolumeIsoValue.toRelative(absVal, loci.volume.dataStats);
+            const absVal = Volume.IsoValue.absolute(loci.volume.grid.cells.data[start]);
+            const relVal = Volume.IsoValue.toRelative(absVal, loci.volume.grid.stats);
             const label = [
                 `${loci.volume.label || 'Volume'}`,
                 `${size === 1 ? `Cell #${start}` : `${size} Cells`}`
             ];
             if (size === 1) {
-                label.push(`${VolumeIsoValue.toString(absVal)} (${VolumeIsoValue.toString(relVal)})`);
+                label.push(`${Volume.IsoValue.toString(absVal)} (${Volume.IsoValue.toString(relVal)})`);
             }
             return label.join(' | ');
     }

+ 2 - 2
src/mol-theme/theme.ts

@@ -7,7 +7,7 @@
 import { ColorTheme } from './color';
 import { SizeTheme } from './size';
 import { Structure } from '../mol-model/structure';
-import { VolumeData } from '../mol-model/volume';
+import { Volume } from '../mol-model/volume';
 import { ParamDefinition as PD } from '../mol-util/param-definition';
 import { Shape } from '../mol-model/shape';
 import { CustomProperty } from '../mol-model-props/common/custom-property';
@@ -21,7 +21,7 @@ export interface ThemeRegistryContext {
 export interface ThemeDataContext {
     [k: string]: any
     structure?: Structure
-    volume?: VolumeData
+    volume?: Volume
     shape?: Shape
 }
 

+ 2 - 1
src/perf-tests/mol-script.ts

@@ -1,6 +1,6 @@
 import { MolScriptBuilder } from '../mol-script/language/builder';
 import { compile, QuerySymbolRuntime, DefaultQueryRuntimeTable } from '../mol-script/runtime/query/compiler';
-import { QueryContext, Structure, StructureQuery, CustomPropertyDescriptor } from '../mol-model/structure';
+import { QueryContext, Structure, StructureQuery } from '../mol-model/structure';
 import { readCifFile, getModelsAndStructure } from '../apps/structure-info/model';
 import { CustomPropSymbol } from '../mol-script/language/symbol';
 import Type from '../mol-script/language/type';
@@ -10,6 +10,7 @@ import { transpileMolScript } from '../mol-script/script/mol-script/symbols';
 import { formatMolScript } from '../mol-script/language/expression-formatter';
 import { StructureQualityReport, StructureQualityReportProvider } from '../extensions/pdbe/structure-quality-report/prop';
 import fetch from 'node-fetch';
+import { CustomPropertyDescriptor } from '../mol-model/custom-property';
 
 // import Examples from 'mol-script/script/mol-script/examples'
 // import { parseMolScript } from 'mol-script/script/mol-script/parser'