Browse Source

Merge branch 'master' of https://github.com/molstar/molstar-proto into state_representation

David Sehnal 6 years ago
parent
commit
8d7ae26da0
94 changed files with 1654 additions and 1291 deletions
  1. 21 5
      package-lock.json
  2. 1 0
      package.json
  3. 17 0
      src/apps/canvas/app.ts
  4. 4 4
      src/apps/canvas/assembly-symmetry.ts
  5. 14 20
      src/apps/canvas/component/representation.tsx
  6. 3 2
      src/apps/canvas/component/structure-view.tsx
  7. 3 2
      src/apps/canvas/component/volume-view.tsx
  8. 28 32
      src/apps/canvas/structure-view.ts
  9. 2 2
      src/apps/canvas/volume-view.ts
  10. 52 36
      src/mol-canvas3d/canvas3d.ts
  11. 2 34
      src/mol-geo/geometry/color-data.ts
  12. 2 1
      src/mol-geo/geometry/direct-volume/direct-volume.ts
  13. 1 1
      src/mol-geo/geometry/direct-volume/transfer-function.ts
  14. 19 24
      src/mol-geo/geometry/geometry.ts
  15. 2 1
      src/mol-geo/geometry/lines/lines.ts
  16. 3 2
      src/mol-geo/geometry/mesh/mesh.ts
  17. 3 2
      src/mol-geo/geometry/points/points.ts
  18. 1 18
      src/mol-geo/geometry/size-data.ts
  19. 2 0
      src/mol-gl/_spec/renderer.spec.ts
  20. 4 1
      src/mol-gl/render-object.ts
  21. 1 5
      src/mol-gl/renderable.ts
  22. 2 3
      src/mol-gl/renderable/schema.ts
  23. 0 5
      src/mol-gl/renderer.ts
  24. 1 1
      src/mol-gl/webgl/context.ts
  25. 2 2
      src/mol-model/structure/structure/unit/gaussian-density.ts
  26. 1 1
      src/mol-plugin/context.ts
  27. 3 3
      src/mol-plugin/state/objects.ts
  28. 10 4
      src/mol-plugin/state/transforms/visuals.ts
  29. 1 1
      src/mol-plugin/util/canvas3d-identify.ts
  30. 0 129
      src/mol-repr/index.ts
  31. 181 0
      src/mol-repr/representation.ts
  32. 18 16
      src/mol-repr/shape/representation.ts
  33. 21 10
      src/mol-repr/structure/complex-representation.ts
  34. 32 28
      src/mol-repr/structure/complex-visual.ts
  35. 28 0
      src/mol-repr/structure/registry.ts
  36. 10 16
      src/mol-repr/structure/representation.ts
  37. 25 21
      src/mol-repr/structure/representation/backbone.ts
  38. 29 16
      src/mol-repr/structure/representation/ball-and-stick.ts
  39. 28 28
      src/mol-repr/structure/representation/carbohydrate.ts
  40. 29 15
      src/mol-repr/structure/representation/cartoon.ts
  41. 25 24
      src/mol-repr/structure/representation/distance-restraint.ts
  42. 31 29
      src/mol-repr/structure/representation/molecular-surface.ts
  43. 25 21
      src/mol-repr/structure/representation/point.ts
  44. 25 21
      src/mol-repr/structure/representation/spacefill.ts
  45. 32 17
      src/mol-repr/structure/units-representation.ts
  46. 50 54
      src/mol-repr/structure/units-visual.ts
  47. 7 11
      src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts
  48. 9 13
      src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts
  49. 8 12
      src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts
  50. 9 13
      src/mol-repr/structure/visual/element-point.ts
  51. 8 10
      src/mol-repr/structure/visual/element-sphere.ts
  52. 8 12
      src/mol-repr/structure/visual/gaussian-density-point.ts
  53. 8 9
      src/mol-repr/structure/visual/gaussian-density-volume.ts
  54. 8 9
      src/mol-repr/structure/visual/gaussian-surface-mesh.ts
  55. 8 12
      src/mol-repr/structure/visual/gaussian-surface-wireframe.ts
  56. 11 10
      src/mol-repr/structure/visual/inter-unit-link-cylinder.ts
  57. 14 15
      src/mol-repr/structure/visual/intra-unit-link-cylinder.ts
  58. 20 13
      src/mol-repr/structure/visual/nucleotide-block-mesh.ts
  59. 8 9
      src/mol-repr/structure/visual/polymer-backbone-cylinder.ts
  60. 11 12
      src/mol-repr/structure/visual/polymer-direction-wedge.ts
  61. 12 16
      src/mol-repr/structure/visual/polymer-gap-cylinder.ts
  62. 11 11
      src/mol-repr/structure/visual/polymer-trace-mesh.ts
  63. 10 16
      src/mol-repr/structure/visual/util/common.ts
  64. 5 4
      src/mol-repr/structure/visual/util/element.ts
  65. 1 2
      src/mol-repr/structure/visual/util/link.ts
  66. 0 20
      src/mol-repr/util.ts
  67. 17 67
      src/mol-repr/volume/direct-volume.ts
  68. 16 39
      src/mol-repr/volume/isosurface-mesh.ts
  69. 25 0
      src/mol-repr/volume/registry.ts
  70. 63 43
      src/mol-repr/volume/representation.ts
  71. 82 107
      src/mol-theme/color.ts
  72. 26 5
      src/mol-theme/color/carbohydrate-symbol.ts
  73. 21 10
      src/mol-theme/color/chain-id.ts
  74. 23 9
      src/mol-theme/color/cross-link.ts
  75. 0 22
      src/mol-theme/color/custom.ts
  76. 21 6
      src/mol-theme/color/element-index.ts
  77. 15 3
      src/mol-theme/color/element-symbol.ts
  78. 15 3
      src/mol-theme/color/molecule-type.ts
  79. 21 6
      src/mol-theme/color/polymer-index.ts
  80. 15 3
      src/mol-theme/color/residue-name.ts
  81. 15 3
      src/mol-theme/color/secondary-structure.ts
  82. 21 12
      src/mol-theme/color/sequence-id.ts
  83. 17 5
      src/mol-theme/color/shape-group.ts
  84. 17 3
      src/mol-theme/color/uniform.ts
  85. 21 6
      src/mol-theme/color/unit-index.ts
  86. 57 23
      src/mol-theme/size.ts
  87. 20 8
      src/mol-theme/size/physical.ts
  88. 21 8
      src/mol-theme/size/uniform.ts
  89. 46 0
      src/mol-theme/theme.ts
  90. 13 3
      src/mol-util/color/color.ts
  91. 29 7
      src/mol-util/color/scale.ts
  92. 25 1
      src/mol-util/object.ts
  93. 15 2
      src/mol-util/param-definition.ts
  94. 7 1
      webpack.config.js

+ 21 - 5
package-lock.json

@@ -1721,6 +1721,12 @@
         "safe-buffer": "^5.0.1"
       }
     },
