JonStargaryen 4 years ago
parent
commit
75266ad257
100 changed files with 8464 additions and 2199 deletions
  1. 2 2
      .eslintrc.json
  2. 1 0
      .gitignore
  3. 1 1
      .npmignore
  4. 0 1
      .travis.yml
  5. 12 8
      README.md
  6. 24 0
      docs/interesting-pdb-entries.md
  7. 2 2
      docs/model-server/readme.md
  8. 3 3
      docs/volume-server/README.md
  9. 250 300
      package-lock.json
  10. 34 32
      package.json
  11. 2 0
      src/apps/viewer/index.ts
  12. 2 1
      src/cli/chem-comp-bond/create-table.ts
  13. 0 0
      src/cli/cif2bcif/converter.ts
  14. 1 0
      src/cli/cif2bcif/index.ts
  15. 6 5
      src/cli/cifschema/index.ts
  16. 0 0
      src/cli/cifschema/util/cif-dic.ts
  17. 5 5
      src/cli/cifschema/util/generate.ts
  18. 0 0
      src/cli/cifschema/util/helper.ts
  19. 0 0
      src/cli/cifschema/util/schema.ts
  20. 88 0
      src/cli/lipid-params/index.ts
  21. 1 0
      src/cli/state-docs/index.ts
  22. 0 0
      src/cli/state-docs/pd-to-md.ts
  23. 0 0
      src/cli/structure-info/helpers.ts
  24. 10 7
      src/cli/structure-info/model.ts
  25. 1 0
      src/cli/structure-info/volume.ts
  26. 1 1
      src/examples/proteopedia-wrapper/helpers.ts
  27. 7 7
      src/extensions/cellpack/color/generate.ts
  28. 5 5
      src/extensions/cellpack/color/provided.ts
  29. 1 3
      src/extensions/cellpack/model.ts
  30. 2 56
      src/extensions/cellpack/state.ts
  31. 10 0
      src/extensions/dnatco/README.md
  32. 103 0
      src/extensions/dnatco/confal-pyramids/behavior.ts
  33. 186 0
      src/extensions/dnatco/confal-pyramids/color.ts
  34. 172 0
      src/extensions/dnatco/confal-pyramids/property.ts
  35. 186 0
      src/extensions/dnatco/confal-pyramids/representation.ts
  36. 60 0
      src/extensions/dnatco/confal-pyramids/types.ts
  37. 299 0
      src/extensions/dnatco/confal-pyramids/util.ts
  38. 8 0
      src/extensions/dnatco/index.ts
  39. 2 2
      src/extensions/rcsb/assembly-symmetry/behavior.ts
  40. 5767 1303
      src/extensions/rcsb/graphql/types.ts
  41. 13 12
      src/extensions/rcsb/validation-report/behavior.ts
  42. 3 3
      src/extensions/rcsb/validation-report/representation.ts
  43. 1 0
      src/mol-canvas3d/canvas3d.ts
  44. 6 0
      src/mol-data/db/column.ts
  45. 1 1
      src/mol-geo/geometry/lines/lines-builder.ts
  46. 5 5
      src/mol-io/reader/cif/schema/bird.ts
  47. 3 3
      src/mol-io/reader/cif/schema/ccd.ts
  48. 2 2
      src/mol-io/reader/cif/schema/cif-core.ts
  49. 9 2
      src/mol-io/reader/cif/schema/mmcif-extras.ts
  50. 186 186
      src/mol-io/reader/cif/schema/mmcif.ts
  51. 7 7
      src/mol-io/reader/dcd/parser.ts
  52. 416 0
      src/mol-io/reader/xtc/parser.ts
  53. 7 6
      src/mol-math/geometry/boundary.ts
  54. 2 1
      src/mol-model-formats/shape/ply.ts
  55. 4 2
      src/mol-model-formats/structure/basic/atomic.ts
  56. 24 3
      src/mol-model-formats/structure/basic/entities.ts
  57. 3 6
      src/mol-model-formats/structure/basic/parser.ts
  58. 6 2
      src/mol-model-formats/structure/basic/schema.ts
  59. 2 8
      src/mol-model-formats/structure/basic/sort.ts
  60. 1 1
      src/mol-model-formats/structure/cif-core.ts
  61. 25 6
      src/mol-model-formats/structure/common/component.ts
  62. 12 6
      src/mol-model-formats/structure/gro.ts
  63. 0 9
      src/mol-model-formats/structure/mmcif.ts
  64. 2 2
      src/mol-model-formats/structure/property/symmetry.ts
  65. 22 10
      src/mol-model-formats/structure/psf.ts
  66. 34 0
      src/mol-model-formats/structure/xtc.ts
  67. 6 3
      src/mol-model-formats/volume/ccp4.ts
  68. 24 1
      src/mol-model-props/common/custom-model-property.ts
  69. 8 4
      src/mol-model-props/common/custom-property.ts
  70. 24 1
      src/mol-model-props/common/custom-structure-property.ts
  71. 1 1
      src/mol-model-props/computed/accessible-surface-area/shrake-rupley.ts
  72. 2 2
      src/mol-model-props/computed/accessible-surface-area/shrake-rupley/radii.ts
  73. 1 1
      src/mol-model-props/computed/chemistry/util.ts
  74. 4 4
      src/mol-model-props/computed/interactions/charged.ts
  75. 2 2
      src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts
  76. 2 2
      src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts
  77. 9 1
      src/mol-model/sequence/sequence.ts
  78. 28 7
      src/mol-model/structure/coordinates/coordinates.ts
  79. 6 6
      src/mol-model/structure/export/categories/atom_site.ts
  80. 66 16
      src/mol-model/structure/model/model.ts
  81. 12 10
      src/mol-model/structure/model/properties/atomic/hierarchy.ts
  82. 6 2
      src/mol-model/structure/model/properties/common.ts
  83. 10 4
      src/mol-model/structure/model/properties/sequence.ts
  84. 3 3
      src/mol-model/structure/model/properties/symmetry.ts
  85. 6 4
      src/mol-model/structure/model/properties/utils/atomic-derived.ts
  86. 24 4
      src/mol-model/structure/model/types.ts
  87. 9 0
      src/mol-model/structure/model/types/lipids.ts
  88. 12 1
      src/mol-model/structure/model/util.ts
  89. 3 3
      src/mol-model/structure/structure/carbohydrates/compute.ts
  90. 8 11
      src/mol-model/structure/structure/properties.ts
  91. 26 7
      src/mol-model/structure/structure/structure.ts
  92. 15 8
      src/mol-model/structure/structure/unit/bonds/inter-compute.ts
  93. 4 7
      src/mol-model/structure/structure/unit/bonds/intra-compute.ts
  94. 2 3
      src/mol-model/structure/structure/unit/rings.ts
  95. 0 35
      src/mol-model/structure/util.ts
  96. 4 4
      src/mol-plugin-state/builder/structure/hierarchy-preset.ts
  97. 56 13
      src/mol-plugin-state/builder/structure/representation-preset.ts
  98. 7 2
      src/mol-plugin-state/builder/structure/representation.ts
  99. 9 0
      src/mol-plugin-state/formats/shape.ts
  100. 15 0
      src/mol-plugin-state/formats/structure.ts

+ 2 - 2
.eslintrc.json

