瀏覽代碼

Merge branch 'master' into graphics

# Conflicts:
#	src/mol-gl/shader-code.ts
Alexander Rose 6 年之前
父節點
當前提交
2a25e0abcd
共有 38 個文件被更改,包括 2476 次插入1165 次删除
  1. 762 904
      package-lock.json
  2. 15 15
      package.json
  3. 1 1
      src/mol-geo/geometry/mesh/mesh-builder.ts
  4. 13 13
      src/mol-gl/shader-code.ts
  5. 6 2
      src/mol-gl/webgl/render-item.ts
  6. 15 15
      src/mol-io/reader/_spec/mol2.spec.ts
  7. 152 0
      src/mol-io/reader/_spec/ply.spec.ts
  8. 1 2
      src/mol-io/reader/cif/data-model.ts
  9. 3 10
      src/mol-io/reader/csv/data-model.ts
  10. 263 0
      src/mol-io/reader/ply/parser.ts
  11. 79 0
      src/mol-io/reader/ply/schema.ts
  12. 2 2
      src/mol-math/geometry/gaussian-density/gpu.ts
  13. 23 0
      src/mol-math/linear-algebra/3d/vec3.ts
  14. 14 0
      src/mol-math/linear-algebra/_spec/vec3.spec.ts
  15. 260 0
      src/mol-model-formats/shape/ply.ts
  16. 26 0
      src/mol-model-formats/structure/_spec/pdb.spec.ts
  17. 40 4
      src/mol-model-formats/structure/pdb/to-cif.ts
  18. 16 0
      src/mol-model/shape/provider.ts
  19. 4 1
      src/mol-model/shape/shape.ts
  20. 6 1
      src/mol-model/structure/model/properties/seconday-structure.ts
  21. 1 1
      src/mol-model/structure/model/properties/utils/guess-element.ts
  22. 494 105
      src/mol-model/structure/model/properties/utils/secondary-structure.ts
  23. 75 0
      src/mol-model/structure/model/properties/utils/secondary-structure.validation
  24. 32 31
      src/mol-model/structure/model/types.ts
  25. 1 1
      src/mol-plugin/behavior/dynamic/labels.ts
  26. 2 0
      src/mol-plugin/state/actions/data-format.ts
  27. 31 0
      src/mol-plugin/state/actions/shape.ts
  28. 10 0
      src/mol-plugin/state/objects.ts
  29. 18 0
      src/mol-plugin/state/transforms/data.ts
  30. 22 2
      src/mol-plugin/state/transforms/model.ts
  31. 35 1
      src/mol-plugin/state/transforms/representation.ts
  32. 1 1
      src/mol-plugin/util/structure-labels.ts
  33. 1 3
      src/mol-repr/shape/representation.ts
  34. 5 1
      src/mol-theme/color/secondary-structure.ts
  35. 6 6
      src/mol-util/array.ts
  36. 1 1
      src/mol-util/param-definition.ts
  37. 34 35
      src/tests/browser/index.html
  38. 6 7
      src/tests/browser/render-shape.ts

File diff suppressed because it is too large
+ 762 - 904
package-lock.json


+ 15 - 15
package.json

@@ -79,18 +79,18 @@
     "@types/benchmark": "^1.0.31",
     "@types/benchmark": "^1.0.31",
     "@types/compression": "0.0.36",
     "@types/compression": "0.0.36",
     "@types/express": "^4.16.1",
     "@types/express": "^4.16.1",
-    "@types/jest": "^24.0.9",
-    "@types/node": "^11.10.4",
-    "@types/node-fetch": "^2.1.6",
-    "@types/react": "^16.8.6",
-    "@types/react-dom": "^16.8.2",
+    "@types/jest": "^24.0.11",
+    "@types/node": "^11.11.6",
+    "@types/node-fetch": "^2.1.7",
+    "@types/react": "^16.8.8",
+    "@types/react-dom": "^16.8.3",
     "@types/webgl2": "0.0.4",
     "@types/webgl2": "0.0.4",
     "@types/swagger-ui-dist": "3.0.0",
     "@types/swagger-ui-dist": "3.0.0",
     "benchmark": "^2.1.4",
     "benchmark": "^2.1.4",
     "circular-dependency-plugin": "^5.0.2",
     "circular-dependency-plugin": "^5.0.2",
     "concurrently": "^4.1.0",
     "concurrently": "^4.1.0",
     "cpx": "^1.5.0",
     "cpx": "^1.5.0",
-    "css-loader": "^2.1.0",
+    "css-loader": "^2.1.1",
     "extra-watch-webpack-plugin": "^1.0.3",
     "extra-watch-webpack-plugin": "^1.0.3",
     "file-loader": "^3.0.1",
     "file-loader": "^3.0.1",
     "glslify": "^7.0.0",
     "glslify": "^7.0.0",
@@ -99,31 +99,31 @@
     "graphql-code-generator": "^0.18.0",
     "graphql-code-generator": "^0.18.0",
     "graphql-codegen-time": "^0.18.0",
     "graphql-codegen-time": "^0.18.0",
     "graphql-codegen-typescript-template": "^0.18.0",
     "graphql-codegen-typescript-template": "^0.18.0",
-    "jest": "^24.1.0",
+    "jest": "^24.5.0",
     "jest-raw-loader": "^1.0.1",
     "jest-raw-loader": "^1.0.1",
     "mini-css-extract-plugin": "^0.5.0",
     "mini-css-extract-plugin": "^0.5.0",
     "node-sass": "^4.11.0",
     "node-sass": "^4.11.0",
-    "raw-loader": "^1.0.0",
+    "raw-loader": "^2.0.0",
     "resolve-url-loader": "^3.0.1",
     "resolve-url-loader": "^3.0.1",
     "sass-loader": "^7.1.0",
     "sass-loader": "^7.1.0",
     "style-loader": "^0.23.1",
     "style-loader": "^0.23.1",
     "ts-jest": "^24.0.0",
     "ts-jest": "^24.0.0",
-    "tslint": "^5.13.1",
-    "typescript": "^3.3.3",
-    "uglify-js": "^3.4.9",
+    "tslint": "^5.14.0",
+    "typescript": "^3.3.4000",
+    "uglify-js": "^3.5.1",
     "util.promisify": "^1.0.0",
     "util.promisify": "^1.0.0",
     "webpack": "^4.29.6",
     "webpack": "^4.29.6",
-    "webpack-cli": "^3.2.3"
+    "webpack-cli": "^3.3.0"
   },
   },
   "dependencies": {
   "dependencies": {
     "argparse": "^1.0.10",
     "argparse": "^1.0.10",
-    "compression": "^1.7.3",
+    "compression": "^1.7.4",
     "express": "^4.16.4",
     "express": "^4.16.4",
     "graphql": "^14.1.1",
     "graphql": "^14.1.1",
     "immutable": "^3.8.2",
     "immutable": "^3.8.2",
     "node-fetch": "^2.3.0",
     "node-fetch": "^2.3.0",
-    "react": "^16.8.4",
-    "react-dom": "^16.8.4",
+    "react": "^16.8.5",
+    "react-dom": "^16.8.5",
     "rxjs": "^6.4.0",
     "rxjs": "^6.4.0",
     "swagger-ui-dist": "^3.21.0"
     "swagger-ui-dist": "^3.21.0"
   }
   }

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