+    "circular-dependency-plugin": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.0.2.tgz",
+      "integrity": "sha512-oC7/DVAyfcY3UWKm0sN/oVoDedQDQiw/vIiAnuTWTpE5s0zWf7l3WY417Xw/Fbi/QbAjctAkxgMiS9P0s3zkmA==",
+      "dev": true
+    },
     "class-utils": {
       "version": "0.3.6",
       "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
@@ -3412,7 +3418,8 @@
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "concat-map": {
           "version": "0.0.1",
@@ -3423,7 +3430,8 @@
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -3540,7 +3548,8 @@
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "ini": {
           "version": "1.3.5",
@@ -3552,6 +3561,7 @@
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -3574,12 +3584,14 @@
         "minimist": {
           "version": "0.0.8",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "minipass": {
           "version": "2.2.4",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.1",
             "yallist": "^3.0.0"
@@ -3598,6 +3610,7 @@
           "version": "0.5.1",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -3678,7 +3691,8 @@
         "number-is-nan": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -3690,6 +3704,7 @@
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -3811,6 +3826,7 @@
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",

+ 1 - 0
package.json

@@ -88,6 +88,7 @@
     "@types/react-dom": "^16.0.9",
     "@types/webgl2": "0.0.4",
     "benchmark": "^2.1.4",
+    "circular-dependency-plugin": "^5.0.2",
     "cpx": "^1.5.0",
     "css-loader": "^1.0.1",
     "extra-watch-webpack-plugin": "^1.0.3",

+ 17 - 0
src/apps/canvas/app.ts

@@ -12,6 +12,10 @@ import { CifBlock } from 'mol-io/reader/cif';
 import { VolumeView } from './volume-view';
 import { Ccp4File } from 'mol-io/reader/ccp4/schema';
 import { Progress } from 'mol-task';
+import { ColorTheme } from 'mol-theme/color';
+import { SizeTheme } from 'mol-theme/size';
+import { StructureRepresentationRegistry } from 'mol-repr/structure/registry';
+import { VolumeRepresentationRegistry } from 'mol-repr/volume/registry';
 
 export class App {
     canvas3d: Canvas3D
@@ -23,6 +27,11 @@ export class App {
     structureLoaded: BehaviorSubject<StructureView | null> = new BehaviorSubject<StructureView | null>(null)
     volumeLoaded: BehaviorSubject<VolumeView | null> = new BehaviorSubject<VolumeView | null>(null)
 
+    colorThemeRegistry = new ColorTheme.Registry()
+    sizeThemeRegistry = new SizeTheme.Registry()
+    structureRepresentationRegistry = new StructureRepresentationRegistry()
+    volumeRepresentationRegistry = new VolumeRepresentationRegistry()
+
     initViewer(_canvas: HTMLCanvasElement, _container: HTMLDivElement) {
         this.canvas = _canvas
         this.container = _container
@@ -64,6 +73,14 @@ export class App {
         console.log(Progress.format(progress))
     }
 
+    get reprCtx () {
+        return {
+            webgl: this.canvas3d.webgl,
+            colorThemeRegistry: this.colorThemeRegistry,
+            sizeThemeRegistry: this.sizeThemeRegistry
+        }
+    }
+
     //
 
     async loadMmcif(cif: CifBlock, assemblyId?: string) {

+ 4 - 4
src/apps/canvas/assembly-symmetry.ts

@@ -66,10 +66,10 @@ export function getClusterColorTheme(symmetryId: number, assemblySymmetry: Assem
     const DefaultColor = Color(0xCCCCCC)
     const s = assemblySymmetry.db.rcsb_assembly_symmetry
     const symmetry = Table.pickRow(s, i => s.id.value(i) === symmetryId)
-    if (!symmetry) return { features: {}, granularity: 'uniform', color: () => DefaultColor }
+    if (!symmetry) return { granularity: 'uniform', color: () => DefaultColor, props: {} }
 
     const clusters = assemblySymmetry.getClusters(symmetryId)
-    if (!clusters._rowCount) return { features: {}, granularity: 'uniform', color: () => DefaultColor }
+    if (!clusters._rowCount) return { granularity: 'uniform', color: () => DefaultColor, props: {} }
 
     const clusterByMember = new Map<string, number>()
     for (let i = 0, il = clusters._rowCount; i < il; ++i) {
@@ -83,7 +83,6 @@ export function getClusterColorTheme(symmetryId: number, assemblySymmetry: Assem
     const scale = ColorScale.create({ domain: [ 0, clusters._rowCount - 1 ] })
 
     return {
-        features: {},
         granularity: 'instance',
         color: (location: Location): Color => {
             if (StructureElement.isLocation(location)) {
@@ -94,6 +93,7 @@ export function getClusterColorTheme(symmetryId: number, assemblySymmetry: Assem
                 return cluster !== undefined ? scale.color(cluster) : DefaultColor
             }
             return DefaultColor
-        }
+        },
+        props: {}
     }
 }

+ 14 - 20
src/apps/canvas/component/representation.tsx

@@ -8,16 +8,13 @@ import * as React from 'react'
 import { Canvas3D } from 'mol-canvas3d/canvas3d';
 import { App } from '../app';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { Representation } from 'mol-repr';
+import { Representation } from 'mol-repr/representation';
 import { ParametersComponent } from 'mol-app/component/parameters';
-import { ColorTheme } from 'mol-theme/color';
-import { getColorThemeProps } from 'mol-geo/geometry/color-data';
-import { ColorThemeComponent } from 'mol-app/component/color-theme';
 
-export interface RepresentationComponentProps {
+export interface RepresentationComponentProps<P extends PD.Params> {
     app: App
     canvas3d: Canvas3D
-    repr: Representation<PD.Params>
+    repr: Representation<P>
 }
 
 export interface RepresentationComponentState {
@@ -26,13 +23,13 @@ export interface RepresentationComponentState {
     reprProps: Readonly<{}>
 }
 
-export class RepresentationComponent extends React.Component<RepresentationComponentProps, RepresentationComponentState> {
+export class RepresentationComponent<P extends PD.Params> extends React.Component<RepresentationComponentProps<P>, RepresentationComponentState> {
 
-    private stateFromRepr(repr: Representation<PD.Params>) {
+    private stateFromRepr(repr: Representation<P>) {
         return {
-            label: this.props.repr.label,
-            reprParams: this.props.repr.params,
-            reprProps: this.props.repr.props
+            label: repr.label,
+            reprParams: repr.params,
+            reprProps: repr.props
         }
     }
 
@@ -41,21 +38,18 @@ export class RepresentationComponent extends React.Component<RepresentationCompo
     }
 
     async onChange(k: string, v: any) {
-        const ctx = { webgl: this.props.canvas3d.webgl }
-        await this.props.app.runTask(this.props.repr.createOrUpdate(ctx, { [k]: v }).run(
+        await this.props.app.runTask(this.props.repr.createOrUpdate(this.props.app.reprCtx, { [k]: v }).run(
             progress => this.props.app.log(progress)
         ), 'Representation Update')
-        this.props.canvas3d.add(this.props.repr)
-        this.props.canvas3d.requestDraw(true)
         this.setState(this.stateFromRepr(this.props.repr))
     }
 
     render() {
         const { label, reprParams, reprProps } = this.state
-        let colorTheme: ColorTheme | undefined = undefined
-        if ('colorTheme' in reprProps) {
-            colorTheme = ColorTheme(getColorThemeProps(reprProps))
-        }
+        // let colorTheme: ColorTheme | undefined = undefined
+        // if ('colorTheme' in reprProps) {
+        //     colorTheme = ColorTheme(getColorThemeProps(reprProps))
+        // }
 
         return <div>
             <div>
@@ -68,7 +62,7 @@ export class RepresentationComponent extends React.Component<RepresentationCompo
                     onChange={(k, v) => this.onChange(k as string, v)}
                 />
             </div>
-            { colorTheme !== undefined ? <ColorThemeComponent colorTheme={colorTheme} /> : '' }
+            {/* { colorTheme !== undefined ? <ColorThemeComponent colorTheme={colorTheme} /> : '' } */}
         </div>;
     }
 }

+ 3 - 2
src/apps/canvas/component/structure-view.tsx

@@ -7,8 +7,8 @@
 import * as React from 'react'
 import { StructureView } from '../structure-view';
 import { RepresentationComponent } from './representation';
-import { Representation } from 'mol-repr';
-import { StructureRepresentation } from 'mol-repr/structure/index';
+import { Representation } from 'mol-repr/representation';
+import { StructureRepresentation } from 'mol-repr/structure/representation';
 
 export interface StructureViewComponentProps {
     structureView: StructureView
@@ -37,6 +37,7 @@ export class StructureViewComponent extends React.Component<StructureViewCompone
             structureView: sv,
 
             label: sv.label,
+            structure: sv.structure,
             modelId: sv.modelId,
             modelIds: sv.getModelIds(),
             assemblyId: sv.assemblyId,

+ 3 - 2
src/apps/canvas/component/volume-view.tsx

@@ -6,9 +6,9 @@
 
 import * as React from 'react'
 import { RepresentationComponent } from './representation';
-import { Representation } from 'mol-repr';
+import { Representation } from 'mol-repr/representation';
 import { VolumeView } from '../volume-view';
-import { VolumeRepresentation } from 'mol-repr/volume/index';
+import { VolumeRepresentation } from 'mol-repr/volume/representation';
 
 export interface VolumeViewComponentProps {
     volumeView: VolumeView
@@ -28,6 +28,7 @@ export class VolumeViewComponent extends React.Component<VolumeViewComponentProp
         return {
             volumeView: vv,
             label: vv.label,
+            volume: vv.volume,
             active: vv.active,
             volumeRepresentations: vv.volumeRepresentations
         }

+ 28 - 32
src/apps/canvas/structure-view.ts

@@ -17,15 +17,8 @@ import { Canvas3D } from 'mol-canvas3d/canvas3d';
 // import { addBoundingBox } from 'mol-geo/mesh/builder/bounding-box';
 import { BehaviorSubject } from 'rxjs';
 import { App } from './app';
-import { StructureRepresentation } from 'mol-repr/structure/index';
-import { ShapeRepresentation, ShapeProps } from 'mol-repr/shape/index';
-import { CartoonRepresentation } from 'mol-repr/structure/representation/cartoon';
-import { MolecularSurfaceRepresentation } from 'mol-repr/structure/representation/molecular-surface';
-import { PointRepresentation } from 'mol-repr/structure/representation/point';
-import { BallAndStickRepresentation } from 'mol-repr/structure/representation/ball-and-stick';
-import { CarbohydrateRepresentation } from 'mol-repr/structure/representation/carbohydrate';
-import { SpacefillRepresentation } from 'mol-repr/structure/representation/spacefill';
-import { DistanceRestraintRepresentation } from 'mol-repr/structure/representation/distance-restraint';
+import { StructureRepresentation } from 'mol-repr/structure/representation';
+import { ShapeRepresentation, ShapeParams } from 'mol-repr/shape/representation';
 
 export interface StructureView {
     readonly app: App
@@ -39,7 +32,7 @@ export interface StructureView {
     readonly active: { [k: string]: boolean }
     readonly structureRepresentations: { [k: string]: StructureRepresentation<any> }
     readonly updated: BehaviorSubject<null>
-    readonly symmetryAxes: ShapeRepresentation<ShapeProps>
+    readonly symmetryAxes: ShapeRepresentation<ShapeParams>
 
     setSymmetryAxes(value: boolean): void
     setStructureRepresentation(name: string, value: boolean): void
@@ -65,26 +58,18 @@ interface StructureViewProps {
 
 export async function StructureView(app: App, canvas3d: Canvas3D, models: ReadonlyArray<Model>, props: StructureViewProps = {}): Promise<StructureView> {
     const active: { [k: string]: boolean } = {
-        cartoon: true,
-        point: false,
-        surface: false,
-        ballAndStick: false,
-        carbohydrate: false,
-        spacefill: false,
-        distanceRestraint: false,
-        symmetryAxes: true,
+        'cartoon': true,
+        'ball-and-stick': true,
+        // point: false,
+        // surface: false,
+        // carbohydrate: false,
+        // spacefill: false,
+        // distanceRestraint: false,
+        // symmetryAxes: true,
         // polymerSphere: false,
     }
 
-    const structureRepresentations: { [k: string]: StructureRepresentation<any> } = {
-        cartoon: CartoonRepresentation(),
-        surface: MolecularSurfaceRepresentation(),
-        point: PointRepresentation(),
-        ballAndStick: BallAndStickRepresentation(),
-        carbohydrate: CarbohydrateRepresentation(),
-        spacefill: SpacefillRepresentation(),
-        distanceRestraint: DistanceRestraintRepresentation(),
-    }
+    const structureRepresentations: { [k: string]: StructureRepresentation<any> } = {}
 
     const symmetryAxes = ShapeRepresentation()
     const polymerSphere = ShapeRepresentation()
@@ -206,14 +191,25 @@ export async function StructureView(app: App, canvas3d: Canvas3D, models: Readon
     async function createStructureRepr() {
         if (structure) {
             console.log('createStructureRepr')
-            for (const k in structureRepresentations) {
+            for (const k in active) {
                 if (active[k]) {
-                    await app.runTask(structureRepresentations[k].createOrUpdate({ webgl: canvas3d.webgl }, {}, structure).run(
+                    let repr: StructureRepresentation
+                    if (structureRepresentations[k]) {
+                        repr = structureRepresentations[k]
+                    } else {
+                        repr = app.structureRepresentationRegistry.create(k, app.reprCtx, structure)
+                        structureRepresentations[k] = repr
+                        canvas3d.add(repr)
+                    }
+                    await app.runTask(repr.createOrUpdate(app.reprCtx, {}, {}, structure).run(
                         progress => app.log(progress)
                     ), 'Create/update representation')
-                    canvas3d.add(structureRepresentations[k])
                 } else {
-                    canvas3d.remove(structureRepresentations[k])
+                    if (structureRepresentations[k]) {
+                        canvas3d.remove(structureRepresentations[k])
+                        structureRepresentations[k].destroy()
+                        delete structureRepresentations[k]
+                    }
                 }
             }
 
@@ -264,7 +260,7 @@ export async function StructureView(app: App, canvas3d: Canvas3D, models: Readon
                     //     colorFunction: colorTheme.color,
                     //     colorGranularity: colorTheme.granularity,
                     // }).run()
-                    await symmetryAxes.createOrUpdate({ webgl: canvas3d.webgl }, {}, axesShape).run()
+                    await symmetryAxes.createOrUpdate(app.reprCtx, {}, {}, axesShape).run()
                     canvas3d.add(symmetryAxes)
                 } else {
                     canvas3d.remove(symmetryAxes)

+ 2 - 2
src/apps/canvas/volume-view.ts

@@ -8,7 +8,7 @@ import { Canvas3D } from 'mol-canvas3d/canvas3d';
 import { BehaviorSubject } from 'rxjs';
 import { App } from './app';
 import { VolumeData } from 'mol-model/volume';
-import { VolumeRepresentation } from 'mol-repr/volume/index';
+import { VolumeRepresentation } from 'mol-repr/volume/representation';
 import { IsosurfaceRepresentation } from 'mol-repr/volume/isosurface-mesh';
 import { DirectVolumeRepresentation } from 'mol-repr/volume/direct-volume';
 
@@ -54,7 +54,7 @@ export async function VolumeView(app: App, viewer: Canvas3D, volume: VolumeData,
     async function createVolumeRepr() {
         for (const k in volumeRepresentations) {
             if (active[k]) {
-                await app.runTask(volumeRepresentations[k].createOrUpdate({ webgl: viewer.webgl }, {}, volume).run(
+                await app.runTask(volumeRepresentations[k].createOrUpdate(app.reprCtx, {}, {}, volume).run(
                     progress => app.log(progress)
                 ), 'Create/update representation')
                 viewer.add(volumeRepresentations[k])

+ 52 - 36
src/mol-canvas3d/canvas3d.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { BehaviorSubject } from 'rxjs';
+import { BehaviorSubject, Subscription } from 'rxjs';
 import { now } from 'mol-util/now';
 
 import { Vec3 } from 'mol-math/linear-algebra'
@@ -17,7 +17,7 @@ import TrackballControls from './controls/trackball'
 import { Viewport } from './camera/util'
 import { resizeCanvas } from './util';
 import { createContext, getGLContext, WebGLContext } from 'mol-gl/webgl/context';
-import { Representation } from 'mol-repr';
+import { Representation } from 'mol-repr/representation';
 import { createRenderTarget } from 'mol-gl/webgl/render-target';
 import Scene from 'mol-gl/scene';
 import { RenderVariant } from 'mol-gl/webgl/render-item';
@@ -41,11 +41,11 @@ export { Canvas3D }
 interface Canvas3D {
     readonly webgl: WebGLContext,
 
-    hide: (repr: Representation<any>) => void
-    show: (repr: Representation<any>) => void
+    hide: (repr: Representation.Any) => void
+    show: (repr: Representation.Any) => void
 
-    add: (repr: Representation<any>) => void
-    remove: (repr: Representation<any>) => void
+    add: (repr: Representation.Any) => void
+    remove: (repr: Representation.Any) => void
     update: () => void
     clear: () => void
 
@@ -54,8 +54,8 @@ interface Canvas3D {
     animate: () => void
     pick: () => void
     identify: (x: number, y: number) => Promise<PickingId | undefined>
-    mark: (loci: Loci, action: MarkerAction) => void
-    getLoci: (pickingId: PickingId) => { loci: Loci, repr?: Representation<any> }
+    mark: (loci: Loci, action: MarkerAction, repr?: Representation.Any) => void
+    getLoci: (pickingId: PickingId) => { loci: Loci, repr?: Representation.Any }
 
     readonly didDraw: BehaviorSubject<now.Timestamp>
 
@@ -77,7 +77,9 @@ namespace Canvas3D {
     export function create(canvas: HTMLCanvasElement, container: Element, props: Partial<Canvas3DProps> = {}): Canvas3D {
         const p = { ...props, ...DefaultCanvas3DProps }
 
-        const reprMap = new Map<Representation<any>, Set<RenderObject>>()
+        const reprRenderObjects = new Map<Representation.Any, Set<RenderObject>>()
+        const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>()
+        const reprCount = new BehaviorSubject(0)
 
         const startTime = now()
         const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp)
@@ -120,7 +122,7 @@ namespace Canvas3D {
         function getLoci(pickingId: PickingId) {
             let loci: Loci = EmptyLoci
             let repr: Representation.Any = Representation.Empty
-            reprMap.forEach((_, _repr) => {
+            reprRenderObjects.forEach((_, _repr) => {
                 const _loci = _repr.getLoci(pickingId)
                 if (!isEmptyLoci(_loci)) {
                     if (!isEmptyLoci(loci)) console.warn('found another loci')
@@ -131,10 +133,12 @@ namespace Canvas3D {
             return { loci, repr }
         }
 
-        function mark(loci: Loci, action: MarkerAction) {
+        function mark(loci: Loci, action: MarkerAction, repr?: Representation.Any) {
             let changed = false
-            reprMap.forEach((roSet, repr) => {
-                changed = repr.mark(loci, action) || changed
+            reprRenderObjects.forEach((_, _repr) => {
+                if (!repr || repr === _repr) {
+                    changed = _repr.mark(loci, action) || changed
+                }
             })
             if (changed) {
                 // console.log('changed')
@@ -270,45 +274,57 @@ namespace Canvas3D {
             }
         }
 
+        function add(repr: Representation.Any) {
+            const oldRO = reprRenderObjects.get(repr)
+            const newRO = new Set<RenderObject>()
+            repr.renderObjects.forEach(o => newRO.add(o))
+            if (oldRO) {
+                SetUtils.difference(newRO, oldRO).forEach(o => scene.add(o))
+                SetUtils.difference(oldRO, newRO).forEach(o => scene.remove(o))
+                scene.update()
+            } else {
+                repr.renderObjects.forEach(o => scene.add(o))
+            }
+            reprRenderObjects.set(repr, newRO)
+            reprCount.next(reprRenderObjects.size)
+            scene.update()
+            requestDraw(true)
+        }
+
         handleResize()
 
         return {
             webgl,
 
-            hide: (repr: Representation<any>) => {
-                const renderObjectSet = reprMap.get(repr)
+            hide: (repr: Representation.Any) => {
+                const renderObjectSet = reprRenderObjects.get(repr)
                 if (renderObjectSet) renderObjectSet.forEach(o => o.state.visible = false)
             },
-            show: (repr: Representation<any>) => {
-                const renderObjectSet = reprMap.get(repr)
+            show: (repr: Representation.Any) => {
+                const renderObjectSet = reprRenderObjects.get(repr)
                 if (renderObjectSet) renderObjectSet.forEach(o => o.state.visible = true)
             },
 
-            add: (repr: Representation<any>) => {
-                const oldRO = reprMap.get(repr)
-                const newRO = new Set<RenderObject>()
-                repr.renderObjects.forEach(o => newRO.add(o))
-                if (oldRO) {
-                    SetUtils.difference(newRO, oldRO).forEach(o => scene.add(o))
-                    SetUtils.difference(oldRO, newRO).forEach(o => scene.remove(o))
-                    scene.update()
-                } else {
-                    repr.renderObjects.forEach(o => scene.add(o))
-                }
-                reprMap.set(repr, newRO)
-                scene.update()
+            add: (repr: Representation.Any) => {
+                add(repr)
+                reprUpdatedSubscriptions.set(repr, repr.updated.subscribe(_ => add(repr)))
             },
-            remove: (repr: Representation<any>) => {
-                const renderObjectSet = reprMap.get(repr)
-                if (renderObjectSet) {
-                    renderObjectSet.forEach(o => scene.remove(o))
-                    reprMap.delete(repr)
+            remove: (repr: Representation.Any) => {
+                const updatedSubscription = reprUpdatedSubscriptions.get(repr)
+                if (updatedSubscription) {
+                    updatedSubscription.unsubscribe()
+                }
+                const renderObjects = reprRenderObjects.get(repr)
+                if (renderObjects) {
+                    renderObjects.forEach(o => scene.remove(o))
+                    reprRenderObjects.delete(repr)
+                    reprCount.next(reprRenderObjects.size)
                     scene.update()
                 }
             },
             update: () => scene.update(),
             clear: () => {
-                reprMap.clear()
+                reprRenderObjects.clear()
                 scene.clear()
             },
 

+ 2 - 34
src/mol-geo/geometry/color-data.ts

@@ -6,14 +6,13 @@
 
 import { ValueCell } from 'mol-util';
 import { TextureImage, createTextureImage } from 'mol-gl/renderable/util';
-import { Color, ColorMap } from 'mol-util/color';
+import { Color } from 'mol-util/color';
 import { Vec2, Vec3 } from 'mol-math/linear-algebra';
 import { LocationIterator } from '../util/location-iterator';
 import { NullLocation } from 'mol-model/location';
-import { LocationColor, ColorThemeProps, ColorTheme, ColorThemeName, ScaleLegend, TableLegend, ColorScaleName, getColorScaleFromName } from 'mol-theme/color';
+import { LocationColor, ColorTheme } from 'mol-theme/color';
 import { RuntimeContext } from 'mol-task';
 import { getGranularity } from './geometry';
-import { Structure } from 'mol-model/structure';
 
 export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance'
 
@@ -25,37 +24,6 @@ export type ColorData = {
     dColorType: ValueCell<string>,
 }
 
-export interface ColorProps {
-    colorTheme: ColorThemeName
-    colorList?: Color[] | ColorScaleName
-    colorMap?: ColorMap<any>
-    colorDomain?: [number, number]
-    colorValue?: Color
-    colorFunction?: LocationColor,
-    colorGranularity?: ColorType,
-    colorDescription?: string,
-    colorLegend?: ScaleLegend | TableLegend
-    structure?: Structure
-}
-
-export function getColorThemeProps(props: ColorProps): ColorThemeProps {
-    const p: ColorThemeProps = {
-        name: props.colorTheme
-    }
-    if (props.colorDomain !== undefined) p.domain = props.colorDomain
-    if (props.colorList !== undefined) {
-        p.list = typeof props.colorList === 'string' ? getColorScaleFromName(props.colorList) : props.colorList
-    }
-    if (props.colorMap !== undefined) p.map = props.colorMap
-    if (props.colorValue !== undefined) p.value = props.colorValue
-    if (props.structure !== undefined) p.structure = props.structure
-    if (props.colorFunction !== undefined) p.color = props.colorFunction
-    if (props.colorGranularity !== undefined) p.granularity = props.colorGranularity
-    if (props.colorDescription !== undefined) p.description = props.colorDescription
-    if (props.colorLegend !== undefined) p.legend = props.colorLegend
-    return p
-}
-
 export function createColors(ctx: RuntimeContext, locationIt: LocationIterator, colorTheme: ColorTheme, colorData?: ColorData): Promise<ColorData> {
     switch (getGranularity(locationIt, colorTheme.granularity)) {
         case 'uniform': return createUniformColor(ctx, locationIt, colorTheme.color, colorData)

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

@@ -17,9 +17,10 @@ import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { TransformData } from '../transform-data';
 import { createColors } from '../color-data';
 import { createMarkers } from '../marker-data';
-import { Geometry, Theme } from '../geometry';
+import { Geometry } from '../geometry';
 import { transformPositionArray } from 'mol-geo/util';
 import { calculateBoundingSphere } from 'mol-gl/renderable/util';
+import { Theme } from 'mol-theme/theme';
 
 const VolumeBox = Box()
 const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][]

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

@@ -29,7 +29,7 @@ export function createTransferFunctionTexture(controlPoints: ControlPoint[], tex
     ]
     const scale = ColorScale.create({
         domain: [0, 1],
-        list: ColorMatplotlib.viridis
+        listOrName: ColorMatplotlib.viridis
     })
 
     const n = 256

+ 19 - 24
src/mol-geo/geometry/geometry.ts

@@ -9,15 +9,16 @@ import { Points } from './points/points';
 import { RenderableState } from 'mol-gl/renderable';
 import { ValueCell } from 'mol-util';
 import { BaseValues } from 'mol-gl/renderable/schema';
-import { Color } from 'mol-util/color';
-import { ColorThemeOptions, ColorThemeName, ColorScaleOptions, ColorScaleName, ColorTheme } from 'mol-theme/color';
 import { LocationIterator } from '../util/location-iterator';
-import { ColorType, getColorThemeProps } from './color-data';
-import { SizeType, getSizeThemeProps } from './size-data';
+import { ColorType } from './color-data';
+import { SizeType } from './size-data';
 import { Lines } from './lines/lines';
 import { ParamDefinition as PD } from 'mol-util/param-definition'
 import { DirectVolume } from './direct-volume/direct-volume';
-import { SizeTheme, SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
+import { BuiltInSizeThemeOptions, BuiltInSizeThemeName } from 'mol-theme/size';
+import { BuiltInColorThemeName, BuiltInColorThemeOptions } from 'mol-theme/color';
+import { Color } from 'mol-util/color';
+import { Vec3 } from 'mol-math/linear-algebra';
 
 //
 
@@ -36,18 +37,6 @@ export type VisualQuality = keyof typeof VisualQualityInfo
 export const VisualQualityNames = Object.keys(VisualQualityInfo)
 export const VisualQualityOptions = VisualQualityNames.map(n => [n, n] as [VisualQuality, string])
 
-export interface Theme {
-    color: ColorTheme
-    size: SizeTheme
-}
-
-export function createTheme(props: Geometry.Props) {
-    return {
-        color: ColorTheme(getColorThemeProps(props)),
-        size: SizeTheme(getSizeThemeProps(props))
-    }
-}
-
 //
 
 export type GeometryKindType = {
@@ -76,15 +65,13 @@ export namespace Geometry {
         visible: PD.Boolean('Visible', '', true),
         depthMask: PD.Boolean('Depth Mask', '', true),
         useFog: PD.Boolean('Use Fog', '', false),
-        quality: PD.Select<VisualQuality>('Quality', '', 'auto', VisualQualityOptions),
+        highlightColor: PD.Color('Highlight Color', '', Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
+        selectColor: PD.Color('Select Color', '', Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
 
-        colorTheme: PD.Select<ColorThemeName>('Color Name', '', 'uniform', ColorThemeOptions),
-        colorList: PD.Select<ColorScaleName>('Color Scale', '', 'default', ColorScaleOptions),
-        colorValue: PD.Color('Color Value', '', Color(0xCCCCCC)),
+        quality: PD.Select<VisualQuality>('Quality', '', 'auto', VisualQualityOptions),
 
-        sizeTheme: PD.Select<SizeThemeName>('Size Name', '', 'uniform', SizeThemeOptions),
-        sizeValue: PD.Numeric('Size Value', '', 1, 0, 20, 0.1),
-        sizeFactor: PD.Numeric('Size Factor', '', 1, 0, 10, 0.1),
+        colorTheme: PD.Select<BuiltInColorThemeName>('Color Name', '', 'uniform', BuiltInColorThemeOptions),
+        sizeTheme: PD.Select<BuiltInSizeThemeName>('Size Name', '', 'uniform', BuiltInSizeThemeOptions),
     }
     export const DefaultProps = PD.getDefaultValues(Params)
     export type Props = typeof DefaultProps
@@ -94,6 +81,8 @@ export namespace Geometry {
     export function createValues(props: Props, counts: Counts) {
         return {
             uAlpha: ValueCell.create(props.alpha),
+            uHighlightColor: ValueCell.create(Color.toArrayNormalized(props.highlightColor, Vec3.zero(), 0)),
+            uSelectColor: ValueCell.create(Color.toArrayNormalized(props.selectColor, Vec3.zero(), 0)),
             uGroupCount: ValueCell.create(counts.groupCount),
             drawCount: ValueCell.create(counts.drawCount),
             dUseFog: ValueCell.create(props.useFog),
@@ -101,6 +90,12 @@ export namespace Geometry {
     }
 
     export function updateValues(values: BaseValues, props: Props) {
+        if (Color.fromNormalizedArray(values.uHighlightColor.ref.value, 0) !== props.highlightColor) {
+            ValueCell.update(values.uHighlightColor, Color.toArrayNormalized(props.highlightColor, values.uHighlightColor.ref.value, 0))
+        }
+        if (Color.fromNormalizedArray(values.uSelectColor.ref.value, 0) !== props.selectColor) {
+            ValueCell.update(values.uSelectColor, Color.toArrayNormalized(props.selectColor, values.uSelectColor.ref.value, 0))
+        }
         ValueCell.updateIfChanged(values.uAlpha, props.alpha)
         ValueCell.updateIfChanged(values.dUseFog, props.useFog)
     }

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

@@ -7,7 +7,7 @@
 import { ValueCell } from 'mol-util'
 import { Mat4 } from 'mol-math/linear-algebra'
 import { transformPositionArray/* , transformDirectionArray, getNormalMatrix */ } from '../../util';
-import { Geometry, Theme } from '../geometry';
+import { Geometry } from '../geometry';
 import { RuntimeContext } from 'mol-task';
 import { createColors } from '../color-data';
 import { createMarkers } from '../marker-data';
@@ -20,6 +20,7 @@ import { LinesBuilder } from './lines-builder';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { calculateBoundingSphere } from 'mol-gl/renderable/util';
 import { Sphere3D } from 'mol-math/geometry';
+import { Theme } from 'mol-theme/theme';
 
 /** Wide line */
 export interface Lines {

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

@@ -9,8 +9,7 @@ import { ValueCell } from 'mol-util'
 import { Vec3, Mat4 } from 'mol-math/linear-algebra'
 import { Sphere3D } from 'mol-math/geometry'
 import { transformPositionArray/* , transformDirectionArray, getNormalMatrix */ } from '../../util';
-import { MeshValues } from 'mol-gl/renderable';
-import { Geometry, Theme } from '../geometry';
+import { Geometry } from '../geometry';
 import { createMarkers } from '../marker-data';
 import { TransformData } from '../transform-data';
 import { LocationIterator } from '../../util/location-iterator';
@@ -18,6 +17,8 @@ import { createColors } from '../color-data';
 import { ChunkedArray } from 'mol-data/util';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { calculateBoundingSphere } from 'mol-gl/renderable/util';
+import { Theme } from 'mol-theme/theme';
+import { MeshValues } from 'mol-gl/renderable/mesh';
 
 export interface Mesh {
     readonly kind: 'mesh',

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

@@ -7,8 +7,7 @@
 import { ValueCell } from 'mol-util'
 import { Mat4 } from 'mol-math/linear-algebra'
 import { transformPositionArray/* , transformDirectionArray, getNormalMatrix */ } from '../../util';
-import { Geometry, Theme } from '../geometry';
-import { PointsValues } from 'mol-gl/renderable';
+import { Geometry } from '../geometry';
 import { RuntimeContext } from 'mol-task';
 import { createColors } from '../color-data';
 import { createMarkers } from '../marker-data';
@@ -18,6 +17,8 @@ import { LocationIterator } from '../../util/location-iterator';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { calculateBoundingSphere } from 'mol-gl/renderable/util';
 import { Sphere3D } from 'mol-math/geometry';
+import { Theme } from 'mol-theme/theme';
+import { PointsValues } from 'mol-gl/renderable/points';
 
 /** Point cloud */
 export interface Points {

+ 1 - 18
src/mol-geo/geometry/size-data.ts

@@ -10,9 +10,8 @@ import { TextureImage, createTextureImage } from 'mol-gl/renderable/util';
 import { LocationIterator } from '../util/location-iterator';
 import { Location, NullLocation } from 'mol-model/location';
 import { RuntimeContext } from 'mol-task';
-import { SizeThemeProps, SizeTheme, SizeThemeName } from 'mol-theme/size';
+import { SizeTheme } from 'mol-theme/size';
 import { getGranularity } from './geometry';
-import { Structure } from 'mol-model/structure';
 
 export type SizeType = 'uniform' | 'instance' | 'group' | 'groupInstance'
 
@@ -24,22 +23,6 @@ export type SizeData = {
     dSizeType: ValueCell<string>,
 }
 
-export interface SizeProps {
-    sizeTheme: SizeThemeName
-    sizeValue?: number
-    sizeFactor?: number
-    structure?: Structure
-}
-
-export function getSizeThemeProps(props: SizeProps): SizeThemeProps {
-    return {
-        name: props.sizeTheme,
-        value: props.sizeValue,
-        factor: props.sizeFactor,
-        structure: props.structure,
-    }
-}
-
 export async function createSizes(ctx: RuntimeContext, locationIt: LocationIterator, sizeTheme: SizeTheme, sizeData?: SizeData): Promise<SizeData> {
     switch (getGranularity(locationIt, sizeTheme.granularity)) {
         case 'uniform': return createUniformSize(ctx, locationIt, sizeTheme.size, sizeData)

+ 2 - 0
src/mol-gl/_spec/renderer.spec.ts

@@ -69,6 +69,8 @@ function createPoints() {
         ...size,
 
         uAlpha: ValueCell.create(1.0),
+        uHighlightColor: ValueCell.create(Vec3.create(1.0, 0.4, 0.6)),
+        uSelectColor: ValueCell.create(Vec3.create(0.2, 1.0, 0.1)),
         uInstanceCount: ValueCell.create(1),
         uGroupCount: ValueCell.create(3),
 

+ 4 - 1
src/mol-gl/render-object.ts

@@ -4,12 +4,15 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { PointsRenderable, MeshRenderable, RenderableState, MeshValues, PointsValues, LinesValues, LinesRenderable, Renderable } from './renderable'
+import { RenderableState, Renderable } from './renderable'
 import { RenderableValues } from './renderable/schema';
 import { idFactory } from 'mol-util/id-factory';
 import { WebGLContext } from './webgl/context';
 import { GaussianDensityValues, GaussianDensityRenderable } from './renderable/gaussian-density';
 import { DirectVolumeValues, DirectVolumeRenderable } from './renderable/direct-volume';
+import { MeshValues, MeshRenderable } from './renderable/mesh';
+import { PointsValues, PointsRenderable } from './renderable/points';
+import { LinesValues, LinesRenderable } from './renderable/lines';
 
 const getNextId = idFactory(0, 0x7FFFFFFF)
 

+ 1 - 5
src/mol-gl/renderable.ts

@@ -46,8 +46,4 @@ export function createRenderable<T extends Values<RenderableSchema>>(renderItem:
         update: () => renderItem.update(),
         dispose: () => renderItem.destroy()
     }
-}
-
-export { MeshRenderable, MeshSchema, MeshValues } from './renderable/mesh'
-export { PointsRenderable, PointsSchema, PointsValues } from './renderable/points'
-export { LinesRenderable, LinesSchema, LinesValues } from './renderable/lines'
+}

+ 2 - 3
src/mol-gl/renderable/schema.ts

@@ -146,9 +146,6 @@ export const GlobalUniformSchema = {
     uViewportHeight: UniformSpec('f'),
     uViewport: UniformSpec('v4'),
 
-    uHighlightColor: UniformSpec('v3'),
-    uSelectColor: UniformSpec('v3'),
-
     uFogNear: UniformSpec('f'),
     uFogFar: UniformSpec('f'),
     uFogColor: UniformSpec('v3'),
@@ -193,6 +190,8 @@ export const BaseSchema = {
     uInstanceCount: UniformSpec('i'),
     uGroupCount: UniformSpec('i'),
     uMarkerTexDim: UniformSpec('v2'),
+    uHighlightColor: UniformSpec('v3'),
+    uSelectColor: UniformSpec('v3'),
 
     tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
 

+ 0 - 5
src/mol-gl/renderer.ts

@@ -60,8 +60,6 @@ namespace Renderer {
         // const lightPosition = Vec3.create(0, 0, -100)
         const lightColor = Vec3.create(1.0, 1.0, 1.0)
         const lightAmbient = Vec3.create(0.5, 0.5, 0.5)
-        const highlightColor = Vec3.create(1.0, 0.4, 0.6)
-        const selectColor = Vec3.create(0.2, 1.0, 0.1)
         const fogColor = Vec3.create(0.0, 0.0, 0.0)
 
         function setClearColor(color: Color) {
@@ -97,9 +95,6 @@ namespace Renderer {
             uLightColor: ValueCell.create(Vec3.clone(lightColor)),
             uLightAmbient: ValueCell.create(Vec3.clone(lightAmbient)),
 
-            uHighlightColor: ValueCell.create(Vec3.clone(highlightColor)),
-            uSelectColor: ValueCell.create(Vec3.clone(selectColor)),
-
             uFogNear: ValueCell.create(camera.state.near),
             uFogFar: ValueCell.create(camera.state.far / 50),
             uFogColor: ValueCell.create(Vec3.clone(fogColor)),

+ 1 - 1
src/mol-gl/webgl/context.ts

@@ -199,7 +199,7 @@ export function createContext(gl: GLRenderingContext): WebGLContext {
         const pbo = gl.createBuffer()
         readPixelsAsync = async (x: number, y: number, width: number, height: number, buffer: Uint8Array) => {
             gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo)
-            gl.bufferData(gl.PIXEL_PACK_BUFFER, width * height * 4, gl.STATIC_COPY)
+            gl.bufferData(gl.PIXEL_PACK_BUFFER, width * height * 4, gl.STREAM_READ)
             gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, 0)
             gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null)
             // need to unbind/bind PBO before/after async awaiting the fence

+ 2 - 2
src/mol-model/structure/structure/unit/gaussian-density.ts

@@ -5,7 +5,6 @@
  */
 
 import { Unit, StructureElement, ElementIndex } from 'mol-model/structure';
-import { SizeTheme } from 'mol-theme/size';
 import { GaussianDensity } from 'mol-math/geometry/gaussian-density';
 import { Task, RuntimeContext } from 'mol-task';
 import { DensityData } from 'mol-math/geometry';
@@ -13,6 +12,7 @@ import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { GaussianDensityTexture } from 'mol-math/geometry/gaussian-density/gpu';
 import { Texture } from 'mol-gl/webgl/texture';
 import { WebGLContext } from 'mol-gl/webgl/context';
+import { PhysicalSizeTheme } from 'mol-theme/size/physical';
 
 export const GaussianDensityParams = {
     resolution: PD.Numeric('Resolution', '', 1, 0.1, 10, 0.1),
@@ -43,7 +43,7 @@ function getConformationAndRadius(unit: Unit) {
     }
 
     const l = StructureElement.create(unit)
-    const sizeTheme = SizeTheme({ name: 'physical' })
+    const sizeTheme = PhysicalSizeTheme({}, {})
     const radius = (index: number) => {
         l.element = index as ElementIndex
         return sizeTheme.size(l)

+ 1 - 1
src/mol-plugin/context.ts

@@ -15,7 +15,7 @@ import { Task } from 'mol-task';
 import { merge } from 'rxjs';
 import { PluginBehaviors, BuiltInPluginBehaviors } from './behavior';
 import { Loci, EmptyLoci } from 'mol-model/loci';
-import { Representation } from 'mol-repr';
+import { Representation } from 'mol-repr/representation';
 import { CreateStructureFromPDBe } from './state/actions/basic';
 import { LogEntry } from 'mol-util/log-entry';
 import { TaskManager } from './util/task-manager';

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

@@ -8,9 +8,9 @@ import { CifFile } from 'mol-io/reader/cif';
 import { Model as _Model, Structure as _Structure } from 'mol-model/structure';
 import { VolumeData } from 'mol-model/volume';
 import { PluginBehavior } from 'mol-plugin/behavior/behavior';
-import { Representation } from 'mol-repr';
-import { StructureRepresentation } from 'mol-repr/structure/index';
-import { VolumeRepresentation } from 'mol-repr/volume';
+import { Representation } from 'mol-repr/representation';
+import { StructureRepresentation } from 'mol-repr/structure/representation';
+import { VolumeRepresentation } from 'mol-repr/volume/representation';
 import { StateObject, Transformer } from 'mol-state';
 
 export type TypeClass = 'root' | 'data' | 'prop'

+ 10 - 4
src/mol-plugin/state/transforms/visuals.ts

@@ -8,8 +8,14 @@ import { Transformer } from 'mol-state';
 import { Task } from 'mol-task';
 import { PluginStateTransform } from '../objects';
 import { PluginStateObject as SO } from '../objects';
-import { BallAndStickRepresentation, DefaultBallAndStickProps } from 'mol-repr/structure/representation/ball-and-stick';
 import { PluginContext } from 'mol-plugin/context';
+import { ColorTheme } from 'mol-theme/color';
+import { SizeTheme } from 'mol-theme/size';
+import { RepresentationRegistry } from 'mol-repr/representation';
+
+const colorThemeRegistry = new ColorTheme.Registry()
+const sizeThemeRegistry = new SizeTheme.Registry()
+const representationRegistry = new RepresentationRegistry()
 
 export { CreateStructureRepresentation }
 namespace CreateStructureRepresentation { export interface Params { } }
@@ -20,14 +26,14 @@ const CreateStructureRepresentation = PluginStateTransform.Create<SO.Molecule.St
     to: [SO.Molecule.Representation3D],
     apply({ a, params }, plugin: PluginContext) {
         return Task.create('Structure Representation', async ctx => {
-            const repr = BallAndStickRepresentation(); // CartoonRepresentation();
-            await repr.createOrUpdate({ webgl: plugin.canvas3d.webgl }, DefaultBallAndStickProps, a.data).runInContext(ctx);
+            const repr = representationRegistry.create('cartoon', { colorThemeRegistry, sizeThemeRegistry }, a.data)
+            await repr.createOrUpdate({ webgl: plugin.canvas3d.webgl, colorThemeRegistry, sizeThemeRegistry }, {}, {}, a.data).runInContext(ctx);
             return new SO.Molecule.Representation3D(repr);
         });
     },
     update({ a, b }, plugin: PluginContext) {
         return Task.create('Structure Representation', async ctx => {
-            await b.data.createOrUpdate({ webgl: plugin.canvas3d.webgl }, b.data.props, a.data).runInContext(ctx);
+            await b.data.createOrUpdate({ webgl: plugin.canvas3d.webgl, colorThemeRegistry, sizeThemeRegistry }, b.data.props, {}, a.data).runInContext(ctx);
             return Transformer.UpdateResult.Updated;
         });
     }

+ 1 - 1
src/mol-plugin/util/canvas3d-identify.ts

@@ -7,7 +7,7 @@
 import { PluginContext } from '../context';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { EmptyLoci, Loci, areLociEqual } from 'mol-model/loci';
-import { Representation } from 'mol-repr';
+import { Representation } from 'mol-repr/representation';
 
 export class Canvas3dIdentifyHelper {
     private cX = -1;

+ 0 - 129
src/mol-repr/index.ts

@@ -1,129 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { Task, RuntimeContext } from 'mol-task'
-import { RenderObject } from 'mol-gl/render-object'
-import { PickingId } from '../mol-geo/geometry/picking';
-import { Loci, isEmptyLoci, EmptyLoci } from 'mol-model/loci';
-import { MarkerAction } from '../mol-geo/geometry/marker-data';
-import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { WebGLContext } from 'mol-gl/webgl/context';
-import { getQualityProps } from './util';
-import { Theme } from 'mol-geo/geometry/geometry';
-// import { ColorTheme } from 'mol-theme/color';
-
-// export interface RepresentationProps {
-//     visuals?: string[]
-// }
-export type RepresentationProps = { [k: string]: any }
-
-export interface RepresentationContext {
-    webgl?: WebGLContext
-}
-
-export interface Representation<D, P extends RepresentationProps = {}> {
-    readonly label: string
-    readonly params: PD.Params
-    readonly renderObjects: ReadonlyArray<RenderObject>
-    readonly props: Readonly<P>
-    createOrUpdate: (ctx: RepresentationContext, props?: Partial<P>, data?: D) => Task<void>
-    getLoci: (pickingId: PickingId) => Loci
-    mark: (loci: Loci, action: MarkerAction) => boolean
-    destroy: () => void
-}
-
-export namespace Representation {
-    export type Any = Representation<any>
-    export const Empty: Representation<undefined> = {
-        label: '', params: {}, renderObjects: [], props: {},
-        createOrUpdate: () => Task.constant('', undefined),
-        getLoci: () => EmptyLoci,
-        mark: () => false,
-        destroy: () => {}
-    }
-
-    export function createMulti<D, P extends RepresentationProps = {}>(label: string, params: PD.Params, defaultProps: P, reprList: Representation<D, P>[]): Representation<D, P> {
-        let currentProps: P
-        let currentData: D
-
-        const visualsOptions: [string, string][] = []
-        for (let i = 0, il = reprList.length; i < il; ++i) {
-            visualsOptions.push([ i.toString(), reprList[i].label ])
-        }
-        params['visuals'] = PD.MultiSelect<string>('Visuals', '', ['surface'], visualsOptions)
-
-        if (!defaultProps.visuals) {
-            defaultProps.visuals = reprList.map((r, i) => i.toString())
-        }
-
-        return {
-            label,
-            params,
-            get renderObjects() {
-                const { visuals } = currentProps
-                const renderObjects: RenderObject[] = []
-                for (let i = 0, il = reprList.length; i < il; ++i) {
-                    if (!visuals || visuals.includes(i.toString())) {
-                        renderObjects.push(...reprList[i].renderObjects)
-                    }
-                }
-                return renderObjects
-            },
-            get props() {
-                const props = {}
-                reprList.forEach(r => Object.assign(props, r.props))
-                return props as P
-            },
-            createOrUpdate: (ctx: RepresentationContext, props: Partial<P> = {}, data?: D) => {
-                if (data) currentData = data
-                const qualityProps = getQualityProps(Object.assign({}, currentProps, props), data)
-                currentProps = Object.assign({}, defaultProps, currentProps, props, qualityProps)
-
-                const { visuals } = currentProps
-                return Task.create(`Creating '${label}' representation`, async runtime => {
-                    for (let i = 0, il = reprList.length; i < il; ++i) {
-                        if (!visuals || visuals.includes(i.toString())) {
-                            await reprList[i].createOrUpdate(ctx, currentProps, currentData).runInContext(runtime)
-                        }
-                    }
-                })
-            },
-            getLoci: (pickingId: PickingId) => {
-                for (let i = 0, il = reprList.length; i < il; ++i) {
-                    const loci = reprList[i].getLoci(pickingId)
-                    if (!isEmptyLoci(loci)) return loci
-                }
-                return EmptyLoci
-            },
-            mark: (loci: Loci, action: MarkerAction) => {
-                let marked = false
-                for (let i = 0, il = reprList.length; i < il; ++i) {
-                    marked = reprList[i].mark(loci, action) || marked
-                }
-                return marked
-            },
-            destroy() {
-                for (let i = 0, il = reprList.length; i < il; ++i) {
-                    reprList[i].destroy()
-                }
-            }
-        }
-    }
-}
-
-//
-
-export interface VisualContext extends RepresentationContext {
-    runtime: RuntimeContext,
-}
-
-export interface Visual<D, P extends RepresentationProps> {
-    readonly renderObject: RenderObject | undefined
-    createOrUpdate: (ctx: VisualContext, theme: Theme, props?: Partial<P>, data?: D) => Promise<void>
-    getLoci: (pickingId: PickingId) => Loci
-    mark: (loci: Loci, action: MarkerAction) => boolean
-    destroy: () => void
-}

+ 181 - 0
src/mol-repr/representation.ts

@@ -0,0 +1,181 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Task, RuntimeContext } from 'mol-task'
+import { RenderObject } from 'mol-gl/render-object'
+import { PickingId } from '../mol-geo/geometry/picking';
+import { Loci, isEmptyLoci, EmptyLoci } from 'mol-model/loci';
+import { MarkerAction } from '../mol-geo/geometry/marker-data';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { WebGLContext } from 'mol-gl/webgl/context';
+import { getQualityProps } from './util';
+import { ColorTheme } from 'mol-theme/color';
+import { SizeTheme } from 'mol-theme/size';
+import { ThemeProps, Theme, ThemeRegistryContext } from 'mol-theme/theme';
+import { BehaviorSubject } from 'rxjs';
+
+// export interface RepresentationProps {
+//     visuals?: string[]
+// }
+export type RepresentationProps = { [k: string]: any }
+
+export type RepresentationParamsGetter<D, P extends PD.Params> = (ctx: ThemeRegistryContext, data: D) => P
+
+//
+
+export interface RepresentationProvider<D, P extends PD.Params> {
+    readonly factory: (getParams: RepresentationParamsGetter<D, P>) => Representation<D, P>
+    readonly getParams: (ctx: ThemeRegistryContext, data: D) => P
+}
+
+export class RepresentationRegistry<D> {
+    private _list: { name: string, provider: RepresentationProvider<D, any> }[] = []
+    private _map = new Map<string, RepresentationProvider<D, any>>()
+
+    constructor() {};
+
+    add<P extends PD.Params>(name: string, factory: RepresentationProvider<D, P>['factory'], getParams: RepresentationProvider<D, P>['getParams']) {
+        const provider = { factory, getParams } as RepresentationProvider<D, P>
+        this._list.push({ name, provider })
+        this._map.set(name, provider)
+    }
+
+    get(id: string) {
+        return this._map.get(id)
+    }
+
+    create(id: string, ctx: ThemeRegistryContext, data: D, props = {}): Representation<D, any> {
+        const provider = this.get(id)
+        return provider ? provider.factory(provider.getParams) : Representation.Empty
+    }
+
+    get list() {
+        return this._list
+    }
+}
+
+//
+
+export interface RepresentationContext {
+    webgl?: WebGLContext
+    colorThemeRegistry: ColorTheme.Registry
+    sizeThemeRegistry: SizeTheme.Registry
+}
+
+export { Representation }
+interface Representation<D, P extends PD.Params = {}> {
+    readonly label: string
+    readonly updated: BehaviorSubject<number>
+    readonly renderObjects: ReadonlyArray<RenderObject>
+    readonly props: Readonly<PD.DefaultValues<P>>
+    readonly params: Readonly<P>
+    createOrUpdate: (ctx: RepresentationContext, props?: Partial<PD.DefaultValues<P>>, themeProps?: ThemeProps, data?: D) => Task<void>
+    getLoci: (pickingId: PickingId) => Loci
+    mark: (loci: Loci, action: MarkerAction) => boolean
+    destroy: () => void
+}
+namespace Representation {
+    export type Any = Representation<any>
+    export const Empty: Representation<any> = {
+        label: '', renderObjects: [], props: {}, params: {}, updated: new BehaviorSubject(0),
+        createOrUpdate: () => Task.constant('', undefined),
+        getLoci: () => EmptyLoci,
+        mark: () => false,
+        destroy: () => {}
+    }
+
+    export type Def<D, P extends PD.Params = {}> = { [k: string]: (getParams: RepresentationParamsGetter<D, P>) => Representation<any, P> }
+
+    export function createMulti<D, P extends PD.Params = {}>(label: string, getParams: RepresentationParamsGetter<D, P>, reprDefs: Def<D, P>): Representation<D, P> {
+        const updated = new BehaviorSubject(0)
+
+        let currentParams: P
+        let currentProps: PD.DefaultValues<P>
+        let currentData: D
+
+        const reprMap: { [k: number]: string } = {}
+        const reprList: Representation<D, P>[] = Object.keys(reprDefs).map((name, i) => {
+            reprMap[i] = name
+            return reprDefs[name](getParams)
+        })
+
+        return {
+            label,
+            updated,
+            get renderObjects() {
+                const renderObjects: RenderObject[] = []
+                if (currentProps) {
+                    const { visuals } = currentProps
+                    for (let i = 0, il = reprList.length; i < il; ++i) {
+                        if (!visuals || visuals.includes(reprMap[i])) {
+                            renderObjects.push(...reprList[i].renderObjects)
+                        }
+                    }
+                }
+                return renderObjects
+            },
+            get props() {
+                const props = {}
+                reprList.forEach(r => Object.assign(props, r.props))
+                return props as P
+            },
+            get params() { return currentParams },
+            createOrUpdate: (ctx: RepresentationContext, props: Partial<P> = {}, themeProps: ThemeProps = {}, data?: D) => {
+                if (data && data !== currentData) {
+                    currentParams = getParams(ctx, data)
+                    currentData = data
+                    if (!currentProps) currentProps = PD.getDefaultValues(currentParams) as P
+                }
+                const qualityProps = getQualityProps(Object.assign({}, currentProps, props), currentData)
+                Object.assign(currentProps, props, qualityProps)
+
+                const { visuals } = currentProps
+                return Task.create(`Creating '${label}' representation`, async runtime => {
+                    for (let i = 0, il = reprList.length; i < il; ++i) {
+                        if (!visuals || visuals.includes(reprMap[i])) {
+                            await reprList[i].createOrUpdate(ctx, currentProps, themeProps, currentData).runInContext(runtime)
+                        }
+                    }
+                    updated.next(updated.getValue() + 1)
+                })
+            },
+            getLoci: (pickingId: PickingId) => {
+                for (let i = 0, il = reprList.length; i < il; ++i) {
+                    const loci = reprList[i].getLoci(pickingId)
+                    if (!isEmptyLoci(loci)) return loci
+                }
+                return EmptyLoci
+            },
+            mark: (loci: Loci, action: MarkerAction) => {
+                let marked = false
+                for (let i = 0, il = reprList.length; i < il; ++i) {
+                    marked = reprList[i].mark(loci, action) || marked
+                }
+                return marked
+            },
+            destroy() {
+                for (let i = 0, il = reprList.length; i < il; ++i) {
+                    reprList[i].destroy()
+                }
+            }
+        }
+    }
+}
+
+//
+
+export interface VisualContext {
+    webgl?: WebGLContext
+    runtime: RuntimeContext,
+}
+
+export interface Visual<D, P extends PD.Params> {
+    readonly renderObject: RenderObject | undefined
+    createOrUpdate: (ctx: VisualContext, theme: Theme, props?: Partial<PD.DefaultValues<P>>, data?: D) => Promise<void>
+    getLoci: (pickingId: PickingId) => Loci
+    mark: (loci: Loci, action: MarkerAction) => boolean
+    destroy: () => void
+}

+ 18 - 16
src/mol-repr/shape/index.ts → src/mol-repr/shape/representation.ts

@@ -6,40 +6,40 @@
 
 import { Task } from 'mol-task'
 import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object';
-import { RepresentationProps, Representation, RepresentationContext } from '..';
+import { Representation, RepresentationContext } from '../representation';
 import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci';
 import { ValueCell } from 'mol-util';
-import { ColorThemeName, ColorThemeOptions } from 'mol-theme/color';
 import { Shape } from 'mol-model/shape';
 import { OrderedSet, Interval } from 'mol-data/int';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
-import { createRenderableState, createTheme } from 'mol-geo/geometry/geometry';
+import { createRenderableState } from 'mol-geo/geometry/geometry';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
+import { ThemeProps, createTheme } from 'mol-theme/theme';
+import { BehaviorSubject } from 'rxjs';
 
-export interface ShapeRepresentation<P extends RepresentationProps = {}> extends Representation<Shape, P> { }
+export interface ShapeRepresentation<P extends ShapeParams> extends Representation<Shape, P> { }
 
 export const ShapeParams = {
     ...Mesh.Params,
-    colorTheme: PD.Select<ColorThemeName>('Color Theme', '', 'shape-group', ColorThemeOptions)
+    // TODO
+    // colorTheme: PD.Select<ColorThemeName>('Color Theme', '', 'shape-group', ColorThemeOptions)
 }
-export const DefaultShapeProps = PD.getDefaultValues(ShapeParams)
-export type ShapeProps = typeof DefaultShapeProps
+export type ShapeParams = typeof ShapeParams
 
-// TODO
-// export type ShapeRepresentation = ShapeRepresentation<ShapeProps>
-
-export function ShapeRepresentation<P extends ShapeProps>(): ShapeRepresentation<P> {
+export function ShapeRepresentation<P extends ShapeParams>(): ShapeRepresentation<P> {
+    const updated = new BehaviorSubject(0)
     const renderObjects: RenderObject[] = []
     let _renderObject: MeshRenderObject | undefined
     let _shape: Shape
-    let currentProps: P
+    let currentProps: PD.DefaultValues<P> = PD.getDefaultValues(ShapeParams) as PD.DefaultValues<P>
+    let currentParams: P
 
-    function createOrUpdate(ctx: RepresentationContext, props: Partial<P> = {}, shape?: Shape) {
-        currentProps = Object.assign({}, DefaultShapeProps, currentProps, props)
+    function createOrUpdate(ctx: RepresentationContext, props: Partial<PD.DefaultValues<P>> = {}, themeProps: ThemeProps = {}, shape?: Shape) {
+        currentProps = Object.assign({}, currentProps, props)
         if (shape) _shape = shape
 
         return Task.create('ShapeRepresentation.create', async runtime => {
@@ -49,7 +49,7 @@ export function ShapeRepresentation<P extends ShapeProps>(): ShapeRepresentation
 
             const mesh = _shape.mesh
             const locationIt = ShapeGroupIterator.fromShape(_shape)
-            const theme = createTheme(currentProps)
+            const theme = createTheme(ctx, currentProps, themeProps, {})
             const transform = createIdentityTransform()
 
             const values = await Mesh.createValues(runtime, mesh, transform, locationIt, theme, currentProps)
@@ -57,13 +57,15 @@ export function ShapeRepresentation<P extends ShapeProps>(): ShapeRepresentation
 
             _renderObject = createMeshRenderObject(values, state)
             renderObjects.push(_renderObject)
+            updated.next(updated.getValue() + 1)
         });
     }
 
     return {
         label: 'Shape mesh',
-        params: ShapeParams,
+        updated,
         get renderObjects () { return renderObjects },
+        get params () { return currentParams },
         get props () { return currentProps },
         createOrUpdate,
         getLoci(pickingId: PickingId) {

+ 21 - 10
src/mol-repr/structure/complex-representation.ts

@@ -8,27 +8,37 @@
 import { Structure } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { Loci, EmptyLoci } from 'mol-model/loci';
-import { StructureProps, StructureRepresentation, StructureParams } from './index';
+import { StructureRepresentation, StructureParams } from './representation';
 import { ComplexVisual } from './complex-visual';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { MarkerAction } from 'mol-geo/geometry/marker-data';
-import { RepresentationContext } from 'mol-repr';
-import { createTheme, Theme } from 'mol-geo/geometry/geometry';
+import { RepresentationContext, RepresentationParamsGetter } from 'mol-repr/representation';
+import { Theme, ThemeProps, createTheme } from 'mol-theme/theme';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { BehaviorSubject } from 'rxjs';
 
-export function ComplexRepresentation<P extends StructureProps>(label: string, visualCtor: () => ComplexVisual<P>): StructureRepresentation<P> {
+export function ComplexRepresentation<P extends StructureParams>(label: string, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: () => ComplexVisual<P>): StructureRepresentation<P> {
+    const updated = new BehaviorSubject(0)
     let visual: ComplexVisual<P> | undefined
+
     let _structure: Structure
-    let _props: P
+    let _params: P
+    let _props: PD.DefaultValues<P>
     let _theme: Theme
 
-    function createOrUpdate(ctx: RepresentationContext, props: Partial<P> = {}, structure?: Structure) {
-        if (structure) _structure = structure
-        _props = Object.assign({}, _props, props, { structure: _structure })
-        _theme = createTheme(_props)
+    function createOrUpdate(ctx: RepresentationContext, props: Partial<PD.DefaultValues<P>> = {}, themeProps: ThemeProps = {}, structure?: Structure) {
+        if (structure && structure !== _structure) {
+            _params = getParams(ctx, structure)
+            _structure = structure
+            if (!_props) _props = PD.getDefaultValues(_params)
+        }
+        _props = Object.assign({}, _props, props)
+        _theme = createTheme(ctx, { structure: _structure }, props, themeProps, _theme)
 
         return Task.create('Creating or updating ComplexRepresentation', async runtime => {
             if (!visual) visual = visualCtor()
             await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, structure)
+            updated.next(updated.getValue() + 1)
         });
     }
 
@@ -46,11 +56,12 @@ export function ComplexRepresentation<P extends StructureProps>(label: string, v
 
     return {
         label,
-        params: StructureParams, // TODO
         get renderObjects() {
             return visual && visual.renderObject ? [ visual.renderObject ] : []
         },
         get props() { return _props },
+        get params() { return _params },
+        get updated() { return updated },
         createOrUpdate,
         getLoci,
         mark,

+ 32 - 28
src/mol-repr/structure/complex-visual.ts

@@ -5,64 +5,68 @@
  */
 
 import { Structure } from 'mol-model/structure';
-import { Visual, VisualContext } from '..';
+import { Visual, VisualContext } from '../representation';
 import { MeshRenderObject, LinesRenderObject, PointsRenderObject, DirectVolumeRenderObject } from 'mol-gl/render-object';
 import { createComplexMeshRenderObject, UnitKind, UnitKindOptions } from './visual/util/common';
-import { StructureProps, StructureMeshParams, StructureParams } from './index';
+import { StructureMeshParams, StructureParams } from './representation';
 import { deepEqual, ValueCell } from 'mol-util';
 import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
 import { Interval } from 'mol-data/int';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { RenderableValues } from 'mol-gl/renderable/schema';
 import { createSizes } from 'mol-geo/geometry/size-data';
-import { Geometry, updateRenderableState, Theme } from 'mol-geo/geometry/geometry';
+import { Geometry, updateRenderableState } from 'mol-geo/geometry/geometry';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { createColors } from 'mol-geo/geometry/color-data';
 import { MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
-import { VisualUpdateState, colorChanged, sizeChanged } from 'mol-repr/util';
+import { VisualUpdateState } from 'mol-repr/util';
+import { Theme } from 'mol-theme/theme';
+import { ColorTheme } from 'mol-theme/color';
+import { SizeTheme } from 'mol-theme/size';
 
-export interface  ComplexVisual<P extends StructureProps> extends Visual<Structure, P> { }
+export interface  ComplexVisual<P extends StructureParams> extends Visual<Structure, P> { }
 
 const ComplexParams = {
     ...StructureParams,
     unitKinds: PD.MultiSelect<UnitKind>('Unit Kind', '', ['atomic', 'spheres'], UnitKindOptions),
 }
-const DefaultComplexProps = PD.getDefaultValues(ComplexParams)
-type ComplexProps = typeof DefaultComplexProps
+type ComplexParams = typeof ComplexParams
 
 type ComplexRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject
 
-interface ComplexVisualBuilder<P extends ComplexProps, G extends Geometry> {
-    defaultProps: P
-    createGeometry(ctx: VisualContext, structure: Structure, theme: Theme, props: P, geometry?: G): Promise<G>
+interface ComplexVisualBuilder<P extends ComplexParams, G extends Geometry> {
+    defaultProps: PD.DefaultValues<P>
+    createGeometry(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.DefaultValues<P>, geometry?: G): Promise<G>
     createLocationIterator(structure: Structure): LocationIterator
     getLoci(pickingId: PickingId, structure: Structure, id: number): Loci
     mark(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean): boolean,
-    setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
+    setUpdateState(state: VisualUpdateState, newProps: PD.DefaultValues<P>, currentProps: PD.DefaultValues<P>, newTheme: Theme, currentTheme: Theme): void
 }
 
-interface ComplexVisualGeometryBuilder<P extends ComplexProps, G extends Geometry> extends ComplexVisualBuilder<P, G> {
+interface ComplexVisualGeometryBuilder<P extends ComplexParams, G extends Geometry> extends ComplexVisualBuilder<P, G> {
     createEmptyGeometry(geometry?: G): G
-    createRenderObject(ctx: VisualContext, structure: Structure, geometry: Geometry, locationIt: LocationIterator, theme: Theme, currentProps: P): Promise<ComplexRenderObject>
-    updateValues(values: RenderableValues, newProps: P): void
+    createRenderObject(ctx: VisualContext, structure: Structure, geometry: Geometry, locationIt: LocationIterator, theme: Theme, currentProps: PD.DefaultValues<P>): Promise<ComplexRenderObject>
+    updateValues(values: RenderableValues, newProps: PD.DefaultValues<P>): void
 }
 
-export function ComplexVisual<P extends ComplexMeshProps>(builder: ComplexVisualGeometryBuilder<P, Geometry>): ComplexVisual<P> {
+export function ComplexVisual<P extends ComplexParams>(builder: ComplexVisualGeometryBuilder<P, Geometry>): ComplexVisual<P> {
     const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder
     const { createRenderObject, updateValues } = builder
     const updateState = VisualUpdateState.create()
 
     let renderObject: ComplexRenderObject | undefined
-    let currentProps: P
+    let currentProps: PD.DefaultValues<P>
+    let currentTheme: Theme
     let geometry: Geometry
     let currentStructure: Structure
     let locationIt: LocationIterator
     let conformationHash: number
 
-    async function create(ctx: VisualContext, structure: Structure, theme: Theme, props: Partial<P> = {}) {
+    async function create(ctx: VisualContext, structure: Structure, theme: Theme, props: Partial<PD.DefaultValues<P>> = {}) {
         currentProps = Object.assign({}, defaultProps, props)
+        currentTheme = theme
         currentStructure = structure
 
         conformationHash = Structure.conformationHash(currentStructure)
@@ -72,14 +76,14 @@ export function ComplexVisual<P extends ComplexMeshProps>(builder: ComplexVisual
         renderObject = await createRenderObject(ctx, structure, geometry, locationIt, theme, currentProps)
     }
 
-    async function update(ctx: VisualContext, theme: Theme, props: Partial<P>) {
+    async function update(ctx: VisualContext, theme: Theme, props: Partial<PD.DefaultValues<P>>) {
         const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
 
         if (!renderObject) return false
 
         locationIt.reset()
         VisualUpdateState.reset(updateState)
-        setUpdateState(updateState, newProps, currentProps)
+        setUpdateState(updateState, newProps, currentProps, theme, currentTheme)
 
         const newConformationHash = Structure.conformationHash(currentStructure)
         if (newConformationHash !== conformationHash) {
@@ -87,7 +91,7 @@ export function ComplexVisual<P extends ComplexMeshProps>(builder: ComplexVisual
             updateState.createGeometry = true
         }
 
-        if (colorChanged(currentProps, newProps)) updateState.updateColor = true
+        if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true
         if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
 
         //
@@ -113,12 +117,13 @@ export function ComplexVisual<P extends ComplexMeshProps>(builder: ComplexVisual
         updateRenderableState(renderObject.state, newProps)
 
         currentProps = newProps
+        currentTheme = theme
         return true
     }
 
     return {
         get renderObject () { return renderObject },
-        async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<P> = {}, structure?: Structure) {
+        async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<PD.DefaultValues<P>> = {}, structure?: Structure) {
             if (!structure && !currentStructure) {
                 throw new Error('missing structure')
             } else if (structure && (!currentStructure || !renderObject)) {
@@ -170,17 +175,16 @@ export const ComplexMeshParams = {
     ...StructureMeshParams,
     unitKinds: PD.MultiSelect<UnitKind>('Unit Kind', '', [ 'atomic', 'spheres' ], UnitKindOptions),
 }
-export const DefaultComplexMeshProps = PD.getDefaultValues(ComplexMeshParams)
-export type ComplexMeshProps = typeof DefaultComplexMeshProps
+export type ComplexMeshParams = typeof ComplexMeshParams
 
-export interface ComplexMeshVisualBuilder<P extends ComplexMeshProps> extends ComplexVisualBuilder<P, Mesh> { }
+export interface ComplexMeshVisualBuilder<P extends ComplexMeshParams> extends ComplexVisualBuilder<P, Mesh> { }
 
-export function ComplexMeshVisual<P extends ComplexMeshProps>(builder: ComplexMeshVisualBuilder<P>): ComplexVisual<P> {
+export function ComplexMeshVisual<P extends ComplexMeshParams>(builder: ComplexMeshVisualBuilder<P>): ComplexVisual<P> {
     return ComplexVisual({
         ...builder,
-        setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
-            builder.setUpdateState(state, newProps, currentProps)
-            if (sizeChanged(currentProps, newProps)) state.createGeometry = true
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<P>, currentProps: PD.DefaultValues<P>, newTheme: Theme, currentTheme: Theme) => {
+            builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme)
+            if (SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.createGeometry = true
         },
         createEmptyGeometry: Mesh.createEmpty,
         createRenderObject: createComplexMeshRenderObject,

+ 28 - 0
src/mol-repr/structure/registry.ts

@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Structure } from 'mol-model/structure';
+import { RepresentationProvider, RepresentationRegistry } from '../representation';
+import { CartoonRepresentationProvider } from './representation/cartoon';
+import { BallAndStickRepresentationProvider } from './representation/ball-and-stick';
+
+export class StructureRepresentationRegistry extends RepresentationRegistry<Structure> {
+    constructor() {
+        super()
+        Object.keys(BuiltInStructureRepresentations).forEach(name => {
+            const p = (BuiltInStructureRepresentations as { [k: string]: RepresentationProvider<Structure, any> })[name]
+            this.add(name, p.factory, p.getParams)
+        })
+    }
+}
+
+export const BuiltInStructureRepresentations = {
+    'cartoon': CartoonRepresentationProvider,
+    'ball-and-stick': BallAndStickRepresentationProvider,
+}
+export type BuiltInStructureRepresentationsName = keyof typeof BuiltInStructureRepresentations
+export const BuiltInStructureRepresentationsNames = Object.keys(BuiltInStructureRepresentations)
+export const BuiltInStructureRepresentationsOptions = BuiltInStructureRepresentationsNames.map(n => [n, n] as [BuiltInStructureRepresentationsName, string])

+ 10 - 16
src/mol-repr/structure/index.ts → src/mol-repr/structure/representation.ts

@@ -6,9 +6,7 @@
  */
 
 import { Structure } from 'mol-model/structure';
-import { ColorThemeName, ColorThemeOptions } from 'mol-theme/color';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
-import { Representation, RepresentationProps } from '..';
+import { Representation, RepresentationProps, RepresentationProvider } from '../representation';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Geometry } from 'mol-geo/geometry/geometry';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
@@ -17,43 +15,39 @@ import { Lines } from 'mol-geo/geometry/lines/lines';
 import { DirectVolume } from 'mol-geo/geometry/direct-volume/direct-volume';
 
 export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { }
-// export interface  StructureVisual<P extends RepresentationProps = {}> extends Visual<Structure, P> { }
+
+export type StructureRepresentationProvider<P extends PD.Params> = RepresentationProvider<Structure, P>
+
+//
 
 export const StructureParams = {
     ...Geometry.Params,
-    colorTheme: PD.Select<ColorThemeName>('Color Theme', '', 'polymer-index', ColorThemeOptions),
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'physical', SizeThemeOptions),
 }
-export const DefaultStructureProps = PD.getDefaultValues(StructureParams)
-export type StructureProps = typeof DefaultStructureProps
+export type StructureParams = typeof StructureParams
 
 export const StructureMeshParams = {
     ...Mesh.Params,
     ...StructureParams,
 }
-export const DefaultStructureMeshProps = PD.getDefaultValues(StructureMeshParams)
-export type StructureMeshProps = typeof DefaultStructureMeshProps
+export type StructureMeshParams = typeof StructureMeshParams
 
 export const StructurePointsParams = {
     ...Points.Params,
     ...StructureParams,
 }
-export const DefaultStructurePointsProps = PD.getDefaultValues(StructurePointsParams)
-export type StructurePointsProps = typeof DefaultStructurePointsProps
+export type StructurePointsParams = typeof StructurePointsParams
 
 export const StructureLinesParams = {
     ...Lines.Params,
     ...StructureParams,
 }
-export const DefaultStructureLinesProps = PD.getDefaultValues(StructureLinesParams)
-export type StructureLinesProps = typeof DefaultStructureLinesProps
+export type StructureLinesParams = typeof StructureLinesParams
 
 export const StructureDirectVolumeParams = {
     ...DirectVolume.Params,
     ...StructureParams,
 }
-export const DefaultStructureDirectVolumeProps = PD.getDefaultValues(StructureDirectVolumeParams)
-export type StructureDirectVolumeProps = typeof DefaultStructureDirectVolumeProps
+export type StructureDirectVolumeParams = typeof StructureDirectVolumeParams
 
 export { ComplexRepresentation } from './complex-representation'
 export { UnitsRepresentation } from './units-representation'

+ 25 - 21
src/mol-repr/structure/representation/backbone.ts

@@ -1,25 +1,29 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
+// /**
+//  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+//  *
+//  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+//  */
 
-import { PolymerBackboneVisual, PolymerBackboneParams } from '../visual/polymer-backbone-cylinder';
-import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { UnitsRepresentation } from '../units-representation';
-import { StructureRepresentation } from '../index';
-import { Representation } from 'mol-repr';
+// import { PolymerBackboneVisual, PolymerBackboneParams } from '../visual/polymer-backbone-cylinder';
+// import { ParamDefinition as PD } from 'mol-util/param-definition';
+// import { UnitsRepresentation } from '../units-representation';
+// import { StructureRepresentation } from '../representation';
+// import { Representation } from 'mol-repr/representation';
+// import { ThemeRegistryContext } from 'mol-theme/theme';
+// import { Structure } from 'mol-model/structure';
 
-export const BackboneParams = {
-    ...PolymerBackboneParams
-}
-export const DefaultBackboneProps = PD.getDefaultValues(BackboneParams)
-export type BackboneProps = typeof DefaultBackboneProps
+// export const BackboneParams = {
+//     ...PolymerBackboneParams,
+// }
+// export function getBackboneParams(ctx: ThemeRegistryContext, structure: Structure) {
+//     return BackboneParams // TODO return copy
+// }
+// export type BackboneProps = PD.DefaultValues<typeof BackboneParams>
 
-export type BackboneRepresentation = StructureRepresentation<BackboneProps>
+// export type BackboneRepresentation = StructureRepresentation<BackboneProps>
 
-export function BackboneRepresentation(): BackboneRepresentation {
-    return Representation.createMulti('Backbone', BackboneParams, DefaultBackboneProps, [
-        UnitsRepresentation('Polymer backbone cylinder', PolymerBackboneVisual)
-    ] as StructureRepresentation<BackboneProps>[])
-}
+// export function BackboneRepresentation(defaultProps: BackboneProps): BackboneRepresentation {
+//     return Representation.createMulti('Backbone', defaultProps, [
+//         UnitsRepresentation('Polymer backbone cylinder', defaultProps, PolymerBackboneVisual)
+//     ])
+// }

+ 29 - 16
src/mol-repr/structure/representation/ball-and-stick.ts

@@ -7,32 +7,45 @@
 import { ElementSphereVisual, ElementSphereParams } from '../visual/element-sphere';
 import { IntraUnitLinkVisual, IntraUnitLinkParams } from '../visual/intra-unit-link-cylinder';
 import { InterUnitLinkVisual, InterUnitLinkParams } from '../visual/inter-unit-link-cylinder';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { UnitKind, UnitKindOptions } from '../visual/util/common';
 import { UnitsRepresentation } from '../units-representation';
 import { ComplexRepresentation } from '../complex-representation';
-import { StructureRepresentation } from '../index';
-import { Representation } from 'mol-repr';
+import { StructureRepresentation, StructureRepresentationProvider } from '../representation';
+import { Representation, RepresentationParamsGetter } from 'mol-repr/representation';
+import { ThemeRegistryContext } from 'mol-theme/theme';
+import { Structure } from 'mol-model/structure';
+import { BuiltInSizeThemeName, BuiltInSizeThemeOptions } from 'mol-theme/size';
+import { BuiltInColorThemeName, BuiltInColorThemeOptions } from 'mol-theme/color';
+import { UnitKind, UnitKindOptions } from '../visual/util/common';
+
+const BallAndStickVisuals = {
+    'element-sphere': (getParams: RepresentationParamsGetter<Structure, ElementSphereParams>) => UnitsRepresentation('Element sphere mesh', getParams, ElementSphereVisual),
+    'intra-link': (getParams: RepresentationParamsGetter<Structure, IntraUnitLinkParams>) => UnitsRepresentation('Intra-unit link cylinder', getParams, IntraUnitLinkVisual),
+    'inter-link': (getParams: RepresentationParamsGetter<Structure, InterUnitLinkParams>) => ComplexRepresentation('Inter-unit link cylinder', getParams, InterUnitLinkVisual),
+}
+type BallAndStickVisualName = keyof typeof BallAndStickVisuals
+const BallAndStickVisualOptions = Object.keys(BallAndStickVisuals).map(name => [name, name] as [BallAndStickVisualName, string])
 
 export const BallAndStickParams = {
     ...ElementSphereParams,
     ...IntraUnitLinkParams,
     ...InterUnitLinkParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 0.2, 0, 10, 0.1),
-    sizeFactor: PD.Numeric('Size Factor', '', 1, 0, 10, 0.1),
     unitKinds: PD.MultiSelect<UnitKind>('Unit Kind', '', ['atomic'], UnitKindOptions),
+    sizeFactor: PD.Numeric('Size Factor', '', 0.2, 0.01, 10, 0.01),
+    sizeTheme: PD.Select<BuiltInSizeThemeName>('Size Theme', '', 'uniform', BuiltInSizeThemeOptions),
+    colorTheme: PD.Select<BuiltInColorThemeName>('Color Theme', '', 'polymer-index', BuiltInColorThemeOptions),
+    visuals: PD.MultiSelect<BallAndStickVisualName>('Visuals', '', ['element-sphere', 'intra-link', 'inter-link'], BallAndStickVisualOptions),
+}
+export type BallAndStickParams = typeof BallAndStickParams
+export function getBallAndStickParams(ctx: ThemeRegistryContext, structure: Structure) {
+    return PD.clone(BallAndStickParams)
 }
-export const DefaultBallAndStickProps = PD.getDefaultValues(BallAndStickParams)
-export type BallAndStickProps = typeof DefaultBallAndStickProps
 
-export type BallAndStickRepresentation = StructureRepresentation<BallAndStickProps>
+export type BallAndStickRepresentation = StructureRepresentation<BallAndStickParams>
+export function BallAndStickRepresentation(getParams: RepresentationParamsGetter<Structure, BallAndStickParams>): BallAndStickRepresentation {
+    return Representation.createMulti('Ball & Stick', getParams, BallAndStickVisuals as unknown as Representation.Def<Structure, BallAndStickParams>)
+}
 
-export function BallAndStickRepresentation(): BallAndStickRepresentation {
-    return Representation.createMulti('Ball & Stick', BallAndStickParams, DefaultBallAndStickProps, [
-        UnitsRepresentation('Element sphere mesh', ElementSphereVisual),
-        UnitsRepresentation('Intra-unit link cylinder', IntraUnitLinkVisual),
-        ComplexRepresentation('Inter-unit link cylinder', InterUnitLinkVisual)
-    ] as unknown as StructureRepresentation<BallAndStickProps>[]) // TODO avoid cast to unknown
+export const BallAndStickRepresentationProvider: StructureRepresentationProvider<typeof BallAndStickParams> = {
+    factory: BallAndStickRepresentation, getParams: getBallAndStickParams
 }

+ 28 - 28
src/mol-repr/structure/representation/carbohydrate.ts

@@ -1,32 +1,32 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
+// /**
+//  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+//  *
+//  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+//  */
 
-import { CarbohydrateSymbolVisual, CarbohydrateSymbolParams } from '../visual/carbohydrate-symbol-mesh';
-import { CarbohydrateLinkVisual, CarbohydrateLinkParams } from '../visual/carbohydrate-link-cylinder';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
-import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { ComplexRepresentation } from '../complex-representation';
-import { StructureRepresentation } from '../index';
-import { Representation } from 'mol-repr';
+// import { CarbohydrateSymbolVisual, CarbohydrateSymbolParams } from '../visual/carbohydrate-symbol-mesh';
+// import { CarbohydrateLinkVisual, CarbohydrateLinkParams } from '../visual/carbohydrate-link-cylinder';
+// import { ParamDefinition as PD } from 'mol-util/param-definition';
+// import { ComplexRepresentation } from '../complex-representation';
+// import { StructureRepresentation } from '../representation';
+// import { Representation } from 'mol-repr/representation';
+// import { ThemeRegistryContext } from 'mol-theme/theme';
+// import { Structure } from 'mol-model/structure';
 
-export const CarbohydrateParams = {
-    ...CarbohydrateSymbolParams,
-    ...CarbohydrateLinkParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 1, 0, 0.1, 20),
-    sizeFactor: PD.Numeric('Size Factor', '', 1, 0, 10, 0.1),
-}
-export const DefaultCarbohydrateProps = PD.getDefaultValues(CarbohydrateParams)
-export type CarbohydrateProps = typeof DefaultCarbohydrateProps
+// export const CarbohydrateParams = {
+//     ...CarbohydrateSymbolParams,
+//     ...CarbohydrateLinkParams,
+// }
+// export function getCarbohydrateParams(ctx: ThemeRegistryContext, structure: Structure) {
+//     return CarbohydrateParams // TODO return copy
+// }
+// export type CarbohydrateProps = PD.DefaultValues<typeof CarbohydrateParams>
 
-export type CarbohydrateRepresentation = StructureRepresentation<CarbohydrateProps>
+// export type CarbohydrateRepresentation = StructureRepresentation<CarbohydrateProps>
 
-export function CarbohydrateRepresentation(): CarbohydrateRepresentation {
-    return Representation.createMulti('Carbohydrate', CarbohydrateParams, DefaultCarbohydrateProps, [
-        ComplexRepresentation('Carbohydrate symbol mesh', CarbohydrateSymbolVisual),
-        ComplexRepresentation('Carbohydrate link cylinder', CarbohydrateLinkVisual)
-    ] as StructureRepresentation<CarbohydrateProps>[])
-}
+// export function CarbohydrateRepresentation(defaultProps: CarbohydrateProps): CarbohydrateRepresentation {
+//     return Representation.createMulti('Carbohydrate', defaultProps, [
+//         ComplexRepresentation('Carbohydrate symbol mesh', defaultProps, CarbohydrateSymbolVisual),
+//         ComplexRepresentation('Carbohydrate link cylinder', defaultProps, CarbohydrateLinkVisual)
+//     ])
+// }

+ 29 - 15
src/mol-repr/structure/representation/cartoon.ts

@@ -7,31 +7,45 @@
 import { PolymerTraceVisual,  PolymerTraceParams } from '../visual/polymer-trace-mesh';
 import { PolymerGapVisual, PolymerGapParams } from '../visual/polymer-gap-cylinder';
 import { NucleotideBlockVisual, NucleotideBlockParams } from '../visual/nucleotide-block-mesh';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { UnitsRepresentation } from '../units-representation';
-import { StructureRepresentation } from '../index';
-import { Representation } from 'mol-repr';
+import { StructureRepresentation, StructureRepresentationProvider } from '../representation';
+import { Representation, RepresentationParamsGetter } from 'mol-repr/representation';
 import { PolymerDirectionVisual, PolymerDirectionParams } from '../visual/polymer-direction-wedge';
+import { Structure } from 'mol-model/structure';
+import { ThemeRegistryContext } from 'mol-theme/theme';
+import { BuiltInSizeThemeName, BuiltInSizeThemeOptions } from 'mol-theme/size';
+import { BuiltInColorThemeOptions, BuiltInColorThemeName } from 'mol-theme/color';
+
+const CartoonVisuals = {
+    'polymer-trace': (getParams: RepresentationParamsGetter<Structure, PolymerTraceParams>) => UnitsRepresentation('Polymer trace mesh', getParams, PolymerTraceVisual),
+    'polymer-gap': (getParams: RepresentationParamsGetter<Structure, PolymerGapParams>) => UnitsRepresentation('Polymer gap cylinder', getParams, PolymerGapVisual),
+    'nucleotide-block': (getParams: RepresentationParamsGetter<Structure, NucleotideBlockParams>) => UnitsRepresentation('Nucleotide block mesh', getParams, NucleotideBlockVisual),
+    'direction-wedge': (getParams: RepresentationParamsGetter<Structure, PolymerDirectionParams>) => UnitsRepresentation('Polymer direction wedge', getParams, PolymerDirectionVisual)
+}
+type CartoonVisualName = keyof typeof CartoonVisuals
+const CartoonVisualOptions = Object.keys(CartoonVisuals).map(name => [name, name] as [CartoonVisualName, string])
 
 export const CartoonParams = {
     ...PolymerTraceParams,
     ...PolymerGapParams,
     ...NucleotideBlockParams,
     ...PolymerDirectionParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 0.2, 0, 10, 0.1),
+    sizeFactor: PD.Numeric('Size Factor', '', 0.2, 0, 10, 0.01),
+    sizeTheme: PD.Select<BuiltInSizeThemeName>('Size Theme', '', 'uniform', BuiltInSizeThemeOptions),
+    colorTheme: PD.Select<BuiltInColorThemeName>('Color Theme', '', 'polymer-index', BuiltInColorThemeOptions),
+    visuals: PD.MultiSelect<CartoonVisualName>('Visuals', '', ['polymer-trace', 'polymer-gap', 'nucleotide-block'], CartoonVisualOptions),
+}
+export type CartoonParams = typeof CartoonParams
+export function getCartoonParams(ctx: ThemeRegistryContext, structure: Structure) {
+    return PD.clone(CartoonParams)
 }
-export const DefaultCartoonProps = { ...PD.getDefaultValues(CartoonParams), visuals: [ '0', '1', '2' ] }
-export type CartoonProps = typeof DefaultCartoonProps
 
-export type CartoonRepresentation = StructureRepresentation<CartoonProps>
+export type CartoonRepresentation = StructureRepresentation<CartoonParams>
+export function CartoonRepresentation(getParams: RepresentationParamsGetter<Structure, CartoonParams>): CartoonRepresentation {
+    return Representation.createMulti('Cartoon', getParams, CartoonVisuals as unknown as Representation.Def<Structure, CartoonParams>)
+}
 
-export function CartoonRepresentation(): CartoonRepresentation {
-    return Representation.createMulti('Cartoon', CartoonParams, DefaultCartoonProps, [
-        UnitsRepresentation('Polymer trace mesh', PolymerTraceVisual),
-        UnitsRepresentation('Polymer gap cylinder', PolymerGapVisual),
-        UnitsRepresentation('Nucleotide block mesh', NucleotideBlockVisual),
-        UnitsRepresentation('Polymer direction wedge', PolymerDirectionVisual)
-    ] as unknown as StructureRepresentation<CartoonProps>[]) // TODO avoid cast to unknown
+export const CartoonRepresentationProvider: StructureRepresentationProvider<CartoonParams> = {
+    factory: CartoonRepresentation, getParams: getCartoonParams
 }

+ 25 - 24
src/mol-repr/structure/representation/distance-restraint.ts

@@ -1,28 +1,29 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
+// /**
+//  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+//  *
+//  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+//  */
 
-import { CrossLinkRestraintVisual, CrossLinkRestraintParams } from '../visual/cross-link-restraint-cylinder';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
-import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { ComplexRepresentation } from '../complex-representation';
-import { StructureRepresentation } from '../index';
-import { Representation } from 'mol-repr';
+// import { CrossLinkRestraintVisual, CrossLinkRestraintParams } from '../visual/cross-link-restraint-cylinder';
+// import { ParamDefinition as PD } from 'mol-util/param-definition';
+// import { ComplexRepresentation } from '../complex-representation';
+// import { StructureRepresentation } from '../representation';
+// import { Representation } from 'mol-repr/representation';
+// import { ThemeRegistryContext } from 'mol-theme/theme';
+// import { Structure } from 'mol-model/structure';
 
-export const DistanceRestraintParams = {
-    ...CrossLinkRestraintParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 0.25, 0, 0.05, 20),
-}
-export const DefaultDistanceRestraintProps = PD.getDefaultValues(DistanceRestraintParams)
-export type DistanceRestraintProps = typeof DefaultDistanceRestraintProps
+// export const DistanceRestraintParams = {
+//     ...CrossLinkRestraintParams,
+// }
+// export function getDistanceRestraintParams(ctx: ThemeRegistryContext, structure: Structure) {
+//     return DistanceRestraintParams // TODO return copy
+// }
+// export type DistanceRestraintProps = PD.DefaultValues<typeof DistanceRestraintParams>
 
-export type DistanceRestraintRepresentation = StructureRepresentation<DistanceRestraintProps>
+// export type DistanceRestraintRepresentation = StructureRepresentation<DistanceRestraintProps>
 
-export function DistanceRestraintRepresentation(): DistanceRestraintRepresentation {
-    return Representation.createMulti('Distance restraint', DistanceRestraintParams, DefaultDistanceRestraintProps, [
-        ComplexRepresentation('Cross-link restraint', CrossLinkRestraintVisual)
-    ] as StructureRepresentation<DistanceRestraintProps>[])
-}
+// export function DistanceRestraintRepresentation(defaultProps: DistanceRestraintProps): DistanceRestraintRepresentation {
+//     return Representation.createMulti('Distance restraint', defaultProps, [
+//         ComplexRepresentation('Cross-link restraint', defaultProps, CrossLinkRestraintVisual)
+//     ])
+// }

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

@@ -1,33 +1,35 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
+// /**
+//  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+//  *
+//  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+//  */
 
-import { GaussianSurfaceVisual, GaussianSurfaceParams } from '../visual/gaussian-surface-mesh';
-import { UnitsRepresentation } from '../units-representation';
-import { GaussianWireframeVisual, GaussianWireframeParams } from '../visual/gaussian-surface-wireframe';
-import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { GaussianDensityVolumeParams, GaussianDensityVolumeVisual } from '../visual/gaussian-density-volume';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
-import { StructureRepresentation } from '../index';
-import { Representation } from 'mol-repr';
+// import { GaussianSurfaceVisual, GaussianSurfaceParams } from '../visual/gaussian-surface-mesh';
+// import { UnitsRepresentation } from '../units-representation';
+// import { GaussianWireframeVisual, GaussianWireframeParams } from '../visual/gaussian-surface-wireframe';
+// import { ParamDefinition as PD } from 'mol-util/param-definition';
+// import { GaussianDensityVolumeParams, GaussianDensityVolumeVisual } from '../visual/gaussian-density-volume';
+// import { StructureRepresentation } from '../representation';
+// import { Representation } from 'mol-repr/representation';
+// import { ThemeRegistryContext } from 'mol-theme/theme';
+// import { Structure } from 'mol-model/structure';
 
-export const MolecularSurfaceParams = {
-    ...GaussianSurfaceParams,
-    ...GaussianWireframeParams,
-    ...GaussianDensityVolumeParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-}
-export const DefaultMolecularSurfaceProps = { ...PD.getDefaultValues(MolecularSurfaceParams), visuals: [ '0' ] }
-export type MolecularSurfaceProps = typeof DefaultMolecularSurfaceProps
+// export const MolecularSurfaceParams = {
+//     ...GaussianSurfaceParams,
+//     ...GaussianWireframeParams,
+//     ...GaussianDensityVolumeParams,
+// }
+// export function getMolecularSurfaceParams(ctx: ThemeRegistryContext, structure: Structure) {
+//     return MolecularSurfaceParams // TODO return copy
+// }
+// export type MolecularSurfaceProps = PD.DefaultValues<typeof MolecularSurfaceParams>
 
-export type MolecularSurfaceRepresentation = StructureRepresentation<MolecularSurfaceProps>
+// export type MolecularSurfaceRepresentation = StructureRepresentation<MolecularSurfaceProps>
 
-export function MolecularSurfaceRepresentation(): MolecularSurfaceRepresentation {
-    return Representation.createMulti('Molecular Surface', MolecularSurfaceParams, DefaultMolecularSurfaceProps, [
-        UnitsRepresentation('Gaussian surface', GaussianSurfaceVisual),
-        UnitsRepresentation('Gaussian wireframe', GaussianWireframeVisual),
-        UnitsRepresentation('Gaussian volume', GaussianDensityVolumeVisual)
-    ] as unknown as StructureRepresentation<MolecularSurfaceProps>[]) // TODO avoid cast to unknown
-}
+// export function MolecularSurfaceRepresentation(defaultProps: MolecularSurfaceProps): MolecularSurfaceRepresentation {
+//     return Representation.createMulti('Molecular Surface', defaultProps, [
+//         UnitsRepresentation('Gaussian surface', defaultProps, GaussianSurfaceVisual),
+//         UnitsRepresentation('Gaussian wireframe', defaultProps, GaussianWireframeVisual),
+//         UnitsRepresentation('Gaussian volume', defaultProps, GaussianDensityVolumeVisual)
+//     ])
+// }

+ 25 - 21
src/mol-repr/structure/representation/point.ts

@@ -1,25 +1,29 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
+// /**
+//  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+//  *
+//  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+//  */
 
-import { ElementPointVisual, ElementPointParams } from '../visual/element-point';
-import { UnitsRepresentation } from '../units-representation';
-import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { StructureRepresentation } from '../index';
-import { Representation } from 'mol-repr';
+// import { ElementPointVisual, ElementPointParams } from '../visual/element-point';
+// import { UnitsRepresentation } from '../units-representation';
+// import { ParamDefinition as PD } from 'mol-util/param-definition';
+// import { StructureRepresentation } from '../representation';
+// import { Representation } from 'mol-repr/representation';
+// import { ThemeRegistryContext } from 'mol-theme/theme';
+// import { Structure } from 'mol-model/structure';
 
-export const PointParams = {
-    ...ElementPointParams,
-}
-export const DefaultPointProps = PD.getDefaultValues(PointParams)
-export type PointProps = typeof DefaultPointProps
+// export const PointParams = {
+//     ...ElementPointParams,
+// }
+// export function getPointParams(ctx: ThemeRegistryContext, structure: Structure) {
+//     return PointParams // TODO return copy
+// }
+// export type PointProps = PD.DefaultValues<typeof PointParams>
 
-export type PointRepresentation = StructureRepresentation<PointProps>
+// export type PointRepresentation = StructureRepresentation<PointProps>
 
-export function PointRepresentation(): PointRepresentation {
-    return Representation.createMulti('Point', PointParams, DefaultPointProps, [
-        UnitsRepresentation('Point', ElementPointVisual)
-    ] as StructureRepresentation<PointProps>[])
-}
+// export function PointRepresentation(defaultProps: PointProps): PointRepresentation {
+//     return Representation.createMulti('Point', defaultProps, [
+//         UnitsRepresentation('Point', defaultProps, ElementPointVisual)
+//     ])
+// }

+ 25 - 21
src/mol-repr/structure/representation/spacefill.ts

@@ -1,25 +1,29 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
+// /**
+//  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+//  *
+//  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+//  */
 
-import { ElementSphereVisual, ElementSphereParams } from '../visual/element-sphere';
-import { UnitsRepresentation } from '../units-representation';
-import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { StructureRepresentation } from '../index';
-import { Representation } from 'mol-repr';
+// import { ElementSphereVisual, ElementSphereParams } from '../visual/element-sphere';
+// import { UnitsRepresentation } from '../units-representation';
+// import { ParamDefinition as PD } from 'mol-util/param-definition';
+// import { StructureRepresentation } from '../representation';
+// import { Representation } from 'mol-repr/representation';
+// import { ThemeRegistryContext } from 'mol-theme/theme';
+// import { Structure } from 'mol-model/structure';
 
-export const SpacefillParams = {
-    ...ElementSphereParams
-}
-export const DefaultSpacefillProps = PD.getDefaultValues(SpacefillParams)
-export type SpacefillProps = typeof DefaultSpacefillProps
+// export const SpacefillParams = {
+//     ...ElementSphereParams,
+// }
+// export function getSpacefillParams(ctx: ThemeRegistryContext, structure: Structure) {
+//     return SpacefillParams // TODO return copy
+// }
+// export type SpacefillProps = PD.DefaultValues<typeof SpacefillParams>
 
-export type SpacefillRepresentation = StructureRepresentation<SpacefillProps>
+// export type SpacefillRepresentation = StructureRepresentation<SpacefillProps>
 
-export function SpacefillRepresentation(): SpacefillRepresentation {
-    return Representation.createMulti('Spacefill', SpacefillParams, DefaultSpacefillProps, [
-        UnitsRepresentation('Sphere mesh', ElementSphereVisual)
-    ] as StructureRepresentation<SpacefillProps>[])
-}
+// export function SpacefillRepresentation(defaultProps: SpacefillProps): SpacefillRepresentation {
+//     return Representation.createMulti('Spacefill', defaultProps, [
+//         UnitsRepresentation('Sphere mesh', defaultProps, ElementSphereVisual)
+//     ])
+// }

+ 32 - 17
src/mol-repr/structure/units-representation.ts

@@ -8,27 +8,42 @@
 import { Structure, Unit } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/render-object';
-import { RepresentationProps, Visual, RepresentationContext } from '..';
+import { Visual, RepresentationContext, RepresentationParamsGetter } from '../representation';
 import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
 import { StructureGroup } from './units-visual';
-import { StructureProps, StructureParams, StructureRepresentation } from './index';
+import { StructureRepresentation, StructureParams } from './representation';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { MarkerAction } from 'mol-geo/geometry/marker-data';
-import { Theme, createTheme } from 'mol-geo/geometry/geometry';
+import { Theme, ThemeProps, createTheme } from 'mol-theme/theme';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { UnitKind, UnitKindOptions } from './visual/util/common';
+import { BehaviorSubject } from 'rxjs';
 
-export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { }
+export const UnitsParams = {
+    ...StructureParams,
+    unitKinds: PD.MultiSelect<UnitKind>('Unit Kind', '', ['atomic', 'spheres'], UnitKindOptions),
+}
+export type UnitsParams = typeof UnitsParams
 
-export function UnitsRepresentation<P extends StructureProps>(label: string, visualCtor: () => UnitsVisual<P>): StructureRepresentation<P> {
+export interface UnitsVisual<P extends UnitsParams> extends Visual<StructureGroup, P> { }
+
+export function UnitsRepresentation<P extends UnitsParams>(label: string, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: () => UnitsVisual<P>): StructureRepresentation<P> {
+    const updated = new BehaviorSubject(0)
     let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>()
 
-    let _props: P
-    let _theme: Theme
     let _structure: Structure
     let _groups: ReadonlyArray<Unit.SymmetryGroup>
+    let _params: P
+    let _props: PD.DefaultValues<P>
+    let _theme: Theme
 
-    function createOrUpdate(ctx: RepresentationContext, props: Partial<P> = {}, structure?: Structure) {
-        _props = Object.assign({}, _props, props, { structure: structure || _structure })
-        _theme = createTheme(_props)
+    function createOrUpdate(ctx: RepresentationContext, props: Partial<PD.DefaultValues<P>> = {}, themeProps: ThemeProps = {}, structure?: Structure) {
+        if (structure && structure !== _structure) {
+            _params = getParams(ctx, structure)
+            if (!_props) _props = PD.getDefaultValues(_params)
+        }
+        _props = Object.assign({}, _props, props)
+        _theme = createTheme(ctx, { structure: structure || _structure }, props, themeProps, _theme)
 
         return Task.create('Creating or updating UnitsRepresentation', async runtime => {
             if (!_structure && !structure) {
@@ -78,7 +93,7 @@ export function UnitsRepresentation<P extends StructureProps>(label: string, vis
                 //     visuals.set(group.hashCode, { visual, group })
                 // })
                 // unusedVisuals.forEach(visual => visual.destroy())
-            } else if (structure && _structure.hashCode === structure.hashCode) {
+            } else if (structure && structure !== _structure && _structure.hashCode === structure.hashCode) {
                 // console.log('_structure.hashCode === structure.hashCode')
                 // Expects that for structures with the same hashCode,
                 // the unitSymmetryGroups are the same as well.
@@ -100,11 +115,12 @@ export function UnitsRepresentation<P extends StructureProps>(label: string, vis
                 const visualsList: [ UnitsVisual<P>, Unit.SymmetryGroup ][] = [] // TODO avoid allocation
                 visuals.forEach(({ visual, group }) => visualsList.push([ visual, group ]))
                 for (let i = 0, il = visualsList.length; i < il; ++i) {
-                    const [ visual, group ] = visualsList[i]
-                    await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure: _structure })
+                    const [ visual ] = visualsList[i]
+                    await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props)
                 }
             }
             if (structure) _structure = structure
+            updated.next(updated.getValue() + 1)
         });
     }
 
@@ -132,7 +148,6 @@ export function UnitsRepresentation<P extends StructureProps>(label: string, vis
 
     return {
         label,
-        params: StructureParams, // TODO
         get renderObjects() {
             const renderObjects: RenderObject[] = []
             visuals.forEach(({ visual }) => {
@@ -140,9 +155,9 @@ export function UnitsRepresentation<P extends StructureProps>(label: string, vis
             })
             return renderObjects
         },
-        get props() {
-            return _props
-        },
+        get props() { return _props },
+        get params() { return _params },
+        get updated() { return updated },
         createOrUpdate,
         getLoci,
         mark,

+ 50 - 54
src/mol-repr/structure/units-visual.ts

@@ -5,16 +5,16 @@
  */
 
 import { Unit, Structure } from 'mol-model/structure';
-import { RepresentationProps, Visual, VisualContext } from '../';
-import { StructureMeshParams, StructurePointsParams, StructureLinesParams, StructureDirectVolumeParams, StructureParams } from './index';
+import { RepresentationProps, Visual, VisualContext } from '../representation';
+import { StructureMeshParams, StructurePointsParams, StructureLinesParams, StructureDirectVolumeParams } from './representation';
 import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
 import { MeshRenderObject, PointsRenderObject, LinesRenderObject, DirectVolumeRenderObject } from 'mol-gl/render-object';
-import { createUnitsMeshRenderObject, createUnitsPointsRenderObject, createUnitsTransform, createUnitsLinesRenderObject, createUnitsDirectVolumeRenderObject, UnitKind, UnitKindOptions, includesUnitKind } from './visual/util/common';
+import { createUnitsMeshRenderObject, createUnitsPointsRenderObject, createUnitsTransform, createUnitsLinesRenderObject, createUnitsDirectVolumeRenderObject, includesUnitKind } from './visual/util/common';
 import { deepEqual, ValueCell, UUID } from 'mol-util';
 import { Interval } from 'mol-data/int';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { RenderableValues } from 'mol-gl/renderable/schema';
-import { Geometry, updateRenderableState, Theme } from 'mol-geo/geometry/geometry';
+import { Geometry, updateRenderableState } from 'mol-geo/geometry/geometry';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { createMarkers, MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data';
@@ -24,7 +24,11 @@ import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { Points } from 'mol-geo/geometry/points/points';
 import { Lines } from 'mol-geo/geometry/lines/lines';
 import { DirectVolume } from 'mol-geo/geometry/direct-volume/direct-volume';
-import { VisualUpdateState, colorChanged, sizeChanged } from 'mol-repr/util';
+import { VisualUpdateState } from 'mol-repr/util';
+import { Theme } from 'mol-theme/theme';
+import { ColorTheme } from 'mol-theme/color';
+import { SizeTheme } from 'mol-theme/size';
+import { UnitsParams } from './units-representation';
 
 export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
 
@@ -37,45 +41,40 @@ function sameGroupConformation(groupA: Unit.SymmetryGroup, groupB: Unit.Symmetry
     )
 }
 
-const UnitsParams = {
-    ...StructureParams,
-    unitKinds: PD.MultiSelect<UnitKind>('Unit Kind', '', ['atomic', 'spheres'], UnitKindOptions),
-}
-const DefaultUnitsProps = PD.getDefaultValues(UnitsParams)
-type UnitsProps = typeof DefaultUnitsProps
-
 type UnitsRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject
 
-interface UnitsVisualBuilder<P extends UnitsProps, G extends Geometry> {
-    defaultProps: P
-    createGeometry(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: P, geometry?: G): Promise<G>
+interface UnitsVisualBuilder<P extends UnitsParams, G extends Geometry> {
+    defaultProps: PD.DefaultValues<P>
+    createGeometry(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.DefaultValues<P>, geometry?: G): Promise<G>
     createLocationIterator(group: Unit.SymmetryGroup): LocationIterator
     getLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number): Loci
     mark(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean): boolean
-    setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
+    setUpdateState(state: VisualUpdateState, newProps: PD.DefaultValues<P>, currentProps: PD.DefaultValues<P>, newTheme: Theme, currentTheme: Theme): void
 }
 
-interface UnitsVisualGeometryBuilder<P extends UnitsProps, G extends Geometry> extends UnitsVisualBuilder<P, G> {
+interface UnitsVisualGeometryBuilder<P extends UnitsParams, G extends Geometry> extends UnitsVisualBuilder<P, G> {
     createEmptyGeometry(geometry?: G): G
-    createRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, geometry: Geometry, locationIt: LocationIterator, theme: Theme, currentProps: P): Promise<UnitsRenderObject>
-    updateValues(values: RenderableValues, newProps: P): void
+    createRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, geometry: Geometry, locationIt: LocationIterator, theme: Theme, currentProps: PD.DefaultValues<P>): Promise<UnitsRenderObject>
+    updateValues(values: RenderableValues, newProps: PD.DefaultValues<P>): void
 }
 
-export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBuilder<P, Geometry>): UnitsVisual<P> {
+export function UnitsVisual<P extends UnitsParams>(builder: UnitsVisualGeometryBuilder<P, Geometry>): UnitsVisual<P> {
     const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder
     const { createEmptyGeometry, createRenderObject, updateValues } = builder
     const updateState = VisualUpdateState.create()
 
     let renderObject: UnitsRenderObject | undefined
-    let currentProps: P
+    let currentProps: PD.DefaultValues<P>
+    let currentTheme: Theme
     let geometry: Geometry
     let currentGroup: Unit.SymmetryGroup
     let currentStructure: Structure
     let locationIt: LocationIterator
     let currentConformationId: UUID
 
-    async function create(ctx: VisualContext, group: Unit.SymmetryGroup, theme: Theme, props: Partial<P> = {}) {
+    async function create(ctx: VisualContext, group: Unit.SymmetryGroup, theme: Theme, props: Partial<PD.DefaultValues<P>> = {}) {
         currentProps = Object.assign({}, defaultProps, props, { structure: currentStructure })
+        currentTheme = theme
         currentGroup = group
 
         const unit = group.units[0]
@@ -89,7 +88,7 @@ export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBu
         renderObject = await createRenderObject(ctx, group, geometry, locationIt, theme, currentProps)
     }
 
-    async function update(ctx: VisualContext, theme: Theme, props: Partial<P> = {}) {
+    async function update(ctx: VisualContext, theme: Theme, props: Partial<PD.DefaultValues<P>> = {}) {
         if (!renderObject) return
 
         const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
@@ -97,7 +96,7 @@ export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBu
 
         locationIt.reset()
         VisualUpdateState.reset(updateState)
-        setUpdateState(updateState, newProps, currentProps)
+        setUpdateState(updateState, newProps, currentProps, theme, currentTheme)
 
         const newConformationId = Unit.conformationId(unit)
         if (newConformationId !== currentConformationId) {
@@ -107,7 +106,7 @@ export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBu
 
         if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
 
-        if (colorChanged(currentProps, newProps)) updateState.updateColor = true
+        if (ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true
         if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
 
         //
@@ -143,11 +142,12 @@ export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBu
         updateRenderableState(renderObject.state, newProps)
 
         currentProps = newProps
+        currentTheme = theme
     }
 
     return {
         get renderObject () { return renderObject },
-        async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<P> = {}, structureGroup?: StructureGroup) {
+        async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<PD.DefaultValues<P>> = {}, structureGroup?: StructureGroup) {
             if (structureGroup) currentStructure = structureGroup.structure
             const group = structureGroup ? structureGroup.group : undefined
             if (!group && !currentGroup) {
@@ -205,16 +205,15 @@ export const UnitsMeshParams = {
     ...StructureMeshParams,
     ...UnitsParams,
 }
-export const DefaultUnitsMeshProps = PD.getDefaultValues(UnitsMeshParams)
-export type UnitsMeshProps = typeof DefaultUnitsMeshProps
-export interface UnitsMeshVisualBuilder<P extends UnitsMeshProps> extends UnitsVisualBuilder<P, Mesh> { }
+export type UnitsMeshParams = typeof UnitsMeshParams
+export interface UnitsMeshVisualBuilder<P extends UnitsMeshParams> extends UnitsVisualBuilder<P, Mesh> { }
 
-export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisualBuilder<P>): UnitsVisual<P> {
+export function UnitsMeshVisual<P extends UnitsMeshParams>(builder: UnitsMeshVisualBuilder<P>): UnitsVisual<P> {
     return UnitsVisual({
         ...builder,
-        setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
-            builder.setUpdateState(state, newProps, currentProps)
-            if (sizeChanged(currentProps, newProps)) state.createGeometry = true
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<P>, currentProps: PD.DefaultValues<P>, newTheme: Theme, currentTheme: Theme) => {
+            builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme)
+            if (SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.createGeometry = true
         },
         createEmptyGeometry: Mesh.createEmpty,
         createRenderObject: createUnitsMeshRenderObject,
@@ -228,18 +227,17 @@ export const UnitsPointsParams = {
     ...StructurePointsParams,
     ...UnitsParams,
 }
-export const DefaultUnitsPointsProps = PD.getDefaultValues(UnitsPointsParams)
-export type UnitsPointsProps = typeof DefaultUnitsPointsProps
-export interface UnitsPointVisualBuilder<P extends UnitsPointsProps> extends UnitsVisualBuilder<P, Points> { }
+export type UnitsPointsParams = typeof UnitsPointsParams
+export interface UnitsPointVisualBuilder<P extends UnitsPointsParams> extends UnitsVisualBuilder<P, Points> { }
 
-export function UnitsPointsVisual<P extends UnitsPointsProps>(builder: UnitsPointVisualBuilder<P>): UnitsVisual<P> {
+export function UnitsPointsVisual<P extends UnitsPointsParams>(builder: UnitsPointVisualBuilder<P>): UnitsVisual<P> {
     return UnitsVisual({
         ...builder,
         createEmptyGeometry: Points.createEmpty,
         createRenderObject: createUnitsPointsRenderObject,
-        setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
-            builder.setUpdateState(state, newProps, currentProps)
-            if (sizeChanged(currentProps, newProps)) state.updateSize = true
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<P>, currentProps: PD.DefaultValues<P>, newTheme: Theme, currentTheme: Theme) => {
+            builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme)
+            if (SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.updateSize = true
         },
         updateValues: Points.updateValues
     })
@@ -251,18 +249,17 @@ export const UnitsLinesParams = {
     ...StructureLinesParams,
     ...UnitsParams,
 }
-export const DefaultUnitsLinesProps = PD.getDefaultValues(UnitsLinesParams)
-export type UnitsLinesProps = typeof DefaultUnitsLinesProps
-export interface UnitsLinesVisualBuilder<P extends UnitsLinesProps> extends UnitsVisualBuilder<P, Lines> { }
+export type UnitsLinesParams = typeof UnitsLinesParams
+export interface UnitsLinesVisualBuilder<P extends UnitsLinesParams> extends UnitsVisualBuilder<P, Lines> { }
 
-export function UnitsLinesVisual<P extends UnitsLinesProps>(builder: UnitsLinesVisualBuilder<P>): UnitsVisual<P> {
+export function UnitsLinesVisual<P extends UnitsLinesParams>(builder: UnitsLinesVisualBuilder<P>): UnitsVisual<P> {
     return UnitsVisual({
         ...builder,
         createEmptyGeometry: Lines.createEmpty,
         createRenderObject: createUnitsLinesRenderObject,
-        setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
-            builder.setUpdateState(state, newProps, currentProps)
-            if (sizeChanged(currentProps, newProps)) state.updateSize = true
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<P>, currentProps: PD.DefaultValues<P>, newTheme: Theme, currentTheme: Theme) => {
+            builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme)
+            if (SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.updateSize = true
         },
         updateValues: Lines.updateValues
     })
@@ -274,18 +271,17 @@ export const UnitsDirectVolumeParams = {
     ...StructureDirectVolumeParams,
     ...UnitsParams,
 }
-export const DefaultUnitsDirectVolumeProps = PD.getDefaultValues(UnitsDirectVolumeParams)
-export type UnitsDirectVolumeProps = typeof DefaultUnitsDirectVolumeProps
-export interface UnitsDirectVolumeVisualBuilder<P extends UnitsDirectVolumeProps> extends UnitsVisualBuilder<P, DirectVolume> { }
+export type UnitsDirectVolumeParams = typeof UnitsDirectVolumeParams
+export interface UnitsDirectVolumeVisualBuilder<P extends UnitsDirectVolumeParams> extends UnitsVisualBuilder<P, DirectVolume> { }
 
-export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builder: UnitsDirectVolumeVisualBuilder<P>): UnitsVisual<P> {
+export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeParams>(builder: UnitsDirectVolumeVisualBuilder<P>): UnitsVisual<P> {
     return UnitsVisual({
         ...builder,
         createEmptyGeometry: DirectVolume.createEmpty,
         createRenderObject: createUnitsDirectVolumeRenderObject,
-        setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
-            builder.setUpdateState(state, newProps, currentProps)
-            if (sizeChanged(currentProps, newProps)) state.createGeometry = true
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<P>, currentProps: PD.DefaultValues<P>, newTheme: Theme, currentTheme: Theme) => {
+            builder.setUpdateState(state, newProps, currentProps, newTheme, currentTheme)
+            if (SizeTheme.areEqual(newTheme.size, currentTheme.size)) state.createGeometry = true
         },
         updateValues: DirectVolume.updateValues
     })

+ 7 - 11
src/mol-repr/structure/visual/carbohydrate-link-cylinder.ts

@@ -10,7 +10,6 @@ import { Vec3 } from 'mol-math/linear-algebra';
 import { createLinkCylinderMesh, LinkCylinderProps, LinkCylinderParams } from './util/link';
 import { OrderedSet, Interval } from 'mol-data/int';
 import { ComplexMeshVisual, ComplexVisual } from '../complex-visual';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { LinkType } from 'mol-model/structure/model/types';
 import { BitFlags } from 'mol-util';
 import { UnitsMeshParams } from '../units-visual';
@@ -19,8 +18,8 @@ import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { VisualUpdateState } from '../../util';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 // TODO create seperate visual
 // for (let i = 0, il = carbohydrates.terminalLinks.length; i < il; ++i) {
@@ -64,21 +63,18 @@ async function createCarbohydrateLinkCylinderMesh(ctx: VisualContext, structure:
 export const CarbohydrateLinkParams = {
     ...UnitsMeshParams,
     ...LinkCylinderParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'physical', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 1, 0, 20, 0.1),
     detail: PD.Numeric('Sphere Detail', '', 0, 0, 3, 1),
 }
-export const DefaultCarbohydrateLinkProps = PD.getDefaultValues(CarbohydrateLinkParams)
-export type CarbohydrateLinkProps = typeof DefaultCarbohydrateLinkProps
+export type CarbohydrateLinkParams = typeof CarbohydrateLinkParams
 
-export function CarbohydrateLinkVisual(): ComplexVisual<CarbohydrateLinkProps> {
-    return ComplexMeshVisual<CarbohydrateLinkProps>({
-        defaultProps: DefaultCarbohydrateLinkProps,
+export function CarbohydrateLinkVisual(): ComplexVisual<CarbohydrateLinkParams> {
+    return ComplexMeshVisual<CarbohydrateLinkParams>({
+        defaultProps: PD.getDefaultValues(CarbohydrateLinkParams),
         createGeometry: createCarbohydrateLinkCylinderMesh,
         createLocationIterator: CarbohydrateLinkIterator,
         getLoci: getLinkLoci,
         mark: markLink,
-        setUpdateState: (state: VisualUpdateState, newProps: CarbohydrateLinkProps, currentProps: CarbohydrateLinkProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<CarbohydrateLinkParams>, currentProps: PD.DefaultValues<CarbohydrateLinkParams>) => {
             state.createGeometry = newProps.radialSegments !== currentProps.radialSegments
         }
     })

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

@@ -13,19 +13,18 @@ import { DiamondPrism, PentagonalPrism, HexagonalPrism } from 'mol-geo/primitive
 import { Structure, StructureElement } from 'mol-model/structure';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { getSaccharideShape, SaccharideShapes } from 'mol-model/structure/structure/carbohydrates/constants';
 import { addSphere } from 'mol-geo/geometry/mesh/builder/sphere';
 import { ComplexMeshParams, ComplexMeshVisual } from '../complex-visual';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { ComplexVisual } from '../index';
+import { ComplexVisual } from '../representation';
 import { VisualUpdateState } from '../../util';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { OrderedSet, Interval } from 'mol-data/int';
 import { EmptyLoci, Loci } from 'mol-model/loci';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 const t = Mat4.identity()
 const sVec = Vec3.zero()
@@ -45,7 +44,7 @@ const diamondPrism = DiamondPrism()
 const pentagonalPrism = PentagonalPrism()
 const hexagonalPrism = HexagonalPrism()
 
-async function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: CarbohydrateSymbolProps, mesh?: Mesh) {
+async function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.DefaultValues<CarbohydrateSymbolParams>, mesh?: Mesh) {
     const builder = MeshBuilder.create(256, 128, mesh)
 
     const { detail } = props
@@ -148,21 +147,18 @@ async function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Struc
 
 export const CarbohydrateSymbolParams = {
     ...ComplexMeshParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 1, 0, 10, 0.1),
     detail: PD.Numeric('Sphere Detail', '', 0, 0, 3, 1),
 }
-export const DefaultCarbohydrateSymbolProps = PD.getDefaultValues(CarbohydrateSymbolParams)
-export type CarbohydrateSymbolProps = typeof DefaultCarbohydrateSymbolProps
+export type CarbohydrateSymbolParams = typeof CarbohydrateSymbolParams
 
-export function CarbohydrateSymbolVisual(): ComplexVisual<CarbohydrateSymbolProps> {
-    return ComplexMeshVisual<CarbohydrateSymbolProps>({
-        defaultProps: DefaultCarbohydrateSymbolProps,
+export function CarbohydrateSymbolVisual(): ComplexVisual<CarbohydrateSymbolParams> {
+    return ComplexMeshVisual<CarbohydrateSymbolParams>({
+        defaultProps: PD.getDefaultValues(CarbohydrateSymbolParams),
         createGeometry: createCarbohydrateSymbolMesh,
         createLocationIterator: CarbohydrateElementIterator,
         getLoci: getCarbohydrateLoci,
         mark: markCarbohydrate,
-        setUpdateState: (state: VisualUpdateState, newProps: CarbohydrateSymbolProps, currentProps: CarbohydrateSymbolProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<CarbohydrateSymbolParams>, currentProps: PD.DefaultValues<CarbohydrateSymbolParams>) => {
             state.createGeometry = newProps.detail !== currentProps.detail
         }
     })

+ 8 - 12
src/mol-repr/structure/visual/cross-link-restraint-cylinder.ts

@@ -5,22 +5,21 @@
  */
 
 import { Link, Structure, StructureElement } from 'mol-model/structure';
-import { ComplexVisual } from '../index';
+import { ComplexVisual } from '../representation';
 import { VisualUpdateState } from '../../util';
 import { LinkCylinderProps, createLinkCylinderMesh, LinkCylinderParams } from './util/link';
 import { Vec3 } from 'mol-math/linear-algebra';
 import { Loci, EmptyLoci } from 'mol-model/loci';
 import { ComplexMeshVisual, ComplexMeshParams } from '../complex-visual';
 import { Interval } from 'mol-data/int';
-import { SizeThemeOptions, SizeThemeName } from 'mol-theme/size';
 import { BitFlags } from 'mol-util';
 import { LinkType } from 'mol-model/structure/model/types';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { PickingId } from 'mol-geo/geometry/picking';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 async function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: LinkCylinderProps, mesh?: Mesh) {
 
@@ -54,20 +53,17 @@ async function createCrossLinkRestraintCylinderMesh(ctx: VisualContext, structur
 export const CrossLinkRestraintParams = {
     ...ComplexMeshParams,
     ...LinkCylinderParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'physical', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 1, 0, 20, 0.1),
 }
-export const DefaultCrossLinkRestraintProps = PD.getDefaultValues(CrossLinkRestraintParams)
-export type CrossLinkRestraintProps = typeof DefaultCrossLinkRestraintProps
+export type CrossLinkRestraintParams = typeof CrossLinkRestraintParams
 
-export function CrossLinkRestraintVisual(): ComplexVisual<CrossLinkRestraintProps> {
-    return ComplexMeshVisual<CrossLinkRestraintProps>({
-        defaultProps: DefaultCrossLinkRestraintProps,
+export function CrossLinkRestraintVisual(): ComplexVisual<CrossLinkRestraintParams> {
+    return ComplexMeshVisual<CrossLinkRestraintParams>({
+        defaultProps: PD.getDefaultValues(CrossLinkRestraintParams),
         createGeometry: createCrossLinkRestraintCylinderMesh,
         createLocationIterator: CrossLinkRestraintIterator,
         getLoci: getLinkLoci,
         mark: markLink,
-        setUpdateState: (state: VisualUpdateState, newProps: CrossLinkRestraintProps, currentProps: CrossLinkRestraintProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<CrossLinkRestraintParams>, currentProps: PD.DefaultValues<CrossLinkRestraintParams>) => {
             state.createGeometry = newProps.radialSegments !== currentProps.radialSegments
         }
     })

+ 9 - 13
src/mol-repr/structure/visual/element-point.ts

@@ -5,30 +5,26 @@
  */
 
 import { Unit, Structure } from 'mol-model/structure';
-import { UnitsVisual } from '../index';
+import { UnitsVisual } from '../representation';
 import { VisualUpdateState } from '../../util';
 import { getElementLoci, StructureElementIterator, markElement } from './util/element';
 import { Vec3 } from 'mol-math/linear-algebra';
-import { SizeThemeOptions, SizeThemeName } from 'mol-theme/size';
 import { UnitsPointsVisual, UnitsPointsParams } from '../units-visual';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Points } from 'mol-geo/geometry/points/points';
 import { PointsBuilder } from 'mol-geo/geometry/points/points-builder';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 export const ElementPointParams = {
     ...UnitsPointsParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 3, 0, 20, 0.1),
     pointSizeAttenuation: PD.Boolean('Point Size Attenuation', '', false),
 }
-export const DefaultElementPointProps = PD.getDefaultValues(ElementPointParams)
-export type ElementPointProps = typeof DefaultElementPointProps
+export type ElementPointParams = typeof ElementPointParams
 
 // TODO size
 
-export async function createElementPoint(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: ElementPointProps, points: Points) {
+export async function createElementPoint(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.DefaultValues<ElementPointParams>, points: Points) {
     const elements = unit.elements
     const n = elements.length
     const builder = PointsBuilder.create(n, n / 10, points)
@@ -47,14 +43,14 @@ export async function createElementPoint(ctx: VisualContext, unit: Unit, structu
     return builder.getPoints()
 }
 
-export function ElementPointVisual(): UnitsVisual<ElementPointProps> {
-    return UnitsPointsVisual<ElementPointProps>({
-        defaultProps: DefaultElementPointProps,
+export function ElementPointVisual(): UnitsVisual<ElementPointParams> {
+    return UnitsPointsVisual<ElementPointParams>({
+        defaultProps: PD.getDefaultValues(ElementPointParams),
         createGeometry: createElementPoint,
         createLocationIterator: StructureElementIterator.fromGroup,
         getLoci: getElementLoci,
         mark: markElement,
-        setUpdateState: (state: VisualUpdateState, newProps: ElementPointProps, currentProps: ElementPointProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<ElementPointParams>, currentProps: PD.DefaultValues<ElementPointParams>) => {
 
         }
     })

+ 8 - 10
src/mol-repr/structure/visual/element-sphere.ts

@@ -5,31 +5,29 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { UnitsVisual } from '../index';
+import { UnitsVisual } from '../representation';
 import { VisualUpdateState } from '../../util';
 import { createElementSphereMesh, markElement, getElementLoci, StructureElementIterator } from './util/element';
 import { UnitsMeshVisual, UnitsMeshParams } from '../units-visual';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
+import { BuiltInSizeThemeName, BuiltInSizeThemeOptions } from 'mol-theme/size';
 
 export const ElementSphereParams = {
     ...UnitsMeshParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'physical', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 0.2, 0, 10, 0.1),
+    sizeTheme: PD.Select<BuiltInSizeThemeName>('Size Theme', '', 'physical', BuiltInSizeThemeOptions),
     sizeFactor: PD.Numeric('Size Factor', '', 1, 0, 10, 0.1),
     detail: PD.Numeric('Sphere Detail', '', 0, 0, 3, 1),
 }
-export const DefaultElementSphereProps = PD.getDefaultValues(ElementSphereParams)
-export type ElementSphereProps = typeof DefaultElementSphereProps
+export type ElementSphereParams = typeof ElementSphereParams
 
-export function ElementSphereVisual(): UnitsVisual<ElementSphereProps> {
-    return UnitsMeshVisual<ElementSphereProps>({
-        defaultProps: DefaultElementSphereProps,
+export function ElementSphereVisual(): UnitsVisual<ElementSphereParams> {
+    return UnitsMeshVisual<ElementSphereParams>({
+        defaultProps: PD.getDefaultValues(ElementSphereParams),
         createGeometry: createElementSphereMesh,
         createLocationIterator: StructureElementIterator.fromGroup,
         getLoci: getElementLoci,
         mark: markElement,
-        setUpdateState: (state: VisualUpdateState, newProps: ElementSphereProps, currentProps: ElementSphereProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<ElementSphereParams>, currentProps: PD.DefaultValues<ElementSphereParams>) => {
             state.createGeometry = newProps.detail !== currentProps.detail
         }
     })

+ 8 - 12
src/mol-repr/structure/visual/gaussian-density-point.ts

@@ -5,29 +5,25 @@
  */
 
 import { Unit, Structure } from 'mol-model/structure';
-import { UnitsVisual } from '../index';
+import { UnitsVisual } from '../representation';
 import { VisualUpdateState } from '../../util';
 import { StructureElementIterator } from './util/element';
 import { EmptyLoci } from 'mol-model/loci';
 import { Vec3 } from 'mol-math/linear-algebra';
 import { UnitsPointsVisual, UnitsPointsParams } from '../units-visual';
-import { SizeThemeOptions, SizeThemeName } from 'mol-theme/size';
 import { GaussianDensityProps, GaussianDensityParams } from 'mol-model/structure/structure/unit/gaussian-density';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Points } from 'mol-geo/geometry/points/points';
 import { PointsBuilder } from 'mol-geo/geometry/points/points-builder';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 export const GaussianDensityPointParams = {
     ...UnitsPointsParams,
     ...GaussianDensityParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 1, 0, 20, 0.1),
     pointSizeAttenuation: PD.Boolean('Point Size Attenuation', '', false),
 }
-export const DefaultGaussianDensityPointProps = PD.getDefaultValues(GaussianDensityPointParams)
-export type GaussianDensityPointProps = typeof DefaultGaussianDensityPointProps
+export type GaussianDensityPointParams = typeof GaussianDensityPointParams
 
 export async function createGaussianDensityPoint(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, points?: Points) {
     const { transform, field: { space, data } } = await unit.computeGaussianDensity(props, ctx.runtime, ctx.webgl)
@@ -59,14 +55,14 @@ export async function createGaussianDensityPoint(ctx: VisualContext, unit: Unit,
     return builder.getPoints()
 }
 
-export function GaussianDensityPointVisual(): UnitsVisual<GaussianDensityPointProps> {
-    return UnitsPointsVisual<GaussianDensityPointProps>({
-        defaultProps: DefaultGaussianDensityPointProps,
+export function GaussianDensityPointVisual(): UnitsVisual<GaussianDensityPointParams> {
+    return UnitsPointsVisual<GaussianDensityPointParams>({
+        defaultProps: PD.getDefaultValues(GaussianDensityPointParams),
         createGeometry: createGaussianDensityPoint,
         createLocationIterator: StructureElementIterator.fromGroup,
         getLoci: () => EmptyLoci,
         mark: () => false,
-        setUpdateState: (state: VisualUpdateState, newProps: GaussianDensityPointProps, currentProps: GaussianDensityPointProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<GaussianDensityPointParams>, currentProps: PD.DefaultValues<GaussianDensityPointParams>) => {
             if (newProps.resolution !== currentProps.resolution) state.createGeometry = true
             if (newProps.radiusOffset !== currentProps.radiusOffset) state.createGeometry = true
             if (newProps.smoothness !== currentProps.smoothness) state.createGeometry = true

+ 8 - 9
src/mol-repr/structure/visual/gaussian-density-volume.ts

@@ -5,15 +5,15 @@
  */
 
 import { Unit, Structure } from 'mol-model/structure';
-import { UnitsVisual } from '../index';
+import { UnitsVisual } from '../representation';
 import { VisualUpdateState } from '../../util';
 import { UnitsDirectVolumeVisual, UnitsDirectVolumeParams } from '../units-visual';
 import { StructureElementIterator, getElementLoci, markElement } from './util/element';
 import { GaussianDensityProps, GaussianDensityParams, computeUnitGaussianDensityTexture } from 'mol-model/structure/structure/unit/gaussian-density';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { DirectVolume } from 'mol-geo/geometry/direct-volume/direct-volume';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 async function createGaussianDensityVolume(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, directVolume?: DirectVolume): Promise<DirectVolume> {
     const { runtime, webgl } = ctx
@@ -31,17 +31,16 @@ export const GaussianDensityVolumeParams = {
     ...UnitsDirectVolumeParams,
     ...GaussianDensityParams,
 }
-export const DefaultGaussianDensityVolumeProps = PD.getDefaultValues(GaussianDensityVolumeParams)
-export type GaussianDensityVolumeProps = typeof DefaultGaussianDensityVolumeProps
+export type GaussianDensityVolumeParams = typeof GaussianDensityVolumeParams
 
-export function GaussianDensityVolumeVisual(): UnitsVisual<GaussianDensityVolumeProps> {
-    return UnitsDirectVolumeVisual<GaussianDensityVolumeProps>({
-        defaultProps: DefaultGaussianDensityVolumeProps,
+export function GaussianDensityVolumeVisual(): UnitsVisual<GaussianDensityVolumeParams> {
+    return UnitsDirectVolumeVisual<GaussianDensityVolumeParams>({
+        defaultProps: PD.getDefaultValues(GaussianDensityVolumeParams),
         createGeometry: createGaussianDensityVolume,
         createLocationIterator: StructureElementIterator.fromGroup,
         getLoci: getElementLoci,
         mark: markElement,
-        setUpdateState: (state: VisualUpdateState, newProps: GaussianDensityVolumeProps, currentProps: GaussianDensityVolumeProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<GaussianDensityVolumeParams>, currentProps: PD.DefaultValues<GaussianDensityVolumeParams>) => {
             if (newProps.resolution !== currentProps.resolution) state.createGeometry = true
             if (newProps.radiusOffset !== currentProps.radiusOffset) state.createGeometry = true
             if (newProps.smoothness !== currentProps.smoothness) {

+ 8 - 9
src/mol-repr/structure/visual/gaussian-surface-mesh.ts

@@ -5,7 +5,7 @@
  */
 
 import { Unit, Structure } from 'mol-model/structure';
-import { UnitsVisual } from '../index';
+import { UnitsVisual } from '../representation';
 import { VisualUpdateState } from '../../util';
 import { UnitsMeshVisual, UnitsMeshParams } from '../units-visual';
 import { StructureElementIterator, getElementLoci, markElement } from './util/element';
@@ -13,8 +13,8 @@ import { GaussianDensityProps, GaussianDensityParams } from 'mol-model/structure
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { computeMarchingCubesMesh } from 'mol-geo/util/marching-cubes/algorithm';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
     const { smoothness } = props
@@ -38,17 +38,16 @@ export const GaussianSurfaceParams = {
     ...UnitsMeshParams,
     ...GaussianDensityParams,
 }
-export const DefaultGaussianSurfaceProps = PD.getDefaultValues(GaussianSurfaceParams)
-export type GaussianSurfaceProps = typeof DefaultGaussianSurfaceProps
+export type GaussianSurfaceParams = typeof GaussianSurfaceParams
 
-export function GaussianSurfaceVisual(): UnitsVisual<GaussianSurfaceProps> {
-    return UnitsMeshVisual<GaussianSurfaceProps>({
-        defaultProps: DefaultGaussianSurfaceProps,
+export function GaussianSurfaceVisual(): UnitsVisual<GaussianSurfaceParams> {
+    return UnitsMeshVisual<GaussianSurfaceParams>({
+        defaultProps: PD.getDefaultValues(GaussianSurfaceParams),
         createGeometry: createGaussianSurfaceMesh,
         createLocationIterator: StructureElementIterator.fromGroup,
         getLoci: getElementLoci,
         mark: markElement,
-        setUpdateState: (state: VisualUpdateState, newProps: GaussianSurfaceProps, currentProps: GaussianSurfaceProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<GaussianSurfaceParams>, currentProps: PD.DefaultValues<GaussianSurfaceParams>) => {
             if (newProps.resolution !== currentProps.resolution) state.createGeometry = true
             if (newProps.radiusOffset !== currentProps.radiusOffset) state.createGeometry = true
             if (newProps.smoothness !== currentProps.smoothness) state.createGeometry = true

+ 8 - 12
src/mol-repr/structure/visual/gaussian-surface-wireframe.ts

@@ -5,17 +5,16 @@
  */
 
 import { Unit, Structure } from 'mol-model/structure';
-import { UnitsVisual } from '../index';
+import { UnitsVisual } from '../representation';
 import { VisualUpdateState } from '../../util';
 import { UnitsLinesVisual, UnitsLinesParams } from '../units-visual';
 import { StructureElementIterator, getElementLoci, markElement } from './util/element';
 import { GaussianDensityProps, GaussianDensityParams } from 'mol-model/structure/structure/unit/gaussian-density';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { Lines } from 'mol-geo/geometry/lines/lines';
 import { computeMarchingCubesLines } from 'mol-geo/util/marching-cubes/algorithm';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, lines?: Lines): Promise<Lines> {
     const { smoothness } = props
@@ -36,21 +35,18 @@ async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure
 export const GaussianWireframeParams = {
     ...UnitsLinesParams,
     ...GaussianDensityParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 2, 0, 10, 0.1),
     lineSizeAttenuation: PD.Boolean('Line Size Attenuation', '', false),
 }
-export const DefaultGaussianWireframeProps = PD.getDefaultValues(GaussianWireframeParams)
-export type GaussianWireframeProps = typeof DefaultGaussianWireframeProps
+export type GaussianWireframeParams = typeof GaussianWireframeParams
 
-export function GaussianWireframeVisual(): UnitsVisual<GaussianWireframeProps> {
-    return UnitsLinesVisual<GaussianWireframeProps>({
-        defaultProps: DefaultGaussianWireframeProps,
+export function GaussianWireframeVisual(): UnitsVisual<GaussianWireframeParams> {
+    return UnitsLinesVisual<GaussianWireframeParams>({
+        defaultProps: PD.getDefaultValues(GaussianWireframeParams),
         createGeometry: createGaussianWireframe,
         createLocationIterator: StructureElementIterator.fromGroup,
         getLoci: getElementLoci,
         mark: markElement,
-        setUpdateState: (state: VisualUpdateState, newProps: GaussianWireframeProps, currentProps: GaussianWireframeProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<GaussianWireframeParams>, currentProps: PD.DefaultValues<GaussianWireframeParams>) => {
             if (newProps.resolution !== currentProps.resolution) state.createGeometry = true
             if (newProps.radiusOffset !== currentProps.radiusOffset) state.createGeometry = true
             if (newProps.smoothness !== currentProps.smoothness) state.createGeometry = true

+ 11 - 10
src/mol-repr/structure/visual/inter-unit-link-cylinder.ts

@@ -5,7 +5,7 @@
  */
 
 import { Link, Structure, StructureElement } from 'mol-model/structure';
-import { ComplexVisual } from '../index';
+import { ComplexVisual } from '../representation';
 import { VisualUpdateState } from '../../util';
 import { LinkCylinderProps, createLinkCylinderMesh, LinkIterator, LinkCylinderParams } from './util/link';
 import { Vec3 } from 'mol-math/linear-algebra';
@@ -16,8 +16,8 @@ import { BitFlags } from 'mol-util';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { PickingId } from 'mol-geo/geometry/picking';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 async function createInterUnitLinkCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: LinkCylinderProps, mesh?: Mesh) {
     const links = structure.links
@@ -53,18 +53,19 @@ export const InterUnitLinkParams = {
     ...ComplexMeshParams,
     ...LinkCylinderParams,
 }
-export const DefaultInterUnitLinkProps = PD.getDefaultValues(InterUnitLinkParams)
-export type InterUnitLinkProps = typeof DefaultInterUnitLinkProps
+export type InterUnitLinkParams = typeof InterUnitLinkParams
 
-export function InterUnitLinkVisual(): ComplexVisual<InterUnitLinkProps> {
-    return ComplexMeshVisual<InterUnitLinkProps>({
-        defaultProps: DefaultInterUnitLinkProps,
+export function InterUnitLinkVisual(): ComplexVisual<InterUnitLinkParams> {
+    return ComplexMeshVisual<InterUnitLinkParams>({
+        defaultProps: PD.getDefaultValues(InterUnitLinkParams),
         createGeometry: createInterUnitLinkCylinderMesh,
         createLocationIterator: LinkIterator.fromStructure,
         getLoci: getLinkLoci,
         mark: markLink,
-        setUpdateState: (state: VisualUpdateState, newProps: InterUnitLinkProps, currentProps: InterUnitLinkProps) => {
-            state.createGeometry = newProps.radialSegments !== currentProps.radialSegments
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<InterUnitLinkParams>, currentProps: PD.DefaultValues<InterUnitLinkParams>) => {
+            if (newProps.linkScale !== currentProps.linkScale) state.createGeometry = true
+            if (newProps.linkSpacing !== currentProps.linkSpacing) state.createGeometry = true
+            if (newProps.radialSegments !== currentProps.radialSegments) state.createGeometry = true
         }
     })
 }

+ 14 - 15
src/mol-repr/structure/visual/intra-unit-link-cylinder.ts

@@ -6,22 +6,21 @@
  */
 
 import { Unit, Link, StructureElement, Structure } from 'mol-model/structure';
-import { UnitsVisual } from '../index';
+import { UnitsVisual } from '../representation';
 import { VisualUpdateState } from '../../util';
 import { LinkCylinderProps, createLinkCylinderMesh, LinkIterator, LinkCylinderParams } from './util/link';
 import { Vec3 } from 'mol-math/linear-algebra';
 import { Loci, EmptyLoci } from 'mol-model/loci';
 import { UnitsMeshVisual, UnitsMeshParams, StructureGroup } from '../units-visual';
 import { Interval } from 'mol-data/int';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { BitFlags } from 'mol-util';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { PickingId } from 'mol-geo/geometry/picking';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
-async function createIntraUnitLinkCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: LinkCylinderProps, mesh?: Mesh) {
+async function createIntraUnitLinkCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.DefaultValues<IntraUnitLinkParams>, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
 
     const location = StructureElement.create(unit)
@@ -30,6 +29,7 @@ async function createIntraUnitLinkCylinderMesh(ctx: VisualContext, unit: Unit, s
     const links = unit.links
     const { edgeCount, a, b, edgeProps, offset } = links
     const { order: _order, flags: _flags } = edgeProps
+    const { sizeFactor } = props
 
     if (!edgeCount) return Mesh.createEmpty(mesh)
 
@@ -57,7 +57,7 @@ async function createIntraUnitLinkCylinderMesh(ctx: VisualContext, unit: Unit, s
         flags: (edgeIndex: number) => BitFlags.create(_flags[edgeIndex]),
         radius: (edgeIndex: number) => {
             location.element = elements[a[edgeIndex]]
-            return theme.size.size(location)
+            return theme.size.size(location) * sizeFactor
         }
     }
 
@@ -67,22 +67,21 @@ async function createIntraUnitLinkCylinderMesh(ctx: VisualContext, unit: Unit, s
 export const IntraUnitLinkParams = {
     ...UnitsMeshParams,
     ...LinkCylinderParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'physical', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 0.2, 0, 10, 0.1),
-    sizeFactor: PD.Numeric('Size Factor', '', 1, 0, 10, 0.1),
+    sizeFactor: PD.Numeric('Size Factor', '', 0.2, 0, 10, 0.01),
 }
-export const DefaultIntraUnitLinkProps = PD.getDefaultValues(IntraUnitLinkParams)
-export type IntraUnitLinkProps = typeof DefaultIntraUnitLinkProps
+export type IntraUnitLinkParams = typeof IntraUnitLinkParams
 
-export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> {
-    return UnitsMeshVisual<IntraUnitLinkProps>({
-        defaultProps: DefaultIntraUnitLinkProps,
+export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkParams> {
+    return UnitsMeshVisual<IntraUnitLinkParams>({
+        defaultProps: PD.getDefaultValues(IntraUnitLinkParams),
         createGeometry: createIntraUnitLinkCylinderMesh,
         createLocationIterator: LinkIterator.fromGroup,
         getLoci: getLinkLoci,
         mark: markLink,
         setUpdateState: (state: VisualUpdateState, newProps: LinkCylinderProps, currentProps: LinkCylinderProps) => {
-            state.createGeometry = newProps.radialSegments !== currentProps.radialSegments
+            if (newProps.linkScale !== currentProps.linkScale) state.createGeometry = true
+            if (newProps.linkSpacing !== currentProps.linkSpacing) state.createGeometry = true
+            if (newProps.radialSegments !== currentProps.radialSegments) state.createGeometry = true
         }
     })
 }

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

@@ -5,7 +5,7 @@
  */
 
 import { Unit, Structure } from 'mol-model/structure';
-import { UnitsVisual } from '../index';
+import { UnitsVisual } from '../representation';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { Segmentation } from 'mol-data/int';
 import { MoleculeType, isNucleic, isPurinBase, isPyrimidineBase } from 'mol-model/structure/model/types';
@@ -17,8 +17,8 @@ import { Box } from 'mol-geo/primitive/box';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
 import { addCylinder } from 'mol-geo/geometry/mesh/builder/cylinder';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 const p1 = Vec3.zero()
 const p2 = Vec3.zero()
@@ -34,10 +34,17 @@ const t = Mat4.identity()
 const sVec = Vec3.zero()
 const box = Box()
 
-// TODO define props, should be scalable
-async function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: {}, mesh?: Mesh) {
+export const NucleotideBlockMeshParams = {
+    sizeFactor: PD.Numeric('Size Factor', '', 0.2, 0, 10, 0.01),
+}
+export const DefaultNucleotideBlockMeshProps = PD.getDefaultValues(NucleotideBlockMeshParams)
+export type NucleotideBlockMeshProps = typeof DefaultNucleotideBlockMeshProps
+
+async function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: NucleotideBlockMeshProps, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
 
+    const { sizeFactor } = props
+
     // TODO better vertex count estimate
     const builder = MeshBuilder.create(256, 128, mesh)
 
@@ -64,7 +71,7 @@ async function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structu
                 const parentId = modifiedResidues.parentId.get(compId)
                 if (parentId !== undefined) compId = parentId
                 let idx1 = -1, idx2 = -1, idx3 = -1, idx4 = -1, idx5 = -1, idx6 = -1
-                let width = 4.5, height = 4.5, depth = 0.5
+                let width = 4.5, height = 4.5, depth = 2.5 * sizeFactor
 
                 if (isPurinBase(compId)) {
                     height = 4.5
@@ -87,7 +94,7 @@ async function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structu
                 if (idx5 !== -1 && idx6 !== -1) {
                     pos(idx5, p5); pos(idx6, p6)
                     builder.setGroup(i)
-                    addCylinder(builder, p5, p6, 1, { radiusTop: 0.2, radiusBottom: 0.2 })
+                    addCylinder(builder, p5, p6, 1, { radiusTop: 1 * sizeFactor, radiusBottom: 1 * sizeFactor })
                     if (idx1 !== -1 && idx2 !== -1 && idx3 !== -1 && idx4 !== -1) {
                         pos(idx1, p1); pos(idx2, p2); pos(idx3, p3); pos(idx4, p4);
                         Vec3.normalize(v12, Vec3.sub(v12, p2, p1))
@@ -113,14 +120,14 @@ async function createNucleotideBlockMesh(ctx: VisualContext, unit: Unit, structu
 }
 
 export const NucleotideBlockParams = {
-    ...UnitsMeshParams
+    ...UnitsMeshParams,
+    ...NucleotideBlockMeshParams
 }
-export const DefaultNucleotideBlockProps = PD.getDefaultValues(NucleotideBlockParams)
-export type NucleotideBlockProps = typeof DefaultNucleotideBlockProps
+export type NucleotideBlockParams = typeof NucleotideBlockParams
 
-export function NucleotideBlockVisual(): UnitsVisual<NucleotideBlockProps> {
-    return UnitsMeshVisual<NucleotideBlockProps>({
-        defaultProps: DefaultNucleotideBlockProps,
+export function NucleotideBlockVisual(): UnitsVisual<NucleotideBlockParams> {
+    return UnitsMeshVisual<NucleotideBlockParams>({
+        defaultProps: PD.getDefaultValues(NucleotideBlockParams),
         createGeometry: createNucleotideBlockMesh,
         createLocationIterator: NucleotideLocationIterator.fromGroup,
         getLoci: getNucleotideElementLoci,

+ 8 - 9
src/mol-repr/structure/visual/polymer-backbone-cylinder.ts

@@ -5,7 +5,7 @@
  */
 
 import { Unit, Structure } from 'mol-model/structure';
-import { UnitsVisual } from '../index';
+import { UnitsVisual } from '../representation';
 import { VisualUpdateState } from '../../util';
 import { PolymerBackboneIterator } from './util/polymer';
 import { getElementLoci, markElement, StructureElementIterator } from './util/element';
@@ -17,8 +17,8 @@ import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
 import { CylinderProps } from 'mol-geo/primitive/cylinder';
 import { addCylinder } from 'mol-geo/geometry/mesh/builder/cylinder';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 export const PolymerBackboneCylinderParams = {
     radialSegments: PD.Numeric('Radial Segments', '', 16, 3, 56, 1),
@@ -69,18 +69,17 @@ export const PolymerBackboneParams = {
     ...UnitsMeshParams,
     ...PolymerBackboneCylinderParams,
 }
-export const DefaultPolymerBackboneProps = PD.getDefaultValues(PolymerBackboneParams)
-export type PolymerBackboneProps = typeof DefaultPolymerBackboneProps
+export type PolymerBackboneParams = typeof PolymerBackboneParams
 
-export function PolymerBackboneVisual(): UnitsVisual<PolymerBackboneProps> {
-    return UnitsMeshVisual<PolymerBackboneProps>({
-        defaultProps: DefaultPolymerBackboneProps,
+export function PolymerBackboneVisual(): UnitsVisual<PolymerBackboneParams> {
+    return UnitsMeshVisual<PolymerBackboneParams>({
+        defaultProps: PD.getDefaultValues(PolymerBackboneParams),
         createGeometry: createPolymerBackboneCylinderMesh,
         // TODO create a specialized location iterator
         createLocationIterator: StructureElementIterator.fromGroup,
         getLoci: getElementLoci,
         mark: markElement,
-        setUpdateState: (state: VisualUpdateState, newProps: PolymerBackboneProps, currentProps: PolymerBackboneProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<PolymerBackboneParams>, currentProps: PD.DefaultValues<PolymerBackboneParams>) => {
             state.createGeometry = newProps.radialSegments !== currentProps.radialSegments
         }
     })

+ 11 - 12
src/mol-repr/structure/visual/polymer-direction-wedge.ts

@@ -5,18 +5,17 @@
  */
 
 import { Unit, Structure } from 'mol-model/structure';
-import { UnitsVisual } from '../index';
+import { UnitsVisual } from '../representation';
 import { PolymerTraceIterator, createCurveSegmentState, interpolateCurveSegment, PolymerLocationIterator, getPolymerElementLoci, markPolymerElement } from './util/polymer';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { SecondaryStructureType, isNucleic } from 'mol-model/structure/model/types';
 import { UnitsMeshVisual, UnitsMeshParams } from '../units-visual';
-import { SizeThemeName, SizeThemeOptions } from 'mol-theme/size';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Wedge } from 'mol-geo/primitive/wedge';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 const t = Mat4.identity()
 const sVec = Vec3.zero()
@@ -31,15 +30,16 @@ const heightFactor = 6
 const wedge = Wedge()
 
 export const PolymerDirectionWedgeParams = {
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'uniform', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 1, 0, 20, 0.1),
+    sizeFactor: PD.Numeric('Size Factor', '', 0.2, 0, 10, 0.01),
 }
 export const DefaultPolymerDirectionWedgeProps = PD.getDefaultValues(PolymerDirectionWedgeParams)
 export type PolymerDirectionWedgeProps = typeof DefaultPolymerDirectionWedgeProps
 
 async function createPolymerDirectionWedgeMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PolymerDirectionWedgeProps, mesh?: Mesh) {
     const polymerElementCount = unit.polymerElements.length
+
     if (!polymerElementCount) return Mesh.createEmpty(mesh)
+    const { sizeFactor } = props
 
     const vertexCount = polymerElementCount * 24
     const builder = MeshBuilder.create(vertexCount, vertexCount / 10, mesh)
@@ -62,7 +62,7 @@ async function createPolymerDirectionWedgeMesh(ctx: VisualContext, unit: Unit, s
         interpolateCurveSegment(state, v, tension, shift)
 
         if ((isSheet && !v.secStrucChange) || !isSheet) {
-            const size = theme.size.size(v.center)
+            const size = theme.size.size(v.center) * sizeFactor
             const depth = depthFactor * size
             const width = widthFactor * size
             const height = heightFactor * size
@@ -92,12 +92,11 @@ export const PolymerDirectionParams = {
     ...UnitsMeshParams,
     ...PolymerDirectionWedgeParams
 }
-export const DefaultPolymerDirectionProps = PD.getDefaultValues(PolymerDirectionParams)
-export type PolymerDirectionProps = typeof DefaultPolymerDirectionProps
+export type PolymerDirectionParams = typeof PolymerDirectionParams
 
-export function PolymerDirectionVisual(): UnitsVisual<PolymerDirectionProps> {
-    return UnitsMeshVisual<PolymerDirectionProps>({
-        defaultProps: DefaultPolymerDirectionProps,
+export function PolymerDirectionVisual(): UnitsVisual<PolymerDirectionParams> {
+    return UnitsMeshVisual<PolymerDirectionParams>({
+        defaultProps: PD.getDefaultValues(PolymerDirectionParams),
         createGeometry: createPolymerDirectionWedgeMesh,
         createLocationIterator: PolymerLocationIterator.fromGroup,
         getLoci: getPolymerElementLoci,

+ 12 - 16
src/mol-repr/structure/visual/polymer-gap-cylinder.ts

@@ -5,12 +5,11 @@
  */
 
 import { Unit, Structure } from 'mol-model/structure';
-import { UnitsVisual } from '../index';
+import { UnitsVisual } from '../representation';
 import { VisualUpdateState } from '../../util';
 import { PolymerGapIterator, PolymerGapLocationIterator, markPolymerGapElement, getPolymerGapElementLoci } from './util/polymer';
 import { Vec3 } from 'mol-math/linear-algebra';
 import { UnitsMeshVisual, UnitsMeshParams } from '../units-visual';
-import { SizeThemeOptions, SizeThemeName } from 'mol-theme/size';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { LinkCylinderParams } from './util/link';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
@@ -18,12 +17,13 @@ import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
 import { CylinderProps } from 'mol-geo/primitive/cylinder';
 import { addSphere } from 'mol-geo/geometry/mesh/builder/sphere';
 import { addFixedCountDashedCylinder } from 'mol-geo/geometry/mesh/builder/cylinder';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 const segmentCount = 10
 
 export const PolymerGapCylinderParams = {
+    sizeFactor: PD.Numeric('Size Factor', '', 0.2, 0, 10, 0.01),
     radialSegments: PD.Numeric('Radial Segments', '', 16, 3, 56, 1),
 }
 export const DefaultPolymerGapCylinderProps = PD.getDefaultValues(PolymerGapCylinderParams)
@@ -33,7 +33,7 @@ async function createPolymerGapCylinderMesh(ctx: VisualContext, unit: Unit, stru
     const polymerGapCount = unit.gapElements.length
     if (!polymerGapCount) return Mesh.createEmpty(mesh)
 
-    const { radialSegments } = props
+    const { sizeFactor, radialSegments } = props
 
     const vertexCountEstimate = segmentCount * radialSegments * 2 * polymerGapCount * 2
     const builder = MeshBuilder.create(vertexCountEstimate, vertexCountEstimate / 10, mesh)
@@ -57,11 +57,11 @@ async function createPolymerGapCylinderMesh(ctx: VisualContext, unit: Unit, stru
             pos(centerA.element, pA)
             pos(centerB.element, pB)
 
-            cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerA)
+            cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerA) * sizeFactor
             builder.setGroup(i)
             addFixedCountDashedCylinder(builder, pA, pB, 0.5, segmentCount, cylinderProps)
 
-            cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerB)
+            cylinderProps.radiusTop = cylinderProps.radiusBottom = theme.size.size(centerB) * sizeFactor
             builder.setGroup(i + 1)
             addFixedCountDashedCylinder(builder, pB, pA, 0.5, segmentCount, cylinderProps)
         }
@@ -78,9 +78,6 @@ async function createPolymerGapCylinderMesh(ctx: VisualContext, unit: Unit, stru
 export const InterUnitLinkParams = {
     ...UnitsMeshParams,
     ...LinkCylinderParams,
-    sizeTheme: PD.Select<SizeThemeName>('Size Theme', '', 'physical', SizeThemeOptions),
-    sizeValue: PD.Numeric('Size Value', '', 1, 0, 20, 0.1),
-    sizeFactor: PD.Numeric('Size Factor', '', 0.3, 0, 10, 0.1),
 }
 export const DefaultIntraUnitLinkProps = PD.getDefaultValues(InterUnitLinkParams)
 export type IntraUnitLinkProps = typeof DefaultIntraUnitLinkProps
@@ -89,17 +86,16 @@ export const PolymerGapParams = {
     ...UnitsMeshParams,
     ...PolymerGapCylinderParams
 }
-export const DefaultPolymerGapProps = PD.getDefaultValues(PolymerGapParams)
-export type PolymerGapProps = typeof DefaultPolymerGapProps
+export type PolymerGapParams = typeof PolymerGapParams
 
-export function PolymerGapVisual(): UnitsVisual<PolymerGapProps> {
-    return UnitsMeshVisual<PolymerGapProps>({
-        defaultProps: DefaultPolymerGapProps,
+export function PolymerGapVisual(): UnitsVisual<PolymerGapParams> {
+    return UnitsMeshVisual<PolymerGapParams>({
+        defaultProps: PD.getDefaultValues(PolymerGapParams),
         createGeometry: createPolymerGapCylinderMesh,
         createLocationIterator: PolymerGapLocationIterator.fromGroup,
         getLoci: getPolymerGapElementLoci,
         mark: markPolymerGapElement,
-        setUpdateState: (state: VisualUpdateState, newProps: PolymerGapProps, currentProps: PolymerGapProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<PolymerGapParams>, currentProps: PD.DefaultValues<PolymerGapParams>) => {
             state.createGeometry = newProps.radialSegments !== currentProps.radialSegments
         }
     })

+ 11 - 11
src/mol-repr/structure/visual/polymer-trace-mesh.ts

@@ -5,7 +5,7 @@
  */
 
 import { Unit, Structure } from 'mol-model/structure';
-import { UnitsVisual } from '../index';
+import { UnitsVisual } from '../representation';
 import { VisualUpdateState } from '../../util';
 import { PolymerTraceIterator, createCurveSegmentState, interpolateCurveSegment, PolymerLocationIterator, getPolymerElementLoci, markPolymerElement } from './util/polymer';
 import { SecondaryStructureType, isNucleic } from 'mol-model/structure/model/types';
@@ -15,10 +15,11 @@ import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
 import { addSheet } from 'mol-geo/geometry/mesh/builder/sheet';
 import { addTube } from 'mol-geo/geometry/mesh/builder/tube';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 
 export const PolymerTraceMeshParams = {
+    sizeFactor: PD.Numeric('Size Factor', '', 0.2, 0, 10, 0.01),
     linearSegments: PD.Numeric('Linear Segments', '', 8, 1, 48, 1),
     radialSegments: PD.Numeric('Radial Segments', '', 16, 3, 56, 1),
     aspectRatio: PD.Numeric('Aspect Ratio', '', 5, 0.1, 5, 0.1),
@@ -33,7 +34,7 @@ async function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure:
     const polymerElementCount = unit.polymerElements.length
 
     if (!polymerElementCount) return Mesh.createEmpty(mesh)
-    const { linearSegments, radialSegments, aspectRatio, arrowFactor } = props
+    const { sizeFactor, linearSegments, radialSegments, aspectRatio, arrowFactor } = props
 
     const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2
     const builder = MeshBuilder.create(vertexCount, vertexCount / 10, mesh)
@@ -56,7 +57,7 @@ async function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure:
 
         interpolateCurveSegment(state, v, tension, shift)
 
-        let width = theme.size.size(v.center)
+        let width = theme.size.size(v.center) * sizeFactor
         if (isCoarse) width *= aspectRatio / 2
 
         if (isSheet) {
@@ -89,17 +90,16 @@ export const PolymerTraceParams = {
     ...UnitsMeshParams,
     ...PolymerTraceMeshParams
 }
-export const DefaultPolymerTraceProps = PD.getDefaultValues(PolymerTraceParams)
-export type PolymerTraceProps = typeof DefaultPolymerTraceProps
+export type PolymerTraceParams = typeof PolymerTraceParams
 
-export function PolymerTraceVisual(): UnitsVisual<PolymerTraceProps> {
-    return UnitsMeshVisual<PolymerTraceProps>({
-        defaultProps: DefaultPolymerTraceProps,
+export function PolymerTraceVisual(): UnitsVisual<PolymerTraceParams> {
+    return UnitsMeshVisual<PolymerTraceParams>({
+        defaultProps: PD.getDefaultValues(PolymerTraceParams),
         createGeometry: createPolymerTraceMesh,
         createLocationIterator: PolymerLocationIterator.fromGroup,
         getLoci: getPolymerElementLoci,
         mark: markPolymerElement,
-        setUpdateState: (state: VisualUpdateState, newProps: PolymerTraceProps, currentProps: PolymerTraceProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<PolymerTraceParams>, currentProps: PD.DefaultValues<PolymerTraceParams>) => {
             state.createGeometry = (
                 newProps.linearSegments !== currentProps.linearSegments ||
                 newProps.radialSegments !== currentProps.radialSegments ||

+ 10 - 16
src/mol-repr/structure/visual/util/common.ts

@@ -5,17 +5,19 @@
  */
 
 import { Unit, Structure } from 'mol-model/structure';
-import { StructureProps } from '../../index';
 import { createMeshRenderObject, createPointsRenderObject, createLinesRenderObject, createDirectVolumeRenderObject } from 'mol-gl/render-object';
 import { Mat4 } from 'mol-math/linear-algebra';
 import { TransformData, createTransform, createIdentityTransform } from 'mol-geo/geometry/transform-data';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
-import { createRenderableState, Theme } from 'mol-geo/geometry/geometry';
+import { createRenderableState } from 'mol-geo/geometry/geometry';
 import { Points } from 'mol-geo/geometry/points/points';
 import { Lines } from 'mol-geo/geometry/lines/lines';
 import { DirectVolume } from 'mol-geo/geometry/direct-volume/direct-volume';
-import { VisualContext } from 'mol-repr';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { StructureMeshParams, StructurePointsParams, StructureLinesParams, StructureDirectVolumeParams } from 'mol-repr/structure/representation';
 
 export function createUnitsTransform({ units }: Unit.SymmetryGroup, transformData?: TransformData) {
     const unitCount = units.length
@@ -47,16 +49,14 @@ export function includesUnitKind(unitKinds: UnitKind[], unit: Unit) {
 
 // mesh
 
-type StructureMeshProps = Mesh.Props & StructureProps
-
-export async function createComplexMeshRenderObject(ctx: VisualContext, structure: Structure, mesh: Mesh, locationIt: LocationIterator, theme: Theme, props: StructureMeshProps) {
+export async function createComplexMeshRenderObject(ctx: VisualContext, structure: Structure, mesh: Mesh, locationIt: LocationIterator, theme: Theme, props: PD.DefaultValues<StructureMeshParams>) {
     const transform = createIdentityTransform()
     const values = await Mesh.createValues(ctx.runtime, mesh, transform, locationIt, theme, props)
     const state = createRenderableState(props)
     return createMeshRenderObject(values, state)
 }
 
-export async function createUnitsMeshRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, mesh: Mesh, locationIt: LocationIterator, theme: Theme, props: StructureMeshProps) {
+export async function createUnitsMeshRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, mesh: Mesh, locationIt: LocationIterator, theme: Theme, props: PD.DefaultValues<StructureMeshParams>) {
     const transform = createUnitsTransform(group)
     const values = await Mesh.createValues(ctx.runtime, mesh, transform, locationIt, theme, props)
     const state = createRenderableState(props)
@@ -65,9 +65,7 @@ export async function createUnitsMeshRenderObject(ctx: VisualContext, group: Uni
 
 // points
 
-type StructurePointsProps = Points.Props & StructureProps
-
-export async function createUnitsPointsRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, points: Points, locationIt: LocationIterator, theme: Theme, props: StructurePointsProps) {
+export async function createUnitsPointsRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, points: Points, locationIt: LocationIterator, theme: Theme, props: PD.DefaultValues<StructurePointsParams>) {
     const transform = createUnitsTransform(group)
     const values = await Points.createValues(ctx.runtime, points, transform, locationIt, theme, props)
     const state = createRenderableState(props)
@@ -76,9 +74,7 @@ export async function createUnitsPointsRenderObject(ctx: VisualContext, group: U
 
 // lines
 
-type StructureLinesProps = Lines.Props & StructureProps
-
-export async function createUnitsLinesRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, lines: Lines, locationIt: LocationIterator, theme: Theme, props: StructureLinesProps) {
+export async function createUnitsLinesRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, lines: Lines, locationIt: LocationIterator, theme: Theme, props: PD.DefaultValues<StructureLinesParams>) {
     const transform = createUnitsTransform(group)
     const values = await Lines.createValues(ctx.runtime, lines, transform, locationIt, theme, props)
     const state = createRenderableState(props)
@@ -87,9 +83,7 @@ export async function createUnitsLinesRenderObject(ctx: VisualContext, group: Un
 
 // direct-volume
 
-type StructureDirectVolumeProps = DirectVolume.Props & StructureProps
-
-export async function createUnitsDirectVolumeRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, directVolume: DirectVolume, locationIt: LocationIterator, theme: Theme, props: StructureDirectVolumeProps) {
+export async function createUnitsDirectVolumeRenderObject(ctx: VisualContext, group: Unit.SymmetryGroup, directVolume: DirectVolume, locationIt: LocationIterator, theme: Theme, props: PD.DefaultValues<StructureDirectVolumeParams>) {
     const transform = createUnitsTransform(group)
     const values = await DirectVolume.createValues(ctx.runtime, directVolume, transform, locationIt, theme, props)
     const state = createRenderableState(props)

+ 5 - 4
src/mol-repr/structure/visual/util/element.ts

@@ -14,16 +14,17 @@ import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
 import { addSphere } from 'mol-geo/geometry/mesh/builder/sphere';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
-import { VisualContext } from 'mol-repr';
-import { Theme } from 'mol-geo/geometry/geometry';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme } from 'mol-theme/theme';
 import { StructureGroup } from 'mol-repr/structure/units-visual';
 
 export interface ElementSphereMeshProps {
     detail: number,
+    sizeFactor: number
 }
 
 export async function createElementSphereMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: ElementSphereMeshProps, mesh?: Mesh) {
-    const { detail } = props
+    const { detail, sizeFactor } = props
 
     const { elements } = unit;
     const elementCount = elements.length;
@@ -40,7 +41,7 @@ export async function createElementSphereMesh(ctx: VisualContext, unit: Unit, st
         pos(elements[i], v)
 
         meshBuilder.setGroup(i)
-        addSphere(meshBuilder, v, theme.size.size(l), detail)
+        addSphere(meshBuilder, v, theme.size.size(l) * sizeFactor, detail)
 
         if (i % 10000 === 0 && ctx.runtime.shouldUpdate) {
             await ctx.runtime.update({ message: 'Sphere mesh', current: i, max: elementCount });

+ 1 - 2
src/mol-repr/structure/visual/util/link.ts

@@ -13,12 +13,11 @@ import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
 import { CylinderProps } from 'mol-geo/primitive/cylinder';
 import { addFixedCountDashedCylinder, addCylinder, addDoubleCylinder } from 'mol-geo/geometry/mesh/builder/cylinder';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
-import { VisualContext } from 'mol-repr';
+import { VisualContext } from 'mol-repr/representation';
 
 export const LinkCylinderParams = {
     linkScale: PD.Range('Link Scale', '', 0.4, 0, 1, 0.1),
     linkSpacing: PD.Range('Link Spacing', '', 1, 0, 2, 0.01),
-    linkRadius: PD.Range('Link Radius', '', 0.25, 0, 10, 0.05),
     radialSegments: PD.Numeric('Radial Segments', '', 16, 3, 56, 1),
 }
 export const DefaultLinkCylinderProps = PD.getDefaultValues(LinkCylinderParams)

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

@@ -7,8 +7,6 @@
 import { defaults } from 'mol-util';
 import { Structure } from 'mol-model/structure';
 import { VisualQuality } from 'mol-geo/geometry/geometry';
-import { SizeProps } from 'mol-geo/geometry/size-data';
-import { ColorProps } from 'mol-geo/geometry/color-data';
 
 export interface VisualUpdateState {
     updateTransform: boolean
@@ -33,24 +31,6 @@ export namespace VisualUpdateState {
     }
 }
 
-export function sizeChanged(oldProps: SizeProps, newProps: SizeProps) {
-    return (
-        oldProps.sizeTheme !== newProps.sizeTheme ||
-        oldProps.sizeValue !== newProps.sizeValue ||
-        oldProps.sizeFactor !== newProps.sizeFactor
-    )
-}
-
-export function colorChanged(oldProps: ColorProps, newProps: ColorProps) {
-    return (
-        oldProps.colorTheme !== newProps.colorTheme ||
-        oldProps.colorValue !== newProps.colorValue ||
-        oldProps.colorDomain !== newProps.colorDomain ||
-        oldProps.colorList !== newProps.colorList ||
-        oldProps.colorMap !== newProps.colorMap
-    )
-}
-
 //
 
 export interface QualityProps {

+ 17 - 67
src/mol-repr/volume/direct-volume.ts

@@ -6,9 +6,9 @@
 
 import { VolumeData } from 'mol-model/volume'
 import { RuntimeContext } from 'mol-task'
-import { VolumeVisual, VolumeRepresentation } from './index';
+import { VolumeVisual, VolumeRepresentation } from './representation';
 import { createDirectVolumeRenderObject } from 'mol-gl/render-object';
-import { Loci, EmptyLoci } from 'mol-model/loci';
+import { EmptyLoci } from 'mol-model/loci';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { Box3D } from 'mol-math/geometry';
@@ -17,11 +17,10 @@ import { createTexture } from 'mol-gl/webgl/texture';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
 import { DirectVolume } from 'mol-geo/geometry/direct-volume/direct-volume';
-import { Geometry, createRenderableState, Theme } from 'mol-geo/geometry/geometry';
-import { PickingId } from 'mol-geo/geometry/picking';
-import { MarkerAction } from 'mol-geo/geometry/marker-data';
+import { Geometry, createRenderableState } from 'mol-geo/geometry/geometry';
 import { VisualUpdateState } from 'mol-repr/util';
-import { VisualContext, RepresentationContext } from 'mol-repr';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme, ThemeRegistryContext } from 'mol-theme/theme';
 
 function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
     const bbox = Box3D.empty()
@@ -113,8 +112,6 @@ function createVolumeTexture3d(volume: VolumeData) {
     const array = new Uint8Array(width * height * depth * 4)
     const textureVolume = { array, width, height, depth }
 
-    console.log('stats', stats)
-
     let i = 0
     for (let z = 0; z < depth; ++z) {
         for (let y = 0; y < height; ++y) {
@@ -123,36 +120,11 @@ function createVolumeTexture3d(volume: VolumeData) {
                     console.log(get(data, x, y, z), ((get(data, x, y, z) - stats.min) / (stats.max - stats.min)) * 255)
                 }
                 array[i + 3] = ((get(data, x, y, z) - stats.min) / (stats.max - stats.min)) * 255
-                // array[i + 3] = ((get(data, x, z, y) - stats.min) / (stats.max - stats.min)) * 255
-                // array[i + 3] = ((get(data, y, x, z) - stats.min) / (stats.max - stats.min)) * 255
-                // array[i + 3] = ((get(data, y, z, x) - stats.min) / (stats.max - stats.min)) * 255
-                // array[i + 3] = ((get(data, z, y, x) - stats.min) / (stats.max - stats.min)) * 255
-                // array[i + 3] = ((get(data, z, x, y) - stats.min) / (stats.max - stats.min)) * 255
                 i += 4
             }
         }
     }
 
-    // let i = 0
-    // for (let z = 0; z < depth; ++z) {
-    //     for (let x = 0; x < width; ++x) {
-    //         for (let y = 0; y < height; ++y) {
-    //             array[i + 3] = ((get(data, x, y, z) - stats.min) / (stats.max - stats.min)) * 255
-    //             i += 4
-    //         }
-    //     }
-    // }
-
-    // let i = 0
-    // for (let x = 0; x < width; ++x) {
-    //     for (let y = 0; y < height; ++y) {
-    //         for (let z = 0; z < depth; ++z) {
-    //             array[i + 3] = ((get(data, x, y, z) - stats.min) / (stats.max - stats.min)) * 255
-    //             i += 4
-    //         }
-    //     }
-    // }
-
     return textureVolume
 }
 
@@ -171,7 +143,7 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, v
 
 //
 
-export async function createDirectVolume(ctx: VisualContext, volume: VolumeData, props: DirectVolumeProps, directVolume?: DirectVolume) {
+export async function createDirectVolume(ctx: VisualContext, volume: VolumeData, props: PD.DefaultValues<DirectVolumeParams>, directVolume?: DirectVolume) {
     const { runtime, webgl } = ctx
     if (webgl === undefined) throw new Error('DirectVolumeVisual requires `webgl` in props')
 
@@ -187,18 +159,20 @@ export const DirectVolumeParams = {
     ...Geometry.Params,
     ...DirectVolume.Params
 }
-export const DefaultDirectVolumeProps = PD.getDefaultValues(DirectVolumeParams)
-export type DirectVolumeProps = typeof DefaultDirectVolumeProps
+export type DirectVolumeParams = typeof DirectVolumeParams
+export function getDirectVolumeParams(ctx: ThemeRegistryContext, volume: VolumeData) {
+    return PD.clone(DirectVolumeParams)
+}
 
-export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> {
-    return VolumeVisual<DirectVolumeProps>({
-        defaultProps: DefaultDirectVolumeProps,
+export function DirectVolumeVisual(): VolumeVisual<DirectVolumeParams> {
+    return VolumeVisual<DirectVolumeParams>({
+        defaultProps: PD.getDefaultValues(DirectVolumeParams),
         createGeometry: createDirectVolume,
         getLoci: () => EmptyLoci,
         mark: () => false,
-        setUpdateState: (state: VisualUpdateState, newProps: DirectVolumeProps, currentProps: DirectVolumeProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<DirectVolumeParams>, currentProps: PD.DefaultValues<DirectVolumeParams>) => {
         },
-        createRenderObject: async (ctx: VisualContext, geometry: DirectVolume, locationIt: LocationIterator, theme: Theme, props: DirectVolumeProps) => {
+        createRenderObject: async (ctx: VisualContext, geometry: DirectVolume, locationIt: LocationIterator, theme: Theme, props: PD.DefaultValues<DirectVolumeParams>) => {
             const transform = createIdentityTransform()
             const values = await DirectVolume.createValues(ctx.runtime, geometry, transform, locationIt, theme, props)
             const state = createRenderableState(props)
@@ -208,30 +182,6 @@ export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> {
     })
 }
 
-export function DirectVolumeRepresentation(): VolumeRepresentation<DirectVolumeProps> {
-    let currentProps: DirectVolumeProps
-    const volumeRepr = VolumeRepresentation(DirectVolumeVisual)
-    return {
-        label: 'Direct Volume',
-        params: DirectVolumeParams,
-        get renderObjects() {
-            return [ ...volumeRepr.renderObjects ]
-        },
-        get props() {
-            return { ...volumeRepr.props }
-        },
-        createOrUpdate: (ctx: RepresentationContext, props: Partial<DirectVolumeProps> = {}, volume?: VolumeData) => {
-            currentProps = Object.assign({}, DefaultDirectVolumeProps, currentProps, props)
-            return volumeRepr.createOrUpdate(ctx, currentProps, volume)
-        },
-        getLoci: (pickingId: PickingId) => {
-            return volumeRepr.getLoci(pickingId)
-        },
-        mark: (loci: Loci, action: MarkerAction) => {
-            return volumeRepr.mark(loci, action)
-        },
-        destroy() {
-            volumeRepr.destroy()
-        }
-    }
+export function DirectVolumeRepresentation(): VolumeRepresentation<DirectVolumeParams> {
+    return VolumeRepresentation('Direct Volume', getDirectVolumeParams, DirectVolumeVisual)
 }

+ 16 - 39
src/mol-repr/volume/isosurface-mesh.ts

@@ -6,19 +6,18 @@
  */
 
 import { VolumeData } from 'mol-model/volume'
-import { VolumeVisual, VolumeRepresentation } from './index';
+import { VolumeVisual, VolumeRepresentation } from './representation';
 import { createMeshRenderObject } from 'mol-gl/render-object';
-import { Loci, EmptyLoci } from 'mol-model/loci';
+import { EmptyLoci } from 'mol-model/loci';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Mesh } from 'mol-geo/geometry/mesh/mesh';
 import { computeMarchingCubesMesh } from 'mol-geo/util/marching-cubes/algorithm';
 import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
-import { createRenderableState, Theme } from 'mol-geo/geometry/geometry';
-import { PickingId } from 'mol-geo/geometry/picking';
-import { MarkerAction } from 'mol-geo/geometry/marker-data';
+import { createRenderableState } from 'mol-geo/geometry/geometry';
 import { VisualUpdateState } from 'mol-repr/util';
-import { RepresentationContext, VisualContext } from 'mol-repr';
+import { VisualContext } from 'mol-repr/representation';
+import { Theme, ThemeRegistryContext } from 'mol-theme/theme';
 
 interface VolumeIsosurfaceProps {
     isoValueAbsolute: number
@@ -45,19 +44,21 @@ export const IsosurfaceParams = {
     isoValueAbsolute: PD.Range('Iso Value Absolute', '', 0.22, -1, 1, 0.01),
     isoValueRelative: PD.Range('Iso Value Relative', '', 2, -10, 10, 0.1),
 }
-export const DefaultIsosurfaceProps = PD.getDefaultValues(IsosurfaceParams)
-export type IsosurfaceProps = typeof DefaultIsosurfaceProps
+export type IsosurfaceParams = typeof IsosurfaceParams
+export function getIsosurfaceParams(ctx: ThemeRegistryContext, volume: VolumeData) {
+    return PD.clone(IsosurfaceParams)
+}
 
-export function IsosurfaceVisual(): VolumeVisual<IsosurfaceProps> {
-    return VolumeVisual<IsosurfaceProps>({
-        defaultProps: DefaultIsosurfaceProps,
+export function IsosurfaceVisual(): VolumeVisual<IsosurfaceParams> {
+    return VolumeVisual<IsosurfaceParams>({
+        defaultProps: PD.getDefaultValues(IsosurfaceParams),
         createGeometry: createVolumeIsosurface,
         getLoci: () => EmptyLoci,
         mark: () => false,
-        setUpdateState: (state: VisualUpdateState, newProps: IsosurfaceProps, currentProps: IsosurfaceProps) => {
+        setUpdateState: (state: VisualUpdateState, newProps: PD.DefaultValues<IsosurfaceParams>, currentProps: PD.DefaultValues<IsosurfaceParams>) => {
             if (newProps.isoValueAbsolute !== currentProps.isoValueAbsolute) state.createGeometry = true
         },
-        createRenderObject: async (ctx: VisualContext, geometry: Mesh, locationIt: LocationIterator, theme: Theme, props: IsosurfaceProps) => {
+        createRenderObject: async (ctx: VisualContext, geometry: Mesh, locationIt: LocationIterator, theme: Theme, props: PD.DefaultValues<IsosurfaceParams>) => {
             const transform = createIdentityTransform()
             const values = await Mesh.createValues(ctx.runtime, geometry, transform, locationIt, theme, props)
             const state = createRenderableState(props)
@@ -67,30 +68,6 @@ export function IsosurfaceVisual(): VolumeVisual<IsosurfaceProps> {
     })
 }
 
-export function IsosurfaceRepresentation(): VolumeRepresentation<IsosurfaceProps> {
-    let currentProps: IsosurfaceProps
-    const volumeRepr = VolumeRepresentation(IsosurfaceVisual)
-    return {
-        label: 'Isosurface',
-        params: IsosurfaceParams,
-        get renderObjects() {
-            return [ ...volumeRepr.renderObjects ]
-        },
-        get props() {
-            return { ...volumeRepr.props }
-        },
-        createOrUpdate: (ctx: RepresentationContext, props: Partial<IsosurfaceProps> = {}, volume?: VolumeData) => {
-            currentProps = Object.assign({}, DefaultIsosurfaceProps, currentProps, props)
-            return volumeRepr.createOrUpdate(ctx, currentProps, volume)
-        },
-        getLoci: (pickingId: PickingId) => {
-            return volumeRepr.getLoci(pickingId)
-        },
-        mark: (loci: Loci, action: MarkerAction) => {
-            return volumeRepr.mark(loci, action)
-        },
-        destroy() {
-            volumeRepr.destroy()
-        }
-    }
+export function IsosurfaceRepresentation(): VolumeRepresentation<IsosurfaceParams> {
+    return VolumeRepresentation('Isosurface', getIsosurfaceParams, IsosurfaceVisual)
 }

+ 25 - 0
src/mol-repr/volume/registry.ts

@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { RepresentationProvider, RepresentationRegistry } from '../representation';
+import { VolumeData } from 'mol-model/volume';
+
+export class VolumeRepresentationRegistry extends RepresentationRegistry<VolumeData> {
+    constructor() {
+        super()
+        Object.keys(BuiltInVolumeRepresentations).forEach(name => {
+            const p = (BuiltInVolumeRepresentations as { [k: string]: RepresentationProvider<VolumeData, any> })[name]
+            this.add(name, p.factory, p.getParams)
+        })
+    }
+}
+
+export const BuiltInVolumeRepresentations = {
+    // TODO
+}
+export type BuiltInVolumeRepresentationsName = keyof typeof BuiltInVolumeRepresentations
+export const BuiltInVolumeRepresentationsNames = Object.keys(BuiltInVolumeRepresentations)
+export const BuiltInVolumeRepresentationsOptions = BuiltInVolumeRepresentationsNames.map(n => [n, n] as [BuiltInVolumeRepresentationsName, string])

+ 63 - 43
src/mol-repr/volume/index.ts → src/mol-repr/volume/representation.ts

@@ -5,10 +5,10 @@
  */
 
 import { Task } from 'mol-task'
-import { RepresentationProps, Representation, Visual, RepresentationContext, VisualContext } from '..';
+import { Representation, Visual, RepresentationContext, VisualContext, RepresentationProvider, RepresentationParamsGetter } from '../representation';
 import { VolumeData, VolumeIsoValue } from 'mol-model/volume';
 import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci';
-import { Geometry, updateRenderableState, Theme, createTheme } from 'mol-geo/geometry/geometry';
+import { Geometry, updateRenderableState } from 'mol-geo/geometry/geometry';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { MarkerAction, applyMarkerAction } from 'mol-geo/geometry/marker-data';
@@ -19,36 +19,38 @@ import { LocationIterator } from 'mol-geo/util/location-iterator';
 import { NullLocation } from 'mol-model/location';
 import { VisualUpdateState } from 'mol-repr/util';
 import { ValueCell } from 'mol-util';
+import { ThemeProps, Theme, createTheme } from 'mol-theme/theme';
+import { BehaviorSubject } from 'rxjs';
 
-export interface VolumeVisual<P extends RepresentationProps = {}> extends Visual<VolumeData, P> { }
+export interface VolumeVisual<P extends VolumeParams> extends Visual<VolumeData, P> { }
 
 type VolumeRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject
 
-interface VolumeVisualBuilder<P extends VolumeProps, G extends Geometry> {
-    defaultProps: P
-    createGeometry(ctx: VisualContext, volumeData: VolumeData, props: P, geometry?: G): Promise<G>
+interface VolumeVisualBuilder<P extends VolumeParams, G extends Geometry> {
+    defaultProps: PD.DefaultValues<P>
+    createGeometry(ctx: VisualContext, volumeData: VolumeData, props: PD.DefaultValues<P>, geometry?: G): Promise<G>
     getLoci(pickingId: PickingId, id: number): Loci
     mark(loci: Loci, apply: (interval: Interval) => boolean): boolean
-    setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
+    setUpdateState(state: VisualUpdateState, newProps: PD.DefaultValues<P>, currentProps: PD.DefaultValues<P>): void
 }
 
-interface VolumeVisualGeometryBuilder<P extends VolumeProps, G extends Geometry> extends VolumeVisualBuilder<P, G> {
-    createRenderObject(ctx: VisualContext, geometry: G, locationIt: LocationIterator, theme: Theme, currentProps: P): Promise<VolumeRenderObject>
-    updateValues(values: RenderableValues, newProps: P): void
+interface VolumeVisualGeometryBuilder<P extends VolumeParams, G extends Geometry> extends VolumeVisualBuilder<P, G> {
+    createRenderObject(ctx: VisualContext, geometry: G, locationIt: LocationIterator, theme: Theme, currentProps: PD.DefaultValues<P>): Promise<VolumeRenderObject>
+    updateValues(values: RenderableValues, newProps: PD.DefaultValues<P>): void
 }
 
-export function VolumeVisual<P extends VolumeProps>(builder: VolumeVisualGeometryBuilder<P, Geometry>) {
+export function VolumeVisual<P extends VolumeParams>(builder: VolumeVisualGeometryBuilder<P, Geometry>) {
     const { defaultProps, createGeometry, getLoci, mark, setUpdateState } = builder
     const { createRenderObject, updateValues } = builder
     const updateState = VisualUpdateState.create()
 
-    let currentProps: P
+    let currentProps: PD.DefaultValues<P>
     let renderObject: VolumeRenderObject | undefined
     let currentVolume: VolumeData
     let geometry: Geometry
     let locationIt: LocationIterator
 
-    async function create(ctx: VisualContext, volume: VolumeData, theme: Theme, props: Partial<VolumeProps> = {}) {
+    async function create(ctx: VisualContext, volume: VolumeData, theme: Theme, props: Partial<PD.DefaultValues<P>> = {}) {
         currentProps = Object.assign({}, defaultProps, props)
         if (props.isoValueRelative) {
             currentProps.isoValueAbsolute = VolumeIsoValue.calcAbsolute(currentVolume.dataStats, props.isoValueRelative)
@@ -60,7 +62,7 @@ export function VolumeVisual<P extends VolumeProps>(builder: VolumeVisualGeometr
         renderObject = await createRenderObject(ctx, geometry, locationIt, theme, currentProps)
     }
 
-    async function update(ctx: VisualContext, theme: Theme, props: Partial<VolumeProps> = {}) {
+    async function update(ctx: VisualContext, theme: Theme, props: Partial<PD.DefaultValues<P>> = {}) {
         if (!renderObject) return
         const newProps = Object.assign({}, currentProps, props)
 
@@ -85,7 +87,7 @@ export function VolumeVisual<P extends VolumeProps>(builder: VolumeVisualGeometr
 
     return {
         get renderObject () { return renderObject },
-        async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<VolumeProps> = {}, volume?: VolumeData) {
+        async createOrUpdate(ctx: VisualContext, theme: Theme, props: Partial<PD.DefaultValues<P>> = {}, volume?: VolumeData) {
             if (!volume && !currentVolume) {
                 throw new Error('missing volume')
             } else if (volume && (!currentVolume || !renderObject)) {
@@ -132,63 +134,81 @@ export function VolumeVisual<P extends VolumeProps>(builder: VolumeVisualGeometr
     }
 }
 
-export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> { }
+export interface VolumeRepresentation<P extends VolumeParams> extends Representation<VolumeData, P> { }
+
+export type VolumeRepresentationProvider<P extends VolumeParams> = RepresentationProvider<VolumeData, P>
+
+//
 
 export const VolumeParams = {
     ...Geometry.Params,
     isoValueAbsolute: PD.Range('Iso Value Absolute', '', 0.22, -1, 1, 0.01),
     isoValueRelative: PD.Range('Iso Value Relative', '', 2, -10, 10, 0.1),
 }
-export const DefaultVolumeProps = PD.getDefaultValues(VolumeParams)
-export type VolumeProps = typeof DefaultVolumeProps
+export type VolumeParams = typeof VolumeParams
+
+export function VolumeRepresentation<P extends VolumeParams>(label: string, getParams: RepresentationParamsGetter<VolumeData, P>, visualCtor: (volume: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> {
+    const updated = new BehaviorSubject(0)
+    let visual: VolumeVisual<P>
 
-export function VolumeRepresentation<P extends VolumeProps>(visualCtor: (volumeData: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> {
-    let visual: VolumeVisual<any>
-    let _props: P
+    let _volume: VolumeData
+    let _props: PD.DefaultValues<P>
+    let _params: P
     let _theme: Theme
     let busy = false
 
-    function createOrUpdate(ctx: RepresentationContext, props: Partial<P> = {}, volumeData?: VolumeData) {
-        _props = Object.assign({}, DefaultVolumeProps, _props, props)
-        _theme = createTheme(_props)
+    function createOrUpdate(ctx: RepresentationContext, props: Partial<PD.DefaultValues<P>> = {}, themeProps: ThemeProps = {}, volume?: VolumeData) {
+        if (volume && volume !== _volume) {
+            _params = getParams(ctx, volume)
+            _volume = volume
+            if (!_props) _props = PD.getDefaultValues(_params)
+        }
+        _props = Object.assign({}, _props, props)
+        _theme = createTheme(ctx, _props, themeProps, {}, _theme)
 
         return Task.create('VolumeRepresentation.create', async runtime => {
             // TODO queue it somehow
             if (busy) return
 
-            if (!visual && !volumeData) {
-                throw new Error('volumeData missing')
-            } else if (volumeData && !visual) {
+            if (!visual && !volume) {
+                throw new Error('volume data missing')
+            } else if (volume && !visual) {
                 busy = true
-                visual = visualCtor(volumeData)
-                await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, volumeData)
+                visual = visualCtor(volume)
+                await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, volume)
                 busy = false
             } else {
                 busy = true
-                await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, volumeData)
+                await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, volume)
                 busy = false
             }
+            updated.next(updated.getValue() + 1)
         });
     }
 
+    function getLoci(pickingId: PickingId) {
+        return visual ? visual.getLoci(pickingId) : EmptyLoci
+    }
+
+    function mark(loci: Loci, action: MarkerAction) {
+        return visual ? visual.mark(loci, action) : false
+    }
+
+    function destroy() {
+        if (visual) visual.destroy()
+    }
+
     return {
-        label: 'Volume',
-        params: VolumeParams,
+        label,
         get renderObjects() {
             return visual && visual.renderObject ? [ visual.renderObject ] : []
         },
         get props () { return _props },
+        get params() { return _params },
+        get updated() { return updated },
         createOrUpdate,
-        getLoci(pickingId: PickingId) {
-            // TODO
-            return EmptyLoci
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            // TODO
-            return false
-        },
-        destroy() {
-            // TODO
-        }
+        getLoci,
+        mark,
+        destroy
     }
 }

+ 82 - 107
src/mol-theme/color.ts

@@ -4,26 +4,25 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Color, ColorMap } from 'mol-util/color';
-import { Structure } from 'mol-model/structure';
+import { Color } from 'mol-util/color';
 import { Location } from 'mol-model/location';
 import { ColorType } from 'mol-geo/geometry/color-data';
-
-import { ElementIndexColorTheme } from './color/element-index';
-import { CarbohydrateSymbolColorTheme } from './color/carbohydrate-symbol';
-import { ChainIdColorTheme } from './color/chain-id';
-import { ElementSymbolColorTheme } from './color/element-symbol';
-import { UnitIndexColorTheme } from './color/unit-index';
-import { UniformColorTheme } from './color/uniform';
-import { CrossLinkColorTheme } from './color/cross-link';
-import { ShapeGroupColorTheme } from './color/shape-group';
-import { CustomColorTheme } from './color/custom';
-import { ResidueNameColorTheme } from './color/residue-name';
-import { SequenceIdColorTheme } from './color/sequence-id';
-import { SecondaryStructureColorTheme } from './color/secondary-structure';
-import { MoleculeTypeColorTheme } from './color/molecule-type';
-import { PolymerIndexColorTheme } from './color/polymer-index';
-import { ColorMatplotlib, ColorBrewer, ColorOther } from 'mol-util/color/tables';
+import { CarbohydrateSymbolColorThemeProvider } from './color/carbohydrate-symbol';
+import { UniformColorTheme, UniformColorThemeProvider } from './color/uniform';
+import { deepEqual } from 'mol-util';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { ThemeDataContext } from 'mol-theme/theme';
+import { ChainIdColorThemeProvider } from './color/chain-id';
+import { CrossLinkColorThemeProvider } from './color/cross-link';
+import { ElementIndexColorThemeProvider } from './color/element-index';
+import { ElementSymbolColorThemeProvider } from './color/element-symbol';
+import { MoleculeTypeColorThemeProvider } from './color/molecule-type';
+import { PolymerIndexColorThemeProvider } from './color/polymer-index';
+import { ResidueNameColorThemeProvider } from './color/residue-name';
+import { SecondaryStructureColorThemeProvider } from './color/secondary-structure';
+import { SequenceIdColorThemeProvider } from './color/sequence-id';
+import { ShapeGroupColorThemeProvider } from './color/shape-group';
+import { UnitIndexColorThemeProvider } from './color/unit-index';
 
 export type LocationColor = (location: Location, isSecondary: boolean) => Color
 
@@ -37,29 +36,6 @@ export function ScaleLegend(minLabel: string, maxLabel: string, colors: Color[])
     return { kind: 'scale-legend', minLabel, maxLabel, colors }
 }
 
-export type ColorScaleName = (
-    'default' |
-    keyof typeof ColorBrewer | keyof typeof ColorMatplotlib | keyof typeof ColorOther
-)
-export const ColorScaleNames = [
-    'default',
-    ...Object.keys(ColorBrewer), ...Object.keys(ColorMatplotlib), ...Object.keys(ColorOther)
-]
-export const ColorScaleOptions = ColorScaleNames.map(n => [n, n] as [ColorScaleName, string])
-
-export function getColorScaleFromName(name: string) {
-    if (name === 'default') {
-        return
-    } else if (name in ColorBrewer) {
-        return ColorBrewer[name as keyof typeof ColorBrewer]
-    } else if (name in ColorMatplotlib) {
-        return ColorMatplotlib[name as keyof typeof ColorMatplotlib]
-    } else if (name in ColorOther) {
-        return ColorOther[name as keyof typeof ColorOther]
-    }
-    console.warn(`unknwon color list named '${name}'`)
-}
-
 export interface TableLegend {
     kind: 'table-legend'
     table: [ string, Color ][]
@@ -68,77 +44,76 @@ export function TableLegend(table: [ string, Color ][]): TableLegend {
     return { kind: 'table-legend', table }
 }
 
-export interface ColorThemeFeatures {
-    /** Does allow providing a structure object */
-    structure?: boolean
-    /** Does allow providing a volume object */
-    volume?: boolean
-    /** Does allow providing a list of colors (for creating a scale) */
-    list?: boolean
-    /** Does allow providing a map of colors */
-    map?: boolean
-    /** Does allow providing the boundaries for the scale */
-    domain?: boolean
-    /** Does allow providing a single/special color value */
-    value?: boolean
-}
+export type ColorThemeProps = { [k: string]: any }
 
-export interface ColorTheme {
-    features: ColorThemeFeatures
-    granularity: ColorType
-    color: LocationColor
-    description?: string
-    legend?: ScaleLegend | TableLegend
+export { ColorTheme }
+interface ColorTheme<P extends ColorThemeProps = {}> {
+    readonly granularity: ColorType
+    readonly color: LocationColor
+    readonly props: Readonly<P>
+    readonly description?: string
+    readonly legend?: Readonly<ScaleLegend | TableLegend>
 }
+namespace ColorTheme {
+    export type Props = { [k: string]: any }
+    export const Empty = UniformColorTheme({}, { value: Color(0xCCCCCC) })
 
-export function ColorTheme(props: ColorThemeProps): ColorTheme {
-    switch (props.name) {
-        case 'carbohydrate-symbol': return CarbohydrateSymbolColorTheme(props)
-        case 'chain-id': return ChainIdColorTheme(props)
-        case 'cross-link': return CrossLinkColorTheme(props)
-        case 'custom': return CustomColorTheme(props)
-        case 'element-index': return ElementIndexColorTheme(props)
-        case 'element-symbol': return ElementSymbolColorTheme(props)
-        case 'molecule-type': return MoleculeTypeColorTheme(props)
-        case 'polymer-index': return PolymerIndexColorTheme(props)
-        case 'residue-name': return ResidueNameColorTheme(props)
-        case 'secondary-structure': return SecondaryStructureColorTheme(props)
-        case 'sequence-id': return SequenceIdColorTheme(props)
-        case 'shape-group': return ShapeGroupColorTheme(props)
-        case 'unit-index': return UnitIndexColorTheme(props)
-        case 'uniform': return UniformColorTheme(props)
+    export function areEqual(themeA: ColorTheme, themeB: ColorTheme) {
+        return themeA === themeB && deepEqual(themeA.props, themeB.props)
     }
-}
 
-export interface ColorThemeProps {
-    name: ColorThemeName
-    domain?: [number, number]
-    value?: Color
-    list?: Color[]
-    map?: ColorMap<any>
-    structure?: Structure
-    color?: LocationColor
-    granularity?: ColorType,
-    description?: string,
-    legend?: ScaleLegend | TableLegend
+    export interface Provider<P extends PD.Params> {
+        readonly factory: (ctx: ThemeDataContext, props: PD.DefaultValues<P>) => ColorTheme<PD.DefaultValues<P>>
+        readonly params: (ctx: ThemeDataContext) => P
+    }
+
+    export class Registry {
+        private _list: { name: string, provider: Provider<any> }[] = []
+        private _map = new Map<string, Provider<any>>()
+
+        constructor() {
+            Object.keys(BuiltInColorThemes).forEach(name => {
+                const p = (BuiltInColorThemes as { [k: string]: Provider<any> })[name]
+                this.add(name, p.factory, p.params)
+            })
+        }
+
+        add<P extends PD.Params>(name: string, factory: Provider<P>['factory'], params: Provider<P>['params']) {
+            const provider = { factory, params } as Provider<P>
+            this._list.push({ name, provider })
+            this._map.set(name, provider)
+        }
+
+        get(id: string) {
+            return this._map.get(id)
+        }
+
+        create(id: string, ctx: ThemeDataContext, props = {}) {
+            const provider = this.get(id)
+            return provider ? provider.factory(ctx, { ...PD.getDefaultValues(provider.params(ctx)), ...props }) : Empty
+        }
+
+        get list() {
+            return this._list
+        }
+    }
 }
 
-export const ColorThemeInfo = {
-    'carbohydrate-symbol': {},
-    'chain-id': {},
-    'cross-link': {},
-    'custom': {},
-    'element-index': {},
-    'element-symbol': {},
-    'molecule-type': {},
-    'polymer-index': {},
-    'residue-name': {},
-    'secondary-structure': {},
-    'sequence-id': {},
-    'shape-group': {},
-    'unit-index': {},
-    'uniform': {},
+export const BuiltInColorThemes = {
+    'carbohydrate-symbol': CarbohydrateSymbolColorThemeProvider,
+    'chain-id': ChainIdColorThemeProvider,
+    'cross-link': CrossLinkColorThemeProvider,
+    'element-index': ElementIndexColorThemeProvider,
+    'element-symbol': ElementSymbolColorThemeProvider,
+    'molecule-type': MoleculeTypeColorThemeProvider,
+    'polymer-index': PolymerIndexColorThemeProvider,
+    'residue-name': ResidueNameColorThemeProvider,
+    'secondary-structure': SecondaryStructureColorThemeProvider,
+    'sequence-id': SequenceIdColorThemeProvider,
+    'shape-group': ShapeGroupColorThemeProvider,
+    'unit-index': UnitIndexColorThemeProvider,
+    'uniform': UniformColorThemeProvider,
 }
-export type ColorThemeName = keyof typeof ColorThemeInfo
-export const ColorThemeNames = Object.keys(ColorThemeInfo)
-export const ColorThemeOptions = ColorThemeNames.map(n => [n, n] as [ColorThemeName, string])
+export type BuiltInColorThemeName = keyof typeof BuiltInColorThemes
+export const BuiltInColorThemeNames = Object.keys(BuiltInColorThemes)
+export const BuiltInColorThemeOptions = BuiltInColorThemeNames.map(n => [n, n] as [BuiltInColorThemeName, string])

+ 26 - 5
src/mol-theme/color/carbohydrate-symbol.ts

@@ -8,17 +8,34 @@ import { StructureElement, Link, ElementIndex, Unit } from 'mol-model/structure'
 
 import { SaccharideColors, MonosaccharidesColorTable } from 'mol-model/structure/structure/carbohydrates/constants';
 import { Location } from 'mol-model/location';
-import { ColorThemeProps, ColorTheme, LocationColor, TableLegend } from '../color';
+import { ColorTheme, LocationColor, TableLegend } from '../color';
 import { Color } from 'mol-util/color';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
 
 const DefaultColor = Color(0xCCCCCC)
 const Description = 'Assigns colors according to the Symbol Nomenclature for Glycans (SNFG).'
 
-export function CarbohydrateSymbolColorTheme(props: ColorThemeProps): ColorTheme {
+// name: ColorThemeName
+// domain?: [number, number]
+// value?: Color
+// list?: Color[]
+// map?: ColorMap<any>
+
+export const CarbohydrateSymbolColorThemeParams = {
+    // domain: PD.Interval('Color Domain', '', [0, 1]),
+    // value: PD.Color('Color Value', '', DefaultColor),
+}
+export function getCarbohydrateSymbolColorThemeParams(ctx: ThemeDataContext) {
+    return CarbohydrateSymbolColorThemeParams // TODO return copy
+}
+export type CarbohydrateSymbolColorThemeProps = PD.DefaultValues<typeof CarbohydrateSymbolColorThemeParams>
+
+export function CarbohydrateSymbolColorTheme(ctx: ThemeDataContext, props: CarbohydrateSymbolColorThemeProps): ColorTheme<CarbohydrateSymbolColorThemeProps> {
     let color: LocationColor
 
-    if (props.structure) {
-        const { elements, getElementIndex, getAnomericCarbon } = props.structure.carbohydrates
+    if (ctx.structure) {
+        const { elements, getElementIndex, getAnomericCarbon } = ctx.structure.carbohydrates
 
         const getColor = (unit: Unit, index: ElementIndex) => {
             const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index[index]
@@ -47,10 +64,14 @@ export function CarbohydrateSymbolColorTheme(props: ColorThemeProps): ColorTheme
     }
 
     return {
-        features: {},
         granularity: 'group',
         color: color,
+        props: props,
         description: Description,
         legend: TableLegend(MonosaccharidesColorTable)
     }
+}
+
+export const CarbohydrateSymbolColorThemeProvider: ColorTheme.Provider<typeof CarbohydrateSymbolColorThemeParams> = {
+    factory: CarbohydrateSymbolColorTheme, params: getCarbohydrateSymbolColorThemeParams
 }

+ 21 - 10
src/mol-theme/color/chain-id.ts

@@ -8,11 +8,22 @@ import { Unit, StructureProperties, StructureElement, Link } from 'mol-model/str
 
 import { ColorScale, Color } from 'mol-util/color';
 import { Location } from 'mol-model/location';
-import { ColorThemeProps, ColorTheme, LocationColor } from '../color';
+import { ColorTheme, LocationColor } from '../color';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
+import { ColorListOptions, ColorListName } from 'mol-util/color/scale';
 
 const DefaultColor = Color(0xCCCCCC)
 const Description = 'Gives every chain a color based on its `asym_id` value.'
 
+export const ChainIdColorThemeParams = {
+    list: PD.Select<ColorListName>('Color Scale', '', 'RdYlBu', ColorListOptions),
+}
+export function getChainIdColorThemeParams(ctx: ThemeDataContext) {
+    return ChainIdColorThemeParams // TODO return copy
+}
+export type ChainIdColorThemeProps = PD.DefaultValues<typeof ChainIdColorThemeParams>
+
 function getAsymId(unit: Unit): StructureElement.Property<string> {
     switch (unit.kind) {
         case Unit.Kind.Atomic:
@@ -23,14 +34,13 @@ function getAsymId(unit: Unit): StructureElement.Property<string> {
     }
 }
 
-export function ChainIdColorTheme(props: ColorThemeProps): ColorTheme {
+export function ChainIdColorTheme(ctx: ThemeDataContext, props: ChainIdColorThemeProps): ColorTheme<ChainIdColorThemeProps> {
     let color: LocationColor
-    const scale = ColorScale.create({ list: props.list, minLabel: 'Start', maxLabel: 'End' })
-    // const table: [string, Color][] = []
+    const scale = ColorScale.create({ listOrName: props.list, minLabel: 'Start', maxLabel: 'End' })
 
-    if (props.structure) {
+    if (ctx.structure) {
         const l = StructureElement.create()
-        const { models } = props.structure
+        const { models } = ctx.structure
         const asymIdSerialMap = new Map<string, number>()
         let j = 0
         for (let i = 0, il = models.length; i <il; ++i) {
@@ -44,8 +54,6 @@ export function ChainIdColorTheme(props: ColorThemeProps): ColorTheme {
         scale.setDomain(0, asymIdSerialMap.size - 1)
         const scaleColor = scale.color
 
-        // asymIdSerialMap.forEach((v, k) => table.push([k, scaleColor(v)]))
-
         color = (location: Location): Color => {
             if (StructureElement.isLocation(location)) {
                 const asym_id = getAsymId(location.unit)
@@ -63,11 +71,14 @@ export function ChainIdColorTheme(props: ColorThemeProps): ColorTheme {
     }
 
     return {
-        features: { structure: true, list: true },
         granularity: 'group',
         color,
+        props,
         description: Description,
-        // legend: scale ? TableLegend(table) : undefined
         legend: scale ? scale.legend : undefined
     }
+}
+
+export const ChainIdColorThemeProvider: ColorTheme.Provider<typeof ChainIdColorThemeParams> = {
+    factory: ChainIdColorTheme, params: getChainIdColorThemeParams
 }

+ 23 - 9
src/mol-theme/color/cross-link.ts

@@ -8,14 +8,24 @@ import { Link } from 'mol-model/structure';
 
 import { Color, ColorScale } from 'mol-util/color';
 import { Location } from 'mol-model/location';
-import { ColorThemeProps, ColorTheme, LocationColor } from '../color';
+import { ColorTheme, LocationColor } from '../color';
 import { Vec3 } from 'mol-math/linear-algebra';
-import { ColorBrewer } from 'mol-util/color/tables';
-import { defaults } from 'mol-util';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
+import { ColorListName, ColorListOptions } from 'mol-util/color/scale';
 
 const DefaultColor = Color(0xCCCCCC)
 const Description = 'Colors cross-links by the deviation of the observed distance versus the modeled distance (e.g. `ihm_cross_link_restraint.distance_threshold`).'
 
+export const CrossLinkColorThemeParams = {
+    domain: PD.Interval('Color Domain', '', [-10, 10]),
+    list: PD.Select<ColorListName>('Color Scale', '', 'RdYlBu', ColorListOptions),
+}
+export function getCrossLinkColorThemeParams(ctx: ThemeDataContext) {
+    return CrossLinkColorThemeParams // TODO return copy
+}
+export type CrossLinkColorThemeProps = PD.DefaultValues<typeof CrossLinkColorThemeParams>
+
 const distVecA = Vec3.zero(), distVecB = Vec3.zero()
 function linkDistance(link: Link.Location) {
     link.aUnit.conformation.position(link.aIndex, distVecA)
@@ -23,15 +33,15 @@ function linkDistance(link: Link.Location) {
     return Vec3.distance(distVecA, distVecB)
 }
 
-export function CrossLinkColorTheme(props: ColorThemeProps): ColorTheme {
+export function CrossLinkColorTheme(ctx: ThemeDataContext, props: CrossLinkColorThemeProps): ColorTheme<CrossLinkColorThemeProps> {
     let color: LocationColor
     let scale: ColorScale | undefined = undefined
 
-    if (props.structure) {
-        const crosslinks = props.structure.crossLinkRestraints
+    if (ctx.structure) {
+        const crosslinks = ctx.structure.crossLinkRestraints
         scale = ColorScale.create({
-            domain: defaults(props.domain, [ -10, 10 ]),
-            list: defaults(props.list, ColorBrewer.RdYlBu)
+            domain: props.domain,
+            listOrName: props.list
         })
         const scaleColor = scale.color
 
@@ -49,10 +59,14 @@ export function CrossLinkColorTheme(props: ColorThemeProps): ColorTheme {
     }
 
     return {
-        features: { list: true, domain: true, structure: true },
         granularity: 'group',
         color,
+        props,
         description: Description,
         legend: scale ? scale.legend : undefined
     }
+}
+
+export const CrossLinkColorThemeProvider: ColorTheme.Provider<typeof CrossLinkColorThemeParams> = {
+    factory: CrossLinkColorTheme, params: getCrossLinkColorThemeParams
 }

+ 0 - 22
src/mol-theme/color/custom.ts

@@ -1,22 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { Color } from 'mol-util/color';
-import { ColorThemeProps, ColorTheme } from '../color';
-import { defaults } from 'mol-util';
-
-const DefaultColor = Color(0xCCCCCC)
-
-export function CustomColorTheme(props: ColorThemeProps): ColorTheme {
-    const value = defaults(props.value, DefaultColor)
-    return {
-        features: {},
-        granularity: defaults(props.granularity, 'uniform'),
-        color: defaults(props.color, () => value),
-        description: props.description,
-        legend: props.legend
-    }
-}

+ 21 - 6
src/mol-theme/color/element-index.ts

@@ -8,17 +8,28 @@ import { ColorScale, Color } from 'mol-util/color';
 import { Location } from 'mol-model/location';
 import { StructureElement, Link } from 'mol-model/structure';
 import { OrderedSet } from 'mol-data/int';
-import { ColorThemeProps, ColorTheme, LocationColor } from '../color';
+import { ColorTheme, LocationColor } from '../color';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
+import { ColorListOptions, ColorListName } from 'mol-util/color/scale';
 
 const DefaultColor = Color(0xCCCCCC)
 const Description = 'Gives every element (atom or coarse sphere/gaussian) a unique color based on the position (index) of the element in the list of elements in the structure.'
 
-export function ElementIndexColorTheme(props: ColorThemeProps): ColorTheme {
+export const ElementIndexColorThemeParams = {
+    list: PD.Select<ColorListName>('Color Scale', '', 'RdYlBu', ColorListOptions),
+}
+export function getElementIndexColorThemeParams(ctx: ThemeDataContext) {
+    return ElementIndexColorThemeParams // TODO return copy
+}
+export type ElementIndexColorThemeProps = PD.DefaultValues<typeof ElementIndexColorThemeParams>
+
+export function ElementIndexColorTheme(ctx: ThemeDataContext, props: ElementIndexColorThemeProps): ColorTheme<ElementIndexColorThemeProps> {
     let color: LocationColor
-    let scale = ColorScale.create({ list: props.list, minLabel: 'Start', maxLabel: 'End' })
+    let scale = ColorScale.create({ listOrName: props.list, minLabel: 'Start', maxLabel: 'End' })
 
-    if (props.structure) {
-        const { units } = props.structure
+    if (ctx.structure) {
+        const { units } = ctx.structure
         const unitCount = units.length
         const cummulativeElementCount = new Map<number, number>()
         const unitIdIndex = new Map<number, number>()
@@ -48,10 +59,14 @@ export function ElementIndexColorTheme(props: ColorThemeProps): ColorTheme {
     }
 
     return {
-        features: { structure: true, list: true },
         granularity: 'groupInstance',
         color,
+        props,
         description: Description,
         legend: scale ? scale.legend : undefined
     }
+}
+
+export const ElementIndexColorThemeProvider: ColorTheme.Provider<typeof ElementIndexColorThemeParams> = {
+    factory: ElementIndexColorTheme, params: getElementIndexColorThemeParams
 }

+ 15 - 3
src/mol-theme/color/element-symbol.ts

@@ -8,7 +8,9 @@ import { ElementSymbol } from 'mol-model/structure/model/types';
 import { Color, ColorMap } from 'mol-util/color';
 import { StructureElement, Unit, Link } from 'mol-model/structure';
 import { Location } from 'mol-model/location';
-import { ColorThemeProps, ColorTheme, TableLegend } from '../color';
+import { ColorTheme, TableLegend } from '../color';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
 
 // from Jmol http://jmol.sourceforge.net/jscolors/ (or 0xFFFFFF)
 export const ElementSymbolColors = ColorMap({
@@ -18,12 +20,18 @@ export const ElementSymbolColors = ColorMap({
 const DefaultElementSymbolColor = Color(0xFFFFFF)
 const Description = 'Assigns a color to every atom according to its chemical element.'
 
+export const ElementSymbolColorThemeParams = {}
+export function getElementSymbolColorThemeParams(ctx: ThemeDataContext) {
+    return ElementSymbolColorThemeParams // TODO return copy
+}
+export type ElementSymbolColorThemeProps = PD.DefaultValues<typeof ElementSymbolColorThemeParams>
+
 export function elementSymbolColor(element: ElementSymbol): Color {
     const c = (ElementSymbolColors as { [k: string]: Color })[element];
     return c === undefined ? DefaultElementSymbolColor : c
 }
 
-export function ElementSymbolColorTheme(props: ColorThemeProps): ColorTheme {
+export function ElementSymbolColorTheme(ctx: ThemeDataContext, props: ElementSymbolColorThemeProps): ColorTheme<ElementSymbolColorThemeProps> {
     function color(location: Location): Color {
         if (StructureElement.isLocation(location)) {
             if (Unit.isAtomic(location.unit)) {
@@ -40,12 +48,16 @@ export function ElementSymbolColorTheme(props: ColorThemeProps): ColorTheme {
     }
 
     return {
-        features: {},
         granularity: 'group',
         color,
+        props,
         description: Description,
         legend: TableLegend(Object.keys(ElementSymbolColors).map(name => {
             return [name, (ElementSymbolColors as any)[name] as Color] as [string, Color]
         }))
     }
+}
+
+export const ElementSymbolColorThemeProvider: ColorTheme.Provider<typeof ElementSymbolColorThemeParams> = {
+    factory: ElementSymbolColorTheme, params: getElementSymbolColorThemeParams
 }

+ 15 - 3
src/mol-theme/color/molecule-type.ts

@@ -7,9 +7,11 @@
 import { Color, ColorMap } from 'mol-util/color';
 import { StructureElement, Unit, Link, ElementIndex } from 'mol-model/structure';
 import { Location } from 'mol-model/location';
-import { ColorThemeProps, ColorTheme, TableLegend } from '../color';
+import { ColorTheme, TableLegend } from '../color';
 import { MoleculeType } from 'mol-model/structure/model/types';
 import { getElementMoleculeType } from 'mol-model/structure/util';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
 
 const MoleculeTypeColors = ColorMap({
     water: 0x386cb0,
@@ -24,6 +26,12 @@ const MoleculeTypeColors = ColorMap({
 const DefaultMoleculeTypeColor = Color(0xffff99)
 const Description = 'Assigns a color based on the molecule type of a residue.'
 
+export const MoleculeTypeColorThemeParams = {}
+export function getMoleculeTypeColorThemeParams(ctx: ThemeDataContext) {
+    return MoleculeTypeColorThemeParams // TODO return copy
+}
+export type MoleculeTypeColorThemeProps = PD.DefaultValues<typeof MoleculeTypeColorThemeParams>
+
 export function moleculeTypeColor(unit: Unit, element: ElementIndex): Color {
     const moleculeType = getElementMoleculeType(unit, element)
     switch (moleculeType) {
@@ -38,7 +46,7 @@ export function moleculeTypeColor(unit: Unit, element: ElementIndex): Color {
     return DefaultMoleculeTypeColor
 }
 
-export function MoleculeTypeColorTheme(props: ColorThemeProps): ColorTheme {
+export function MoleculeTypeColorTheme(ctx: ThemeDataContext, props: MoleculeTypeColorThemeProps): ColorTheme<MoleculeTypeColorThemeProps> {
     function color(location: Location): Color {
         if (StructureElement.isLocation(location)) {
             return moleculeTypeColor(location.unit, location.element)
@@ -49,12 +57,16 @@ export function MoleculeTypeColorTheme(props: ColorThemeProps): ColorTheme {
     }
 
     return {
-        features: {},
         granularity: 'group',
         color,
+        props,
         description: Description,
         legend: TableLegend(Object.keys(MoleculeTypeColors).map(name => {
             return [name, (MoleculeTypeColors as any)[name] as Color] as [string, Color]
         }).concat([[ 'Other/unknown', DefaultMoleculeTypeColor ]]))
     }
+}
+
+export const MoleculeTypeColorThemeProvider: ColorTheme.Provider<typeof MoleculeTypeColorThemeParams> = {
+    factory: MoleculeTypeColorTheme, params: getMoleculeTypeColorThemeParams
 }

+ 21 - 6
src/mol-theme/color/polymer-index.ts

@@ -7,17 +7,28 @@
 import { ColorScale, Color } from 'mol-util/color';
 import { Location } from 'mol-model/location';
 import { StructureElement, Link } from 'mol-model/structure';
-import { ColorTheme, ColorThemeProps, LocationColor } from '../color';
+import { ColorTheme, LocationColor } from '../color';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
+import { ColorListName, ColorListOptions } from 'mol-util/color/scale';
 
 const DefaultColor = Color(0xCCCCCC)
 const Description = 'Gives every polymer a unique color based on the position (index) of the polymer in the list of polymers in the structure.'
 
-export function PolymerIndexColorTheme(props: ColorThemeProps): ColorTheme {
+export const PolymerIndexColorThemeParams = {
+    list: PD.Select<ColorListName>('Color Scale', '', 'RdYlBu', ColorListOptions),
+}
+export function getPolymerIndexColorThemeParams(ctx: ThemeDataContext) {
+    return PolymerIndexColorThemeParams // TODO return copy
+}
+export type PolymerIndexColorThemeProps = PD.DefaultValues<typeof PolymerIndexColorThemeParams>
+
+export function PolymerIndexColorTheme(ctx: ThemeDataContext, props: PolymerIndexColorThemeProps): ColorTheme<PolymerIndexColorThemeProps> {
     let color: LocationColor
-    const scale = ColorScale.create({ list: props.list, minLabel: 'Start', maxLabel: 'End' })
+    const scale = ColorScale.create({ listOrName: props.list, minLabel: 'Start', maxLabel: 'End' })
 
-    if (props.structure) {
-        const { units } = props.structure
+    if (ctx.structure) {
+        const { units } = ctx.structure
         let polymerCount = 0
         for (let i = 0, il = units.length; i <il; ++i) {
             if (units[i].polymerElements.length > 0) ++polymerCount
@@ -45,10 +56,14 @@ export function PolymerIndexColorTheme(props: ColorThemeProps): ColorTheme {
     }
 
     return {
-        features: { structure: true, list: true },
         granularity: 'instance',
         color,
+        props,
         description: Description,
         legend: scale ? scale.legend : undefined
     }
+}
+
+export const PolymerIndexColorThemeProvider: ColorTheme.Provider<typeof PolymerIndexColorThemeParams> = {
+    factory: PolymerIndexColorTheme, params: getPolymerIndexColorThemeParams
 }

+ 15 - 3
src/mol-theme/color/residue-name.ts

@@ -7,7 +7,9 @@
 import { Color, ColorMap } from 'mol-util/color';
 import { StructureElement, Unit, Link, ElementIndex } from 'mol-model/structure';
 import { Location } from 'mol-model/location';
-import { ColorThemeProps, ColorTheme, TableLegend } from '../color';
+import { ColorTheme, TableLegend } from '../color';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
 
 // protein colors from Jmol http://jmol.sourceforge.net/jscolors/
 const ResidueNameColors = ColorMap({
@@ -59,6 +61,12 @@ const ResidueNameColors = ColorMap({
 const DefaultResidueNameColor = Color(0xFF00FF)
 const Description = 'Assigns a color to every residue according to its name.'
 
+export const ResidueNameColorThemeParams = {}
+export function getResidueNameColorThemeParams(ctx: ThemeDataContext) {
+    return ResidueNameColorThemeParams // TODO return copy
+}
+export type ResidueNameColorThemeProps = PD.DefaultValues<typeof ResidueNameColorThemeParams>
+
 export function residueNameColor(residueName: string): Color {
     const c = (ResidueNameColors as { [k: string]: Color })[residueName];
     return c === undefined ? DefaultResidueNameColor : c
@@ -84,7 +92,7 @@ function getCoarseCompId(unit: Unit.Spheres | Unit.Gaussians, element: ElementIn
     }
 }
 
-export function ResidueNameColorTheme(props: ColorThemeProps): ColorTheme {
+export function ResidueNameColorTheme(ctx: ThemeDataContext, props: ResidueNameColorThemeProps): ColorTheme<ResidueNameColorThemeProps> {
     function color(location: Location): Color {
         if (StructureElement.isLocation(location)) {
             if (Unit.isAtomic(location.unit)) {
@@ -105,12 +113,16 @@ export function ResidueNameColorTheme(props: ColorThemeProps): ColorTheme {
     }
 
     return {
-        features: {},
         granularity: 'group',
         color,
+        props,
         description: Description,
         legend: TableLegend(Object.keys(ResidueNameColors).map(name => {
             return [name, (ResidueNameColors as any)[name] as Color] as [string, Color]
         }).concat([[ 'Unknown', DefaultResidueNameColor ]]))
     }
+}
+
+export const ResidueNameColorThemeProvider: ColorTheme.Provider<typeof ResidueNameColorThemeParams> = {
+    factory: ResidueNameColorTheme, params: getResidueNameColorThemeParams
 }

+ 15 - 3
src/mol-theme/color/secondary-structure.ts

@@ -7,9 +7,11 @@
 import { Color, ColorMap } from 'mol-util/color';
 import { StructureElement, Unit, Link, ElementIndex } from 'mol-model/structure';
 import { Location } from 'mol-model/location';
-import { ColorThemeProps, ColorTheme, TableLegend } from '../color';
+import { ColorTheme, TableLegend } from '../color';
 import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/types';
 import { getElementMoleculeType } from 'mol-model/structure/util';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
 
 // from Jmol http://jmol.sourceforge.net/jscolors/ (shapely)
 const SecondaryStructureColors = ColorMap({
@@ -29,6 +31,12 @@ const SecondaryStructureColors = ColorMap({
 const DefaultSecondaryStructureColor = Color(0x808080)
 const Description = 'Assigns a color based on the type of secondary structure and basic molecule type.'
 
+export const SecondaryStructureColorThemeParams = {}
+export function getSecondaryStructureColorThemeParams(ctx: ThemeDataContext) {
+    return SecondaryStructureColorThemeParams // TODO return copy
+}
+export type SecondaryStructureColorThemeProps = PD.DefaultValues<typeof SecondaryStructureColorThemeParams>
+
 export function secondaryStructureColor(unit: Unit, element: ElementIndex): Color {
     let secStrucType = SecondaryStructureType.create(SecondaryStructureType.Flag.None)
     if (Unit.isAtomic(unit)) {
@@ -61,7 +69,7 @@ export function secondaryStructureColor(unit: Unit, element: ElementIndex): Colo
     return DefaultSecondaryStructureColor
 }
 
-export function SecondaryStructureColorTheme(props: ColorThemeProps): ColorTheme {
+export function SecondaryStructureColorTheme(ctx: ThemeDataContext, props: SecondaryStructureColorThemeProps): ColorTheme<SecondaryStructureColorThemeProps> {
     function color(location: Location): Color {
         if (StructureElement.isLocation(location)) {
             return secondaryStructureColor(location.unit, location.element)
@@ -72,12 +80,16 @@ export function SecondaryStructureColorTheme(props: ColorThemeProps): ColorTheme
     }
 
     return {
-        features: {},
         granularity: 'group',
         color,
+        props,
         description: Description,
         legend: TableLegend(Object.keys(SecondaryStructureColors).map(name => {
             return [name, (SecondaryStructureColors as any)[name] as Color] as [string, Color]
         }).concat([[ 'Other', DefaultSecondaryStructureColor ]]))
     }
+}
+
+export const SecondaryStructureColorThemeProvider: ColorTheme.Provider<typeof SecondaryStructureColorThemeParams> = {
+    factory: SecondaryStructureColorTheme, params: getSecondaryStructureColorThemeParams
 }

+ 21 - 12
src/mol-theme/color/sequence-id.ts

@@ -8,13 +8,22 @@ import { Unit, StructureElement, Link, ElementIndex } from 'mol-model/structure'
 
 import { ColorScale, Color } from 'mol-util/color';
 import { Location } from 'mol-model/location';
-import { ColorThemeProps, ColorTheme } from '../color';
-import { ColorOther } from 'mol-util/color/tables';
-import { defaults } from 'mol-util';
+import { ColorTheme } from '../color';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
+import { ColorListOptions, ColorListName } from 'mol-util/color/scale';
 
 const DefaultColor = Color(0xCCCCCC)
 const Description = 'Gives every polymer residue a color based on its `seq_id` value.'
 
+export const SequenceIdColorThemeParams = {
+    list: PD.Select<ColorListName>('Color Scale', '', 'rainbow', ColorListOptions),
+}
+export function getSequenceIdColorThemeParams(ctx: ThemeDataContext) {
+    return SequenceIdColorThemeParams // TODO return copy
+}
+export type SequenceIdColorThemeProps = PD.DefaultValues<typeof SequenceIdColorThemeParams>
+
 function getSeqId(unit: Unit, element: ElementIndex): number {
     const { model } = unit
     switch (unit.kind) {
@@ -55,15 +64,12 @@ function getSequenceLength(unit: Unit, element: ElementIndex) {
     return model.sequence.byEntityKey[entityIndex].sequence.sequence.length
 }
 
-export function SequenceIdColorTheme(props: ColorThemeProps): ColorTheme {
-    const p = {
-        ...props,
-        list: defaults(props.list, ColorOther.rainbow),
+export function SequenceIdColorTheme(ctx: ThemeDataContext, props: SequenceIdColorThemeProps): ColorTheme<SequenceIdColorThemeProps> {
+    const scale = ColorScale.create({
+        listOrName: props.list,
         minLabel: 'Start',
         maxLabel: 'End',
-    }
-
-    const scale = ColorScale.create(p)
+    })
     const color = (location: Location): Color => {
         if (StructureElement.isLocation(location)) {
             const { unit, element } = location
@@ -84,11 +90,14 @@ export function SequenceIdColorTheme(props: ColorThemeProps): ColorTheme {
     }
 
     return {
-        features: { list: true },
         granularity: 'group',
         color,
+        props,
         description: Description,
-        // legend: scale ? TableLegend(table) : undefined
         legend: scale ? scale.legend : undefined
     }
+}
+
+export const SequenceIdColorThemeProvider: ColorTheme.Provider<typeof SequenceIdColorThemeParams> = {
+    factory: SequenceIdColorTheme, params: getSequenceIdColorThemeParams
 }

+ 17 - 5
src/mol-theme/color/shape-group.ts

@@ -4,16 +4,24 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ColorTheme, ColorThemeProps } from '../color';
+import { ColorTheme } from '../color';
 import { Color } from 'mol-util/color';
 import { Location } from 'mol-model/location';
 import { Shape } from 'mol-model/shape';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
 
 const DefaultColor = Color(0xCCCCCC)
+const Description = 'Assigns colors as defined by the shape object.'
 
-export function ShapeGroupColorTheme(props: ColorThemeProps): ColorTheme {
+export const ShapeGroupColorThemeParams = {}
+export function getShapeGroupColorThemeParams(ctx: ThemeDataContext) {
+    return ShapeGroupColorThemeParams // TODO return copy
+}
+export type ShapeGroupColorThemeProps = PD.DefaultValues<typeof ShapeGroupColorThemeParams>
+
+export function ShapeGroupColorTheme(ctx: ThemeDataContext, props: ShapeGroupColorThemeProps): ColorTheme<ShapeGroupColorThemeProps> {
     return {
-        features: {},
         granularity: 'group',
         color: (location: Location): Color => {
             if (Shape.isLocation(location)) {
@@ -21,7 +29,11 @@ export function ShapeGroupColorTheme(props: ColorThemeProps): ColorTheme {
             }
             return DefaultColor
         },
-        description: props.description,
-        legend: props.legend
+        props,
+        description: Description
     }
+}
+
+export const ShapeGroupColorThemeProvider: ColorTheme.Provider<typeof ShapeGroupColorThemeParams> = {
+    factory: ShapeGroupColorTheme, params: getShapeGroupColorThemeParams
 }

+ 17 - 3
src/mol-theme/color/uniform.ts

@@ -4,20 +4,34 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ColorTheme, ColorThemeProps, TableLegend } from '../color';
+import { ColorTheme, TableLegend } from '../color';
 import { Color } from 'mol-util/color';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
 
 const DefaultColor = Color(0xCCCCCC)
 const Description = 'Gives everything the same, uniform color.'
 
-export function UniformColorTheme(props: ColorThemeProps): ColorTheme {
+export const UniformColorThemeParams = {
+    value: PD.Color('Color Value', '', DefaultColor),
+}
+export function getUniformColorThemeParams(ctx: ThemeDataContext) {
+    return UniformColorThemeParams // TODO return copy
+}
+export type UniformColorThemeProps = PD.DefaultValues<typeof UniformColorThemeParams>
+
+export function UniformColorTheme(ctx: ThemeDataContext, props: UniformColorThemeProps): ColorTheme<UniformColorThemeProps> {
     const color = props.value || DefaultColor
 
     return {
-        features: {},
         granularity: 'uniform',
         color: () => color,
+        props: props,
         description: Description,
         legend: TableLegend([['uniform', color]])
     }
+}
+
+export const UniformColorThemeProvider: ColorTheme.Provider<typeof UniformColorThemeParams> = {
+    factory: UniformColorTheme, params: getUniformColorThemeParams
 }

+ 21 - 6
src/mol-theme/color/unit-index.ts

@@ -7,17 +7,28 @@
 import { ColorScale, Color } from 'mol-util/color';
 import { Location } from 'mol-model/location';
 import { StructureElement, Link } from 'mol-model/structure';
-import { ColorTheme, ColorThemeProps, LocationColor } from '../color';
+import { ColorTheme, LocationColor } from '../color';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
+import { ColorListOptions, ColorListName } from 'mol-util/color/scale';
 
 const DefaultColor = Color(0xCCCCCC)
 const Description = 'Gives every unit (single chain or collection of single elements) a unique color based on the position (index) of the unit in the list of units in the structure.'
 
-export function UnitIndexColorTheme(props: ColorThemeProps): ColorTheme {
+export const UnitIndexColorThemeParams = {
+    list: PD.Select<ColorListName>('Color Scale', '', 'RdYlBu', ColorListOptions),
+}
+export function getUnitIndexColorThemeParams(ctx: ThemeDataContext) {
+    return UnitIndexColorThemeParams // TODO return copy
+}
+export type UnitIndexColorThemeProps = PD.DefaultValues<typeof UnitIndexColorThemeParams>
+
+export function UnitIndexColorTheme(ctx: ThemeDataContext, props: UnitIndexColorThemeProps): ColorTheme<UnitIndexColorThemeProps> {
     let color: LocationColor
-    const scale = ColorScale.create({ list: props.list, minLabel: 'Start', maxLabel: 'End' })
+    const scale = ColorScale.create({ listOrName: props.list, minLabel: 'Start', maxLabel: 'End' })
 
-    if (props.structure) {
-        const { units } = props.structure
+    if (ctx.structure) {
+        const { units } = ctx.structure
         scale.setDomain(0, units.length - 1)
         const unitIdColor = new Map<number, Color>()
         for (let i = 0, il = units.length; i <il; ++i) {
@@ -37,10 +48,14 @@ export function UnitIndexColorTheme(props: ColorThemeProps): ColorTheme {
     }
 
     return {
-        features: { structure: true, list: true },
         granularity: 'instance',
         color,
+        props,
         description: Description,
         legend: scale ? scale.legend : undefined
     }
+}
+
+export const UnitIndexColorThemeProvider: ColorTheme.Provider<typeof UnitIndexColorThemeParams> = {
+    factory: UnitIndexColorTheme, params: getUnitIndexColorThemeParams
 }

+ 57 - 23
src/mol-theme/size.ts

@@ -4,35 +4,69 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Structure } from 'mol-model/structure';
 import { SizeType, LocationSize } from 'mol-geo/geometry/size-data';
+import { UniformSizeTheme, UniformSizeThemeProvider } from './size/uniform';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { ThemeDataContext } from 'mol-theme/theme';
+import { PhysicalSizeThemeProvider } from './size/physical';
+import { deepEqual } from 'mol-util';
 
-import { PhysicalSizeTheme } from './size/physical';
-import { UniformSizeTheme } from './size/uniform';
-
-export interface SizeTheme {
-    granularity: SizeType
-    size: LocationSize
+export { SizeTheme }
+interface SizeTheme<P extends SizeTheme.Props = {}> {
+    readonly granularity: SizeType
+    readonly size: LocationSize
+    readonly props: Readonly<P>
+    readonly description?: string
 }
+namespace SizeTheme {
+    export type Props = { [k: string]: any }
+    export const Empty = UniformSizeTheme({}, { value: 1 })
 
-export function SizeTheme(props: SizeThemeProps): SizeTheme {
-    switch (props.name) {
-        case 'physical': return PhysicalSizeTheme(props)
-        case 'uniform': return UniformSizeTheme(props)
+    export function areEqual(themeA: SizeTheme, themeB: SizeTheme) {
+        return themeA === themeB && deepEqual(themeA.props, themeB.props)
     }
-}
 
-export interface SizeThemeProps {
-    name: 'physical' | 'uniform'
-    value?: number
-    factor?: number
-    structure?: Structure
+    export interface Provider<P extends PD.Params> {
+        readonly factory: (ctx: ThemeDataContext, props: PD.DefaultValues<P>) => SizeTheme<PD.DefaultValues<P>>
+        readonly params: (ctx: ThemeDataContext) => P
+    }
+
+    export class Registry {
+        private _list: { name: string, provider: Provider<any> }[] = []
+        private _map = new Map<string, Provider<any>>()
+
+        constructor() {
+            Object.keys(BuiltInSizeThemes).forEach(name => {
+                const p = (BuiltInSizeThemes as { [k: string]: Provider<any> })[name]
+                this.add(name, p.factory, p.params)
+            })
+        }
+
+        add<P extends PD.Params>(name: string, factory: Provider<P>['factory'], params: Provider<P>['params']) {
+            const provider = { factory, params } as Provider<P>
+            this._list.push({ name, provider })
+            this._map.set(name, provider)
+        }
+
+        get(id: string) {
+            return this._map.get(id)
+        }
+
+        create(id: string, ctx: ThemeDataContext, props = {}) {
+            const provider = this.get(id)
+            return provider ? provider.factory(ctx, { ...PD.getDefaultValues(provider.params(ctx)), ...props }) : Empty
+        }
+
+        get list() {
+            return this._list
+        }
+    }
 }
 
-export const SizeThemeInfo = {
-    'physical': {},
-    'uniform': {}
+export const BuiltInSizeThemes = {
+    'physical': PhysicalSizeThemeProvider,
+    'uniform': UniformSizeThemeProvider
 }
-export type SizeThemeName = keyof typeof SizeThemeInfo
-export const SizeThemeNames = Object.keys(SizeThemeInfo)
-export const SizeThemeOptions = SizeThemeNames.map(n => [n, n] as [SizeThemeName, string])
+export type BuiltInSizeThemeName = keyof typeof BuiltInSizeThemes
+export const BuiltInSizeThemeNames = Object.keys(BuiltInSizeThemes)
+export const BuiltInSizeThemeOptions = BuiltInSizeThemeNames.map(n => [n, n] as [BuiltInSizeThemeName, string])

+ 20 - 8
src/mol-theme/size/physical.ts

@@ -6,11 +6,19 @@
 
 import { StructureElement, Unit, Link, ElementIndex } from 'mol-model/structure';
 import { Location } from 'mol-model/location';
-import { SizeThemeProps, SizeTheme } from '../size';
+import { SizeTheme } from '../size';
 import { VdwRadius } from 'mol-model/structure/model/properties/atomic';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
 
 const DefaultSize = 1
-const DefaultFactor = 1
+const Description = 'Assigns a physical size.'
+
+export const PhysicalSizeThemeParams = {}
+export function getPhysicalSizeThemeParams(ctx: ThemeDataContext) {
+    return PhysicalSizeThemeParams // TODO return copy
+}
+export type PhysicalSizeThemeProps = PD.DefaultValues<typeof PhysicalSizeThemeParams>
 
 export function getPhysicalRadius(unit: Unit, element: ElementIndex): number {
     if (Unit.isAtomic(unit)) {
@@ -26,10 +34,8 @@ export function getPhysicalRadius(unit: Unit, element: ElementIndex): number {
  * Create attribute data with the physical size of an element,
  * i.e. vdw for atoms and radius for coarse spheres
  */
-export function PhysicalSizeTheme(props: SizeThemeProps): SizeTheme {
-    const factor = props.factor || DefaultFactor
-
-    function sizeFn(location: Location): number {
+export function PhysicalSizeTheme(ctx: ThemeDataContext, props: PhysicalSizeThemeProps): SizeTheme<PhysicalSizeThemeProps> {
+    function size(location: Location): number {
         let size: number
         if (StructureElement.isLocation(location)) {
             size = getPhysicalRadius(location.unit, location.element)
@@ -38,11 +44,17 @@ export function PhysicalSizeTheme(props: SizeThemeProps): SizeTheme {
         } else {
             size = DefaultSize
         }
-        return factor * size
+        return size
     }
 
     return {
         granularity: 'group',
-        size: sizeFn
+        size,
+        props,
+        description: Description
     }
+}
+
+export const PhysicalSizeThemeProvider: SizeTheme.Provider<typeof PhysicalSizeThemeParams> = {
+    factory: PhysicalSizeTheme, params: getPhysicalSizeThemeParams
 }

+ 21 - 8
src/mol-theme/size/uniform.ts

@@ -4,18 +4,31 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { SizeTheme, SizeThemeProps } from '../size';
+import { SizeTheme } from '../size';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { ThemeDataContext } from 'mol-theme/theme';
 
-const DefaultSize = 1
-const DefaultFactor = 1
+const Description = 'Gives everything the same, uniform size.'
 
-export function UniformSizeTheme(props: SizeThemeProps): SizeTheme {
-    const value = props.value || DefaultSize
-    const factor = props.factor || DefaultFactor
-    const size = value * factor
+export const UniformSizeThemeParams = {
+    value: PD.Numeric('Size Value', '', 1, 0, 20, 0.1),
+}
+export function getUniformSizeThemeParams(ctx: ThemeDataContext) {
+    return UniformSizeThemeParams // TODO return copy
+}
+export type UniformSizeThemeProps = PD.DefaultValues<typeof UniformSizeThemeParams>
+
+export function UniformSizeTheme(ctx: ThemeDataContext, props: UniformSizeThemeProps): SizeTheme<UniformSizeThemeProps> {
+    const size = props.value
 
     return {
         granularity: 'uniform',
-        size: () => size
+        size: () => size,
+        props,
+        description: Description
     }
+}
+
+export const UniformSizeThemeProvider: SizeTheme.Provider<typeof UniformSizeThemeParams> = {
+    factory: UniformSizeTheme, params: getUniformSizeThemeParams
 }

+ 46 - 0
src/mol-theme/theme.ts

@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ColorTheme } from './color';
+import { SizeTheme } from './size';
+import { Structure } from 'mol-model/structure';
+import { VolumeData } from 'mol-model/volume';
+
+export interface ThemeRegistryContext {
+    colorThemeRegistry: ColorTheme.Registry
+    sizeThemeRegistry: SizeTheme.Registry
+}
+
+export interface ThemeDataContext {
+    [k: string]: any
+    structure?: Structure
+    volume?: VolumeData
+}
+
+export interface ThemeProps { color?: {}, size?: {} }
+
+export interface Theme {
+    color: ColorTheme
+    size: SizeTheme
+    // label: LabelTheme // TODO
+}
+
+type Props = { [k: string]: any }
+
+export function createTheme(ctx: ThemeRegistryContext, data: ThemeDataContext, props: Props, themeProps: ThemeProps = {}, theme?: Theme) {
+    theme = theme || {
+        color: ColorTheme.Empty,
+        size: SizeTheme.Empty
+    }
+    // TODO check if props have changed
+    if (typeof props.colorTheme === 'string') {
+        theme.color = ctx.colorThemeRegistry.create(props.colorTheme, data, themeProps.color)
+    }
+    if (typeof props.sizeTheme === 'string') {
+        theme.size = ctx.sizeThemeRegistry.create(props.sizeTheme, data, themeProps.size)
+    }
+    return theme
+}

+ 13 - 3
src/mol-util/color/color.ts

@@ -26,26 +26,36 @@ export namespace Color {
         return [ (hexColor >> 16 & 255) / 255, (hexColor >> 8 & 255) / 255, (hexColor & 255) / 255 ]
     }
 
-    export function fromRgb(r: number, g: number, b: number ): Color {
+    export function fromRgb(r: number, g: number, b: number): Color {
         return ((r << 16) | (g << 8) | b) as Color
     }
 
-    export function fromNormalizedRgb(r: number, g: number, b: number ): Color {
+    export function fromNormalizedRgb(r: number, g: number, b: number): Color {
         return (((r * 255) << 16) | ((g * 255) << 8) | (b * 255)) as Color
     }
 
+    export function fromArray(array: Helpers.NumberArray, offset: number): Color {
+        return fromRgb(array[offset], array[offset + 1], array[offset + 2])
+    }
+
+    export function fromNormalizedArray(array: Helpers.NumberArray, offset: number): Color {
+        return fromNormalizedRgb(array[offset], array[offset + 1], array[offset + 2])
+    }
+
     /** Copies hex color to rgb array */
     export function toArray(hexColor: Color, array: Helpers.NumberArray, offset: number) {
         array[ offset ] = (hexColor >> 16 & 255)
         array[ offset + 1 ] = (hexColor >> 8 & 255)
         array[ offset + 2 ] = (hexColor & 255)
+        return array
     }
 
     /** Copies normalized (0 to 1) hex color to rgb array */
-    export function toArrayNormalized(hexColor: Color, array: Helpers.NumberArray, offset: number) {
+    export function toArrayNormalized<T extends Helpers.NumberArray>(hexColor: Color, array: T, offset: number) {
         array[ offset ] = (hexColor >> 16 & 255) / 255
         array[ offset + 1 ] = (hexColor >> 8 & 255) / 255
         array[ offset + 2 ] = (hexColor & 255) / 255
+        return array
     }
 
     /** Linear interpolation between two colors */

+ 29 - 7
src/mol-util/color/scale.ts

@@ -5,10 +5,32 @@
  */
 
 import { Color } from './color'
-import { ColorBrewer } from './tables'
+import { ColorBrewer, ColorMatplotlib, ColorOther } from './tables'
 import { ScaleLegend } from 'mol-theme/color';
 import { defaults } from 'mol-util';
 
+export type ColorListName = (
+    keyof typeof ColorBrewer | keyof typeof ColorMatplotlib | keyof typeof ColorOther
+)
+export const ColorListNames = [
+    ...Object.keys(ColorBrewer), ...Object.keys(ColorMatplotlib), ...Object.keys(ColorOther)
+]
+export const ColorListOptions = ColorListNames.map(n => [n, n] as [ColorListName, string])
+
+export function getColorListFromName(name: ColorListName) {
+    if (name in ColorBrewer) {
+        return ColorBrewer[name as keyof typeof ColorBrewer]
+    } else if (name in ColorMatplotlib) {
+        return ColorMatplotlib[name as keyof typeof ColorMatplotlib]
+    } else if (name in ColorOther) {
+        return ColorOther[name as keyof typeof ColorOther]
+    }
+    console.warn(`unknown color list named '${name}'`)
+    return ColorBrewer.RdYlBu
+}
+
+//
+
 export interface ColorScale {
     /** Returns hex color for given value */
     color: (value: number) => Color
@@ -22,20 +44,20 @@ export interface ColorScale {
     readonly legend: ScaleLegend
 }
 
-export const DefaultColorScale = {
+export const DefaultColorScaleProps = {
     domain: [0, 1],
     reverse: false,
-    list: ColorBrewer.RdYlBu,
+    listOrName: ColorBrewer.RdYlBu as Color[] | ColorListName,
     minLabel: '' as string | undefined,
     maxLabel: '' as string | undefined,
 }
-export type ColorScaleProps = Partial<typeof DefaultColorScale>
+export type ColorScaleProps = Partial<typeof DefaultColorScaleProps>
 
 export namespace ColorScale {
     export function create(props: ColorScaleProps): ColorScale {
-        // ensure that no undefined .list property exists so that the default assignment works
-        if (props.list === undefined) delete props.list
-        const { domain, reverse, list } = { ...DefaultColorScale, ...props }
+        const { domain, reverse, listOrName } = { ...DefaultColorScaleProps, ...props }
+        const list = typeof listOrName === 'string' ? getColorListFromName(listOrName) : listOrName
+
         const colors = reverse ? list.slice().reverse() : list
         const count1 = colors.length - 1
 

+ 25 - 1
src/mol-util/object.ts

@@ -2,6 +2,7 @@
  * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 const hasOwnProperty = Object.prototype.hasOwnProperty;
@@ -54,4 +55,27 @@ export function shallowMerge<T>(source: T, ...rest: (Partial<T> | undefined)[]):
         }
     }
     return ret;
-}
+}
+
+/** Simple deep clone for number, boolean, string, null, undefined, object, array */
+export function deepClone<T>(source: T): T {
+    if (null === source || "object" !== typeof source) return source;
+  
+    if (source instanceof Array) {
+      const copy: any[] = [];
+      for (let i = 0, len = source.length; i < len; i++) {
+          copy[i] = deepClone(source[i]);
+      }
+      return copy as any as T;
+    }
+  
+    if (source instanceof Object) {
+        const copy: { [k: string]: any } = {};
+        for (let k in source) {
+            if (hasOwnProperty.call(source, k)) copy[k] = deepClone(source[k]);
+        }
+        return copy as any as T;
+    }
+  
+    throw new Error(`Can't clone, type "${typeof source}" unsupported`);
+  }

+ 15 - 2
src/mol-util/param-definition.ts

@@ -6,6 +6,7 @@
  */
 
 import { Color as ColorData } from './color';
+import { shallowClone } from 'mol-util';
 
 export namespace ParamDefinition {
     export interface Base<T> {
@@ -82,13 +83,25 @@ export namespace ParamDefinition {
         return { type: 'number', label, description, defaultValue, min, max, step }
     }
 
-    export type Any = /* ValueParam<any> | */ Select<any> | MultiSelect<any> | Boolean | Range | Text | Color | Numeric
+    export interface Interval extends Base<[number, number]> {
+        type: 'interval'
+    }
+    export function Interval(label: string, description: string, defaultValue: [number, number]): Interval {
+        return { type: 'interval', label, description, defaultValue }
+    }
+
+    export type Any = /* GenericValue<any> | */ Select<any> | MultiSelect<any> | Boolean | Range | Text | Color | Numeric | Interval
     export type Params = { [k: string]: Any }
+    export type DefaultValues<T extends Params> = { [k in keyof T]: T[k]['defaultValue'] }
 
     export function getDefaultValues<T extends Params>(params: T) {
         const d: { [k: string]: any } = {}
         Object.keys(params).forEach(k => d[k] = params[k].defaultValue)
-        return d as { [k in keyof T]: T[k]['defaultValue'] }
+        return d as DefaultValues<T>
+    }
+
+    export function clone<P extends Params>(params: P): P {
+        return shallowClone(params)
     }
 
     /**

+ 7 - 1
webpack.config.js

@@ -1,6 +1,7 @@
 const path = require('path');
 const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin');
-const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+// const CircularDependencyPlugin = require('circular-dependency-plugin');
 module.exports = {
     module: {
         rules: [
@@ -30,6 +31,11 @@ module.exports = {
         ]
     },
     plugins: [
+        // new CircularDependencyPlugin({
+        //     include: [ path.resolve(__dirname, 'build/node_modules/') ],
+        //     failOnError: false,
+        //     cwd: process.cwd(),
+        // }),
         new ExtraWatchWebpackPlugin({
             files: [
                 './build/node_modules/**/*.vert',