@@ -5,14 +5,14 @@
     },
     "parser": "@typescript-eslint/parser",
     "parserOptions": {
-        "project": ["tsconfig.json", "tsconfig.servers.json"],
+        "project": ["tsconfig.json", "tsconfig.commonjs.json"],
         "sourceType": "module"
     },
     "plugins": [
         "@typescript-eslint"
     ],
     "rules": {
-        "@typescript-eslint/ban-types": "warn",
+        "@typescript-eslint/ban-types": "off",
         "@typescript-eslint/class-name-casing": "off",
         "indent": "off",
         "@typescript-eslint/indent": [

+ 1 - 0
.gitignore

@@ -5,6 +5,7 @@ node_modules/
 debug.log
 npm-debug.log
 tsconfig.tsbuildinfo
+tsconfig.commonjs.tsbuildinfo
 
 *.sublime-workspace
 .idea

+ 1 - 1
.npmignore

@@ -1 +1 @@
-tsconfig.servers.buildinfo
+tsconfig.commonjs.buildinfo

+ 0 - 1
.travis.yml

@@ -14,6 +14,5 @@ before_install:
 node_js:
   - "12"
   - "10"
-  - "8"
 before_script:
   - export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start

+ 12 - 8
README.md

@@ -41,7 +41,7 @@ Moreover, the project contains the implementation of `servers`, including
 - `servers/volume` A tool for accessing volumetric experimental data related to molecular structures.
 - `servers/plugin-state` A basic server to store Mol* Plugin states.
 
-The project also contains performance tests (`perf-tests`), `examples`, and basic proof of concept `apps` (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
+The project also contains performance tests (`perf-tests`), `examples`, and basic proof of concept `cli` apps (CIF to BinaryCIF converter and JSON domain annotation to CIF converter).
 
 ## Previous Work
 This project builds on experience from previous solutions:
@@ -90,19 +90,23 @@ and navigate to `build/viewer`
 ### Code generation
 **CIF schemas**
 
-    node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF
-    node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD
-    node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD
-    node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/cif-core.ts -p CifCore -aa
+    node ./lib/commonjs/cli/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF
+    node ./lib/commonjs/cli/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD
+    node ./lib/commonjs/cli/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD
+    node ./lib/commonjs/cli/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/cif-core.ts -p CifCore -aa
+
+**Lipid names**
+
+    node lib/commonjs/cli/lipid-params -o src/mol-model/structure/model/types/lipids.ts
 
 **GraphQL schemas**
 
-    ./node_modules/.bin/graphql-codegen -c ./src/extensions/rcsb/graphql/codegen.yml
+    node node_modules//@graphql-codegen/cli/bin -c src/extensions/rcsb/graphql/codegen.yml
 
 ### Other scripts
 **Create chem comp bond table**
 
-    node --max-old-space-size=4096 lib/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
+    node --max-old-space-size=4096 lib/commonjs/cli/chem-comp-bond/create-table.js build/data/ccb.bcif -b
 
 **Test model server**
 
@@ -120,7 +124,7 @@ To see all available commands, use ``node lib/servers/model/preprocess -h``.
 
 Or
 
-    node ./lib/apps/cif2bcif
+    node lib/commonjs/cli/cif2bcif
 
 ## Development
 

+ 24 - 0
docs/interesting-pdb-entries.md

@@ -0,0 +1,24 @@
+* Cyclic polymers (1sfi, 6dny, 1HVZ)
+* B-DNA (1bna)
+* Missing carbonyl oxygen (1gfl)
+* Mono-saccharides with alt locs (1B5F)
+* Microheterogeneity
+    * Protein (1EJG, 3NIR)
+    * DNA (3VOK)
+* PNA: peptide nucleic acid (5eme, 1xj9)
+* Peptide derived residues
+    * GFP chromophores (5Z6Y)
+* Nucleotides that don’t have a parent base set, i.e. detect purine/pyrimidine from geometry (THX in 1AUL, OMC in e.g. 5D3G)
+* Bases with modified ring atoms
+    * DZ has C1 instead of N1 (e.g. 5I4N)
+    * DP has N5 instead of C5 and C7 instead of N7 (e.g. 5I4N)
+* Beta & Gamma peptides (e.g. 1GAC, 6PQF)
+* Mixed (heterogeneous) all-atom/trace-only RNA model (1JGQ)
+* Polymers with residues with missing trace atoms (e.g. 2QFJ)
+* Modified RNA bases (1y26, 5L4O)
+* Discontinuous chains, i.e. gaps in the sequence (3sn6)
+* Lots of sheets (1cbs)
+* DNA (2np2, 1d66)
+* C-alpha only (2rcj)
+* Not cyclic, but termini are backbone-only and within distance but seqIds are not compatible (6SW3)
+* Close backbone atoms but not linked (e.g. 4HIV)

+ 2 - 2
docs/model-server/readme.md

@@ -54,12 +54,12 @@ Sometimes nodejs might run into problems with memory. This is usually resolved b
 
 ## Preprocessor
 
-The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. ``node lib/servers/servers/model/preprocess`` or ``model-server-preprocess`` binary from the NPM package.
+The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. ``node lib/commonjs/servers/model/preprocess`` or ``model-server-preprocess`` binary from the NPM package.
 
 
 ## Local Mode
 
-The server can be run in local/file based mode using ``node lib/servers/servers/model/query`` (``model-server-query`` binary from the NPM package).
+The server can be run in local/file based mode using ``node lib/commonjs/servers/model/query`` (``model-server-query`` binary from the NPM package).
 
 Custom Properties
 =================

+ 3 - 3
docs/volume-server/README.md

@@ -28,7 +28,7 @@ npm run build-tsc
 and run the server by 
 
 ```
-node lib/servers/servers/volume/server
+node lib/commonjs/servers/volume/server
 ```
 
 ## From NPM
@@ -60,11 +60,11 @@ Sometimes nodejs might run into problems with memory. This is usually resolved b
 ## Preparing the Data
 
 For the server to work, CCP4/MAP (models 0, 1, 2 are supported) input data need to be converted into a custom block format. 
-To achieve this, use the ``pack`` application (``node lib/servers/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
+To achieve this, use the ``pack`` application (``node lib/commonjs/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
 
 ## Local Mode
 
-The program  ``lib/servers/servers/volume/pack`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
+The program  ``lib/commonjs/servers/volume/pack`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
 
 ## Navigating the Source Code
 

+ 250 - 300
package-lock.json

@@ -1,6 +1,6 @@
 {
   "name": "molstar",
-  "version": "0.7.1-dev.11",
+  "version": "1.1.1",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -700,9 +700,9 @@
       }
     },
     "@babel/runtime": {
-      "version": "7.9.2",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
-      "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
+      "version": "7.9.6",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz",
+      "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==",
       "dev": true,
       "requires": {
         "regenerator-runtime": "^0.13.4"
@@ -764,31 +764,23 @@
       }
     },
     "@graphql-codegen/add": {
-      "version": "1.13.5",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-1.13.5.tgz",
-      "integrity": "sha512-zxkqrqYR3cQyrzbX1SMdQQhlyESvvfwZgpSzzui8s8rH90ylzxB9oM3uY6agcYSBUQqigKNaO+1SFtGYMWzYvQ==",
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/add/-/add-1.14.0.tgz",
+      "integrity": "sha512-/H66a/H/vOPzhS9Wbx7U7Kx785XQxfI+HM2zGhSvAxVFf2HmucBQNOU9PEgLTUvWCEo7t23PzymlTqoh4d9wfA==",
       "dev": true,
       "requires": {
-        "@graphql-codegen/plugin-helpers": "1.13.5",
-        "tslib": "~1.11.1"
-      },
-      "dependencies": {
-        "tslib": {
-          "version": "1.11.2",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
-          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
-          "dev": true
-        }
+        "@graphql-codegen/plugin-helpers": "1.14.0",
+        "tslib": "~2.0.0"
       }
     },
     "@graphql-codegen/cli": {
-      "version": "1.13.5",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-1.13.5.tgz",
-      "integrity": "sha512-/A19GkD9NpFhRNSt6/sVepZ09hwwpQUhjgKthBea0cteH3jFXa/3h1Sgtk/0z90MLxRNRVaY9V85O7SUU21xBA==",
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-1.14.0.tgz",
+      "integrity": "sha512-ocdVvjXNcRecLBeJSWrA+lzGMBbGD662EB1hVMUkFAR1RNUBjeZGIbtp6N3CM/OoLR0PmasWtgQxffJhPNPQMQ==",
       "dev": true,
       "requires": {
-        "@graphql-codegen/core": "1.13.5",
-        "@graphql-codegen/plugin-helpers": "1.13.5",
+        "@graphql-codegen/core": "1.14.0",
+        "@graphql-codegen/plugin-helpers": "1.14.0",
         "@graphql-toolkit/apollo-engine-loader": "~0.10.4",
         "@graphql-toolkit/code-file-loader": "~0.10.4",
         "@graphql-toolkit/common": "~0.10.4",
@@ -811,7 +803,7 @@
         "dependency-graph": "0.9.0",
         "detect-indent": "6.0.0",
         "glob": "7.1.6",
-        "graphql-config": "3.0.1",
+        "graphql-config": "3.0.2",
         "indent-string": "4.0.0",
         "inquirer": "7.1.0",
         "is-glob": "4.0.1",
@@ -825,44 +817,28 @@
         "pascal-case": "3.1.1",
         "request": "2.88.2",
         "ts-log": "2.1.4",
-        "tslib": "^1.11.1",
+        "tslib": "^2.0.0",
         "upper-case": "2.0.1",
         "valid-url": "1.0.9",
         "wrap-ansi": "7.0.0"
-      },
-      "dependencies": {
-        "tslib": {
-          "version": "1.13.0",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
-          "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==",
-          "dev": true
-        }
       }
     },
     "@graphql-codegen/core": {
-      "version": "1.13.5",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/core/-/core-1.13.5.tgz",
-      "integrity": "sha512-RKkzvCXWfXaNeleHRpp5qWmiwnNCxsc6cVlLmSiZMQad363yOjU2m95oqy4a7WwL6pE/x0NqxtxCFLP7fD+LMQ==",
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/core/-/core-1.14.0.tgz",
+      "integrity": "sha512-bzm/BbPOPpTwhFaHBc6wFHiQGr+7UutSlTVxEZxrxCAkV2YVpu4qvD/NMO0R0jeIO2GLrrLKyYyQJAYDzNNEwQ==",
       "dev": true,
       "requires": {
-        "@graphql-codegen/plugin-helpers": "1.13.5",
+        "@graphql-codegen/plugin-helpers": "1.14.0",
         "@graphql-toolkit/common": "~0.10.4",
         "@graphql-toolkit/schema-merging": "~0.10.4",
-        "tslib": "~1.11.1"
-      },
-      "dependencies": {
-        "tslib": {
-          "version": "1.11.2",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
-          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
-          "dev": true
-        }
+        "tslib": "~2.0.0"
       }
     },
     "@graphql-codegen/plugin-helpers": {
-      "version": "1.13.5",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.13.5.tgz",
-      "integrity": "sha512-Vq+Qh1K74YHge4uxzekWr4Mf63dFem+RpVsw2w/EmssQLK8WasgIEJ1wQp5nWGwvc5Bj7gImO3+VZRxgMEcrcw==",
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.14.0.tgz",
+      "integrity": "sha512-VV74wsEVrzsEXUREQi2ZetO+EkQWj2C+DzjxKmRiFak/RpCasoVPkO/DxspZsljyHi6htIGX5r9m5x8uhfphmg==",
       "dev": true,
       "requires": {
         "@graphql-toolkit/common": "~0.10.4",
@@ -873,114 +849,74 @@
         "lower-case": "2.0.1",
         "param-case": "3.0.3",
         "pascal-case": "3.1.1",
-        "tslib": "~1.11.1",
+        "tslib": "~2.0.0",
         "upper-case": "2.0.1"
-      },
-      "dependencies": {
-        "tslib": {
-          "version": "1.11.2",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
-          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
-          "dev": true
-        }
       }
     },
     "@graphql-codegen/time": {
-      "version": "1.13.5",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/time/-/time-1.13.5.tgz",
-      "integrity": "sha512-ItHfkye5By0EnEyNj7fbqD502dWLrbkwNUTC7dnGIKnz54ey52NslQpO5iEiimSQWhkmFkyWJuggeHG5RSsiaQ==",
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/time/-/time-1.14.0.tgz",
+      "integrity": "sha512-pbvj8grP8mURxyJEcQMtw1q1f2PjGbyjPDxGJHDrqkYpwm60g0NOK4zBkfJyU3zs2G1+v8GMn98rb3hTy2X4Lw==",
       "dev": true,
       "requires": {
-        "@graphql-codegen/plugin-helpers": "1.13.5",
+        "@graphql-codegen/plugin-helpers": "1.14.0",
         "moment": "~2.25.0"
       }
     },
     "@graphql-codegen/typescript": {
-      "version": "1.13.5",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-1.13.5.tgz",
-      "integrity": "sha512-wa9rF7xFaobxzw79Ok40pkjAS2as7nsgm+bRIwyP07YZSOYoa2TOepvAcT4NdZg5+ocmYWIZDpWwtchRHZoT5A==",
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-1.14.0.tgz",
+      "integrity": "sha512-GRoNH4n7j5hvnNwuqDDyAKJ+hyxjhYdA6j4fmJQ3xZa0CvtMb6G6qvZUtdhis7ITQJ6M0dldjIpw9SM1ozMjrw==",
       "dev": true,
       "requires": {
-        "@graphql-codegen/plugin-helpers": "1.13.5",
-        "@graphql-codegen/visitor-plugin-common": "1.13.5",
+        "@graphql-codegen/plugin-helpers": "1.14.0",
+        "@graphql-codegen/visitor-plugin-common": "1.14.0",
         "auto-bind": "~4.0.0",
-        "tslib": "~1.11.1"
-      },
-      "dependencies": {
-        "tslib": {
-          "version": "1.11.2",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
-          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
-          "dev": true
-        }
+        "tslib": "~2.0.0"
       }
     },
     "@graphql-codegen/typescript-graphql-files-modules": {
-      "version": "1.13.5",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-graphql-files-modules/-/typescript-graphql-files-modules-1.13.5.tgz",
-      "integrity": "sha512-UcnGxf0tixS5KHZM4OUM12ec8g44O5ZR5RD0vRg3fPJCRvASa5pS/9w2FWpPE5ob2G3RmgAWT39jxzCTqsp4nQ==",
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-graphql-files-modules/-/typescript-graphql-files-modules-1.14.0.tgz",
+      "integrity": "sha512-FSxtEOE0+3dcCEFnL9lwBJ373ustKTMhi2zjSs7pMVIBgC0LHh9vo0TxoV9PsYKuHhwd0meIJd2/++jdBE0YSQ==",
       "dev": true,
       "requires": {
-        "@graphql-codegen/plugin-helpers": "1.13.5",
-        "tslib": "~1.11.1"
-      },
-      "dependencies": {
-        "tslib": {
-          "version": "1.11.2",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
-          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
-          "dev": true
-        }
+        "@graphql-codegen/plugin-helpers": "1.14.0",
+        "tslib": "~2.0.0"
       }
     },
     "@graphql-codegen/typescript-graphql-request": {
-      "version": "1.13.5",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-graphql-request/-/typescript-graphql-request-1.13.5.tgz",
-      "integrity": "sha512-nl2l0Dz0L7c6pxowzvEWW8bkcjP3Q4JlRo5LpAdToQxnpW7+Q4yVJwaUCjBnZ87hEhj0mTM4vvD44Y/f9xhapA==",
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-graphql-request/-/typescript-graphql-request-1.14.0.tgz",
+      "integrity": "sha512-lumOPfKLj9z3Agjz7ytxULz18LjM/AU4as7z4r2J1gEWSMUj0+aSZCz750KWEma06nW0TPkBgam/P02IpSkBLw==",
       "dev": true,
       "requires": {
-        "@graphql-codegen/plugin-helpers": "1.13.5",
-        "@graphql-codegen/visitor-plugin-common": "1.13.5",
+        "@graphql-codegen/plugin-helpers": "1.14.0",
+        "@graphql-codegen/visitor-plugin-common": "1.14.0",
         "auto-bind": "~4.0.0",
-        "tslib": "~1.11.1"
-      },
-      "dependencies": {
-        "tslib": {
-          "version": "1.11.2",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
-          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
-          "dev": true
-        }
+        "tslib": "~2.0.0"
       }
     },
     "@graphql-codegen/typescript-operations": {
-      "version": "1.13.5",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-1.13.5.tgz",
-      "integrity": "sha512-OusFqBo2zUij0w4uLIw8+lWPXdRTWaW1sm56TjJZFwSHMQjZywYir+I0/x/wxZRX2aalE4sMJTy0AQVQx1f+Mg==",
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-1.14.0.tgz",
+      "integrity": "sha512-dAKA20Np+csY5aBlcq9uI/r/AKSjO5Oqs3kObFeInAX7ntVeDqFpMPT9OFfvJ8fCtzgVSNM8NiNBRwemcuGQrw==",
       "dev": true,
       "requires": {
-        "@graphql-codegen/plugin-helpers": "1.13.5",
-        "@graphql-codegen/typescript": "1.13.5",
-        "@graphql-codegen/visitor-plugin-common": "1.13.5",
+        "@graphql-codegen/plugin-helpers": "1.14.0",
+        "@graphql-codegen/typescript": "1.14.0",
+        "@graphql-codegen/visitor-plugin-common": "1.14.0",
         "auto-bind": "~4.0.0",
-        "tslib": "~1.11.1"
-      },
-      "dependencies": {
-        "tslib": {
-          "version": "1.11.2",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
-          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
-          "dev": true
-        }
+        "tslib": "~2.0.0"
       }
     },
     "@graphql-codegen/visitor-plugin-common": {
-      "version": "1.13.5",
-      "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.13.5.tgz",
-      "integrity": "sha512-NBQBYCGasRKXQK9JW91hjz3CdHK9QjXw7MjfXxkEGcyt7YKtvEKsfCapoUP5BUl7pQFmkqBUacoNg6iVMhtEKg==",
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.14.0.tgz",
+      "integrity": "sha512-gjsj3zKEjhOfsQapx2i/oN6rPvmzpRCuCJTwgYPpSMzpGwMCp930ds1bGPQilFWb6l0N6LtcQSrjOitEvTlRIQ==",
       "dev": true,
       "requires": {
-        "@graphql-codegen/plugin-helpers": "1.13.5",
+        "@graphql-codegen/plugin-helpers": "1.14.0",
         "@graphql-toolkit/relay-operation-optimizer": "~0.10.4",
         "array.prototype.flatmap": "1.2.3",
         "auto-bind": "~4.0.0",
@@ -988,59 +924,51 @@
         "graphql-tag": "2.10.3",
         "parse-filepath": "1.0.2",
         "pascal-case": "3.1.1",
-        "tslib": "~1.11.1"
-      },
-      "dependencies": {
-        "tslib": {
-          "version": "1.11.2",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
-          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
-          "dev": true
-        }
+        "tslib": "~2.0.0"
       }
     },
     "@graphql-toolkit/apollo-engine-loader": {
-      "version": "0.10.6",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/apollo-engine-loader/-/apollo-engine-loader-0.10.6.tgz",
-      "integrity": "sha512-/IsLQiUwECkWtFkVpIeGt4OCXXBOzSbhZ7LYFoAph8eqJzjrnOb/L5XIFTj/pC/WPUHx4oC4KJz0/1ybjkEVRg==",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/apollo-engine-loader/-/apollo-engine-loader-0.10.7.tgz",
+      "integrity": "sha512-nQMvUPjwG2+drBbTdR9m+LmggWr/MimP8vf5ry3yo/ZJmNDVWG6XQqNsfIau5ygpQ+y20dB+lWYbwquYBJYajQ==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.10.6",
+        "@graphql-toolkit/common": "0.10.7",
         "cross-fetch": "3.0.4",
-        "tslib": "1.11.1"
+        "tslib": "1.11.2"
       },
       "dependencies": {
         "tslib": {
-          "version": "1.11.1",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
-          "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
+          "version": "1.11.2",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
+          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
           "dev": true
         }
       }
     },
     "@graphql-toolkit/code-file-loader": {
-      "version": "0.10.6",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/code-file-loader/-/code-file-loader-0.10.6.tgz",
-      "integrity": "sha512-ITkPCURTtJGh0WmMshVQ5bh54AWwOgur8iuAil3Vh14gjuOYxhdOSVQ+L2AVQ22CXclhJLbcm6mhPKjtG7UmBw==",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/code-file-loader/-/code-file-loader-0.10.7.tgz",
+      "integrity": "sha512-siRedPr9Kg4Njn5H6N4LK00y6KChopH6Snv/4RGPar4xxEmh5lBzw5vdfJTlipu3RCw1W5jTTyJZD4qNCndDGA==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.10.6",
-        "@graphql-toolkit/graphql-tag-pluck": "0.10.6",
-        "tslib": "1.11.1"
+        "@graphql-toolkit/common": "0.10.7",
+        "@graphql-toolkit/graphql-tag-pluck": "0.10.7",
+        "tslib": "1.11.2"
       },
       "dependencies": {
         "tslib": {
-          "version": "1.11.1",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
-          "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
+          "version": "1.11.2",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
+          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
           "dev": true
         }
       }
     },
     "@graphql-toolkit/common": {
-      "version": "0.10.6",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/common/-/common-0.10.6.tgz",
-      "integrity": "sha512-rrH/KPheh/wCZzqUmNayBHd+aNWl/751C4iTL/327TzONdAVrV7ZQOyEkpGLW6YEFWPIlWxNkaBoEALIjCxTGg==",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/common/-/common-0.10.7.tgz",
+      "integrity": "sha512-epcJvmIAo+vSEY76F0Dj1Ef6oeewT5pdMe1obHj7LHXN9V22O86aQzwdEEm1iG91qROqSw/apcDnSCMjuVeQVA==",
       "dev": true,
       "requires": {
         "aggregate-error": "3.0.1",
@@ -1050,13 +978,13 @@
       }
     },
     "@graphql-toolkit/core": {
-      "version": "0.10.6",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/core/-/core-0.10.6.tgz",
-      "integrity": "sha512-dUgYTmyIZH+rBVacjPgqO+7qCG5b6pD8niHVghX2h4UAMEApx2o/2TAsSsAMFlqrMA/haW1UIMsmKPw8Yj19ZA==",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/core/-/core-0.10.7.tgz",
+      "integrity": "sha512-LXcFLG7XcRJrPz/xD+0cExzLx/ptVynDM20650/FbmHbKOU50d9mSbcsrzAOq/3f4q3HrRDssvn0f6pPm0EHMg==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.10.6",
-        "@graphql-toolkit/schema-merging": "0.10.6",
+        "@graphql-toolkit/common": "0.10.7",
+        "@graphql-toolkit/schema-merging": "0.10.7",
         "aggregate-error": "3.0.1",
         "globby": "11.0.0",
         "import-from": "^3.0.0",
@@ -1064,168 +992,193 @@
         "lodash": "4.17.15",
         "p-limit": "2.3.0",
         "resolve-from": "5.0.0",
-        "tslib": "1.11.1",
+        "tslib": "1.11.2",
         "unixify": "1.0.0",
         "valid-url": "1.0.9"
       },
       "dependencies": {
         "tslib": {
-          "version": "1.11.1",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
-          "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
+          "version": "1.11.2",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
+          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
           "dev": true
         }
       }
     },
     "@graphql-toolkit/git-loader": {
-      "version": "0.10.6",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/git-loader/-/git-loader-0.10.6.tgz",
-      "integrity": "sha512-n8ZeFTI9PFakLg4++okI9wdPbBUqnnvNsRPqTozdwgO+97kls0fvhe6Cp45dKLG4xVPPTDdqPazQbmjOQtW32g==",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/git-loader/-/git-loader-0.10.7.tgz",
+      "integrity": "sha512-qN8ho16hC2KAdyxNY91yYlwc8y1VptF6KmW/e2WUj7RMWN1h0wmWY9n6eAwF1N5IqqVEzkQ3aeoZe0K/vfa3bg==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.10.6",
-        "@graphql-toolkit/graphql-tag-pluck": "0.10.6",
-        "simple-git": "1.132.0"
-      },
-      "dependencies": {
-        "simple-git": {
-          "version": "1.132.0",
-          "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.132.0.tgz",
-          "integrity": "sha512-xauHm1YqCTom1sC9eOjfq3/9RKiUA9iPnxBbrY2DdL8l4ADMu0jjM5l5lphQP5YWNqAL2aXC/OeuQ76vHtW5fg==",
-          "dev": true,
-          "requires": {
-            "debug": "^4.0.1"
-          }
-        }
+        "@graphql-toolkit/common": "0.10.7",
+        "@graphql-toolkit/graphql-tag-pluck": "0.10.7",
+        "simple-git": "2.5.0"
       }
     },
     "@graphql-toolkit/github-loader": {
-      "version": "0.10.6",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/github-loader/-/github-loader-0.10.6.tgz",
-      "integrity": "sha512-fwwclgMmpcvUQqWCX4kh99N0R3Qjs5kXN6Fcz5IJQSI4ExNlAJk0xG1+8ipYp35oF5+1dc+dMezgLT/kMpRUOA==",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/github-loader/-/github-loader-0.10.7.tgz",
+      "integrity": "sha512-UYbkjDJX4/vjphHutnjRpfFYMDAfoWxqs1Wa45GsT+FJvOJbJb9IGshCTpS81gYmGbcVIeqt/T1XrxuT5sTEsQ==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.10.6",
-        "@graphql-toolkit/graphql-tag-pluck": "0.10.6",
+        "@graphql-toolkit/common": "0.10.7",
+        "@graphql-toolkit/graphql-tag-pluck": "0.10.7",
         "cross-fetch": "3.0.4"
       }
     },
     "@graphql-toolkit/graphql-file-loader": {
-      "version": "0.10.6",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/graphql-file-loader/-/graphql-file-loader-0.10.6.tgz",
-      "integrity": "sha512-D5GutvfUccIFX5Cx/blvrHnt8fXxQ9gM51cgTyGV+6dL2VdCrmOJucEWG7+ki5baCAB78/OyhtP+/tmKNQVPKQ==",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/graphql-file-loader/-/graphql-file-loader-0.10.7.tgz",
+      "integrity": "sha512-6tUIuw/YBlm0VyVgXgMrOXsEQ+WpXVgr2NQwHNzmZo82kPGqImveq7A2D3gBWLyVTcinDScRcKJMxM4kCF5T0A==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.10.6",
-        "tslib": "1.11.1"
+        "@graphql-toolkit/common": "0.10.7",
+        "tslib": "1.11.2"
       },
       "dependencies": {
         "tslib": {
-          "version": "1.11.1",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
-          "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
+          "version": "1.11.2",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
+          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
           "dev": true
         }
       }
     },
     "@graphql-toolkit/graphql-tag-pluck": {
-      "version": "0.10.6",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/graphql-tag-pluck/-/graphql-tag-pluck-0.10.6.tgz",
-      "integrity": "sha512-LZpDGZpsRHlK6fyDVWAC7Bn82RBKrjwrSfi1UTL5uIXyZd1t7YbF3MwvTYMJ+bbJQv21D/vHhXeCDwiWTDaYZw==",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/graphql-tag-pluck/-/graphql-tag-pluck-0.10.7.tgz",
+      "integrity": "sha512-au0Q95/Wbw7fBHeCHU1vMsvYSCKRD5slk+ZZ183LL6lX71phkXIwWD+JpBPBLIY/3Zm+3QUUg66crC+igw4ziw==",
       "dev": true,
       "requires": {
-        "@babel/parser": "7.9.4",
-        "@babel/traverse": "7.9.5",
-        "@babel/types": "7.9.5",
-        "@graphql-toolkit/common": "0.10.6",
+        "@babel/parser": "7.9.6",
+        "@babel/traverse": "7.9.6",
+        "@babel/types": "7.9.6",
+        "@graphql-toolkit/common": "0.10.7",
         "vue-template-compiler": "^2.6.11"
+      },
+      "dependencies": {
+        "@babel/parser": {
+          "version": "7.9.6",
+          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz",
+          "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==",
+          "dev": true
+        },
+        "@babel/traverse": {
+          "version": "7.9.6",
+          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz",
+          "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==",
+          "dev": true,
+          "requires": {
+            "@babel/code-frame": "^7.8.3",
+            "@babel/generator": "^7.9.6",
+            "@babel/helper-function-name": "^7.9.5",
+            "@babel/helper-split-export-declaration": "^7.8.3",
+            "@babel/parser": "^7.9.6",
+            "@babel/types": "^7.9.6",
+            "debug": "^4.1.0",
+            "globals": "^11.1.0",
+            "lodash": "^4.17.13"
+          }
+        },
+        "@babel/types": {
+          "version": "7.9.6",
+          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz",
+          "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==",
+          "dev": true,
+          "requires": {
+            "@babel/helper-validator-identifier": "^7.9.5",
+            "lodash": "^4.17.13",
+            "to-fast-properties": "^2.0.0"
+          }
+        }
       }
     },
     "@graphql-toolkit/json-file-loader": {
-      "version": "0.10.6",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/json-file-loader/-/json-file-loader-0.10.6.tgz",
-      "integrity": "sha512-gTf3gWtc4ZH1OFLl79BRHX0DsjecV0xDxKLKFpGYx22ay72iJqddKFKXxRHeGWFjIrNIi90RUyKlKY8uGyYw2w==",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/json-file-loader/-/json-file-loader-0.10.7.tgz",
+      "integrity": "sha512-nVISrODqvn5LiQ4nKL5pz1Let/W1tuj2viEwrNyTS+9mcjaCE2nhV5MOK/7ZY0cR+XeA4N2u65EH1lQd63U3Cw==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.10.6",
-        "tslib": "1.11.1"
+        "@graphql-toolkit/common": "0.10.7",
+        "tslib": "1.11.2"
       },
       "dependencies": {
         "tslib": {
-          "version": "1.11.1",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
-          "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
+          "version": "1.11.2",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
+          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
           "dev": true
         }
       }
     },
     "@graphql-toolkit/prisma-loader": {
-      "version": "0.10.6",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/prisma-loader/-/prisma-loader-0.10.6.tgz",
-      "integrity": "sha512-vjY5fHn0048Kedhj9DAe/z4+u8VKX6OSLtVQglWYPaxh3Plytv+EPQCsN7AMyjuaNkfQJYBZvTZOL4V/lyK0YQ==",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/prisma-loader/-/prisma-loader-0.10.7.tgz",
+      "integrity": "sha512-yUGB1pyje1GfnO2YNAP/3gWsykMyk8LRC357y5ktL+MBUz1TqwXPeShoQYEZLWPrLmoB6VF/7yu82lMEJW9TnQ==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.10.6",
-        "@graphql-toolkit/url-loader": "0.10.6",
+        "@graphql-toolkit/common": "0.10.7",
+        "@graphql-toolkit/url-loader": "0.10.7",
         "prisma-yml": "1.34.10",
-        "tslib": "1.11.1"
+        "tslib": "1.11.2"
       },
       "dependencies": {
         "tslib": {
-          "version": "1.11.1",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
-          "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
+          "version": "1.11.2",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
+          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
           "dev": true
         }
       }
     },
     "@graphql-toolkit/relay-operation-optimizer": {
-      "version": "0.10.6",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/relay-operation-optimizer/-/relay-operation-optimizer-0.10.6.tgz",
-      "integrity": "sha512-cbaVFJQc6xPRKBwz139RxwQgzrqEw0ggL+0Y7HkyjyiBScq+CzfqYjgwhaLYPGJiACwwjq6X6D6ESDtTS34HXQ==",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/relay-operation-optimizer/-/relay-operation-optimizer-0.10.7.tgz",
+      "integrity": "sha512-Abxu7X9azJ6UU1xmmkvFEnwzhHkq2LEmoFsy5nJUScH/cTa1e9EVmMnsEOxnF0VbfIGanMCrVoFADRntKZZ8Yw==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.10.6",
+        "@graphql-toolkit/common": "0.10.7",
         "relay-compiler": "9.1.0"
       }
     },
     "@graphql-toolkit/schema-merging": {
-      "version": "0.10.6",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/schema-merging/-/schema-merging-0.10.6.tgz",
-      "integrity": "sha512-BNABgYaNCw4Li3EiH/x7oDpkN+ml3M0SWqjnsW1Pf2NcyfGlv033Bda+O/q4XYtseZ0OOOh52GLXtUgwyPFb8A==",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/schema-merging/-/schema-merging-0.10.7.tgz",
+      "integrity": "sha512-VngxJbVdRfXYhdMLhL90pqN+hD/2XTZwhHPGvpWqmGQhT6roc98yN3xyDyrWFYYsuiY4gTexdmrHQ3d7mzitwA==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.10.6",
+        "@graphql-toolkit/common": "0.10.7",
         "deepmerge": "4.2.2",
         "graphql-tools": "5.0.0",
-        "tslib": "1.11.1"
+        "tslib": "1.11.2"
       },
       "dependencies": {
         "tslib": {
-          "version": "1.11.1",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
-          "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
+          "version": "1.11.2",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
+          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
           "dev": true
         }
       }
     },
     "@graphql-toolkit/url-loader": {
-      "version": "0.10.6",
-      "resolved": "https://registry.npmjs.org/@graphql-toolkit/url-loader/-/url-loader-0.10.6.tgz",
-      "integrity": "sha512-Ts8h4zfcOKSp9TNk6uOvmkwDofXUr5cspxO2cpmd0zLnxceHYhe0D2Q/Bq1SPw3NGN2AqvAr7zD8T2z5NDVUKQ==",
+      "version": "0.10.7",
+      "resolved": "https://registry.npmjs.org/@graphql-toolkit/url-loader/-/url-loader-0.10.7.tgz",
+      "integrity": "sha512-Ec3T4Zuo63LwG+RfK2ryz8ChPfncBf8fiSJ1xr68FtLDVznDNulvlNKFbfREE5koWejwsnJrjLCv6IX5IbhExg==",
       "dev": true,
       "requires": {
-        "@graphql-toolkit/common": "0.10.6",
+        "@graphql-toolkit/common": "0.10.7",
         "cross-fetch": "3.0.4",
         "graphql-tools": "5.0.0",
-        "tslib": "1.11.1",
+        "tslib": "1.11.2",
         "valid-url": "1.0.9"
       },
       "dependencies": {
         "tslib": {
-          "version": "1.11.1",
-          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
-          "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
+          "version": "1.11.2",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.2.tgz",
+          "integrity": "sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg==",
           "dev": true
         }
       }
@@ -1941,9 +1894,9 @@
       }
     },
     "@types/jest": {
-      "version": "25.2.2",
-      "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.2.tgz",
-      "integrity": "sha512-aRctFbG8Pb7DSLzUt/fEtL3q/GKb9mretFuYhRub2J0q6NhzBYbx9HTQzHrWgBNIxYOlxGNVe6Z54cpbUt+Few==",
+      "version": "25.2.3",
+      "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.3.tgz",
+      "integrity": "sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw==",
       "requires": {
         "jest-diff": "^25.2.1",
         "pretty-format": "^25.2.1"
@@ -1961,9 +1914,9 @@
       "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw=="
     },
     "@types/node": {
-      "version": "14.0.1",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.1.tgz",
-      "integrity": "sha512-FAYBGwC+W6F9+huFIDtn43cpy7+SzG+atzRiTfdp3inUKL2hXnd4rG8hylJLIh4+hqrQy1P17kvJByE/z825hA=="
+      "version": "14.0.5",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.5.tgz",
+      "integrity": "sha512-90hiq6/VqtQgX8Sp0EzeIsv3r+ellbGj4URKj5j30tLlZvRUpnAe9YbYnjl3pJM93GyXU0tghHhvXHq+5rnCKA=="
     },
     "@types/node-fetch": {
       "version": "2.5.7",
@@ -2058,45 +2011,54 @@
       "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw=="
     },
     "@typescript-eslint/eslint-plugin": {
-      "version": "2.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.33.0.tgz",
-      "integrity": "sha512-QV6P32Btu1sCI/kTqjTNI/8OpCYyvlGjW5vD8MpTIg+HGE5S88HtT1G+880M4bXlvXj/NjsJJG0aGcVh0DdbeQ==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.0.0.tgz",
+      "integrity": "sha512-lcZ0M6jD4cqGccYOERKdMtg+VWpoq3NSnWVxpc/AwAy0zhkUYVioOUZmfNqiNH8/eBNGhCn6HXd6mKIGRgNc1Q==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/experimental-utils": "2.33.0",
+        "@typescript-eslint/experimental-utils": "3.0.0",
         "functional-red-black-tree": "^1.0.1",
         "regexpp": "^3.0.0",
+        "semver": "^7.3.2",
         "tsutils": "^3.17.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "7.3.2",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
+          "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
+          "dev": true
+        }
       }
     },
     "@typescript-eslint/experimental-utils": {
-      "version": "2.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.33.0.tgz",
-      "integrity": "sha512-qzPM2AuxtMrRq78LwyZa8Qn6gcY8obkIrBs1ehqmQADwkYzTE1Pb4y2W+U3rE/iFkSWcWHG2LS6MJfj6SmHApg==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.0.0.tgz",
+      "integrity": "sha512-BN0vmr9N79M9s2ctITtChRuP1+Dls0x/wlg0RXW1yQ7WJKPurg6X3Xirv61J2sjPif4F8SLsFMs5Nzte0WYoTQ==",
       "dev": true,
       "requires": {
         "@types/json-schema": "^7.0.3",
-        "@typescript-eslint/typescript-estree": "2.33.0",
+        "@typescript-eslint/typescript-estree": "3.0.0",
         "eslint-scope": "^5.0.0",
         "eslint-utils": "^2.0.0"
       }
     },
     "@typescript-eslint/parser": {
-      "version": "2.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.33.0.tgz",
-      "integrity": "sha512-AUtmwUUhJoH6yrtxZMHbRUEMsC2G6z5NSxg9KsROOGqNXasM71I8P2NihtumlWTUCRld70vqIZ6Pm4E5PAziEA==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.0.0.tgz",
+      "integrity": "sha512-8RRCA9KLxoFNO0mQlrLZA0reGPd/MsobxZS/yPFj+0/XgMdS8+mO8mF3BDj2ZYQj03rkayhSJtF1HAohQ3iylw==",
       "dev": true,
       "requires": {
         "@types/eslint-visitor-keys": "^1.0.0",
-        "@typescript-eslint/experimental-utils": "2.33.0",
-        "@typescript-eslint/typescript-estree": "2.33.0",
+        "@typescript-eslint/experimental-utils": "3.0.0",
+        "@typescript-eslint/typescript-estree": "3.0.0",
         "eslint-visitor-keys": "^1.1.0"
       }
     },
     "@typescript-eslint/typescript-estree": {
-      "version": "2.33.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.33.0.tgz",
-      "integrity": "sha512-d8rY6/yUxb0+mEwTShCQF2zYQdLlqihukNfG9IUlLYz5y1CH6G/9XYbrxQLq3Z14RNvkCC6oe+OcFlyUpwUbkg==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.0.0.tgz",
+      "integrity": "sha512-nevQvHyNghsfLrrByzVIH4ZG3NROgJ8LZlfh3ddwPPH4CH7W4GAiSx5qu+xHuX5pWsq6q/eqMc1io840ZhAnUg==",
       "dev": true,
       "requires": {
         "debug": "^4.1.1",
@@ -5415,9 +5377,9 @@
       "dev": true
     },
     "fastq": {
-      "version": "1.7.0",
-      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.7.0.tgz",
-      "integrity": "sha512-YOadQRnHd5q6PogvAR/x62BGituF2ufiEA6s8aavQANw5YKHERI4AREboX6KotzP8oX2klxYF2wcV/7bn1clfQ==",
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz",
+      "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==",
       "dev": true,
       "requires": {
         "reusify": "^1.0.4"
@@ -6123,9 +6085,9 @@
       "dev": true
     },
     "graphql-config": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-3.0.1.tgz",
-      "integrity": "sha512-RKktfOcMAh/Lg7jpXXR/u1yOlgWF+bvSUP1wT2aGeUnKfm0B4tu9SBoOAAtt2Vf/bIyOGUSKYkMzqQOiBLTuFw==",
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/graphql-config/-/graphql-config-3.0.2.tgz",
+      "integrity": "sha512-efoimZ4F2wF2OwZJzPq2KdPjQs1K+UgJSfsHoHBBA0TwveGyQ/0kS3lUphhJg/JXIrZociuRkfjrk8JC4iPPJQ==",
       "dev": true,
       "requires": {
         "@graphql-toolkit/common": "~0.10.6",
@@ -6569,15 +6531,15 @@
       "dev": true
     },
     "ignore": {
-      "version": "5.1.4",
-      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
-      "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==",
+      "version": "5.1.6",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.6.tgz",
+      "integrity": "sha512-cgXgkypZBcCnOgSihyeqbo6gjIaIyDqPQB7Ra4vhE9m6kigdGoQDMHjviFhRZo3IMlRy6yElosoviMs5YxZXUA==",
       "dev": true
     },
     "immer": {
-      "version": "6.0.5",
-      "resolved": "https://registry.npmjs.org/immer/-/immer-6.0.5.tgz",
-      "integrity": "sha512-Q2wd90qrgFieIpLzAO2q9NLEdmyp/sr76Ml4Vm5peUKgyTa2CQa3ey8zuzwSKOlKH7grCeGBGUcLLVCVW1aguA=="
+      "version": "6.0.6",
+      "resolved": "https://registry.npmjs.org/immer/-/immer-6.0.6.tgz",
+      "integrity": "sha512-KAo8XDbDcF59lDlKEFOhyssB/z6805ZvH/S3wqMPaTzLMFDUUu1Lq647LrUyuXzI36wMpzwZ83mMxwOXM961aA=="
     },
     "immutable": {
       "version": "3.8.2",
@@ -12279,9 +12241,9 @@
       "dev": true
     },
     "simple-git": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.4.0.tgz",
-      "integrity": "sha512-lqeAiq+P7A7oIGIUllU1Jg9U2SHOdxzhnFU4p4yJdvNoR4O3lYGJCfaC4cGx//J7jkrE+FPs5dJR0JVg1wVwfQ==",
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.5.0.tgz",
+      "integrity": "sha512-4gmtMqfIL9bsBNJDP/rDwZe3GsQL/tp85Qv5cmRc8iIDNOZJS4IX1oPfcqp9b7BGPc5bfuw4yd1i3lQacvuqDQ==",
       "dev": true,
       "requires": {
         "@kwsites/exec-p": "^0.4.0",
@@ -12854,9 +12816,9 @@
       }
     },
     "swagger-ui-dist": {
-      "version": "3.25.3",
-      "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.25.3.tgz",
-      "integrity": "sha512-/8DSx431mdN94t8mIZejhVUdN9r8zM+V1l+VGT0h7smrzYFa9vWi2sLVCg4YfgKgMjXYhU4OKADHPnWkbLb+ZQ=="
+      "version": "3.25.4",
+      "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.25.4.tgz",
+      "integrity": "sha512-IiupbwT2znZ/BXDgSv9gIaxTL+oVSvhaSe5K4NxSCxRe6bTFK/Q4JwOGzYLep5I2h460xSbwdIuUUblNDolTNg=="
     },
     "symbol-observable": {
       "version": "1.2.0",
@@ -13254,9 +13216,9 @@
       }
     },
     "ts-jest": {
-      "version": "25.5.1",
-      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-25.5.1.tgz",
-      "integrity": "sha512-kHEUlZMK8fn8vkxDjwbHlxXRB9dHYpyzqKIGDNxbzs+Rz+ssNDSDNusEK8Fk/sDd4xE6iKoQLfFkFVaskmTJyw==",
+      "version": "26.0.0",
+      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.0.0.tgz",
+      "integrity": "sha512-eBpWH65mGgzobuw7UZy+uPP9lwu+tPp60o324ASRX4Ijg8UC5dl2zcge4kkmqr2Zeuk9FwIjvCTOPuNMEyGWWw==",
       "dev": true,
       "requires": {
         "bs-logger": "0.x",
@@ -13266,24 +13228,15 @@
         "lodash.memoize": "4.x",
         "make-error": "1.x",
         "micromatch": "4.x",
-        "mkdirp": "0.x",
-        "semver": "6.x",
+        "mkdirp": "1.x",
+        "semver": "7.x",
         "yargs-parser": "18.x"
       },
       "dependencies": {
-        "mkdirp": {
-          "version": "0.5.5",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
-          "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
-          "dev": true,
-          "requires": {
-            "minimist": "^1.2.5"
-          }
-        },
         "semver": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "version": "7.3.2",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
+          "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
           "dev": true
         },
         "yargs-parser": {
@@ -13393,9 +13346,9 @@
       }
     },
     "typescript": {
-      "version": "3.9.2",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.2.tgz",
-      "integrity": "sha512-q2ktq4n/uLuNNShyayit+DTobV2ApPEo/6so68JaD5ojvc/6GClBipedB9zNWYxRSAlZXAe405Rlijzl6qDiSw==",
+      "version": "3.9.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz",
+      "integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==",
       "dev": true
     },
     "ua-parser-js": {
@@ -15178,13 +15131,10 @@
       "dev": true
     },
     "yaml": {
-      "version": "1.9.2",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.9.2.tgz",
-      "integrity": "sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg==",
-      "dev": true,
-      "requires": {
-        "@babel/runtime": "^7.9.2"
-      }
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
+      "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==",
+      "dev": true
     },
     "yaml-ast-parser": {
       "version": "0.0.40",

+ 34 - 32
package.json

@@ -1,6 +1,6 @@
 {
   "name": "molstar",
-  "version": "0.7.1-dev.11",
+  "version": "1.1.1",
   "description": "A comprehensive macromolecular library.",
   "homepage": "https://github.com/molstar/molstar#readme",
   "repository": {
@@ -16,7 +16,7 @@
     "test": "npm run lint && jest",
     "build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
     "build-viewer": "npm run build-tsc && npm run build-extra && npm run build-webpack-viewer",
-    "build-tsc": "tsc --incremental && tsc --build tsconfig.servers.json --incremental",
+    "build-tsc": "concurrently \"tsc --incremental\" \"tsc --build tsconfig.commonjs.json --incremental\"",
     "build-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/",
     "build-webpack": "webpack --mode production --config ./webpack.config.production.js",
     "build-webpack-viewer": "webpack --mode production --config ./webpack.config.viewer.js",
@@ -24,16 +24,16 @@
     "watch-viewer": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer\"",
     "watch-viewer-debug": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer-debug\"",
     "watch-tsc": "tsc --watch --incremental",
-    "watch-servers": "tsc --build tsconfig.servers.json --watch --incremental",
+    "watch-servers": "tsc --build tsconfig.commonjs.json --watch --incremental",
     "watch-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/ --watch",
     "watch-webpack": "webpack -w --mode development --display minimal",
     "watch-webpack-viewer": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.js",
     "watch-webpack-viewer-debug": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.debug.js",
     "serve": "http-server -p 1338",
-    "model-server": "node lib/servers/servers/model/server.js",
-    "model-server-watch": "nodemon --watch lib lib/servers/servers/model/server.js",
-    "volume-server-test": "node lib/servers/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
-    "plugin-state": "node lib/servers/servers/plugin-state/index.js",
+    "model-server": "node lib/commonjs/servers/model/server.js",
+    "model-server-watch": "nodemon --watch lib lib/commonjs/servers/model/server.js",
+    "volume-server-test": "node lib/commonjs/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
+    "plugin-state": "node lib/commonjs/servers/plugin-state/index.js",
     "preversion": "npm run test",
     "version": "npm run build",
     "postversion": "git push && git push --tags"
@@ -43,14 +43,14 @@
     "build/viewer/"
   ],
   "bin": {
-    "cif2bcif": "lib/apps/cif2bcif/index.js",
-    "cifschema": "lib/apps/cifschema/index.js",
-    "model-server": "lib/servers/servers/model/server.js",
-    "model-server-query": "lib/servers/servers/model/query.js",
-    "model-server-preprocess": "lib/servers/servers/model/preprocess.js",
-    "volume-server": "lib/servers/servers/volume/server.js",
-    "volume-server-query": "lib/servers/servers/volume/query.js",
-    "volume-server-pack": "lib/servers/servers/volume/pack.js"
+    "cif2bcif": "lib/commonjs/cli/cif2bcif/index.js",
+    "cifschema": "lib/commonjs/cli/cifschema/index.js",
+    "model-server": "lib/commonjs/servers/model/server.js",
+    "model-server-query": "lib/commonjs/servers/model/query.js",
+    "model-server-preprocess": "lib/commonjs/servers/model/preprocess.js",
+    "volume-server": "lib/commonjs/servers/volume/server.js",
+    "volume-server-query": "lib/commonjs/servers/volume/query.js",
+    "volume-server-pack": "lib/commonjs/servers/volume/pack.js"
   },
   "nodemonConfig": {
     "ignoreRoot": [
@@ -80,20 +80,22 @@
     "Alexander Rose <alexander.rose@weirdbyte.de>",
     "David Sehnal <david.sehnal@gmail.com>",
     "Sebastian Bittrich <sebastian.bittrich@rcsb.org>",
-    "Ludovic Autin <autin@scripps.edu>"
+    "Ludovic Autin <autin@scripps.edu>",
+    "Michal Malý <michal.maly@ibt.cas.cz>",
+    "Jiří Černý <jiri.cerny@ibt.cas.cz>"
   ],
   "license": "MIT",
   "devDependencies": {
-    "@graphql-codegen/add": "^1.13.5",
-    "@graphql-codegen/cli": "^1.13.5",
-    "@graphql-codegen/time": "^1.13.5",
-    "@graphql-codegen/typescript": "^1.13.5",
-    "@graphql-codegen/typescript-graphql-files-modules": "^1.13.5",
-    "@graphql-codegen/typescript-graphql-request": "^1.13.5",
-    "@graphql-codegen/typescript-operations": "^1.13.5",
+    "@graphql-codegen/add": "^1.14.0",
+    "@graphql-codegen/cli": "^1.14.0",
+    "@graphql-codegen/time": "^1.14.0",
+    "@graphql-codegen/typescript": "^1.14.0",
+    "@graphql-codegen/typescript-graphql-files-modules": "^1.14.0",
+    "@graphql-codegen/typescript-graphql-request": "^1.14.0",
+    "@graphql-codegen/typescript-operations": "^1.14.0",
     "@types/cors": "^2.8.6",
-    "@typescript-eslint/eslint-plugin": "^2.33.0",
-    "@typescript-eslint/parser": "^2.33.0",
+    "@typescript-eslint/eslint-plugin": "^3.0.0",
+    "@typescript-eslint/parser": "^3.0.0",
     "benchmark": "^2.1.4",
     "concurrently": "^5.2.0",
     "cpx2": "^2.0.0",
@@ -109,10 +111,10 @@
     "node-sass": "^4.14.1",
     "raw-loader": "^4.0.1",
     "sass-loader": "^8.0.2",
-    "simple-git": "^2.4.0",
+    "simple-git": "^2.5.0",
     "style-loader": "^1.2.1",
-    "ts-jest": "^25.5.1",
-    "typescript": "^3.9.2",
+    "ts-jest": "^26.0.0",
+    "typescript": "^3.9.3",
     "webpack": "^4.43.0",
     "webpack-cli": "^3.3.11",
     "webpack-version-file-plugin": "^0.4.0"
@@ -122,8 +124,8 @@
     "@types/benchmark": "^1.0.33",
     "@types/compression": "1.7.0",
     "@types/express": "^4.17.6",
-    "@types/jest": "^25.2.2",
-    "@types/node": "^14.0.1",
+    "@types/jest": "^25.2.3",
+    "@types/node": "^14.0.5",
     "@types/node-fetch": "^2.5.7",
     "@types/react": "^16.9.35",
     "@types/react-dom": "^16.9.8",
@@ -133,13 +135,13 @@
     "compression": "^1.7.4",
     "cors": "^2.8.5",
     "express": "^4.17.1",
-    "immer": "^6.0.5",
+    "immer": "^6.0.6",
     "immutable": "^3.8.2",
     "node-fetch": "^2.6.0",
     "react": "^16.13.1",
     "react-dom": "^16.13.1",
     "rxjs": "^6.5.5",
-    "swagger-ui-dist": "^3.25.3",
+    "swagger-ui-dist": "^3.25.4",
     "tslib": "^2.0.0",
     "util.promisify": "^1.0.1",
     "xhr2": "^0.2.0"

+ 2 - 0
src/apps/viewer/index.ts

@@ -25,6 +25,7 @@ import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
 import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
 import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
 import { MembraneOrientationData } from '../../extensions/membrane-orientation/behavior';
+import { DnatcoConfalPyramids } from '../../extensions/dnatco';
 
 require('mol-plugin-ui/skin/light.scss');
 
@@ -33,6 +34,7 @@ export { setProductionMode, setDebugMode } from '../../mol-util/debug';
 
 const Extensions = {
     'cellpack': PluginSpec.Behavior(CellPack),
+    'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids),
     'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),
     'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
     'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),

+ 2 - 1
src/apps/chem-comp-bond/create-table.ts → src/cli/chem-comp-bond/create-table.ts

@@ -1,3 +1,4 @@
+#!/usr/bin/env node
 /**
  * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
@@ -219,7 +220,7 @@ async function run(out: string, binary = false) {
 
 const TABLE_NAME = 'CHEM_COMP_BONDS';
 
-const DATA_DIR = path.join(__dirname, '..', '..', '..', 'build/data');
+const DATA_DIR = path.join(__dirname, '..', '..', '..', '..', 'build/data');
 const CCD_PATH = path.join(DATA_DIR, 'components.cif');
 const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif');
 const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif';

+ 0 - 0
src/apps/cif2bcif/converter.ts → src/cli/cif2bcif/converter.ts


+ 1 - 0
src/apps/cif2bcif/index.ts → src/cli/cif2bcif/index.ts

@@ -1,3 +1,4 @@
+#!/usr/bin/env node
 /**
  * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *

+ 6 - 5
src/apps/cifschema/index.ts → src/cli/cifschema/index.ts

@@ -1,3 +1,4 @@
+#!/usr/bin/env node
 /**
  * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
@@ -159,7 +160,7 @@ async function ensureDicAvailable(dicPath: string, dicUrl: string) {
     }
 }
 
-const DIC_DIR = path.resolve(__dirname, '../../../build/dics/');
+const DIC_DIR = path.resolve(__dirname, '../../../../build/dics/');
 const MMCIF_DIC_PATH = `${DIC_DIR}/mmcif_pdbx_v50.dic`;
 const MMCIF_DIC_URL = 'http://mmcif.wwpdb.org/dictionaries/ascii/mmcif_pdbx_v50.dic';
 const IHM_DIC_PATH = `${DIC_DIR}/ihm-extension.dic`;
@@ -237,22 +238,22 @@ switch (args.preset) {
     case 'mmCIF':
         args.name = 'mmCIF';
         args.dic = 'mmCIF';
-        args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/mmcif-field-names.csv');
+        args.fieldNamesPath = path.resolve(__dirname, '../../../../data/cif-field-names/mmcif-field-names.csv');
         break;
     case 'CCD':
         args.name = 'CCD';
         args.dic = 'mmCIF';
-        args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/ccd-field-names.csv');
+        args.fieldNamesPath = path.resolve(__dirname, '../../../../data/cif-field-names/ccd-field-names.csv');
         break;
     case 'BIRD':
         args.name = 'BIRD';
         args.dic = 'mmCIF';
-        args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/bird-field-names.csv');
+        args.fieldNamesPath = path.resolve(__dirname, '../../../../data/cif-field-names/bird-field-names.csv');
         break;
     case 'CifCore':
         args.name = 'CifCore';
         args.dic = 'CifCore';
-        args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/cif-core-field-names.csv');
+        args.fieldNamesPath = path.resolve(__dirname, '../../../../data/cif-field-names/cif-core-field-names.csv');
         break;
 }
 

+ 0 - 0
src/apps/cifschema/util/cif-dic.ts → src/cli/cifschema/util/cif-dic.ts


+ 5 - 5
src/apps/cifschema/util/generate.ts → src/cli/cifschema/util/generate.ts

@@ -17,15 +17,15 @@ function header (name: string, info: string, moldataImportPath: string) {
  * @author molstar/ciftools package
  */
 
-import { Database, Column } from '${moldataImportPath}/db'
+import { Database, Column } from '${moldataImportPath}/db';
 
-import Schema = Column.Schema`;
+import Schema = Column.Schema;`;
 }
 
 function footer (name: string) {
     return `
 export type ${name}_Schema = typeof ${name}_Schema;
-export interface ${name}_Database extends Database<${name}_Schema> {}`;
+export interface ${name}_Database extends Database<${name}_Schema> {};`;
 }
 
 function getTypeShorthands(schema: Database, fields?: Filter) {
@@ -122,7 +122,7 @@ export function generate (name: string, info: string, schema: Database, fields:
         });
         codeLines.push('    },');
     });
-    codeLines.push('}');
+    codeLines.push('};');
 
     if (addAliases) {
         codeLines.push('');
@@ -144,7 +144,7 @@ export function generate (name: string, info: string, schema: Database, fields:
             });
             codeLines.push('    ],');
         });
-        codeLines.push('}');
+        codeLines.push('};');
     }
 
     return `${header(name, info, moldataImportPath)}\n\n${getTypeShorthands(schema, fields)}\n\n${codeLines.join('\n')}\n${footer(name)}`;

+ 0 - 0
src/apps/cifschema/util/helper.ts → src/cli/cifschema/util/helper.ts


+ 0 - 0
src/apps/cifschema/util/schema.ts → src/cli/cifschema/util/schema.ts


+ 88 - 0
src/cli/lipid-params/index.ts

@@ -0,0 +1,88 @@
+#!/usr/bin/env node
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as argparse from 'argparse';
+import * as fs from 'fs';
+import * as path from 'path';
+import fetch from 'node-fetch';
+import { UniqueArray } from '../../mol-data/generic';
+
+const LIPIDS_DIR = path.resolve(__dirname, '../../../../build/lipids/');
+
+const MARTINI_LIPIDS_PATH = path.resolve(LIPIDS_DIR, 'martini_lipids.itp');
+const MARTINI_LIPIDS_URL = 'http://www.cgmartini.nl/images/parameters/lipids/Collections/martini_v2.0_lipids_all_201506.itp';
+
+async function ensureAvailable(path: string, url: string) {
+    if (FORCE_DOWNLOAD || !fs.existsSync(path)) {
+        const name = url.substr(url.lastIndexOf('/') + 1);
+        console.log(`downloading ${name}...`);
+        const data = await fetch(url);
+        if (!fs.existsSync(LIPIDS_DIR)) {
+            fs.mkdirSync(LIPIDS_DIR);
+        }
+        fs.writeFileSync(path, await data.text());
+        console.log(`done downloading ${name}`);
+    }
+}
+
+async function ensureLipidsAvailable() { await ensureAvailable(MARTINI_LIPIDS_PATH, MARTINI_LIPIDS_URL); }
+
+async function run(out: string) {
+    await ensureLipidsAvailable();
+    const lipidsItpStr = fs.readFileSync(MARTINI_LIPIDS_PATH, 'utf8');
+
+    const lipids = UniqueArray.create<string>();
+    const reLipid = /\[moleculetype\]\n; molname      nrexcl\n +([a-zA-Z]{3,5})/g;
+    let m: RegExpExecArray | null;
+
+    while ((m = reLipid.exec(lipidsItpStr)) !== null) {
+        const v = m[0].substr(m[0].lastIndexOf(' ') + 1);
+        UniqueArray.add(lipids, v, v);
+    }
+
+
+    const lipidNames = JSON.stringify(lipids.array);
+
+    if (out) {
+        const output = `/**
+* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+*
+* Code-generated lipid params file. Names extracted from Martini FF lipids itp.
+*
+* @author molstar/lipid-params cli
+*/
+
+export const LipidNames = new Set(${lipidNames.replace(/"/g, "'").replace(/,/g, ', ')});
+`;
+        fs.writeFileSync(out, output);
+    } else {
+        console.log(lipidNames);
+    }
+}
+
+const parser = new argparse.ArgumentParser({
+    addHelp: true,
+    description: 'Create lipid params (from martini lipids itp)'
+});
+parser.addArgument([ '--out', '-o' ], {
+    help: 'Generated lipid params output path, if not given printed to stdout'
+});
+parser.addArgument([ '--forceDownload', '-f' ], {
+    action: 'storeTrue',
+    help: 'Force download of martini lipids itp'
+});
+interface Args {
+    out: string
+    forceDownload: boolean
+}
+const args: Args = parser.parseArgs();
+
+const FORCE_DOWNLOAD = args.forceDownload;
+
+run(args.out || '').catch(e => {
+    console.error(e);
+});

+ 1 - 0
src/apps/state-docs/index.ts → src/cli/state-docs/index.ts

@@ -1,3 +1,4 @@
+#!/usr/bin/env node
 /**
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *

+ 0 - 0
src/apps/state-docs/pd-to-md.ts → src/cli/state-docs/pd-to-md.ts


+ 0 - 0
src/apps/structure-info/helpers.ts → src/cli/structure-info/helpers.ts


+ 10 - 7
src/apps/structure-info/model.ts → src/cli/structure-info/model.ts

@@ -1,3 +1,4 @@
+#!/usr/bin/env node
 /**
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
@@ -33,20 +34,22 @@ export async function readCifFile(path: string) {
 
 export function atomLabel(model: Model, aI: number) {
     const { atoms, residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy;
-    const { label_atom_id } = atoms;
-    const { label_comp_id, label_seq_id } = residues;
+    const { label_atom_id, label_comp_id } = atoms;
+    const { label_seq_id } = residues;
     const { label_asym_id } = chains;
     const rI = residueAtomSegments.index[aI];
     const cI = chainAtomSegments.index[aI];
-    return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`;
+    return `${label_asym_id.value(cI)} ${label_comp_id.value(aI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`;
 }
 
 export function residueLabel(model: Model, rI: number) {
-    const { residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy;
-    const { label_comp_id, label_seq_id } = residues;
+    const { atoms, residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy;
+    const { label_comp_id } = atoms;
+    const { label_seq_id } = residues;
     const { label_asym_id } = chains;
-    const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]];
-    return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`;
+    const aI = residueAtomSegments.offsets[rI];
+    const cI = chainAtomSegments.index[aI];
+    return `${label_asym_id.value(cI)} ${label_comp_id.value(aI)} ${label_seq_id.value(rI)}`;
 }
 
 export function printSecStructure(model: Model) {

+ 1 - 0
src/apps/structure-info/volume.ts → src/cli/structure-info/volume.ts

@@ -1,3 +1,4 @@
+#!/usr/bin/env node
 /**
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *

+ 1 - 1
src/examples/proteopedia-wrapper/helpers.ts

@@ -58,7 +58,7 @@ export namespace ModelInfo {
             const entityType = model.entities.data.type.value(eI);
             if (entityType !== 'non-polymer' && entityType !== 'branched') continue;
 
-            const comp_id = model.atomicHierarchy.residues.label_comp_id.value(rI);
+            const comp_id = model.atomicHierarchy.atoms.label_comp_id.value(residueOffsets[rI]);
 
             let lig = hetMap.get(comp_id);
             if (!lig) {

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

@@ -10,7 +10,7 @@ import { Color } from '../../../mol-util/color';
 import { getPalette } from '../../../mol-util/color/palette';
 import { ColorTheme, LocationColor } from '../../../mol-theme/color';
 import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
-import { StructureElement, Bond } from '../../../mol-model/structure';
+import { StructureElement, Bond, Model } from '../../../mol-model/structure';
 import { Location } from '../../../mol-model/location';
 import { CellPackInfoProvider } from '../property';
 import { distinctColors } from '../../../mol-util/color/distinct';
@@ -40,7 +40,7 @@ export function CellPackGenerateColorTheme(ctx: ThemeDataContext, props: PD.Valu
         const { models } = ctx.structure.root;
 
         let size = 0;
-        for (const m of models) size = Math.max(size, m.trajectoryInfo.size);
+        for (const m of models) size = Math.max(size, Model.TrajectoryInfo.get(m).size);
 
         const palette = getPalette(size, { palette: {
             name: 'generate',
@@ -53,15 +53,15 @@ export function CellPackGenerateColorTheme(ctx: ThemeDataContext, props: PD.Valu
         legend = palette.legend;
         const modelColor = new Map<number, Color>();
         for (let i = 0, il = models.length; i < il; ++i) {
-            const idx = models[i].trajectoryInfo.index;
-            modelColor.set(models[i].trajectoryInfo.index, palette.color(idx));
+            const idx = Model.TrajectoryInfo.get(models[i]).index;
+            modelColor.set(Model.TrajectoryInfo.get(models[i]).index, palette.color(idx));
         }
 
         color = (location: Location): Color => {
             if (StructureElement.Location.is(location)) {
-                return modelColor.get(location.unit.model.trajectoryInfo.index)!;
+                return modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!;
             } else if (Bond.isLocation(location)) {
-                return modelColor.get(location.aUnit.model.trajectoryInfo.index)!;
+                return modelColor.get(Model.TrajectoryInfo.get(location.aUnit.model).index)!;
             }
             return DefaultColor;
         };
@@ -89,7 +89,7 @@ export const CellPackGenerateColorThemeProvider: ColorTheme.Provider<CellPackGen
     isApplicable: (ctx: ThemeDataContext) => {
         return (
             !!ctx.structure && ctx.structure.elementCount > 0 &&
-            ctx.structure.models[0].trajectoryInfo.size > 1 &&
+            Model.TrajectoryInfo.get(ctx.structure.models[0]).size > 1 &&
             !!CellPackInfoProvider.get(ctx.structure).value
         );
     }

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

@@ -9,7 +9,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { Color } from '../../../mol-util/color';
 import { ColorTheme, LocationColor } from '../../../mol-theme/color';
 import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
-import { StructureElement } from '../../../mol-model/structure';
+import { StructureElement, Model } from '../../../mol-model/structure';
 import { Location } from '../../../mol-model/location';
 import { CellPackInfoProvider } from '../property';
 
@@ -32,13 +32,13 @@ export function CellPackProvidedColorTheme(ctx: ThemeDataContext, props: PD.Valu
         const { models } = ctx.structure.root;
         const modelColor = new Map<number, Color>();
         for (let i = 0, il = models.length; i < il; ++i) {
-            const idx = models[i].trajectoryInfo.index;
-            modelColor.set(models[i].trajectoryInfo.index, info.colors[idx]);
+            const idx = Model.TrajectoryInfo.get(models[i]).index;
+            modelColor.set(Model.TrajectoryInfo.get(models[i]).index, info.colors[idx]);
         }
 
         color = (location: Location): Color => {
             return StructureElement.Location.is(location)
-                ? modelColor.get(location.unit.model.trajectoryInfo.index)!
+                ? modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!
                 : DefaultColor;
         };
     } else {
@@ -65,7 +65,7 @@ export const CellPackProvidedColorThemeProvider: ColorTheme.Provider<CellPackPro
     isApplicable: (ctx: ThemeDataContext) => {
         return (
             !!ctx.structure && ctx.structure.elementCount > 0 &&
-            ctx.structure.models[0].trajectoryInfo.size > 1 &&
+            Model.TrajectoryInfo.get(ctx.structure.models[0]).size > 1 &&
             !!CellPackInfoProvider.get(ctx.structure).value?.colors
         );
     }

+ 1 - 3
src/extensions/cellpack/model.ts

@@ -412,9 +412,7 @@ export function createStructureFromCellPack(plugin: PluginContext, packing: Cell
         if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
         const structure = builder.getStructure();
         for( let i = 0, il = structure.models.length; i < il; ++i) {
-            const { trajectoryInfo } = structure.models[i];
-            trajectoryInfo.size = il;
-            trajectoryInfo.index = i;
+            Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
         }
         return { structure, assets, colors: skipColors ? undefined : colors };
     });

+ 2 - 56
src/extensions/cellpack/state.ts

@@ -13,7 +13,7 @@ import { IngredientFiles } from './util';
 import { Asset } from '../../mol-util/assets';
 import { PluginContext } from '../../mol-plugin/context';
 import { CellPackInfoProvider } from './property';
-import { Structure, StructureSymmetry, Unit } from '../../mol-model/structure';
+import { Structure, StructureSymmetry, Unit, Model } from '../../mol-model/structure';
 import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
 
 export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/';
@@ -138,9 +138,7 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
                 }
                 structure = builder.getStructure();
                 for( let i = 0, il = structure.models.length; i < il; ++i) {
-                    const { trajectoryInfo } = structure.models[i];
-                    trajectoryInfo.size = il;
-                    trajectoryInfo.index = i;
+                    Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
                 }
             }
             return new PSO.Molecule.Structure(structure, { label: a.label, description: `${a.description}` });
@@ -150,55 +148,3 @@ const StructureFromAssemblies = PluginStateTransform.BuiltIn({
         b?.data.customPropertyDescriptors.dispose();
     }
 });
-
-// export { GetAllAssamblyinOneStructure };
-// type GetAllAssamblyinOneStructure = typeof GetAllAssamblyinOneStructure
-// const GetAllAssamblyinOneStructure = PluginStateTransform.BuiltIn({
-//     name: 'get assambly from structure',
-//     display: { name: 'get assambly from structure' },
-//     isDecorator: true,
-//     from: PSO.Molecule.Structure,
-//     to: PSO.Molecule.Structure,
-//     params(a) {
-//         return { };
-//     }
-// })({
-//     apply({ a, params }) {
-//         return Task.create('Build Structure Assemblies', async ctx => {
-//             // TODO: optimze
-//             // TODO: think of ways how to fast-track changes to this for animations
-//             const initial_structure = a.data;
-//             const structures: Structure[] = [];
-//             let structure: Structure = initial_structure;
-//             // the list of asambly *?
-//             const symmetry = ModelSymmetry.Provider.get(initial_structure.model);
-//             if (symmetry){
-//                 if (symmetry.assemblies.length !== 0) {
-//                     for (const a of symmetry.assemblies) {
-//                         const s = await StructureSymmetry.buildAssembly(initial_structure, a.id!).runInContext(ctx);
-//                         structures.push(s);
-//                     }
-//                     const builder = Structure.Builder({ label: name });
-//                     let offsetInvariantId = 0;
-//                     for (const s of structures) {
-//                         let maxInvariantId = 0;
-//                         for (const u of s.units) {
-//                             const invariantId = u.invariantId + offsetInvariantId;
-//                             if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
-//                             builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId);
-//                         }
-//                         offsetInvariantId += maxInvariantId + 1;
-//                     }
-//                     structure = builder.getStructure();
-//                     for( let i = 0, il = structure.models.length; i < il; ++i) {
-//                         const { trajectoryInfo } = structure.models[i];
-//                         trajectoryInfo.size = il;
-//                         trajectoryInfo.index = i;
-//                     }
-//                 }
-//             }
-//             return new PSO.Molecule.Structure(structure, { label: a.label, description: `${a.description}` });
-//         });
-//     }
-// });
-

+ 10 - 0
src/extensions/dnatco/README.md

@@ -0,0 +1,10 @@
+## DNATCO Extensions
+
+### Confal Pyramids
+
+The Confal Pyramids extensions displays tetrahedron-like pyramids. These pyramids are a simple visual representation of nucleotide conformer classes that can be assigned to individual steps in nucleic acid structures.
+
+For more information, see:
+* [Černý et al., Nucleic Acids Research, 44, W284 (2016)](http://dx.doi.org/10.1093/nar/gkw381)
+* [Schneider et al., Acta Cryst D, 74, 52-64 (2018)](http://dx.doi.org/10.1107/S2059798318000050)
+* [Schneider et al., Genes, 8(10), 278, (2017)](http://dx.doi.org/10.3390/genes8100278)

+ 103 - 0
src/extensions/dnatco/confal-pyramids/behavior.ts

@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Michal Malý <michal.maly@ibt.cas.cz>
+ * @author Jiří Černý <jiri.cerny@ibt.cas.cz>
+ */
+
+import { ConfalPyramidsColorThemeProvider } from './color';
+import { ConfalPyramids, ConfalPyramidsProvider } from './property';
+import { ConfalPyramidsRepresentationProvider } from './representation';
+import { Loci } from '../../../mol-model/loci';
+import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
+import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../mol-plugin-state/builder/structure/representation-preset';
+import { StateObjectRef } from '../../../mol-state';
+import { Task } from '../../../mol-task';
+import { ParamDefinition as PD } from '../../../mol-util/param-definition';
+
+export const DnatcoConfalPyramidsPreset = StructureRepresentationPresetProvider({
+    id: 'preset-structure-representation-confal-pyramids',
+    display: {
+        name: 'Confal Pyramids', group: 'Annotation',
+        description: 'Schematic depiction of conformer class and confal value.',
+    },
+    isApplicable(a) {
+        return a.data.models.length >= 1 && a.data.models.some(m => ConfalPyramids.isApplicable(m));
+    },
+    params: () => StructureRepresentationPresetProvider.CommonParams,
+    async apply(ref, params, plugin) {
+        const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
+        const model = structureCell?.obj?.data.model;
+        if (!structureCell || !model) return {};
+
+        await plugin.runTask(Task.create('Confal Pyramids', async runtime => {
+            await ConfalPyramidsProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
+        }));
+
+        const { components, representations } = await PresetStructureRepresentations.auto.apply(ref, { ...params }, plugin);
+
+        const pyramids = await plugin.builders.structure.tryCreateComponentStatic(structureCell, 'nucleic', { label: 'Confal Pyramids' });
+        const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
+
+        let pyramidsRepr;
+        if (representations)
+            pyramidsRepr = builder.buildRepresentation(update, pyramids,  { type: ConfalPyramidsRepresentationProvider, typeParams, color: ConfalPyramidsColorThemeProvider }, { tag: 'confal-pyramdis' } );
+
+        await update.commit({ revertOnError: true });
+        return  { components: { ...components, pyramids }, representations: { ...representations, pyramidsRepr } };
+    }
+});
+
+export const DnatcoConfalPyramids = PluginBehavior.create<{ autoAttach: boolean, showToolTip: boolean }>({
+    name: 'dnatco-confal-pyramids-prop',
+    category: 'custom-props',
+    display: {
+        name: 'Confal Pyramids',
+        description: 'Schematic depiction of conformer class and confal value.',
+    },
+    ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showToolTip: boolean }> {
+
+        private provider = ConfalPyramidsProvider;
+
+        private labelConfalPyramids = {
+            label: (loci: Loci): string | undefined => {
+                if (!this.params.showToolTip) return void 0;
+
+                /* TODO: Implement this */
+                return void 0;
+            }
+        }
+
+        register(): void {
+            this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
+            this.ctx.managers.lociLabels.addProvider(this.labelConfalPyramids);
+
+            this.ctx.representation.structure.themes.colorThemeRegistry.add(ConfalPyramidsColorThemeProvider);
+            this.ctx.representation.structure.registry.add(ConfalPyramidsRepresentationProvider);
+
+            this.ctx.builders.structure.representation.registerPreset(DnatcoConfalPyramidsPreset);
+        }
+
+        update(p: { autoAttach: boolean, showToolTip: boolean }) {
+            const updated = this.params.autoAttach !== p.autoAttach;
+            this.params.autoAttach = p.autoAttach;
+            this.params.showToolTip = p.showToolTip;
+            this.ctx.customModelProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
+            return updated;
+        }
+
+        unregister() {
+            this.ctx.customModelProperties.unregister(ConfalPyramidsProvider.descriptor.name);
+            this.ctx.managers.lociLabels.removeProvider(this.labelConfalPyramids);
+
+            this.ctx.representation.structure.registry.remove(ConfalPyramidsRepresentationProvider);
+            this.ctx.representation.structure.themes.colorThemeRegistry.remove(ConfalPyramidsColorThemeProvider);
+
+            this.ctx.builders.structure.representation.unregisterPreset(DnatcoConfalPyramidsPreset);
+        }
+    },
+    params: () => ({
+        autoAttach: PD.Boolean(true),
+        showToolTip: PD.Boolean(true)
+    })
+});

+ 186 - 0
src/extensions/dnatco/confal-pyramids/color.ts

@@ -0,0 +1,186 @@
+/**
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Michal Malý <michal.maly@ibt.cas.cz>
+ * @author Jiří Černý <jiri.cerny@ibt.cas.cz>
+ */
+
+import { ConfalPyramids, ConfalPyramidsProvider } from './property';
+import { ConfalPyramidsTypes as CPT } from './types';
+import { Location } from '../../../mol-model/location';
+import { CustomProperty } from '../../../mol-model-props/common/custom-property';
+import { ColorTheme } from '../../../mol-theme/color';
+import { ThemeDataContext } from '../../../mol-theme/theme';
+import { Color } from '../../../mol-util/color';
+import { ParamDefinition as PD } from '../../../mol-util/param-definition';
+import { TableLegend } from '../../../mol-util/legend';
+import { iterableToArray } from '../../../mol-data/util';
+
+const DefaultColor = Color(0xCCCCCC);
+const Description = 'Assigns colors to confal pyramids';
+const ErrorColor = Color(0xFFA10A);
+
+type ConformerClasses = 'A' | 'B' | 'BII' | 'miB' | 'Z' | 'IC' | 'OPN' | 'SYN' | 'N';
+
+const ColorMapping: ReadonlyMap<ConformerClasses, Color> = new Map([
+    ['A', Color(0xFFC1C1)],
+    ['B', Color(0xC8CFFF)],
+    ['BII', Color(0x0059DA)],
+    ['miB', Color(0x3BE8FB)],
+    ['Z',  Color(0x01F60E)],
+    ['IC', Color(0xFA5CFB)],
+    ['OPN', Color(0xE90000)],
+    ['SYN', Color(0xFFFF01)],
+    ['N', Color(0xF2F2F2)],
+]);
+
+const NtCToClasses: ReadonlyMap<string, [ConformerClasses, ConformerClasses]> = new Map([
+    ['NANT', ['N', 'N']],
+    ['AA00', ['A', 'A']],
+    ['AA02', ['A', 'A']],
+    ['AA03', ['A', 'A']],
+    ['AA04', ['A', 'A']],
+    ['AA08', ['A', 'A']],
+    ['AA09', ['A', 'A']],
+    ['AA01', ['A', 'A']],
+    ['AA05', ['A', 'A']],
+    ['AA06', ['A', 'A']],
+    ['AA10', ['A', 'A']],
+    ['AA11', ['A', 'A']],
+    ['AA07', ['A', 'A']],
+    ['AA12', ['A', 'A']],
+    ['AA13', ['A', 'A']],
+    ['AB01', ['A', 'B']],
+    ['AB02', ['A', 'B']],
+    ['AB03', ['A', 'B']],
+    ['AB04', ['A', 'B']],
+    ['AB05', ['A', 'B']],
+    ['BA01', ['B', 'A']],
+    ['BA05', ['B', 'A']],
+    ['BA09', ['B', 'A']],
+    ['BA08', ['BII', 'A']],
+    ['BA10', ['B', 'A']],
+    ['BA13', ['BII', 'A']],
+    ['BA16', ['BII', 'A']],
+    ['BA17', ['BII', 'A']],
+    ['BB00', ['B', 'B']],
+    ['BB01', ['B', 'B']],
+    ['BB17', ['B', 'B']],
+    ['BB02', ['B', 'B']],
+    ['BB03', ['B', 'B']],
+    ['BB11', ['B', 'B']],
+    ['BB16', ['B', 'B']],
+    ['BB04', ['B', 'BII']],
+    ['BB05', ['B', 'BII']],
+    ['BB07', ['BII', 'BII']],
+    ['BB08', ['BII', 'BII']],
+    ['BB10', ['miB', 'miB']],
+    ['BB12', ['miB', 'miB']],
+    ['BB13', ['miB', 'miB']],
+    ['BB14', ['miB', 'miB']],
+    ['BB15', ['miB', 'miB']],
+    ['BB20', ['miB', 'miB']],
+    ['IC01', ['IC', 'IC']],
+    ['IC02', ['IC', 'IC']],
+    ['IC03', ['IC', 'IC']],
+    ['IC04', ['IC', 'IC']],
+    ['IC05', ['IC', 'IC']],
+    ['IC06', ['IC', 'IC']],
+    ['IC07', ['IC', 'IC']],
+    ['OP01', ['OPN', 'OPN']],
+    ['OP02', ['OPN', 'OPN']],
+    ['OP03', ['OPN', 'OPN']],
+    ['OP04', ['OPN', 'OPN']],
+    ['OP05', ['OPN', 'OPN']],
+    ['OP06', ['OPN', 'OPN']],
+    ['OP07', ['OPN', 'OPN']],
+    ['OP08', ['OPN', 'OPN']],
+    ['OP09', ['OPN', 'OPN']],
+    ['OP10', ['OPN', 'OPN']],
+    ['OP11', ['OPN', 'OPN']],
+    ['OP12', ['OPN', 'OPN']],
+    ['OP13', ['OPN', 'OPN']],
+    ['OP14', ['OPN', 'OPN']],
+    ['OP15', ['OPN', 'OPN']],
+    ['OP16', ['OPN', 'OPN']],
+    ['OP17', ['OPN', 'OPN']],
+    ['OP18', ['OPN', 'OPN']],
+    ['OP19', ['OPN', 'OPN']],
+    ['OP20', ['OPN', 'OPN']],
+    ['OP21', ['OPN', 'OPN']],
+    ['OP22', ['OPN', 'OPN']],
+    ['OP23', ['OPN', 'OPN']],
+    ['OP24', ['OPN', 'OPN']],
+    ['OP25', ['OPN', 'OPN']],
+    ['OP26', ['OPN', 'OPN']],
+    ['OP27', ['OPN', 'OPN']],
+    ['OP28', ['OPN', 'OPN']],
+    ['OP29', ['OPN', 'OPN']],
+    ['OP30', ['OPN', 'OPN']],
+    ['OP31', ['OPN', 'OPN']],
+    ['OPS1', ['OPN', 'OPN']],
+    ['OP1S', ['OPN', 'SYN']],
+    ['AAS1', ['SYN', 'A']],
+    ['AB1S', ['A', 'SYN']],
+    ['AB2S', ['A', 'SYN']],
+    ['BB1S', ['B', 'SYN']],
+    ['BB2S', ['B', 'SYN']],
+    ['BBS1', ['SYN', 'B']],
+    ['ZZ01', ['Z', 'Z']],
+    ['ZZ02', ['Z', 'Z']],
+    ['ZZ1S', ['Z', 'SYN']],
+    ['ZZ2S', ['Z', 'SYN']],
+    ['ZZS1', ['SYN', 'Z']],
+    ['ZZS2', ['SYN', 'Z']],
+]);
+
+function getConformerColor(ntc: string, useLower: boolean): Color {
+    const item = NtCToClasses.get(ntc);
+    if (!item) return ErrorColor;
+    return ColorMapping.get(useLower ? item[1] : item[0]) ?? ErrorColor;
+}
+
+export const ConfalPyramidsColorThemeParams = {};
+export type ConfalPyramidsColorThemeParams = typeof ConfalPyramidsColorThemeParams
+export function getConfalPyramidsColorThemeParams(ctx: ThemeDataContext) {
+    return ConfalPyramidsColorThemeParams; // TODO return copy
+}
+
+export function ConfalPyramidsColorTheme(ctx: ThemeDataContext, props: PD.Values<ConfalPyramidsColorThemeParams>): ColorTheme<ConfalPyramidsColorThemeParams> {
+    function color(location: Location, isSecondary: boolean): Color {
+        if (CPT.isLocation(location)) {
+            const { pyramid, isLower } = location.data;
+            return getConformerColor(pyramid.NtC, isLower);
+        }
+
+        return DefaultColor;
+    }
+
+    return {
+        factory: ConfalPyramidsColorTheme,
+        granularity: 'group',
+        color,
+        props,
+        description: Description,
+        legend: TableLegend(iterableToArray(ColorMapping.entries()).map(([conformer, color]) => {
+            return [conformer, color] as [string, Color];
+        }).concat([
+            [ 'Error', ErrorColor ],
+            [ 'Unknown', DefaultColor ]
+        ]))
+    };
+}
+
+export const ConfalPyramidsColorThemeProvider: ColorTheme.Provider<ConfalPyramidsColorThemeParams, 'confal-pyramids'> = {
+    name: 'confal-pyramids',
+    label: 'Confal Pyramids',
+    category: ColorTheme.Category.Residue,
+    factory: ConfalPyramidsColorTheme,
+    getParams: getConfalPyramidsColorThemeParams,
+    defaultValues: PD.getDefaultValues(ConfalPyramidsColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models.some(m => ConfalPyramids.isApplicable(m)),
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ConfalPyramidsProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
+        detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ConfalPyramidsProvider.descriptor, false)
+    }
+};

+ 172 - 0
src/extensions/dnatco/confal-pyramids/property.ts

@@ -0,0 +1,172 @@
+/**
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Michal Malý <michal.maly@ibt.cas.cz>
+ * @author Jiří Černý <jiri.cerny@ibt.cas.cz>
+ */
+
+import { ConfalPyramidsTypes as CPT } from './types';
+import { Column, Table } from '../../../mol-data/db';
+import { toTable } from '../../../mol-io/reader/cif/schema';
+import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
+import { Model } from '../../../mol-model/structure';
+import { CustomProperty } from '../../../mol-model-props/common/custom-property';
+import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
+import { PropertyWrapper } from '../../../mol-model-props/common/wrapper';
+import { ParamDefinition as PD } from '../../../mol-util/param-definition';
+import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
+
+export type ConfalPyramids = PropertyWrapper<CPT.PyramidsData | undefined >;
+
+export namespace ConfalPyramids {
+    export const Schema = {
+        ndb_struct_ntc_step: {
+            id: Column.Schema.int,
+            name: Column.Schema.str,
+            PDB_model_number: Column.Schema.int,
+            label_entity_id_1: Column.Schema.int,
+            label_asym_id_1: Column.Schema.str,
+            label_seq_id_1: Column.Schema.int,
+            label_comp_id_1: Column.Schema.str,
+            label_alt_id_1: Column.Schema.str,
+            label_entity_id_2: Column.Schema.int,
+            label_asym_id_2: Column.Schema.str,
+            label_seq_id_2: Column.Schema.int,
+            label_comp_id_2: Column.Schema.str,
+            label_alt_id_2: Column.Schema.str,
+            auth_asym_id_1: Column.Schema.str,
+            auth_seq_id_1: Column.Schema.int,
+            auth_asym_id_2: Column.Schema.str,
+            auth_seq_id_2: Column.Schema.int,
+            PDB_ins_code_1: Column.Schema.str,
+            PDB_ins_code_2: Column.Schema.str,
+        },
+        ndb_struct_ntc_step_summary: {
+            step_id: Column.Schema.int,
+            assigned_CANA: Column.Schema.str,
+            assigned_NtC: Column.Schema.str,
+            confal_score: Column.Schema.int,
+            euclidean_distance_NtC_ideal: Column.Schema.float,
+            cartesian_rmsd_closest_NtC_representative: Column.Schema.float,
+            closest_CANA: Column.Schema.str,
+            closest_NtC: Column.Schema.str,
+            closest_step_golden: Column.Schema.str
+        }
+    };
+    export type Schema = typeof Schema;
+
+    export async function fromCif(ctx: CustomProperty.Context, model: Model, props: ConfalPyramidsProps): Promise<CustomProperty.Data<ConfalPyramids>> {
+        const info = PropertyWrapper.createInfo();
+        const data = getCifData(model);
+        if (data === undefined) return { value: { info, data: undefined } };
+
+        const fromCif = createPyramidsFromCif(model, data.steps, data.stepsSummary);
+        return { value: { info, data: fromCif } };
+    }
+
+    function getCifData(model: Model) {
+        if (!MmcifFormat.is(model.sourceData)) throw new Error('Data format must be mmCIF');
+        if (!hasNdbStructNtcCategories(model)) return undefined;
+        return {
+            steps: toTable(Schema.ndb_struct_ntc_step, model.sourceData.data.frame.categories.ndb_struct_ntc_step),
+            stepsSummary: toTable(Schema.ndb_struct_ntc_step_summary, model.sourceData.data.frame.categories.ndb_struct_ntc_step_summary)
+        };
+    }
+
+    function hasNdbStructNtcCategories(model: Model): boolean {
+        if (!MmcifFormat.is(model.sourceData)) return false;
+        const names = (model.sourceData).data.frame.categoryNames;
+        return names.includes('ndb_struct_ntc_step') && names.includes('ndb_struct_ntc_step_summary');
+    }
+
+    export function isApplicable(model?: Model): boolean {
+        return !!model && hasNdbStructNtcCategories(model);
+    }
+}
+
+export const ConfalPyramidsParams = {};
+export type ConfalPyramidsParams = typeof ConfalPyramidsParams;
+export type ConfalPyramidsProps = PD.Values<ConfalPyramidsParams>;
+
+export const ConfalPyramidsProvider: CustomModelProperty.Provider<ConfalPyramidsParams, ConfalPyramids> = CustomModelProperty.createProvider({
+    label: 'Confal Pyramids',
+    descriptor: CustomPropertyDescriptor({
+        name: 'confal_pyramids',
+    }),
+    type: 'static',
+    defaultParams: ConfalPyramidsParams,
+    getParams: (data: Model) => ConfalPyramidsParams,
+    isApplicable: (data: Model) => ConfalPyramids.isApplicable(data),
+    obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<ConfalPyramidsProps>) => {
+        const p = { ...PD.getDefaultValues(ConfalPyramidsParams), ...props };
+        return ConfalPyramids.fromCif(ctx, data, p);
+    }
+});
+
+type StepsSummaryTable = Table<typeof ConfalPyramids.Schema.ndb_struct_ntc_step_summary>;
+
+function createPyramidsFromCif(model: Model,
+    steps: Table<typeof ConfalPyramids.Schema.ndb_struct_ntc_step>,
+    stepsSummary: StepsSummaryTable): CPT.PyramidsData {
+    const pyramids = new Array<CPT.Pyramid>();
+    const names = new Map<string, number>();
+    const locations = new Array<CPT.Location>();
+    let hasMultipleModels = false;
+
+    const {
+        id, PDB_model_number, name,
+        auth_asym_id_1, auth_seq_id_1, label_comp_id_1, label_alt_id_1, PDB_ins_code_1,
+        auth_asym_id_2, auth_seq_id_2, label_comp_id_2, label_alt_id_2, PDB_ins_code_2,
+        _rowCount } = steps;
+
+    if (_rowCount !== stepsSummary._rowCount) throw new Error('Inconsistent mmCIF data');
+
+    for (let i = 0; i < _rowCount; i++) {
+        const model_num = PDB_model_number.value(i);
+        if (model_num !== model.modelNum) {
+            hasMultipleModels = true;
+            continue; // We are only interested in data for the current model
+        }
+
+        const { _NtC, _confal_score } = getNtCAndConfalScore(id.value(i), i, stepsSummary);
+
+        const pyramid = {
+            PDB_model_number: model_num,
+            name: name.value(i),
+            auth_asym_id_1: auth_asym_id_1.value(i),
+            auth_seq_id_1: auth_seq_id_1.value(i),
+            label_comp_id_1: label_comp_id_1.value(i),
+            label_alt_id_1: label_alt_id_1.value(i),
+            PDB_ins_code_1: PDB_ins_code_1.value(i),
+            auth_asym_id_2: auth_asym_id_2.value(i),
+            auth_seq_id_2: auth_seq_id_2.value(i),
+            label_comp_id_2: label_comp_id_2.value(i),
+            label_alt_id_2: label_alt_id_2.value(i),
+            PDB_ins_code_2: PDB_ins_code_2.value(i),
+            confal_score: _confal_score,
+            NtC: _NtC
+        };
+
+        pyramids.push(pyramid);
+        names.set(pyramid.name, pyramids.length - 1);
+
+        locations.push(CPT.Location(pyramid, false));
+        locations.push(CPT.Location(pyramid, true));
+    }
+
+    return { pyramids, names, locations, hasMultipleModels };
+}
+
+function getNtCAndConfalScore(id: number, i: number, stepsSummary: StepsSummaryTable) {
+    const { step_id, confal_score, assigned_NtC } = stepsSummary;
+
+    // Assume that step_ids in ntc_step_summary are in the same order as steps in ntc_step
+    for (let j = i; j < stepsSummary._rowCount; j++) {
+        if (id === step_id.value(j)) return { _NtC: assigned_NtC.value(j), _confal_score: confal_score.value(j) };
+    }
+    // Safety net for cases where the previous assumption is not met
+    for (let j = 0; j < i; j++) {
+        if (id === step_id.value(j)) return { _NtC: assigned_NtC.value(j), _confal_score: confal_score.value(j) };
+    }
+    throw new Error('Inconsistent mmCIF data');
+}

+ 186 - 0
src/extensions/dnatco/confal-pyramids/representation.ts

@@ -0,0 +1,186 @@
+/**
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Michal Malý <michal.maly@ibt.cas.cz>
+ * @author Jiří Černý <jiri.cerny@ibt.cas.cz>
+ */
+
+import { ConfalPyramids, ConfalPyramidsProvider } from './property';
+import { ConfalPyramidsUtil } from './util';
+import { ConfalPyramidsTypes as CPT } from './types';
+import { Interval } from '../../../mol-data/int';
+import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
+import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
+import { PickingId } from '../../../mol-geo/geometry/picking';
+import { PrimitiveBuilder } from '../../../mol-geo/primitive/primitive';
+import { LocationIterator } from '../../../mol-geo/util/location-iterator';
+import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
+import { EmptyLoci, Loci } from '../../../mol-model/loci';
+import { Structure, StructureProperties, Unit } from '../../../mol-model/structure';
+import { CustomProperty } from '../../../mol-model-props/common/custom-property';
+import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
+import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder, UnitsRepresentation } from '../../../mol-repr/structure/representation';
+import { StructureGroup, UnitsMeshParams, UnitsMeshVisual, UnitsVisual } from '../../../mol-repr/structure/units-visual';
+import { VisualUpdateState } from '../../../mol-repr/util';
+import { VisualContext } from '../../../mol-repr/visual';
+import { getAltResidueLociFromId } from '../../../mol-repr/structure/visual/util/common';
+import { ParamDefinition as PD } from '../../../mol-util/param-definition';
+import { Theme, ThemeRegistryContext } from '../../../mol-theme/theme';
+import { NullLocation } from '../../../mol-model/location';
+
+const t = Mat4.identity();
+const w = Vec3.zero();
+const mp = Vec3.zero();
+
+function calcMidpoint(mp: Vec3, v: Vec3, w: Vec3) {
+    Vec3.sub(mp, v, w);
+    Vec3.scale(mp, mp, 0.5);
+    Vec3.add(mp, mp, w);
+}
+
+function shiftVertex(vec: Vec3, ref: Vec3, scale: number) {
+    Vec3.sub(w, vec, ref);
+    Vec3.scale(w, w, scale);
+    Vec3.add(vec, vec, w);
+}
+
+const ConfalPyramidsMeshParams = {
+    ...UnitsMeshParams
+};
+type ConfalPyramidsMeshParams = typeof ConfalPyramidsMeshParams;
+
+function createConfalPyramidsIterator(structureGroup: StructureGroup): LocationIterator {
+    const { structure, group } = structureGroup;
+    const instanceCount = group.units.length;
+
+    const prop = ConfalPyramidsProvider.get(structure.model).value;
+    if (prop === undefined || prop.data === undefined) {
+        return LocationIterator(0, 1, () => NullLocation);
+    }
+
+    const { locations } = prop.data;
+
+    const getLocation = (groupIndex: number, instanceIndex: number) => {
+        if (locations.length <= groupIndex) return NullLocation;
+        return locations[groupIndex];
+    };
+    return LocationIterator(locations.length, instanceCount, getLocation);
+}
+
+function createConfalPyramidsMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<ConfalPyramidsMeshParams>, mesh?: Mesh) {
+    if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
+
+    const prop = ConfalPyramidsProvider.get(structure.model).value;
+    if (prop === undefined || prop.data === undefined) return Mesh.createEmpty(mesh);
+
+    const { pyramids } = prop.data;
+    if (pyramids.length === 0) return Mesh.createEmpty(mesh);
+
+    const mb = MeshBuilder.createState(512, 512, mesh);
+
+    const handler = (pyramid: CPT.Pyramid, first: ConfalPyramidsUtil.FirstResidueAtoms, second: ConfalPyramidsUtil.SecondResidueAtoms, firsLocIndex: number, secondLocIndex: number) => {
+        if (firsLocIndex === -1 || secondLocIndex === -1)
+            throw new Error('Invalid location index');
+
+        const scale = (pyramid.confal_score - 20.0) / 100.0;
+        const O3 = first.O3.pos;
+        const OP1 = second.OP1.pos; const OP2 = second.OP2.pos; const O5 = second.O5.pos; const P = second.P.pos;
+
+        shiftVertex(O3, P, scale);
+        shiftVertex(OP1, P, scale);
+        shiftVertex(OP2, P, scale);
+        shiftVertex(O5, P, scale);
+        calcMidpoint(mp, O3, O5);
+
+        mb.currentGroup = firsLocIndex;
+        let pb = PrimitiveBuilder(3);
+        /* Upper part (for first residue in step) */
+        pb.add(O3, OP1, OP2);
+        pb.add(O3, mp, OP1);
+        pb.add(O3, OP2, mp);
+        MeshBuilder.addPrimitive(mb, t, pb.getPrimitive());
+
+        /* Lower part (for second residue in step */
+        mb.currentGroup = secondLocIndex;
+        pb = PrimitiveBuilder(3);
+        pb.add(mp, O5, OP1);
+        pb.add(mp, OP2, O5);
+        pb.add(O5, OP2, OP1);
+        MeshBuilder.addPrimitive(mb, t, pb.getPrimitive());
+    };
+
+    const walker = new ConfalPyramidsUtil.UnitWalker(structure, unit, handler);
+    walker.walk();
+
+    return MeshBuilder.getMesh(mb);
+}
+
+function getConfalPyramidLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
+    const { groupId, objectId, instanceId } = pickingId;
+    if (objectId !== id) return EmptyLoci;
+
+    const { structure } = structureGroup;
+
+    const unit = structureGroup.group.units[instanceId];
+    if (!Unit.isAtomic(unit)) return EmptyLoci;
+
+    const prop = ConfalPyramidsProvider.get(structure.model).value;
+    if (prop === undefined || prop.data === undefined) return EmptyLoci;
+
+    const { locations } = prop.data;
+
+    if (locations.length <= groupId) return EmptyLoci;
+    const altId = StructureProperties.atom.label_alt_id(CPT.toElementLocation(locations[groupId]));
+    const rI = unit.residueIndex[locations[groupId].element.element];
+
+    return getAltResidueLociFromId(structure, unit, rI, altId);
+}
+
+function eachConfalPyramid(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
+    return false; // TODO: Implement me
+}
+
+function ConfalPyramidsVisual(materialId: number): UnitsVisual<ConfalPyramidsMeshParams> {
+    return UnitsMeshVisual<ConfalPyramidsMeshParams>({
+        defaultProps: PD.getDefaultValues(ConfalPyramidsMeshParams),
+        createGeometry: createConfalPyramidsMesh,
+        createLocationIterator: createConfalPyramidsIterator,
+        getLoci: getConfalPyramidLoci,
+        eachLocation: eachConfalPyramid,
+        setUpdateState: (state: VisualUpdateState, newProps: PD.Values<ConfalPyramidsMeshParams>, currentProps: PD.Values<ConfalPyramidsMeshParams>) => {
+        }
+    }, materialId);
+}
+const ConfalPyramidsVisuals = {
+    'confal-pyramids-symbol': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, UnitsMeshParams>) => UnitsRepresentation('Confal Pyramids Symbol Mesh', ctx, getParams, ConfalPyramidsVisual),
+};
+
+export const ConfalPyramidsParams = {
+    ...UnitsMeshParams
+};
+export type ConfalPyramidsParams = typeof ConfalPyramidsParams;
+export function getConfalPyramidsParams(ctx: ThemeRegistryContext, structure: Structure) {
+    return PD.clone(ConfalPyramidsParams);
+}
+
+export type ConfalPyramidsRepresentation = StructureRepresentation<ConfalPyramidsParams>;
+export function ConfalPyramidsRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, ConfalPyramidsParams>): ConfalPyramidsRepresentation {
+    const repr = Representation.createMulti('Confal Pyramids', ctx, getParams, StructureRepresentationStateBuilder, ConfalPyramidsVisuals as unknown as Representation.Def<Structure, ConfalPyramidsParams>);
+    return repr;
+}
+
+export const ConfalPyramidsRepresentationProvider = StructureRepresentationProvider({
+    name: 'confal-pyramids',
+    label: 'Confal Pyramids',
+    description: 'Displays schematic depiction of conformer classes and confal values',
+    factory: ConfalPyramidsRepresentation,
+    getParams: getConfalPyramidsParams,
+    defaultValues: PD.getDefaultValues(ConfalPyramidsParams),
+    defaultColorTheme: { name: 'confal-pyramids' },
+    defaultSizeTheme: { name: 'uniform' },
+    isApplicable: (structure: Structure) => structure.models.some(m => ConfalPyramids.isApplicable(m)),
+    ensureCustomProperties: {
+        attach: (ctx: CustomProperty.Context, structure: Structure) => ConfalPyramidsProvider.attach(ctx, structure.model, void 0, true),
+        detach: (data) => ConfalPyramidsProvider.ref(data.model, false),
+    }
+});

+ 60 - 0
src/extensions/dnatco/confal-pyramids/types.ts

@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Michal Malý <michal.maly@ibt.cas.cz>
+ * @author Jiří Černý <jiri.cerny@ibt.cas.cz>
+ */
+
+import { DataLocation } from '../../../mol-model/location';
+import { ElementIndex, Structure, StructureElement, Unit } from '../../../mol-model/structure';
+
+export namespace ConfalPyramidsTypes {
+    export type Pyramid = {
+        PDB_model_number: number,
+        name: string,
+        auth_asym_id_1: string,
+        auth_seq_id_1: number,
+        label_comp_id_1: string,
+        label_alt_id_1: string,
+        PDB_ins_code_1: string,
+        auth_asym_id_2: string,
+        auth_seq_id_2: number,
+        label_comp_id_2: string,
+        label_alt_id_2: string,
+        PDB_ins_code_2: string,
+        confal_score: number,
+        NtC: string
+    }
+
+    export interface PyramidsData {
+        pyramids: Array<Pyramid>,
+        names: Map<string, number>,
+        locations: Array<Location>,
+        hasMultipleModels: boolean
+    }
+
+    export interface LocationData {
+        readonly pyramid: Pyramid
+        readonly isLower: boolean;
+    }
+
+    export interface Element {
+        structure: Structure;
+        unit: Unit.Atomic;
+        element: ElementIndex;
+    }
+
+    export interface Location extends DataLocation<LocationData, Element> {}
+
+    export function Location(pyramid: Pyramid, isLower: boolean, structure?: Structure, unit?: Unit.Atomic, element?: ElementIndex) {
+        return DataLocation('pyramid', { pyramid, isLower }, { structure: structure as any, unit: unit as any, element: element as any });
+    }
+
+    export function isLocation(x: any): x is Location {
+        return !!x && x.kind === 'data-location' && x.tag === 'pyramid';
+    }
+
+    export function toElementLocation(location: Location) {
+        return StructureElement.Location.create(location.element.structure, location.element.unit, location.element.element);
+    }
+}

+ 299 - 0
src/extensions/dnatco/confal-pyramids/util.ts

@@ -0,0 +1,299 @@
+/**
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Michal Malý <michal.maly@ibt.cas.cz>
+ * @author Jiří Černý <jiri.cerny@ibt.cas.cz>
+ */
+
+import { ConfalPyramidsProvider } from './property';
+import { ConfalPyramidsTypes as CPT } from './types';
+import { OrderedSet, Segmentation } from '../../../mol-data/int';
+import { Vec3 } from '../../../mol-math/linear-algebra';
+import { ChainIndex, ElementIndex, ResidueIndex, Structure, StructureElement, StructureProperties, Unit } from '../../../mol-model/structure';
+
+export namespace ConfalPyramidsUtil {
+    type Residue = Segmentation.Segment<ResidueIndex>;
+
+    export type AtomInfo = {
+        pos: Vec3,
+        index: ElementIndex,
+        fakeAltId: string,
+    };
+
+    export type FirstResidueAtoms = {
+        O3: AtomInfo,
+    };
+
+    export type SecondResidueAtoms = {
+        OP1: AtomInfo,
+        OP2: AtomInfo,
+        O5: AtomInfo,
+        P: AtomInfo,
+    };
+
+    type ResidueInfo = {
+        PDB_model_num: number,
+        asym_id: string,
+        auth_asym_id: string,
+        seq_id: number,
+        auth_seq_id: number,
+        comp_id: string,
+        alt_id: string,
+        ins_code: string,
+    };
+
+    export type Handler = (pyramid: CPT.Pyramid, first: FirstResidueAtoms, second: SecondResidueAtoms, firstLocIndex: number, secondLocIndex: number) => void;
+
+    function residueInfoFromLocation(loc: StructureElement.Location): ResidueInfo {
+        return {
+            PDB_model_num: StructureProperties.unit.model_num(loc),
+            asym_id: StructureProperties.chain.label_asym_id(loc),
+            auth_asym_id: StructureProperties.chain.auth_asym_id(loc),
+            seq_id: StructureProperties.residue.label_seq_id(loc),
+            auth_seq_id: StructureProperties.residue.auth_seq_id(loc),
+            comp_id: StructureProperties.atom.label_comp_id(loc),
+            alt_id: StructureProperties.atom.label_alt_id(loc),
+            ins_code: StructureProperties.residue.pdbx_PDB_ins_code(loc)
+        };
+    }
+
+    export function hasMultipleModels(unit: Unit.Atomic): boolean {
+        const prop = ConfalPyramidsProvider.get(unit.model).value;
+        if (prop === undefined || prop.data === undefined) throw new Error('No custom properties data');
+        return prop.data.hasMultipleModels;
+    }
+
+    function getPossibleAltIdsIndices(eIFirst: ElementIndex, eILast: ElementIndex, structure: Structure, unit: Unit.Atomic): string[] {
+        const loc = StructureElement.Location.create(structure, unit, -1 as ElementIndex);
+
+        const uIFirst = OrderedSet.indexOf(unit.elements, eIFirst);
+        const uILast = OrderedSet.indexOf(unit.elements, eILast);
+
+        const possibleAltIds: string[] = [];
+        for (let uI = uIFirst; uI <= uILast; uI++) {
+            loc.element = unit.elements[uI];
+            const altId = StructureProperties.atom.label_alt_id(loc);
+            if (altId !== '' && !possibleAltIds.includes(altId)) possibleAltIds.push(altId);
+        }
+
+        return possibleAltIds;
+    }
+
+    function getPossibleAltIdsResidue(residue: Residue, structure: Structure, unit: Unit.Atomic): string[] {
+        return getPossibleAltIdsIndices(unit.elements[residue.start], unit.elements[residue.end - 1], structure, unit);
+    }
+
+    class Utility {
+        protected getPyramidByName(name: string): { pyramid: CPT.Pyramid | undefined, index: number } {
+            const index = this.data.names.get(name);
+            if (index === undefined) return { pyramid: undefined, index: -1 };
+
+            return { pyramid: this.data.pyramids[index], index };
+        }
+
+        protected stepToName(entry_id: string, modelNum: number, locFirst: StructureElement.Location, locSecond: StructureElement.Location, fakeAltId_1: string, fakeAltId_2: string) {
+            const first = residueInfoFromLocation(locFirst);
+            const second = residueInfoFromLocation(locSecond);
+            const model_id = this.hasMultipleModels ? `-m${modelNum}` : '';
+            const alt_id_1 =  fakeAltId_1 !== '' ? `.${fakeAltId_1}` : (first.alt_id.length ? `.${first.alt_id}` : '');
+            const alt_id_2 = fakeAltId_2 !== '' ? `.${fakeAltId_2}` : (second.alt_id.length ? `.${second.alt_id}` : '');
+            const ins_code_1 = first.ins_code.length ? `.${first.ins_code}` : '';
+            const ins_code_2 = second.ins_code.length ? `.${second.ins_code}` : '';
+
+            return `${entry_id}${model_id}_${first.auth_asym_id}_${first.comp_id}${alt_id_1}_${first.auth_seq_id}${ins_code_1}_${second.comp_id}${alt_id_2}_${second.auth_seq_id}${ins_code_2}`;
+        }
+
+        constructor(unit: Unit.Atomic) {
+            const prop = ConfalPyramidsProvider.get(unit.model).value;
+            if (prop === undefined || prop.data === undefined) throw new Error('No custom properties data');
+
+            this.data = prop.data;
+            this.hasMultipleModels = hasMultipleModels(unit);
+
+            this.entryId = unit.model.entryId.toLowerCase();
+            this.modelNum = unit.model.modelNum;
+        }
+
+        protected readonly data: CPT.PyramidsData
+        protected readonly hasMultipleModels: boolean;
+        protected readonly entryId: string;
+        protected readonly modelNum: number;
+    }
+
+    export class UnitWalker extends Utility {
+        private getAtomIndices(names: string[], residue: Residue): ElementIndex[] {
+            let rI = residue.start;
+            const rILast = residue.end - 1;
+            const indices: ElementIndex[] = [];
+
+            for (; rI !== rILast; rI++) {
+                const eI = this.unit.elements[rI];
+                const loc = StructureElement.Location.create(this.structure, this.unit, eI);
+                const thisName = StructureProperties.atom.label_atom_id(loc);
+                if (names.includes(thisName)) indices.push(eI);
+            }
+
+            if (indices.length === 0)
+                throw new Error(`Element ${name} not found on residue ${residue.index}`);
+
+            return indices;
+        }
+
+        private getAtomPositions(indices: ElementIndex[]): Vec3[] {
+            const pos = this.unit.conformation.invariantPosition;
+            const positions: Vec3[] = [];
+
+            for (const eI of indices) {
+                const v = Vec3.zero();
+                pos(eI, v);
+                positions.push(v);
+            }
+
+            return positions;
+        }
+
+        private handleStep(firstAtoms: FirstResidueAtoms[], secondAtoms: SecondResidueAtoms[]) {
+            const modelNum = this.hasMultipleModels ? this.modelNum : -1;
+            let ok = false;
+
+            const firstLoc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex);
+            const secondLoc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex);
+            for (let i = 0; i < firstAtoms.length; i++) {
+                const first = firstAtoms[i];
+                for (let j = 0; j < secondAtoms.length; j++) {
+                    const second = secondAtoms[j];
+                    firstLoc.element = first.O3.index;
+                    secondLoc.element = second.OP1.index;
+
+                    const name = this.stepToName(this.entryId, modelNum, firstLoc, secondLoc, first.O3.fakeAltId, second.OP1.fakeAltId);
+                    const { pyramid, index } = this.getPyramidByName(name);
+                    if (pyramid !== undefined) {
+                        const setLoc = (loc: CPT.Location, eI: ElementIndex) => {
+                            loc.element.structure = this.structure;
+                            loc.element.unit = this.unit;
+                            loc.element.element = eI;
+                        };
+
+                        const locIndex = index * 2;
+                        setLoc(this.data.locations[locIndex], firstLoc.element);
+                        setLoc(this.data.locations[locIndex + 1], secondLoc.element);
+                        this.handler(pyramid, first, second, locIndex, locIndex + 1);
+                        ok = true;
+                    }
+                }
+            }
+
+            if (!ok) throw new Error('Bogus step');
+        }
+
+        private processFirstResidue(residue: Residue, possibleAltIds: string[]) {
+            const indO3 = this.getAtomIndices(['O3\'', 'O3*'], residue);
+            const posO3 = this.getAtomPositions(indO3);
+
+            const altPos: FirstResidueAtoms[] = [
+                { O3: { pos: posO3[0], index: indO3[0], fakeAltId: '' } }
+            ];
+
+            for (let i = 1; i < indO3.length; i++) {
+                altPos.push({ O3: { pos: posO3[i], index: indO3[i], fakeAltId: '' } });
+            }
+
+            if (altPos.length === 1 && possibleAltIds.length > 1) {
+                /* We have some alternate positions on the residue but O3 does not have any - fake them */
+                altPos[0].O3.fakeAltId = possibleAltIds[0];
+
+                for (let i = 1; i < possibleAltIds.length; i++)
+                    altPos.push({ O3: { pos: posO3[0], index: indO3[0], fakeAltId: possibleAltIds[i] } });
+            }
+
+            return altPos;
+        }
+
+        private processSecondResidue(residue: Residue, possibleAltIds: string[]) {
+            const indOP1 = this.getAtomIndices(['OP1'], residue);
+            const indOP2 = this.getAtomIndices(['OP2'], residue);
+            const indO5 = this.getAtomIndices(['O5\'', 'O5*'], residue);
+            const indP = this.getAtomIndices(['P'], residue);
+
+            const posOP1 = this.getAtomPositions(indOP1);
+            const posOP2 = this.getAtomPositions(indOP2);
+            const posO5 = this.getAtomPositions(indO5);
+            const posP = this.getAtomPositions(indP);
+
+            const infoOP1: AtomInfo[] = [];
+            /* We use OP1 as "pivotal" atom. There is no specific reason
+             * to pick OP1, it is as good a choice as any other atom
+             */
+            if (indOP1.length === 1 && possibleAltIds.length > 1) {
+                /* No altIds on OP1, fake them */
+                for (const altId of possibleAltIds)
+                    infoOP1.push({ pos: posOP1[0], index: indOP1[0], fakeAltId: altId });
+            } else {
+                for (let i = 0; i < indOP1.length; i++)
+                    infoOP1.push({ pos: posOP1[i], index: indOP1[i], fakeAltId: '' });
+            }
+
+            const mkInfo = (i: number, indices: ElementIndex[], positions: Vec3[], altId: string) => {
+                if (i >= indices.length) {
+                    const last = indices.length - 1;
+                    return { pos: positions[last], index: indices[last], fakeAltId: altId };
+                }
+
+                return { pos: positions[i], index: indices[i], fakeAltId: altId };
+            };
+
+            const altPos: SecondResidueAtoms[] = [];
+            for (let i = 0; i < infoOP1.length; i++) {
+                const altId = infoOP1[i].fakeAltId;
+
+                const OP2 = mkInfo(i, indOP2, posOP2, altId);
+                const O5 = mkInfo(i, indO5, posO5, altId);
+                const P = mkInfo(i, indP, posP, altId);
+
+                altPos.push({ OP1: infoOP1[i], OP2, O5, P });
+            }
+
+            return altPos;
+        }
+
+        private step(residue: Residue): { firstAtoms: FirstResidueAtoms[], secondAtoms: SecondResidueAtoms[] } {
+            const firstPossibleAltIds = getPossibleAltIdsResidue(residue, this.structure, this.unit);
+            const firstAtoms = this.processFirstResidue(residue, firstPossibleAltIds);
+
+            residue = this.residueIt.move();
+
+            const secondPossibleAltIds = getPossibleAltIdsResidue(residue, this.structure, this.unit);
+            const secondAtoms = this.processSecondResidue(residue, secondPossibleAltIds);
+
+            return { firstAtoms, secondAtoms };
+        }
+
+        walk() {
+            while (this.chainIt.hasNext) {
+                this.residueIt.setSegment(this.chainIt.move());
+
+                let residue = this.residueIt.move();
+                while (this.residueIt.hasNext) {
+                    try {
+                        const { firstAtoms, secondAtoms } = this.step(residue);
+
+                        this.handleStep(firstAtoms, secondAtoms);
+                    } catch (error) {
+                        /* Skip and move along */
+                        residue = this.residueIt.move();
+                    }
+                }
+            }
+        }
+
+        constructor(private structure: Structure, private unit: Unit.Atomic, private handler: Handler) {
+            super(unit);
+
+            this.chainIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, unit.elements);
+            this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
+        }
+
+        private chainIt: Segmentation.SegmentIterator<ChainIndex>;
+        private residueIt: Segmentation.SegmentIterator<ResidueIndex>;
+    }
+}

+ 8 - 0
src/extensions/dnatco/index.ts

@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Michal Malý <michal.maly@ibt.cas.cz>
+ * @author Jiří Černý <jiri.cerny@ibt.cas.cz>
+ */
+
+export { DnatcoConfalPyramids } from './confal-pyramids/behavior';

+ 2 - 2
src/extensions/rcsb/assembly-symmetry/behavior.ts

@@ -181,8 +181,8 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
         }
 
         const assemblySymmetry = await tryCreateAssemblySymmetry(plugin, structureCell);
-        const globalThemeName = assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
-        const preset = await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName }, plugin);
+        const colorTheme = assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
+        const preset = await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
 
         return { components: preset.components, representations: { ...preset.representations, assemblySymmetry } };
     }

File diff suppressed because it is too large
+ 5767 - 1303
src/extensions/rcsb/graphql/types.ts


+ 13 - 12
src/extensions/rcsb/validation-report/behavior.ts

@@ -309,15 +309,15 @@ export const ValidationReportGeometryQualityPreset = StructureRepresentationPres
     params: () => StructureRepresentationPresetProvider.CommonParams,
     async apply(ref, params, plugin) {
         const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
-        const model = structureCell?.obj?.data.model;
-        if (!structureCell || !model) return {};
+        const structure = structureCell?.obj?.data;
+        if (!structureCell || !structure) return {};
 
         await plugin.runTask(Task.create('Validation Report', async runtime => {
-            await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
+            await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, structure.models[0]);
         }));
 
         const colorTheme = GeometryQualityColorThemeProvider.name as any;
-        const { components, representations } = await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin);
+        const { components, representations } = await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
 
         const clashes = await plugin.builders.structure.tryCreateComponentFromExpression(structureCell, hasClash.expression, 'clashes', { label: 'Clashes' });
 
@@ -329,6 +329,7 @@ export const ValidationReportGeometryQualityPreset = StructureRepresentationPres
         }
 
         await update.commit({ revertOnError: true });
+
         return { components: { ...components, clashes }, representations: { ...representations, clashesBallAndStick, clashesRepr } };
     }
 });
@@ -345,15 +346,15 @@ export const ValidationReportDensityFitPreset = StructureRepresentationPresetPro
     params: () => StructureRepresentationPresetProvider.CommonParams,
     async apply(ref, params, plugin) {
         const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
-        const model = structureCell?.obj?.data.model;
-        if (!structureCell || !model) return {};
+        const structure = structureCell?.obj?.data;
+        if (!structureCell || !structure) return {};
 
         await plugin.runTask(Task.create('Validation Report', async runtime => {
-            await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
+            await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, structure.models[0]);
         }));
 
         const colorTheme = DensityFitColorThemeProvider.name as any;
-        return await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin);
+        return await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
     }
 });
 
@@ -369,14 +370,14 @@ export const ValidationReportRandomCoilIndexPreset = StructureRepresentationPres
     params: () => StructureRepresentationPresetProvider.CommonParams,
     async apply(ref, params, plugin) {
         const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
-        const model = structureCell?.obj?.data.model;
-        if (!structureCell || !model) return {};
+        const structure = structureCell?.obj?.data;
+        if (!structureCell || !structure) return {};
 
         await plugin.runTask(Task.create('Validation Report', async runtime => {
-            await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
+            await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, structure.models[0]);
         }));
 
         const colorTheme = RandomCoilIndexColorThemeProvider.name as any;
-        return await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin);
+        return await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
     }
 });

+ 3 - 3
src/extensions/rcsb/validation-report/representation.ts

@@ -15,7 +15,7 @@ import { Interval } from '../../../mol-data/int';
 import { RepresentationContext, RepresentationParamsGetter, Representation } from '../../../mol-repr/representation';
 import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
 import { VisualContext } from '../../../mol-repr/visual';
-import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from '../../../mol-repr/structure/visual/util/link';
+import { createLinkCylinderMesh, LinkCylinderParams, LinkStyle } from '../../../mol-repr/structure/visual/util/link';
 import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../../../mol-repr/structure/units-visual';
 import { VisualUpdateState } from '../../../mol-repr/util';
 import { LocationIterator } from '../../../mol-geo/util/location-iterator';
@@ -50,7 +50,7 @@ function createIntraUnitClashCylinderMesh(ctx: VisualContext, unit: Unit, struct
             pos(elements[a[edgeIndex]], posA);
             pos(elements[b[edgeIndex]], posB);
         },
-        style: (edgeIndex: number) => LinkCylinderStyle.Disk,
+        style: (edgeIndex: number) => LinkStyle.Disk,
         radius: (edgeIndex: number) => magnitude[edgeIndex] * sizeFactor,
     };
 
@@ -163,7 +163,7 @@ function createInterUnitClashCylinderMesh(ctx: VisualContext, structure: Structu
             uA.conformation.position(uA.elements[b.indexA], posA);
             uB.conformation.position(uB.elements[b.indexB], posB);
         },
-        style: (edgeIndex: number) => LinkCylinderStyle.Disk,
+        style: (edgeIndex: number) => LinkStyle.Disk,
         radius: (edgeIndex: number) => edges[edgeIndex].props.magnitude * sizeFactor
     };
 

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

@@ -304,6 +304,7 @@ namespace Canvas3D {
             if (allCommited) {
                 resolveCameraReset();
                 if (forceDrawAfterAllCommited) {
+                    if (debugHelper.isEnabled) debugHelper.update();
                     draw(true);
                     forceDrawAfterAllCommited = false;
                 }

+ 6 - 0
src/mol-data/db/column.ts

@@ -220,7 +220,13 @@ namespace Column {
         } else {
             for (let i = 0, _i = c.rowCount; i < _i; i++) array[offset + i] = c.value(i);
         }
+    }
 
+    export function isIdentity<T extends number>(c: Column<T>) {
+        for (let i = 0, _i = c.rowCount; i < _i; i++) {
+            if (i !== c.value(i)) return false;
+        }
+        return true;
     }
 }
 

+ 1 - 1
src/mol-geo/geometry/lines/lines-builder.ts

@@ -83,7 +83,7 @@ export namespace LinesBuilder {
                 const gb = ChunkedArray.compact(groups, true) as Float32Array;
                 const sb = ChunkedArray.compact(starts, true) as Float32Array;
                 const eb = ChunkedArray.compact(ends, true) as Float32Array;
-                return Lines.create(mb, ib, gb, sb, eb, indices.elementCount / 2);
+                return Lines.create(mb, ib, gb, sb, eb, indices.elementCount / 2, lines);
             }
         };
     }

+ 5 - 5
src/mol-io/reader/cif/schema/bird.ts

@@ -1,14 +1,14 @@
 /**
  * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
- * Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
+ * Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.326, IHM 1.09, CARB draft.
  *
  * @author molstar/ciftools package
  */
 
 import { Database, Column } from '../../../../mol-data/db';
 
-import Schema = Column.Schema
+import Schema = Column.Schema;
 
 const str = Schema.str;
 const float = Schema.float;
@@ -66,7 +66,7 @@ export const BIRD_Schema = {
         /**
          * Broadly defines the function of the entity.
          */
-        class: Aliased<'Antagonist' | 'Antibiotic' | 'Anticancer' | 'Anticoagulant' | 'Antifungal' | 'Antiinflammatory' | 'Antimicrobial' | 'Antineoplastic' | 'Antiparasitic' | 'Antiretroviral' | 'Anthelmintic' | 'Antithrombotic' | 'Antitumor' | 'Antiviral' | 'CASPASE inhibitor' | 'Chaperone binding' | 'Enzyme inhibitor' | 'Growth factor' | 'Immunosuppressant' | 'Inhibitor' | 'Lantibiotic' | 'Metabolism' | 'Metal transport' | 'Oxidation-reduction' | 'Receptor' | 'Thrombin inhibitor' | 'Trypsin inhibitor' | 'Toxin' | 'Unknown' | 'Anticoagulant, Antithrombotic' | 'Antibiotic, Antimicrobial' | 'Antibiotic, Anthelmintic' | 'Antibiotic, Antineoplastic' | 'Antimicrobial, Antiretroviral' | 'Antimicrobial, Antitumor' | 'Antimicrobial, Antiparasitic, Antibiotic' | 'Thrombin inhibitor, Trypsin inhibitor'>(str),
+        class: Aliased<'Antagonist' | 'Antibiotic' | 'Anticancer' | 'Anticoagulant' | 'Antifungal' | 'Antigen' | 'Antiinflammatory' | 'Antimicrobial' | 'Antineoplastic' | 'Antiparasitic' | 'Antiretroviral' | 'Anthelmintic' | 'Antithrombotic' | 'Antitumor' | 'Antiviral' | 'CASPASE inhibitor' | 'Chaperone binding' | 'Enzyme inhibitor' | 'Drug delivery' | 'Glycan component' | 'Growth factor' | 'Immunosuppressant' | 'Inducer' | 'Inhibitor' | 'Lantibiotic' | 'Metabolism' | 'Metal transport' | 'Nutrient' | 'Oxidation-reduction' | 'Protein binding' | 'Receptor' | 'Substrate analog' | 'Thrombin inhibitor' | 'Trypsin inhibitor' | 'Toxin' | 'Unknown' | 'Water retention' | 'Anticoagulant, Antithrombotic' | 'Antibiotic, Antimicrobial' | 'Antibiotic, Anthelmintic' | 'Antibiotic, Antineoplastic' | 'Antimicrobial, Antiretroviral' | 'Antimicrobial, Antitumor' | 'Antimicrobial, Antiparasitic, Antibiotic' | 'Thrombin inhibitor, Trypsin inhibitor'>(str),
         /**
          * Evidence for the assignment of _pdbx_reference_molecule.class
          */
@@ -356,7 +356,7 @@ export const BIRD_Schema = {
         /**
          * The type of the polymer.
          */
-        type: Aliased<'peptide-like' | 'nucleic-acid-like' | 'polysaccharide-like'>(str),
+        type: Aliased<'peptide-like' | 'nucleic-acid-like' | 'polysaccharide-like' | 'oligosaccharide'>(str),
         /**
          * The database code for this source information
          */
@@ -496,4 +496,4 @@ export const BIRD_Schema = {
 };
 
 export type BIRD_Schema = typeof BIRD_Schema;
-export interface BIRD_Database extends Database<BIRD_Schema> {}
+export interface BIRD_Database extends Database<BIRD_Schema> {};

+ 3 - 3
src/mol-io/reader/cif/schema/ccd.ts

@@ -1,14 +1,14 @@
 /**
  * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
- * Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
+ * Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.326, IHM 1.09, CARB draft.
  *
  * @author molstar/ciftools package
  */
 
 import { Database, Column } from '../../../../mol-data/db';
 
-import Schema = Column.Schema
+import Schema = Column.Schema;
 
 const str = Schema.str;
 const float = Schema.float;
@@ -397,4 +397,4 @@ export const CCD_Schema = {
 };
 
 export type CCD_Schema = typeof CCD_Schema;
-export interface CCD_Database extends Database<CCD_Schema> {}
+export interface CCD_Database extends Database<CCD_Schema> {};

+ 2 - 2
src/mol-io/reader/cif/schema/cif-core.ts

@@ -8,7 +8,7 @@
 
 import { Database, Column } from '../../../../mol-data/db';
 
-import Schema = Column.Schema
+import Schema = Column.Schema;
 
 const int = Schema.int;
 const float = Schema.float;
@@ -792,4 +792,4 @@ export const CifCore_Aliases = {
 };
 
 export type CifCore_Schema = typeof CifCore_Schema;
-export interface CifCore_Database extends Database<CifCore_Schema> {}
+export interface CifCore_Database extends Database<CifCore_Schema> {};

+ 9 - 2
src/mol-io/reader/cif/schema/mmcif-extras.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>
@@ -23,4 +23,11 @@ export const mmCIF_chemCompBond_schema = {
     ...mmCIF_Schema.chem_comp_bond,
     /** Indicates if the bond entry was taken from the protonation variant dictionary */
     molstar_protonation_variant: Column.Schema.Str()
-};
+};
+
+/** Has `type` extended with 'Ion' and 'Lipid' */
+export const mmCIF_chemComp_schema = {
+    ...mmCIF_Schema.chem_comp,
+    type: Column.Schema.Aliased<mmCIF_Schema['chem_comp']['type']['T'] | 'Ion' | 'Lipid'>(Column.Schema.str)
+};
+export type mmCIF_chemComp_schema = typeof mmCIF_chemComp_schema;

+ 186 - 186
src/mol-io/reader/cif/schema/mmcif.ts

@@ -1,14 +1,14 @@
 /**
  * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
- * Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.324, IHM 1.09, CARB draft.
+ * Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.326, IHM 1.09, CARB draft.
  *
  * @author molstar/ciftools package
  */
 
 import { Database, Column } from '../../../../mol-data/db';
 
-import Schema = Column.Schema
+import Schema = Column.Schema;
 
 const str = Schema.str;
 const int = Schema.int;
@@ -2559,7 +2559,7 @@ export const mmCIF_Schema = {
         /**
          * This data item contains the descriptor type.
          */
-        type: Aliased<'LINUCS' | 'Glycam Condensed Sequence' | 'Glycam Condensed Core Sequence'>(str),
+        type: Aliased<'LINUCS' | 'Glycam Condensed Sequence' | 'Glycam Condensed Core Sequence' | 'WURCS'>(str),
         /**
          * This data item contains the name of the program
          * or library used to compute the descriptor.
@@ -2617,6 +2617,188 @@ export const mmCIF_Schema = {
          */
         ordinal: int,
     },
+    /**
+     * Data items in the PDBX_ENTITY_BRANCH_LIST category specify the list
+     * of monomers in a branched entity.  Allowance is made for the possibility
+     * of microheterogeneity in a sample by allowing a given sequence
+     * number to be correlated with more than one monomer ID. The
+     * corresponding ATOM_SITE entries should reflect this
+     * heterogeneity.
+     */
+    pdbx_entity_branch_list: {
+        /**
+         * This data item is a pointer to _entity.id in the ENTITY category.
+         */
+        entity_id: str,
+        /**
+         * A flag to indicate whether this monomer in the entity is
+         * heterogeneous in sequence.
+         */
+        hetero: Aliased<'no' | 'n' | 'yes' | 'y'>(str),
+        /**
+         * This data item is a pointer to _chem_comp.id in the CHEM_COMP
+         * category.
+         */
+        comp_id: str,
+        /**
+         * The value pair  _pdbx_entity_branch_list.num and _pdbx_entity_branch_list.comp_id
+         * must uniquely identify a record in the PDBX_ENTITY_BRANCH_LIST list.
+         */
+        num: int,
+    },
+    /**
+     * Data items in the PDBX_ENTITY_BRANCH_LINK category give details about
+     * the linkages between components within a branched entity.
+     */
+    pdbx_entity_branch_link: {
+        /**
+         * The value of _pdbx_entity_branch_link.link_id uniquely identifies
+         * linkages within the branched entity.
+         */
+        link_id: int,
+        /**
+         * A description of special aspects of this linkage.
+         */
+        details: str,
+        /**
+         * The entity id for this branched entity.
+         *
+         * This data item is a pointer to _pdbx_entity_branch_list.entity_id
+         * in the PDBX_ENTITY_BRANCH_LIST category.
+         */
+        entity_id: str,
+        /**
+         * The component number for the first component making the linkage.
+         *
+         * This data item is a pointer to _pdbx_entity_branch_list.num
+         * in the PDBX_ENTITY_BRANCH_LIST category.
+         */
+        entity_branch_list_num_1: int,
+        /**
+         * The component number for the second component making the linkage.
+         *
+         * This data item is a pointer to _pdbx_entity_branch_list.num
+         * in the PDBX_ENTITY_BRANCH_LIST category.
+         */
+        entity_branch_list_num_2: int,
+        /**
+         * The component identifier for the first component making the linkage.
+         *
+         * This data item is a pointer to _pdbx_entity_branch_list.comp_id
+         * in the PDBX_ENTITY_BRANCH_LIST category.
+         */
+        comp_id_1: str,
+        /**
+         * The component identifier for the second component making the linkage.
+         *
+         * This data item is a pointer to _pdbx_entity_branch_list.comp_id
+         * in the PDBX_ENTITY_BRANCH_LIST category.
+         */
+        comp_id_2: str,
+        /**
+         * The atom identifier/name for the first atom making the linkage.
+         */
+        atom_id_1: str,
+        /**
+         * The leaving atom identifier/name bonded to the first atom making the linkage.
+         */
+        leaving_atom_id_1: str,
+        /**
+         * The chiral configuration of the first atom making the linkage.
+         */
+        atom_stereo_config_1: Aliased<'R' | 'S' | 'N'>(str),
+        /**
+         * The atom identifier/name for the second atom making the linkage.
+         */
+        atom_id_2: str,
+        /**
+         * The leaving atom identifier/name bonded to the second atom making the linkage.
+         */
+        leaving_atom_id_2: str,
+        /**
+         * The chiral configuration of the second atom making the linkage.
+         */
+        atom_stereo_config_2: Aliased<'R' | 'S' | 'N'>(str),
+        /**
+         * The bond order target for the chemical linkage.
+         */
+        value_order: Aliased<'sing' | 'doub' | 'trip' | 'quad' | 'arom' | 'poly' | 'delo' | 'pi'>(str),
+    },
+    /**
+     * Data items in the PDBX_ENTITY_BRANCH category specify the list
+     * of branched entities and the type.
+     */
+    pdbx_entity_branch: {
+        /**
+         * The entity id for this branched entity.
+         *
+         * This data item is a pointer to _entity.id
+         */
+        entity_id: str,
+        /**
+         * The type of this branched oligosaccharide.
+         */
+        type: Aliased<'oligosaccharide'>(str),
+    },
+    /**
+     * The PDBX_BRANCH_SCHEME category provides residue level nomenclature
+     * mapping for branch chain entities.
+     */
+    pdbx_branch_scheme: {
+        /**
+         * This data item is a pointer to _entity.id in the ENTITY category.
+         */
+        entity_id: str,
+        /**
+         * A flag to indicate whether this monomer in the entity is
+         * heterogeneous in sequence.
+         */
+        hetero: Aliased<'no' | 'n' | 'yes' | 'y'>(str),
+        /**
+         * Pointer to _atom_site.label_asym_id.
+         */
+        asym_id: str,
+        /**
+         * This data item is a pointer to _atom_site.label_comp_id in the
+         * PDBX_ENTITY_BRANCH_LIST category.
+         */
+        mon_id: str,
+        /**
+         * This data item is a pointer to _pdbx_entity_branch_list.num in the
+         * PDBX_ENTITY_BRANCH_LIST category.
+         */
+        num: int,
+        /**
+         * This data item is a pointer to _atom_site.auth_asym_id in the
+         * ATOM_SITE category.
+         */
+        pdb_asym_id: str,
+        /**
+         * This data item is a pointer to _atom_site.auth_seq_id in the
+         * ATOM_SITE category.
+         */
+        pdb_seq_num: str,
+        /**
+         * This data item is a pointer to _atom_site.auth_comp_id in the
+         * ATOM_SITE category.
+         */
+        pdb_mon_id: str,
+        /**
+         * This data item is a pointer to _atom_site.pdbx_auth_asym_id in the
+         * ATOM_SITE category.
+         */
+        auth_asym_id: str,
+        /**
+         * This data item is a pointer to _atom_site.pdbx_auth_seq_id in the
+         * ATOM_SITE category.
+         */
+        auth_seq_num: str,
+        /**
+         * This data item is a pointer to _atom_site.pdbx_auth_comp_id in the
+         * ATOM_SITE category.
+         */
+        auth_mon_id: str,
+    },
     /**
      * Data items in the IHM_STARTING_MODEL_DETAILS category records the
      * details about structural models used as starting inputs in
@@ -4476,188 +4658,6 @@ export const mmCIF_Schema = {
          */
         dataset_list_id: int,
     },
-    /**
-     * Data items in the PDBX_ENTITY_BRANCH_LIST category specify the list
-     * of monomers in a branched entity.  Allowance is made for the possibility
-     * of microheterogeneity in a sample by allowing a given sequence
-     * number to be correlated with more than one monomer ID. The
-     * corresponding ATOM_SITE entries should reflect this
-     * heterogeneity.
-     */
-    pdbx_entity_branch_list: {
-        /**
-         * This data item is a pointer to _entity.id in the ENTITY category.
-         */
-        entity_id: str,
-        /**
-         * A flag to indicate whether this monomer in the entity is
-         * heterogeneous in sequence.
-         */
-        hetero: Aliased<'no' | 'n' | 'yes' | 'y'>(str),
-        /**
-         * This data item is a pointer to _chem_comp.id in the CHEM_COMP
-         * category.
-         */
-        comp_id: str,
-        /**
-         * The value pair  _pdbx_entity_branch_list.num and _pdbx_entity_branch_list.comp_id
-         * must uniquely identify a record in the PDBX_ENTITY_BRANCH_LIST list.
-         */
-        num: int,
-    },
-    /**
-     * Data items in the PDBX_ENTITY_BRANCH_LINK category give details about
-     * the linkages between components within a branched entity.
-     */
-    pdbx_entity_branch_link: {
-        /**
-         * The value of _pdbx_entity_branch_link.link_id uniquely identifies
-         * linkages within the branched entity.
-         */
-        link_id: int,
-        /**
-         * A description of special aspects of this linkage.
-         */
-        details: str,
-        /**
-         * The entity id for this branched entity.
-         *
-         * This data item is a pointer to _pdbx_entity_branch_list.entity_id
-         * in the PDBX_ENTITY_BRANCH_LIST category.
-         */
-        entity_id: str,
-        /**
-         * The component number for the first component making the linkage.
-         *
-         * This data item is a pointer to _pdbx_entity_branch_list.num
-         * in the PDBX_ENTITY_BRANCH_LIST category.
-         */
-        entity_branch_list_num_1: int,
-        /**
-         * The component number for the second component making the linkage.
-         *
-         * This data item is a pointer to _pdbx_entity_branch_list.num
-         * in the PDBX_ENTITY_BRANCH_LIST category.
-         */
-        entity_branch_list_num_2: int,
-        /**
-         * The component identifier for the first component making the linkage.
-         *
-         * This data item is a pointer to _pdbx_entity_branch_list.comp_id
-         * in the PDBX_ENTITY_BRANCH_LIST category.
-         */
-        comp_id_1: str,
-        /**
-         * The component identifier for the second component making the linkage.
-         *
-         * This data item is a pointer to _pdbx_entity_branch_list.comp_id
-         * in the PDBX_ENTITY_BRANCH_LIST category.
-         */
-        comp_id_2: str,
-        /**
-         * The atom identifier/name for the first atom making the linkage.
-         */
-        atom_id_1: str,
-        /**
-         * The leaving atom identifier/name bonded to the first atom making the linkage.
-         */
-        leaving_atom_id_1: str,
-        /**
-         * The chiral configuration of the first atom making the linkage.
-         */
-        atom_stereo_config_1: Aliased<'R' | 'S' | 'N'>(str),
-        /**
-         * The atom identifier/name for the second atom making the linkage.
-         */
-        atom_id_2: str,
-        /**
-         * The leaving atom identifier/name bonded to the second atom making the linkage.
-         */
-        leaving_atom_id_2: str,
-        /**
-         * The chiral configuration of the second atom making the linkage.
-         */
-        atom_stereo_config_2: Aliased<'R' | 'S' | 'N'>(str),
-        /**
-         * The bond order target for the chemical linkage.
-         */
-        value_order: Aliased<'sing' | 'doub' | 'trip' | 'quad' | 'arom' | 'poly' | 'delo' | 'pi'>(str),
-    },
-    /**
-     * Data items in the PDBX_ENTITY_BRANCH category specify the list
-     * of branched entities and the type.
-     */
-    pdbx_entity_branch: {
-        /**
-         * The entity id for this branched entity.
-         *
-         * This data item is a pointer to _entity.id
-         */
-        entity_id: str,
-        /**
-         * The type of this branched oligosaccharide.
-         */
-        type: Aliased<'oligosaccharide'>(str),
-    },
-    /**
-     * The PDBX_BRANCH_SCHEME category provides residue level nomenclature
-     * mapping for branch chain entities.
-     */
-    pdbx_branch_scheme: {
-        /**
-         * This data item is a pointer to _entity.id in the ENTITY category.
-         */
-        entity_id: str,
-        /**
-         * A flag to indicate whether this monomer in the entity is
-         * heterogeneous in sequence.
-         */
-        hetero: Aliased<'no' | 'n' | 'yes' | 'y'>(str),
-        /**
-         * Pointer to _atom_site.label_asym_id.
-         */
-        asym_id: str,
-        /**
-         * This data item is a pointer to _atom_site.label_comp_id in the
-         * PDBX_ENTITY_BRANCH_LIST category.
-         */
-        mon_id: str,
-        /**
-         * This data item is a pointer to _pdbx_entity_branch_list.num in the
-         * PDBX_ENTITY_BRANCH_LIST category.
-         */
-        num: int,
-        /**
-         * This data item is a pointer to _atom_site.auth_asym_id in the
-         * ATOM_SITE category.
-         */
-        pdb_asym_id: str,
-        /**
-         * This data item is a pointer to _atom_site.auth_seq_id in the
-         * ATOM_SITE category.
-         */
-        pdb_seq_num: str,
-        /**
-         * This data item is a pointer to _atom_site.auth_comp_id in the
-         * ATOM_SITE category.
-         */
-        pdb_mon_id: str,
-        /**
-         * This data item is a pointer to _atom_site.pdbx_auth_asym_id in the
-         * ATOM_SITE category.
-         */
-        auth_asym_id: str,
-        /**
-         * This data item is a pointer to _atom_site.pdbx_auth_seq_id in the
-         * ATOM_SITE category.
-         */
-        auth_seq_num: str,
-        /**
-         * This data item is a pointer to _atom_site.pdbx_auth_comp_id in the
-         * ATOM_SITE category.
-         */
-        auth_mon_id: str,
-    },
     /**
      * PDBX_CHEM_COMP_RELATED describes the relationship between two chemical components.
      */
@@ -4682,4 +4682,4 @@ export const mmCIF_Schema = {
 };
 
 export type mmCIF_Schema = typeof mmCIF_Schema;
-export interface mmCIF_Database extends Database<mmCIF_Schema> {}
+export interface mmCIF_Database extends Database<mmCIF_Schema> {};

+ 7 - 7
src/mol-io/reader/dcd/parser.ts

@@ -1,5 +1,5 @@
 /**
- * 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 Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -22,13 +22,15 @@ export interface DcdHeader {
 }
 
 export interface DcdFrame {
-    readonly cell: Cell
     readonly elementCount: number
 
     // positions
     readonly x: ArrayLike<number>
     readonly y: ArrayLike<number>
     readonly z: ArrayLike<number>
+
+    // optional cell
+    readonly cell?: Cell
 }
 
 export interface DcdFile {
@@ -147,17 +149,16 @@ export function _parseDcd(data: Uint8Array): DcdFile {
     }
 
     // frames
-
     const natom = header.NATOM;
     const natom4 = natom * 4;
 
     for (let i = 0, n = header.NSET; i < n; ++i) {
-        const frame: Mutable<DcdFrame> = Object.create({
-            elementCount: natom
-        });
+        const frame: Mutable<DcdFrame> = Object.create(null);
+        frame.elementCount = natom;
 
         if (extraBlock) {
             nextPos += 4; // block start
+            // TODO this is not standardized and we need to add heuristics to handle more variants
             // cell: A, alpha, B, beta, gamma, C (doubles)
             const size = Vec3.create(
                 dv.getFloat64(nextPos, ef),
@@ -199,7 +200,6 @@ export function _parseDcd(data: Uint8Array): DcdFile {
 
         frames.push(frame);
     }
-
     return { header, frames };
 }
 

+ 416 - 0
src/mol-io/reader/xtc/parser.ts

@@ -0,0 +1,416 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from NGL.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { RuntimeContext, Task } from '../../../mol-task';
+import { ReaderResult as Result } from '../result';
+
+export interface XtcFile {
+    frames: { count: number, x: Float32Array, y: Float32Array, z: Float32Array }[],
+    boxes: number[][],
+    times: number[],
+    timeOffset: number,
+    deltaTime: number
+}
+
+const MagicInts = new Uint32Array([
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 10, 12, 16, 20, 25, 32, 40, 50, 64,
+    80, 101, 128, 161, 203, 256, 322, 406, 512, 645, 812, 1024, 1290,
+    1625, 2048, 2580, 3250, 4096, 5060, 6501, 8192, 10321, 13003,
+    16384, 20642, 26007, 32768, 41285, 52015, 65536, 82570, 104031,
+    131072, 165140, 208063, 262144, 330280, 416127, 524287, 660561,
+    832255, 1048576, 1321122, 1664510, 2097152, 2642245, 3329021,
+    4194304, 5284491, 6658042, 8388607, 10568983, 13316085, 16777216
+]);
+const FirstIdx = 9;
+// const LastIdx = MagicInts.length
+
+namespace Decoder {
+    export function sizeOfInt(size: number) {
+        let num = 1;
+        let numOfBits = 0;
+        while (size >= num && numOfBits < 32) {
+            numOfBits++;
+            num <<= 1;
+        }
+        return numOfBits;
+    }
+
+    const _tmpBytes = new Uint8Array(32);
+
+    export function sizeOfInts(numOfInts: number, sizes: number[]) {
+        let numOfBytes = 1;
+        let numOfBits = 0;
+        _tmpBytes[0] = 1;
+        for (let i = 0; i < numOfInts; i++) {
+            let bytecnt;
+            let tmp = 0;
+            for (bytecnt = 0; bytecnt < numOfBytes; bytecnt++) {
+                tmp += _tmpBytes[bytecnt] * sizes[i];
+                _tmpBytes[bytecnt] = tmp & 0xff;
+                tmp >>= 8;
+            }
+            while (tmp !== 0) {
+                _tmpBytes[bytecnt++] = tmp & 0xff;
+                tmp >>= 8;
+            }
+            numOfBytes = bytecnt;
+        }
+        let num = 1;
+        numOfBytes--;
+        while (_tmpBytes[numOfBytes] >= num) {
+            numOfBits++;
+            num *= 2;
+        }
+        return numOfBits + numOfBytes * 8;
+    }
+
+    const _buffer = new ArrayBuffer(8 * 3);
+    export const buf = new Int32Array(_buffer);
+    const uint32view = new Uint32Array(_buffer);
+
+    export function decodeBits(cbuf: Uint8Array, offset: number, numOfBits1: number) {
+        let numOfBits = numOfBits1;
+        const mask = (1 << numOfBits) - 1;
+        let lastBB0 = uint32view[1];
+        let lastBB1 = uint32view[2];
+        let cnt = buf[0];
+        let num = 0;
+
+        while (numOfBits >= 8) {
+            lastBB1 = (lastBB1 << 8) | cbuf[offset + cnt++];
+            num |= (lastBB1 >> lastBB0) << (numOfBits - 8);
+            numOfBits -= 8;
+        }
+
+        if (numOfBits > 0) {
+            if (lastBB0 < numOfBits) {
+                lastBB0 += 8;
+                lastBB1 = (lastBB1 << 8) | cbuf[offset + cnt++];
+            }
+            lastBB0 -= numOfBits;
+            num |= (lastBB1 >> lastBB0) & ((1 << numOfBits) - 1);
+        }
+
+        num &= mask;
+        buf[0] = cnt;
+        buf[1] = lastBB0;
+        buf[2] = lastBB1;
+
+        return num;
+    }
+
+    function decodeByte(cbuf: Uint8Array, offset: number) {
+        // special version of decodeBits with numOfBits = 8
+
+        // const mask = 0xff; // (1 << 8) - 1;
+        // let lastBB0 = uint32view[1];
+        let lastBB1 = uint32view[2];
+        let cnt = buf[0];
+
+        lastBB1 = (lastBB1 << 8) | cbuf[offset + cnt];
+
+        buf[0] = cnt + 1;
+        // buf[1] = lastBB0;
+        buf[2] = lastBB1;
+
+        return (lastBB1 >> uint32view[1]) & 0xff;
+    }
+
+    const intBytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+    // new Int32Array(32);
+
+    export function decodeInts(cbuf: Uint8Array, offset: number, numOfBits1: number, sizes: number[], nums: number[]) {
+        let numOfBits = numOfBits1;
+        let numOfBytes = 0;
+
+        intBytes[0] = 0;
+        intBytes[1] = 0;
+        intBytes[2] = 0;
+        intBytes[3] = 0;
+
+        while (numOfBits > 8) {
+            // this is inversed??? why??? because of the endiannness???
+            intBytes[numOfBytes++] = decodeByte(cbuf, offset);
+            numOfBits -= 8;
+        }
+
+        if (numOfBits > 0) {
+            intBytes[numOfBytes++] = decodeBits(cbuf, offset, numOfBits);
+        }
+
+        for (let i = 2; i > 0; i--) {
+            let num = 0;
+            const s = sizes[i];
+            for (let j = numOfBytes - 1; j >= 0; j--) {
+                num = (num << 8) | intBytes[j];
+                const t = (num / s) | 0;
+                intBytes[j] = t;
+                num = num - t * s;
+            }
+            nums[i] = num;
+        }
+        nums[0] = intBytes[0] | (intBytes[1] << 8) | (intBytes[2] << 16) | (intBytes[3] << 24);
+    }
+}
+
+function undefinedError() {
+    throw new Error('(xdrfile error) Undefined error.');
+}
+
+async function parseInternal(ctx: RuntimeContext, data: Uint8Array) {
+    // https://github.com/gromacs/gromacs/blob/master/src/gromacs/fileio/xtcio.cpp
+    // https://github.com/gromacs/gromacs/blob/master/src/gromacs/fileio/libxdrf.cpp
+
+    const dv = new DataView(data.buffer, data.byteOffset);
+
+    const f: XtcFile = {
+        frames: [],
+        boxes: [],
+        times: [],
+        timeOffset: 0,
+        deltaTime: 0
+    };
+    const coordinates = f.frames;
+    const boxes = f.boxes;
+    const times = f.times;
+
+    const minMaxInt = [0, 0, 0, 0, 0, 0];
+    const sizeint = [0, 0, 0];
+    const bitsizeint = [0, 0, 0];
+    const sizesmall = [0, 0, 0];
+    const thiscoord = [0.1, 0.1, 0.1];
+    const prevcoord = [0.1, 0.1, 0.1];
+
+    let offset = 0;
+    const buf = Decoder.buf;
+    // const buf2 = new Uint32Array(buf.buffer);
+
+    while (true) {
+        let frameCoords: XtcFile['frames'][0];
+
+        // const magicnum = dv.getInt32(offset)
+        const natoms = dv.getInt32(offset + 4);
+        // const step = dv.getInt32(offset + 8)
+        offset += 12;
+
+        times.push(dv.getFloat32(offset));
+        offset += 4;
+
+        const box = new Float32Array(9);
+        for (let i = 0; i < 9; ++i) {
+            box[i] = dv.getFloat32(offset) * 10;
+            offset += 4;
+        }
+        boxes.push(box as unknown as number[]);
+
+        if (natoms <= 9) { // no compression
+            frameCoords = { count: natoms / 3, x: new Float32Array(natoms / 3), y: new Float32Array(natoms / 3), z: new Float32Array(natoms / 3) };
+            for (let i = 0; i < natoms / 3; ++i) {
+                frameCoords.x[i] = dv.getFloat32(offset);
+                frameCoords.y[i] = dv.getFloat32(offset);
+                frameCoords.z[i] = dv.getFloat32(offset);
+                offset += 4;
+            }
+        } else {
+            buf[0] = buf[1] = buf[2] = 0;
+            sizeint[0] = sizeint[1] = sizeint[2] = 0;
+            sizesmall[0] = sizesmall[1] = sizesmall[2] = 0;
+            bitsizeint[0] = bitsizeint[1] = bitsizeint[2] = 0;
+            thiscoord[0] = thiscoord[1] = thiscoord[2] = 0;
+            prevcoord[0] = prevcoord[1] = prevcoord[2] = 0;
+
+            frameCoords = { count: natoms, x: new Float32Array(natoms), y: new Float32Array(natoms), z: new Float32Array(natoms) };
+            let lfp = 0;
+
+            const lsize = dv.getInt32(offset);
+            offset += 4;
+            const precision = dv.getFloat32(offset);
+            offset += 4;
+
+            minMaxInt[0] = dv.getInt32(offset);
+            minMaxInt[1] = dv.getInt32(offset + 4);
+            minMaxInt[2] = dv.getInt32(offset + 8);
+            minMaxInt[3] = dv.getInt32(offset + 12);
+            minMaxInt[4] = dv.getInt32(offset + 16);
+            minMaxInt[5] = dv.getInt32(offset + 20);
+            sizeint[0] = minMaxInt[3] - minMaxInt[0] + 1;
+            sizeint[1] = minMaxInt[4] - minMaxInt[1] + 1;
+            sizeint[2] = minMaxInt[5] - minMaxInt[2] + 1;
+            offset += 24;
+
+            let bitsize;
+            if ((sizeint[0] | sizeint[1] | sizeint[2]) > 0xffffff) {
+                bitsizeint[0] = Decoder.sizeOfInt(sizeint[0]);
+                bitsizeint[1] = Decoder.sizeOfInt(sizeint[1]);
+                bitsizeint[2] = Decoder.sizeOfInt(sizeint[2]);
+                bitsize = 0; // flag the use of large sizes
+            } else {
+                bitsize = Decoder.sizeOfInts(3, sizeint);
+            }
+
+            let smallidx = dv.getInt32(offset);
+            offset += 4;
+
+            let tmpIdx = smallidx - 1;
+            tmpIdx = (FirstIdx > tmpIdx) ? FirstIdx : tmpIdx;
+            let smaller = (MagicInts[tmpIdx] / 2) | 0;
+            let smallnum = (MagicInts[smallidx] / 2) | 0;
+
+            sizesmall[0] = sizesmall[1] = sizesmall[2] = MagicInts[smallidx];
+
+            let adz = Math.ceil(dv.getInt32(offset) / 4) * 4;
+            offset += 4;
+
+            const invPrecision = 1.0 / precision;
+            let run = 0;
+            let i = 0;
+
+            // const buf8 = new Uint8Array(data.buffer, data.byteOffset + offset, 32 * 4); // 229...
+
+            thiscoord[0] = thiscoord[1] = thiscoord[2] = 0;
+
+            while (i < lsize) {
+                if (bitsize === 0) {
+                    thiscoord[0] = Decoder.decodeBits(data, offset, bitsizeint[0]);
+                    thiscoord[1] = Decoder.decodeBits(data, offset, bitsizeint[1]);
+                    thiscoord[2] = Decoder.decodeBits(data, offset, bitsizeint[2]);
+                } else {
+                    Decoder.decodeInts(data, offset, bitsize, sizeint, thiscoord);
+                }
+
+                i++;
+
+                thiscoord[0] += minMaxInt[0];
+                thiscoord[1] += minMaxInt[1];
+                thiscoord[2] += minMaxInt[2];
+
+                prevcoord[0] = thiscoord[0];
+                prevcoord[1] = thiscoord[1];
+                prevcoord[2] = thiscoord[2];
+
+                const flag = Decoder.decodeBits(data, offset, 1);
+                let isSmaller = 0;
+
+                if (flag === 1) {
+                    run = Decoder.decodeBits(data, offset, 5);
+                    isSmaller = run % 3;
+                    run -= isSmaller;
+                    isSmaller--;
+                }
+
+                // if ((lfp-ptrstart)+run > size3){
+                //   fprintf(stderr, "(xdrfile error) Buffer overrun during decompression.\n");
+                //   return 0;
+                // }
+
+                if (run > 0) {
+                    thiscoord[0] = thiscoord[1] = thiscoord[2] = 0;
+
+                    for (let k = 0; k < run; k += 3) {
+                        Decoder.decodeInts(data, offset, smallidx, sizesmall, thiscoord);
+                        i++;
+
+                        thiscoord[0] += prevcoord[0] - smallnum;
+                        thiscoord[1] += prevcoord[1] - smallnum;
+                        thiscoord[2] += prevcoord[2] - smallnum;
+
+                        if (k === 0) {
+                            // interchange first with second atom for
+                            // better compression of water molecules
+                            let tmpSwap = thiscoord[0];
+                            thiscoord[0] = prevcoord[0];
+                            prevcoord[0] = tmpSwap;
+
+                            tmpSwap = thiscoord[1];
+                            thiscoord[1] = prevcoord[1];
+                            prevcoord[1] = tmpSwap;
+
+                            tmpSwap = thiscoord[2];
+                            thiscoord[2] = prevcoord[2];
+                            prevcoord[2] = tmpSwap;
+
+                            frameCoords.x[lfp] = prevcoord[0] * invPrecision;
+                            frameCoords.y[lfp] = prevcoord[1] * invPrecision;
+                            frameCoords.z[lfp] = prevcoord[2] * invPrecision;
+                            lfp++;
+                        } else {
+                            prevcoord[0] = thiscoord[0];
+                            prevcoord[1] = thiscoord[1];
+                            prevcoord[2] = thiscoord[2];
+                        }
+                        frameCoords.x[lfp] = thiscoord[0] * invPrecision;
+                        frameCoords.y[lfp] = thiscoord[1] * invPrecision;
+                        frameCoords.z[lfp] = thiscoord[2] * invPrecision;
+                        lfp++;
+                    }
+                } else {
+                    frameCoords.x[lfp] = thiscoord[0] * invPrecision;
+                    frameCoords.y[lfp] = thiscoord[1] * invPrecision;
+                    frameCoords.z[lfp] = thiscoord[2] * invPrecision;
+                    lfp++;
+                }
+
+                smallidx += isSmaller;
+
+                if (isSmaller < 0) {
+                    smallnum = smaller;
+                    if (smallidx > FirstIdx) {
+                        smaller = (MagicInts[smallidx - 1] / 2) | 0;
+                    } else {
+                        smaller = 0;
+                    }
+                } else if (isSmaller > 0) {
+                    smaller = smallnum;
+                    smallnum = (MagicInts[smallidx] / 2) | 0;
+                }
+                sizesmall[0] = sizesmall[1] = sizesmall[2] = MagicInts[smallidx];
+
+                if (sizesmall[0] === 0 || sizesmall[1] === 0 || sizesmall[2] === 0) {
+                    undefinedError();
+                }
+            }
+            offset += adz;
+        }
+
+        for (let c = 0; c < natoms; c++) {
+            frameCoords.x[c] *= 10;
+            frameCoords.y[c] *= 10;
+            frameCoords.z[c] *= 10;
+        }
+
+        coordinates.push(frameCoords);
+
+        if (ctx.shouldUpdate) {
+            await ctx.update({ current: offset, max: data.length });
+        }
+
+        if (offset >= data.length) break;
+    }
+
+    if (times.length >= 1) {
+        f.timeOffset = times[0];
+    }
+    if (times.length >= 2) {
+        f.deltaTime = times[1] - times[0];
+    }
+
+    return f;
+}
+
+export function parseXtc(data: Uint8Array) {
+    return Task.create<Result<XtcFile>>('Parse XTC', async ctx => {
+        try {
+            ctx.update({ canAbort: true, message: 'Parsing trajectory...' });
+            const file = await parseInternal(ctx, data);
+            return Result.success(file);
+        } catch (e) {
+            return Result.error('' + e);
+        }
+    });
+}

+ 7 - 6
src/mol-math/geometry/boundary.ts

@@ -14,7 +14,7 @@ import { Box3D, Sphere3D } from '../geometry';
 const boundaryHelperCoarse = new BoundaryHelper('14');
 const boundaryHelperFine = new BoundaryHelper('98');
 function getBoundaryHelper(count: number) {
-    return count > 100_000 ? boundaryHelperCoarse : boundaryHelperFine;
+    return count > 10_000 ? boundaryHelperCoarse : boundaryHelperFine;
 }
 
 export type Boundary = { readonly box: Box3D, readonly sphere: Sphere3D }
@@ -22,16 +22,17 @@ export type Boundary = { readonly box: Box3D, readonly sphere: Sphere3D }
 export function getBoundary(data: PositionData): Boundary {
     const { x, y, z, radius, indices } = data;
     const p = Vec3();
+    const n = OrderedSet.size(indices);
 
-    const boundaryHelper = getBoundaryHelper(OrderedSet.size(indices));
+    const boundaryHelper = getBoundaryHelper(n);
     boundaryHelper.reset();
-    for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
+    for (let t = 0; t < n; t++) {
         const i = OrderedSet.getAt(indices, t);
         Vec3.set(p, x[i], y[i], z[i]);
         boundaryHelper.includePositionRadius(p, (radius && radius[i]) || 0);
     }
     boundaryHelper.finishedIncludeStep();
-    for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
+    for (let t = 0; t < n; t++) {
         const i = OrderedSet.getAt(indices, t);
         Vec3.set(p, x[i], y[i], z[i]);
         boundaryHelper.radiusPositionRadius(p, (radius && radius[i]) || 0);
@@ -39,9 +40,9 @@ export function getBoundary(data: PositionData): Boundary {
 
     const sphere = boundaryHelper.getSphere();
 
-    if (!radius && OrderedSet.size(indices) <= 98) {
+    if (!radius && Sphere3D.hasExtrema(sphere) && n <= sphere.extrema.length) {
         const extrema: Vec3[] = [];
-        for (let t = 0, _t = OrderedSet.size(indices); t < _t; t++) {
+        for (let t = 0; t < n; t++) {
             const i = OrderedSet.getAt(indices, t);
             extrema.push(Vec3.create(x[i], y[i], z[i]));
         }

+ 2 - 1
src/mol-model-formats/shape/ply.ts

@@ -38,7 +38,8 @@ function createPlyShapeParams(plyFile?: PlyFile) {
             if (
                 type === 'uchar' || type === 'uint8' ||
                 type === 'ushort' || type === 'uint16' ||
-                type === 'uint' || type === 'uint32'
+                type === 'uint' || type === 'uint32' ||
+                type === 'int'
             ) groupOptions.push([ name, name ]);
             if (type === 'uchar' || type === 'uint8') colorOptions.push([ name, name ]);
         }

+ 4 - 2
src/mol-model-formats/structure/basic/atomic.ts

@@ -60,6 +60,8 @@ function createHierarchyData(atom_site: AtomSite, sourceIndex: Column<number>, o
         label_atom_id: atom_site.label_atom_id,
         auth_atom_id: atom_site.auth_atom_id,
         label_alt_id: atom_site.label_alt_id,
+        label_comp_id: atom_site.label_comp_id,
+        auth_comp_id: atom_site.auth_comp_id,
         pdbx_formal_charge: atom_site.pdbx_formal_charge,
         sourceIndex
     });
@@ -74,8 +76,8 @@ function createHierarchyData(atom_site: AtomSite, sourceIndex: Column<number>, o
 
     // Fix possibly missing auth_/label_ columns
     substUndefinedColumn(atoms, 'label_atom_id', 'auth_atom_id');
+    substUndefinedColumn(atoms, 'label_comp_id', 'auth_comp_id');
     substUndefinedColumn(residues, 'label_seq_id', 'auth_seq_id');
-    substUndefinedColumn(residues, 'label_comp_id', 'auth_comp_id');
     substUndefinedColumn(chains, 'label_asym_id', 'auth_asym_id');
 
     return { atoms, residues, chains };
@@ -174,7 +176,7 @@ function getAtomicHierarchy(atom_site: AtomSite, sourceIndex: Column<number>, en
     };
 
     const index = getAtomicIndex(hierarchyData, entities, hierarchySegments);
-    const derived = getAtomicDerivedData(hierarchyData, index, chemicalComponentMap);
+    const derived = getAtomicDerivedData(hierarchyData, hierarchySegments, index, chemicalComponentMap);
     const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, index, derived };
     return { sameAsPrevious: false, hierarchy, chainOperatorMapping };
 }

+ 24 - 3
src/mol-model-formats/structure/basic/entities.ts

@@ -8,10 +8,10 @@
 import { Column, Table } from '../../../mol-data/db';
 import { Entities, EntitySubtype } from '../../../mol-model/structure/model/properties/common';
 import { getEntityType, getEntitySubtype } from '../../../mol-model/structure/model/types';
-import { ElementIndex, EntityIndex } from '../../../mol-model/structure/model';
+import { ElementIndex, EntityIndex, Model } from '../../../mol-model/structure/model';
 import { BasicData, BasicSchema, Entity } from './schema';
 
-export function getEntities(data: BasicData): Entities {
+export function getEntities(data: BasicData, properties: Model['properties']): Entities {
     let entityData: Entity;
 
     if (!data.entity.id.isDefined) {
@@ -118,5 +118,26 @@ export function getEntities(data: BasicData): Entities {
 
     //
 
-    return { data: entityData, subtype: subtypeColumn, getEntityIndex };
+    const prdIds: string[] = new Array(entityData._rowCount);
+    prdIds.fill('');
+
+    if (data.pdbx_molecule && data.pdbx_molecule.prd_id.isDefined) {
+        const { asym_id, prd_id, _rowCount } = data.pdbx_molecule;
+        for (let i = 0; i < _rowCount; ++i) {
+            const asymId = asym_id.value(i);
+            const entityId = properties.structAsymMap.get(asymId)?.entity_id;
+            if (entityId !== undefined) {
+                prdIds[getEntityIndex(entityId)] = prd_id.value(i);
+            }
+        }
+    }
+
+    const prdIdColumn = Column.ofArray({ array: prdIds, schema: Column.Schema.str });
+
+    return {
+        data: entityData,
+        subtype: subtypeColumn,
+        prd_id: prdIdColumn,
+        getEntityIndex
+    };
 }

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

@@ -29,8 +29,7 @@ export async function createModels(data: BasicData, format: ModelFormat, ctx: Ru
         : await readStandard(ctx, data, properties, format);
 
     for (let i = 0; i < models.length; i++) {
-        models[i].trajectoryInfo.index = i;
-        models[i].trajectoryInfo.size = models.length;
+        Model.TrajectoryInfo.set(models[i], { index: i, size: models.length });
     }
 
     return models;
@@ -69,7 +68,6 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
         entry,
         sourceData: format,
         modelNum,
-        trajectoryInfo: { index: 0, size: 1 },
         entities,
         sequence,
         atomicHierarchy: atomic.hierarchy,
@@ -108,7 +106,6 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
         entry,
         sourceData: format,
         modelNum: ihm.model_id,
-        trajectoryInfo: { index: 0, size: 1 },
         entities: ihm.entities,
         sequence,
         atomicHierarchy: atomic.hierarchy,
@@ -137,7 +134,7 @@ async function readStandard(ctx: RuntimeContext, data: BasicData, properties: Mo
 
     if (data.atom_site) {
         const atomCount = data.atom_site.id.rowCount;
-        const entities = getEntities(data);
+        const entities = getEntities(data, properties);
 
         let modelStart = 0;
         while (modelStart < atomCount) {
@@ -171,7 +168,7 @@ function splitTable<T extends Table<any>>(table: T, col: Column<number>) {
 
 
 async function readIntegrative(ctx: RuntimeContext, data: BasicData, properties: Model['properties'], format: ModelFormat) {
-    const entities = getEntities(data);
+    const entities = getEntities(data, properties);
     // when `atom_site.ihm_model_id` is undefined fall back to `atom_site.pdbx_PDB_model_num`
     const atom_sites_modelColumn = data.atom_site.ihm_model_id.isDefined
         ? data.atom_site.ihm_model_id : data.atom_site.pdbx_PDB_model_num;

+ 6 - 2
src/mol-model-formats/structure/basic/schema.ts

@@ -6,6 +6,7 @@
 
 import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
 import { Table } from '../../../mol-data/db';
+import { mmCIF_chemComp_schema } from '../../../mol-io/reader/cif/schema/mmcif-extras';
 
 // TODO split into conformation and hierarchy parts
 
@@ -19,12 +20,13 @@ export type Entity = Table<mmCIF_Schema['entity']>
 export type EntityPoly = Table<mmCIF_Schema['entity_poly']>
 export type EntityPolySeq = Table<mmCIF_Schema['entity_poly_seq']>
 export type EntityBranch = Table<mmCIF_Schema['pdbx_entity_branch']>
-export type ChemComp = Table<mmCIF_Schema['chem_comp']>
+export type ChemComp = Table<mmCIF_chemComp_schema>
 export type ChemCompIdentifier = Table<mmCIF_Schema['pdbx_chem_comp_identifier']>
 export type AtomSite = Table<mmCIF_Schema['atom_site']>
 export type IhmSphereObjSite = Table<mmCIF_Schema['ihm_sphere_obj_site']>
 export type IhmGaussianObjSite =Table<mmCIF_Schema['ihm_gaussian_obj_site']>
 export type UnobsOrZeroOccResidues =Table<mmCIF_Schema['pdbx_unobs_or_zero_occ_residues']>
+export type Molecule =Table<mmCIF_Schema['pdbx_molecule']>
 
 export const BasicSchema = {
     entry: mmCIF_Schema.entry,
@@ -37,12 +39,13 @@ export const BasicSchema = {
     entity_poly: mmCIF_Schema.entity_poly,
     entity_poly_seq: mmCIF_Schema.entity_poly_seq,
     pdbx_entity_branch: mmCIF_Schema.pdbx_entity_branch,
-    chem_comp: mmCIF_Schema.chem_comp,
+    chem_comp: mmCIF_chemComp_schema,
     pdbx_chem_comp_identifier: mmCIF_Schema.pdbx_chem_comp_identifier,
     atom_site: mmCIF_Schema.atom_site,
     ihm_sphere_obj_site: mmCIF_Schema.ihm_sphere_obj_site,
     ihm_gaussian_obj_site: mmCIF_Schema.ihm_gaussian_obj_site,
     pdbx_unobs_or_zero_occ_residues: mmCIF_Schema.pdbx_unobs_or_zero_occ_residues,
+    pdbx_molecule: mmCIF_Schema.pdbx_molecule,
 };
 
 export interface BasicData {
@@ -62,6 +65,7 @@ export interface BasicData {
     ihm_sphere_obj_site: IhmSphereObjSite
     ihm_gaussian_obj_site: IhmGaussianObjSite
     pdbx_unobs_or_zero_occ_residues: UnobsOrZeroOccResidues
+    pdbx_molecule: Molecule
 }
 
 export function createBasic(data: Partial<BasicData>): BasicData {

+ 2 - 8
src/mol-model-formats/structure/basic/sort.ts

@@ -8,19 +8,13 @@ import { createRangeArray, makeBuckets } from '../../../mol-data/util';
 import { Column, Table } from '../../../mol-data/db';
 import { RuntimeContext } from '../../../mol-task';
 import { AtomSite } from './schema';
+import { arrayIsIdentity } from '../../../mol-util/array';
 
 export type SortedAtomSite = {
     atom_site: AtomSite
     sourceIndex: Column<number>
 }
 
-function isIdentity(xs: ArrayLike<number>) {
-    for (let i = 0, _i = xs.length; i < _i; i++) {
-        if (xs[i] !== i) return false;
-    }
-    return true;
-}
-
 export async function sortAtomSite(ctx: RuntimeContext, atom_site: AtomSite, start: number, end: number): Promise<SortedAtomSite> {
     const indices = createRangeArray(start, end - 1);
 
@@ -40,7 +34,7 @@ export async function sortAtomSite(ctx: RuntimeContext, atom_site: AtomSite, sta
         if (ctx.shouldUpdate) await ctx.update();
     }
 
-    if (isIdentity(indices) && indices.length === atom_site._rowCount) {
+    if (arrayIsIdentity(indices) && indices.length === atom_site._rowCount) {
         return { atom_site, sourceIndex: Column.ofIntArray(indices) };
     }
 

+ 1 - 1
src/mol-model-formats/structure/cif-core.ts

@@ -40,7 +40,7 @@ function getSymmetry(db: CifCore_Database): Symmetry {
     return {
         spacegroup: Spacegroup.create(spaceCell),
         assemblies : [],
-        isNonStandardCrytalFrame: false,
+        isNonStandardCrystalFrame: false,
         ncsOperators: []
     };
 }

+ 25 - 6
src/mol-model-formats/structure/common/component.ts

@@ -5,12 +5,12 @@
  */
 
 import { Table, Column } from '../../../mol-data/db';
-import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
 import { WaterNames, PolymerNames } from '../../../mol-model/structure/model/types';
 import { SetUtils } from '../../../mol-util/set';
 import { BasicSchema } from '../basic/schema';
+import { mmCIF_chemComp_schema } from '../../../mol-io/reader/cif/schema/mmcif-extras';
 
-type Component = Table.Row<Pick<mmCIF_Schema['chem_comp'], 'id' | 'name' | 'type'>>
+type Component = Table.Row<Pick<mmCIF_chemComp_schema, 'id' | 'name' | 'type'>>
 
 const ProteinAtomIdsList = [
     new Set([ 'CA' ]),
@@ -71,13 +71,27 @@ const StandardComponents = (function() {
     return map;
 })();
 
+const CharmmIonComponents = (function() {
+    const map = new Map<string, Component>();
+    const components: Component[] = [
+        { id: 'ZN2', name: 'ZINC ION', type: 'Ion' },
+        { id: 'SOD', name: 'SODIUM ION', type: 'Ion' },
+        { id: 'CES', name: 'CESIUM ION', type: 'Ion' },
+        { id: 'CLA', name: 'CHLORIDE ION', type: 'Ion' },
+        { id: 'CAL', name: 'CALCIUM ION', type: 'Ion' },
+        { id: 'POT', name: 'POTASSIUM ION', type: 'Ion' },
+    ];
+    components.forEach(c => map.set(c.id, c));
+    return map;
+})();
+
 export class ComponentBuilder {
     private namesMap = new Map<string, string>()
     private comps = new Map<string, Component>()
     private ids: string[] = []
     private names: string[] = []
-    private types: mmCIF_Schema['chem_comp']['type']['T'][] = []
-    private mon_nstd_flags: mmCIF_Schema['chem_comp']['mon_nstd_flag']['T'][] = []
+    private types: mmCIF_chemComp_schema['type']['T'][] = []
+    private mon_nstd_flags: mmCIF_chemComp_schema['mon_nstd_flag']['T'][] = []
 
     private set(c: Component) {
         this.comps.set(c.id, c);
@@ -131,8 +145,13 @@ export class ComponentBuilder {
             } else if (WaterNames.has(compId)) {
                 this.set({ id: compId, name: 'WATER', type: 'non-polymer' });
             } else {
-                const type = this.getType(this.getAtomIds(index));
-                this.set({ id: compId, name: this.namesMap.get(compId) || compId, type });
+                const atomIds = this.getAtomIds(index);
+                if (CharmmIonComponents.has(compId) && atomIds.size === 1) {
+                    this.set(CharmmIonComponents.get(compId)!);
+                } else {
+                    const type = this.getType(atomIds);
+                    this.set({ id: compId, name: this.namesMap.get(compId) || compId, type });
+                }
             }
         }
         return this.get(compId)!;

+ 12 - 6
src/mol-model-formats/structure/gro.ts

@@ -17,9 +17,7 @@ import { EntityBuilder } from './common/entity';
 import { BasicData, BasicSchema, createBasic } from './basic/schema';
 import { createModels } from './basic/parser';
 
-// TODO multi model files
-
-function getBasic(atoms: GroAtoms): BasicData {
+function getBasic(atoms: GroAtoms, modelNum: number): BasicData {
     const auth_atom_id = atoms.atomName;
     const auth_comp_id = atoms.residueName;
 
@@ -89,7 +87,7 @@ function getBasic(atoms: GroAtoms): BasicData {
         occupancy: Column.ofConst(1, atoms.count, Column.Schema.float),
         type_symbol: Column.ofStringArray(Column.mapToArray(atoms.atomName, s => guessElementSymbolString(s))),
 
-        pdbx_PDB_model_num: Column.ofConst(1, atoms.count, Column.Schema.int),
+        pdbx_PDB_model_num: Column.ofConst(modelNum, atoms.count, Column.Schema.int),
     }, atoms.count);
 
     return createBasic({
@@ -115,10 +113,18 @@ namespace GroFormat {
     }
 }
 
+// TODO reuse static model parts when hierarchy is identical
+//      need to pass all gro.structures as one table into createModels
+
 export function trajectoryFromGRO(gro: GroFile): Task<Model.Trajectory> {
     return Task.create('Parse GRO', async ctx => {
         const format = GroFormat.fromGro(gro);
-        const basic = getBasic(gro.structures[0].atoms);
-        return createModels(basic, format, ctx);
+        const models: Model[] = [];
+        for (let i = 0, il = gro.structures.length; i < il; ++i) {
+            const basic = getBasic(gro.structures[i].atoms, i + 1);
+            const m = await createModels(basic, format, ctx);
+            if (m.length === 1) models.push(m[0]);
+        }
+        return models;
     });
 }

+ 0 - 9
src/mol-model-formats/structure/mmcif.ts

@@ -84,15 +84,6 @@ namespace MmcifFormat {
         if (!db) db = CIF.schema.mmCIF(frame);
         return { kind: 'mmCIF', name: db._name, data: { db, frame } };
     }
-
-    export function isBirdMolecule(model: Model, asymId: string) {
-        if (!MmcifFormat.is(model.sourceData)) return false;
-        const { _rowCount, asym_id } = model.sourceData.data.db.pdbx_molecule;
-        for (let i = 0, il = _rowCount; i < il; ++i) {
-            if (asym_id.value(i) === asymId) return true;
-        }
-        return false;
-    }
 }
 
 export function trajectoryFromMmCIF(frame: CifFrame): Task<Model.Trajectory> {

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

@@ -36,8 +36,8 @@ namespace ModelSymmetry {
     export function fromData(data: Data): Symmetry {
         const assemblies = createAssemblies(data.pdbx_struct_assembly, data.pdbx_struct_assembly_gen, data.pdbx_struct_oper_list);
         const spacegroup = getSpacegroup(data.symmetry, data.cell);
-        const isNonStandardCrytalFrame = checkNonStandardCrystalFrame(data.atom_sites, spacegroup);
-        return { assemblies, spacegroup, isNonStandardCrytalFrame, ncsOperators: getNcsOperators(data.struct_ncs_oper) };
+        const isNonStandardCrystalFrame = checkNonStandardCrystalFrame(data.atom_sites, spacegroup);
+        return { assemblies, spacegroup, isNonStandardCrystalFrame, ncsOperators: getNcsOperators(data.struct_ncs_oper) };
     }
 }
 

+ 22 - 10
src/mol-model-formats/structure/psf.ts

@@ -4,17 +4,17 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { PsfFile } from '../../mol-io/reader/psf/parser';
 import { Column, Table } from '../../mol-data/db';
-import { EntityBuilder } from './common/entity';
-import { ComponentBuilder } from './common/component';
-import { guessElementSymbolString } from './util';
-import { MoleculeType, getMoleculeType } from '../../mol-model/structure/model/types';
-import { getChainId } from './common/util';
+import { PsfFile } from '../../mol-io/reader/psf/parser';
+import { getMoleculeType, MoleculeType } from '../../mol-model/structure/model/types';
+import { Topology } from '../../mol-model/structure/topology/topology';
 import { Task } from '../../mol-task';
 import { ModelFormat } from '../format';
-import { Topology } from '../../mol-model/structure/topology/topology';
-import { createBasic, BasicSchema } from './basic/schema';
+import { BasicSchema, createBasic } from './basic/schema';
+import { ComponentBuilder } from './common/component';
+import { EntityBuilder } from './common/entity';
+import { getChainId } from './common/util';
+import { guessElementSymbolString } from './util';
 
 function getBasic(atoms: PsfFile['atoms']) {
     const auth_atom_id = atoms.atomName;
@@ -32,16 +32,28 @@ function getBasic(atoms: PsfFile['atoms']) {
     let currentAsymIndex = 0;
     let currentAsymId = '';
     let currentSeqId = 0;
+    let currentSegmentName = atoms.segmentName.value(0), segmentChanged = false;
     let prevMoleculeType = MoleculeType.Unknown;
     let prevResidueNumber = -1;
 
     for (let i = 0, il = atoms.count; i < il; ++i) {
         const residueNumber = atoms.residueId.value(i);
-        if (residueNumber !== prevResidueNumber) {
+
+        if (currentSegmentName !== atoms.segmentName.value(i)) {
+            currentAsymId = getChainId(currentAsymIndex);
+            currentAsymIndex += 1;
+            currentSeqId = 0;
+            segmentChanged = true;
+            currentSegmentName = atoms.segmentName.value(i);
+        } else {
+            segmentChanged = false;
+        }
+
+        if (segmentChanged || residueNumber !== prevResidueNumber) {
             const compId = atoms.residueName.value(i);
             const moleculeType = getMoleculeType(componentBuilder.add(compId, i).type, compId);
 
-            if (moleculeType !== prevMoleculeType || residueNumber !== prevResidueNumber + 1) {
+            if (!segmentChanged && (moleculeType !== prevMoleculeType || residueNumber !== prevResidueNumber + 1)) {
                 currentAsymId = getChainId(currentAsymIndex);
                 currentAsymIndex += 1;
                 currentSeqId = 0;

+ 34 - 0
src/mol-model-formats/structure/xtc.ts

@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Task } from '../../mol-task';
+import { XtcFile } from '../../mol-io/reader/xtc/parser';
+import { Coordinates, Frame, Time } from '../../mol-model/structure/coordinates';
+import { Cell } from '../../mol-math/geometry/spacegroup/cell';
+
+export function coordinatesFromXtc(file: XtcFile): Task<Coordinates> {
+    return Task.create('Parse XTC', async ctx => {
+        await ctx.update('Converting to coordinates');
+
+        const deltaTime = Time(file.deltaTime, 'step');
+        const offsetTime = Time(file.timeOffset, deltaTime.unit);
+
+        const frames: Frame[] = [];
+        for (let i = 0, il = file.frames.length; i < il; ++i) {
+            frames.push({
+                elementCount: file.frames[i].count,
+                // TODO:
+                cell: Cell.empty(),
+                x: file.frames[i].x,
+                y: file.frames[i].y,
+                z: file.frames[i].z,
+                time: Time(offsetTime.value + deltaTime.value * i, deltaTime.unit)
+            });
+        }
+
+        return Coordinates.create(frames, deltaTime, offsetTime);
+    });
+}

+ 6 - 3
src/mol-model-formats/volume/ccp4.ts

@@ -68,15 +68,18 @@ export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, of
         // Min/max/mean are reliable (based on LiteMol/DensityServer usage)
         // These, however, calculate sigma, so no data on that.
 
+        // always calculate stats when all stats related values are zero
+        const calcStats = header.AMIN === 0 && header.AMAX === 0 && header.AMEAN === 0 && header.ARMS === 0;
+
         return {
             label: params?.label,
             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,
+                    min: (isNaN(header.AMIN) || calcStats) ? arrayMin(values) : header.AMIN,
+                    max: (isNaN(header.AMAX) || calcStats) ? arrayMax(values) : header.AMAX,
+                    mean: (isNaN(header.AMEAN) || calcStats) ? arrayMean(values) : header.AMEAN,
                     sigma: (isNaN(header.ARMS) || header.ARMS === 0) ? arrayRms(values) : header.ARMS
                 },
             },

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

@@ -9,6 +9,7 @@ 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';
+import { stringToWords } from '../../mol-util/string';
 
 export { CustomModelProperty };
 
@@ -18,6 +19,7 @@ namespace CustomModelProperty {
     export interface ProviderBuilder<Params extends PD.Params, Value> {
         readonly label: string
         readonly descriptor: CustomPropertyDescriptor
+        readonly isHidden?: boolean
         readonly defaultParams: Params
         readonly getParams: (data: Model) => Params
         readonly isApplicable: (data: Model) => boolean
@@ -49,7 +51,12 @@ namespace CustomModelProperty {
         return {
             label: builder.label,
             descriptor: builder.descriptor,
-            getParams: builder.getParams,
+            isHidden: builder.isHidden,
+            getParams: (data: Model) => {
+                const params = PD.clone(builder.getParams(data));
+                PD.setDefaultValues(params, get(data).props);
+                return params;
+            },
             defaultParams: builder.defaultParams,
             isApplicable: builder.isApplicable,
             attach: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<Params>> = {}, addRef) => {
@@ -77,4 +84,20 @@ namespace CustomModelProperty {
             props: (data: Model) => get(data).props,
         };
     }
+
+    export function createSimple<T>(name: string, type: 'static' | 'dynamic', defaultValue?: T) {
+        const defaultParams = { value: PD.Value(defaultValue, { isHidden: true }) };
+        return createProvider({
+            label: stringToWords(name),
+            descriptor: CustomPropertyDescriptor({ name }),
+            isHidden: true,
+            type,
+            defaultParams,
+            getParams: () => ({ value: PD.Value(defaultValue, { isHidden: true }) }),
+            isApplicable: () => true,
+            obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<typeof defaultParams>>) => {
+                return { value: props.value ?? defaultValue };
+            }
+        });
+    }
 }

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

@@ -29,6 +29,8 @@ namespace CustomProperty {
     export interface Provider<Data, Params extends PD.Params, Value> {
         readonly label: string
         readonly descriptor: CustomPropertyDescriptor
+        /** hides property in ui and always attaches */
+        readonly isHidden?: boolean
         readonly getParams: (data: Data) => Params
         readonly defaultParams: Params
         readonly isApplicable: (data: Data) => boolean
@@ -57,14 +59,16 @@ namespace CustomProperty {
                     const provider = v.value;
                     if (!provider.isApplicable(data)) continue;
 
-                    autoAttachOptions.push([provider.descriptor.name, provider.label]);
-                    if (this.defaultAutoAttachValues.get(provider.descriptor.name)) {
-                        autoAttachDefault.push(provider.descriptor.name);
+                    if (!provider.isHidden) {
+                        autoAttachOptions.push([provider.descriptor.name, provider.label]);
+                        if (this.defaultAutoAttachValues.get(provider.descriptor.name)) {
+                            autoAttachDefault.push(provider.descriptor.name);
+                        }
                     }
 
                     propertiesParams[provider.descriptor.name] = PD.Group({
                         ...provider.getParams(data)
-                    }, { label: provider.label });
+                    }, { label: provider.label, isHidden: provider.isHidden });
                 }
             }
             return {

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

@@ -9,6 +9,7 @@ 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';
+import { stringToWords } from '../../mol-util/string';
 
 export { CustomStructureProperty };
 
@@ -18,6 +19,7 @@ namespace CustomStructureProperty {
     export interface ProviderBuilder<Params extends PD.Params, Value> {
         readonly label: string
         readonly descriptor: CustomPropertyDescriptor
+        readonly isHidden?: boolean
         readonly defaultParams: Params
         readonly getParams: (data: Structure) => Params
         readonly isApplicable: (data: Structure) => boolean
@@ -49,7 +51,12 @@ namespace CustomStructureProperty {
         return {
             label: builder.label,
             descriptor: builder.descriptor,
-            getParams: builder.getParams,
+            isHidden: builder.isHidden,
+            getParams: (data: Structure) => {
+                const params = PD.clone(builder.getParams(data));
+                PD.setDefaultValues(params, get(data).props);
+                return params;
+            },
             defaultParams: builder.defaultParams,
             isApplicable: builder.isApplicable,
             attach: async (ctx: CustomProperty.Context, data: Structure, props: Partial<PD.Values<Params>> = {}, addRef) => {
@@ -80,4 +87,20 @@ namespace CustomStructureProperty {
             props: (data: Structure) => get(data).props,
         };
     }
+
+    export function createSimple<T>(name: string, type: 'root' | 'local', defaultValue?: T) {
+        const defaultParams = { value: PD.Value(defaultValue, { isHidden: true }) };
+        return createProvider({
+            label: stringToWords(name),
+            descriptor: CustomPropertyDescriptor({ name }),
+            isHidden: true,
+            type,
+            defaultParams,
+            getParams: () => ({ value: PD.Value(defaultValue, { isHidden: true }) }),
+            isApplicable: () => true,
+            obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<PD.Values<typeof defaultParams>>) => {
+                return { ...PD.getDefaultValues(defaultParams), ...props };
+            }
+        });
+    }
 }

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

@@ -108,7 +108,7 @@ namespace AccessibleSurfaceArea {
 
     export function getNormalizedValue(location: StructureElement.Location, accessibleSurfaceArea: AccessibleSurfaceArea) {
         const value = getValue(location, accessibleSurfaceArea);
-        return value === -1 ? -1 : normalize(StructureProperties.residue.label_comp_id(location), value);
+        return value === -1 ? -1 : normalize(StructureProperties.atom.label_comp_id(location), value);
     }
 
     export function getFlag(location: StructureElement.Location, accessibleSurfaceArea: AccessibleSurfaceArea) {

+ 2 - 2
src/mol-model-props/computed/accessible-surface-area/shrake-rupley/radii.ts

@@ -15,8 +15,8 @@ import { getElementMoleculeType } from '../../../../mol-model/structure/util';
 const l = StructureElement.Location.create(void 0);
 
 export function assignRadiusForHeavyAtoms(ctx: ShrakeRupleyContext) {
-    const { label_comp_id, key } = StructureProperties.residue;
-    const { type_symbol, label_atom_id } = StructureProperties.atom;
+    const { key } = StructureProperties.residue;
+    const { type_symbol, label_atom_id, label_comp_id } = StructureProperties.atom;
     const { structure, atomRadiusType, serialResidueIndex } = ctx;
 
     let prevResidueIdx = 0;

+ 1 - 1
src/mol-model-props/computed/chemistry/util.ts

@@ -27,7 +27,7 @@ export function altLoc(unit: Unit.Atomic, index: StructureElement.UnitIndex) {
 }
 
 export function compId(unit: Unit.Atomic, index: StructureElement.UnitIndex) {
-    return unit.model.atomicHierarchy.residues.label_comp_id.value(unit.getResidueIndex(index));
+    return unit.model.atomicHierarchy.atoms.label_comp_id.value(unit.elements[index]);
 }
 
 //

+ 4 - 4
src/mol-model-props/computed/interactions/charged.ts

@@ -62,12 +62,12 @@ function addUnitPositiveCharges(structure: Structure, unit: Unit.Atomic, builder
 
     const addedElements = new Set<StructureElement.UnitIndex>();
 
-    const { label_comp_id } = unit.model.atomicHierarchy.residues;
+    const { label_comp_id } = unit.model.atomicHierarchy.atoms;
     const residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
 
     while (residueIt.hasNext) {
         const { index: residueIndex, start, end } = residueIt.move();
-        const compId = label_comp_id.value(residueIndex);
+        const compId = label_comp_id.value(unit.model.atomicHierarchy.residueAtomSegments.offsets[residueIndex]);
 
         if (PositvelyCharged.includes(compId)) {
             builder.startState();
@@ -115,12 +115,12 @@ function addUnitNegativeCharges(structure: Structure, unit: Unit.Atomic, builder
 
     const addedElements = new Set<StructureElement.UnitIndex>();
 
-    const { label_comp_id } = unit.model.atomicHierarchy.residues;
+    const { label_comp_id } = unit.model.atomicHierarchy.atoms;
     const residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements);
 
     while (residueIt.hasNext) {
         const { index: residueIndex, start, end } = residueIt.move();
-        const compId = label_comp_id.value(residueIndex);
+        const compId = label_comp_id.value(unit.model.atomicHierarchy.residueAtomSegments.offsets[residueIndex]);
 
         if (NegativelyCharged.includes(compId)) {
             builder.startState();

+ 2 - 2
src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts

@@ -10,7 +10,7 @@ import { Structure, StructureElement } from '../../../mol-model/structure';
 import { Theme } from '../../../mol-theme/theme';
 import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
 import { Vec3 } from '../../../mol-math/linear-algebra';
-import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from '../../../mol-repr/structure/visual/util/link';
+import { createLinkCylinderMesh, LinkCylinderParams, LinkStyle } from '../../../mol-repr/structure/visual/util/link';
 import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../../../mol-repr/structure/complex-visual';
 import { VisualUpdateState } from '../../../mol-repr/util';
 import { PickingId } from '../../../mol-geo/geometry/picking';
@@ -47,7 +47,7 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S
             Vec3.set(posB, fB.x[indexB], fB.y[indexB], fB.z[indexB]);
             Vec3.transformMat4(posB, posB, unitB.conformation.operator.matrix);
         },
-        style: (edgeIndex: number) => LinkCylinderStyle.Dashed,
+        style: (edgeIndex: number) => LinkStyle.Dashed,
         radius: (edgeIndex: number) => {
             const b = edges[edgeIndex];
             const fA = unitsFeatures.get(b.unitA.id);

+ 2 - 2
src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts

@@ -14,7 +14,7 @@ import { PickingId } from '../../../mol-geo/geometry/picking';
 import { VisualContext } from '../../../mol-repr/visual';
 import { Theme } from '../../../mol-theme/theme';
 import { InteractionsProvider } from '../interactions';
-import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from '../../../mol-repr/structure/visual/util/link';
+import { createLinkCylinderMesh, LinkCylinderParams, LinkStyle } from '../../../mol-repr/structure/visual/util/link';
 import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../../../mol-repr/structure/units-visual';
 import { VisualUpdateState } from '../../../mol-repr/util';
 import { LocationIterator } from '../../../mol-geo/util/location-iterator';
@@ -42,7 +42,7 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit:
             Vec3.set(posA, x[a[edgeIndex]], y[a[edgeIndex]], z[a[edgeIndex]]);
             Vec3.set(posB, x[b[edgeIndex]], y[b[edgeIndex]], z[b[edgeIndex]]);
         },
-        style: (edgeIndex: number) => LinkCylinderStyle.Dashed,
+        style: (edgeIndex: number) => LinkStyle.Dashed,
         radius: (edgeIndex: number) => {
             location.element = unit.elements[members[offsets[a[edgeIndex]]]];
             const sizeA = theme.size.size(location);

+ 9 - 1
src/mol-model/sequence/sequence.ts

@@ -130,7 +130,15 @@ namespace Sequence {
             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];
+                if (mh) {
+                    const l = mh.map(id => {
+                        const c = codeFromName(id);
+                        return c === 'X' ? id : c;
+                    });
+                    labels[i] = `(${l.join('|')})`;
+                } else {
+                    labels[i] = codes[i] === 'X' ? compIds[idx] : codes[i];
+                }
             }
 
             this.length = idx;

+ 28 - 7
src/mol-model/structure/coordinates/coordinates.ts

@@ -1,5 +1,5 @@
 /**
- * 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 Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -11,7 +11,6 @@ import { Column } from '../../../mol-data/db';
 
 export interface Frame {
     readonly elementCount: number
-    readonly cell: Cell
     readonly time: Time
 
     // positions
@@ -19,6 +18,9 @@ export interface Frame {
     readonly y: ArrayLike<number>
     readonly z: ArrayLike<number>
 
+    // optional cell
+    readonly cell?: Cell
+
     // optional velocities
     readonly velocities?: {
         readonly vx: ArrayLike<number>
@@ -62,9 +64,7 @@ interface Coordinates {
 
     readonly frames: Frame[]
 
-    /** Number of elements (e.g. atoms) in frames */
-    readonly elementCount: number
-
+    readonly hasCell: boolean
     readonly hasVelocities: boolean
     readonly hasForces: boolean
 
@@ -74,14 +74,14 @@ interface Coordinates {
 
 namespace Coordinates {
     export function create(frames: Frame[], deltaTime: Time, timeOffset: Time): Coordinates {
-        const elementCount = frames[0].elementCount;
+        const hasCell = !!frames[0].cell;
         const hasVelocities = !!frames[0].velocities;
         const hasForces = !!frames[0].forces;
 
         return {
             id: UUID.create22(),
             frames,
-            elementCount,
+            hasCell,
             hasVelocities,
             hasForces,
             deltaTime,
@@ -101,4 +101,25 @@ namespace Coordinates {
             z: frame.z,
         };
     }
+
+    function reorderCoords(xs: ArrayLike<number>, index: ArrayLike<number>) {
+        const ret = new Float32Array(xs.length);
+        for (let i = 0, _i = xs.length; i < _i; i++) {
+            ret[i] = xs[index[i]];
+        }
+        return ret;
+    }
+
+    export function getAtomicConformationReordered(frame: Frame, atomId: Column<number>, srcIndex: ArrayLike<number>): AtomicConformation {
+        return {
+            id: UUID.create22(),
+            atomId,
+            occupancy: Column.ofConst(1, frame.elementCount, Column.Schema.int),
+            B_iso_or_equiv: Column.ofConst(0, frame.elementCount, Column.Schema.float),
+            xyzDefined: true,
+            x: reorderCoords(frame.x, srcIndex),
+            y: reorderCoords(frame.y, srcIndex),
+            z: reorderCoords(frame.z, srcIndex)
+        };
+    }
 }

+ 6 - 6
src/mol-model/structure/export/categories/atom_site.ts

@@ -34,7 +34,7 @@ const atom_site_fields = CifWriter.fields<StructureElement.Location, Structure>(
     .str('type_symbol', P.atom.type_symbol as any)
     .str('label_atom_id', P.atom.label_atom_id)
 
-    .str('label_comp_id', P.residue.label_comp_id)
+    .str('label_comp_id', P.atom.label_comp_id)
     .int('label_seq_id', P.residue.label_seq_id, {
         encoder: E.deltaRLE,
         valueKind: (k, d) => {
@@ -58,7 +58,7 @@ const atom_site_fields = CifWriter.fields<StructureElement.Location, Structure>(
     })
 
     .str('auth_atom_id', P.atom.auth_atom_id)
-    .str('auth_comp_id', P.residue.auth_comp_id)
+    .str('auth_comp_id', P.atom.auth_comp_id)
     .int('auth_seq_id', P.residue.auth_seq_id, { encoder: E.deltaRLE })
     .str('auth_asym_id', atom_site_auth_asym_id)
 
@@ -108,7 +108,7 @@ export interface IdFieldsOptions {
 export function residueIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] {
     const prefix = options && options.prefix, postfix = options && options.postfix;
     const ret = CifWriter.fields<K, D>()
-        .str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.residue.label_comp_id))
+        .str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
         .int(prepostfixed(prefix, postfix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
             encoder: E.deltaRLE,
             valueKind: (k, d) => {
@@ -122,7 +122,7 @@ export function residueIdFields<K, D>(getLocation: (key: K, data: D) => Structur
         .str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id))
         .str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
 
-        .str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.residue.auth_comp_id))
+        .str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
         .int(prepostfixed(prefix, postfix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
         .str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
 
@@ -154,7 +154,7 @@ export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureEl
     const prefix = options && options.prefix, postfix = options && options.postfix;
     const ret = CifWriter.fields<K, D>()
         .str(prepostfixed(prefix, postfix, `label_atom_id`), mappedProp(getLocation, P.atom.label_atom_id))
-        .str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.residue.label_comp_id))
+        .str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id))
         .int(prepostfixed(prefix, postfix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), {
             encoder: E.deltaRLE,
             valueKind: (k, d) => {
@@ -170,7 +170,7 @@ export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureEl
         .str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id))
 
         .str(prepostfixed(prefix, postfix, `auth_atom_id`), mappedProp(getLocation, P.atom.auth_atom_id))
-        .str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.residue.auth_comp_id))
+        .str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id))
         .int(prepostfixed(prefix, postfix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE })
         .str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id));
 

+ 66 - 16
src/mol-model/structure/model/model.ts

@@ -13,7 +13,7 @@ import { Entities, ChemicalComponentMap, MissingResidues, StructAsymMap } from '
 import { CustomProperties } from '../../custom-property';
 import { SaccharideComponentMap } from '../structure/carbohydrates/constants';
 import { ModelFormat } from '../../../mol-model-formats/format';
-import { calcModelCenter } from './util';
+import { calcModelCenter, getAsymIdCount } from './util';
 import { Vec3 } from '../../../mol-math/linear-algebra';
 import { Mutable } from '../../../mol-util/type-helpers';
 import { Coordinates } from '../coordinates';
@@ -26,6 +26,7 @@ import { ChainIndex } from './indexing';
 import { SymmetryOperator } from '../../../mol-math/geometry';
 import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 import { Column } from '../../../mol-data/db';
+import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
 
 /**
  * Interface to the "source data" of the molecule.
@@ -48,14 +49,6 @@ export interface Model extends Readonly<{
      */
     modelNum: number,
 
-    /**
-     * This is a hack to allow "model-index coloring"
-     */
-    trajectoryInfo: {
-        index: number,
-        size: number
-    },
-
     sourceData: ModelFormat,
 
     entities: Entities,
@@ -95,16 +88,23 @@ export interface Model extends Readonly<{
 export namespace Model {
     export type Trajectory = ReadonlyArray<Model>
 
-    export function trajectoryFromModelAndCoordinates(model: Model, coordinates: Coordinates): Trajectory {
+    function _trajectoryFromModelAndCoordinates(model: Model, coordinates: Coordinates) {
         const trajectory: Mutable<Model.Trajectory> = [];
         const { frames } = coordinates;
+
+        const srcIndex = model.atomicHierarchy.atoms.sourceIndex;
+        const isIdentity = Column.isIdentity(srcIndex);
+        const srcIndexArray = isIdentity ? void 0 : srcIndex.toArray({ array: Int32Array });
+
         for (let i = 0, il = frames.length; i < il; ++i) {
             const f = frames[i];
             const m = {
                 ...model,
                 id: UUID.create22(),
                 modelNum: i,
-                atomicConformation: Coordinates.getAtomicConformation(f, model.atomicConformation.atomId),
+                atomicConformation: isIdentity
+                    ? Coordinates.getAtomicConformation(f, model.atomicConformation.atomId)
+                    : Coordinates.getAtomicConformationReordered(f, model.atomicConformation.atomId, srcIndexArray!),
                 // TODO: add support for supplying sphere and gaussian coordinates in addition to atomic coordinates?
                 // coarseConformation: coarse.conformation,
                 customProperties: new CustomProperties(),
@@ -113,22 +113,44 @@ export namespace Model {
             };
             trajectory.push(m);
         }
-        return trajectory;
+        return { trajectory, srcIndexArray };
+    }
+
+    export function trajectoryFromModelAndCoordinates(model: Model, coordinates: Coordinates): Trajectory {
+        return _trajectoryFromModelAndCoordinates(model, coordinates).trajectory;
+    }
+
+    function invertIndex(xs: ArrayLike<number>) {
+        const ret = new Int32Array(xs.length);
+        for (let i = 0, _i = xs.length; i < _i; i++) {
+            ret[xs[i]] = i;
+        }
+        return ret;
     }
 
     export function trajectoryFromTopologyAndCoordinates(topology: Topology, coordinates: Coordinates): Task<Trajectory> {
         return Task.create('Create Trajectory', async ctx => {
             const model = (await createModels(topology.basic, topology.sourceData, ctx))[0];
             if (!model) throw new Error('found no model');
-            const trajectory = trajectoryFromModelAndCoordinates(model, coordinates);
-            const bondData = { pairs: topology.bonds, count: model.atomicHierarchy.atoms._rowCount };
+            const { trajectory, srcIndexArray } = _trajectoryFromModelAndCoordinates(model, coordinates);
+
+            // TODO: cache the inverted index somewhere?
+            const invertedIndex = srcIndexArray ? invertIndex(srcIndexArray) : void 0;
+            const pairs = srcIndexArray
+                ? {
+                    indexA: Column.ofIntArray(Column.mapToArray(topology.bonds.indexA, i => invertedIndex![i], Int32Array)),
+                    indexB: Column.ofIntArray(Column.mapToArray(topology.bonds.indexB, i => invertedIndex![i], Int32Array)),
+                    order: topology.bonds.order
+                }
+                : topology.bonds;
+
+            const bondData = { pairs, count: model.atomicHierarchy.atoms._rowCount };
             const indexPairBonds = IndexPairBonds.fromData(bondData);
 
             let index = 0;
             for (const m of trajectory) {
                 IndexPairBonds.Provider.set(m, indexPairBonds);
-                m.trajectoryInfo.index = index++;
-                m.trajectoryInfo.size = trajectory.length;
+                TrajectoryInfo.set(m, { index: index++, size: trajectory.length });
             }
             return trajectory;
         });
@@ -142,6 +164,34 @@ export namespace Model {
         return center;
     }
 
+    const TrajectoryInfoProp = '__TrajectoryInfo__';
+    export type TrajectoryInfo = { readonly index: number, readonly size: number }
+    export const TrajectoryInfo = {
+        get(model: Model): TrajectoryInfo {
+            return model._staticPropertyData[TrajectoryInfoProp] || { index: 0, size: 1 };
+        },
+        set(model: Model, trajectoryInfo: TrajectoryInfo) {
+            return model._staticPropertyData[TrajectoryInfoProp] = trajectoryInfo;
+        }
+    };
+
+    const AsymIdCountProp = '__AsymIdCount__';
+    export type AsymIdCount = { readonly auth: number, readonly label: number }
+    export const AsymIdCount = {
+        get(model: Model): AsymIdCount {
+            if (model._staticPropertyData[AsymIdCountProp]) return model._staticPropertyData[AsymIdCountProp];
+            const asymIdCount = getAsymIdCount(model);
+            model._staticPropertyData[AsymIdCountProp] = asymIdCount;
+            return asymIdCount;
+        },
+    };
+
+    export type AsymIdOffset = { auth: number, label: number };
+    export const AsymIdOffset = CustomModelProperty.createSimple<AsymIdOffset>('asym_id_offset', 'static');
+
+    export type Index = number;
+    export const Index = CustomModelProperty.createSimple<Index>('index', 'static');
+
     //
 
     export function hasCarbohydrate(model: Model): boolean {

+ 12 - 10
src/mol-model/structure/model/properties/atomic/hierarchy.ts

@@ -34,6 +34,16 @@ export const AtomsSchema = {
      * Identifies an alternative conformation for this atom site.
      */
     label_alt_id: mmCIF.atom_site.label_alt_id,
+    /**
+     * A component of the identifier for this atom site.
+     * For mmCIF files, this points to chem_comp.id in the CHEM_COMP category.
+     */
+    label_comp_id: mmCIF.atom_site.label_comp_id,
+    /**
+     * An alternative identifier for atom_site.label_comp_id that may be provided by an author
+     * in order to match the identification used in the publication that describes the structure.
+     */
+    auth_comp_id: mmCIF.atom_site.auth_comp_id,
     /**
      * The net integer charge assigned to this atom.
      * This is the formal charge assignment normally found in chemical diagrams.
@@ -58,16 +68,6 @@ export const ResiduesSchema = {
      * compatibility with the original Protein Data Bank format, and only for that purpose.
      */
     group_PDB: mmCIF.atom_site.group_PDB,
-    /**
-     * A component of the identifier for this atom site.
-     * For mmCIF files, this points to chem_comp.id in the CHEM_COMP category.
-     */
-    label_comp_id: mmCIF.atom_site.label_comp_id,
-    /**
-     * An alternative identifier for atom_site.label_comp_id that may be provided by an author
-     * in order to match the identification used in the publication that describes the structure.
-     */
-    auth_comp_id: mmCIF.atom_site.auth_comp_id,
     /**
      * For mmCIF files, this points to entity_poly_seq.num in the ENTITY_POLY_SEQ category.
      */
@@ -81,6 +81,8 @@ export const ResiduesSchema = {
      * PDB insertion code.
      */
     pdbx_PDB_ins_code: mmCIF.atom_site.pdbx_PDB_ins_code,
+
+    // comp_id is part of atoms because of microheterogeneity
 };
 export type ResiduesSchema = typeof ResiduesSchema
 export type Residues = Table<ResiduesSchema>

+ 6 - 2
src/mol-model/structure/model/properties/common.ts

@@ -8,20 +8,24 @@
 import { mmCIF_Database, mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
 import { Table, Column } from '../../../../mol-data/db';
 import { EntityIndex } from '../indexing';
+import { mmCIF_chemComp_schema } from '../../../../mol-io/reader/cif/schema/mmcif-extras';
 
 export type EntitySubtype = (
     mmCIF_Schema['entity_poly']['type']['T'] |
-    mmCIF_Schema['pdbx_entity_branch']['type']['T']
+    mmCIF_Schema['pdbx_entity_branch']['type']['T'] |
+    'ion' |
+    'lipid'
 )
 export const EntitySubtype = Column.Schema.Aliased<EntitySubtype>(Column.Schema.Str(''));
 
 export interface Entities {
     data: mmCIF_Database['entity'],
     subtype: Column<EntitySubtype>,
+    prd_id: Column<string>,
     getEntityIndex(id: string): EntityIndex
 }
 
-export type ChemicalComponent = Table.Row<mmCIF_Schema['chem_comp']>
+export type ChemicalComponent = Table.Row<mmCIF_chemComp_schema>
 export type ChemicalComponentMap = ReadonlyMap<string, ChemicalComponent>
 
 export type MissingResidue = Table.Row<Pick<mmCIF_Schema['pdbx_unobs_or_zero_occ_residues'], 'polymer_flag' | 'occupancy_flag'>>

+ 10 - 4
src/mol-model/structure/model/properties/sequence.ts

@@ -44,7 +44,8 @@ namespace StructureSequence {
     }
 
     export function fromAtomicHierarchy(entities: Entities, hierarchy: AtomicHierarchy): StructureSequence {
-        const { label_comp_id, label_seq_id } = hierarchy.residues;
+        const { label_comp_id } = hierarchy.atoms;
+        const { label_seq_id } = hierarchy.residues;
         const { chainAtomSegments, residueAtomSegments } = hierarchy;
         const { count, offsets } = chainAtomSegments;
 
@@ -70,12 +71,17 @@ namespace StructureSequence {
 
             const rStart = residueAtomSegments.index[offsets[start]];
             const rEnd = residueAtomSegments.index[offsets[cI + 1] - 1] + 1;
+            const seqId = Column.window(label_seq_id, rStart, rEnd);
+
+            const _compId: string[] = [];
+            for (let rI = rStart; rI < rEnd; ++rI) {
+                _compId.push(label_comp_id.value(residueAtomSegments.offsets[rI]));
+            }
+            const compId = Column.ofStringArray(_compId);
 
-            const compId = Column.window(label_comp_id, rStart, rEnd);
-            const num = Column.window(label_seq_id, rStart, rEnd);
             byEntityKey[entityKey] = {
                 entityId: entities.data.id.value(entityKey),
-                sequence: Sequence.ofResidueNames(compId, num)
+                sequence: Sequence.ofResidueNames(compId, seqId)
             };
 
             sequences.push(byEntityKey[entityKey]);

+ 3 - 3
src/mol-model/structure/model/properties/symmetry.ts

@@ -47,7 +47,7 @@ export namespace Assembly {
 interface Symmetry {
     readonly assemblies: ReadonlyArray<Assembly>,
     readonly spacegroup: Spacegroup,
-    readonly isNonStandardCrytalFrame: boolean,
+    readonly isNonStandardCrystalFrame: boolean,
     readonly ncsOperators?: ReadonlyArray<SymmetryOperator>,
 
     /**
@@ -61,7 +61,7 @@ interface Symmetry {
 }
 
 namespace Symmetry {
-    export const Default: Symmetry = { assemblies: [], spacegroup: Spacegroup.ZeroP1, isNonStandardCrytalFrame: false };
+    export const Default: Symmetry = { assemblies: [], spacegroup: Spacegroup.ZeroP1, isNonStandardCrystalFrame: false };
 
     export function findAssembly(model: Model, id: string): Assembly | undefined {
         const _id = id.toLocaleLowerCase();
@@ -80,7 +80,7 @@ namespace Symmetry {
         const gamma = radToDeg(anglesInRadians[2]).toFixed(2);
         const label: string[] = [];
         // name
-        label.push(`Unitcell <b>${name}</b> #${num}`);
+        label.push(`Unit Cell <b>${name}</b> #${num}`);
         // sizes
         label.push(`${a}\u00D7${b}\u00D7${c} \u212B`);
         // angles

+ 6 - 4
src/mol-model/structure/model/properties/utils/atomic-derived.ts

@@ -5,15 +5,17 @@
  */
 
 import { AtomicData } from '../atomic';
-import { AtomicIndex, AtomicDerivedData } from '../atomic/hierarchy';
+import { AtomicIndex, AtomicDerivedData, AtomicSegments } from '../atomic/hierarchy';
 import { ElementIndex, ResidueIndex } from '../../indexing';
 import { MoleculeType, getMoleculeType, getComponentType, PolymerType, getPolymerType } from '../../types';
 import { getAtomIdForAtomRole } from '../../../../../mol-model/structure/util';
 import { ChemicalComponentMap } from '../common';
 import { isProductionMode } from '../../../../../mol-util/debug';
 
-export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemicalComponentMap: ChemicalComponentMap): AtomicDerivedData {
-    const { label_comp_id, _rowCount: n } = data.residues;
+export function getAtomicDerivedData(data: AtomicData, segments: AtomicSegments, index: AtomicIndex, chemicalComponentMap: ChemicalComponentMap): AtomicDerivedData {
+    const { label_comp_id } = data.atoms;
+    const { _rowCount: n } = data.residues;
+    const { offsets } = segments.residueAtomSegments;
 
     const traceElementIndex = new Int32Array(n);
     const directionFromElementIndex = new Int32Array(n);
@@ -25,7 +27,7 @@ export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemi
     const polymerTypeMap = new Map<string, PolymerType>();
 
     for (let i = 0 as ResidueIndex; i < n; ++i) {
-        const compId = label_comp_id.value(i);
+        const compId = label_comp_id.value(offsets[i]);
         const chemCompMap = chemicalComponentMap;
 
         let molType: MoleculeType;

+ 24 - 4
src/mol-model/structure/model/types.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
@@ -10,6 +10,8 @@ import { SaccharideCompIdMap } from '../structure/carbohydrates/constants';
 import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
 import { SetUtils } from '../../../mol-util/set';
 import { EntitySubtype, ChemicalComponent } from './properties/common';
+import { LipidNames } from './types/lipids';
+import { mmCIF_chemComp_schema } from '../../../mol-io/reader/cif/schema/mmcif-extras';
 
 const _esCache = (function () {
     const cache = Object.create(null);
@@ -57,6 +59,8 @@ export const enum MoleculeType {
     Water,
     /** Small ionic molecule */
     Ion,
+    /** Lipid molecule */
+    Lipid,
     /** Protein, e.g. component type included in `ProteinComponentTypeNames` */
     Protein,
     /** RNA, e.g. component type included in `RNAComponentTypeNames` */
@@ -145,7 +149,7 @@ export const PolymerTypeAtomRoleId: { [k in PolymerType]: { [k in AtomRole]: Set
 
 export const ProteinBackboneAtoms = new Set([
     'CA', 'C', 'N', 'O',
-    'O1', 'O2', 'OC1', 'OC2', 'OX1', 'OXT',
+    'O1', 'O2', 'OC1', 'OC2', 'OT1', 'OT2', 'OX1', 'OXT',
     'H', 'H1', 'H2', 'H3', 'HA', 'HN', 'HXT',
     'BB'
 ]);
@@ -222,9 +226,19 @@ export const OtherComponentTypeNames = new Set([
     'NON-POLYMER', 'OTHER'
 ]);
 
+/** Chemical component type names for ion (extension to mmcif) */
+export const IonComponentTypeNames = new Set([
+    'ION'
+]);
+
+/** Chemical component type names for lipid (extension to mmcif) */
+export const LipidComponentTypeNames = new Set([
+    'LIPID'
+]);
+
 /** Common names for water molecules */
 export const WaterNames = new Set([
-    'SOL', 'WAT', 'HOH', 'H2O', 'W', 'DOD', 'D3O', 'TIP3', 'TIP4', 'SPC'
+    'SOL', 'WAT', 'HOH', 'H2O', 'W', 'DOD', 'D3O', 'TIP', 'TIP3', 'TIP4', 'SPC'
 ]);
 
 export const AminoAcidNamesL = new Set([
@@ -293,6 +307,8 @@ export function getMoleculeType(compType: string, compId: string): MoleculeType
         return MoleculeType.Water;
     } else if (IonNames.has(compId)) {
         return MoleculeType.Ion;
+    } else if (LipidNames.has(compId)) {
+        return MoleculeType.Lipid;
     } else if (OtherComponentTypeNames.has(compType)) {
         if (SaccharideCompIdMap.has(compId)) {
             // trust our saccharide table more than given 'non-polymer' or 'other' component type
@@ -328,7 +344,7 @@ export function getPolymerType(compType: string, molType: MoleculeType): Polymer
     }
 }
 
-export function getComponentType(compId: string): mmCIF_Schema['chem_comp']['type']['T'] {
+export function getComponentType(compId: string): mmCIF_chemComp_schema['type']['T'] {
     compId = compId.toUpperCase();
     if (AminoAcidNames.has(compId)) {
         return 'peptide linking';
@@ -395,6 +411,10 @@ export function getEntitySubtype(compId: string, compType: string): EntitySubtyp
         return 'polyribonucleotide';
     } else if (DnaBaseNames.has(compId)) {
         return 'polydeoxyribonucleotide';
+    } else if (IonComponentTypeNames.has(compType) || IonNames.has(compId)) {
+        return 'ion';
+    } else if (LipidComponentTypeNames.has(compType) || LipidNames.has(compId)) {
+        return 'lipid';
     } else {
         return 'other';
     }

+ 9 - 0
src/mol-model/structure/model/types/lipids.ts

@@ -0,0 +1,9 @@
+/**
+* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+*
+* Code-generated lipid params file. Names extracted from Martini FF lipids itp.
+*
+* @author molstar/lipid-params cli
+*/
+
+export const LipidNames = new Set(['DAPC', 'DBPC', 'DFPC', 'DGPC', 'DIPC', 'DLPC', 'DNPC', 'DOPC', 'DPPC', 'DRPC', 'DTPC', 'DVPC', 'DXPC', 'DYPC', 'LPPC', 'PAPC', 'PEPC', 'PGPC', 'PIPC', 'POPC', 'PRPC', 'PUPC', 'DAPE', 'DBPE', 'DFPE', 'DGPE', 'DIPE', 'DLPE', 'DNPE', 'DOPE', 'DPPE', 'DRPE', 'DTPE', 'DUPE', 'DVPE', 'DXPE', 'DYPE', 'LPPE', 'PAPE', 'PGPE', 'PIPE', 'POPE', 'PQPE', 'PRPE', 'PUPE', 'DAPS', 'DBPS', 'DFPS', 'DGPS', 'DIPS', 'DLPS', 'DNPS', 'DOPS', 'DPPS', 'DRPS', 'DTPS', 'DUPS', 'DVPS', 'DXPS', 'DYPS', 'LPPS', 'PAPS', 'PGPS', 'PIPS', 'POPS', 'PQPS', 'PRPS', 'PUPS', 'DAPG', 'DBPG', 'DFPG', 'DGPG', 'DIPG', 'DLPG', 'DNPG', 'DOPG', 'DPPG', 'DRPG', 'DTPG', 'DVPG', 'DXPG', 'DYPG', 'LPPG', 'PAPG', 'PGPG', 'PIPG', 'POPG', 'PRPG', 'DAPA', 'DBPA', 'DFPA', 'DGPA', 'DIPA', 'DLPA', 'DNPA', 'DOPA', 'DPPA', 'DRPA', 'DTPA', 'DVPA', 'DXPA', 'DYPA', 'LPPA', 'PAPA', 'PGPA', 'PIPA', 'POPA', 'PRPA', 'PUPA', 'DPP', 'DPPI', 'PAPI', 'PIPI', 'POP', 'POPI', 'PUPI', 'PVP', 'PVPI', 'PADG', 'PIDG', 'PODG', 'PUDG', 'PVDG', 'APC', 'CPC', 'IPC', 'LPC', 'OPC', 'PPC', 'TPC', 'UPC', 'VPC', 'BNSM', 'DBSM', 'DPSM', 'DXSM', 'PGSM', 'PNSM', 'POSM', 'PVSM', 'XNSM', 'DPCE', 'DXCE', 'PNCE', 'XNCE']);

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

@@ -1,5 +1,5 @@
 /**
- * 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 Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -8,6 +8,7 @@ import { Vec3 } from '../../../mol-math/linear-algebra';
 import { AtomicConformation } from './properties/atomic';
 import { CoarseConformation } from './properties/coarse';
 import { arrayMinMax } from '../../../mol-util/array';
+import { Model } from './model';
 
 export function calcModelCenter(atomicConformation: AtomicConformation, coarseConformation?: CoarseConformation) {
     let rangesX: number[] = [];
@@ -42,4 +43,14 @@ export function calcModelCenter(atomicConformation: AtomicConformation, coarseCo
     const z = minZ + (maxZ - minZ) / 2;
 
     return Vec3.create(x, y, z);
+}
+
+export function getAsymIdCount(model: Model) {
+    const auth = new Set<string>();
+    const label = new Set<string>();
+    model.properties.structAsymMap.forEach(({ auth_id }, label_id) => {
+        auth.add(auth_id);
+        label.add(label_id);
+    });
+    return { auth: auth.size, label: label.size };
 }

+ 3 - 3
src/mol-model/structure/structure/carbohydrates/compute.ts

@@ -154,8 +154,8 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
         if (!Unit.isAtomic(unit)) continue;
 
         const { model, rings } = unit;
-        const { chainAtomSegments, residueAtomSegments, residues } = model.atomicHierarchy;
-        const { label_comp_id } = residues;
+        const { chainAtomSegments, residueAtomSegments, atoms } = model.atomicHierarchy;
+        const { label_comp_id } = atoms;
 
         const chainIt = Segmentation.transientSegments(chainAtomSegments, unit.elements);
         const residueIt = Segmentation.transientSegments(residueAtomSegments, unit.elements);
@@ -168,7 +168,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
             while (residueIt.hasNext) {
                 const { index: residueIndex } = residueIt.move();
 
-                const saccharideComp = getSaccharideComp(label_comp_id.value(residueIndex), model);
+                const saccharideComp = getSaccharideComp(label_comp_id.value(residueAtomSegments.offsets[residueIndex]), model);
                 if (!saccharideComp) continue;
 
                 if (!sugarResidueMap) {

+ 8 - 11
src/mol-model/structure/structure/properties.ts

@@ -51,20 +51,17 @@ const atom = {
     label_atom_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_atom_id.value(l.element)),
     auth_atom_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_atom_id.value(l.element)),
     label_alt_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_alt_id.value(l.element)),
+    label_comp_id: p(compId),
+    auth_comp_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_comp_id.value(l.unit.residueIndex[l.element])),
     pdbx_formal_charge: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.value(l.element)),
 
     // Derived
     vdw_radius: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element))),
 };
 
-function _compId(l: StructureElement.Location) {
-    return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element]);
-}
-
 function compId(l: StructureElement.Location) {
     if (!Unit.isAtomic(l.unit)) notAtomic();
-    if (!hasMicroheterogeneity(l)) return _compId(l);
-    return l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element]);
+    return l.unit.model.atomicHierarchy.atoms.label_comp_id.value(l.element);
 }
 
 function seqId(l: StructureElement.Location) {
@@ -91,14 +88,12 @@ const residue = {
     key: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.residueIndex[l.element]),
 
     group_PDB: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
-    label_comp_id: p(compId),
-    auth_comp_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])),
     label_seq_id: p(seqId),
     auth_seq_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])),
     pdbx_PDB_ins_code: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element])),
 
     // Properties
-    isNonStandard: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.chemicalComponentMap.get(compId(l))!.mon_nstd_flag[0] !== 'y'),
+    isNonStandard: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : microheterogeneityCompIds(l).some(c => l.unit.model.properties.chemicalComponentMap.get(c)!.mon_nstd_flag[0] !== 'y')),
     hasMicroheterogeneity: p(hasMicroheterogeneity),
     microheterogeneityCompIds: p(microheterogeneityCompIds),
     secondary_structure_type: p(l => {
@@ -157,7 +152,6 @@ const entity = {
 
     id: p(l => l.unit.model.entities.data.id.value(eK(l))),
     type: p(l => l.unit.model.entities.data.type.value(eK(l))),
-    subtype: p(l => l.unit.model.entities.subtype.value(eK(l))),
     src_method: p(l => l.unit.model.entities.data.src_method.value(eK(l))),
     pdbx_description: p(l => l.unit.model.entities.data.pdbx_description.value(eK(l))),
     formula_weight: p(l => l.unit.model.entities.data.formula_weight.value(eK(l))),
@@ -165,7 +159,10 @@ const entity = {
     details: p(l => l.unit.model.entities.data.details.value(eK(l))),
     pdbx_mutation: p(l => l.unit.model.entities.data.pdbx_mutation.value(eK(l))),
     pdbx_fragment: p(l => l.unit.model.entities.data.pdbx_fragment.value(eK(l))),
-    pdbx_ec: p(l => l.unit.model.entities.data.pdbx_ec.value(eK(l)))
+    pdbx_ec: p(l => l.unit.model.entities.data.pdbx_ec.value(eK(l))),
+
+    subtype: p(l => l.unit.model.entities.subtype.value(eK(l))),
+    prd_id: p(l => l.unit.model.entities.prd_id.value(eK(l))),
 };
 
 const _emptyList: any[] = [];

+ 26 - 7
src/mol-model/structure/structure/structure.ts

@@ -30,6 +30,7 @@ import { AtomicHierarchy } from '../model/properties/atomic';
 import { StructureSelection } from '../query/selection';
 import { getBoundary } from '../../../mol-math/geometry/boundary';
 import { ElementSymbol } from '../model/types';
+import { CustomStructureProperty } from '../../../mol-model-props/common/custom-structure-property';
 
 class Structure {
     /** Maps unit.id to unit */
@@ -407,7 +408,7 @@ function getModels(s: Structure) {
 }
 
 function getUniqueResidueNames(s: Structure) {
-    const prop = StructureProperties.residue.label_comp_id;
+    const { microheterogeneityCompIds } = StructureProperties.residue;
     const names = new Set<string>();
     const loc = StructureElement.Location.create(s);
     for (const unitGroup of s.unitSymmetryGroups) {
@@ -419,7 +420,8 @@ function getUniqueResidueNames(s: Structure) {
         while (residues.hasNext) {
             const seg = residues.move();
             loc.element = unit.elements[seg.start];
-            names.add(prop(loc));
+            const compIds = microheterogeneityCompIds(loc);
+            for (const compId of compIds) names.add(compId);
         }
     }
     return names;
@@ -658,6 +660,8 @@ namespace Structure {
         return create(units, { representativeModel: trajectory[0], label: trajectory[0].label });
     }
 
+    const PARTITION = false;
+
     /**
      * Construct a Structure from a model.
      *
@@ -704,11 +708,16 @@ namespace Structure {
 
             const elements = SortedArray.ofBounds(start as ElementIndex, chains.offsets[c + 1] as ElementIndex);
 
-            if (singleAtomResidues) {
-                partitionAtomicUnitByAtom(model, elements, builder, multiChain, operator);
-            } else if (elements.length > 200000 || isWaterChain(model, c)) {
-                // split up very large chains e.g. lipid bilayers, micelles or water with explicit H
-                partitionAtomicUnitByResidue(model, elements, builder, multiChain, operator);
+            if (PARTITION) {
+                // check for polymer to exclude CA/P-only models
+                if (singleAtomResidues && !isPolymerChain(model, c)) {
+                    partitionAtomicUnitByAtom(model, elements, builder, multiChain, operator);
+                } else if (elements.length > 200000 || isWaterChain(model, c)) {
+                    // split up very large chains e.g. lipid bilayers, micelles or water with explicit H
+                    partitionAtomicUnitByResidue(model, elements, builder, multiChain, operator);
+                } else {
+                    builder.addUnit(Unit.Kind.Atomic, model, operator, elements, multiChain ? Unit.Trait.MultiChain : Unit.Trait.None);
+                }
             } else {
                 builder.addUnit(Unit.Kind.Atomic, model, operator, elements, multiChain ? Unit.Trait.MultiChain : Unit.Trait.None);
             }
@@ -732,6 +741,11 @@ namespace Structure {
         return model.entities.data.type.value(e) === 'water';
     }
 
+    function isPolymerChain(model: Model, chainIndex: ChainIndex) {
+        const e = model.atomicHierarchy.index.getEntityFromChain(chainIndex);
+        return model.entities.data.type.value(e) === 'polymer';
+    }
+
     function partitionAtomicUnitByAtom(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean, operator: SymmetryOperator) {
         const { x, y, z } = model.atomicConformation;
         const position = { x, y, z, indices };
@@ -1124,6 +1138,11 @@ namespace Structure {
             return Size.Large;
         }
     }
+
+    //
+
+    export type Index = number;
+    export const Index = CustomStructureProperty.createSimple<Index>('index', 'root');
 }
 
 export default Structure;

+ 15 - 8
src/mol-model/structure/structure/unit/bonds/inter-compute.ts

@@ -5,7 +5,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { BondType } from '../../../model/types';
+import { BondType, MoleculeType } from '../../../model/types';
 import Structure from '../../structure';
 import Unit from '../../unit';
 import { getElementIdx, getElementPairThreshold, getElementThreshold, isHydrogen, BondComputationProps, MetalsSet, DefaultBondComputationProps } from './common';
@@ -38,10 +38,10 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
     const { elements: atomsB, residueIndex: residueIndexB } = unitB;
     const atomCount = unitA.elements.length;
 
-    const { type_symbol: type_symbolA, label_alt_id: label_alt_idA, label_atom_id: label_atom_idA } = unitA.model.atomicHierarchy.atoms;
-    const { type_symbol: type_symbolB, label_alt_id: label_alt_idB, label_atom_id: label_atom_idB } = unitB.model.atomicHierarchy.atoms;
-    const { label_comp_id: label_comp_idA, auth_seq_id: auth_seq_idA } = unitA.model.atomicHierarchy.residues;
-    const { label_comp_id: label_comp_idB, auth_seq_id: auth_seq_idB } = unitB.model.atomicHierarchy.residues;
+    const { type_symbol: type_symbolA, label_alt_id: label_alt_idA, label_atom_id: label_atom_idA, label_comp_id: label_comp_idA } = unitA.model.atomicHierarchy.atoms;
+    const { type_symbol: type_symbolB, label_alt_id: label_alt_idB, label_atom_id: label_atom_idB, label_comp_id: label_comp_idB } = unitB.model.atomicHierarchy.atoms;
+    const { auth_seq_id: auth_seq_idA } = unitA.model.atomicHierarchy.residues;
+    const { auth_seq_id: auth_seq_idB } = unitB.model.atomicHierarchy.residues;
     const { occupancy: occupancyA } = unitA.model.atomicConformation;
     const { occupancy: occupancyB } = unitB.model.atomicConformation;
     const hasOccupancy = occupancyA.isDefined && occupancyB.isDefined;
@@ -75,6 +75,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
                 const _bI = SortedArray.indexOf(unitA.elements, indexPairs.b[i]) as StructureElement.UnitIndex;
                 if (_bI < 0) continue;
                 if (symmetryA[i] === symmetryB[i]) continue;
+                if (type_symbolA.value(aI) === 'H' && type_symbolB.value(indexPairs.b[i]) === 'H') continue;
                 if (symmUnitA === symmetryA[i] && symmUnitB === symmetryB[i]) {
                     builder.add(_aI, _bI, { order: order[i], flag: BondType.Flag.Covalent });
                 }
@@ -102,9 +103,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
             if (added) continue;
         }
 
-        // ignore atoms with zero occupancy (assuming they are not actually atoms)
         const occA = occupancyA.value(aI);
-        if (hasOccupancy && occA === 0) continue;
 
         const { indices, count, squaredDistances } = lookup3d.find(imageA[0], imageA[1], imageA[2], MAX_RADIUS);
         if (count === 0) continue;
@@ -193,7 +192,15 @@ function findBonds(structure: Structure, props: InterBondComputationProps) {
 function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondComputationProps>): InterUnitBonds {
     return findBonds(structure, {
         ...DefaultBondComputationProps,
-        validUnitPair: (props && props.validUnitPair) || Structure.validUnitPair,
+        validUnitPair: (props && props.validUnitPair) || ((s, a, b) => {
+            const mtA = a.model.atomicHierarchy.derived.residue.moleculeType;
+            const mtB = b.model.atomicHierarchy.derived.residue.moleculeType;
+            const notWater = (
+                (!Unit.isAtomic(a) || mtA[a.residueIndex[a.elements[0]]] !== MoleculeType.Water) &&
+                (!Unit.isAtomic(b) || mtB[b.residueIndex[b.elements[0]]] !== MoleculeType.Water)
+            );
+            return Structure.validUnitPair(s, a, b) && notWater;
+        }),
     });
 }
 

+ 4 - 7
src/mol-model/structure/structure/unit/bonds/intra-compute.ts

@@ -38,9 +38,8 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
     const { x, y, z } = unit.model.atomicConformation;
     const atomCount = unit.elements.length;
     const { elements: atoms, residueIndex, chainIndex } = unit;
-    const { type_symbol, label_atom_id, label_alt_id } = unit.model.atomicHierarchy.atoms;
-    const { occupancy } = unit.model.atomicConformation;
-    const { label_comp_id, label_seq_id } = unit.model.atomicHierarchy.residues;
+    const { type_symbol, label_atom_id, label_alt_id, label_comp_id } = unit.model.atomicHierarchy.atoms;
+    const { label_seq_id } = unit.model.atomicHierarchy.residues;
     const { index } = unit.model.atomicHierarchy;
     const { byEntityKey } = unit.model.sequence;
     const query3d = unit.lookup3d;
@@ -68,6 +67,7 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
                 const _bI = SortedArray.indexOf(unit.elements, indexPairs.b[i]) as StructureElement.UnitIndex;
                 if (_bI < 0) continue;
                 if (edgeProps.symmetryA[i] !== edgeProps.symmetryB[i]) continue;
+                if (type_symbol.value(aI) === 'H' && type_symbol.value(indexPairs.b[i]) === 'H') continue;
                 atomA[atomA.length] = _aI;
                 atomB[atomB.length] = _bI;
                 order[order.length] = edgeProps.order[i];
@@ -100,7 +100,7 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
         }
 
         const raI = residueIndex[aI];
-        const compId = label_comp_id.value(raI);
+        const compId = label_comp_id.value(aI);
 
         if (!props.forceCompute && raI !== lastResidue) {
             if (!!component && component.entries.has(compId)) {
@@ -117,9 +117,6 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
         }
         lastResidue = raI;
 
-        // ignore atoms with zero occupancy (assuming they are not actually atoms)
-        if (occupancy.isDefined && occupancy.value(aI) === 0) continue;
-
         const aeI = getElementIdx(type_symbol.value(aI));
         const atomIdA = label_atom_id.value(aI);
         const componentPairs = componentMap ? componentMap.get(atomIdA) : void 0;

+ 2 - 3
src/mol-model/structure/structure/unit/rings.ts

@@ -99,11 +99,10 @@ namespace UnitRing {
 
     export function isAromatic(unit: Unit.Atomic, ring: UnitRing): boolean {
         const { elements, bonds: { b, offset, edgeProps: { flags } } } = unit;
-        const { type_symbol } = unit.model.atomicHierarchy.atoms;
-        const { label_comp_id } = unit.model.atomicHierarchy.residues;
+        const { type_symbol, label_comp_id } = unit.model.atomicHierarchy.atoms;
 
         // ignore Proline (can be flat because of bad geometry)
-        if (label_comp_id.value(unit.getResidueIndex(ring[0])) === 'PRO') return false;
+        if (label_comp_id.value(unit.elements[ring[0]]) === 'PRO') return false;
 
         let aromaticBondCount = 0;
         let hasAromaticRingElement = false;

+ 0 - 35
src/mol-model/structure/util.ts

@@ -45,41 +45,6 @@ export function getAtomIdForAtomRole(polymerType: PolymerType, atomRole: AtomRol
     return EmptyAtomIds;
 }
 
-export function residueLabel(model: Model, rI: number) {
-    const { residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy;
-    const { label_comp_id, label_seq_id } = residues;
-    const { label_asym_id } = chains;
-    const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]];
-    return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`;
-}
-
-export function elementLabel(model: Model, index: ElementIndex) {
-    const { atoms, residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy;
-    const { label_atom_id } = atoms;
-    const { auth_seq_id, label_comp_id } = residues;
-    const { auth_asym_id } = chains;
-
-    const residueIndex = residueAtomSegments.index[index];
-    const chainIndex = chainAtomSegments.index[residueIndex];
-
-    return `[${label_comp_id.value(residueIndex)}]${auth_seq_id.value(residueIndex)}:${auth_asym_id.value(chainIndex)}.${label_atom_id.value(index)}`;
-}
-
-// const centerPos = Vec3.zero()
-// const centerMin = Vec3.zero()
-// export function getCenterAndRadius(centroid: Vec3, unit: Unit, indices: ArrayLike<number>) {
-//     const pos = unit.conformation.position
-//     const { elements } = unit
-//     Vec3.set(centroid, 0, 0, 0)
-//     for (let i = 0, il = indices.length; i < il; ++i) {
-//         pos(elements[indices[i]], centerPos)
-//         Vec3.add(centroid, centroid, centerPos)
-//         Vec3.min(centerMin, centerMin, centerPos)
-//     }
-//     Vec3.scale(centroid, centroid, 1/indices.length)
-//     return Vec3.distance(centerMin, centroid)
-// }
-
 const tmpPositionsVec = Vec3.zero();
 export function getPositions(unit: Unit, indices: ArrayLike<number>): NumberArray {
     const pos = unit.conformation.position;

+ 4 - 4
src/mol-plugin-state/builder/structure/hierarchy-preset.ts

@@ -140,8 +140,8 @@ async function applyCrystalSymmetry(props: { ijkMin: Vec3, ijkMax: Vec3, theme?:
 const unitcell = TrajectoryHierarchyPresetProvider({
     id: 'preset-trajectory-unitcell',
     display: {
-        name: 'Unitcell', group: 'Preset',
-        description: 'Shows the fully populated unitcell.'
+        name: 'Unit Cell', group: 'Preset',
+        description: 'Shows the fully populated unit cell.'
     },
     isApplicable: o => {
         return Model.hasCrystalSymmetry(o.data[0]);
@@ -155,8 +155,8 @@ const unitcell = TrajectoryHierarchyPresetProvider({
 const supercell = TrajectoryHierarchyPresetProvider({
     id: 'preset-trajectory-supercell',
     display: {
-        name: 'Supercell', group: 'Preset',
-        description: 'Shows the supercell, i.e. the central unitcell and all adjacent unitcells.'
+        name: 'Super Cell', group: 'Preset',
+        description: 'Shows the super cell, i.e. the central unit cell and all adjacent unit cells.'
     },
     isApplicable: o => {
         return Model.hasCrystalSymmetry(o.data[0]);

+ 56 - 13
src/mol-plugin-state/builder/structure/representation-preset.ts

@@ -16,6 +16,8 @@ import { StateObjectRef, StateObjectSelector } from '../../../mol-state';
 import { StaticStructureComponentType } from '../../helpers/structure-component';
 import { StructureSelectionQueries as Q } from '../../helpers/structure-selection-query';
 import { PluginConfig } from '../../../mol-plugin/config';
+import { StructureFocusRepresentation } from '../../../mol-plugin/behavior/dynamic/selection/structure-focus-representation';
+import { createStructureColorThemeParams } from '../../helpers/structure-representation-params';
 
 export interface StructureRepresentationPresetProvider<P = any, S extends _Result = _Result> extends PresetProvider<PluginStateObject.Molecule.Structure, P, S> { }
 export function StructureRepresentationPresetProvider<P, S extends _Result>(repr: StructureRepresentationPresetProvider<P, S>) { return repr; }
@@ -30,7 +32,14 @@ export namespace StructureRepresentationPresetProvider {
     export const CommonParams = {
         ignoreHydrogens: PD.Optional(PD.Boolean(false)),
         quality: PD.Optional(PD.Select<VisualQuality>('auto', VisualQualityOptions)),
-        globalThemeName: PD.Optional(PD.Text<ColorTheme.BuiltIn>(''))
+        theme: PD.Optional(PD.Group({
+            globalName: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
+            carbonByChainId: PD.Optional(PD.Boolean(true)),
+            focus: PD.Optional(PD.Group({
+                name: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
+                params: PD.Optional(PD.Value<ColorTheme.BuiltInParams<ColorTheme.BuiltIn>>({} as any))
+            }))
+        }))
     };
     export type CommonParams = PD.ValuesFor<typeof CommonParams>
 
@@ -43,9 +52,20 @@ export namespace StructureRepresentationPresetProvider {
         };
         if (params.quality && params.quality !== 'auto') typeParams.quality = params.quality;
         if (params.ignoreHydrogens !== void 0) typeParams.ignoreHydrogens = !!params.ignoreHydrogens;
-        const color: ColorTheme.BuiltIn | undefined = params.globalThemeName ? params.globalThemeName : void 0;
+        const color: ColorTheme.BuiltIn | undefined = params.theme?.globalName ? params.theme?.globalName : void 0;
+        const ballAndStickColor: ColorTheme.BuiltInParams<'element-symbol'> = typeof params.theme?.carbonByChainId !== 'undefined' ? { carbonByChainId: !!params.theme?.carbonByChainId } : { };
 
-        return { update, builder, color, typeParams };
+        return { update, builder, color, typeParams, ballAndStickColor };
+    }
+
+    export function updateFocusRepr<T extends ColorTheme.BuiltIn>(plugin: PluginContext, structure: Structure, themeName: T | undefined, themeParams: ColorTheme.BuiltInParams<T> | undefined) {
+        if (!themeName && !themeParams) return;
+
+        return plugin.state.updateBehavior(StructureFocusRepresentation, p => {
+            const c = createStructureColorThemeParams(plugin, structure, 'ball-and-stick', themeName, themeParams);
+            p.surroundingsParams.colorTheme = c;
+            p.targetParams.colorTheme = c;
+        });
     }
 }
 
@@ -54,6 +74,7 @@ type _Result = StructureRepresentationPresetProvider.Result
 const CommonParams = StructureRepresentationPresetProvider.CommonParams;
 type CommonParams = StructureRepresentationPresetProvider.CommonParams
 const reprBuilder = StructureRepresentationPresetProvider.reprBuilder;
+const updateFocusRepr = StructureRepresentationPresetProvider.updateFocusRepr;
 
 const auto = StructureRepresentationPresetProvider({
     id: 'preset-structure-representation-auto',
@@ -111,6 +132,8 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
             nonStandard: await presetStaticComponent(plugin, structureCell, 'non-standard'),
             branched: await presetStaticComponent(plugin, structureCell, 'branched', { label: 'Carbohydrate' }),
             water: await presetStaticComponent(plugin, structureCell, 'water'),
+            ion: await presetStaticComponent(plugin, structureCell, 'ion'),
+            lipid: await presetStaticComponent(plugin, structureCell, 'lipid'),
             coarse: await presetStaticComponent(plugin, structureCell, 'coarse')
         };
 
@@ -119,18 +142,26 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
             sizeFactor: structure.isCoarseGrained ? 0.8 : 0.2,
         };
 
-        const { update, builder, typeParams, color } = reprBuilder(plugin, params);
+        // TODO make configurable
+        const waterType = (components.water?.obj?.data?.elementCount || 0) > 50_000 ? 'line' : 'ball-and-stick';
+        const lipidType = (components.lipid?.obj?.data?.elementCount || 0) > 20_000 ? 'line' : 'ball-and-stick';
+
+        const { update, builder, typeParams, color, ballAndStickColor } = reprBuilder(plugin, params);
+
         const representations = {
             polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color }, { tag: 'polymer' }),
-            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams, color }, { tag: 'ligand' }),
-            nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color: color || 'polymer-id' }, { tag: 'non-standard' }),
-            branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.3 }, color }, { tag: 'branched-ball-and-stick' }),
+            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'ligand' }),
+            nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'non-standard' }),
+            branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.3 }, color, colorParams: ballAndStickColor }, { tag: 'branched-ball-and-stick' }),
             branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }, { tag: 'branched-snfg-3d' }),
-            water: builder.buildRepresentation(update, components.water, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.6 }, color }, { tag: 'water' }),
-            coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'polymer-id' }, { tag: 'coarse' })
+            water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6 }, color }, { tag: 'water' }),
+            ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonByChainId: false } }, { tag: 'ion' }),
+            lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6 }, color, colorParams: { carbonByChainId: false } }, { tag: 'lipid' }),
+            coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id' }, { tag: 'coarse' })
         };
 
         await update.commit({ revertOnError: false });
+        await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
 
         return { components, representations };
     }
@@ -168,6 +199,8 @@ const proteinAndNucleic = StructureRepresentationPresetProvider({
         };
 
         await update.commit({ revertOnError: true });
+        await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
+
         return { components, representations };
     }
 });
@@ -215,6 +248,8 @@ const coarseSurface = StructureRepresentationPresetProvider({
         };
 
         await update.commit({ revertOnError: true });
+        await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
+
         return { components, representations };
     }
 });
@@ -245,6 +280,8 @@ const polymerCartoon = StructureRepresentationPresetProvider({
         };
 
         await update.commit({ revertOnError: true });
+        await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
+
         return { components, representations };
     }
 });
@@ -267,17 +304,23 @@ const atomicDetail = StructureRepresentationPresetProvider({
             all: await presetStaticComponent(plugin, structureCell, 'all'),
             branched: undefined
         };
-        if (params.showCarbohydrateSymbol) {
+
+        const structure = structureCell.obj!.data;
+        const highElementCount = structure.elementCount > 200_000; // TODO make configurable
+        const atomicType = highElementCount ? 'line' : 'ball-and-stick';
+        const showCarbohydrateSymbol = params.showCarbohydrateSymbol && !highElementCount;
+
+        if (showCarbohydrateSymbol) {
             Object.assign(components, {
                 branched: await presetStaticComponent(plugin, structureCell, 'branched', { label: 'Carbohydrate' }),
             });
         }
 
-        const { update, builder, typeParams, color } = reprBuilder(plugin, params);
+        const { update, builder, typeParams, color, ballAndStickColor } = reprBuilder(plugin, params);
         const representations = {
-            all: builder.buildRepresentation(update, components.all, { type: 'ball-and-stick', typeParams, color }, { tag: 'all' }),
+            all: builder.buildRepresentation(update, components.all, { type: atomicType, typeParams, color, colorParams: ballAndStickColor }, { tag: 'all' }),
         };
-        if (params.showCarbohydrateSymbol) {
+        if (showCarbohydrateSymbol) {
             Object.assign(representations, {
                 snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color }, { tag: 'snfg-3d' }),
             });

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

@@ -16,6 +16,7 @@ import { PluginStateObject } from '../../objects';
 import { StructureRepresentation3D } from '../../transforms/representation';
 import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from './representation-preset';
 import { arrayRemoveInPlace } from '../../../mol-util/array';
+import { PluginConfig } from '../../../mol-plugin/config';
 
 // TODO factor out code shared with TrajectoryHierarchyBuilder?
 
@@ -102,10 +103,14 @@ export class StructureRepresentationBuilder {
             return;
         }
 
-        const prms = params || (provider.params
-            ? PD.getDefaultValues(provider.params(cell.obj, this.plugin) as PD.Params)
+        const pd = provider.params?.(cell.obj, this.plugin) as PD.Params || {};
+        let prms = params || (provider.params
+            ? PD.getDefaultValues(pd)
             : {});
 
+        const defaults = this.plugin.config.get(PluginConfig.Structure.DefaultRepresentationPresetParams);
+        prms = PD.merge(pd, defaults, prms);
+
         const task = Task.create(`${provider.display.name}`, () => provider.apply(cell, prms, this.plugin) as Promise<any>);
         return this.plugin.runTask(task);
     }

+ 9 - 0
src/mol-plugin-state/formats/shape.ts

@@ -7,6 +7,9 @@
 
 import { StateTransforms } from '../transforms';
 import { DataFormatProvider } from './provider';
+import { PluginContext } from '../../mol-plugin/context';
+import { StateObjectRef } from '../../mol-state';
+import { PluginStateObject } from '../objects';
 
 const Category = 'Shape';
 
@@ -25,6 +28,12 @@ export const PlyProvider = DataFormatProvider({
         await format.commit();
 
         return { format: format.selector, shape: shape.selector };
+    },
+    visuals(plugin: PluginContext, data: { shape: StateObjectRef<PluginStateObject.Shape.Provider> }) {
+        const repr = plugin.state.data.build()
+            .to(data.shape)
+            .apply(StateTransforms.Representation.ShapeRepresentation3D);
+        return repr.commit();
     }
 });
 

+ 15 - 0
src/mol-plugin-state/formats/structure.ts

@@ -41,9 +41,24 @@ export const DcdProvider = DataFormatProvider({
     }
 });
 
+export const XtcProvider = DataFormatProvider({
+    label: 'XTC',
+    description: 'XTC',
+    category: Category,
+    binaryExtensions: ['xtc'],
+    parse: (plugin, data) => {
+        const coordinates = plugin.state.data.build()
+            .to(data)
+            .apply(StateTransforms.Model.CoordinatesFromXtc);
+
+        return coordinates.commit();
+    }
+});
+
 export const BuiltInStructureFormats = [
     ['psf', PsfProvider] as const,
     ['dcd', DcdProvider] as const,
+    ['xtc', XtcProvider] as const,
 ] as const;
 
 export type BuildInStructureFormat = (typeof BuiltInStructureFormats)[number][0]

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