Browse Source

merge master

David Sehnal 6 years ago
parent
commit
9c27896a2d
38 changed files with 656 additions and 437 deletions
  1. 353 283
      package-lock.json
  2. 12 11
      package.json
  3. 1 1
      src/mol-geo/geometry/direct-volume/direct-volume.ts
  4. 3 3
      src/mol-geo/geometry/lines/lines.ts
  5. 2 2
      src/mol-geo/geometry/mesh/mesh.ts
  6. 2 2
      src/mol-geo/geometry/points/points.ts
  7. 2 2
      src/mol-gl/shader/chunks/apply-marker-color.glsl
  8. 3 1
      src/mol-gl/shader/chunks/common-frag-params.glsl
  9. 2 1
      src/mol-gl/shader/chunks/common-vert-params.glsl
  10. 2 0
      src/mol-gl/shader/chunks/common.glsl
  11. 4 3
      src/mol-gl/shader/direct-volume.frag
  12. 2 1
      src/mol-gl/shader/gaussian-density.frag
  13. 0 2
      src/mol-gl/shader/utils/encode-float-rgb.glsl
  14. 0 3
      src/mol-gl/shader/utils/read-from-texture.glsl
  15. 0 3
      src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl
  16. 0 3
      src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl
  17. 14 14
      src/mol-math/geometry/symmetry-operator.ts
  18. 12 24
      src/mol-model/structure/model/formats/mmcif.ts
  19. 0 2
      src/mol-model/structure/model/model.ts
  20. 6 9
      src/mol-model/structure/structure/carbohydrates/compute.ts
  21. 11 1
      src/mol-model/structure/structure/carbohydrates/constants.ts
  22. 18 2
      src/mol-model/structure/structure/structure.ts
  23. 9 0
      src/mol-model/structure/structure/symmetry.ts
  24. 12 8
      src/mol-model/structure/structure/unit.ts
  25. 6 6
      src/mol-repr/structure/complex-visual.ts
  26. 1 1
      src/mol-repr/structure/representation/cartoon.ts
  27. 1 1
      src/mol-repr/structure/representation/molecular-surface.ts
  28. 16 7
      src/mol-repr/structure/units-representation.ts
  29. 24 23
      src/mol-repr/structure/units-visual.ts
  30. 2 2
      src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts
  31. 3 3
      src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts
  32. 2 2
      src/mol-repr/structure/visual/nucleotide-block-mesh.ts
  33. 3 0
      src/mol-repr/util.ts
  34. 2 2
      src/mol-repr/volume/representation.ts
  35. 2 0
      src/mol-theme/color.ts
  36. 18 7
      src/mol-theme/color/chain-id.ts
  37. 2 2
      src/mol-theme/color/cross-link.ts
  38. 104 0
      src/mol-theme/color/polymer-id.ts

File diff suppressed because it is too large
+ 353 - 283
package-lock.json


+ 12 - 11
package.json

@@ -82,9 +82,9 @@
     "@types/compression": "0.0.36",
     "@types/express": "^4.16.0",
     "@types/jest": "^23.3.9",
-    "@types/node": "^10.12.1",
-    "@types/node-fetch": "^2.1.2",
-    "@types/react": "^16.4.18",
+    "@types/node": "^10.12.9",
+    "@types/node-fetch": "^2.1.4",
+    "@types/react": "^16.7.6",
     "@types/react-dom": "^16.0.9",
     "@types/webgl2": "0.0.4",
     "benchmark": "^2.1.4",
@@ -93,24 +93,25 @@
     "css-loader": "^1.0.1",
     "extra-watch-webpack-plugin": "^1.0.3",
     "file-loader": "^2.0.0",
+    "glslify": "^7.0.0",
     "glslify-import": "^3.1.0",
     "glslify-loader": "^1.0.2",
-    "graphql-code-generator": "^0.13.0",
-    "graphql-codegen-typescript-template": "^0.13.0",
+    "graphql-code-generator": "^0.14.1",
+    "graphql-codegen-typescript-template": "^0.14.1",
     "jest": "^23.6.0",
     "jest-raw-loader": "^1.0.1",
     "mini-css-extract-plugin": "^0.4.4",
-    "node-sass": "^4.9.4",
+    "node-sass": "^4.10.0",
     "raw-loader": "^0.5.1",
     "resolve-url-loader": "^3.0.0",
     "sass-loader": "^7.1.0",
     "style-loader": "^0.23.1",
     "ts-jest": "^23.10.4",
     "tslint": "^5.11.0",
-    "typescript": "^3.1.4",
+    "typescript": "^3.1.6",
     "uglify-js": "^3.4.9",
     "util.promisify": "^1.0.0",
-    "webpack": "^4.23.1",
+    "webpack": "^4.26.0",
     "webpack-cli": "^3.1.2"
   },
   "dependencies": {
@@ -120,9 +121,9 @@
     "graphql": "^14.0.2",
     "graphql-request": "^1.8.2",
     "immutable": "^3.8.2",
-    "node-fetch": "^2.2.0",
-    "react": "^16.6.0",
-    "react-dom": "^16.6.0",
+    "node-fetch": "^2.3.0",
+    "react": "^16.6.3",
+    "react-dom": "^16.6.3",
     "rxjs": "^6.3.3"
   }
 }

+ 1 - 1
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -124,7 +124,7 @@ export namespace DirectVolume {
         }
     }
 