@@ -45,7 +45,7 @@ export namespace MeshBuilder {
     export function addTriangle(state: State, a: Vec3, b: Vec3, c: Vec3) {
     export function addTriangle(state: State, a: Vec3, b: Vec3, c: Vec3) {
         const { vertices, normals, indices, groups, currentGroup } = state
         const { vertices, normals, indices, groups, currentGroup } = state
         const offset = vertices.elementCount
         const offset = vertices.elementCount
-        
+
         // positions
         // positions
         ChunkedArray.add3(vertices, a[0], a[1], a[2]);
         ChunkedArray.add3(vertices, a[0], a[1], a[2]);
         ChunkedArray.add3(vertices, b[0], b[1], b[2]);
         ChunkedArray.add3(vertices, b[0], b[1], b[2]);

+ 13 - 13
src/mol-gl/shader-code.ts

@@ -1,5 +1,5 @@
 /**
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
  */
@@ -31,38 +31,38 @@ export function ShaderCode(vert: string, frag: string, extensions: ShaderExtensi
 }
 }
 
 
 export const PointsShaderCode = ShaderCode(
 export const PointsShaderCode = ShaderCode(
-    require('mol-gl/shader/points.vert'),
-    require('mol-gl/shader/points.frag'),
+    require('mol-gl/shader/points.vert').default,
+    require('mol-gl/shader/points.frag').default,
     { standardDerivatives: false, fragDepth: false }
     { standardDerivatives: false, fragDepth: false }
 )
 )
 
 
 export const SpheresShaderCode = ShaderCode(
 export const SpheresShaderCode = ShaderCode(
-    require('mol-gl/shader/spheres.vert'),
-    require('mol-gl/shader/spheres.frag'),
+    require('mol-gl/shader/spheres.vert').default,
+    require('mol-gl/shader/spheres.frag').default,
     { standardDerivatives: false, fragDepth: true }
     { standardDerivatives: false, fragDepth: true }
 )
 )
 
 
 export const TextShaderCode = ShaderCode(
 export const TextShaderCode = ShaderCode(
-    require('mol-gl/shader/text.vert'),
-    require('mol-gl/shader/text.frag'),
+    require('mol-gl/shader/text.vert').default,
+    require('mol-gl/shader/text.frag').default,
     { standardDerivatives: true, fragDepth: false }
     { standardDerivatives: true, fragDepth: false }
 )
 )
 
 
 export const LinesShaderCode = ShaderCode(
 export const LinesShaderCode = ShaderCode(
-    require('mol-gl/shader/lines.vert'),
-    require('mol-gl/shader/lines.frag'),
+    require('mol-gl/shader/lines.vert').default,
+    require('mol-gl/shader/lines.frag').default,
     { standardDerivatives: false, fragDepth: false }
     { standardDerivatives: false, fragDepth: false }
 )
 )
 
 
 export const MeshShaderCode = ShaderCode(
 export const MeshShaderCode = ShaderCode(
-    require('mol-gl/shader/mesh.vert'),
-    require('mol-gl/shader/mesh.frag'),
+    require('mol-gl/shader/mesh.vert').default,
+    require('mol-gl/shader/mesh.frag').default,
     { standardDerivatives: true, fragDepth: false }
     { standardDerivatives: true, fragDepth: false }
 )
 )
 
 
 export const DirectVolumeShaderCode = ShaderCode(
 export const DirectVolumeShaderCode = ShaderCode(
-    require('mol-gl/shader/direct-volume.vert'),
-    require('mol-gl/shader/direct-volume.frag'),
+    require('mol-gl/shader/direct-volume.vert').default,
+    require('mol-gl/shader/direct-volume.frag').default,
     { standardDerivatives: false, fragDepth: true }
     { standardDerivatives: false, fragDepth: true }
 )
 )
 
 

+ 6 - 2
src/mol-gl/webgl/render-item.ts

@@ -131,6 +131,7 @@ export function createRenderItem(ctx: WebGLContext, drawMode: DrawMode, shaderCo
     const valueChanges = createValueChanges()
     const valueChanges = createValueChanges()
 
 
     let destroyed = false
     let destroyed = false
+    let currentProgramId = -1
 
 
     return {
     return {
         id,
         id,
@@ -142,9 +143,12 @@ export function createRenderItem(ctx: WebGLContext, drawMode: DrawMode, shaderCo
             const program = programs[variant].value
             const program = programs[variant].value
             const vertexArray = vertexArrays[variant]
             const vertexArray = vertexArrays[variant]
             program.setUniforms(uniformValueEntries)
             program.setUniforms(uniformValueEntries)
-            if (materialId === -1 || materialId !== ctx.currentMaterialId) {
-                // console.log('materialId changed or -1', materialId)
+            if (program.id !== currentProgramId ||
+                materialId === -1 || materialId !== ctx.currentMaterialId
+            ) {
+                // console.log('program.id changed or materialId changed/-1', materialId)
                 program.setUniforms(materialUniformValueEntries)
                 program.setUniforms(materialUniformValueEntries)
+                currentProgramId = program.id
                 ctx.currentMaterialId = materialId
                 ctx.currentMaterialId = materialId
             }
             }
             program.bindTextures(textures)
             program.bindTextures(textures)

+ 15 - 15
src/mol-io/reader/_spec/mol2.spec.ts

@@ -265,10 +265,10 @@ describe('mol2 reader', () => {
         expect(molecule.num_subst).toBe(0);
         expect(molecule.num_subst).toBe(0);
         expect(molecule.num_feat).toBe(0);
         expect(molecule.num_feat).toBe(0);
         expect(molecule.num_sets).toBe(0);
         expect(molecule.num_sets).toBe(0);
-        expect(molecule.mol_type).toBe("SMALL")
-        expect(molecule.charge_type).toBe("GASTEIGER");
-        expect(molecule.status_bits).toBe("");
-        expect(molecule.mol_comment).toBe("");
+        expect(molecule.mol_type).toBe('SMALL')
+        expect(molecule.charge_type).toBe('GASTEIGER');
+        expect(molecule.status_bits).toBe('');
+        expect(molecule.mol_comment).toBe('');
 
 
         // required atom fields
         // required atom fields
         expect(atoms.count).toBe(26);
         expect(atoms.count).toBe(26);
@@ -277,7 +277,7 @@ describe('mol2 reader', () => {
         expect(atoms.x.value(0)).toBeCloseTo(1.7394, 0.001);
         expect(atoms.x.value(0)).toBeCloseTo(1.7394, 0.001);
         expect(atoms.y.value(0)).toBeCloseTo(-2.1169, 0.0001);
         expect(atoms.y.value(0)).toBeCloseTo(-2.1169, 0.0001);
         expect(atoms.z.value(0)).toBeCloseTo(-1.0893, 0.0001);
         expect(atoms.z.value(0)).toBeCloseTo(-1.0893, 0.0001);
-        expect(atoms.atom_type.value(0)).toBe("O.3");
+        expect(atoms.atom_type.value(0)).toBe('O.3');
 
 
         // optional atom fields
         // optional atom fields
         expect(atoms.subst_id.value(0)).toBe(1);
         expect(atoms.subst_id.value(0)).toBe(1);
@@ -316,10 +316,10 @@ describe('mol2 reader', () => {
         expect(molecule.num_subst).toBe(0);
         expect(molecule.num_subst).toBe(0);
         expect(molecule.num_feat).toBe(0);
         expect(molecule.num_feat).toBe(0);
         expect(molecule.num_sets).toBe(0);
         expect(molecule.num_sets).toBe(0);
-        expect(molecule.mol_type).toBe("SMALL")
-        expect(molecule.charge_type).toBe("GASTEIGER");
-        expect(molecule.status_bits).toBe("");
-        expect(molecule.mol_comment).toBe("");
+        expect(molecule.mol_type).toBe('SMALL')
+        expect(molecule.charge_type).toBe('GASTEIGER');
+        expect(molecule.status_bits).toBe('');
+        expect(molecule.mol_comment).toBe('');
 
 
         // required atom fields
         // required atom fields
         expect(atoms.count).toBe(26);
         expect(atoms.count).toBe(26);
@@ -328,7 +328,7 @@ describe('mol2 reader', () => {
         expect(atoms.x.value(0)).toBeCloseTo(1.7394, 0.001);
         expect(atoms.x.value(0)).toBeCloseTo(1.7394, 0.001);
         expect(atoms.y.value(0)).toBeCloseTo(-2.1169, 0.0001);
         expect(atoms.y.value(0)).toBeCloseTo(-2.1169, 0.0001);
         expect(atoms.z.value(0)).toBeCloseTo(-1.0893, 0.0001);
         expect(atoms.z.value(0)).toBeCloseTo(-1.0893, 0.0001);
-        expect(atoms.atom_type.value(0)).toBe("O.3");
+        expect(atoms.atom_type.value(0)).toBe('O.3');
 
 
         // optional atom fields
         // optional atom fields
         expect(atoms.subst_id.value(0)).toBe(1);
         expect(atoms.subst_id.value(0)).toBe(1);
@@ -367,10 +367,10 @@ describe('mol2 reader', () => {
         expect(molecule.num_subst).toBe(0);
         expect(molecule.num_subst).toBe(0);
         expect(molecule.num_feat).toBe(0);
         expect(molecule.num_feat).toBe(0);
         expect(molecule.num_sets).toBe(0);
         expect(molecule.num_sets).toBe(0);
-        expect(molecule.mol_type).toBe("SMALL")
-        expect(molecule.charge_type).toBe("GASTEIGER");
-        expect(molecule.status_bits).toBe("");
-        expect(molecule.mol_comment).toBe("");
+        expect(molecule.mol_type).toBe('SMALL')
+        expect(molecule.charge_type).toBe('GASTEIGER');
+        expect(molecule.status_bits).toBe('');
+        expect(molecule.mol_comment).toBe('');
 
 
         // required atom fields
         // required atom fields
         expect(atoms.count).toBe(26);
         expect(atoms.count).toBe(26);
@@ -379,7 +379,7 @@ describe('mol2 reader', () => {
         expect(atoms.x.value(0)).toBeCloseTo(1.7394, 0.001);
         expect(atoms.x.value(0)).toBeCloseTo(1.7394, 0.001);
         expect(atoms.y.value(0)).toBeCloseTo(-2.1169, 0.0001);
         expect(atoms.y.value(0)).toBeCloseTo(-2.1169, 0.0001);
         expect(atoms.z.value(0)).toBeCloseTo(-1.0893, 0.0001);
         expect(atoms.z.value(0)).toBeCloseTo(-1.0893, 0.0001);
-        expect(atoms.atom_type.value(0)).toBe("O.3");
+        expect(atoms.atom_type.value(0)).toBe('O.3');
 
 
         // optional atom fields
         // optional atom fields
         expect(atoms.subst_id.value(0)).toBe(0);
         expect(atoms.subst_id.value(0)).toBe(0);

+ 152 - 0
src/mol-io/reader/_spec/ply.spec.ts

@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import Ply from '../ply/parser'
+import { PlyTable, PlyList } from '../ply/schema';
+
+const plyString = `ply
+format ascii 1.0
+comment file created by MegaMol
+element vertex 6
+property float x
+property float y
+property float z
+property uchar red
+property uchar green
+property uchar blue
+property uchar alpha
+property float nx
+property float ny
+property float nz
+property int atomid
+property uchar contactcount_r
+property uchar contactcount_g
+property uchar contactcount_b
+property uchar contactsteps_r
+property uchar contactsteps_g
+property uchar contactsteps_b
+property uchar hbonds_r
+property uchar hbonds_g
+property uchar hbonds_b
+property uchar hbondsteps_r
+property uchar hbondsteps_g
+property uchar hbondsteps_b
+property uchar molcount_r
+property uchar molcount_g
+property uchar molcount_b
+property uchar spots_r
+property uchar spots_g
+property uchar spots_b
+property uchar rmsf_r
+property uchar rmsf_g
+property uchar rmsf_b
+element face 2
+property list uchar int vertex_index
+end_header
+130.901 160.016 163.033 90 159 210 255 -0.382 -0.895 -0.231 181 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 171 196 212
+131.372 159.778 162.83 90 159 210 255 -0.618 -0.776 -0.129 178 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 141 177 199
+131.682 159.385 163.089 90 159 210 255 -0.773 -0.579 -0.259 180 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 172 196 212
+131.233 160.386 162.11 90 159 210 255 -0.708 -0.383 -0.594 178 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 141 177 199
+130.782 160.539 162.415 90 159 210 255 -0.482 -0.459 -0.746 181 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 171 196 212
+131.482 160.483 161.621 90 159 210 255 -0.832 -0.431 -0.349 179 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 171 196 212
+3 0 2 1
+3 3 5 4
+`
+
+const plyCubeString = `ply
+format ascii 1.0
+comment test cube
+element vertex 24
+property float32 x
+property float32 y
+property float32 z
+property uint32 material_index
+element face 6
+property list uint8 int32 vertex_indices
+element material 6
+property uint8 red
+property uint8 green
+property uint8 blue
+end_header
+-1 -1 -1 0
+1 -1 -1 0
+1 1 -1 0
+-1 1 -1 0
+1 -1 1 1
+-1 -1 1 1
+-1 1 1 1
+1 1 1 1
+1 1 1 2
+1 1 -1 2
+1 -1 -1 2
+1 -1 1 2
+-1 1 -1 3
+-1 1 1 3
+-1 -1 1 3
+-1 -1 -1 3
+-1 1 1 4
+-1 1 -1 4
+1 1 -1 4
+1 1 1 4
+1 -1 1 5
+1 -1 -1 5
+-1 -1 -1 5
+-1 -1 1 5
+4 0 1 2 3
+4 4 5 6 7
+4 8 9 10 11
+4 12 13 14 15
+4 16 17 18 19
+4 20 21 22 23
+255 0 0
+0 255 0
+0 0 255
+255 255 0
+0 255 255
+255 0 255
+`
+
+
+describe('ply reader', () => {
+    it('basic', async () => {
+        const parsed = await Ply(plyString).run();
+        if (parsed.isError) return;
+        const plyFile = parsed.result;
+
+        const vertex = plyFile.getElement('vertex') as PlyTable
+        if (!vertex) return
+        const x = vertex.getProperty('x')
+        if (!x) return
+        expect(x.value(0)).toEqual(130.901)
+
+        const face = plyFile.getElement('face') as PlyList
+        if (!face) return
+        expect(face.value(0)).toEqual({ count: 3, entries: [0, 2, 1]})
+        expect(face.value(1)).toEqual({ count: 3, entries: [3, 5, 4]})
+
+        expect.assertions(3)
+    });
+
+    it('material', async () => {
+        const parsed = await Ply(plyCubeString).run();
+        if (parsed.isError) return;
+        const plyFile = parsed.result;
+
+        const vertex = plyFile.getElement('vertex') as PlyTable
+        if (!vertex) return
+        expect(vertex.rowCount).toBe(24)
+
+        const face = plyFile.getElement('face') as PlyList
+        if (!face) return
+        expect(face.rowCount).toBe(6)
+
+        const material = plyFile.getElement('face') as PlyTable
+        if (!material) return
+        expect(face.rowCount).toBe(6)
+
+        expect.assertions(3)
+    });
+});

+ 1 - 2
src/mol-io/reader/cif/data-model.ts

@@ -199,7 +199,7 @@ export namespace CifField {
 
 
     export function ofColumn(column: Column<any>): CifField {
     export function ofColumn(column: Column<any>): CifField {
         const { rowCount, valueKind, areValuesEqual } = column;
         const { rowCount, valueKind, areValuesEqual } = column;
-        
+
         let str: CifField['str']
         let str: CifField['str']
         let int: CifField['int']
         let int: CifField['int']
         let float: CifField['float']
         let float: CifField['float']
@@ -219,7 +219,6 @@ export namespace CifField {
             default:
             default:
                 throw new Error('unsupported')
                 throw new Error('unsupported')
         }
         }
-                
 
 
         return {
         return {
             __array: void 0,
             __array: void 0,

+ 3 - 10
src/mol-io/reader/csv/data-model.ts

@@ -9,12 +9,11 @@ import { CifField as CsvColumn } from '../cif/data-model'
 export { CsvColumn }
 export { CsvColumn }
 
 
 export interface CsvFile {
 export interface CsvFile {
-    readonly name?: string,
     readonly table: CsvTable
     readonly table: CsvTable
 }
 }
 
 
-export function CsvFile(table: CsvTable, name?: string): CsvFile {
-    return { name, table };
+export function CsvFile(table: CsvTable): CsvFile {
+    return { table };
 }
 }
 
 
 export interface CsvTable {
 export interface CsvTable {
@@ -27,10 +26,4 @@ export function CsvTable(rowCount: number, columnNames: string[], columns: CsvCo
     return { rowCount, columnNames: [...columnNames], getColumn(name) { return columns[name]; } };
     return { rowCount, columnNames: [...columnNames], getColumn(name) { return columns[name]; } };
 }
 }
 
 
-export type CsvColumns = { [name: string]: CsvColumn }
-
-// export namespace CsvTable {
-//     export function empty(name: string): Table {
-//         return { rowCount: 0, name, fieldNames: [], getColumn(name: string) { return void 0; } };
-//     };
-// }
+export type CsvColumns = { [name: string]: CsvColumn }

+ 263 - 0
src/mol-io/reader/ply/parser.ts

@@ -0,0 +1,263 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ReaderResult as Result } from '../result'
+import { Task, RuntimeContext } from 'mol-task'
+import { PlyFile, PlyType, PlyElement } from './schema';
+import { Tokenizer, TokenBuilder, Tokens } from '../common/text/tokenizer';
+import { Column } from 'mol-data/db';
+import { TokenColumn } from '../common/text/column/token';
+
+interface State {
+    data: string
+    tokenizer: Tokenizer
+    runtimeCtx: RuntimeContext
+
+    comments: string[]
+    elementSpecs: ElementSpec[]
+    elements: PlyElement[]
+}
+
+function State(data: string, runtimeCtx: RuntimeContext): State {
+    const tokenizer = Tokenizer(data)
+    return {
+        data,
+        tokenizer,
+        runtimeCtx,
+
+        comments: [],
+        elementSpecs: [],
+        elements: []
+    }
+}
+
+type ColumnProperty = { kind: 'column', type: PlyType, name: string }
+type ListProperty = { kind: 'list', countType: PlyType, dataType: PlyType, name: string }
+type Property = ColumnProperty | ListProperty
+
+type TableElementSpec = { kind: 'table', name: string, count: number, properties: ColumnProperty[] }
+type ListElementSpec = { kind: 'list', name: string, count: number, property: ListProperty }
+type ElementSpec = TableElementSpec | ListElementSpec
+
+function markHeader(tokenizer: Tokenizer) {
+    const endHeaderIndex = tokenizer.data.indexOf('end_header', tokenizer.position)
+    if (endHeaderIndex === -1) throw new Error(`no 'end_header' record found`)
+    // TODO set `tokenizer.lineNumber` correctly
+    tokenizer.tokenStart = tokenizer.position
+    tokenizer.tokenEnd = endHeaderIndex
+    tokenizer.position = endHeaderIndex
+    Tokenizer.eatLine(tokenizer)
+}
+
+function parseHeader(state: State) {
+    const { tokenizer, comments, elementSpecs } = state
+
+    markHeader(tokenizer)
+    const headerLines = Tokenizer.getTokenString(tokenizer).split(/\r?\n/)
+
+    if (headerLines[0] !== 'ply') throw new Error(`data not starting with 'ply'`)
+    if (headerLines[1] !== 'format ascii 1.0') throw new Error(`format not 'ascii 1.0'`)
+
+    let currentName: string | undefined
+    let currentCount: number | undefined
+    let currentProperties: Property[] | undefined
+
+
+    function addCurrentElementSchema() {
+        if (currentName !== undefined && currentCount !== undefined && currentProperties !== undefined) {
+            let isList = false
+            for (let i = 0, il = currentProperties.length; i < il; ++i) {
+                const p = currentProperties[i]
+                if (p.kind === 'list') {
+                    isList = true
+                    break
+                }
+            }
+            if (isList && currentProperties.length !== 1) throw new Error('expected single list property')
+            if (isList) {
+                elementSpecs.push({
+                    kind: 'list',
+                    name: currentName,
+                    count: currentCount,
+                    property: currentProperties[0] as ListProperty
+                })
+            } else {
+                elementSpecs.push({
+                    kind: 'table',
+                    name: currentName,
+                    count: currentCount,
+                    properties: currentProperties as ColumnProperty[]
+                })
+            }
+        }
+    }
+
+    for (let i = 2, il = headerLines.length; i < il; ++i) {
+        const l = headerLines[i]
+        const ls = l.split(' ')
+        if (l.startsWith('comment')) {
+            comments.push(l.substr(8))
+        } else if (l.startsWith('element')) {
+            addCurrentElementSchema()
+            currentProperties = []
+            currentName = ls[1]
+            currentCount = parseInt(ls[2])
+        } else if (l.startsWith('property')) {
+            if (currentProperties === undefined) throw new Error(`properties outside of element`)
+            if (ls[1] === 'list') {
+                currentProperties.push({
+                    kind: 'list',
+                    countType: PlyType(ls[2]),
+                    dataType: PlyType(ls[3]),
+                    name: ls[4]
+                })
+            } else {
+                currentProperties.push({
+                    kind: 'column',
+                    type: PlyType(ls[1]),
+                    name: ls[2]
+                })
+            }
+        } else if (l.startsWith('end_header')) {
+            addCurrentElementSchema()
+        } else {
+            console.warn('unknown header line')
+        }
+    }
+}
+
+function parseElements(state: State) {
+    const { elementSpecs } = state
+    for (let i = 0, il = elementSpecs.length; i < il; ++i) {
+        const spec = elementSpecs[i]
+        if (spec.kind === 'table') parseTableElement(state, spec)
+        else if (spec.kind === 'list') parseListElement(state, spec)
+    }
+}
+
+function getColumnSchema(type: PlyType): Column.Schema {
+    switch (type) {
+        case 'char': case 'uchar': case 'int8': case 'uint8':
+        case 'short': case 'ushort': case 'int16': case 'uint16':
+        case 'int': case 'uint': case 'int32': case 'uint32':
+            return Column.Schema.int
+        case 'float': case 'double': case 'float32': case 'float64':
+            return Column.Schema.float
+    }
+}
+
+function parseTableElement(state: State, spec: TableElementSpec) {
+    const { elements, tokenizer } = state
+    const { count, properties } = spec
+    const propertyCount = properties.length
+    const propertyNames: string[] = []
+    const propertyTypes: PlyType[] = []
+    const propertyTokens: Tokens[] = []
+    const propertyColumns = new Map<string, Column<number>>()
+
+    for (let i = 0, il = propertyCount; i < il; ++i) {
+        const tokens = TokenBuilder.create(tokenizer.data, count * 2)
+        propertyTokens.push(tokens)
+    }
+
+    for (let i = 0, il = count; i < il; ++i) {
+        for (let j = 0, jl = propertyCount; j < jl; ++j) {
+            Tokenizer.skipWhitespace(tokenizer)
+            Tokenizer.markStart(tokenizer)
+            Tokenizer.eatValue(tokenizer)
+            TokenBuilder.addUnchecked(propertyTokens[j], tokenizer.tokenStart, tokenizer.tokenEnd)
+        }
+    }
+
+    for (let i = 0, il = propertyCount; i < il; ++i) {
+        const { type, name } = properties[i]
+        const column = TokenColumn(propertyTokens[i], getColumnSchema(type))
+        propertyNames.push(name)
+        propertyTypes.push(type)
+        propertyColumns.set(name, column)
+    }
+
+    elements.push({
+        kind: 'table',
+        rowCount: count,
+        propertyNames,
+        propertyTypes,
+        getProperty: (name: string) => propertyColumns.get(name)
+    })
+}
+
+function parseListElement(state: State, spec: ListElementSpec) {
+    const { elements, tokenizer } = state
+    const { count, property } = spec
+
+    // initial tokens size assumes triangle index data
+    const tokens = TokenBuilder.create(tokenizer.data, count * 2 * 3)
+
+    const offsets = new Uint32Array(count + 1)
+    let entryCount = 0
+
+    for (let i = 0, il = count; i < il; ++i) {
+        // skip over row entry count as it is determined by line break
+        Tokenizer.skipWhitespace(tokenizer)
+        Tokenizer.eatValue(tokenizer)
+
+        while (Tokenizer.skipWhitespace(tokenizer) !== 10) {
+            ++entryCount
+            Tokenizer.markStart(tokenizer)
+            Tokenizer.eatValue(tokenizer)
+            TokenBuilder.addToken(tokens, tokenizer)
+        }
+        offsets[i + 1] = entryCount
+    }
+
+    // console.log(tokens.indices)
+    // console.log(offsets)
+
+    /** holds row value entries transiently */
+    const listValue = {
+        entries: [] as number[],
+        count: 0
+    }
+
+    const column = TokenColumn(tokens, getColumnSchema(property.dataType))
+
+    elements.push({
+        kind: 'list',
+        rowCount: count,
+        name: property.name,
+        type: property.dataType,
+        value: (row: number) => {
+            const start = offsets[row]
+            const end = offsets[row + 1]
+            for (let i = start; i < end; ++i) {
+                listValue.entries[i - start] = column.value(i)
+            }
+            listValue.count = end - start
+            return listValue
+        }
+    })
+}
+
+async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<PlyFile>> {
+    const state = State(data, ctx);
+    ctx.update({ message: 'Parsing...', current: 0, max: data.length });
+    parseHeader(state)
+    // console.log(state.comments)
+    // console.log(JSON.stringify(state.elementSpecs, undefined, 4))
+    parseElements(state)
+    const { elements, elementSpecs, comments } = state
+    const elementNames = elementSpecs.map(s => s.name)
+    const result = PlyFile(elements, elementNames, comments)
+    return Result.success(result);
+}
+
+export function parse(data: string) {
+    return Task.create<Result<PlyFile>>('Parse PLY', async ctx => {
+        return await parseInternal(data, ctx)
+    })
+}
+
+export default parse;

+ 79 - 0
src/mol-io/reader/ply/schema.ts

@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Column } from 'mol-data/db';
+
+// http://paulbourke.net/dataformats/ply/
+// https://en.wikipedia.org/wiki/PLY_(file_format)
+
+export const PlyTypeByteLength = {
+    'char': 1,
+    'uchar': 1,
+    'short': 2,
+    'ushort': 2,
+    'int': 4,
+    'uint': 4,
+    'float': 4,
+    'double': 8,
+
+    'int8': 1,
+    'uint8': 1,
+    'int16': 2,
+    'uint16': 2,
+    'int32': 4,
+    'uint32': 4,
+    'float32': 4,
+    'float64': 8
+}
+export type PlyType = keyof typeof PlyTypeByteLength
+export const PlyTypes = new Set(Object.keys(PlyTypeByteLength))
+export function PlyType(str: string) {
+    if (!PlyTypes.has(str)) throw new Error(`unknown ply type '${str}'`)
+    return str as PlyType
+}
+
+export interface PlyFile {
+    readonly comments: ReadonlyArray<string>
+    readonly elementNames: ReadonlyArray<string>
+    getElement(name: string): PlyElement | undefined
+}
+
+export function PlyFile(elements: PlyElement[], elementNames: string[], comments: string[]): PlyFile {
+    const elementMap = new Map<string, PlyElement>()
+    for (let i = 0, il = elementNames.length; i < il; ++i) {
+        elementMap.set(elementNames[i], elements[i])
+    }
+    return {
+        comments,
+        elementNames,
+        getElement: (name: string) => {
+            return elementMap.get(name)
+        }
+    };
+}
+
+export type PlyElement = PlyTable | PlyList
+
+export interface PlyTable {
+    readonly kind: 'table'
+    readonly rowCount: number
+    readonly propertyNames: ReadonlyArray<string>
+    readonly propertyTypes: ReadonlyArray<PlyType>
+    getProperty(name: string): Column<number> | undefined
+}
+
+export interface PlyListValue {
+    readonly entries: ArrayLike<number>
+    readonly count: number
+}
+
+export interface PlyList {
+    readonly kind: 'list'
+    readonly rowCount: number,
+    readonly name: string,
+    readonly type: PlyType,
+    value: (row: number) => PlyListValue
+}

+ 2 - 2
src/mol-math/geometry/gaussian-density/gpu.ts

@@ -45,8 +45,8 @@ export const GaussianDensitySchema = {
 }
 }
 
 
 export const GaussianDensityShaderCode = ShaderCode(
 export const GaussianDensityShaderCode = ShaderCode(
-    require('mol-gl/shader/gaussian-density.vert'),
-    require('mol-gl/shader/gaussian-density.frag'),
+    require('mol-gl/shader/gaussian-density.vert').default,
+    require('mol-gl/shader/gaussian-density.frag').default,
     { standardDerivatives: false, fragDepth: false }
     { standardDerivatives: false, fragDepth: false }
 )
 )
 
 

+ 23 - 0
src/mol-math/linear-algebra/3d/vec3.ts

@@ -414,6 +414,7 @@ namespace Vec3 {
     }
     }
 
 
     const angleTempA = zero(), angleTempB = zero();
     const angleTempA = zero(), angleTempB = zero();
+    /** Computes the angle between 2 vectors, reports in rad. */
     export function angle(a: Vec3, b: Vec3) {
     export function angle(a: Vec3, b: Vec3) {
         copy(angleTempA, a);
         copy(angleTempA, a);
         copy(angleTempB, b);
         copy(angleTempB, b);
@@ -433,6 +434,28 @@ namespace Vec3 {
         }
         }
     }
     }
 
 
+    const tmp_dh_ab = Vec3.zero();
+    const tmp_dh_cb = Vec3.zero();
+    const tmp_dh_bc = Vec3.zero();
+    const tmp_dh_dc = Vec3.zero();
+    const tmp_dh_abc = Vec3.zero();
+    const tmp_dh_bcd = Vec3.zero();
+    const tmp_dh_cross = Vec3.zero();
+    /** Computes the dihedral angles of 4 related atoms. phi: C, N+1, CA+1, C+1 - psi: N, CA, C, N+1 - omega: CA, C, N+1, CA+1 */
+    export function dihedralAngle(a: Vec3, b: Vec3, c: Vec3, d: Vec3) {
+        Vec3.sub(tmp_dh_ab, a, b);
+        Vec3.sub(tmp_dh_cb, c, b);
+        Vec3.sub(tmp_dh_bc, b, c);
+        Vec3.sub(tmp_dh_dc, d, c);
+
+        Vec3.cross(tmp_dh_abc, tmp_dh_ab, tmp_dh_cb);
+        Vec3.cross(tmp_dh_bcd, tmp_dh_bc, tmp_dh_dc);
+
+        const angle = Vec3.angle(tmp_dh_abc, tmp_dh_bcd) * 360.0 / (2 * Math.PI);
+        Vec3.cross(tmp_dh_cross, tmp_dh_abc, tmp_dh_bcd);
+        return Vec3.dot(tmp_dh_cb, tmp_dh_cross) > 0 ? angle : -angle;
+    }
+
     /**
     /**
      * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)
      * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)
      */
      */

+ 14 - 0
src/mol-math/linear-algebra/_spec/vec3.spec.ts

@@ -0,0 +1,14 @@
+import { Vec3 } from '../3d'
+
+describe('vec3', () => {
+    const vec1 = [ 1, 2, 3 ] as Vec3;
+    const vec2 = [ 2, 3, 1 ] as Vec3;
+    const orthVec1 = [ 0, 1, 0 ] as Vec3;
+    const orthVec2 = [ 1, 0, 0 ] as Vec3;
+
+    it('angle calculation', () => {
+        expect(Vec3.angle(vec1, vec1) * 360 / (2 * Math.PI)).toBe(0.0);
+        expect(Vec3.angle(orthVec1, orthVec2) * 360 / (2 * Math.PI)).toBe(90.0);
+        expect(Vec3.angle(vec1, vec2)).toBeCloseTo(0.666946);
+    });
+})

+ 260 - 0
src/mol-model-formats/shape/ply.ts

@@ -0,0 +1,260 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Schäfer, Marco <marco.schaefer@uni-tuebingen.de>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { RuntimeContext, Task } from 'mol-task';
+import { ShapeProvider } from 'mol-model/shape/provider';
+import { Color } from 'mol-util/color';
+import { PlyFile, PlyTable, PlyList } from 'mol-io/reader/ply/schema';
+import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
+import { Mesh } from 'mol-geo/geometry/mesh/mesh';
+import { Shape } from 'mol-model/shape';
+import { ChunkedArray } from 'mol-data/util';
+import { arrayMax, fillSerial } from 'mol-util/array';
+import { Column } from 'mol-data/db';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { ColorNames } from 'mol-util/color/tables';
+import { deepClone } from 'mol-util/object';
+
+// TODO support 'edge' element, see https://www.mathworks.com/help/vision/ug/the-ply-format.html
+// TODO support missing face element
+
+function createPlyShapeParams(plyFile?: PlyFile) {
+    const vertex = plyFile && plyFile.getElement('vertex') as PlyTable
+    const material = plyFile && plyFile.getElement('material') as PlyTable
+
+    const defaultValues = { group: '', vRed: '', vGreen: '', vBlue: '', mRed: '', mGreen: '', mBlue: '' }
+
+    const groupOptions: [string, string][] = [['', '']]
+    const colorOptions: [string, string][] = [['', '']]
+    if (vertex) {
+        for (let i = 0, il = vertex.propertyNames.length; i < il; ++i) {
+            const name = vertex.propertyNames[i]
+            const type = vertex.propertyTypes[i]
+            if (
+                type === 'uchar' || type === 'uint8' ||
+                type === 'ushort' || type === 'uint16' ||
+                type === 'uint' || type === 'uint32'
+            ) groupOptions.push([ name, name ])
+            if (type === 'uchar' || type === 'uint8') colorOptions.push([ name, name ])
+        }
+
+        // TODO hardcoded as convenience for data provided by MegaMol
+        if (vertex.propertyNames.includes('atomid')) defaultValues.group = 'atomid'
+        else if (vertex.propertyNames.includes('material_index')) defaultValues.group = 'material_index'
+
+        if (vertex.propertyNames.includes('red')) defaultValues.vRed = 'red'
+        if (vertex.propertyNames.includes('green')) defaultValues.vGreen = 'green'
+        if (vertex.propertyNames.includes('blue')) defaultValues.vBlue = 'blue'
+    }
+
+    const materialOptions: [string, string][] = [['', '']]
+    if (material) {
+        for (let i = 0, il = material.propertyNames.length; i < il; ++i) {
+            const name = material.propertyNames[i]
+            const type = material.propertyTypes[i]
+            if (type === 'uchar' || type === 'uint8') materialOptions.push([ name, name ])
+        }
+
+        if (material.propertyNames.includes('red')) defaultValues.mRed = 'red'
+        if (material.propertyNames.includes('green')) defaultValues.mGreen = 'green'
+        if (material.propertyNames.includes('blue')) defaultValues.mBlue = 'blue'
+    }
+
+    const defaultColoring = defaultValues.vRed && defaultValues.vGreen && defaultValues.vBlue ? 'vertex' :
+        defaultValues.mRed && defaultValues.mGreen && defaultValues.mBlue ? 'material' : 'uniform'
+
+    return {
+        ...Mesh.Params,
+
+        coloring: PD.MappedStatic(defaultColoring, {
+            vertex: PD.Group({
+                red: PD.Select(defaultValues.vRed, colorOptions, { label: 'Red Property' }),
+                green: PD.Select(defaultValues.vGreen, colorOptions, { label: 'Green Property' }),
+                blue: PD.Select(defaultValues.vBlue, colorOptions, { label: 'Blue Property' }),
+            }, { isFlat: true }),
+            material: PD.Group({
+                red: PD.Select(defaultValues.mRed, materialOptions, { label: 'Red Property' }),
+                green: PD.Select(defaultValues.mGreen, materialOptions, { label: 'Green Property' }),
+                blue: PD.Select(defaultValues.mBlue, materialOptions, { label: 'Blue Property' }),
+            }, { isFlat: true }),
+            uniform: PD.Group({
+                color: PD.Color(ColorNames.grey)
+            }, { isFlat: true })
+        }),
+        grouping: PD.MappedStatic(defaultValues.group ? 'vertex' : 'none', {
+            vertex: PD.Group({
+                group: PD.Select(defaultValues.group, groupOptions, { label: 'Group Property' }),
+            }, { isFlat: true }),
+            none: PD.Group({ })
+        }),
+    }
+}
+
+export const PlyShapeParams = createPlyShapeParams()
+export type PlyShapeParams = typeof PlyShapeParams
+
+async function getMesh(ctx: RuntimeContext, vertex: PlyTable, face: PlyList, groupIds: ArrayLike<number>, mesh?: Mesh) {
+    const builderState = MeshBuilder.createState(vertex.rowCount, vertex.rowCount / 4, mesh)
+    const { vertices, normals, indices, groups } = builderState
+
+    const x = vertex.getProperty('x')
+    const y = vertex.getProperty('y')
+    const z = vertex.getProperty('z')
+    if (!x || !y || !z) throw new Error('missing coordinate properties')
+
+    const nx = vertex.getProperty('nx')
+    const ny = vertex.getProperty('ny')
+    const nz = vertex.getProperty('nz')
+
+    const hasNormals = !!nx && !!ny && !!nz
+
+    for (let i = 0, il = vertex.rowCount; i < il; ++i) {
+        if (i % 100000 === 0 && ctx.shouldUpdate) await ctx.update({ current: i, max: il, message: `adding vertex ${i}` })
+
+        ChunkedArray.add3(vertices, x.value(i), y.value(i), z.value(i))
+        if (hasNormals) ChunkedArray.add3(normals, nx!.value(i), ny!.value(i), nz!.value(i));
+        ChunkedArray.add(groups, groupIds[i])
+    }
+
+    for (let i = 0, il = face.rowCount; i < il; ++i) {
+        if (i % 100000 === 0 && ctx.shouldUpdate) await ctx.update({ current: i, max: il, message: `adding face ${i}` })
+
+        const { entries, count } = face.value(i)
+        if (count === 3) {
+            // triangle
+            ChunkedArray.add3(indices, entries[0], entries[1], entries[2])
+        } else if (count === 4) {
+            // quadrilateral
+            ChunkedArray.add3(indices, entries[2], entries[1], entries[0])
+            ChunkedArray.add3(indices, entries[2], entries[0], entries[3])
+        }
+    }
+
+    const m = MeshBuilder.getMesh(builderState);
+    m.normalsComputed = hasNormals
+    await Mesh.computeNormals(m).runInContext(ctx)
+
+    return m
+}
+
+const int = Column.Schema.int
+
+type Grouping = { ids: ArrayLike<number>, map: ArrayLike<number> }
+function getGrouping(vertex: PlyTable, props: PD.Values<PlyShapeParams>): Grouping {
+    const { grouping } = props
+    const { rowCount } = vertex
+    const column = grouping.name === 'vertex' ? vertex.getProperty(grouping.params.group) : undefined
+
+    const ids = column ? column.toArray({ array: Uint32Array }) : fillSerial(new Uint32Array(rowCount))
+    const maxId = arrayMax(ids) // assumes uint ids
+    const map = new Uint32Array(maxId + 1)
+    for (let i = 0, il = ids.length; i < il; ++i) map[ids[i]] = i
+    return { ids, map }
+}
+
+type Coloring = { kind: 'vertex' | 'material' | 'uniform', red: Column<number>, green: Column<number>, blue: Column<number> }
+function getColoring(vertex: PlyTable, material: PlyTable | undefined, props: PD.Values<PlyShapeParams>): Coloring {
+    const { coloring } = props
+    const { rowCount } = vertex
+
+    let red: Column<number>, green: Column<number>, blue: Column<number>
+    if (coloring.name === 'vertex') {
+        red = vertex.getProperty(coloring.params.red) || Column.ofConst(127, rowCount, int)
+        green = vertex.getProperty(coloring.params.green) || Column.ofConst(127, rowCount, int)
+        blue = vertex.getProperty(coloring.params.blue) || Column.ofConst(127, rowCount, int)
+    } else if (coloring.name === 'material') {
+        red = (material && material.getProperty(coloring.params.red)) || Column.ofConst(127, rowCount, int)
+        green = (material && material.getProperty(coloring.params.green)) || Column.ofConst(127, rowCount, int)
+        blue = (material && material.getProperty(coloring.params.blue)) || Column.ofConst(127, rowCount, int)
+    } else {
+        const [r, g, b] = Color.toRgb(coloring.params.color)
+        red = Column.ofConst(r, rowCount, int)
+        green = Column.ofConst(g, rowCount, int)
+        blue = Column.ofConst(b, rowCount, int)
+    }
+    return { kind: coloring.name, red, green, blue }
+}
+
+function createShape(plyFile: PlyFile, mesh: Mesh, coloring: Coloring, grouping: Grouping) {
+    const { kind, red, green, blue } = coloring
+    const { ids, map } = grouping
+    return Shape.create(
+        'ply-mesh', plyFile, mesh,
+        (groupId: number) => {
+            const idx = kind === 'material' ? groupId : map[groupId]
+            return Color.fromRgb(red.value(idx), green.value(idx), blue.value(idx))
+        },
+        () => 1, // size: constant
+        (groupId: number) => {
+            return ids[groupId].toString()
+        }
+    )
+}
+
+function makeShapeGetter() {
+    let _plyFile: PlyFile | undefined
+    let _props: PD.Values<PlyShapeParams> | undefined
+
+    let _shape: Shape<Mesh>
+    let _mesh: Mesh
+    let _coloring: Coloring
+    let _grouping: Grouping
+
+    const getShape = async (ctx: RuntimeContext, plyFile: PlyFile, props: PD.Values<PlyShapeParams>, shape?: Shape<Mesh>) => {
+
+        const vertex = plyFile.getElement('vertex') as PlyTable
+        if (!vertex) throw new Error('missing vertex element')
+
+        const face = plyFile.getElement('face') as PlyList
+        if (!face) throw new Error('missing face element')
+
+        const material = plyFile.getElement('material') as PlyTable
+
+        let newMesh = false
+        let newColor = false
+
+        if (!_plyFile || _plyFile !== plyFile) {
+            newMesh = true
+        }
+
+        if (!_props || !PD.isParamEqual(PlyShapeParams.grouping, _props.grouping, props.grouping)) {
+            newMesh = true
+        }
+
+        if (!_props || !PD.isParamEqual(PlyShapeParams.coloring, _props.coloring, props.coloring)) {
+            newColor = true
+        }
+
+        if (newMesh) {
+            _coloring = getColoring(vertex, material, props)
+            _grouping = getGrouping(vertex, props)
+            _mesh = await getMesh(ctx, vertex, face, _grouping.ids, shape && shape.geometry)
+            _shape = createShape(plyFile, _mesh, _coloring, _grouping)
+        } else if (newColor) {
+            _coloring = getColoring(vertex, material, props)
+            _shape = createShape(plyFile, _mesh, _coloring, _grouping)
+        }
+
+        _plyFile = plyFile
+        _props = deepClone(props)
+
+        return _shape
+    }
+    return getShape
+}
+
+export function shapeFromPly(source: PlyFile, params?: {}) {
+    return Task.create<ShapeProvider<PlyFile, Mesh, PlyShapeParams>>('Shape Provider', async ctx => {
+        return {
+            label: 'Mesh',
+            data: source,
+            params: createPlyShapeParams(source),
+            getShape: makeShapeGetter(),
+            geometryUtils: Mesh.Utils
+        }
+    })
+}

+ 26 - 0
src/mol-model-formats/structure/_spec/pdb.spec.ts

@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { guessElementSymbol } from '../pdb/to-cif';
+import { TokenBuilder } from 'mol-io/reader/common/text/tokenizer';
+
+const records = [
+    ['ATOM     19 HD23 LEU A   1     151.940 143.340 155.670  0.00  0.00', 'H'],
+    ['ATOM     38  CA  SER A   3     146.430 138.150 162.270  0.00  0.00', 'C'],
+    ['ATOM     38 NA   SER A   3     146.430 138.150 162.270  0.00  0.00', 'NA'],
+    ['ATOM     38  NAA SER A   3     146.430 138.150 162.270  0.00  0.00', 'N'],
+]
+
+describe('PDB to-cif', () => {
+    it('guess-element-symbol', () => {
+        for (let i = 0, il = records.length; i < il; ++i) {
+            const [ data, element ] = records[i]
+            const tokens = TokenBuilder.create(data, 2)
+            guessElementSymbol(tokens, data, 12, 16)
+            expect(data.substring(tokens.indices[0], tokens.indices[1])).toBe(element)
+        }
+    });
+});

+ 40 - 4
src/mol-model-formats/structure/pdb/to-cif.ts

@@ -8,7 +8,7 @@
 import { substringStartsWith } from 'mol-util/string';
 import { substringStartsWith } from 'mol-util/string';
 import { CifField, CifCategory, CifFrame } from 'mol-io/reader/cif';
 import { CifField, CifCategory, CifFrame } from 'mol-io/reader/cif';
 import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif';
 import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif';
-import { TokenBuilder, Tokenizer } from 'mol-io/reader/common/text/tokenizer';
+import { TokenBuilder, Tokenizer, Tokens } from 'mol-io/reader/common/text/tokenizer';
 import { PdbFile } from 'mol-io/reader/pdb/schema';
 import { PdbFile } from 'mol-io/reader/pdb/schema';
 import { parseCryst1, parseRemark350, parseMtrix } from './assembly';
 import { parseCryst1, parseRemark350, parseMtrix } from './assembly';
 import { WaterNames } from 'mol-model/structure/model/types';
 import { WaterNames } from 'mol-model/structure/model/types';
@@ -89,6 +89,43 @@ function getEntityId(residueName: string, isHet: boolean) {
     return '1';
     return '1';
 }
 }
 
 
+export function guessElementSymbol(tokens: Tokens, str: string, start: number, end: number) {
+    let s = start, e = end - 1
+
+    // trim spaces and numbers
+    let c = str.charCodeAt(s)
+    while ((c === 32 || (c >= 48 && c <= 57)) && s <= e) c = str.charCodeAt(++s)
+    c = str.charCodeAt(e)
+    while ((c === 32 || (c >= 48 && c <= 57)) && e >= s) c = str.charCodeAt(--e)
+
+    ++e
+
+    if (s === e) return TokenBuilder.add(tokens, s, e) // empty
+    if (s + 1 === e) return TokenBuilder.add(tokens, s, e) // one char
+
+    c = str.charCodeAt(s)
+
+    if (s + 2 === e) { // two chars
+        const c2 = str.charCodeAt(s + 1)
+        if (
+            ((c === 78 || c === 110) && (c2 === 65 || c2 ===  97)) || // NA na Na nA
+            ((c === 67 || c ===  99) && (c2 === 76 || c2 === 108)) || // CL
+            ((c === 70 || c === 102) && (c2 === 69 || c2 === 101))    // FE
+        ) return TokenBuilder.add(tokens, s, s + 2)
+    }
+
+    if (
+        c === 67 || c ===  99 || // C c
+        c === 72 || c === 104 || // H h
+        c === 78 || c === 110 || // N n
+        c === 79 || c === 111 || // O o
+        c === 80 || c === 112 || // P p
+        c === 83 || c === 115    // S s
+    ) return TokenBuilder.add(tokens, s, s + 1)
+
+    TokenBuilder.add(tokens, s, s) // no reasonable guess, add empty token
+}
+
 function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: number, e: number, isHet: boolean) {
 function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: number, e: number, isHet: boolean) {
     const { data: str } = data;
     const { data: str } = data;
     const length = e - s;
     const length = e - s;
@@ -162,11 +199,10 @@ function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: num
         if (data.tokenStart < data.tokenEnd) {
         if (data.tokenStart < data.tokenEnd) {
             TokenBuilder.addToken(sites.type_symbol, data);
             TokenBuilder.addToken(sites.type_symbol, data);
         } else {
         } else {
-            // "guess" the symbol
-            TokenBuilder.add(sites.type_symbol, s + 12, s + 13);
+            guessElementSymbol(sites.type_symbol, str, s + 12, s + 16)
         }
         }
     } else {
     } else {
-        TokenBuilder.add(sites.type_symbol, s + 12, s + 13);
+        guessElementSymbol(sites.type_symbol, str, s + 12, s + 16)
     }
     }
 
 
     sites.label_entity_id[sites.index] = getEntityId(residueName, isHet);
     sites.label_entity_id[sites.index] = getEntityId(residueName, isHet);

+ 16 - 0
src/mol-model/shape/provider.ts

@@ -0,0 +1,16 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ShapeGetter } from 'mol-repr/shape/representation';
+import { Geometry, GeometryUtils } from 'mol-geo/geometry/geometry';
+
+export interface ShapeProvider<D, G extends Geometry, P extends Geometry.Params<G>> {
+    label: string
+    data: D
+    params: P
+    getShape: ShapeGetter<D, G, P>
+    geometryUtils: GeometryUtils<G>
+}

+ 4 - 1
src/mol-model/shape/shape.ts

@@ -15,6 +15,8 @@ export interface Shape<G extends Geometry = Geometry> {
     readonly id: UUID
     readonly id: UUID
     /** A name to describe the shape */
     /** A name to describe the shape */
     readonly name: string
     readonly name: string
+    /** The data used to create the shape */
+    readonly sourceData: unknown
     /** The geometry of the shape, e.g. `Mesh` or `Lines` */
     /** The geometry of the shape, e.g. `Mesh` or `Lines` */
     readonly geometry: G
     readonly geometry: G
     /** An array of transformation matrices to describe multiple instances of the geometry */
     /** An array of transformation matrices to describe multiple instances of the geometry */
@@ -30,10 +32,11 @@ export interface Shape<G extends Geometry = Geometry> {
 }
 }
 
 
 export namespace Shape {
 export namespace Shape {
-    export function create<G extends Geometry>(name: string, geometry: G, getColor: Shape['getColor'], getSize: Shape['getSize'], getLabel: Shape['getLabel'], transforms?: Mat4[]): Shape<G> {
+    export function create<G extends Geometry>(name: string, sourceData: unknown, geometry: G, getColor: Shape['getColor'], getSize: Shape['getSize'], getLabel: Shape['getLabel'], transforms?: Mat4[]): Shape<G> {
         return {
         return {
             id: UUID.create22(),
             id: UUID.create22(),
             name,
             name,
+            sourceData,
             geometry,
             geometry,
             transforms: transforms || [Mat4.identity()],
             transforms: transforms || [Mat4.identity()],
             get groupCount() { return Geometry.getGroupCount(geometry) },
             get groupCount() { return Geometry.getGroupCount(geometry) },

+ 6 - 1
src/mol-model/structure/model/properties/seconday-structure.ts

@@ -17,12 +17,17 @@ interface SecondaryStructure {
 }
 }
 
 
 namespace SecondaryStructure {
 namespace SecondaryStructure {
-    export type Element = None | Helix | Sheet
+    export type Element = None | Turn | Helix | Sheet
 
 
     export interface None {
     export interface None {
         kind: 'none'
         kind: 'none'
     }
     }
 
 
+    export interface Turn {
+        kind: 'turn',
+        flags: SecondaryStructureType
+    }
+
     export interface Helix {
     export interface Helix {
         kind: 'helix',
         kind: 'helix',
         flags: SecondaryStructureType,
         flags: SecondaryStructureType,

+ 1 - 1
src/mol-model/structure/model/properties/utils/guess-element.ts

@@ -12,7 +12,7 @@ function charAtIsNumber(str: string, index: number) {
     return code >= 48 && code <= 57
     return code >= 48 && code <= 57
 }
 }
 
 
-export function guessElement (str: string) {
+export function guessElement(str: string) {
     let at = str.trim().toUpperCase()
     let at = str.trim().toUpperCase()
 
 
     if (charAtIsNumber(at, 0)) at = at.substr(1)
     if (charAtIsNumber(at, 0)) at = at.substr(1)

+ 494 - 105
src/mol-model/structure/model/properties/utils/secondary-structure.ts

@@ -2,6 +2,7 @@
  * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  */
  */
 
 
 import { SecondaryStructure } from 'mol-model/structure/model/properties/seconday-structure';
 import { SecondaryStructure } from 'mol-model/structure/model/properties/seconday-structure';
@@ -14,13 +15,120 @@ import { IntAdjacencyGraph } from 'mol-math/graph';
 import { BitFlags } from 'mol-util';
 import { BitFlags } from 'mol-util';
 import { ElementIndex } from 'mol-model/structure/model/indexing';
 import { ElementIndex } from 'mol-model/structure/model/indexing';
 import { AtomicHierarchy, AtomicConformation } from '../atomic';
 import { AtomicHierarchy, AtomicConformation } from '../atomic';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
 
 
-export function computeSecondaryStructure(hierarchy: AtomicHierarchy, conformation: AtomicConformation): SecondaryStructure {
+/**
+ * TODO bugs to fix:
+ * - some turns are not detected correctly: see e.g. pdb:1acj - maybe more than 2 hbonds require some residue to donate electrons
+ * - some sheets are not extended correctly: see e.g. pdb:1acj
+ * - validate new helix definition
+ * - validate new ordering of secondary structure elements
+ */
+
+ /** max distance between two C-alpha atoms to check for hbond */
+const caMaxDist = 9.0;
+
+/**
+ * Constant for electrostatic energy in kcal/mol
+ *      f  *  q1 *   q2
+ * Q = -332 * 0.42 * 0.20
+ *
+ * f is the dimensional factor
+ *
+ * q1 and q2 are partial charges which are placed on the C,O
+ * (+q1,-q1) and N,H (-q2,+q2)
+ */
+const Q = -27.888
+
+/** cutoff for hbonds in kcal/mol, must be lower to be consider as an hbond */
+const hbondEnergyCutoff = -0.5
+/** prevent extremely low hbond energies */
+const hbondEnergyMinimal = -9.9
+
+interface DSSPContext {
+    params: Partial<PD.Values<SecondaryStructureComputationParams>>,
+    getResidueFlag: (f: DSSPType) => SecondaryStructureType,
+    getFlagName: (f: DSSPType) => String,
+
+    hierarchy: AtomicHierarchy
+    proteinResidues: SortedArray<ResidueIndex>
+    /** flags for each residue */
+    flags: Uint32Array
+    hbonds: DsspHbonds,
+
+    torsionAngles: { phi: Float32Array, psi: Float32Array },
+    backboneIndices: BackboneAtomIndices,
+    conformation: AtomicConformation,
+    ladders: Ladder[],
+    bridges: Bridge[]
+}
+
+interface Ladder {
+    previousLadder: number,
+    nextLadder: number,
+    firstStart: number,
+    secondStart: number,
+    secondEnd: number,
+    firstEnd: number,
+    type: BridgeType
+}
+
+const enum BridgeType {
+    PARALLEL = 0x0,
+    ANTI_PARALLEL = 0x1
+}
+
+class Bridge {
+    partner1: number;
+    partner2: number;
+    type: BridgeType;
+
+    constructor(p1: number, p2: number, type: BridgeType) {
+        this.partner1 = Math.min(p1, p2)
+        this.partner2 = Math.max(p1, p2)
+        this.type = type
+    }
+}
+
+type DSSPType = BitFlags<DSSPType.Flag>
+namespace DSSPType {
+    export const is: (t: DSSPType, f: Flag) => boolean = BitFlags.has
+    export const create: (f: Flag) => DSSPType = BitFlags.create
+    export const enum Flag {
+        _ = 0x0,
+        H = 0x1,
+        B = 0x2,
+        E = 0x4,
+        G = 0x8,
+        I = 0x10,
+        S = 0x20,
+        T = 0x40,
+        T3 = 0x80,
+        T4 = 0x100,
+        T5 = 0x200,
+        T3S = 0x400, // marks 3-turn start
+        T4S = 0x800,
+        T5S = 0x1000
+    }
+}
+
+export const SecondaryStructureComputationParams = {
+    oldDefinition: PD.Boolean(true, { description: 'Whether to use the old DSSP convention for the annotation of turns and helices, causes them to be two residues shorter' }),
+    oldOrdering: PD.Boolean(true, { description: 'Alpha-helices are preferred over 3-10 helices' })
+}
+export type SecondaryStructureComputationParams = typeof SecondaryStructureComputationParams
+
+export function computeSecondaryStructure(hierarchy: AtomicHierarchy,
+    conformation: AtomicConformation) {
     // TODO use Zhang-Skolnik for CA alpha only parts or for coarse parts with per-residue elements
     // TODO use Zhang-Skolnik for CA alpha only parts or for coarse parts with per-residue elements
     return computeModelDSSP(hierarchy, conformation)
     return computeModelDSSP(hierarchy, conformation)
 }
 }
 
 
-export function computeModelDSSP(hierarchy: AtomicHierarchy, conformation: AtomicConformation) {
+export function computeModelDSSP(hierarchy: AtomicHierarchy,
+    conformation: AtomicConformation,
+    params: Partial<PD.Values<SecondaryStructureComputationParams>> = {}): SecondaryStructure {
+    params = { ...PD.getDefaultValues(SecondaryStructureComputationParams), ...params };
+
     const { lookup3d, proteinResidues } = calcAtomicTraceLookup3D(hierarchy, conformation)
     const { lookup3d, proteinResidues } = calcAtomicTraceLookup3D(hierarchy, conformation)
     const backboneIndices = calcBackboneAtomIndices(hierarchy, proteinResidues)
     const backboneIndices = calcBackboneAtomIndices(hierarchy, proteinResidues)
     const hbonds = calcBackboneHbonds(hierarchy, conformation, proteinResidues, backboneIndices, lookup3d)
     const hbonds = calcBackboneHbonds(hierarchy, conformation, proteinResidues, backboneIndices, lookup3d)
@@ -28,69 +136,103 @@ export function computeModelDSSP(hierarchy: AtomicHierarchy, conformation: Atomi
     const residueCount = proteinResidues.length
     const residueCount = proteinResidues.length
     const flags = new Uint32Array(residueCount)
     const flags = new Uint32Array(residueCount)
 
 
+    // console.log(`calculating secondary structure elements using ${ params.oldDefinition ? 'old' : 'revised'} definition and ${ params.oldOrdering ? 'old' : 'revised'} ordering of secondary structure elements`)
+
+    const torsionAngles = calculateDihedralAngles(hierarchy, conformation, proteinResidues, backboneIndices)
+
+    const ladders: Ladder[] = []
+    const bridges: Bridge[] = []
+
+    const getResidueFlag = params.oldDefinition ? getOriginalResidueFlag : getUpdatedResidueFlag
+    const getFlagName = params.oldOrdering ? getOriginalFlagName : getUpdatedFlagName
+
     const ctx: DSSPContext = {
     const ctx: DSSPContext = {
+        params,
+        getResidueFlag,
+        getFlagName,
+
         hierarchy,
         hierarchy,
         proteinResidues,
         proteinResidues,
         flags,
         flags,
-        hbonds
+        hbonds,
+
+        torsionAngles,
+        backboneIndices,
+        conformation,
+        ladders,
+        bridges
     }
     }
 
 
-    assignBends(ctx)
     assignTurns(ctx)
     assignTurns(ctx)
     assignHelices(ctx)
     assignHelices(ctx)
+    assignBends(ctx)
     assignBridges(ctx)
     assignBridges(ctx)
     assignLadders(ctx)
     assignLadders(ctx)
     assignSheets(ctx)
     assignSheets(ctx)
 
 
-    const assignment = getDSSPAssignment(flags)
-
+    const assignment = getDSSPAssignment(flags, getResidueFlag)
     const type = new Uint32Array(hierarchy.residues._rowCount) as unknown as SecondaryStructureType[]
     const type = new Uint32Array(hierarchy.residues._rowCount) as unknown as SecondaryStructureType[]
+    const keys: number[] = []
+    const elements: SecondaryStructure.Element[] = []
+
     for (let i = 0, il = proteinResidues.length; i < il; ++i) {
     for (let i = 0, il = proteinResidues.length; i < il; ++i) {
-        type[proteinResidues[i]] = assignment[i]
+        const assign = assignment[i]
+        type[proteinResidues[i]] = assign
+        const flag = getResidueFlag(flags[i])
+        // TODO is this expected behavior? elements will be strictly split depending on 'winning' flag
+        if (elements.length === 0 /* would fail at very start */ || flag !== (elements[elements.length - 1] as SecondaryStructure.Helix | SecondaryStructure.Sheet | SecondaryStructure.Turn).flags /* flag changed */) {
+            elements[elements.length] = createElement(mapToKind(assign), flags[i], getResidueFlag)
+        }
+        keys[i] = elements.length - 1
     }
     }
 
 
     const secondaryStructure: SecondaryStructure = {
     const secondaryStructure: SecondaryStructure = {
         type,
         type,
-        key: [], // TODO
-        elements: [] // TODO
+        key: keys,
+        elements: elements
     }
     }
+
     return secondaryStructure
     return secondaryStructure
 }
 }
 
 
-interface DSSPContext {
-    hierarchy: AtomicHierarchy
-    proteinResidues: SortedArray<ResidueIndex>
-    /** flags for each residue */
-    flags: Uint32Array
-
-    hbonds: DsspHbonds
+function createElement(kind: string, flag: DSSPType.Flag, getResidueFlag: (f: DSSPType) => SecondaryStructureType): SecondaryStructure.Element {
+    // TODO would be nice to add more detailed information
+    if (kind === 'helix') {
+        return {
+            kind: 'helix',
+            flags: getResidueFlag(flag)
+        } as SecondaryStructure.Helix
+    } else if (kind === 'sheet') {
+        return {
+            kind: 'sheet',
+            flags: getResidueFlag(flag)
+        } as SecondaryStructure.Sheet
+    } else if (kind === 'turn' || kind === 'bend') {
+        return {
+            kind: 'turn',
+            flags: getResidueFlag(flag)
+        }
+    } else {
+        return {
+            kind: 'none'
+        }
+    }
 }
 }
 
 
-type DSSPType = BitFlags<DSSPType.Flag>
-namespace DSSPType {
-    export const is: (t: DSSPType, f: Flag) => boolean = BitFlags.has
-    export const create: (f: Flag) => DSSPType = BitFlags.create
-    export const enum Flag {
-        _ = 0x0,
-        H = 0x1,
-        B = 0x2,
-        E = 0x4,
-        G = 0x8,
-        I = 0x10,
-        S = 0x20,
-        T = 0x40,
-        T3 = 0x80,
-        T4 = 0x100,
-        T5 = 0x200,
+function mapToKind(assignment: SecondaryStructureType.Flag) {
+    if (assignment === SecondaryStructureType.SecondaryStructureDssp.H || assignment === SecondaryStructureType.SecondaryStructureDssp.G || assignment === SecondaryStructureType.SecondaryStructureDssp.I) {
+        return 'helix'
+    } else if (assignment === SecondaryStructureType.SecondaryStructureDssp.B || assignment === SecondaryStructureType.SecondaryStructureDssp.E) {
+        return 'sheet'
+    } else if (assignment === SecondaryStructureType.SecondaryStructureDssp.T) {
+        return 'turn'
+    } else if (assignment === SecondaryStructureType.SecondaryStructureDssp.S) {
+        return 'bend'
+    } else {
+        return 'none'
     }
     }
 }
 }
 
 
-/** max distance between two C-alpha atoms to check for hbond */
-const caMaxDist = 7.0;
-
-/** min distance between two C-alpha atoms to check for hbond */
-const caMinDist = 4.0;
-
 function calcAtomicTraceLookup3D(hierarchy: AtomicHierarchy, conformation: AtomicConformation) {
 function calcAtomicTraceLookup3D(hierarchy: AtomicHierarchy, conformation: AtomicConformation) {
     const { x, y, z } = conformation;
     const { x, y, z } = conformation;
     const { moleculeType, traceElementIndex } = hierarchy.derived.residue
     const { moleculeType, traceElementIndex } = hierarchy.derived.residue
@@ -141,6 +283,104 @@ function calcBackboneAtomIndices(hierarchy: AtomicHierarchy, proteinResidues: So
 
 
 type DsspHbonds = IntAdjacencyGraph<{ readonly energies: ArrayLike<number> }>
 type DsspHbonds = IntAdjacencyGraph<{ readonly energies: ArrayLike<number> }>
 
 
+/**
+ * Bend(i) =: [angle ((CW - Ca(i - 2)),(C"(i + 2) - C"(i))) > 70"]
+ *
+ * Type: S
+ */
+function assignBends(ctx: DSSPContext) {
+    const flags = ctx.flags
+    const { x, y, z } = ctx.conformation
+    const { traceElementIndex } = ctx.hierarchy.derived.residue
+
+    const proteinResidues = ctx.proteinResidues
+    const residueCount = proteinResidues.length
+
+    const position = (i: number, v: Vec3) => Vec3.set(v, x[i], y[i], z[i])
+
+    const caPosPrev2 = Vec3.zero()
+    const caPos = Vec3.zero()
+    const caPosNext2 = Vec3.zero()
+
+    const nIndices = ctx.backboneIndices.nIndices
+    const cPos = Vec3.zero()
+    const nPosNext = Vec3.zero()
+
+    f1: for (let i = 2; i < residueCount - 2; i++) {
+        // check for peptide bond
+        for (let k = 0; k < 4; k++) {
+            let index = i + k - 2
+            position(traceElementIndex[index], cPos)
+            position(nIndices[index + 1], nPosNext)
+            if (Vec3.squaredDistance(cPos, nPosNext) > 6.25 /* max squared peptide bond distance allowed */) {
+                continue f1
+            }
+        }
+
+        const oRIprev2 = proteinResidues[i - 2]
+        const oRI = proteinResidues[i]
+        const oRInext2 = proteinResidues[i + 2]
+
+        const caAtomPrev2 = traceElementIndex[oRIprev2]
+        const caAtom = traceElementIndex[oRI]
+        const caAtomNext2 = traceElementIndex[oRInext2]
+
+        position(caAtomPrev2, caPosPrev2)
+        position(caAtom, caPos)
+        position(caAtomNext2, caPosNext2)
+
+        const caMinus2 = Vec3.zero()
+        const caPlus2 = Vec3.zero()
+
+        Vec3.sub(caMinus2, caPosPrev2, caPos)
+        Vec3.sub(caPlus2, caPos, caPosNext2)
+
+        const angle = Vec3.angle(caMinus2, caPlus2) * 360 / (2 * Math.PI)
+        if (angle && angle > 70.00) {
+            flags[i] |= DSSPType.Flag.S
+        }
+    }
+}
+
+function calculateDihedralAngles(hierarchy: AtomicHierarchy, conformation: AtomicConformation, proteinResidues: SortedArray<ResidueIndex>, backboneIndices: BackboneAtomIndices): { phi: Float32Array, psi: Float32Array } {
+    const { cIndices, nIndices } = backboneIndices
+    const { index } = hierarchy
+    const { x, y, z } = conformation
+    const { traceElementIndex } = hierarchy.derived.residue
+
+    const residueCount = proteinResidues.length
+    const position = (i: number, v: Vec3) => Vec3.set(v, x[i], y[i], z[i])
+
+    let cPosPrev = Vec3.zero(), caPosPrev = Vec3.zero(), nPosPrev = Vec3.zero()
+    let cPos = Vec3.zero(), caPos = Vec3.zero(), nPos = Vec3.zero()
+    let cPosNext = Vec3.zero(), caPosNext = Vec3.zero(), nPosNext = Vec3.zero()
+
+    const phi: Float32Array = new Float32Array(residueCount - 1)
+    const psi: Float32Array = new Float32Array(residueCount - 1)
+
+    const cAtomPrev = cIndices[-1], caAtomPrev = traceElementIndex[proteinResidues[-1]], nAtomPrev = nIndices[-1]
+    position(cAtomPrev, cPosPrev), position(caAtomPrev, caPosPrev), position(nAtomPrev, nPosPrev)
+    const cAtom = cIndices[0], caAtom = traceElementIndex[proteinResidues[0]], nAtom = nIndices[0]
+    position(cAtom, cPos), position(caAtom, caPos), position(nAtom, nPos)
+    const cAtomNext = cIndices[1], caAtomNext = traceElementIndex[proteinResidues[1]], nAtomNext = nIndices[1]
+    position(cAtomNext, cPosNext), position(caAtomNext, caPosNext), position(nAtomNext, nPosNext)
+    for (let i = 0; i < residueCount - 1; ++i) {
+        // ignore C-terminal residue as acceptor
+        if (index.findAtomOnResidue(proteinResidues[i], 'OXT') !== -1) continue
+
+        // returns NaN for missing atoms
+        phi[i] = Vec3.dihedralAngle(cPosPrev, nPos, caPos, cPos)
+        psi[i] = Vec3.dihedralAngle(nPos, caPos, cPos, nPosNext)
+
+        cPosPrev = cPos, caPosPrev = caPos, nPosPrev = nPos
+        cPos = cPosNext, caPos = caPosNext, nPos = nPosNext
+
+        position(cIndices[i + 1], cPosNext), position(traceElementIndex[proteinResidues[i + 1]], caPosNext), position(nIndices[i + 1], nPosNext)
+    }
+
+    return { phi, psi };
+}
+
 function calcBackboneHbonds(hierarchy: AtomicHierarchy, conformation: AtomicConformation, proteinResidues: SortedArray<ResidueIndex>, backboneIndices: BackboneAtomIndices, lookup3d: GridLookup3D): DsspHbonds {
 function calcBackboneHbonds(hierarchy: AtomicHierarchy, conformation: AtomicConformation, proteinResidues: SortedArray<ResidueIndex>, backboneIndices: BackboneAtomIndices, lookup3d: GridLookup3D): DsspHbonds {
     const { cIndices, hIndices, nIndices, oIndices } = backboneIndices
     const { cIndices, hIndices, nIndices, oIndices } = backboneIndices
     const { index } = hierarchy
     const { index } = hierarchy
@@ -163,8 +403,6 @@ function calcBackboneHbonds(hierarchy: AtomicHierarchy, conformation: AtomicConf
     const cPosPrev = Vec3.zero()
     const cPosPrev = Vec3.zero()
     const oPosPrev = Vec3.zero()
     const oPosPrev = Vec3.zero()
 
 
-    const caMinDistSq = caMinDist * caMinDist
-
     for (let i = 0, il = proteinResidues.length; i < il; ++i) {
     for (let i = 0, il = proteinResidues.length; i < il; ++i) {
         const oPI = i
         const oPI = i
         const oRI = proteinResidues[i]
         const oRI = proteinResidues[i]
@@ -183,11 +421,9 @@ function calcBackboneHbonds(hierarchy: AtomicHierarchy, conformation: AtomicConf
         position(cAtom, cPos)
         position(cAtom, cPos)
         position(caAtom, caPos)
         position(caAtom, caPos)
 
 
-        const { indices, count, squaredDistances } = lookup3d.find(caPos[0], caPos[1], caPos[2], caMaxDist)
+        const { indices, count } = lookup3d.find(caPos[0], caPos[1], caPos[2], caMaxDist)
 
 
         for (let j = 0; j < count; ++j) {
         for (let j = 0; j < count; ++j) {
-            if (squaredDistances[j] < caMinDistSq) continue
-
             const nPI = indices[j]
             const nPI = indices[j]
 
 
             // ignore bonds within a residue or to prev or next residue, TODO take chain border into account
             // ignore bonds within a residue or to prev or next residue, TODO take chain border into account
@@ -245,8 +481,8 @@ function buildHbondGraph(residueCount: number, oAtomResidues: number[], nAtomRes
 /** Original priority: H,B,E,G,I,T,S */
 /** Original priority: H,B,E,G,I,T,S */
 function getOriginalResidueFlag(f: DSSPType) {
 function getOriginalResidueFlag(f: DSSPType) {
     if (DSSPType.is(f, DSSPType.Flag.H)) return SecondaryStructureType.SecondaryStructureDssp.H
     if (DSSPType.is(f, DSSPType.Flag.H)) return SecondaryStructureType.SecondaryStructureDssp.H
-    if (DSSPType.is(f, DSSPType.Flag.B)) return SecondaryStructureType.SecondaryStructureDssp.B
     if (DSSPType.is(f, DSSPType.Flag.E)) return SecondaryStructureType.SecondaryStructureDssp.E
     if (DSSPType.is(f, DSSPType.Flag.E)) return SecondaryStructureType.SecondaryStructureDssp.E
+    if (DSSPType.is(f, DSSPType.Flag.B)) return SecondaryStructureType.SecondaryStructureDssp.B
     if (DSSPType.is(f, DSSPType.Flag.G)) return SecondaryStructureType.SecondaryStructureDssp.G
     if (DSSPType.is(f, DSSPType.Flag.G)) return SecondaryStructureType.SecondaryStructureDssp.G
     if (DSSPType.is(f, DSSPType.Flag.I)) return SecondaryStructureType.SecondaryStructureDssp.I
     if (DSSPType.is(f, DSSPType.Flag.I)) return SecondaryStructureType.SecondaryStructureDssp.I
     if (DSSPType.is(f, DSSPType.Flag.T)) return SecondaryStructureType.SecondaryStructureDssp.T
     if (DSSPType.is(f, DSSPType.Flag.T)) return SecondaryStructureType.SecondaryStructureDssp.T
@@ -254,55 +490,50 @@ function getOriginalResidueFlag(f: DSSPType) {
     return SecondaryStructureType.Flag.None
     return SecondaryStructureType.Flag.None
 }
 }
 
 
+function getOriginalFlagName(f: DSSPType) {
+    if (DSSPType.is(f, DSSPType.Flag.H)) return 'H'
+    if (DSSPType.is(f, DSSPType.Flag.E)) return 'E'
+    if (DSSPType.is(f, DSSPType.Flag.B)) return 'B'
+    if (DSSPType.is(f, DSSPType.Flag.G)) return 'G'
+    if (DSSPType.is(f, DSSPType.Flag.I)) return 'I'
+    if (DSSPType.is(f, DSSPType.Flag.T)) return 'T'
+    if (DSSPType.is(f, DSSPType.Flag.S)) return 'S'
+    return '-'
+}
+
 /** Version 2.1.0 priority: I,H,B,E,G,T,S */
 /** Version 2.1.0 priority: I,H,B,E,G,T,S */
 function getUpdatedResidueFlag(f: DSSPType) {
 function getUpdatedResidueFlag(f: DSSPType) {
     if (DSSPType.is(f, DSSPType.Flag.I)) return SecondaryStructureType.SecondaryStructureDssp.I
     if (DSSPType.is(f, DSSPType.Flag.I)) return SecondaryStructureType.SecondaryStructureDssp.I
     if (DSSPType.is(f, DSSPType.Flag.H)) return SecondaryStructureType.SecondaryStructureDssp.H
     if (DSSPType.is(f, DSSPType.Flag.H)) return SecondaryStructureType.SecondaryStructureDssp.H
-    if (DSSPType.is(f, DSSPType.Flag.B)) return SecondaryStructureType.SecondaryStructureDssp.B
     if (DSSPType.is(f, DSSPType.Flag.E)) return SecondaryStructureType.SecondaryStructureDssp.E
     if (DSSPType.is(f, DSSPType.Flag.E)) return SecondaryStructureType.SecondaryStructureDssp.E
+    if (DSSPType.is(f, DSSPType.Flag.B)) return SecondaryStructureType.SecondaryStructureDssp.B
     if (DSSPType.is(f, DSSPType.Flag.G)) return SecondaryStructureType.SecondaryStructureDssp.G
     if (DSSPType.is(f, DSSPType.Flag.G)) return SecondaryStructureType.SecondaryStructureDssp.G
     if (DSSPType.is(f, DSSPType.Flag.T)) return SecondaryStructureType.SecondaryStructureDssp.T
     if (DSSPType.is(f, DSSPType.Flag.T)) return SecondaryStructureType.SecondaryStructureDssp.T
     if (DSSPType.is(f, DSSPType.Flag.S)) return SecondaryStructureType.SecondaryStructureDssp.S
     if (DSSPType.is(f, DSSPType.Flag.S)) return SecondaryStructureType.SecondaryStructureDssp.S
     return SecondaryStructureType.Flag.None
     return SecondaryStructureType.Flag.None
 }
 }
 
 
-// function geFlagName(f: DSSPType) {
-//     if (DSSPType.is(f, DSSPType.Flag.I)) return 'I'
-//     if (DSSPType.is(f, DSSPType.Flag.H)) return 'H'
-//     if (DSSPType.is(f, DSSPType.Flag.B)) return 'B'
-//     if (DSSPType.is(f, DSSPType.Flag.E)) return 'E'
-//     if (DSSPType.is(f, DSSPType.Flag.G)) return 'G'
-//     if (DSSPType.is(f, DSSPType.Flag.T)) return 'T'
-//     if (DSSPType.is(f, DSSPType.Flag.S)) return 'S'
-//     return '-'
-// }
-
-function getDSSPAssignment(flags: Uint32Array, useOriginal = false) {
-    const getResidueFlag = useOriginal ? getOriginalResidueFlag : getUpdatedResidueFlag
+function getUpdatedFlagName(f: DSSPType) {
+    if (DSSPType.is(f, DSSPType.Flag.I)) return 'I'
+    if (DSSPType.is(f, DSSPType.Flag.H)) return 'H'
+    if (DSSPType.is(f, DSSPType.Flag.E)) return 'E'
+    if (DSSPType.is(f, DSSPType.Flag.B)) return 'B'
+    if (DSSPType.is(f, DSSPType.Flag.G)) return 'G'
+    if (DSSPType.is(f, DSSPType.Flag.T)) return 'T'
+    if (DSSPType.is(f, DSSPType.Flag.S)) return 'S'
+    return '-'
+}
+
+function getDSSPAssignment(flags: Uint32Array, getResidueFlag: (f: DSSPType) => SecondaryStructureType) {
     const type = new Uint32Array(flags.length)
     const type = new Uint32Array(flags.length)
     for (let i = 0, il = flags.length; i < il; ++i) {
     for (let i = 0, il = flags.length; i < il; ++i) {
         const f = DSSPType.create(flags[i])
         const f = DSSPType.create(flags[i])
-        // console.log(i, geFlagName(f))
         type[i] = getResidueFlag(f)
         type[i] = getResidueFlag(f)
     }
     }
+
     return type as unknown as ArrayLike<SecondaryStructureType>
     return type as unknown as ArrayLike<SecondaryStructureType>
 }
 }
 
 
-/**
- * Constant for electrostatic energy in kcal/mol
- *      f  *  q1 *   q2
- * Q = -332 * 0.42 * 0.20
- *
- * f is the dimensional factor
- *
- * q1 and q2 are partial charges which are placed on the C,O
- * (+q1,-q1) and N,H (-q2,+q2)
- */
-const Q = -27.888
-
-/** cutoff for hbonds in kcal/mol, must be lower to be consider as an hbond */
-const hbondEnergyCutoff = -0.5
-
 /**
 /**
  * E = Q * (1/r(ON) + l/r(CH) - l/r(OH) - l/r(CN))
  * E = Q * (1/r(ON) + l/r(CH) - l/r(OH) - l/r(CN))
  */
  */
@@ -314,7 +545,13 @@ function calcHbondEnergy(oPos: Vec3, cPos: Vec3, nPos: Vec3, hPos: Vec3) {
 
 
     const e1 = Q / distOH - Q / distCH
     const e1 = Q / distOH - Q / distCH
     const e2 = Q / distCN - Q / distON
     const e2 = Q / distCN - Q / distON
-    return e1 + e2
+    const e = e1 + e2
+
+    // cap lowest possible energy
+    if (e < hbondEnergyMinimal)
+        return hbondEnergyMinimal
+
+    return e
 }
 }
 
 
 /**
 /**
@@ -329,24 +566,31 @@ function assignTurns(ctx: DSSPContext) {
     const { chains, residueAtomSegments, chainAtomSegments } = hierarchy
     const { chains, residueAtomSegments, chainAtomSegments } = hierarchy
     const { label_asym_id } = chains
     const { label_asym_id } = chains
 
 
-    const turnFlag = [0, 0, 0, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5]
+    const turnFlag = [DSSPType.Flag.T3S, DSSPType.Flag.T4S, DSSPType.Flag.T5S, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5]
 
 
-    for (let i = 0, il = proteinResidues.length; i < il; ++i) {
-        const rI = proteinResidues[i]
-        const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]]
+    for (let idx = 0; idx < 3; idx++) {
+        for (let i = 0, il = proteinResidues.length - 1; i < il; ++i) {
+            const rI = proteinResidues[i]
+            const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]]
 
 
-        // TODO should take sequence gaps into account
-        for (let k = 3; k <= 5; ++k) {
-            if (i + k >= proteinResidues.length) continue
-
-            const rN = proteinResidues[i + k]
+            // TODO should take sequence gaps into account
+            const rN = proteinResidues[i + idx + 3]
             const cN = chainAtomSegments.index[residueAtomSegments.offsets[rN]]
             const cN = chainAtomSegments.index[residueAtomSegments.offsets[rN]]
             // check if on same chain
             // check if on same chain
             if (!label_asym_id.areValuesEqual(cI, cN)) continue
             if (!label_asym_id.areValuesEqual(cI, cN)) continue
 
 
             // check if hbond exists
             // check if hbond exists
-            if (hbonds.getDirectedEdgeIndex(i, i + k) !== -1) {
-                flags[i] |= turnFlag[k] | DSSPType.Flag.T
+            if (hbonds.getDirectedEdgeIndex(i, i + idx + 3) !== -1) {
+                flags[i] |= turnFlag[idx + 3] | turnFlag[idx]
+                if (ctx.params.oldDefinition) {
+                    for (let k = 1; k < idx + 3; ++k) {
+                        flags[i + k] |= turnFlag[idx + 3] | DSSPType.Flag.T
+                    }
+                } else {
+                    for (let k = 0; k <= idx + 3; ++k) {
+                        flags[i + k] |= turnFlag[idx + 3] | DSSPType.Flag.T
+                    }
+                }
             }
             }
         }
         }
     }
     }
@@ -369,7 +613,7 @@ function assignTurns(ctx: DSSPContext) {
  * Type: B
  * Type: B
  */
  */
 function assignBridges(ctx: DSSPContext) {
 function assignBridges(ctx: DSSPContext) {
-    const { proteinResidues, hbonds, flags } = ctx
+    const { proteinResidues, hbonds, flags, bridges } = ctx
 
 
     const { offset, b } = hbonds
     const { offset, b } = hbonds
     let i: number, j: number
     let i: number, j: number
@@ -385,6 +629,8 @@ function assignBridges(ctx: DSSPContext) {
             if (i !== j && hbonds.getDirectedEdgeIndex(j, i + 1) !== -1) {
             if (i !== j && hbonds.getDirectedEdgeIndex(j, i + 1) !== -1) {
                 flags[i] |= DSSPType.Flag.B
                 flags[i] |= DSSPType.Flag.B
                 flags[j] |= DSSPType.Flag.B
                 flags[j] |= DSSPType.Flag.B
+                // TODO move to constructor, actually omit object all together
+                bridges[bridges.length] = new Bridge(i, j, BridgeType.PARALLEL)
             }
             }
 
 
             // Parallel Bridge(i, j) =: [Hbond(j - 1, i) and Hbond(i, j + 1)]
             // Parallel Bridge(i, j) =: [Hbond(j - 1, i) and Hbond(i, j + 1)]
@@ -393,6 +639,7 @@ function assignBridges(ctx: DSSPContext) {
             if (i !== j && hbonds.getDirectedEdgeIndex(j - 1, i) !== -1) {
             if (i !== j && hbonds.getDirectedEdgeIndex(j - 1, i) !== -1) {
                 flags[i] |= DSSPType.Flag.B
                 flags[i] |= DSSPType.Flag.B
                 flags[j] |= DSSPType.Flag.B
                 flags[j] |= DSSPType.Flag.B
+                bridges[bridges.length] = new Bridge(j, i, BridgeType.PARALLEL)
             }
             }
 
 
             // Antiparallel Bridge(i, j) =: [Hbond(i, j) and Hbond(j, i)]
             // Antiparallel Bridge(i, j) =: [Hbond(i, j) and Hbond(j, i)]
@@ -401,6 +648,7 @@ function assignBridges(ctx: DSSPContext) {
             if (i !== j && hbonds.getDirectedEdgeIndex(j, i) !== -1) {
             if (i !== j && hbonds.getDirectedEdgeIndex(j, i) !== -1) {
                 flags[i] |= DSSPType.Flag.B
                 flags[i] |= DSSPType.Flag.B
                 flags[j] |= DSSPType.Flag.B
                 flags[j] |= DSSPType.Flag.B
+                bridges[bridges.length] = new Bridge(j, i, BridgeType.ANTI_PARALLEL)
             }
             }
 
 
             // Antiparallel Bridge(i, j) =: [Hbond(i - 1, j + 1) and Hbond(j - 1, i + l)]
             // Antiparallel Bridge(i, j) =: [Hbond(i - 1, j + 1) and Hbond(j - 1, i + l)]
@@ -409,9 +657,12 @@ function assignBridges(ctx: DSSPContext) {
             if (i !== j && hbonds.getDirectedEdgeIndex(j - 1, i + 1) !== -1) {
             if (i !== j && hbonds.getDirectedEdgeIndex(j - 1, i + 1) !== -1) {
                 flags[i] |= DSSPType.Flag.B
                 flags[i] |= DSSPType.Flag.B
                 flags[j] |= DSSPType.Flag.B
                 flags[j] |= DSSPType.Flag.B
+                bridges[bridges.length] = new Bridge(j, i, BridgeType.ANTI_PARALLEL)
             }
             }
         }
         }
     }
     }
+
+    bridges.sort((a, b) => a.partner1 > b.partner1 ? 1 : a.partner1 < b.partner1 ? -1 : 0)
 }
 }
 
 
 /**
 /**
@@ -428,17 +679,41 @@ function assignBridges(ctx: DSSPContext) {
 function assignHelices(ctx: DSSPContext) {
 function assignHelices(ctx: DSSPContext) {
     const { proteinResidues, flags } = ctx
     const { proteinResidues, flags } = ctx
 
 
-    const turnFlag = [0, 0, 0, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5]
+    const turnFlag = [DSSPType.Flag.T3S, DSSPType.Flag.T4S, DSSPType.Flag.T5S, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5]
     const helixFlag = [0, 0, 0, DSSPType.Flag.G, DSSPType.Flag.H, DSSPType.Flag.I]
     const helixFlag = [0, 0, 0, DSSPType.Flag.G, DSSPType.Flag.H, DSSPType.Flag.I]
 
 
-    for (let i = 1, il = proteinResidues.length; i < il; ++i) {
-        const fI = DSSPType.create(flags[i])
-        const fI1 = DSSPType.create(flags[i - 1])
+    const helixCheckOrder = ctx.params.oldOrdering ? [4, 3, 5] : [3, 4, 5]
+    for (let ni = 0; ni < helixCheckOrder.length; ni++) {
+        const n = helixCheckOrder[ni]
+
+        for (let i = 1, il = proteinResidues.length - n; i < il; i++) {
+            const fI = DSSPType.create(flags[i])
+            const fI1 = DSSPType.create(flags[i - 1])
+            const fI2 = DSSPType.create(flags[i + 1])
 
 
-        for (let k = 3; k <= 5; ++k) {
-            if (DSSPType.is(fI, turnFlag[k]) && DSSPType.is(fI1, turnFlag[k])) {
-                for (let l = 0; l < k; ++l) {
-                    flags[i + l] |= helixFlag[k]
+            // TODO rework to elegant solution which will not break instantly
+            if (ctx.params.oldOrdering) {
+                if ((n === 3 && (DSSPType.is(fI, DSSPType.Flag.H) || DSSPType.is(fI2, DSSPType.Flag.H)) || // for 3-10 yield to alpha helix
+                    (n === 5 && ((DSSPType.is(fI, DSSPType.Flag.H) || DSSPType.is(fI, DSSPType.Flag.G)) || (DSSPType.is(fI2, DSSPType.Flag.H) || DSSPType.is(fI2, DSSPType.Flag.G)))))) { // for pi yield to all other helices
+                    continue
+                }
+            } else {
+                if ((n === 4 && (DSSPType.is(fI, DSSPType.Flag.G) || DSSPType.is(fI2, DSSPType.Flag.G)) || // for alpha helix yield to 3-10
+                    (n === 5 && ((DSSPType.is(fI, DSSPType.Flag.H) || DSSPType.is(fI, DSSPType.Flag.G)) || (DSSPType.is(fI2, DSSPType.Flag.H) || DSSPType.is(fI2, DSSPType.Flag.G)))))) { // for pi yield to all other helices
+                    continue
+                }
+            }
+
+            if (DSSPType.is(fI, turnFlag[n]) && DSSPType.is(fI, turnFlag[n - 3]) && // check fI for turn start of proper type
+                DSSPType.is(fI1, turnFlag[n]) && DSSPType.is(fI1, turnFlag[n - 3])) { // check fI1 accordingly
+                if (ctx.params.oldDefinition) {
+                    for (let k = 0; k < n; k++) {
+                        flags[i + k] |= helixFlag[n]
+                    }
+                } else {
+                    for (let k = -1; k <= n; k++) {
+                        flags[i + k] |= helixFlag[n]
+                    }
                 }
                 }
             }
             }
         }
         }
@@ -451,23 +726,137 @@ function assignHelices(ctx: DSSPContext) {
  * Type: E
  * Type: E
  */
  */
 function assignLadders(ctx: DSSPContext) {
 function assignLadders(ctx: DSSPContext) {
-    // TODO
+    const { bridges, ladders } = ctx
+
+    // create ladders
+    for (let bridgeIndex = 0; bridgeIndex < bridges.length; bridgeIndex++) {
+        const bridge = bridges[bridgeIndex]
+        let found = false
+        for (let ladderIndex = 0; ladderIndex < ladders.length; ladderIndex++) {
+            const ladder = ladders[ladderIndex]
+            if (shouldExtendLadder(ladder, bridge)) {
+                found = true
+                ladder.firstEnd++
+                if (bridge.type === BridgeType.PARALLEL) {
+                    ladder.secondEnd++
+                } else {
+                    ladder.secondStart--
+                }
+            }
+        }
+
+        // no suitable assignment: create new ladder with single bridge as content
+        if (!found) {
+            ladders[ladders.length] = {
+                previousLadder: 0,
+                nextLadder: 0,
+                firstStart: bridge.partner1,
+                firstEnd: bridge.partner1,
+                secondStart: bridge.partner2,
+                secondEnd: bridge.partner2,
+                type: bridge.type
+            }
+        }
+    }
+
+    // connect ladders
+    for (let ladderIndex1 = 0; ladderIndex1 < ladders.length; ladderIndex1++) {
+        const ladder1 = ladders[ladderIndex1]
+        for (let ladderIndex2 = ladderIndex1; ladderIndex2 < ladders.length; ladderIndex2++) {
+            const ladder2 = ladders[ladderIndex2]
+            if (resemblesBulge(ladder1, ladder2)) {
+                ladder1.nextLadder = ladderIndex2
+                ladder2.previousLadder = ladderIndex1
+            }
+        }
+    }
 }
 }
 
 
 /**
 /**
- * sheet=: set of one or more ladders connected by shared residues
- *
- * Type: E
+ * For beta structures, we define: a bulge-linked ladder consists of two ladders or bridges of the same type
+ * connected by at most one extra residue of one strand and at most four extra residues  on the other strand,
+ * all residues in bulge-linked ladders are marked E, including any extra residues.
  */
  */
-function assignSheets(ctx: DSSPContext) {
-    // TODO
+function resemblesBulge(ladder1: Ladder, ladder2: Ladder) {
+    if (!(ladder1.type === ladder2.type && ladder2.firstStart - ladder1.firstEnd < 6 &&
+        ladder1.firstStart < ladder2.firstStart && ladder2.nextLadder === 0)) return false
+
+    if (ladder1.type === BridgeType.PARALLEL) {
+        return bulgeCriterion2(ladder1, ladder2)
+    } else {
+        return bulgeCriterion2(ladder2, ladder1)
+    }
+}
+
+function bulgeCriterion2(ladder1: Ladder, ladder2: Ladder) {
+    return ladder2.secondStart - ladder1.secondEnd > 0 && ((ladder2.secondStart - ladder1.secondEnd < 6 &&
+        ladder2.firstStart - ladder1.firstEnd < 3) || ladder2.secondStart - ladder1.secondEnd < 3)
+}
+
+function shouldExtendLadder(ladder: Ladder, bridge: Bridge): boolean {
+    // in order to extend ladders, same type must be present
+    if (bridge.type !== ladder.type) return false
+
+    // only extend if residue 1 is sequence neighbor with regard to ladder
+    if (bridge.partner1 !== ladder.firstEnd + 1) return false
+
+    if (bridge.type === BridgeType.PARALLEL) {
+        if (bridge.partner2 === ladder.secondEnd + 1) {
+            return true
+        }
+    } else {
+        if (bridge.partner2 === ladder.secondStart - 1) {
+            return true
+        }
+    }
+
+    return false
+}
+
+function isHelixType(f: DSSPType) {
+    return DSSPType.is(f, DSSPType.Flag.G) || DSSPType.is(f, DSSPType.Flag.H) || DSSPType.is(f, DSSPType.Flag.I)
 }
 }
 
 
 /**
 /**
- * Bend(i) =: [angle ((CW - Ca(i - 2)),(C"(i + 2) - C"(i))) > 70"]
+ * sheet=: set of one or more ladders connected by shared residues
  *
  *
- * Type: S
+ * Type: E
  */
  */
-function assignBends(ctx: DSSPContext) {
-    // TODO
+function assignSheets(ctx: DSSPContext) {
+    const { ladders, flags } = ctx
+    for (let ladderIndex = 0; ladderIndex < ladders.length; ladderIndex++) {
+        const ladder = ladders[ladderIndex]
+        for (let lcount = ladder.firstStart; lcount <= ladder.firstEnd; lcount++) {
+            const diff = ladder.firstStart - lcount
+            const l2count = ladder.secondStart - diff
+
+            if (ladder.firstStart !== ladder.firstEnd) {
+                flags[lcount] |= DSSPType.Flag.E
+                flags[l2count] |= DSSPType.Flag.E
+            } else {
+                if (!isHelixType(flags[lcount]) && DSSPType.is(flags[lcount], DSSPType.Flag.E)) {
+                    flags[lcount] |= DSSPType.Flag.B
+                }
+                if (!isHelixType(flags[l2count]) && DSSPType.is(flags[l2count], DSSPType.Flag.E)) {
+                    flags[l2count] |= DSSPType.Flag.B
+                }
+            }
+        }
+
+        if (ladder.nextLadder === 0) continue
+
+        const conladder = ladders[ladder.nextLadder]
+        for (let lcount = ladder.firstStart; lcount <= conladder.firstEnd; lcount++) {
+            flags[lcount] |= DSSPType.Flag.E
+        }
+        if (ladder.type === BridgeType.PARALLEL) {
+            for (let lcount = ladder.secondStart; lcount <= conladder.secondEnd; lcount++) {
+                flags[lcount] |= DSSPType.Flag.E
+            }
+        } else {
+            for (let lcount = conladder.secondEnd; lcount <= ladder.secondStart; lcount++) {
+                flags[lcount] |= DSSPType.Flag.E
+            }
+        }
+    }
 }
 }

+ 75 - 0
src/mol-model/structure/model/properties/utils/secondary-structure.validation

@@ -0,0 +1,75 @@
+compares Mol* port of DSSP (with default parameters) to the BioJava implementation
+
+### pdb:1pga ###
+# turns #
+Mol*: ----------------------TTTTTTTTTTTTTTTT--------TTTT------
+53 turns, 18 openings
+DSSP: ----------------------TTTTTTTTTTTTTTTT--------TTTT------
+53 turns, 18 openings
+
+# bends #
+Mol*: ---------SS---------SSSSSSSSSSSSSSSSSS--------SSS-------
+23 bends
+DSSP: ---------SS---------SSSSSSSSSSSSSSSSSS--------SSS-------
+23 bends
+
+# helices #
+Mol*: ----------------------HHHHHHHHHHHHHHTT--------TTTT------
+44 helix elements - 0 3-10, 44 alpha, 0 pi
+DSSP: ----------------------HHHHHHHHHHHHHHTT--------TTTT------
+44 helix elements - 0 3-10, 44 alpha, 0 pi
+
+# all #
+Mol*: -EEEEEEE-SS-EEEEEEE-SSHHHHHHHHHHHHHHTT---EEEEETTTTEEEEE-
+DSSP: -EEEEEEE-SS-EEEEEEE-SSHHHHHHHHHHHHHHTT---EEEEETTTTEEEEE-
+
+
+### pdb:1bta ###
+# turns #
+Mol*: ------TTT---TTTTTTTTTTTTT--TT----TTTTTTTTTTT-----------TTTTTTTTT--TTTTTTTTTTTTTTT--------
+127 turns, 44 openings
+DSSP: ------TTT---TTTTTTTTTTTTT--TT----TTTTTTTTTTT-----------TTTTTTTTT--TTTTTTTTTTTTTTT--------
+127 turns, 44 openings
+
+# bends #
+Mol*: ------SSS--SSSSSSSSSSSSS---SS---SSSSSSSSSSSSS-SS------SSSSSSSSSSSSSSSSSSSSSSSSSSS--------
+60 bends
+DSSP: ------SSS--SSSSSSSSSSSSS---SS---SSSSSSSSSSSSS-SS------SSSSSSSSSSSSSSSSSSSSSSSSSSS--------
+60 bends
+
+# helices #
+Mol*: ------TTT---HHHHHHHHHHHHT--TT----HHHHHHHHTTT-----------TTHHHHTTT--HHHHHHHHHHHHHTT--------
+100 helix elements - 0 3-10, 100 alpha, 0 pi
+DSSP: ------TTT---HHHHHHHHHHHHT--TT----HHHHHHHHTTT-----------TTHHHHTTT--HHHHHHHHHHHHHTT--------
+100 helix elements - 0 3-10, 100 alpha, 0 pi
+
+# all #
+Mol*: -EEEEETTT--SHHHHHHHHHHHHT--TT---SHHHHHHHHTTTS-SSEEEEEESTTHHHHTTTSSHHHHHHHHHHHHHTT--EEEEE-
+DSSP: -EEEEETTT--SHHHHHHHHHHHHT--TT---SHHHHHHHHTTTS-SSEEEEEESTTHHHHTTTSSHHHHHHHHHHHHHTT--EEEEE-
+
+
+### pdb:1acj ###
+# turns #
+Mol*: -------TT----------TT----------------TTTTT----------------------------TTTT-TTTTTT----------------------------------TTT------TTT-TTTTTTTTT-----------TTTT---TT-------TTTTTTTTTTTTTTTTTTTTT--TT-------TTTTTTTTTTTT-TTTTTT----------TT-TT----TTTTTTTTTTTTTTTT-----TTTTTTTTTT--TTTTTTTTTTT-----------------------TTTTTTTT----------------TTTTTTT-TT--TT------TTTTTTTTTTTTTT--TTTTTTTTTTT--TTTTT-TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT-------------TT----TTT---TTTTTTTTTTTTT-TTT---TTTTTTTTTTTTTTTTTTTT--------------------------------TTTTTTTTTTTTTTTTTTT-
+614 turns, 223 openings
+DSSP: -------TT----------TT----------------TTTTT------------------------------TT-TTTTTT----------------------------------TTT------TTT-TTTTTTTTT-----------TTTT---TT-------TTTTTTTTTTTTTTTTTTTTT--TT-------TTTTTTTTTTTT-TTTTTT----------TT-TT----TTTTTTTTTTTTTTTT-----TTTTTTTTTT--TTTTTTTTTTT-----------------------TTTTTTTT----------------TTTTTTT-TT--TT------TTTTTTTTTTT-TT--TTTTTTTTTTT--TTTTT-TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT-------------TT----TTT---TTTTTTTTTTTTT-TTT---TTTTTTTTTTTTTTTTTTTT--------------------------------TTTTTTTTTTTTTTTTTTT-
+606 turns, 220 openings
+
+# bends #
+Mol*: --S----SS----------SSS----------S----SS-SSS--------SS-----S-----------SSSS-SSSSSSS--S---S----------SS--SS---------SSSS---S-SSSS--SSSSSSS-----------SSSS----SS-SSS-S-SSSSSSSSSSSSSSSSSSSSS--SSS------SSSSSSSSSSSS-SSSSSS-S----SS--SSSSSS---SSSSSSSSSSSSSSS----S-SSSSSSSSSSS-SSSSSSSSSS--SS--SS--S------SSSSSS-SSSSSSS--S--S-------S--SSSSSSSSSSS--SSS-----SSSSSSSSSSS-SS--SSSSSSSSSSS--SSSSS-SSSSSSSSSSSSSSSSS----SSSSSSSSSSSS-----------SS--S-SSS-S-SS-SSSSSS--SS-SSS---SSSSSSSSSSSSSSSSSSSSSSSS---------SSS------SSSS----SSSS-S----SSSSSSSSSS--
+305 bends
+DSSP: --S----SS----------SSS----------S----SS-SSS--------SS-----S-----------SSSS-SSSSSSS--S---S----------SS--SS---------SSSS---S-SSSS--SSSSSSS-----------SSSS----SS-SSS-S-SSSSSSSSSSSSSSSSSSSSS--SSS------SSSSSSSSSSSS-SSSSSS-S----SS--SSSSSS---SSSSSSSSSSSSSSS----S-SSSSSSSSSSS-SSSSSSSSSS--SS--SS--S------SSSSSS-SSSSSSS--S--S-------S--SSSSSSSSSSS--SSS-----SSSSSSSSSSS-SS--SSSSSSSSSSS--SSSSS-SSSSSSSSSSSSSSSSS----SSSSSSSSSSSS-----------SS--S-SSS-S-SS-SSSSSS--SS-SSS---SSSSSSSSSSSSSSSSSSSSSSSS---------SSS------SSSS----SSSS-S----SSSSSSSSSS--
+305 bends
+
+# helices #
+Mol*: -------TT----------TT----------------GGGTT----------------------------TTTT-HHHHTT----------------------------------TTT------GGG-THHHHHHHT-----------HHHH---TT-------HHHHHHHHHHHHHHHHGGGGT--TT-------THHHHHHHHHHH-HHHHTT----------TT-TT----HHHHHHHHHHHHHHTT-----HHHHHHHHHH--HHHHHHHHGGG-----------------------HHHHHHHT----------------HHHHHHH-TT--TT------HHHHHHHHHHHTTT--HHHHHHHHHHH--GGGTT-HHHHHHHHHHHHHHHHTHHHHHHHHHHHHTT-------------TT----GGG---TTTTHHHHTTGGG-GGG---HHHHHHHHHHHHHHHHHHHT--------------------------------TTHHHHHHHHTHHHHHHHH-
+523 helix elements - 27 3-10, 496 alpha, 0 pi
+DSSP: -------TT----------TT----------------GGGTT------------------------------TT-HHHHTT----------------------------------TTT------GGG-THHHHHHHT-----------HHHH---TT-------HHHHHHHHHHHHHHHHGGGGT--TT-------THHHHHHHHHHH-HHHHTT----------TT-TT----HHHHHHHHHHHHHHTT-----HHHHHHHHHH--HHHHHHHHGGG-----------------------HHHHHHHT----------------HHHHHHH-TT--TT------HHHHHHHHHHH-TT--HHHHHHHHHHH--GGGTT-HHHHHHHHHHHHHHHHTHHHHHHHHHHHHTT-------------TT----GGG---TTTTHHHHTTGGG-GGG---HHHHHHHHHHHHHHHHHHHT--------------------------------TTHHHHHHHHTHHHHHHHH-
+523 helix elements - 27 3-10, 496 alpha, 0 pi
+
+# all #
+Mol*: --SEEEETTEEEE-EEEEETTEEEEEEEEEE-EE---GGGTTS--EE----SSEEE--S---B-------TTTT-HHHHTTS--S-B-S---EEEEEE-SS--SSEEEEEEE--STTT---S-SGGG-THHHHHHHT-EEEE-----SHHHH---TT-SSS-S-HHHHHHHHHHHHHHHHGGGGTEEEEEEEEEEETHHHHHHHHHHH-HHHHTT-SEEEEES--TTSTTSEEEHHHHHHHHHHHHHHTT---S-HHHHHHHHHHS-HHHHHHHHGGG-SS--SS--S--EEE-SSSSSS-HHHHHHHT-S--S-EEEEEESB-SHHHHHHHSTT--TTS-----HHHHHHHHHHHTTT--HHHHHHHHHHH--GGGTT-HHHHHHHHHHHHHHHHTHHHHHHHHHHHHTTSS-EEEEEE----TT--S-GGG-SBTTTTHHHHTTGGG-GGG---HHHHHHHHHHHHHHHHHHHTSSSS---------SSS-EEEEESSSS--EEESTTHHHHHHHHTHHHHHHHH-
+DSSP: --SEEEETTEEEE-EEEEETTEEEEEEEEEE-EE---GGGTTS--EE----SSEEE--S---B-------SSTT-HHHHTTS--S-B-S---EEEEEE-SS--SSEEEEEEE--STTT---S-SGGG-THHHHHHHT-EEEE-----SHHHH---TT-SSS-S-HHHHHHHHHHHHHHHHGGGGTEEEEEEEEEEETHHHHHHHHHHH-HHHHTT-SEEEEES--TTSTTS-EEHHHHHHHHHHHHHHTT---S-HHHHHHHHHHS-HHHHHHHHGGG-SS--SS--S---EE-SSSSSS-HHHHHHHT-S--S-EEEEEESB-SHHHHHHHSTT--TTS-----HHHHHHHHHHH-TT--HHHHHHHHHHH--GGGTT-HHHHHHHHHHHHHHHHTHHHHHHHHHHHHTTSS-EEEEEE----TT--S-GGG-SBTTTTHHHHTTGGG-GGG---HHHHHHHHHHHHHHHHHHHTSSSS---------SSS-EEEEESSSS--EEESTTHHHHHHHHTHHHHHHHH-
+
+TODO fix mismatches                                                   e.g. here
+TODO move to spec.ts once tests are running

+ 32 - 31
src/mol-model/structure/model/types.ts

@@ -275,41 +275,42 @@ export namespace SecondaryStructureType {
         DoubleHelix = 0x1,
         DoubleHelix = 0x1,
         Helix = 0x2,
         Helix = 0x2,
         Beta = 0x4,
         Beta = 0x4,
-        Turn = 0x8,
+        Bend = 0x8,
+        Turn = 0x10,
 
 
         // category variant
         // category variant
-        LeftHanded = 0x10,  // helix
-        RightHanded = 0x20,
+        LeftHanded = 0x20,  // helix
+        RightHanded = 0x40,
 
 
-        ClassicTurn = 0x40,  // turn
-        InverseTurn = 0x80,
+        ClassicTurn = 0x80,  // turn
+        InverseTurn = 0x100,
 
 
         // sub-category
         // sub-category
-        HelixOther = 0x100,  // protein
-        Helix27 = 0x200,
-        Helix3Ten = 0x400,
-        HelixAlpha = 0x800,
-        HelixGamma = 0x1000,
-        HelixOmega = 0x2000,
-        HelixPi = 0x4000,
-        HelixPolyproline = 0x8000,
-
-        DoubleHelixOther = 0x10000,  // nucleic
-        DoubleHelixZ = 0x20000,
-        DoubleHelixA = 0x40000,
-        DoubleHelixB = 0x80000,
-
-        BetaOther = 0x100000,  // protein
-        BetaStrand = 0x200000,  // single strand
-        BetaSheet = 0x400000,  // multiple hydrogen bonded strands
-        BetaBarell = 0x800000,  // closed series of sheets
-
-        TurnOther = 0x1000000,  // protein
-        Turn1 = 0x2000000,
-        Turn2 = 0x4000000,
-        Turn3 = 0x8000000,
-
-        NA = 0x10000000,  // not applicable/available
+        HelixOther = 0x200,  // protein
+        Helix27 = 0x400,
+        Helix3Ten = 0x800,
+        HelixAlpha = 0x1000,
+        HelixGamma = 0x2000,
+        HelixOmega = 0x4000,
+        HelixPi = 0x8000,
+        HelixPolyproline = 0x10000,
+
+        DoubleHelixOther = 0x20000,  // nucleic
+        DoubleHelixZ = 0x40000,
+        DoubleHelixA = 0x80000,
+        DoubleHelixB = 0x100000,
+
+        BetaOther = 0x200000,  // protein
+        BetaStrand = 0x400000,  // single strand
+        BetaSheet = 0x800000,  // multiple hydrogen bonded strands
+        BetaBarell = 0x1000000,  // closed series of sheets
+
+        TurnOther = 0x2000000,  // protein
+        Turn1 = 0x4000000,
+        Turn2 = 0x8000000,
+        Turn3 = 0x10000000,
+
+        NA = 0x20000000,  // not applicable/available
     }
     }
 
 
     export const SecondaryStructureMmcif: { [value: string]: number } = {
     export const SecondaryStructureMmcif: { [value: string]: number } = {
@@ -386,7 +387,7 @@ export namespace SecondaryStructureType {
         G: Flag.Helix | Flag.Helix3Ten,  // 3-helix (310 helix)
         G: Flag.Helix | Flag.Helix3Ten,  // 3-helix (310 helix)
         I: Flag.Helix | Flag.HelixPi,  // 5 helix (pi-helix)
         I: Flag.Helix | Flag.HelixPi,  // 5 helix (pi-helix)
         T: Flag.Turn,  // hydrogen bonded turn
         T: Flag.Turn,  // hydrogen bonded turn
-        S: Flag.Turn,  // bend
+        S: Flag.Bend,  // bend
     }
     }
 }
 }
 
 

+ 1 - 1
src/mol-plugin/behavior/dynamic/labels.ts

@@ -107,7 +107,7 @@ export const SceneLabels = PluginBehavior.create<SceneLabelsProps>({
 
 
         private getLabelsShape = (ctx: RuntimeContext, data: LabelsData, props: SceneLabelsProps, shape?: Shape<Text>) => {
         private getLabelsShape = (ctx: RuntimeContext, data: LabelsData, props: SceneLabelsProps, shape?: Shape<Text>) => {
             this.geo = getLabelsText(data, props, this.geo)
             this.geo = getLabelsText(data, props, this.geo)
-            return Shape.create('Scene Labels', this.geo, this.getColor, this.getSize, this.getLabel, data.transforms)
+            return Shape.create('Scene Labels', data, this.geo, this.getColor, this.getSize, this.getLabel, data.transforms)
         }
         }
 
 
         /** Update structures to be labeled, returns true if changed */
         /** Update structures to be labeled, returns true if changed */

+ 2 - 0
src/mol-plugin/state/actions/data-format.ts

@@ -14,6 +14,7 @@ import { Ccp4Provider, Dsn6Provider, DscifProvider } from './volume';
 import { StateTransforms } from '../transforms';
 import { StateTransforms } from '../transforms';
 import { MmcifProvider, PdbProvider, GroProvider } from './structure';
 import { MmcifProvider, PdbProvider, GroProvider } from './structure';
 import msgpackDecode from 'mol-io/common/msgpack/decode'
 import msgpackDecode from 'mol-io/common/msgpack/decode'
+import { PlyProvider } from './shape';
 
 
 export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> {
 export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> {
     private _list: { name: string, provider: DataFormatProvider<D> }[] = []
     private _list: { name: string, provider: DataFormatProvider<D> }[] = []
@@ -60,6 +61,7 @@ export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | Plugin
         this.add('gro', GroProvider)
         this.add('gro', GroProvider)
         this.add('mmcif', MmcifProvider)
         this.add('mmcif', MmcifProvider)
         this.add('pdb', PdbProvider)
         this.add('pdb', PdbProvider)
+        this.add('ply', PlyProvider)
     };
     };
 
 
     private _clear() {
     private _clear() {

+ 31 - 0
src/mol-plugin/state/actions/shape.ts

@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { PluginContext } from 'mol-plugin/context';
+import { State, StateBuilder } from 'mol-state';
+import { Task } from 'mol-task';
+import { FileInfo } from 'mol-util/file-info';
+import { PluginStateObject } from '../objects';
+import { StateTransforms } from '../transforms';
+import { DataFormatProvider } from './data-format';
+
+export const PlyProvider: DataFormatProvider<any> = {
+    label: 'PLY',
+    description: 'PLY',
+    stringExtensions: ['ply'],
+    binaryExtensions: [],
+    isApplicable: (info: FileInfo, data: string) => {
+        return info.ext === 'ply'
+    },
+    getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.String>, state: State) => {
+        return Task.create('PLY default builder', async taskCtx => {
+            const tree = data.apply(StateTransforms.Data.ParsePly)
+                .apply(StateTransforms.Model.ShapeFromPly)
+                .apply(StateTransforms.Representation.ShapeRepresentation3D)
+            await state.updateTree(tree).runInContext(taskCtx)
+        })
+    }
+}

+ 10 - 0
src/mol-plugin/state/objects.ts

@@ -6,6 +6,7 @@
  */
  */
 
 
 import { CifFile } from 'mol-io/reader/cif';
 import { CifFile } from 'mol-io/reader/cif';
+import { PlyFile } from 'mol-io/reader/ply/schema';
 import { Model as _Model, Structure as _Structure } from 'mol-model/structure';
 import { Model as _Model, Structure as _Structure } from 'mol-model/structure';
 import { VolumeData } from 'mol-model/volume';
 import { VolumeData } from 'mol-model/volume';
 import { PluginBehavior } from 'mol-plugin/behavior/behavior';
 import { PluginBehavior } from 'mol-plugin/behavior/behavior';
@@ -16,6 +17,8 @@ import { StateObject, StateTransformer } from 'mol-state';
 import { Ccp4File } from 'mol-io/reader/ccp4/schema';
 import { Ccp4File } from 'mol-io/reader/ccp4/schema';
 import { Dsn6File } from 'mol-io/reader/dsn6/schema';
 import { Dsn6File } from 'mol-io/reader/dsn6/schema';
 import { ShapeRepresentation } from 'mol-repr/shape/representation';
 import { ShapeRepresentation } from 'mol-repr/shape/representation';
+import { Shape as _Shape } from 'mol-model/shape';
+import { ShapeProvider } from 'mol-model/shape/provider';
 
 
 export type TypeClass = 'root' | 'data' | 'prop'
 export type TypeClass = 'root' | 'data' | 'prop'
 
 
@@ -61,6 +64,7 @@ export namespace PluginStateObject {
     export namespace Format {
     export namespace Format {
         export class Json extends Create<any>({ name: 'JSON Data', typeClass: 'Data' }) { }
         export class Json extends Create<any>({ name: 'JSON Data', typeClass: 'Data' }) { }
         export class Cif extends Create<CifFile>({ name: 'CIF File', typeClass: 'Data' }) { }
         export class Cif extends Create<CifFile>({ name: 'CIF File', typeClass: 'Data' }) { }
+        export class Ply extends Create<PlyFile>({ name: 'PLY File', typeClass: 'Data' }) { }
         export class Ccp4 extends Create<Ccp4File>({ name: 'CCP4/MRC/MAP File', typeClass: 'Data' }) { }
         export class Ccp4 extends Create<Ccp4File>({ name: 'CCP4/MRC/MAP File', typeClass: 'Data' }) { }
         export class Dsn6 extends Create<Dsn6File>({ name: 'DSN6/BRIX File', typeClass: 'Data' }) { }
         export class Dsn6 extends Create<Dsn6File>({ name: 'DSN6/BRIX File', typeClass: 'Data' }) { }
 
 
@@ -71,6 +75,7 @@ export namespace PluginStateObject {
             | { kind: 'cif', data: CifFile }
             | { kind: 'cif', data: CifFile }
             | { kind: 'ccp4', data: Ccp4File }
             | { kind: 'ccp4', data: Ccp4File }
             | { kind: 'dsn6', data: Dsn6File }
             | { kind: 'dsn6', data: Dsn6File }
+            | { kind: 'ply', data: PlyFile }
             // For non-build in extensions
             // For non-build in extensions
             | { kind: 'custom', data: unknown, tag: string })
             | { kind: 'custom', data: unknown, tag: string })
         export type BlobData = BlobEntry[]
         export type BlobData = BlobEntry[]
@@ -100,6 +105,11 @@ export namespace PluginStateObject {
         export class Data extends Create<VolumeData>({ name: 'Volume Data', typeClass: 'Object' }) { }
         export class Data extends Create<VolumeData>({ name: 'Volume Data', typeClass: 'Object' }) { }
         export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>>({ name: 'Volume 3D' }) { }
         export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>>({ name: 'Volume 3D' }) { }
     }
     }
+
+    export namespace Shape {
+        export class Provider extends Create<ShapeProvider<any, any, any>>({ name: 'Shape Provider', typeClass: 'Object' }) { }
+        export class Representation3D extends CreateRepresentation3D<ShapeRepresentation<any, any, any>>({ name: 'Shape 3D' }) { }
+    }
 }
 }
 
 
 export namespace PluginStateTransform {
 export namespace PluginStateTransform {

+ 18 - 0
src/mol-plugin/state/transforms/data.ts

@@ -15,6 +15,7 @@ import { StateTransformer } from 'mol-state';
 import { readFromFile, ajaxGetMany } from 'mol-util/data-source';
 import { readFromFile, ajaxGetMany } from 'mol-util/data-source';
 import * as CCP4 from 'mol-io/reader/ccp4/parser'
 import * as CCP4 from 'mol-io/reader/ccp4/parser'
 import * as DSN6 from 'mol-io/reader/dsn6/parser'
 import * as DSN6 from 'mol-io/reader/dsn6/parser'
+import * as PLY from 'mol-io/reader/ply/parser'
 
 
 export { Download }
 export { Download }
 type Download = typeof Download
 type Download = typeof Download
@@ -185,6 +186,23 @@ const ParseCif = PluginStateTransform.BuiltIn({
     }
     }
 });
 });
 
 
+export { ParsePly }
+type ParsePly = typeof ParsePly
+const ParsePly = PluginStateTransform.BuiltIn({
+    name: 'parse-ply',
+    display: { name: 'Parse PLY', description: 'Parse PLY from String data' },
+    from: [SO.Data.String],
+    to: SO.Format.Ply
+})({
+    apply({ a }) {
+        return Task.create('Parse PLY', async ctx => {
+            const parsed = await PLY.parse(a.data).runInContext(ctx);
+            if (parsed.isError) throw new Error(parsed.message);
+            return new SO.Format.Ply(parsed.result, { label: parsed.result.comments[0] || 'PLY Data' });
+        });
+    }
+});
+
 export { ParseCcp4 }
 export { ParseCcp4 }
 type ParseCcp4 = typeof ParseCcp4
 type ParseCcp4 = typeof ParseCcp4
 const ParseCcp4 = PluginStateTransform.BuiltIn({
 const ParseCcp4 = PluginStateTransform.BuiltIn({

+ 22 - 2
src/mol-plugin/state/transforms/model.ts

@@ -24,6 +24,7 @@ import { trajectoryFromGRO } from 'mol-model-formats/structure/gro';
 import { parseGRO } from 'mol-io/reader/gro/parser';
 import { parseGRO } from 'mol-io/reader/gro/parser';
 import { parseMolScript } from 'mol-script/language/parser';
 import { parseMolScript } from 'mol-script/language/parser';
 import { transpileMolScript } from 'mol-script/script/mol-script/symbols';
 import { transpileMolScript } from 'mol-script/script/mol-script/symbols';
+import { shapeFromPly } from 'mol-model-formats/shape/ply';
 
 
 export { TrajectoryFromBlob };
 export { TrajectoryFromBlob };
 export { TrajectoryFromMmCif };
 export { TrajectoryFromMmCif };
@@ -338,7 +339,6 @@ function updateStructureFromQuery(query: QueryFn<Sel>, src: Structure, obj: SO.M
     return true;
     return true;
 }
 }
 
 
-
 namespace StructureComplexElement {
 namespace StructureComplexElement {
     export type Types = 'atomic-sequence' | 'water' | 'atomic-het' | 'spheres'
     export type Types = 'atomic-sequence' | 'water' | 'atomic-het' | 'spheres'
 }
 }
@@ -394,4 +394,24 @@ async function attachProps(model: Model, ctx: PluginContext, taskCtx: RuntimeCon
         const p = ctx.customModelProperties.get(name);
         const p = ctx.customModelProperties.get(name);
         await p.attach(model).runInContext(taskCtx);
         await p.attach(model).runInContext(taskCtx);
     }
     }
-}
+}
+
+export { ShapeFromPly }
+type ShapeFromPly = typeof ShapeFromPly
+const ShapeFromPly = PluginStateTransform.BuiltIn({
+    name: 'shape-from-ply',
+    display: { name: 'Shape from PLY', description: 'Create Shape from PLY data' },
+    from: SO.Format.Ply,
+    to: SO.Shape.Provider,
+    params(a) {
+        return { };
+    }
+})({
+    apply({ a, params }) {
+        return Task.create('Create shape from PLY', async ctx => {
+            const shape = await shapeFromPly(a.data, params).runInContext(ctx)
+            const props = { label: 'Shape' };
+            return new SO.Shape.Provider(shape, props);
+        });
+    }
+});

+ 35 - 1
src/mol-plugin/state/transforms/representation.ts

@@ -30,6 +30,7 @@ import { Color } from 'mol-util/color';
 import { Overpaint } from 'mol-theme/overpaint';
 import { Overpaint } from 'mol-theme/overpaint';
 import { Transparency } from 'mol-theme/transparency';
 import { Transparency } from 'mol-theme/transparency';
 import { getStructureOverpaint, getStructureTransparency } from './helpers';
 import { getStructureOverpaint, getStructureTransparency } from './helpers';
+import { BaseGeometry } from 'mol-geo/geometry/base';
 
 
 export { StructureRepresentation3D }
 export { StructureRepresentation3D }
 export { StructureRepresentation3DHelpers }
 export { StructureRepresentation3DHelpers }
@@ -193,7 +194,6 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
     }
     }
 });
 });
 
 
-
 type StructureLabels3D = typeof StructureLabels3D
 type StructureLabels3D = typeof StructureLabels3D
 const StructureLabels3D = PluginStateTransform.BuiltIn({
 const StructureLabels3D = PluginStateTransform.BuiltIn({
     name: 'structure-labels-3d',
     name: 'structure-labels-3d',
@@ -514,4 +514,38 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
             return StateTransformer.UpdateResult.Updated;
             return StateTransformer.UpdateResult.Updated;
         });
         });
     }
     }
+});
+
+//
+
+export { ShapeRepresentation3D }
+type ShapeRepresentation3D = typeof ShapeRepresentation3D
+const ShapeRepresentation3D = PluginStateTransform.BuiltIn({
+    name: 'shape-representation-3d',
+    display: '3D Representation',
+    from: SO.Shape.Provider,
+    to: SO.Shape.Representation3D,
+    params: (a, ctx: PluginContext) => {
+        return a ? a.data.params : BaseGeometry.Params
+    }
+})({
+    canAutoUpdate() {
+        return true;
+    },
+    apply({ a, params }, plugin: PluginContext) {
+        return Task.create('Shape Representation', async ctx => {
+            const props = { ...PD.getDefaultValues(a.data.params), params }
+            const repr = ShapeRepresentation(a.data.getShape, a.data.geometryUtils)
+            // TODO set initial state, repr.setState({})
+            await repr.createOrUpdate(props, a.data.data).runInContext(ctx);
+            return new SO.Shape.Representation3D({ repr, source: a }, { label: a.data.label });
+        });
+    },
+    update({ a, b, oldParams, newParams }, plugin: PluginContext) {
+        return Task.create('Shape Representation', async ctx => {
+            const props = { ...b.data.repr.props, ...newParams }
+            await b.data.repr.createOrUpdate(props, a.data.data).runInContext(ctx);
+            return StateTransformer.UpdateResult.Updated;
+        });
+    }
 });
 });

+ 1 - 1
src/mol-plugin/util/structure-labels.ts

@@ -44,7 +44,7 @@ export async function getLabelRepresentation(ctx: RuntimeContext, structure: Str
 
 
 function getLabelsShape(ctx: RuntimeContext, data: LabelsData, props: PD.Values<Text.Params>, shape?: Shape<Text>) {
 function getLabelsShape(ctx: RuntimeContext, data: LabelsData, props: PD.Values<Text.Params>, shape?: Shape<Text>) {
     const geo = getLabelsText(data, props, shape && shape.geometry);
     const geo = getLabelsText(data, props, shape && shape.geometry);
-    return Shape.create('Scene Labels', geo, () => ColorNames.dimgrey, g => data.sizes[g], () => '')
+    return Shape.create('Scene Labels', data, geo, () => ColorNames.dimgrey, g => data.sizes[g], () => '')
 }
 }
 
 
 const boundaryHelper = new BoundaryHelper();
 const boundaryHelper = new BoundaryHelper();

+ 1 - 3
src/mol-repr/shape/representation.ts

@@ -57,9 +57,7 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
             updateState.createNew = true
             updateState.createNew = true
         } else if (shape && _shape && shape.id === _shape.id) {
         } else if (shape && _shape && shape.id === _shape.id) {
             // console.log('same shape')
             // console.log('same shape')
-            // trigger color update when shape has not changed
-            updateState.updateColor = true
-            updateState.updateTransform = true
+            // nothing to set
         } else if (shape && _shape && shape.id !== _shape.id) {
         } else if (shape && _shape && shape.id !== _shape.id) {
             // console.log('new shape')
             // console.log('new shape')
             updateState.updateTransform = true
             updateState.updateTransform = true

+ 5 - 1
src/mol-theme/color/secondary-structure.ts

@@ -22,6 +22,8 @@ const SecondaryStructureColors = ColorMap({
     'betaTurn': 0x6080FF,
     'betaTurn': 0x6080FF,
     'betaStrand': 0xFFC800,
     'betaStrand': 0xFFC800,
     'coil': 0xFFFFFF,
     'coil': 0xFFFFFF,
+    'bend': 0x66D8C9 /* biting original color used 0x00FF00 */,
+    'turn': 0x00B266,
 
 
     'dna': 0xAE00FE,
     'dna': 0xAE00FE,
     'rna': 0xFD0162,
     'rna': 0xFD0162,
@@ -53,8 +55,10 @@ export function secondaryStructureColor(unit: Unit, element: ElementIndex): Colo
         return SecondaryStructureColors.alphaHelix
         return SecondaryStructureColors.alphaHelix
     } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Beta)) {
     } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Beta)) {
         return SecondaryStructureColors.betaStrand
         return SecondaryStructureColors.betaStrand
+    } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Bend)) {
+        return SecondaryStructureColors.bend
     } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Turn)) {
     } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Turn)) {
-        return SecondaryStructureColors.coil
+        return SecondaryStructureColors.turn
     } else {
     } else {
         const moleculeType = getElementMoleculeType(unit, element)
         const moleculeType = getElementMoleculeType(unit, element)
         if (moleculeType === MoleculeType.DNA) {
         if (moleculeType === MoleculeType.DNA) {

+ 6 - 6
src/mol-util/array.ts

@@ -1,5 +1,5 @@
 /**
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
  */
@@ -9,7 +9,7 @@ import { NumberArray } from './type-helpers';
 // TODO move to mol-math as Vector???
 // TODO move to mol-math as Vector???
 
 
 /** Get the maximum value in an array */
 /** Get the maximum value in an array */
-export function arrayMax(array: NumberArray) {
+export function arrayMax(array: ArrayLike<number>) {
     let max = -Infinity
     let max = -Infinity
     for (let i = 0, il = array.length; i < il; ++i) {
     for (let i = 0, il = array.length; i < il; ++i) {
         if (array[i] > max) max = array[i]
         if (array[i] > max) max = array[i]
@@ -18,7 +18,7 @@ export function arrayMax(array: NumberArray) {
 }
 }
 
 
 /** Get the minimum value in an array */
 /** Get the minimum value in an array */
-export function arrayMin(array: NumberArray) {
+export function arrayMin(array: ArrayLike<number>) {
     let min = Infinity
     let min = Infinity
     for (let i = 0, il = array.length; i < il; ++i) {
     for (let i = 0, il = array.length; i < il; ++i) {
         if (array[i] < min) min = array[i]
         if (array[i] < min) min = array[i]
@@ -27,7 +27,7 @@ export function arrayMin(array: NumberArray) {
 }
 }
 
 
 /** Get the sum of values in an array */
 /** Get the sum of values in an array */
-export function arraySum(array: NumberArray, stride = 1, offset = 0) {
+export function arraySum(array: ArrayLike<number>, stride = 1, offset = 0) {
     const n = array.length
     const n = array.length
     let sum = 0
     let sum = 0
     for (let i = offset; i < n; i += stride) {
     for (let i = offset; i < n; i += stride) {
@@ -37,12 +37,12 @@ export function arraySum(array: NumberArray, stride = 1, offset = 0) {
 }
 }
 
 
 /** Get the mean of values in an array */
 /** Get the mean of values in an array */
-export function arrayMean(array: NumberArray, stride = 1, offset = 0) {
+export function arrayMean(array: ArrayLike<number>, stride = 1, offset = 0) {
     return arraySum(array, stride, offset) / (array.length / stride)
     return arraySum(array, stride, offset) / (array.length / stride)
 }
 }
 
 
 /** Get the root mean square of values in an array */
 /** Get the root mean square of values in an array */
-export function arrayRms(array: NumberArray) {
+export function arrayRms(array: ArrayLike<number>) {
     const n = array.length
     const n = array.length
     let sumSq = 0
     let sumSq = 0
     for (let i = 0; i < n; ++i) {
     for (let i = 0; i < n; ++i) {

+ 1 - 1
src/mol-util/param-definition.ts

@@ -284,7 +284,7 @@ export namespace ParamDefinition {
         return true;
         return true;
     }
     }
 
 
-    function isParamEqual(p: Any, a: any, b: any): boolean {
+    export function isParamEqual(p: Any, a: any, b: any): boolean {
         if (a === b) return true;
         if (a === b) return true;
         if (!a) return !b;
         if (!a) return !b;
         if (!b) return !a;
         if (!b) return !a;

+ 34 - 35
src/tests/browser/index.html

@@ -1,38 +1,37 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
-    <head>
-        <meta charset="utf-8" />
-        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
-        <title>Mol* Browser Test</title>
-        <style>
-            * {
-                margin: 0;
-                padding: 0;
-                box-sizing: border-box;
-            }
-            html, body {
-                width: 100%;
-                height: 100%;
-                overflow: hidden;
-            }
-        </style>
-    </head>
-    <body>
-        <div id="app"></div>
-        <script type="text/javascript">
-            function urlQueryParameter (id) {
-                if (typeof window === 'undefined') return undefined
-                const a = new RegExp(`${id}=([^&#=]*)`)
-                const m = a.exec(window.location.search)
-                return m ? decodeURIComponent(m[1]) : undefined
-            }
-
-            const name = urlQueryParameter('name')
-            if (name) {
-                const script = document.createElement('script')
-                script.src = name + '.js'
-                document.body.appendChild(script)
-            }
-        </script>
-    </body>
+	<head>
+		<meta charset="utf-8" />
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<title>Mol* Browser Test</title>
+		<style>
+			* {
+				margin: 0;
+				padding: 0;
+				box-sizing: border-box;
+			}
+			html, body {
+				width: 100%;
+				height: 100%;
+				overflow: hidden;
+			}
+		</style>
+	</head>
+	<body>
+		<div id="app"></div>
+		<script type="text/javascript">
+			function urlQueryParameter (id) {
+				if (typeof window === 'undefined') return undefined
+				const a = new RegExp(`${id}=([^&#=]*)`)
+				const m = a.exec(window.location.search)
+				return m ? decodeURIComponent(m[1]) : undefined
+			}
+			const name = urlQueryParameter('name')
+			if (name) {
+				const script = document.createElement('script')
+				script.src = name + '.js'
+				document.body.appendChild(script)
+			}
+		</script>
+	</body>
 </html>
 </html>

+ 6 - 7
src/tests/browser/render-shape.ts

@@ -68,7 +68,7 @@ async function getSphereMesh(ctx: RuntimeContext, centers: number[], mesh?: Mesh
     const builderState = MeshBuilder.createState(centers.length * 128, centers.length * 128 / 2, mesh)
     const builderState = MeshBuilder.createState(centers.length * 128, centers.length * 128 / 2, mesh)
     const t = Mat4.identity()
     const t = Mat4.identity()
     const v = Vec3.zero()
     const v = Vec3.zero()
-    const sphere = Sphere(2)
+    const sphere = Sphere(3)
     builderState.currentGroup = 0
     builderState.currentGroup = 0
     for (let i = 0, il = centers.length / 3; i < il; ++i) {
     for (let i = 0, il = centers.length / 3; i < il; ++i) {
         // for production, calls to update should be guarded by `if (ctx.shouldUpdate)`
         // for production, calls to update should be guarded by `if (ctx.shouldUpdate)`
@@ -81,8 +81,8 @@ async function getSphereMesh(ctx: RuntimeContext, centers: number[], mesh?: Mesh
 }
 }
 
 
 const myData = {
 const myData = {
-    centers: [0, 0, 0, 0, 3, 0],
-    colors: [ColorNames.tomato, ColorNames.springgreen],
+    centers: [0, 0, 0, 0, 3, 0, 1, 0 , 4],
+    colors: [ColorNames.tomato, ColorNames.springgreen, ColorNames.springgreen],
     labels: ['Sphere 0, Instance A', 'Sphere 1, Instance A', 'Sphere 0, Instance B', 'Sphere 1, Instance B'],
     labels: ['Sphere 0, Instance A', 'Sphere 1, Instance A', 'Sphere 0, Instance B', 'Sphere 1, Instance B'],
     transforms: [Mat4.identity(), Mat4.fromTranslation(Mat4.zero(), Vec3.create(3, 0, 0))]
     transforms: [Mat4.identity(), Mat4.fromTranslation(Mat4.zero(), Vec3.create(3, 0, 0))]
 }
 }
@@ -96,8 +96,8 @@ async function getShape(ctx: RuntimeContext, data: MyData, props: {}, shape?: Sh
     const { centers, colors, labels, transforms } = data
     const { centers, colors, labels, transforms } = data
     const mesh = await getSphereMesh(ctx, centers, shape && shape.geometry)
     const mesh = await getSphereMesh(ctx, centers, shape && shape.geometry)
     const groupCount = centers.length / 3
     const groupCount = centers.length / 3
-    return shape || Shape.create(
-        'test', mesh,
+    return Shape.create(
+        'test', data, mesh,
         (groupId: number) => colors[groupId], // color: per group, same for instances
         (groupId: number) => colors[groupId], // color: per group, same for instances
         () => 1, // size: constant
         () => 1, // size: constant
         (groupId: number, instanceId: number) => labels[instanceId * groupCount + groupId], // label: per group and instance
         (groupId: number, instanceId: number) => labels[instanceId * groupCount + groupId], // label: per group and instance
@@ -108,10 +108,9 @@ async function getShape(ctx: RuntimeContext, data: MyData, props: {}, shape?: Sh
 // Init ShapeRepresentation container
 // Init ShapeRepresentation container
 const repr = ShapeRepresentation(getShape, Mesh.Utils)
 const repr = ShapeRepresentation(getShape, Mesh.Utils)
 
 
-async function init() {
+export async function init() {
     // Create shape from myData and add to canvas3d
     // Create shape from myData and add to canvas3d
     await repr.createOrUpdate({}, myData).run((p: Progress) => console.log(Progress.format(p)))
     await repr.createOrUpdate({}, myData).run((p: Progress) => console.log(Progress.format(p)))
-    console.log(repr)
     canvas3d.add(repr)
     canvas3d.add(repr)
     canvas3d.resetCamera()
     canvas3d.resetCamera()
 
 

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