-    export function updateValues(values: DirectVolumeValues, props: PD.Values<Params>) {
+    export function updateValues(values: DirectVolumeValues, directVolume: DirectVolume, props: PD.Values<Params>) {
         const vertices = new Float32Array(values.aPosition.ref.value)
         transformPositionArray(values.uTransform.ref.value, vertices, 0, vertices.length / 3)
         const boundingSphere = calculateBoundingSphere(

+ 3 - 3
src/mol-geo/geometry/lines/lines.ts

@@ -137,14 +137,14 @@ export namespace Lines {
         }
     }
 
-    export function updateValues(values: LinesValues, props: PD.Values<Params>) {
+    export function updateValues(values: LinesValues, lines: Lines, props: PD.Values<Params>) {
         const boundingSphere = Sphere3D.addSphere(
             calculateBoundingSphere(
-                values.aStart.ref.value, Math.floor(values.aStart.ref.value.length / 3),
+                values.aStart.ref.value, lines.lineCount,
                 values.aTransform.ref.value, values.instanceCount.ref.value
             ),
             calculateBoundingSphere(
-                values.aEnd.ref.value, Math.floor(values.aEnd.ref.value.length / 3),
+                values.aEnd.ref.value, lines.lineCount,
                 values.aTransform.ref.value, values.instanceCount.ref.value
             ),
         )

+ 2 - 2
src/mol-geo/geometry/mesh/mesh.ts

@@ -408,9 +408,9 @@ export namespace Mesh {
         }
     }
 
-    export function updateValues(values: MeshValues, props: PD.Values<Params>) {
+    export function updateValues(values: MeshValues, mesh: Mesh, props: PD.Values<Params>) {
         const boundingSphere = calculateBoundingSphere(
-            values.aPosition.ref.value, Math.floor(values.aPosition.ref.value.length / 3),
+            values.aPosition.ref.value, mesh.vertexCount,
             values.aTransform.ref.value, values.instanceCount.ref.value
         )
         if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {

+ 2 - 2
src/mol-geo/geometry/points/points.ts

@@ -92,9 +92,9 @@ export namespace Points {
         }
     }
 
-    export function updateValues(values: PointsValues, props: PD.Values<Params>) {
+    export function updateValues(values: PointsValues, points: Points, props: PD.Values<Params>) {
         const boundingSphere = calculateBoundingSphere(
-            values.aPosition.ref.value, Math.floor(values.aPosition.ref.value.length / 3),
+            values.aPosition.ref.value, points.pointCount,
             values.aTransform.ref.value, values.instanceCount.ref.value
         )
         if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) {

+ 2 - 2
src/mol-gl/shader/chunks/apply-marker-color.glsl

@@ -1,8 +1,8 @@
 // only mark elements with an alpha above the picking threshold
 if (uAlpha >= uPickingAlphaThreshold) {
-    float marker = vMarker * 255.0;
+    float marker = floor(vMarker * 255.0 + 0.5); // rounding required to work on some cards on win
     if (marker > 0.1) {
-        if (mod(marker, 2.0) > 0.1) {
+        if (intMod(marker, 2.0) > 0.1) {
             gl_FragColor.rgb = mix(uHighlightColor, gl_FragColor.rgb, 0.3);
         } else {
             gl_FragColor.rgb = mix(uSelectColor, gl_FragColor.rgb, 0.3);

+ 3 - 1
src/mol-gl/shader/chunks/common-frag-params.glsl

@@ -14,4 +14,6 @@ uniform vec3 uFogColor;
 
 uniform float uAlpha;
 uniform float uPickingAlphaThreshold;
-uniform int uPickable;
+uniform int uPickable;
+
+#pragma glslify: import('./common.glsl')

+ 2 - 1
src/mol-gl/shader/chunks/common-vert-params.glsl

@@ -10,4 +10,5 @@ varying float vMarker;
 
 varying vec3 vViewPosition;
 
-#pragma glslify: readFromTexture = require(../utils/read-from-texture.glsl)
+#pragma glslify: import('./common.glsl')
+#pragma glslify: readFromTexture = require('../utils/read-from-texture.glsl', intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify

+ 2 - 0
src/mol-gl/shader/chunks/common.glsl

@@ -0,0 +1,2 @@
+float intDiv(float a, float b) { return float(int(a) / int(b)); }
+float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }

+ 4 - 3
src/mol-gl/shader/direct-volume.frag

@@ -45,11 +45,12 @@ uniform int uPickable;
     uniform sampler2D tColor;
 #endif
 
-#pragma glslify: readFromTexture = require(./utils/read-from-texture.glsl)
+#pragma glslify: import('./chunks/common.glsl')
+#pragma glslify: readFromTexture = require(./utils/read-from-texture.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify
 #pragma glslify: encodeIdRGB = require(./utils/encode-id-rgb.glsl)
 #pragma glslify: decodeIdRGB = require(./utils/decode-id-rgb.glsl)
-#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl)
-#pragma glslify: texture3dFrom2dLinear = require(./utils/texture3d-from-2d-linear.glsl)
+#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify
+#pragma glslify: texture3dFrom2dLinear = require(./utils/texture3d-from-2d-linear.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify
 
 // uniform vec3 uLightPosition;
 uniform vec3 uLightColor;

+ 2 - 1
src/mol-gl/shader/gaussian-density.frag

@@ -21,8 +21,9 @@ varying float vRadius;
     varying float vGroup;
 #endif
 
+#pragma glslify: import('./chunks/common.glsl')
 #pragma glslify: encodeIdRGB = require(./utils/encode-id-rgb.glsl)
-#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl)
+#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify
 
 uniform vec3 uBboxSize;
 uniform vec3 uBboxMin;

+ 0 - 2
src/mol-gl/shader/utils/encode-float-rgb.glsl

@@ -4,8 +4,6 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-// TODO use myMod and myDiv to fix issues with picking?
-
 vec3 encodeFloatRGB(in float value) {
     value = clamp(value, 0.0, 16777216.0);
     vec3 c = vec3(0.0);

+ 0 - 3
src/mol-gl/shader/utils/read-from-texture.glsl

@@ -4,9 +4,6 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-float intDiv(float a, float b) { return float(int(a) / int(b)); }
-float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
-
 vec4 readFromTexture (const in sampler2D tex, const in float i, const in vec2 dim) {
     float x = intMod(i, dim.x);
     float y = floor(intDiv(i, dim.x));

+ 0 - 3
src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl

@@ -5,9 +5,6 @@
  * @author Michael Krone <michael.krone@uni-tuebingen.de>
  */
 
-float intDiv(float a, float b) { return float(int(a) / int(b)); }
-float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
-
 vec4 texture3dFrom2dLinear(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) {
     float zSlice0 = floor(pos.z * gridDim.z);
     float column0 = intMod(zSlice0 * gridDim.x, texDim.x) / gridDim.x;

+ 0 - 3
src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl

@@ -5,9 +5,6 @@
  * @author Michael Krone <michael.krone@uni-tuebingen.de>
  */
 
-float intDiv(float a, float b) { return float(int(a) / int(b)); }
-float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
-
 vec4 texture3dFrom2dNearest(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) {
     float zSlice = floor(pos.z * gridDim.z + 0.5); // round to nearest z-slice
     float column = intMod(zSlice * gridDim.x, texDim.x) / gridDim.x;

+ 14 - 14
src/mol-math/geometry/symmetry-operator.ts

@@ -58,27 +58,27 @@ namespace SymmetryOperator {
         return create(second.name, matrix, second.hkl);
     }
 
-    export interface CoordinateMapper { (index: number, slot: Vec3): Vec3 }
-    export interface ArrayMapping {
+    export interface CoordinateMapper<T extends number> { (index: T, slot: Vec3): Vec3 }
+    export interface ArrayMapping<T extends number> {
         readonly operator: SymmetryOperator,
-        readonly invariantPosition: CoordinateMapper,
-        readonly position: CoordinateMapper,
-        x(index: number): number,
-        y(index: number): number,
-        z(index: number): number,
-        r(index: number): number
+        readonly invariantPosition: CoordinateMapper<T>,
+        readonly position: CoordinateMapper<T>,
+        x(index: T): number,
+        y(index: T): number,
+        z(index: T): number,
+        r(index: T): number
     }
 
     export interface Coordinates { x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number> }
 
-    export function createMapping(operator: SymmetryOperator, coords: Coordinates, radius: ((index: number) => number) | undefined): ArrayMapping {
+    export function createMapping<T extends number>(operator: SymmetryOperator, coords: Coordinates, radius: ((index: T) => number) | undefined): ArrayMapping<T> {
         const invariantPosition = SymmetryOperator.createCoordinateMapper(SymmetryOperator.Default, coords);
         const position = operator.isIdentity ? invariantPosition : SymmetryOperator.createCoordinateMapper(operator, coords);
         const { x, y, z } = createProjections(operator, coords);
         return { operator, invariantPosition, position, x, y, z, r: radius ? radius : _zeroRadius };
     }
 
-    export function createCoordinateMapper(t: SymmetryOperator, coords: Coordinates): CoordinateMapper {
+    export function createCoordinateMapper<T extends number>(t: SymmetryOperator, coords: Coordinates): CoordinateMapper<T> {
         if (t.isIdentity) return identityPosition(coords);
         return generalPosition(t, coords);
     }
@@ -145,7 +145,7 @@ function projectZ({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: Symm
     }
 }
 
-function identityPosition({ x, y, z }: SymmetryOperator.Coordinates): SymmetryOperator.CoordinateMapper {
+function identityPosition<T extends number>({ x, y, z }: SymmetryOperator.Coordinates): SymmetryOperator.CoordinateMapper<T> {
     return (i, s) => {
         s[0] = x[i];
         s[1] = y[i];
@@ -154,10 +154,10 @@ function identityPosition({ x, y, z }: SymmetryOperator.Coordinates): SymmetryOp
     }
 }
 
-function generalPosition({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: SymmetryOperator.Coordinates) {
+function generalPosition<T extends number>({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: SymmetryOperator.Coordinates) {
     if (isW1(m)) {
         // this should always be the case.
-        return (i: number, r: Vec3): Vec3 => {
+        return (i: T, r: Vec3): Vec3 => {
             const x = xs[i], y = ys[i], z = zs[i];
             r[0] = m[0] * x + m[4] * y + m[8] * z + m[12];
             r[1] = m[1] * x + m[5] * y + m[9] * z + m[13];
@@ -165,7 +165,7 @@ function generalPosition({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs
             return r;
         }
     }
-    return (i: number, r: Vec3): Vec3 => {
+    return (i: T, r: Vec3): Vec3 => {
         r[0] = xs[i];
         r[1] = ys[i];
         r[2] = zs[i];

+ 12 - 24
src/mol-model/structure/model/formats/mmcif.ts

@@ -24,10 +24,10 @@ import { getSequence } from './mmcif/sequence';
 import { sortAtomSite } from './mmcif/sort';
 import { StructConn } from './mmcif/bonds/struct_conn';
 import { ChemicalComponent, ChemicalComponentMap } from '../properties/chemical-component';
-import { ComponentType, getMoleculeType } from '../types';
+import { ComponentType, getMoleculeType, MoleculeType } from '../types';
 
 import mmCIF_Format = Format.mmCIF
-import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap } from 'mol-model/structure/structure/carbohydrates/constants';
+import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from 'mol-model/structure/structure/carbohydrates/constants';
 
 type AtomSite = mmCIF_Database['atom_site']
 
@@ -88,24 +88,6 @@ function getModifiedResidueNameMap(format: mmCIF_Format): Model['properties']['m
     return { parentId, details };
 }
 
-function getAsymIdSerialMap(format: mmCIF_Format): ReadonlyMap<string, number> {
-    const data = format.data.struct_asym;
-    const map = new Map<string, number>();
-    let serial = 0
-
-    const id = data.id
-    const count = data._rowCount
-    for (let i = 0; i < count; ++i) {
-        const _id = id.value(i)
-        if (!map.has(_id)) {
-            map.set(_id, serial)
-            serial += 1
-        }
-    }
-
-    return map;
-}
-
 function getChemicalComponentMap(format: mmCIF_Format): ChemicalComponentMap {
     const map = new Map<string, ChemicalComponent>();
     const { id, type, name, pdbx_synonyms, formula, formula_weight } = format.data.chem_comp
@@ -142,15 +124,22 @@ function getSaccharideComponentMap(format: mmCIF_Format): SaccharideComponentMap
                 }
             }
         }
-        return map
     } else {
-        return SaccharideCompIdMap
+        SaccharideCompIdMap.forEach((v, k) => map.set(k, v))
+        const { id, type  } = format.data.chem_comp
+        for (let i = 0, il = id.rowCount; i < il; ++i) {
+            const _id = id.value(i)
+            const _type = type.value(i)
+            if (!map.has(_id) && getMoleculeType(_type, _id) === MoleculeType.saccharide) {
+                map.set(_id, UnknownSaccharideComponent)
+            }
+        }
     }
+    return map
 }
 
 export interface FormatData {
     modifiedResidues: Model['properties']['modifiedResidues']
-    asymIdSerialMap: Model['properties']['asymIdSerialMap']
     chemicalComponentMap: Model['properties']['chemicalComponentMap']
     saccharideComponentMap: Model['properties']['saccharideComponentMap']
 }
@@ -158,7 +147,6 @@ export interface FormatData {
 function getFormatData(format: mmCIF_Format): FormatData {
     return {
         modifiedResidues: getModifiedResidueNameMap(format),
-        asymIdSerialMap: getAsymIdSerialMap(format),
         chemicalComponentMap: getChemicalComponentMap(format),
         saccharideComponentMap: getSaccharideComponentMap(format)
     }

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

@@ -47,8 +47,6 @@ export interface Model extends Readonly<{
             parentId: ReadonlyMap<string, string>,
             details: ReadonlyMap<string, string>
         }>,
-        /** maps asym id to unique serial number */
-        readonly asymIdSerialMap: ReadonlyMap<string, number>
         /** maps residue name to `ChemicalComponent` data */
         readonly chemicalComponentMap: ChemicalComponentMap
         /** maps residue name to `SaccharideComponent` data */

+ 6 - 9
src/mol-model/structure/structure/carbohydrates/compute.ts

@@ -12,12 +12,11 @@ import { Vec3 } from 'mol-math/linear-algebra';
 import PrincipalAxes from 'mol-math/linear-algebra/matrix/principal-axes';
 import { fillSerial } from 'mol-util/array';
 import { ResidueIndex, Model } from '../../model';
-import { ElementSymbol, MoleculeType } from '../../model/types';
-import { getAtomicMoleculeType, getPositionMatrix } from '../../util';
+import { ElementSymbol } from '../../model/types';
+import { getPositionMatrix } from '../../util';
 import StructureElement from '../element';
 import Structure from '../structure';
 import Unit from '../unit';
-import { UnknownSaccharideComponent, SaccharideComponent } from './constants';
 import { CarbohydrateElement, CarbohydrateLink, Carbohydrates, CarbohydrateTerminalLink, PartialCarbohydrateElement } from './data';
 import { UnitRings, UnitRing } from '../unit/rings';
 import { ElementIndex } from '../../model/indexing';
@@ -118,8 +117,8 @@ function filterFusedRings(unitRings: UnitRings, rings: UnitRings.Index[] | undef
     }
 }
 
-function getSaccharideComp(compId: string, model: Model): SaccharideComponent {
-    return model.properties.saccharideComponentMap.get(compId) || UnknownSaccharideComponent
+function getSaccharideComp(compId: string, model: Model) {
+    return model.properties.saccharideComponentMap.get(compId)
 }
 
 export function computeCarbohydrates(structure: Structure): Carbohydrates {
@@ -167,9 +166,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
                 const { index: residueIndex } = residueIt.move();
 
                 const saccharideComp = getSaccharideComp(label_comp_id.value(residueIndex), model)
-                if (saccharideComp === UnknownSaccharideComponent) {
-                    if (getAtomicMoleculeType(unit.model, residueIndex) !== MoleculeType.saccharide) continue
-                }
+                if (!saccharideComp) continue
 
                 if (!sugarResidueMap) {
                     sugarResidueMap = UnitRings.byFingerprintAndResidue(unit.rings, SugarRingFps);
@@ -402,7 +399,7 @@ function buildLookups (elements: CarbohydrateElement[], links: CarbohydrateLink[
         let k: string
         if (fromCarbohydrate) {
             k = terminalLinksKey(unit, anomericCarbon)
-        } else{
+        } else {
             k = terminalLinksKey(elementUnit, elementUnit.elements[elementIndex])
         }
         const e = terminalLinksMap.get(k)

+ 11 - 1
src/mol-model/structure/structure/carbohydrates/constants.ts

@@ -205,7 +205,10 @@ const CommonSaccharideNames: { [k: string]: string[] } = {
         'MLR', // via GlyFinder, tri-saccharide but homomer
     ],
     Man: ['MAN', 'BMA'],
-    Gal: ['GAL', 'GLA'],
+    Gal: [
+        'GAL', 'GLA',
+        'GXL' // via PubChem
+    ],
     Gul: ['GUP', 'GL0'],
     Alt: ['ALT'],
     All: ['ALL', 'AFD'],
@@ -296,6 +299,10 @@ const CommonSaccharideNames: { [k: string]: string[] } = {
     Psi: [],
 }
 
+const UnknownSaccharideNames = [
+    'NGZ', // via CCD
+]
+
 export const SaccharideCompIdMap = (function () {
     const map = new Map<string, SaccharideComponent>()
     for (let i = 0, il = Monosaccharides.length; i < il; ++i) {
@@ -307,6 +314,9 @@ export const SaccharideCompIdMap = (function () {
             }
         }
     }
+    for (let i = 0, il = UnknownSaccharideNames.length; i < il; ++i) {
+        map.set(UnknownSaccharideNames[i], UnknownSaccharideComponent)
+    }
     return map
 })()
 

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

@@ -8,7 +8,7 @@ import { IntMap, SortedArray, Iterator, Segmentation } from 'mol-data/int'
 import { UniqueArray } from 'mol-data/generic'
 import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
 import { Model, ElementIndex } from '../model'
-import { sort, arraySwap, hash1, sortArray, hashString } from 'mol-data/util';
+import { sort, arraySwap, hash1, sortArray, hashString, hashFnv32a } from 'mol-data/util';
 import StructureElement from './element'
 import Unit from './unit'
 import { StructureLookup3D } from './util/lookup3d';
@@ -44,9 +44,11 @@ class Structure {
         entityIndices?: ReadonlyArray<EntityIndex>,
         uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
         hashCode: number,
+        /** Hash based on all unit.id values in the structure, reflecting the units transformation */
+        transformHash: number,
         elementCount: number,
         polymerResidueCount: number,
-    } = { hashCode: -1, elementCount: 0, polymerResidueCount: 0 };
+    } = { hashCode: -1, transformHash: -1, elementCount: 0, polymerResidueCount: 0 };
 
     subsetBuilder(isSorted: boolean) {
         return new StructureSubsetBuilder(this, isSorted);
@@ -74,6 +76,12 @@ class Structure {
         return this.computeHash();
     }
 
+    get transformHash() {
+        if (this._props.transformHash !== -1) return this._props.transformHash;
+        this._props.transformHash = hashFnv32a(this.units.map(u => u.id))
+        return this._props.transformHash;
+    }
+
     private computeHash() {
         let hash = 23;
         for (let i = 0, _i = this.units.length; i < _i; i++) {
@@ -389,6 +397,7 @@ namespace Structure {
         return s.hashCode;
     }
 
+    /** Hash based on all unit.model conformation values in the structure */
     export function conformationHash(s: Structure) {
         return hashString(s.units.map(u => Unit.conformationId(u)).join('|'))
     }
@@ -409,6 +418,13 @@ namespace Structure {
         return true;
     }
 
+    export function areEquivalent(a: Structure, b: Structure) {
+        return a === b || (
+            a.hashCode === b.hashCode &&
+            StructureSymmetry.areTransformGroupsEquivalent(a.unitSymmetryGroups, b.unitSymmetryGroups)
+        )
+    }
+
     export class ElementLocationIterator implements Iterator<StructureElement> {
         private current = StructureElement.create();
         private unitIndex = 0;

+ 9 - 0
src/mol-model/structure/structure/symmetry.ts

@@ -78,6 +78,15 @@ namespace StructureSymmetry {
 
         return ret;
     }
+
+    /** Checks if transform groups are equal up to their unit's transformations */
+    export function areTransformGroupsEquivalent(a: ReadonlyArray<Unit.SymmetryGroup>, b: ReadonlyArray<Unit.SymmetryGroup>) {
+        if (a.length !== b.length) return false
+        for (let i = 0, il = a.length; i < il; ++i) {
+            if (a[i].hashCode !== b[i].hashCode) return false
+        }
+        return true
+    }
 }
 
 function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3) {

+ 12 - 8
src/mol-model/structure/structure/unit.ts

@@ -15,7 +15,7 @@ import { UnitRings } from './unit/rings';
 import StructureElement from './element'
 import { ChainIndex, ResidueIndex, ElementIndex } from '../model/indexing';
 import { IntMap, SortedArray } from 'mol-data/int';
-import { hash2 } from 'mol-data/util';
+import { hash2, hashFnv32a } from 'mol-data/util';
 import { getAtomicPolymerElements, getCoarsePolymerElements, getAtomicGapElements, getCoarseGapElements } from './util/polymer';
 import { getNucleotideElements } from './util/nucleotide';
 import { GaussianDensityProps, computeUnitGaussianDensityCached } from './unit/gaussian-density';
@@ -48,7 +48,10 @@ namespace Unit {
         readonly units: ReadonlyArray<Unit>
         /** Maps unit.id to index of unit in units array */
         readonly unitIndexMap: IntMap<number>
+        /** Hash based on unit.invariantId which is the same for all units in the group */
         readonly hashCode: number
+        /** Hash based on all unit.id values in the group, reflecting the units transformation*/
+        readonly transformHash: number
     }
 
     function getUnitIndexMap(units: Unit[]) {
@@ -72,7 +75,8 @@ namespace Unit {
                 props.unitIndexMap = getUnitIndexMap(units)
                 return props.unitIndexMap
             },
-            hashCode: hashUnit(units[0])
+            hashCode: hashUnit(units[0]),
+            transformHash: hashFnv32a(units.map(u => u.id))
         }
     }
 
@@ -90,7 +94,7 @@ namespace Unit {
         readonly invariantId: number,
         readonly elements: StructureElement.Set,
         readonly model: Model,
-        readonly conformation: SymmetryOperator.ArrayMapping,
+        readonly conformation: SymmetryOperator.ArrayMapping<ElementIndex>,
 
         getChild(elements: StructureElement.Set): Unit,
         applyOperator(id: number, operator: SymmetryOperator, dontCompose?: boolean /* = false */): Unit,
@@ -124,7 +128,7 @@ namespace Unit {
         readonly invariantId: number;
         readonly elements: StructureElement.Set;
         readonly model: Model;
-        readonly conformation: SymmetryOperator.ArrayMapping;
+        readonly conformation: SymmetryOperator.ArrayMapping<ElementIndex>;
 
         // Reference some commonly accessed things for faster access.
         readonly residueIndex: ArrayLike<ResidueIndex>;
@@ -187,7 +191,7 @@ namespace Unit {
             return computeUnitGaussianDensityCached(this, props, this.props.gaussianDensities, ctx, webgl);
         }
 
-        constructor(id: number, invariantId: number, model: Model, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping, props: AtomicProperties) {
+        constructor(id: number, invariantId: number, model: Model, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: AtomicProperties) {
             this.id = id;
             this.invariantId = invariantId;
             this.model = model;
@@ -229,7 +233,7 @@ namespace Unit {
         readonly invariantId: number;
         readonly elements: StructureElement.Set;
         readonly model: Model;
-        readonly conformation: SymmetryOperator.ArrayMapping;
+        readonly conformation: SymmetryOperator.ArrayMapping<ElementIndex>;
 
         readonly coarseElements: CoarseElements;
         readonly coarseConformation: C;
@@ -276,7 +280,7 @@ namespace Unit {
             return computeUnitGaussianDensityCached(this as Unit.Spheres | Unit.Gaussians, props, this.props.gaussianDensities, ctx, webgl); // TODO get rid of casting
         }
 
-        constructor(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping, props: CoarseProperties) {
+        constructor(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: CoarseProperties) {
             this.kind = kind;
             this.id = id;
             this.invariantId = invariantId;
@@ -305,7 +309,7 @@ namespace Unit {
         };
     }
 
-    function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping, props: CoarseProperties): Unit {
+    function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: CoarseProperties): Unit {
         return new Coarse(id, invariantId, model, kind, elements, conformation, props) as any as Unit /** lets call this an ugly temporary hack */;
     }
 

+ 6 - 6
src/mol-repr/structure/complex-visual.ts

@@ -48,7 +48,7 @@ interface ComplexVisualBuilder<P extends ComplexParams, G extends Geometry> {
 interface ComplexVisualGeometryBuilder<P extends ComplexParams, G extends Geometry> extends ComplexVisualBuilder<P, G> {
     createEmptyGeometry(geometry?: G): G
     createRenderObject(ctx: VisualContext, structure: Structure, geometry: Geometry, locationIt: LocationIterator, theme: Theme, currentProps: PD.Values<P>): Promise<ComplexRenderObject>
-    updateValues(values: RenderableValues, newProps: PD.Values<P>): void
+    updateValues(values: RenderableValues, geometry: Geometry, newProps: PD.Values<P>): void
 }
 
 export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeometryBuilder<P, Geometry>): ComplexVisual<P> {
@@ -85,15 +85,15 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo
         VisualUpdateState.reset(updateState)
         setUpdateState(updateState, newProps, currentProps, theme, currentTheme)
 
+        if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true
+        if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
+
         const newConformationHash = Structure.conformationHash(currentStructure)
         if (newConformationHash !== conformationHash) {
             conformationHash = newConformationHash
             updateState.createGeometry = true
         }
 
-        if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true
-        if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
-
         //
 
         if (updateState.createGeometry) {
@@ -113,7 +113,7 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo
             await createColors(ctx.runtime, locationIt, theme.color, renderObject.values)
         }
 
-        updateValues(renderObject.values, newProps)
+        updateValues(renderObject.values, geometry, newProps)
         updateRenderableState(renderObject.state, newProps)
 
         currentProps = newProps
@@ -129,7 +129,7 @@ export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeo
                 throw new Error('missing structure')
             } else if (structure && (!currentStructure || !renderObject)) {
                 await create(ctx, structure, theme, props)
-            } else if (structure && structure.hashCode !== currentStructure.hashCode) {
+            } else if (structure && !Structure.areEquivalent(structure, currentStructure)) {
                 await create(ctx, structure, theme, props)
             } else {
                 if (structure && Structure.conformationHash(structure) !== Structure.conformationHash(currentStructure)) {

+ 1 - 1
src/mol-repr/structure/representation/cartoon.ts

@@ -31,7 +31,7 @@ export const CartoonParams = {
     ...NucleotideBlockParams,
     ...PolymerDirectionParams,
     sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
-    colorTheme: PD.Mapped('polymer-index', BuiltInColorThemeOptions, getBuiltInColorThemeParams),
+    colorTheme: PD.Mapped('polymer-id', BuiltInColorThemeOptions, getBuiltInColorThemeParams),
     visuals: PD.MultiSelect<CartoonVisualName>(['polymer-trace', 'polymer-gap', 'nucleotide-block'], CartoonVisualOptions),
 }
 PD.getDefaultValues(CartoonParams).colorTheme.name

+ 1 - 1
src/mol-repr/structure/representation/molecular-surface.ts

@@ -27,7 +27,7 @@ export const MolecularSurfaceParams = {
     ...GaussianSurfaceParams,
     ...GaussianWireframeParams,
     ...GaussianDensityVolumeParams,
-    colorTheme: PD.Mapped('polymer-index', BuiltInColorThemeOptions, getBuiltInColorThemeParams),
+    colorTheme: PD.Mapped('polymer-id', BuiltInColorThemeOptions, getBuiltInColorThemeParams),
     visuals: PD.MultiSelect<MolecularSurfaceVisualName>(['gaussian-surface'], MolecularSurfaceVisualOptions),
 }
 PD.getDefaultValues(MolecularSurfaceParams).colorTheme.name

+ 16 - 7
src/mol-repr/structure/units-representation.ts

@@ -50,7 +50,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
             if (!_structure && !structure) {
                 throw new Error('missing structure')
             } else if (structure && !_structure) {
-                // console.log('initial structure')
+                // console.log(label, 'initial structure')
                 // First call with a structure, create visuals for each group.
                 _groups = structure.unitSymmetryGroups;
                 for (let i = 0; i < _groups.length; i++) {
@@ -59,8 +59,8 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
                     await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
                     visuals.set(group.hashCode, { visual, group })
                 }
-            } else if (structure && _structure.hashCode !== structure.hashCode) {
-                // console.log('_structure.hashCode !== structure.hashCode')
+            } else if (structure && !Structure.areEquivalent(structure, _structure)) {
+                // console.log(label, 'structure not equivalent')
                 // Tries to re-use existing visuals for the groups of the new structure.
                 // Creates additional visuals if needed, destroys left-over visuals.
                 _groups = structure.unitSymmetryGroups;
@@ -71,18 +71,25 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
                     const group = _groups[i];
                     const visualGroup = oldVisuals.get(group.hashCode)
                     if (visualGroup) {
+                        // console.log(label, 'found visualGroup to reuse')
+                        // console.log('old', visualGroup.group)
+                        // console.log('new', group)
                         const { visual } = visualGroup
                         await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
                         visuals.set(group.hashCode, { visual, group })
                         oldVisuals.delete(group.hashCode)
                     } else {
+                        // console.log(label, 'not found visualGroup to reuse, creating new')
                         // newGroups.push(group)
                         const visual = visualCtor()
                         await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
                         visuals.set(group.hashCode, { visual, group })
                     }
                 }
-                oldVisuals.forEach(({ visual }) => visual.destroy())
+                oldVisuals.forEach(({ visual }) => {
+                    // console.log(label, 'removed unused visual')
+                    visual.destroy()
+                })
 
                 // TODO review logic
                 // For new groups, re-use left-over visuals
@@ -94,12 +101,14 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
                 //     visuals.set(group.hashCode, { visual, group })
                 // })
                 // unusedVisuals.forEach(visual => visual.destroy())
-            } else if (structure && structure !== _structure && _structure.hashCode === structure.hashCode) {
-                // console.log('_structure.hashCode === structure.hashCode')
+            } else if (structure && structure !== _structure && Structure.areEquivalent(structure, _structure)) {
+                console.log(label, 'structures equivalent but not identical')
                 // Expects that for structures with the same hashCode,
                 // the unitSymmetryGroups are the same as well.
                 // Re-uses existing visuals for the groups of the new structure.
                 _groups = structure.unitSymmetryGroups;
+                // console.log('new', structure.unitSymmetryGroups)
+                // console.log('old', _structure.unitSymmetryGroups)
                 for (let i = 0; i < _groups.length; i++) {
                     const group = _groups[i];
                     const visualGroup = visuals.get(group.hashCode)
@@ -111,7 +120,7 @@ export function UnitsRepresentation<P extends UnitsParams>(label: string, getPar
                     }
                 }
             } else {
-                // console.log('no new structure')
+                // console.log(label, 'no new structure')
                 // No new structure given, just update all visuals with new props.
                 const visualsList: [ UnitsVisual<P>, Unit.SymmetryGroup ][] = [] // TODO avoid allocation
                 visuals.forEach(({ visual, group }) => visualsList.push([ visual, group ]))

+ 24 - 23
src/mol-repr/structure/units-visual.ts

@@ -34,13 +34,6 @@ export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
 
 export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { }
 
-function sameGroupConformation(groupA: Unit.SymmetryGroup, groupB: Unit.SymmetryGroup) {
-    return (
-        groupA.units.length === groupB.units.length &&
-        Unit.conformationId(groupA.units[0]) === Unit.conformationId(groupB.units[0])
-    )
-}
-
 type UnitsRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject
 
 interface UnitsVisualBuilder<P extends UnitsParams, G extends Geometry> {
@@ -55,7 +48,7 @@ interface UnitsVisualBuilder<P extends UnitsParams, G extends Geometry> {
 interface UnitsVisualGeometryBuilder<P extends UnitsParams, G extends Geometry> extends UnitsVisualBuilder<P, G> {
     createEmptyGeometry(geometry?: G): G
     createRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, geometry: Geometry, locationIt: LocationIterator, theme: Theme, currentProps: PD.Values<P>): Promise<UnitsRenderObject>
-    updateValues(values: RenderableValues, newProps: PD.Values<P>): void
+    updateValues(values: RenderableValues, geometry: Geometry, newProps: PD.Values<P>): void
 }
 
 export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryBuilder<P, Geometry>): UnitsVisual<P> {
@@ -88,35 +81,46 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB
         renderObject = await createRenderObject(ctx, group, geometry, locationIt, theme, currentProps)
     }
 
-    async function update(ctx: VisualContext, theme: Theme, props: Partial<PD.Values<P>> = {}) {
+    async function update(ctx: VisualContext, group: Unit.SymmetryGroup, theme: Theme, props: Partial<PD.Values<P>> = {}) {
         if (!renderObject) return
 
         const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
-        const unit = currentGroup.units[0]
+        const unit = group.units[0]
 
         locationIt.reset()
         VisualUpdateState.reset(updateState)
         setUpdateState(updateState, newProps, currentProps, theme, currentTheme)
 
+        if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true
+        if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
+
+        if (group.transformHash !== currentGroup.transformHash) {
+            if (group.units.length !== currentGroup.units.length || updateState.updateColor) {
+                updateState.updateTransform = true
+            } else {
+                updateState.updateMatrix = true
+            }
+        }
+
+        // check if the conformation of unit.model has changed
         const newConformationId = Unit.conformationId(unit)
         if (newConformationId !== currentConformationId) {
             currentConformationId = newConformationId
             updateState.createGeometry = true
         }
 
-        if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
-
-        if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true
-        if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
-
         //
 
         if (updateState.updateTransform) {
-            locationIt = createLocationIterator(currentGroup)
+            locationIt = createLocationIterator(group)
             const { instanceCount, groupCount } = locationIt
-            createUnitsTransform(currentGroup, renderObject.values)
             createMarkers(instanceCount * groupCount, renderObject.values)
             updateState.updateColor = true
+            updateState.updateMatrix = true
+        }
+
+        if (updateState.updateMatrix) {
+            createUnitsTransform(group, renderObject.values)
         }
 
         if (updateState.createGeometry) {
@@ -138,11 +142,12 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB
             await createColors(ctx.runtime, locationIt, theme.color, renderObject.values)
         }
 
-        updateValues(renderObject.values, newProps)
+        updateValues(renderObject.values, geometry, newProps)
         updateRenderableState(renderObject.state, newProps)
 
         currentProps = newProps
         currentTheme = theme
+        currentGroup = group
     }
 
     return {
@@ -161,11 +166,7 @@ export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryB
                 await create(ctx, group, theme, props)
             } else {
                 // console.log('unit-visual update')
-                if (group && !sameGroupConformation(group, currentGroup)) {
-                    // console.log('unit-visual new conformation')
-                    currentGroup = group
-                }
-                await update(ctx, theme, props)
+                await update(ctx, group || currentGroup, theme, props)
             }
         },
         getLoci(pickingId: PickingId) {

+ 2 - 2
src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts

@@ -203,12 +203,12 @@ function markCarbohydrate(loci: Loci, structure: Structure, apply: (interval: In
         for (const e of loci.elements) {
             OrderedSet.forEach(e.indices, v => {
                 const { model, elements } = e.unit
-                const { index, offsets } = model.atomicHierarchy.residueAtomSegments        
+                const { index, offsets } = model.atomicHierarchy.residueAtomSegments
                 const rI = index[elements[v]]
                 const unitIndexMin = OrderedSet.findPredecessorIndex(elements, offsets[rI])
                 const unitIndexMax = OrderedSet.findPredecessorIndex(elements, offsets[rI + 1] - 1)
                 const unitIndexInterval = Interval.ofRange(unitIndexMin, unitIndexMax)
-                if(!OrderedSet.isSubset(e.indices, unitIndexInterval)) return
+                if (!OrderedSet.isSubset(e.indices, unitIndexInterval)) return
                 const eI = getAnomericCarbon(e.unit, rI)
                 if (eI !== undefined) {
                     const idx = getElementIndex(e.unit, eI)

+ 3 - 3
src/mol-repr/structure/visual/carbohydrate-terminal-link-cylinder.ts

@@ -34,9 +34,9 @@ async function createCarbohydrateTerminalLinkCylinderMesh(ctx: VisualContext, st
             const l = terminalLinks[edgeIndex]
             if (l.fromCarbohydrate) {
                 Vec3.copy(posA, elements[l.carbohydrateIndex].geometry.center)
-                l.elementUnit.conformation.position(l.elementIndex, posB)
+                l.elementUnit.conformation.position(l.elementUnit.elements[l.elementIndex], posB)
             } else {
-                l.elementUnit.conformation.position(l.elementIndex, posA)
+                l.elementUnit.conformation.position(l.elementUnit.elements[l.elementIndex], posA)
                 Vec3.copy(posB, elements[l.carbohydrateIndex].geometry.center)
             }
         },
@@ -123,7 +123,7 @@ function getTerminalLinkLoci(pickingId: PickingId, structure: Structure, id: num
                 l.elementUnit, l.elementIndex,
                 carb.unit, carbIndex as StructureElement.UnitIndex
             )
-        ])    
+        ])
     }
     return EmptyLoci
 }

+ 2 - 2
src/mol-repr/structure/visual/nucleotide-block-mesh.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit, Structure } from 'mol-model/structure';
+import { Unit, Structure, ElementIndex } from 'mol-model/structure';
 import { UnitsVisual } from '../representation';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { Segmentation } from 'mol-data/int';
@@ -71,7 +71,7 @@ async function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structu
             if (isNucleic(moleculeType)) {
                 const parentId = modifiedResidues.parentId.get(compId)
                 if (parentId !== undefined) compId = parentId
-                let idx1 = -1, idx2 = -1, idx3 = -1, idx4 = -1, idx5 = -1, idx6 = -1
+                let idx1: ElementIndex | -1 = -1, idx2: ElementIndex | -1 = -1, idx3: ElementIndex | -1 = -1, idx4: ElementIndex | -1 = -1, idx5: ElementIndex | -1 = -1, idx6: ElementIndex | -1 = -1
                 let width = 4.5, height = 4.5, depth = 2.5 * sizeFactor
 
                 if (isPurinBase(compId)) {

+ 3 - 0
src/mol-repr/util.ts

@@ -10,6 +10,7 @@ import { VisualQuality } from 'mol-geo/geometry/geometry';
 
 export interface VisualUpdateState {
     updateTransform: boolean
+    updateMatrix: boolean
     updateColor: boolean
     updateSize: boolean
     createGeometry: boolean
@@ -18,6 +19,7 @@ export namespace VisualUpdateState {
     export function create(): VisualUpdateState {
         return {
             updateTransform: false,
+            updateMatrix: false,
             updateColor: false,
             updateSize: false,
             createGeometry: false
@@ -25,6 +27,7 @@ export namespace VisualUpdateState {
     }
     export function reset(state: VisualUpdateState) {
         state.updateTransform = false
+        state.updateMatrix = false
         state.updateColor = false
         state.updateSize = false
         state.createGeometry = false

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

@@ -36,7 +36,7 @@ interface VolumeVisualBuilder<P extends VolumeParams, G extends Geometry> {
 
 interface VolumeVisualGeometryBuilder<P extends VolumeParams, G extends Geometry> extends VolumeVisualBuilder<P, G> {
     createRenderObject(ctx: VisualContext, geometry: G, locationIt: LocationIterator, theme: Theme, currentProps: PD.Values<P>): Promise<VolumeRenderObject>
-    updateValues(values: RenderableValues, newProps: PD.Values<P>): void
+    updateValues(values: RenderableValues, geometry: G, newProps: PD.Values<P>): void
 }
 
 export function VolumeVisual<P extends VolumeParams>(builder: VolumeVisualGeometryBuilder<P, Geometry>): VolumeVisual<P> {
@@ -69,7 +69,7 @@ export function VolumeVisual<P extends VolumeParams>(builder: VolumeVisualGeomet
             ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(geometry))
         }
 
-        updateValues(renderObject.values, newProps)
+        updateValues(renderObject.values, geometry, newProps)
         updateRenderableState(renderObject.state, newProps)
 
         currentProps = newProps

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

@@ -17,6 +17,7 @@ import { CrossLinkColorThemeProvider } from './color/cross-link';
 import { ElementIndexColorThemeProvider } from './color/element-index';
 import { ElementSymbolColorThemeProvider } from './color/element-symbol';
 import { MoleculeTypeColorThemeProvider } from './color/molecule-type';
+import { PolymerIdColorThemeProvider } from './color/polymer-id';
 import { PolymerIndexColorThemeProvider } from './color/polymer-index';
 import { ResidueNameColorThemeProvider } from './color/residue-name';
 import { SecondaryStructureColorThemeProvider } from './color/secondary-structure';
@@ -96,6 +97,7 @@ export const BuiltInColorThemes = {
     'element-index': ElementIndexColorThemeProvider,
     'element-symbol': ElementSymbolColorThemeProvider,
     'molecule-type': MoleculeTypeColorThemeProvider,
+    'polymer-id': PolymerIdColorThemeProvider,
     'polymer-index': PolymerIndexColorThemeProvider,
     'residue-name': ResidueNameColorThemeProvider,
     'secondary-structure': SecondaryStructureColorThemeProvider,

+ 18 - 7
src/mol-theme/color/chain-id.ts

@@ -12,6 +12,7 @@ import { ColorTheme, LocationColor } from '../color';
 import { ParamDefinition as PD } from 'mol-util/param-definition'
 import { ThemeDataContext } from 'mol-theme/theme';
 import { ColorListOptions, ColorListName } from 'mol-util/color/scale';
+import { Column } from 'mol-data/db';
 
 const DefaultColor = Color(0xCCCCCC)
 const Description = 'Gives every chain a color based on its `asym_id` value.'
@@ -34,6 +35,17 @@ function getAsymId(unit: Unit): StructureElement.Property<string> {
     }
 }
 
+function addAsymIds(map: Map<string, number>, data: Column<string>) {
+    let j = map.size
+    for (let o = 0, ol = data.rowCount; o < ol; ++o) {
+        const k = data.value(o)
+        if (!map.has(k)) {
+            map.set(k, j)
+            j += 1
+        }
+    }
+}
+
 export function ChainIdColorTheme(ctx: ThemeDataContext, props: ChainIdColorThemeProps): ColorTheme<ChainIdColorThemeProps> {
     let color: LocationColor
     const scale = ColorScale.create({ listOrName: props.list, minLabel: 'Start', maxLabel: 'End' })
@@ -42,14 +54,13 @@ export function ChainIdColorTheme(ctx: ThemeDataContext, props: ChainIdColorThem
         const l = StructureElement.create()
         const { models } = ctx.structure
         const asymIdSerialMap = new Map<string, number>()
-        let j = 0
         for (let i = 0, il = models.length; i <il; ++i) {
-            models[i].properties.asymIdSerialMap.forEach((v, k) => {
-                if (!asymIdSerialMap.has(k)) {
-                    asymIdSerialMap.set(k, j)
-                    j += 1
-                }
-            })
+            const m = models[i]
+            addAsymIds(asymIdSerialMap, m.atomicHierarchy.chains.label_asym_id)
+            if (m.coarseHierarchy.isDefined) {
+                addAsymIds(asymIdSerialMap, m.coarseHierarchy.spheres.asym_id)
+                addAsymIds(asymIdSerialMap, m.coarseHierarchy.gaussians.asym_id)
+            }
         }
         scale.setDomain(0, asymIdSerialMap.size - 1)
         const scaleColor = scale.color

+ 2 - 2
src/mol-theme/color/cross-link.ts

@@ -28,8 +28,8 @@ export type CrossLinkColorThemeProps = PD.Values<typeof CrossLinkColorThemeParam
 
 const distVecA = Vec3.zero(), distVecB = Vec3.zero()
 function linkDistance(link: Link.Location) {
-    link.aUnit.conformation.position(link.aIndex, distVecA)
-    link.bUnit.conformation.position(link.bIndex, distVecB)
+    link.aUnit.conformation.position(link.aUnit.elements[link.aIndex], distVecA)
+    link.bUnit.conformation.position(link.bUnit.elements[link.bIndex], distVecB)
     return Vec3.distance(distVecA, distVecB)
 }
 

+ 104 - 0
src/mol-theme/color/polymer-id.ts

@@ -0,0 +1,104 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Unit, StructureProperties, StructureElement, Link } from 'mol-model/structure';
+
+import { ColorScale, Color } from 'mol-util/color';
+import { Location } from 'mol-model/location';
+import { ColorTheme, LocationColor } from '../color';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
+import { ColorListOptions, ColorListName } from 'mol-util/color/scale';
+import { Column } from 'mol-data/db';
+import { Entities } from 'mol-model/structure/model/properties/common';
+
+const DefaultColor = Color(0xCCCCCC)
+const Description = 'Gives every polymer chain a color based on its `asym_id` value.'
+
+export const PolymerIdColorThemeParams = {
+    list: PD.Select<ColorListName>('RdYlBu', ColorListOptions),
+}
+export function getPolymerIdColorThemeParams(ctx: ThemeDataContext) {
+    return PolymerIdColorThemeParams // TODO return copy
+}
+export type PolymerIdColorThemeProps = PD.Values<typeof PolymerIdColorThemeParams>
+
+function getAsymId(unit: Unit): StructureElement.Property<string> {
+    switch (unit.kind) {
+        case Unit.Kind.Atomic:
+            return StructureProperties.chain.label_asym_id
+        case Unit.Kind.Spheres:
+        case Unit.Kind.Gaussians:
+            return StructureProperties.coarse.asym_id
+    }
+}
+
+function addPolymerAsymIds(map: Map<string, number>, asymId: Column<string>, entityId: Column<string>, entities: Entities) {
+    let j = map.size
+    for (let o = 0, ol = asymId.rowCount; o < ol; ++o) {
+        const e = entityId.value(o)
+        const eI = entities.getEntityIndex(e)
+        if (entities.data.type.value(eI) === 'polymer') {
+            const k = asymId.value(o)
+            if (!map.has(k)) {
+                map.set(k, j)
+                j += 1
+            }
+        }
+    }
+}
+
+export function PolymerIdColorTheme(ctx: ThemeDataContext, props: PolymerIdColorThemeProps): ColorTheme<PolymerIdColorThemeProps> {
+    let color: LocationColor
+    const scale = ColorScale.create({ listOrName: props.list, minLabel: 'Start', maxLabel: 'End' })
+
+    if (ctx.structure) {
+        const l = StructureElement.create()
+        const { models } = ctx.structure
+        const polymerAsymIdSerialMap = new Map<string, number>()
+        for (let i = 0, il = models.length; i <il; ++i) {
+            for (let i = 0, il = models.length; i <il; ++i) {
+                const m = models[i]
+                addPolymerAsymIds(polymerAsymIdSerialMap, m.atomicHierarchy.chains.label_asym_id, m.atomicHierarchy.chains.label_entity_id, m.entities)
+                if (m.coarseHierarchy.isDefined) {
+                    addPolymerAsymIds(polymerAsymIdSerialMap, m.coarseHierarchy.spheres.asym_id, m.coarseHierarchy.spheres.entity_id, m.entities)
+                    addPolymerAsymIds(polymerAsymIdSerialMap, m.coarseHierarchy.gaussians.asym_id, m.coarseHierarchy.spheres.entity_id, m.entities)
+                }
+            }
+        }
+        scale.setDomain(0, polymerAsymIdSerialMap.size - 1)
+        const scaleColor = scale.color
+
+        color = (location: Location): Color => {
+            if (StructureElement.isLocation(location)) {
+                const asym_id = getAsymId(location.unit)
+                return scaleColor(polymerAsymIdSerialMap.get(asym_id(location)) || 0)
+            } else if (Link.isLocation(location)) {
+                const asym_id = getAsymId(location.aUnit)
+                l.unit = location.aUnit
+                l.element = location.aUnit.elements[location.aIndex]
+                return scaleColor(polymerAsymIdSerialMap.get(asym_id(l)) || 0)
+            }
+            return DefaultColor
+        }
+    } else {
+        color = () => DefaultColor
+    }
+
+    return {
+        granularity: 'group',
+        color,
+        props,
+        description: Description,
+        legend: scale ? scale.legend : undefined
+    }
+}
+
+export const PolymerIdColorThemeProvider: ColorTheme.Provider<typeof PolymerIdColorThemeParams> = {
+    label: 'Polymer Id',
+    factory: PolymerIdColorTheme,
+    getParams: getPolymerIdColorThemeParams
+}

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