Browse Source

wip, refactoring of representations and visuals

Alexander Rose 6 years ago
parent
commit
8d4e760856
35 changed files with 495 additions and 926 deletions
  1. 4 4
      src/mol-app/ui/transform/file-loader.tsx
  2. 5 5
      src/mol-app/ui/transform/url-loader.tsx
  3. 4 4
      src/mol-geo/representation/index.ts
  4. 3 3
      src/mol-geo/representation/structure/complex-representation.ts
  5. 76 3
      src/mol-geo/representation/structure/complex-visual.ts
  6. 8 2
      src/mol-geo/representation/structure/index.ts
  7. 8 7
      src/mol-geo/representation/structure/representation/backbone.ts
  8. 12 11
      src/mol-geo/representation/structure/representation/ball-and-stick.ts
  9. 10 9
      src/mol-geo/representation/structure/representation/carbohydrate.ts
  10. 14 13
      src/mol-geo/representation/structure/representation/cartoon.ts
  11. 8 8
      src/mol-geo/representation/structure/representation/distance-restraint.ts
  12. 2 2
      src/mol-geo/representation/structure/representation/spacefill.ts
  13. 3 3
      src/mol-geo/representation/structure/units-representation.ts
  14. 93 2
      src/mol-geo/representation/structure/units-visual.ts
  15. 10 52
      src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts
  16. 11 53
      src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts
  17. 28 64
      src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts
  18. 2 2
      src/mol-geo/representation/structure/visual/element-point.ts
  19. 14 83
      src/mol-geo/representation/structure/visual/element-sphere.ts
  20. 9 55
      src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts
  21. 11 60
      src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts
  22. 13 79
      src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts
  23. 13 79
      src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts
  24. 15 88
      src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts
  25. 13 79
      src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts
  26. 15 88
      src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts
  27. 8 3
      src/mol-geo/representation/structure/visual/util/common.ts
  28. 16 6
      src/mol-geo/representation/structure/visual/util/element.ts
  29. 2 2
      src/mol-geo/representation/util.ts
  30. 10 4
      src/mol-geo/representation/volume/index.ts
  31. 6 5
      src/mol-model/structure/structure/unit/pair-restraints/data.ts
  32. 7 6
      src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts
  33. 6 6
      src/mol-view/stage.ts
  34. 6 6
      src/mol-view/state/entity.ts
  35. 30 30
      src/mol-view/state/transform.ts

+ 4 - 4
src/mol-app/ui/transform/file-loader.tsx

@@ -16,14 +16,14 @@ import { BallAndStickProps } from 'mol-geo/representation/structure/representati
 import { DistanceRestraintProps } from 'mol-geo/representation/structure/representation/distance-restraint';
 import { BackboneProps } from 'mol-geo/representation/structure/representation/backbone';
 
-const spacefillProps: SpacefillProps = {
+const spacefillProps: Partial<SpacefillProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     quality: 'auto',
     useFog: false
 }
 
-const ballAndStickProps: BallAndStickProps = {
+const ballAndStickProps: Partial<BallAndStickProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     sizeTheme: { name: 'uniform', value: 0.05 },
@@ -32,7 +32,7 @@ const ballAndStickProps: BallAndStickProps = {
     useFog: false
 }
 
-const distanceRestraintProps: DistanceRestraintProps = {
+const distanceRestraintProps: Partial<DistanceRestraintProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     linkRadius: 0.5,
@@ -40,7 +40,7 @@ const distanceRestraintProps: DistanceRestraintProps = {
     useFog: false
 }
 
-const backboneProps: BackboneProps = {
+const backboneProps: Partial<BackboneProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     quality: 'auto',

+ 5 - 5
src/mol-app/ui/transform/url-loader.tsx

@@ -16,14 +16,14 @@ import { DistanceRestraintProps } from 'mol-geo/representation/structure/represe
 import { BackboneProps } from 'mol-geo/representation/structure/representation/backbone';
 import { CartoonProps } from 'mol-geo/representation/structure/representation/cartoon';
 
-const spacefillProps: SpacefillProps = {
+const spacefillProps: Partial<SpacefillProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     quality: 'auto',
     useFog: false
 }
 
-const ballAndStickProps: BallAndStickProps = {
+const ballAndStickProps: Partial<BallAndStickProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     sizeTheme: { name: 'uniform', value: 0.05 },
@@ -32,7 +32,7 @@ const ballAndStickProps: BallAndStickProps = {
     useFog: false
 }
 
-const distanceRestraintProps: DistanceRestraintProps = {
+const distanceRestraintProps: Partial<DistanceRestraintProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     linkRadius: 0.5,
@@ -40,14 +40,14 @@ const distanceRestraintProps: DistanceRestraintProps = {
     useFog: false
 }
 
-const backboneProps: BackboneProps = {
+const backboneProps: Partial<BackboneProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     quality: 'auto',
     useFog: false
 }
 
-const cartoonProps: CartoonProps = {
+const cartoonProps: Partial<CartoonProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     quality: 'auto',

+ 4 - 4
src/mol-geo/representation/index.ts

@@ -15,8 +15,8 @@ export interface RepresentationProps {}
 export interface Representation<D, P extends RepresentationProps = {}> {
     readonly renderObjects: ReadonlyArray<RenderObject>
     readonly props: Readonly<P>
-    create: (data: D, props?: P) => Task<void>
-    update: (props: P) => Task<void>
+    create: (data: D, props?: Partial<P>) => Task<void>
+    update: (props: Partial<P>) => Task<void>
     getLoci: (pickingId: PickingId) => Loci
     mark: (loci: Loci, action: MarkerAction) => void
     destroy: () => void
@@ -24,8 +24,8 @@ export interface Representation<D, P extends RepresentationProps = {}> {
 
 export interface Visual<D, P extends RepresentationProps = {}> {
     readonly renderObject: RenderObject
-    create: (ctx: RuntimeContext, data: D, props: P) => Promise<void>
-    update: (ctx: RuntimeContext, props: P) => Promise<boolean>
+    create: (ctx: RuntimeContext, data: D, props?: Partial<P>) => Promise<void>
+    update: (ctx: RuntimeContext, props: Partial<P>) => Promise<boolean>
     getLoci: (pickingId: PickingId) => Loci
     mark: (loci: Loci, action: MarkerAction) => void
     destroy: () => void

+ 3 - 3
src/mol-geo/representation/structure/complex-representation.ts

@@ -17,10 +17,10 @@ import { ComplexVisual } from './complex-visual';
 export function ComplexRepresentation<P extends StructureProps>(visualCtor: () => ComplexVisual<P>): StructureRepresentation<P> {
     let visual: ComplexVisual<P>
 
-    let _props: Required<P>
+    let _props: P
     let _structure: Structure
 
-    function create(structure: Structure, props: P = {} as P) {
+    function create(structure: Structure, props: Partial<P> = {}) {
         _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, structure))
         _props.colorTheme!.structure = structure
 
@@ -41,7 +41,7 @@ export function ComplexRepresentation<P extends StructureProps>(visualCtor: () =
         });
     }
 
-    function update(props: P) {
+    function update(props: Partial<P>) {
         return Task.create('Updating StructureRepresentation', async ctx => {
             _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, _structure))
             _props.colorTheme!.structure = _structure

+ 76 - 3
src/mol-geo/representation/structure/complex-visual.ts

@@ -2,10 +2,83 @@
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
- * @author David Sehnal <david.sehnal@gmail.com>
  */
 
 import { Structure } from 'mol-model/structure';
-import { RepresentationProps, Visual } from '..';
+import { Visual } from '..';
+import { MeshRenderObject } from 'mol-gl/render-object';
+import { Mesh } from '../../shape/mesh';
+import { RuntimeContext } from 'mol-task';
+import { LocationIterator } from './visual/util/location-iterator';
+import { createComplexMeshRenderObject, createColors } from './visual/util/common';
+import { StructureMeshProps, StructureProps } from '.';
+import { deepEqual } from 'mol-util';
+import { updateMeshValues, updateRenderableState } from '../util';
+import { PickingId } from '../../util/picking';
+import { Loci } from 'mol-model/loci';
+import { MarkerAction, MarkerData } from '../../util/marker-data';
 
-export interface  ComplexVisual<P extends RepresentationProps = {}> extends Visual<Structure, P> { }
+export interface  ComplexVisual<P extends StructureProps> extends Visual<Structure, P> { }
+
+export interface ComplexMeshVisualBuilder<P extends StructureMeshProps> {
+    defaultProps: P
+    createMesh(ctx: RuntimeContext, structure: Structure, props: P, mesh?: Mesh): Promise<Mesh>
+    createLocationIterator(structure: Structure): LocationIterator
+    getLoci(pickingId: PickingId, structure: Structure, id: number): Loci
+    mark(loci: Loci, action: MarkerAction, structure: Structure, values: MarkerData): void
+}
+
+export function ComplexMeshVisual<P extends StructureMeshProps>(builder: ComplexMeshVisualBuilder<P>): ComplexVisual<P> {
+    const { defaultProps, createMesh, createLocationIterator, getLoci, mark } = builder
+
+    let renderObject: MeshRenderObject
+    let currentProps: P
+    let mesh: Mesh
+    let currentStructure: Structure
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, structure: Structure, props: Partial<P> = {}) {
+            currentProps = Object.assign({}, defaultProps, props)
+            currentStructure = structure
+
+            mesh = await createMesh(ctx, currentStructure, currentProps, mesh)
+
+            const locationIt = createLocationIterator(structure)
+            renderObject = createComplexMeshRenderObject(structure, mesh, locationIt, currentProps)
+        },
+        async update(ctx: RuntimeContext, props: Partial<P>) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            let updateColor = false
+
+            // TODO create in-place
+            // if (currentProps.radialSegments !== newProps.radialSegments) return false
+
+            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
+                updateColor = true
+            }
+
+            if (updateColor) {
+                createColors(createLocationIterator(currentStructure), newProps.colorTheme, renderObject.values)
+            }
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            currentProps = newProps
+            return false
+        },
+        getLoci(pickingId: PickingId) {
+            return getLoci(pickingId, currentStructure, renderObject.id)
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            mark(loci, action, currentStructure, renderObject.values)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}

+ 8 - 2
src/mol-geo/representation/structure/index.ts

@@ -8,7 +8,7 @@
 import { Structure } from 'mol-model/structure';
 import { Representation, RepresentationProps } from '..';
 import { ColorTheme, SizeTheme } from '../../theme';
-import { DefaultBaseProps } from '../util';
+import { DefaultBaseProps, DefaultMeshProps } from '../util';
 
 export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { }
 
@@ -17,7 +17,13 @@ export const DefaultStructureProps = {
     colorTheme: { name: 'instance-index' } as ColorTheme,
     sizeTheme: { name: 'physical' } as SizeTheme,
 }
-export type StructureProps = Partial<typeof DefaultStructureProps>
+export type StructureProps = typeof DefaultStructureProps
+
+export const DefaultStructureMeshProps = {
+    ...DefaultStructureProps,
+    ...DefaultMeshProps
+}
+export type StructureMeshProps = typeof DefaultStructureMeshProps
 
 export { ComplexRepresentation } from './complex-representation'
 export { UnitsRepresentation } from './units-representation'

+ 8 - 7
src/mol-geo/representation/structure/representation/backbone.ts

@@ -15,11 +15,12 @@ import { PolymerBackboneVisual, DefaultPolymerBackboneProps } from '../visual/po
 export const DefaultBackboneProps = {
     ...DefaultPolymerBackboneProps
 }
-export type BackboneProps = Partial<typeof DefaultBackboneProps>
+export type BackboneProps = typeof DefaultBackboneProps
 
 export function BackboneRepresentation(): StructureRepresentation<BackboneProps> {
     const traceRepr = UnitsRepresentation(PolymerBackboneVisual)
 
+    let currentProps: BackboneProps
     return {
         get renderObjects() {
             return [ ...traceRepr.renderObjects ]
@@ -27,16 +28,16 @@ export function BackboneRepresentation(): StructureRepresentation<BackboneProps>
         get props() {
             return { ...traceRepr.props }
         },
-        create: (structure: Structure, props: BackboneProps = {} as BackboneProps) => {
-            const p = Object.assign({}, DefaultBackboneProps, props)
+        create: (structure: Structure, props: Partial<BackboneProps> = {}) => {
+            currentProps = Object.assign({}, DefaultBackboneProps, props)
             return Task.create('BackboneRepresentation', async ctx => {
-                await traceRepr.create(structure, p).runInContext(ctx)
+                await traceRepr.create(structure, currentProps).runInContext(ctx)
             })
         },
-        update: (props: BackboneProps) => {
-            const p = Object.assign({}, props)
+        update: (props: Partial<BackboneProps>) => {
+            currentProps = Object.assign(currentProps, props)
             return Task.create('Updating BackboneRepresentation', async ctx => {
-                await traceRepr.update(p).runInContext(ctx)
+                await traceRepr.update(currentProps).runInContext(ctx)
             })
         },
         getLoci: (pickingId: PickingId) => {

+ 12 - 11
src/mol-geo/representation/structure/representation/ball-and-stick.ts

@@ -22,13 +22,14 @@ export const DefaultBallAndStickProps = {
     sizeTheme: { name: 'uniform', value: 0.25 } as SizeTheme,
     unitKinds: [ Unit.Kind.Atomic ] as Unit.Kind[]
 }
-export type BallAndStickProps = Partial<typeof DefaultBallAndStickProps>
+export type BallAndStickProps = typeof DefaultBallAndStickProps
 
 export function BallAndStickRepresentation(): StructureRepresentation<BallAndStickProps> {
     const elmementRepr = UnitsRepresentation(ElementSphereVisual)
     const intraLinkRepr = UnitsRepresentation(IntraUnitLinkVisual)
     const interLinkRepr = ComplexRepresentation(InterUnitLinkVisual)
 
+    let currentProps: BallAndStickProps
     return {
         get renderObjects() {
             return [ ...elmementRepr.renderObjects, ...intraLinkRepr.renderObjects, ...interLinkRepr.renderObjects ]
@@ -36,20 +37,20 @@ export function BallAndStickRepresentation(): StructureRepresentation<BallAndSti
         get props() {
             return { ...elmementRepr.props, ...intraLinkRepr.props, ...interLinkRepr.props }
         },
-        create: (structure: Structure, props: BallAndStickProps = {} as BallAndStickProps) => {
-            const p = Object.assign({}, DefaultBallAndStickProps, props)
+        create: (structure: Structure, props: Partial<BallAndStickProps> = {}) => {
+            currentProps = Object.assign({}, DefaultBallAndStickProps, props)
             return Task.create('DistanceRestraintRepresentation', async ctx => {
-                await elmementRepr.create(structure, p).runInContext(ctx)
-                await intraLinkRepr.create(structure, p).runInContext(ctx)
-                await interLinkRepr.create(structure, p).runInContext(ctx)
+                await elmementRepr.create(structure, currentProps).runInContext(ctx)
+                await intraLinkRepr.create(structure, currentProps).runInContext(ctx)
+                await interLinkRepr.create(structure, currentProps).runInContext(ctx)
             })
         },
-        update: (props: BallAndStickProps) => {
-            const p = Object.assign({}, props)
+        update: (props: Partial<BallAndStickProps>) => {
+            currentProps = Object.assign(currentProps, props)
             return Task.create('Updating BallAndStickRepresentation', async ctx => {
-                await elmementRepr.update(p).runInContext(ctx)
-                await intraLinkRepr.update(p).runInContext(ctx)
-                await interLinkRepr.update(p).runInContext(ctx)
+                await elmementRepr.update(currentProps).runInContext(ctx)
+                await intraLinkRepr.update(currentProps).runInContext(ctx)
+                await interLinkRepr.update(currentProps).runInContext(ctx)
             })
         },
         getLoci: (pickingId: PickingId) => {

+ 10 - 9
src/mol-geo/representation/structure/representation/carbohydrate.ts

@@ -17,12 +17,13 @@ export const DefaultCartoonProps = {
     ...DefaultCarbohydrateSymbolProps,
     ...DefaultCarbohydrateLinkProps
 }
-export type CarbohydrateProps = Partial<typeof DefaultCartoonProps>
+export type CarbohydrateProps = typeof DefaultCartoonProps
 
 export function CarbohydrateRepresentation(): StructureRepresentation<CarbohydrateProps> {
     const carbohydrateSymbolRepr = ComplexRepresentation(CarbohydrateSymbolVisual)
     const carbohydrateLinkRepr = ComplexRepresentation(CarbohydrateLinkVisual)
 
+    let currentProps: CarbohydrateProps
     return {
         get renderObjects() {
             return [ ...carbohydrateSymbolRepr.renderObjects, ...carbohydrateLinkRepr.renderObjects ]
@@ -30,18 +31,18 @@ export function CarbohydrateRepresentation(): StructureRepresentation<Carbohydra
         get props() {
             return { ...carbohydrateSymbolRepr.props, ...carbohydrateLinkRepr.props }
         },
-        create: (structure: Structure, props: CarbohydrateProps = {} as CarbohydrateProps) => {
-            const p = Object.assign({}, DefaultCartoonProps, props)
+        create: (structure: Structure, props: Partial<CarbohydrateProps> = {} as CarbohydrateProps) => {
+            currentProps = Object.assign({}, DefaultCartoonProps, props)
             return Task.create('Creating CarbohydrateRepresentation', async ctx => {
-                await carbohydrateSymbolRepr.create(structure, p).runInContext(ctx)
-                await carbohydrateLinkRepr.create(structure, p).runInContext(ctx)
+                await carbohydrateSymbolRepr.create(structure, currentProps).runInContext(ctx)
+                await carbohydrateLinkRepr.create(structure, currentProps).runInContext(ctx)
             })
         },
-        update: (props: CarbohydrateProps) => {
-            const p = Object.assign({}, props)
+        update: (props: Partial<CarbohydrateProps>) => {
+            currentProps = Object.assign(currentProps, props)
             return Task.create('Updating CarbohydrateRepresentation', async ctx => {
-                await carbohydrateSymbolRepr.update(p).runInContext(ctx)
-                await carbohydrateLinkRepr.update(p).runInContext(ctx)
+                await carbohydrateSymbolRepr.update(currentProps).runInContext(ctx)
+                await carbohydrateLinkRepr.update(currentProps).runInContext(ctx)
             })
         },
         getLoci: (pickingId: PickingId) => {

+ 14 - 13
src/mol-geo/representation/structure/representation/cartoon.ts

@@ -21,7 +21,7 @@ export const DefaultCartoonProps = {
     ...DefaultNucleotideBlockProps,
     ...DefaultPolymerDirectionProps
 }
-export type CartoonProps = Partial<typeof DefaultCartoonProps>
+export type CartoonProps = typeof DefaultCartoonProps
 
 export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
     const traceRepr = UnitsRepresentation(PolymerTraceVisual)
@@ -29,6 +29,7 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
     const blockRepr = UnitsRepresentation(NucleotideBlockVisual)
     const directionRepr = UnitsRepresentation(PolymerDirectionVisual)
 
+    let currentProps: CartoonProps
     return {
         get renderObjects() {
             return [ ...traceRepr.renderObjects, ...gapRepr.renderObjects,
@@ -37,22 +38,22 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
         get props() {
             return { ...traceRepr.props, ...gapRepr.props, ...blockRepr.props }
         },
-        create: (structure: Structure, props: CartoonProps = {} as CartoonProps) => {
-            const p = Object.assign({}, DefaultCartoonProps, props)
+        create: (structure: Structure, props: Partial<CartoonProps> = {}) => {
+            currentProps = Object.assign({}, DefaultCartoonProps, props)
             return Task.create('Creating CartoonRepresentation', async ctx => {
-                await traceRepr.create(structure, p).runInContext(ctx)
-                await gapRepr.create(structure, p).runInContext(ctx)
-                await blockRepr.create(structure, p).runInContext(ctx)
-                await directionRepr.create(structure, p).runInContext(ctx)
+                await traceRepr.create(structure, currentProps).runInContext(ctx)
+                await gapRepr.create(structure, currentProps).runInContext(ctx)
+                await blockRepr.create(structure, currentProps).runInContext(ctx)
+                await directionRepr.create(structure, currentProps).runInContext(ctx)
             })
         },
-        update: (props: CartoonProps) => {
-            const p = Object.assign({}, props)
+        update: (props: Partial<CartoonProps>) => {
+            currentProps = Object.assign(currentProps, props)
             return Task.create('Updating CartoonRepresentation', async ctx => {
-                await traceRepr.update(p).runInContext(ctx)
-                await gapRepr.update(p).runInContext(ctx)
-                await blockRepr.update(p).runInContext(ctx)
-                await directionRepr.update(p).runInContext(ctx)
+                await traceRepr.update(currentProps).runInContext(ctx)
+                await gapRepr.update(currentProps).runInContext(ctx)
+                await blockRepr.update(currentProps).runInContext(ctx)
+                await directionRepr.update(currentProps).runInContext(ctx)
             })
         },
         getLoci: (pickingId: PickingId) => {

+ 8 - 8
src/mol-geo/representation/structure/representation/distance-restraint.ts

@@ -15,14 +15,14 @@ import { CrossLinkRestraintVisual, DefaultCrossLinkRestraintProps } from '../vis
 
 export const DefaultDistanceRestraintProps = {
     ...DefaultCrossLinkRestraintProps,
-
     sizeTheme: { name: 'uniform', value: 0.25 } as SizeTheme,
 }
-export type DistanceRestraintProps = Partial<typeof DefaultDistanceRestraintProps>
+export type DistanceRestraintProps = typeof DefaultDistanceRestraintProps
 
 export function DistanceRestraintRepresentation(): StructureRepresentation<DistanceRestraintProps> {
     const crossLinkRepr = ComplexRepresentation(CrossLinkRestraintVisual)
 
+    let currentProps: DistanceRestraintProps
     return {
         get renderObjects() {
             return [ ...crossLinkRepr.renderObjects ]
@@ -30,16 +30,16 @@ export function DistanceRestraintRepresentation(): StructureRepresentation<Dista
         get props() {
             return { ...crossLinkRepr.props }
         },
-        create: (structure: Structure, props: DistanceRestraintProps = {} as DistanceRestraintProps) => {
-            const p = Object.assign({}, DefaultDistanceRestraintProps, props)
+        create: (structure: Structure, props: Partial<DistanceRestraintProps> = {}) => {
+            currentProps = Object.assign({}, DefaultDistanceRestraintProps, props)
             return Task.create('DistanceRestraintRepresentation', async ctx => {
-                await crossLinkRepr.create(structure, p).runInContext(ctx)
+                await crossLinkRepr.create(structure, currentProps).runInContext(ctx)
             })
         },
-        update: (props: DistanceRestraintProps) => {
-            const p = Object.assign({}, props)
+        update: (props: Partial<DistanceRestraintProps>) => {
+            currentProps = Object.assign(currentProps, props)
             return Task.create('Updating DistanceRestraintRepresentation', async ctx => {
-                await crossLinkRepr.update(p).runInContext(ctx)
+                await crossLinkRepr.update(currentProps).runInContext(ctx)
             })
         },
         getLoci: (pickingId: PickingId) => {

+ 2 - 2
src/mol-geo/representation/structure/representation/spacefill.ts

@@ -8,9 +8,9 @@ import { UnitsRepresentation } from '..';
 import { ElementSphereVisual, DefaultElementSphereProps } from '../visual/element-sphere';
 
 export const DefaultSpacefillProps = {
-    ...DefaultElementSphereProps,
+    ...DefaultElementSphereProps
 }
-export type SpacefillProps = Partial<typeof DefaultSpacefillProps>
+export type SpacefillProps = typeof DefaultSpacefillProps
 
 export function SpacefillRepresentation() {
     return UnitsRepresentation(ElementSphereVisual)

+ 3 - 3
src/mol-geo/representation/structure/units-representation.ts

@@ -23,11 +23,11 @@ export interface StructureRepresentation<P extends RepresentationProps = {}> ext
 export function UnitsRepresentation<P extends StructureProps>(visualCtor: () => UnitsVisual<P>): StructureRepresentation<P> {
     let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>()
 
-    let _props: Required<P>
+    let _props: P
     let _structure: Structure
     let _groups: ReadonlyArray<Unit.SymmetryGroup>
 
-    function create(structure: Structure, props: P = {} as P) {
+    function create(structure: Structure, props: Partial<P> = {}) {
         _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, structure))
         _props.colorTheme!.structure = structure
 
@@ -80,7 +80,7 @@ export function UnitsRepresentation<P extends StructureProps>(visualCtor: () =>
         });
     }
 
-    function update(props: P) {
+    function update(props: Partial<P>) {
         return Task.create('Updating StructureRepresentation', async ctx => {
             _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, _structure))
             _props.colorTheme!.structure = _structure

+ 93 - 2
src/mol-geo/representation/structure/units-visual.ts

@@ -2,10 +2,101 @@
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
- * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { RepresentationProps, Visual } from '..';
 import { Unit } from 'mol-model/structure';
+import { RepresentationProps, Visual } from '..';
+import { DefaultStructureMeshProps } from '.';
+import { RuntimeContext } from 'mol-task';
+import { PickingId } from '../../util/picking';
+import { LocationIterator } from './visual/util/location-iterator';
+import { Mesh } from '../../shape/mesh';
+import { MarkerData, MarkerAction } from '../../util/marker-data';
+import { Loci } from 'mol-model/loci';
+import { MeshRenderObject } from 'mol-gl/render-object';
+import { createUnitsMeshRenderObject, createColors } from './visual/util/common';
+import { deepEqual } from 'mol-util';
+import { updateMeshValues, updateRenderableState } from '../util';
 
 export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<Unit.SymmetryGroup, P> { }
+
+export const DefaultUnitsMeshProps = {
+    ...DefaultStructureMeshProps,
+    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+}
+export type UnitsMeshProps = typeof DefaultUnitsMeshProps
+
+export interface UnitsMeshVisualBuilder<P extends UnitsMeshProps> {
+    defaultProps: P
+    createMesh(ctx: RuntimeContext, unit: Unit, props: P, mesh?: Mesh): Promise<Mesh>
+    createLocationIterator(group: Unit.SymmetryGroup): LocationIterator
+    getLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number): Loci
+    mark(loci: Loci, action: MarkerAction, group: Unit.SymmetryGroup, values: MarkerData): void
+}
+
+export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisualBuilder<P>): UnitsVisual<P> {
+    const { defaultProps, createMesh, createLocationIterator, getLoci, mark } = builder
+
+    let renderObject: MeshRenderObject
+    let currentProps: P
+    let mesh: Mesh
+    let currentGroup: Unit.SymmetryGroup
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
+            currentProps = Object.assign({}, defaultProps, props)
+            currentGroup = group
+
+            const unit = group.units[0]
+            mesh = currentProps.unitKinds.includes(unit.kind)
+                ? await createMesh(ctx, unit, currentProps, mesh)
+                : Mesh.createEmpty(mesh)
+
+            const locationIt = createLocationIterator(group)
+            renderObject = createUnitsMeshRenderObject(group, mesh, locationIt, currentProps)
+        },
+        async update(ctx: RuntimeContext, props: Partial<P>) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            let updateColor = false
+
+            // TODO create in-place
+            // if (currentProps.radialSegments !== newProps.radialSegments) return false
+
+            // TODO
+            // if (newProps.detail !== currentProps.detail) {
+            //     const unit = currentGroup.units[0]
+            //     const radius = getElementRadius(unit, newProps.sizeTheme)
+            //     mesh = await createElementSphereMesh(ctx, unit, radius, newProps.detail, mesh)
+            //     ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
+            //     updateColor = true
+            // }
+
+            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
+                updateColor = true
+            }
+
+            if (updateColor) {
+                createColors(createLocationIterator(currentGroup), newProps.colorTheme, renderObject.values)
+            }
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            currentProps = newProps
+            return true
+        },
+        getLoci(pickingId: PickingId) {
+            return getLoci(pickingId, currentGroup, renderObject.id)
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            mark(loci, action, currentGroup, renderObject.values)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}

+ 10 - 52
src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts

@@ -6,22 +6,20 @@
 
 import { ValueCell } from 'mol-util/value-cell'
 
-import { MeshRenderObject } from 'mol-gl/render-object'
 import { Unit, Structure, Link, StructureElement } from 'mol-model/structure';
 import { DefaultStructureProps, ComplexVisual } from '..';
 import { RuntimeContext } from 'mol-task'
-import { createColors, createStructureMeshRenderObject } from './util/common';
 import { Mesh } from '../../../shape/mesh';
 import { PickingId } from '../../../util/picking';
 import { MarkerAction, MarkerData, applyMarkerAction } from '../../../util/marker-data';
 import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci';
 import { SizeTheme } from '../../../theme';
-import { updateMeshValues, updateRenderableState, DefaultMeshProps } from '../../util';
+import { DefaultMeshProps } from '../../util';
 import { Vec3 } from 'mol-math/linear-algebra';
-import { deepEqual } from 'mol-util';
 import { LocationIterator } from './util/location-iterator';
 import { createLinkCylinderMesh, DefaultLinkCylinderProps, LinkCylinderProps } from './util/link';
 import { OrderedSet } from 'mol-data/int';
+import { ComplexMeshVisual } from '../complex-visual';
 
 // TODO create seperate visual
 // for (let i = 0, il = carbohydrates.terminalLinks.length; i < il; ++i) {
@@ -61,56 +59,16 @@ export const DefaultCarbohydrateLinkProps = {
     detail: 0,
     unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
 }
-export type CarbohydrateLinkProps = Partial<typeof DefaultCarbohydrateLinkProps>
+export type CarbohydrateLinkProps = typeof DefaultCarbohydrateLinkProps
 
 export function CarbohydrateLinkVisual(): ComplexVisual<CarbohydrateLinkProps> {
-    let renderObject: MeshRenderObject
-    let currentProps: typeof DefaultCarbohydrateLinkProps
-    let mesh: Mesh
-    let currentStructure: Structure
-
-    return {
-        get renderObject () { return renderObject },
-        async create(ctx: RuntimeContext, structure: Structure, props: CarbohydrateLinkProps = {}) {
-            currentProps = Object.assign({}, DefaultCarbohydrateLinkProps, props)
-            currentStructure = structure
-
-            mesh = await createCarbohydrateLinkCylinderMesh(ctx, currentStructure, currentProps, mesh)
-
-            const locationIt = CarbohydrateLinkIterator(structure)
-            renderObject = createStructureMeshRenderObject(structure, mesh, locationIt, currentProps)
-        },
-        async update(ctx: RuntimeContext, props: CarbohydrateLinkProps) {
-            const newProps = Object.assign({}, currentProps, props)
-
-            if (!renderObject) return false
-
-            let updateColor = false
-
-            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
-                updateColor = true
-            }
-
-            if (updateColor) {
-                createColors(CarbohydrateLinkIterator(currentStructure), newProps.colorTheme, renderObject.values)
-            }
-
-            updateMeshValues(renderObject.values, newProps)
-            updateRenderableState(renderObject.state, newProps)
-
-            currentProps = newProps
-            return false
-        },
-        getLoci(pickingId: PickingId) {
-            return getLinkLoci(pickingId, currentStructure, renderObject.id)
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            markLink(loci, action, currentStructure, renderObject.values)
-        },
-        destroy() {
-            // TODO
-        }
-    }
+    return ComplexMeshVisual<CarbohydrateLinkProps>({
+        defaultProps: DefaultCarbohydrateLinkProps,
+        createMesh: createCarbohydrateLinkCylinderMesh,
+        createLocationIterator: CarbohydrateLinkIterator,
+        getLoci: getLinkLoci,
+        mark: markLink
+    })
 }
 
 function CarbohydrateLinkIterator(structure: Structure): LocationIterator {

+ 11 - 53
src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts

@@ -6,29 +6,27 @@
 
 import { ValueCell } from 'mol-util/value-cell'
 
-import { MeshRenderObject } from 'mol-gl/render-object'
 import { Unit, Structure, StructureElement } from 'mol-model/structure';
 import { DefaultStructureProps, ComplexVisual } from '..';
 import { RuntimeContext } from 'mol-task'
-import { createColors, createStructureMeshRenderObject } from './util/common';
 import { Mesh } from '../../../shape/mesh';
 import { PickingId } from '../../../util/picking';
 import { MarkerAction, MarkerData, applyMarkerAction } from '../../../util/marker-data';
 import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci';
 import { SizeTheme } from '../../../theme';
-import { updateMeshValues, updateRenderableState, DefaultMeshProps } from '../../util';
+import { DefaultMeshProps } from '../../util';
 import { MeshBuilder } from '../../../shape/mesh-builder';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { getSaccharideShape, SaccharideShapes } from 'mol-model/structure/structure/carbohydrates/constants';
-import { deepEqual } from 'mol-util';
 import { LocationIterator } from './util/location-iterator';
 import { OrderedSet } from 'mol-data/int';
+import { ComplexMeshVisual } from '../complex-visual';
 
 const t = Mat4.identity()
 const sVec = Vec3.zero()
 const pd = Vec3.zero()
 
-async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Structure, mesh?: Mesh) {
+async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Structure, props: CarbohydrateSymbolProps, mesh?: Mesh) {
     const builder = MeshBuilder.create(256, 128, mesh)
 
     const carbohydrates = structure.carbohydrates
@@ -124,56 +122,16 @@ export const DefaultCarbohydrateSymbolProps = {
     detail: 0,
     unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
 }
-export type CarbohydrateSymbolProps = Partial<typeof DefaultCarbohydrateSymbolProps>
+export type CarbohydrateSymbolProps = typeof DefaultCarbohydrateSymbolProps
 
 export function CarbohydrateSymbolVisual(): ComplexVisual<CarbohydrateSymbolProps> {
-    let renderObject: MeshRenderObject
-    let currentProps: typeof DefaultCarbohydrateSymbolProps
-    let mesh: Mesh
-    let currentStructure: Structure
-
-    return {
-        get renderObject () { return renderObject },
-        async create(ctx: RuntimeContext, structure: Structure, props: CarbohydrateSymbolProps = {}) {
-            currentProps = Object.assign({}, DefaultCarbohydrateSymbolProps, props)
-            currentStructure = structure
-
-            mesh = await createCarbohydrateSymbolMesh(ctx, currentStructure, mesh)
-
-            const locationIt = CarbohydrateElementIterator(structure)
-            renderObject = createStructureMeshRenderObject(structure, mesh, locationIt, currentProps)
-        },
-        async update(ctx: RuntimeContext, props: CarbohydrateSymbolProps) {
-            const newProps = Object.assign({}, currentProps, props)
-
-            if (!renderObject) return false
-
-            let updateColor = false
-
-            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
-                updateColor = true
-            }
-
-            if (updateColor) {
-                createColors(CarbohydrateElementIterator(currentStructure), newProps.colorTheme, renderObject.values)
-            }
-
-            updateMeshValues(renderObject.values, newProps)
-            updateRenderableState(renderObject.state, newProps)
-
-            currentProps = newProps
-            return false
-        },
-        getLoci(pickingId: PickingId) {
-            return getCarbohydrateLoci(pickingId, currentStructure, renderObject.id)
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            markCarbohydrate(loci, action, currentStructure, renderObject.values)
-        },
-        destroy() {
-            // TODO
-        }
-    }
+    return ComplexMeshVisual<CarbohydrateSymbolProps>({
+        defaultProps: DefaultCarbohydrateSymbolProps,
+        createMesh: createCarbohydrateSymbolMesh,
+        createLocationIterator: CarbohydrateElementIterator,
+        getLoci: getCarbohydrateLoci,
+        mark: markCarbohydrate
+    })
 }
 
 function CarbohydrateElementIterator(structure: Structure): LocationIterator {

+ 28 - 64
src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts

@@ -6,23 +6,18 @@
 
 import { ValueCell } from 'mol-util/value-cell'
 
-import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
 import { Link, Structure, StructureElement } from 'mol-model/structure';
 import { DefaultStructureProps, ComplexVisual } from '..';
 import { RuntimeContext } from 'mol-task'
 import { LinkCylinderProps, DefaultLinkCylinderProps, createLinkCylinderMesh } from './util/link';
-import { MeshValues } from 'mol-gl/renderable';
-import { getMeshData } from '../../../util/mesh-data';
 import { Mesh } from '../../../shape/mesh';
 import { PickingId } from '../../../util/picking';
 import { Vec3 } from 'mol-math/linear-algebra';
-import { createValueColor } from '../../../util/color-data';
 import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
-import { MarkerAction, applyMarkerAction, createMarkers, MarkerData } from '../../../util/marker-data';
+import { MarkerAction, applyMarkerAction, MarkerData } from '../../../util/marker-data';
 import { SizeTheme } from '../../../theme';
-import { createIdentityTransform } from './util/common';
-import { updateMeshValues, updateRenderableState, createMeshValues, createRenderableState } from '../../util';
-// import { chainIdLinkColorData } from '../../../theme/structure/color/chain-id';
+import { ComplexMeshVisual } from '../complex-visual';
+import { LocationIterator } from './util/location-iterator';
 
 async function createCrossLinkRestraintCylinderMesh(ctx: RuntimeContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) {
 
@@ -54,66 +49,35 @@ export const DefaultCrossLinkRestraintProps = {
     flipSided: false,
     flatShaded: false,
 }
-export type CrossLinkRestraintProps = Partial<typeof DefaultCrossLinkRestraintProps>
+export type CrossLinkRestraintProps = typeof DefaultCrossLinkRestraintProps
 
-export function CrossLinkRestraintVisual(): ComplexVisual<CrossLinkRestraintProps> {
-    let renderObject: MeshRenderObject
-    let currentProps: typeof DefaultCrossLinkRestraintProps
-    let mesh: Mesh
-    let currentStructure: Structure
-
-    return {
-        get renderObject () { return renderObject },
-        async create(ctx: RuntimeContext, structure: Structure, props: CrossLinkRestraintProps = {}) {
-            currentProps = Object.assign({}, DefaultCrossLinkRestraintProps, props)
-            currentStructure = structure
-
-            const elementCount = structure.crossLinkRestraints.count
-            const instanceCount = 1
-
-            mesh = await createCrossLinkRestraintCylinderMesh(ctx, structure, currentProps)
-
-            const transforms = createIdentityTransform()
-            const color = createValueColor(0x119911) // TODO
-            const marker = createMarkers(instanceCount * elementCount)
-
-            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
-
-            const values: MeshValues = {
-                ...getMeshData(mesh),
-                ...color,
-                ...marker,
-                aTransform: transforms,
-                elements: mesh.indexBuffer,
-                ...createMeshValues(currentProps, counts),
-            }
-            const state = createRenderableState(currentProps)
-
-            renderObject = createMeshRenderObject(values, state)
-        },
-        async update(ctx: RuntimeContext, props: CrossLinkRestraintProps) {
-            const newProps = Object.assign({}, currentProps, props)
+// TODO update & create in-place
+// if (currentProps.radialSegments !== newProps.radialSegments) return false
 
-            if (!renderObject) return false
-
-            // TODO create in-place
-            if (currentProps.radialSegments !== newProps.radialSegments) return false
-
-            updateMeshValues(renderObject.values, newProps)
-            updateRenderableState(renderObject.state, newProps)
+export function CrossLinkRestraintVisual(): ComplexVisual<CrossLinkRestraintProps> {
+    return ComplexMeshVisual<CrossLinkRestraintProps>({
+        defaultProps: DefaultCrossLinkRestraintProps,
+        createMesh: createCrossLinkRestraintCylinderMesh,
+        createLocationIterator: CrossLinkRestraintIterator,
+        getLoci: getLinkLoci,
+        mark: markLink
+    })
+}
 
-            return false
-        },
-        getLoci(pickingId: PickingId) {
-            return getLinkLoci(pickingId, currentStructure, renderObject.id)
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            markLink(loci, action, currentStructure, renderObject.values)
-        },
-        destroy() {
-            // TODO
-        }
+function CrossLinkRestraintIterator(structure: Structure): LocationIterator {
+    const { pairs } = structure.crossLinkRestraints
+    const elementCount = pairs.length
+    const instanceCount = 1
+    const location = Link.Location()
+    const getLocation = (elementIndex: number, instanceIndex: number) => {
+        const pair = pairs[elementIndex]
+        location.aUnit = pair.unitA
+        location.aIndex = pair.indexA
+        location.bUnit = pair.unitB
+        location.bIndex = pair.indexB
+        return location
     }
+    return LocationIterator(elementCount, instanceCount, getLocation)
 }
 
 function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {

+ 2 - 2
src/mol-geo/representation/structure/visual/element-point.ts

@@ -130,10 +130,10 @@ export default function PointVisual(): UnitsVisual<PointProps> {
             return false
         },
         getLoci(pickingId: PickingId) {
-            return getElementLoci(renderObject.id, currentGroup, pickingId)
+            return getElementLoci(pickingId, currentGroup, renderObject.id)
         },
         mark(loci: Loci, action: MarkerAction) {
-            markElement(renderObject.values.tMarker, currentGroup, loci, action)
+            markElement(loci, action, currentGroup, renderObject.values)
         },
         destroy() {
             // TODO

+ 14 - 83
src/mol-geo/representation/structure/visual/element-sphere.ts

@@ -5,92 +5,23 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { ValueCell } from 'mol-util/value-cell'
-
-import { MeshRenderObject } from 'mol-gl/render-object'
-import { Unit } from 'mol-model/structure';
-import { DefaultStructureProps, UnitsVisual } from '..';
-import { RuntimeContext } from 'mol-task'
-import { createColors, createUnitsMeshRenderObject } from './util/common';
-import { createElementSphereMesh, markElement, getElementRadius, getElementLoci } from './util/element';
-import { deepEqual } from 'mol-util';
-import { Mesh } from '../../../shape/mesh';
-import { PickingId } from '../../../util/picking';
-import { MarkerAction } from '../../../util/marker-data';
-import { Loci } from 'mol-model/loci';
-import { SizeTheme } from '../../../theme';
-import { updateMeshValues, updateRenderableState, DefaultMeshProps } from '../../util';
+import { UnitsVisual } from '..';
+import { createElementSphereMesh, markElement, getElementLoci } from './util/element';
 import { StructureElementIterator } from './util/location-iterator';
+import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual';
 
 export const DefaultElementSphereProps = {
-    ...DefaultMeshProps,
-    ...DefaultStructureProps,
-    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
-    detail: 0,
-    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+    ...DefaultUnitsMeshProps,
+    detail: 0
 }
-export type ElementSphereProps = Partial<typeof DefaultElementSphereProps>
+export type ElementSphereProps = typeof DefaultElementSphereProps
 
 export function ElementSphereVisual(): UnitsVisual<ElementSphereProps> {
-    let renderObject: MeshRenderObject
-    let currentProps: typeof DefaultElementSphereProps
-    let mesh: Mesh
-    let currentGroup: Unit.SymmetryGroup
-
-    return {
-        get renderObject () { return renderObject },
-        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: ElementSphereProps = {}) {
-            currentProps = Object.assign({}, DefaultElementSphereProps, props)
-            currentGroup = group
-
-            const { detail, sizeTheme, unitKinds } = { ...DefaultElementSphereProps, ...props }
-            const unit = group.units[0]
-
-            const radius = getElementRadius(unit, sizeTheme)
-            mesh = unitKinds.includes(unit.kind)
-                ? await createElementSphereMesh(ctx, unit, radius, detail, mesh)
-                : Mesh.createEmpty(mesh)
-
-            const locationIt = StructureElementIterator.fromGroup(group)
-            renderObject = createUnitsMeshRenderObject(group, mesh, locationIt, currentProps)
-        },
-        async update(ctx: RuntimeContext, props: ElementSphereProps) {
-            const newProps = Object.assign({}, currentProps, props)
-
-            if (!renderObject) return false
-
-            let updateColor = false
-
-            if (newProps.detail !== currentProps.detail) {
-                const unit = currentGroup.units[0]
-                const radius = getElementRadius(unit, newProps.sizeTheme)
-                mesh = await createElementSphereMesh(ctx, unit, radius, newProps.detail, mesh)
-                ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
-                updateColor = true
-            }
-
-            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
-                updateColor = true
-            }
-
-            if (updateColor) {
-                createColors(StructureElementIterator.fromGroup(currentGroup), newProps.colorTheme, renderObject.values)
-            }
-
-            updateMeshValues(renderObject.values, newProps)
-            updateRenderableState(renderObject.state, newProps)
-
-            currentProps = newProps
-            return true
-        },
-        getLoci(pickingId: PickingId) {
-            return getElementLoci(renderObject.id, currentGroup, pickingId)
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            markElement(renderObject.values.tMarker, currentGroup, loci, action)
-        },
-        destroy() {
-            // TODO
-        }
-    }
-}
+    return UnitsMeshVisual<ElementSphereProps>({
+        defaultProps: DefaultElementSphereProps,
+        createMesh: createElementSphereMesh,
+        createLocationIterator: StructureElementIterator.fromGroup,
+        getLoci: getElementLoci,
+        mark: markElement
+    })
+}

+ 9 - 55
src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts

@@ -6,7 +6,6 @@
 
 import { ValueCell } from 'mol-util/value-cell'
 
-import { MeshRenderObject } from 'mol-gl/render-object'
 import { Link, Structure, StructureElement } from 'mol-model/structure';
 import { DefaultStructureProps, ComplexVisual } from '..';
 import { RuntimeContext } from 'mol-task'
@@ -17,10 +16,8 @@ import { Vec3 } from 'mol-math/linear-algebra';
 import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
 import { MarkerAction, applyMarkerAction, MarkerData } from '../../../util/marker-data';
 import { SizeTheme } from '../../../theme';
-import { createColors, createStructureMeshRenderObject } from './util/common';
-import { updateMeshValues, updateRenderableState } from '../../util';
 import { LinkIterator } from './util/location-iterator';
-import { deepEqual } from 'mol-util';
+import { ComplexMeshVisual } from '../complex-visual';
 
 async function createInterUnitLinkCylinderMesh(ctx: RuntimeContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) {
     const links = structure.links
@@ -49,59 +46,16 @@ export const DefaultInterUnitLinkProps = {
     ...DefaultLinkCylinderProps,
     sizeTheme: { name: 'physical', factor: 0.3 } as SizeTheme,
 }
-export type InterUnitLinkProps = Partial<typeof DefaultInterUnitLinkProps>
+export type InterUnitLinkProps = typeof DefaultInterUnitLinkProps
 
 export function InterUnitLinkVisual(): ComplexVisual<InterUnitLinkProps> {
-    let renderObject: MeshRenderObject
-    let currentProps: typeof DefaultInterUnitLinkProps
-    let mesh: Mesh
-    let currentStructure: Structure
-
-    return {
-        get renderObject () { return renderObject },
-        async create(ctx: RuntimeContext, structure: Structure, props: InterUnitLinkProps = {}) {
-            currentProps = Object.assign({}, DefaultInterUnitLinkProps, props)
-            currentStructure = structure
-
-            mesh = await createInterUnitLinkCylinderMesh(ctx, structure, currentProps)
-
-            const locationIt = LinkIterator.fromStructure(structure)
-            renderObject = createStructureMeshRenderObject(structure, mesh, locationIt, currentProps)
-        },
-        async update(ctx: RuntimeContext, props: InterUnitLinkProps) {
-            const newProps = Object.assign({}, currentProps, props)
-
-            if (!renderObject) return false
-
-            let updateColor = false
-
-            // TODO create in-place
-            if (currentProps.radialSegments !== newProps.radialSegments) return false
-
-            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
-                updateColor = true
-            }
-
-            if (updateColor) {
-                createColors(LinkIterator.fromStructure(currentStructure), newProps.colorTheme, renderObject.values)
-            }
-
-            updateMeshValues(renderObject.values, newProps)
-            updateRenderableState(renderObject.state, newProps)
-
-            currentProps = newProps
-            return false
-        },
-        getLoci(pickingId: PickingId) {
-            return getLinkLoci(pickingId, currentStructure, renderObject.id)
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            markLink(loci, action, currentStructure, renderObject.values)
-        },
-        destroy() {
-            // TODO
-        }
-    }
+    return ComplexMeshVisual<InterUnitLinkProps>({
+        defaultProps: DefaultInterUnitLinkProps,
+        createMesh: createInterUnitLinkCylinderMesh,
+        createLocationIterator: LinkIterator.fromStructure,
+        getLoci: getLinkLoci,
+        mark: markLink
+    })
 }
 
 function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {

+ 11 - 60
src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts

@@ -7,22 +7,18 @@
 
 import { ValueCell } from 'mol-util/value-cell'
 
-import { MeshRenderObject } from 'mol-gl/render-object'
 import { Unit, Link, StructureElement } from 'mol-model/structure';
-import { UnitsVisual, DefaultStructureProps } from '..';
+import { UnitsVisual } from '..';
 import { RuntimeContext } from 'mol-task'
 import { DefaultLinkCylinderProps, LinkCylinderProps, createLinkCylinderMesh } from './util/link';
 import { Mesh } from '../../../shape/mesh';
 import { PickingId } from '../../../util/picking';
 import { Vec3 } from 'mol-math/linear-algebra';
-// import { createUniformColor } from '../../../util/color-data';
 import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
 import { MarkerAction, applyMarkerAction, MarkerData } from '../../../util/marker-data';
 import { SizeTheme } from '../../../theme';
-import { createColors, createUnitsMeshRenderObject } from './util/common';
-import { updateMeshValues, updateRenderableState, DefaultMeshProps } from '../../util';
 import { LinkIterator } from './util/location-iterator';
-import { deepEqual } from 'mol-util';
+import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual';
 
 async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, props: LinkCylinderProps, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
@@ -62,65 +58,20 @@ async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit,
 }
 
 export const DefaultIntraUnitLinkProps = {
-    ...DefaultMeshProps,
-    ...DefaultStructureProps,
+    ...DefaultUnitsMeshProps,
     ...DefaultLinkCylinderProps,
     sizeTheme: { name: 'physical', factor: 0.3 } as SizeTheme,
 }
-export type IntraUnitLinkProps = Partial<typeof DefaultIntraUnitLinkProps>
+export type IntraUnitLinkProps = typeof DefaultIntraUnitLinkProps
 
 export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> {
-    let renderObject: MeshRenderObject
-    let currentProps: typeof DefaultIntraUnitLinkProps
-    let mesh: Mesh
-    let currentGroup: Unit.SymmetryGroup
-
-    return {
-        get renderObject () { return renderObject },
-        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: IntraUnitLinkProps = {}) {
-            currentProps = Object.assign({}, DefaultIntraUnitLinkProps, props)
-            currentGroup = group
-
-            const unit = group.units[0]
-            mesh = await createIntraUnitLinkCylinderMesh(ctx, unit, currentProps)
-
-            const locationIt = LinkIterator.fromGroup(group)
-            renderObject = createUnitsMeshRenderObject(group, mesh, locationIt, currentProps)
-        },
-        async update(ctx: RuntimeContext, props: IntraUnitLinkProps) {
-            const newProps = Object.assign({}, currentProps, props)
-
-            if (!renderObject) return false
-
-            let updateColor = false
-
-            // TODO create in-place
-            if (currentProps.radialSegments !== newProps.radialSegments) return false
-
-            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
-                updateColor = true
-            }
-
-            if (updateColor) {
-                createColors(LinkIterator.fromGroup(currentGroup), newProps.colorTheme, renderObject.values)
-            }
-
-            updateMeshValues(renderObject.values, newProps)
-            updateRenderableState(renderObject.state, newProps)
-
-            currentProps = newProps
-            return true
-        },
-        getLoci(pickingId: PickingId) {
-            return getLinkLoci(pickingId, currentGroup, renderObject.id)
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            markLink(loci, action, currentGroup, renderObject.values)
-        },
-        destroy() {
-            // TODO
-        }
-    }
+    return UnitsMeshVisual<IntraUnitLinkProps>({
+        defaultProps: DefaultIntraUnitLinkProps,
+        createMesh: createIntraUnitLinkCylinderMesh,
+        createLocationIterator: LinkIterator.fromGroup,
+        getLoci: getLinkLoci,
+        mark: markLink
+    })
 }
 
 function getLinkLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) {

+ 13 - 79
src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts

@@ -4,20 +4,10 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ValueCell } from 'mol-util/value-cell'
-
-import { MeshRenderObject } from 'mol-gl/render-object'
 import { Unit } from 'mol-model/structure';
-import { DefaultStructureProps, UnitsVisual } from '..';
+import { UnitsVisual } from '..';
 import { RuntimeContext } from 'mol-task'
-import { createColors, createUnitsMeshRenderObject } from './util/common';
-import { deepEqual } from 'mol-util';
 import { Mesh } from '../../../shape/mesh';
-import { PickingId } from '../../../util/picking';
-import { MarkerAction } from '../../../util/marker-data';
-import { Loci } from 'mol-model/loci';
-import { SizeTheme } from '../../../theme';
-import { updateMeshValues, updateRenderableState, DefaultMeshProps } from '../../util';
 import { MeshBuilder } from '../../../shape/mesh-builder';
 import { getElementLoci, markElement } from './util/element';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
@@ -25,6 +15,7 @@ import { Segmentation, SortedArray } from 'mol-data/int';
 import { MoleculeType, isNucleic, isPurinBase, isPyrimidineBase } from 'mol-model/structure/model/types';
 import { getElementIndexForAtomId, getElementIndexForAtomRole } from 'mol-model/structure/util';
 import { StructureElementIterator } from './util/location-iterator';
+import { DefaultUnitsMeshProps, UnitsMeshVisual } from '../units-visual';
 
 const p1 = Vec3.zero()
 const p2 = Vec3.zero()
@@ -39,7 +30,7 @@ const center = Vec3.zero()
 const t = Mat4.identity()
 const sVec = Vec3.zero()
 
-async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
+async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
 
     const builder = MeshBuilder.create(256, 128, mesh)
@@ -113,73 +104,16 @@ async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, mesh?:
 }
 
 export const DefaultNucleotideBlockProps = {
-    ...DefaultMeshProps,
-    ...DefaultStructureProps,
-    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
-    detail: 0,
-    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+    ...DefaultUnitsMeshProps
 }
-export type NucleotideBlockProps = Partial<typeof DefaultNucleotideBlockProps>
+export type NucleotideBlockProps = typeof DefaultNucleotideBlockProps
 
 export function NucleotideBlockVisual(): UnitsVisual<NucleotideBlockProps> {
-    let renderObject: MeshRenderObject
-    let currentProps: typeof DefaultNucleotideBlockProps
-    let mesh: Mesh
-    let currentGroup: Unit.SymmetryGroup
-
-    return {
-        get renderObject () { return renderObject },
-        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: NucleotideBlockProps = {}) {
-            currentProps = Object.assign({}, DefaultNucleotideBlockProps, props)
-            currentGroup = group
-
-            const { unitKinds } = { ...DefaultNucleotideBlockProps, ...props }
-            const unit = group.units[0]
-
-            mesh = unitKinds.includes(unit.kind)
-                ? await createNucleotideBlockMesh(ctx, unit, mesh)
-                : Mesh.createEmpty(mesh)
-
-            const locationIt = StructureElementIterator.fromGroup(group)
-            renderObject = createUnitsMeshRenderObject(group, mesh, locationIt, currentProps)
-        },
-        async update(ctx: RuntimeContext, props: NucleotideBlockProps) {
-            const newProps = Object.assign({}, currentProps, props)
-
-            if (!renderObject) return false
-
-            let updateColor = false
-
-            if (newProps.detail !== currentProps.detail) {
-                const unit = currentGroup.units[0]
-                mesh = await createNucleotideBlockMesh(ctx, unit, mesh)
-                ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
-                updateColor = true
-            }
-
-            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
-                updateColor = true
-            }
-
-            if (updateColor) {
-                if (ctx.shouldUpdate) await ctx.update('Computing nucleotide block colors');
-                createColors(StructureElementIterator.fromGroup(currentGroup), newProps.colorTheme, renderObject.values)
-            }
-
-            updateMeshValues(renderObject.values, newProps)
-            updateRenderableState(renderObject.state, newProps)
-
-            currentProps = newProps
-            return true
-        },
-        getLoci(pickingId: PickingId) {
-            return getElementLoci(renderObject.id, currentGroup, pickingId)
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            markElement(renderObject.values.tMarker, currentGroup, loci, action)
-        },
-        destroy() {
-            // TODO
-        }
-    }
-}
+    return UnitsMeshVisual<NucleotideBlockProps>({
+        defaultProps: DefaultNucleotideBlockProps,
+        createMesh: createNucleotideBlockMesh,
+        createLocationIterator: StructureElementIterator.fromGroup,
+        getLoci: getElementLoci,
+        mark: markElement
+    })
+}

+ 13 - 79
src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts

@@ -4,27 +4,18 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ValueCell } from 'mol-util/value-cell'
-
-import { MeshRenderObject } from 'mol-gl/render-object'
 import { Unit } from 'mol-model/structure';
-import { DefaultStructureProps, UnitsVisual } from '..';
+import { UnitsVisual } from '..';
 import { RuntimeContext } from 'mol-task'
-import { createColors, createUnitsMeshRenderObject } from './util/common';
-import { deepEqual } from 'mol-util';
 import { Mesh } from '../../../shape/mesh';
-import { PickingId } from '../../../util/picking';
-import { MarkerAction } from '../../../util/marker-data';
-import { Loci } from 'mol-model/loci';
-import { SizeTheme } from '../../../theme';
-import { updateMeshValues, updateRenderableState, DefaultMeshProps } from '../../util';
 import { MeshBuilder } from '../../../shape/mesh-builder';
 import { getPolymerElementCount, PolymerBackboneIterator } from './util/polymer';
 import { getElementLoci, markElement } from './util/element';
 import { Vec3 } from 'mol-math/linear-algebra';
 import { StructureElementIterator } from './util/location-iterator';
+import { DefaultUnitsMeshProps, UnitsMeshVisual } from '../units-visual';
 
-async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
+async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) {
     const polymerElementCount = getPolymerElementCount(unit)
     if (!polymerElementCount) return Mesh.createEmpty(mesh)
     console.log('polymerElementCount backbone', polymerElementCount)
@@ -59,73 +50,16 @@ async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit
 }
 
 export const DefaultPolymerBackboneProps = {
-    ...DefaultMeshProps,
-    ...DefaultStructureProps,
-    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
-    detail: 0,
-    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+    ...DefaultUnitsMeshProps
 }
-export type PolymerBackboneProps = Partial<typeof DefaultPolymerBackboneProps>
+export type PolymerBackboneProps = typeof DefaultPolymerBackboneProps
 
 export function PolymerBackboneVisual(): UnitsVisual<PolymerBackboneProps> {
-    let renderObject: MeshRenderObject
-    let currentProps: typeof DefaultPolymerBackboneProps
-    let mesh: Mesh
-    let currentGroup: Unit.SymmetryGroup
-
-    return {
-        get renderObject () { return renderObject },
-        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: PolymerBackboneProps = {}) {
-            currentProps = Object.assign({}, DefaultPolymerBackboneProps, props)
-            currentGroup = group
-
-            const { unitKinds } = { ...DefaultPolymerBackboneProps, ...props }
-            const unit = group.units[0]
-
-            mesh = unitKinds.includes(unit.kind)
-                ? await createPolymerBackboneCylinderMesh(ctx, unit, mesh)
-                : Mesh.createEmpty(mesh)
-
-            const locationIt = StructureElementIterator.fromGroup(group)
-            renderObject = createUnitsMeshRenderObject(group, mesh, locationIt, currentProps)
-        },
-        async update(ctx: RuntimeContext, props: PolymerBackboneProps) {
-            const newProps = Object.assign({}, currentProps, props)
-
-            if (!renderObject) return false
-
-            let updateColor = false
-
-            if (newProps.detail !== currentProps.detail) {
-                const unit = currentGroup.units[0]
-                mesh = await createPolymerBackboneCylinderMesh(ctx, unit, mesh)
-                ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
-                updateColor = true
-            }
-
-            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
-                updateColor = true
-            }
-
-            if (updateColor) {
-                if (ctx.shouldUpdate) await ctx.update('Computing trace colors');
-                createColors(StructureElementIterator.fromGroup(currentGroup), newProps.colorTheme, renderObject.values)
-            }
-
-            updateMeshValues(renderObject.values, newProps)
-            updateRenderableState(renderObject.state, newProps)
-
-            currentProps = newProps
-            return true
-        },
-        getLoci(pickingId: PickingId) {
-            return getElementLoci(renderObject.id, currentGroup, pickingId)
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            markElement(renderObject.values.tMarker, currentGroup, loci, action)
-        },
-        destroy() {
-            // TODO
-        }
-    }
-}
+    return UnitsMeshVisual<PolymerBackboneProps>({
+        defaultProps: DefaultPolymerBackboneProps,
+        createMesh: createPolymerBackboneCylinderMesh,
+        createLocationIterator: StructureElementIterator.fromGroup,
+        getLoci: getElementLoci,
+        mark: markElement
+    })
+}

+ 15 - 88
src/mol-geo/representation/structure/visual/polymer-direction-wedge.ts

@@ -4,27 +4,17 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ValueCell } from 'mol-util/value-cell'
-
-import { MeshRenderObject } from 'mol-gl/render-object'
-import { Unit, StructureElement } from 'mol-model/structure';
-import { DefaultStructureProps, UnitsVisual } from '..';
+import { Unit } from 'mol-model/structure';
+import { UnitsVisual } from '..';
 import { RuntimeContext } from 'mol-task'
-import { createColors, createUnitsMeshRenderObject } from './util/common';
-import { markElement } from './util/element';
-import { deepEqual } from 'mol-util';
+import { markElement, getElementLoci } from './util/element';
 import { Mesh } from '../../../shape/mesh';
-import { PickingId } from '../../../util/picking';
-import { OrderedSet } from 'mol-data/int';
-import { MarkerAction } from '../../../util/marker-data';
-import { Loci, EmptyLoci } from 'mol-model/loci';
-import { SizeTheme } from '../../../theme';
-import { updateMeshValues, updateRenderableState, DefaultMeshProps } from '../../util';
 import { MeshBuilder } from '../../../shape/mesh-builder';
 import { getPolymerElementCount, PolymerTraceIterator, createCurveSegmentState, interpolateCurveSegment } from './util/polymer';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/types';
 import { StructureElementIterator } from './util/location-iterator';
+import { DefaultUnitsMeshProps, UnitsMeshVisual } from '../units-visual';
 
 const t = Mat4.identity()
 const sVec = Vec3.zero()
@@ -32,7 +22,7 @@ const n0 = Vec3.zero()
 const n1 = Vec3.zero()
 const upVec = Vec3.zero()
 
-async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
+async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) {
     const polymerElementCount = getPolymerElementCount(unit)
     console.log('polymerElementCount direction', polymerElementCount)
     if (!polymerElementCount) return Mesh.createEmpty(mesh)
@@ -87,79 +77,16 @@ async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit,
 }
 
 export const DefaultPolymerDirectionProps = {
-    ...DefaultMeshProps,
-    ...DefaultStructureProps,
-    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
-    detail: 0,
-    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+    ...DefaultUnitsMeshProps
 }
-export type PolymerDirectionProps = Partial<typeof DefaultPolymerDirectionProps>
+export type PolymerDirectionProps = typeof DefaultPolymerDirectionProps
 
 export function PolymerDirectionVisual(): UnitsVisual<PolymerDirectionProps> {
-    let renderObject: MeshRenderObject
-    let currentProps: typeof DefaultPolymerDirectionProps
-    let mesh: Mesh
-    let currentGroup: Unit.SymmetryGroup
-
-    return {
-        get renderObject () { return renderObject },
-        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: PolymerDirectionProps = {}) {
-            currentProps = Object.assign({}, DefaultPolymerDirectionProps, props)
-            currentGroup = group
-
-            const { unitKinds } = { ...DefaultPolymerDirectionProps, ...props }
-            const unit = group.units[0]
-
-            mesh = unitKinds.includes(unit.kind)
-                ? await createPolymerDirectionWedgeMesh(ctx, unit, mesh)
-                : Mesh.createEmpty(mesh)
-
-            const locationIt = StructureElementIterator.fromGroup(group)
-            renderObject = createUnitsMeshRenderObject(group, mesh, locationIt, currentProps)
-        },
-        async update(ctx: RuntimeContext, props: PolymerDirectionProps) {
-            const newProps = Object.assign({}, currentProps, props)
-
-            if (!renderObject) return false
-
-            let updateColor = false
-
-            if (newProps.detail !== currentProps.detail) {
-                const unit = currentGroup.units[0]
-                mesh = await createPolymerDirectionWedgeMesh(ctx, unit, mesh)
-                ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
-                updateColor = true
-            }
-
-            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
-                updateColor = true
-            }
-
-            if (updateColor) {
-                if (ctx.shouldUpdate) await ctx.update('Computing direction colors');
-                createColors(StructureElementIterator.fromGroup(currentGroup), newProps.colorTheme, renderObject.values)
-            }
-
-            updateMeshValues(renderObject.values, newProps)
-            updateRenderableState(renderObject.state, newProps)
-
-            currentProps = newProps
-            return true
-        },
-        getLoci(pickingId: PickingId) {
-            const { objectId, instanceId, elementId } = pickingId
-            if (renderObject.id === objectId) {
-                const unit = currentGroup.units[instanceId]
-                const indices = OrderedSet.ofSingleton(elementId as StructureElement.UnitIndex);
-                return StructureElement.Loci([{ unit, indices }])
-            }
-            return EmptyLoci
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            markElement(renderObject.values.tMarker, currentGroup, loci, action)
-        },
-        destroy() {
-            // TODO
-        }
-    }
-}
+    return UnitsMeshVisual<PolymerDirectionProps>({
+        defaultProps: DefaultPolymerDirectionProps,
+        createMesh: createPolymerDirectionWedgeMesh,
+        createLocationIterator: StructureElementIterator.fromGroup,
+        getLoci: getElementLoci,
+        mark: markElement
+    })
+}

+ 13 - 79
src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts

@@ -4,27 +4,18 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ValueCell } from 'mol-util/value-cell'
-
-import { MeshRenderObject } from 'mol-gl/render-object'
 import { Unit } from 'mol-model/structure';
-import { DefaultStructureProps, UnitsVisual } from '..';
+import { UnitsVisual } from '..';
 import { RuntimeContext } from 'mol-task'
-import { createColors, createUnitsMeshRenderObject } from './util/common';
-import { deepEqual } from 'mol-util';
 import { Mesh } from '../../../shape/mesh';
-import { PickingId } from '../../../util/picking';
-import { MarkerAction } from '../../../util/marker-data';
-import { Loci } from 'mol-model/loci';
-import { SizeTheme } from '../../../theme';
-import { updateMeshValues, updateRenderableState, DefaultMeshProps } from '../../util';
 import { MeshBuilder } from '../../../shape/mesh-builder';
 import { getPolymerGapCount, PolymerGapIterator } from './util/polymer';
 import { getElementLoci, markElement } from './util/element';
 import { Vec3 } from 'mol-math/linear-algebra';
 import { StructureElementIterator } from './util/location-iterator';
+import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual';
 
-async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
+async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) {
     const polymerGapCount = getPolymerGapCount(unit)
     if (!polymerGapCount) return Mesh.createEmpty(mesh)
     console.log('polymerGapCount', polymerGapCount)
@@ -65,73 +56,16 @@ async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, mes
 }
 
 export const DefaultPolymerGapProps = {
-    ...DefaultMeshProps,
-    ...DefaultStructureProps,
-    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
-    detail: 0,
-    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+    ...DefaultUnitsMeshProps
 }
-export type PolymerGapProps = Partial<typeof DefaultPolymerGapProps>
+export type PolymerGapProps = typeof DefaultPolymerGapProps
 
 export function PolymerGapVisual(): UnitsVisual<PolymerGapProps> {
-    let renderObject: MeshRenderObject
-    let currentProps: typeof DefaultPolymerGapProps
-    let mesh: Mesh
-    let currentGroup: Unit.SymmetryGroup
-
-    return {
-        get renderObject () { return renderObject },
-        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: PolymerGapProps = {}) {
-            currentProps = Object.assign({}, DefaultPolymerGapProps, props)
-            currentGroup = group
-
-            const { unitKinds } = { ...DefaultPolymerGapProps, ...props }
-            const unit = group.units[0]
-
-            mesh = unitKinds.includes(unit.kind)
-                ? await createPolymerGapCylinderMesh(ctx, unit, mesh)
-                : Mesh.createEmpty(mesh)
-
-            const locationIt = StructureElementIterator.fromGroup(group)
-            renderObject = createUnitsMeshRenderObject(group, mesh, locationIt, currentProps)
-        },
-        async update(ctx: RuntimeContext, props: PolymerGapProps) {
-            const newProps = Object.assign({}, currentProps, props)
-
-            if (!renderObject) return false
-
-            let updateColor = false
-
-            if (newProps.detail !== currentProps.detail) {
-                const unit = currentGroup.units[0]
-                mesh = await createPolymerGapCylinderMesh(ctx, unit, mesh)
-                ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
-                updateColor = true
-            }
-
-            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
-                updateColor = true
-            }
-
-            if (updateColor) {
-                if (ctx.shouldUpdate) await ctx.update('Computing trace colors');
-                createColors(StructureElementIterator.fromGroup(currentGroup), newProps.colorTheme, renderObject.values)
-            }
-
-            updateMeshValues(renderObject.values, newProps)
-            updateRenderableState(renderObject.state, newProps)
-
-            currentProps = newProps
-            return true
-        },
-        getLoci(pickingId: PickingId) {
-            return getElementLoci(renderObject.id, currentGroup, pickingId)
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            markElement(renderObject.values.tMarker, currentGroup, loci, action)
-        },
-        destroy() {
-            // TODO
-        }
-    }
-}
+    return UnitsMeshVisual<PolymerGapProps>({
+        defaultProps: DefaultPolymerGapProps,
+        createMesh: createPolymerGapCylinderMesh,
+        createLocationIterator: StructureElementIterator.fromGroup,
+        getLoci: getElementLoci,
+        mark: markElement
+    })
+}

+ 15 - 88
src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts

@@ -4,30 +4,20 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ValueCell } from 'mol-util/value-cell'
-
-import { MeshRenderObject } from 'mol-gl/render-object'
-import { Unit, StructureElement } from 'mol-model/structure';
-import { DefaultStructureProps, UnitsVisual } from '..';
+import { Unit } from 'mol-model/structure';
+import { UnitsVisual } from '..';
 import { RuntimeContext } from 'mol-task'
-import { createColors, createUnitsMeshRenderObject } from './util/common';
-import { markElement } from './util/element';
-import { deepEqual } from 'mol-util';
+import { markElement, getElementLoci } from './util/element';
 import { Mesh } from '../../../shape/mesh';
-import { PickingId } from '../../../util/picking';
-import { OrderedSet } from 'mol-data/int';
-import { MarkerAction } from '../../../util/marker-data';
-import { Loci, EmptyLoci } from 'mol-model/loci';
-import { SizeTheme } from '../../../theme';
-import { updateMeshValues, updateRenderableState, DefaultMeshProps } from '../../util';
 import { MeshBuilder } from '../../../shape/mesh-builder';
 import { getPolymerElementCount, PolymerTraceIterator, createCurveSegmentState, interpolateCurveSegment } from './util/polymer';
 import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/types';
 import { StructureElementIterator } from './util/location-iterator';
+import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual';
 
 // TODO handle polymer ends properly
 
-async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
+async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) {
     const polymerElementCount = getPolymerElementCount(unit)
     console.log('polymerElementCount trace', polymerElementCount)
     if (!polymerElementCount) return Mesh.createEmpty(mesh)
@@ -80,79 +70,16 @@ async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, mesh?: Me
 }
 
 export const DefaultPolymerTraceProps = {
-    ...DefaultMeshProps,
-    ...DefaultStructureProps,
-    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
-    detail: 0,
-    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+    ...DefaultUnitsMeshProps
 }
-export type PolymerTraceProps = Partial<typeof DefaultPolymerTraceProps>
+export type PolymerTraceProps = typeof DefaultPolymerTraceProps
 
 export function PolymerTraceVisual(): UnitsVisual<PolymerTraceProps> {
-    let renderObject: MeshRenderObject
-    let currentProps: typeof DefaultPolymerTraceProps
-    let mesh: Mesh
-    let currentGroup: Unit.SymmetryGroup
-
-    return {
-        get renderObject () { return renderObject },
-        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: PolymerTraceProps = {}) {
-            currentProps = Object.assign({}, DefaultPolymerTraceProps, props)
-            currentGroup = group
-
-            const { unitKinds } = { ...DefaultPolymerTraceProps, ...props }
-            const unit = group.units[0]
-
-            mesh = unitKinds.includes(unit.kind)
-                ? await createPolymerTraceMesh(ctx, unit, mesh)
-                : Mesh.createEmpty(mesh)
-
-            const locationIt = StructureElementIterator.fromGroup(group)
-            renderObject = createUnitsMeshRenderObject(group, mesh, locationIt, currentProps)
-        },
-        async update(ctx: RuntimeContext, props: PolymerTraceProps) {
-            const newProps = Object.assign({}, currentProps, props)
-
-            if (!renderObject) return false
-
-            let updateColor = false
-
-            if (newProps.detail !== currentProps.detail) {
-                const unit = currentGroup.units[0]
-                mesh = await createPolymerTraceMesh(ctx, unit, mesh)
-                ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
-                updateColor = true
-            }
-
-            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
-                updateColor = true
-            }
-
-            if (updateColor) {
-                if (ctx.shouldUpdate) await ctx.update('Computing trace colors');
-                createColors(StructureElementIterator.fromGroup(currentGroup), newProps.colorTheme, renderObject.values)
-            }
-
-            updateMeshValues(renderObject.values, newProps)
-            updateRenderableState(renderObject.state, newProps)
-
-            currentProps = newProps
-            return true
-        },
-        getLoci(pickingId: PickingId) {
-            const { objectId, instanceId, elementId } = pickingId
-            if (renderObject.id === objectId) {
-                const unit = currentGroup.units[instanceId]
-                const indices = OrderedSet.ofSingleton(elementId as StructureElement.UnitIndex);
-                return StructureElement.Loci([{ unit, indices }])
-            }
-            return EmptyLoci
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            markElement(renderObject.values.tMarker, currentGroup, loci, action)
-        },
-        destroy() {
-            // TODO
-        }
-    }
-}
+    return UnitsMeshVisual<PolymerTraceProps>({
+        defaultProps: DefaultPolymerTraceProps,
+        createMesh: createPolymerTraceMesh,
+        createLocationIterator: StructureElementIterator.fromGroup,
+        getLoci: getElementLoci,
+        mark: markElement
+    })
+}

+ 8 - 3
src/mol-geo/representation/structure/visual/util/common.ts

@@ -86,7 +86,7 @@ function _createMeshValues(transforms: ValueCell<Float32Array>, mesh: Mesh, loca
     }
 }
 
-export function createStructureMeshValues(structure: Structure, mesh: Mesh, locationIt: LocationIterator, props: StructureMeshProps): MeshValues {
+export function createComplexMeshValues(structure: Structure, mesh: Mesh, locationIt: LocationIterator, props: StructureMeshProps): MeshValues {
     const transforms = createIdentityTransform()
     return _createMeshValues(transforms, mesh, locationIt, props)
 }
@@ -96,8 +96,8 @@ export function createUnitsMeshValues(group: Unit.SymmetryGroup, mesh: Mesh, loc
     return _createMeshValues(transforms, mesh, locationIt, props)
 }
 
-export function createStructureMeshRenderObject(structure: Structure, mesh: Mesh, locationIt: LocationIterator, props: StructureMeshProps) {
-    const values = createStructureMeshValues(structure, mesh, locationIt, props)
+export function createComplexMeshRenderObject(structure: Structure, mesh: Mesh, locationIt: LocationIterator, props: StructureMeshProps) {
+    const values = createComplexMeshValues(structure, mesh, locationIt, props)
     const state = createRenderableState(props)
     return createMeshRenderObject(values, state)
 }
@@ -106,4 +106,9 @@ export function createUnitsMeshRenderObject(group: Unit.SymmetryGroup, mesh: Mes
     const values = createUnitsMeshValues(group, mesh, locationIt, props)
     const state = createRenderableState(props)
     return createMeshRenderObject(values, state)
+}
+
+export function updateComplexMeshRenderObject(structure: Structure, mesh: Mesh, locationIt: LocationIterator, props: StructureMeshProps): MeshValues {
+    const transforms = createIdentityTransform()
+    return _createMeshValues(transforms, mesh, locationIt, props)
 }

+ 16 - 6
src/mol-geo/representation/structure/visual/util/element.ts

@@ -12,9 +12,8 @@ import { sphereVertexCount } from '../../../../primitive/sphere';
 import { Mesh } from '../../../../shape/mesh';
 import { MeshBuilder } from '../../../../shape/mesh-builder';
 import { ValueCell, defaults } from 'mol-util';
-import { TextureImage } from 'mol-gl/renderable/util';
 import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
-import { MarkerAction, applyMarkerAction } from '../../../../util/marker-data';
+import { MarkerAction, applyMarkerAction, MarkerData } from '../../../../util/marker-data';
 import { Interval, OrderedSet } from 'mol-data/int';
 import { getPhysicalRadius } from '../../../../theme/structure/size/physical';
 import { PickingId } from '../../../../util/picking';
@@ -30,8 +29,16 @@ export function getElementRadius(unit: Unit, props: SizeTheme): StructureElement
     }
 }
 
-export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, radius: StructureElement.Property<number>, detail: number, mesh?: Mesh) {
+export interface ElementSphereMeshProps {
+    sizeTheme: SizeTheme,
+    detail: number,
+}
+
+export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, props: ElementSphereMeshProps, mesh?: Mesh) {
+    const { detail, sizeTheme } = props
+
     const { elements } = unit;
+    const radius = getElementRadius(unit, sizeTheme)
     const elementCount = elements.length;
     const vertexCount = elementCount * sphereVertexCount(detail)
     const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh)
@@ -56,10 +63,13 @@ export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, r
     return meshBuilder.getMesh()
 }
 
-export function markElement(tMarker: ValueCell<TextureImage>, group: Unit.SymmetryGroup, loci: Loci, action: MarkerAction) {
-    let changed = false
+export function markElement(loci: Loci, action: MarkerAction, group: Unit.SymmetryGroup, values: MarkerData) {
+    const tMarker = values.tMarker
+
     const elementCount = group.elements.length
     const instanceCount = group.units.length
+
+    let changed = false
     const array = tMarker.ref.value.array
     if (isEveryLoci(loci)) {
         applyMarkerAction(array, 0, elementCount * instanceCount, action)
@@ -92,7 +102,7 @@ export function markElement(tMarker: ValueCell<TextureImage>, group: Unit.Symmet
     }
 }
 
-export function getElementLoci(id: number, group: Unit.SymmetryGroup, pickingId: PickingId) {
+export function getElementLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) {
     const { objectId, instanceId, elementId } = pickingId
     if (id === objectId) {
         const unit = group.units[instanceId]

+ 2 - 2
src/mol-geo/representation/util.ts

@@ -18,7 +18,7 @@ export const DefaultBaseProps = {
     useFog: true,
     quality: 'auto' as VisualQuality
 }
-export type BaseProps = Partial<typeof DefaultBaseProps>
+export type BaseProps = typeof DefaultBaseProps
 
 export const DefaultMeshProps = {
     ...DefaultBaseProps,
@@ -26,7 +26,7 @@ export const DefaultMeshProps = {
     flipSided: false,
     flatShaded: false,
 }
-export type MeshProps = Partial<typeof DefaultMeshProps>
+export type MeshProps = typeof DefaultMeshProps
 
 type Counts = { drawCount: number, elementCount: number, instanceCount: number }
 

+ 10 - 4
src/mol-geo/representation/volume/index.ts

@@ -11,18 +11,24 @@ import { VolumeData } from 'mol-model/volume';
 import { PickingId } from '../../util/picking';
 import { Loci, EmptyLoci } from 'mol-model/loci';
 import { MarkerAction } from '../../util/marker-data';
+import { DefaultBaseProps } from '../util';
 
 export interface VolumeVisual<P extends RepresentationProps = {}> extends Visual<VolumeData, P> { }
 
 export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> { }
 
-export function VolumeRepresentation<P>(visualCtor: (volumeData: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> {
+export const DefaultVolumeProps = {
+    ...DefaultBaseProps
+}
+export type VolumeProps = typeof DefaultVolumeProps
+
+export function VolumeRepresentation<P extends VolumeProps>(visualCtor: (volumeData: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> {
     const renderObjects: RenderObject[] = []
     let _volumeData: VolumeData
     let _props: P
 
-    function create(volumeData: VolumeData, props: P = {} as P) {
-        _props = props
+    function create(volumeData: VolumeData, props: Partial<P> = {}) {
+        _props = Object.assign({}, DefaultVolumeProps, _props, props)
         return Task.create('VolumeRepresentation.create', async ctx => {
             _volumeData = volumeData
             const visual = visualCtor(_volumeData)
@@ -31,7 +37,7 @@ export function VolumeRepresentation<P>(visualCtor: (volumeData: VolumeData) =>
         });
     }
 
-    function update(props: P) {
+    function update(props: Partial<P>) {
         return Task.create('VolumeRepresentation.update', async ctx => {})
     }
 

+ 6 - 5
src/mol-model/structure/structure/unit/pair-restraints/data.ts

@@ -5,6 +5,7 @@
  */
 
 import Unit from '../../unit';
+import { StructureElement } from '../../../structure';
 
 const emptyArray: number[] = []
 
@@ -13,13 +14,13 @@ class CrossLinkRestraints {
     private readonly pairKeyIndices: Map<string, number[]>
 
     /** Indices into this.pairs */
-    getPairIndices(indexA: number, unitA: Unit, indexB: number, unitB: Unit): ReadonlyArray<number> {
+    getPairIndices(indexA: StructureElement.UnitIndex, unitA: Unit, indexB: StructureElement.UnitIndex, unitB: Unit): ReadonlyArray<number> {
         const key = CrossLinkRestraints.getPairKey(indexA, unitA, indexB, unitB)
         const indices = this.pairKeyIndices.get(key)
         return indices !== undefined ? indices : emptyArray
     }
 
-    getPairs(indexA: number, unitA: Unit, indexB: number, unitB: Unit): CrossLinkRestraints.Pair[] | undefined {
+    getPairs(indexA: StructureElement.UnitIndex, unitA: Unit, indexB: StructureElement.UnitIndex, unitB: Unit): CrossLinkRestraints.Pair[] | undefined {
         const indices = this.getPairIndices(indexA, unitA, indexB, unitB)
         return indices.length ? indices.map(idx => this.pairs[idx]) : undefined
     }
@@ -42,8 +43,8 @@ namespace CrossLinkRestraints {
     export interface Pair {
         readonly unitA: Unit,
         readonly unitB: Unit,
-        readonly indexA: number,
-        readonly indexB: number,
+        readonly indexA: StructureElement.UnitIndex,
+        readonly indexB: StructureElement.UnitIndex,
 
         readonly restraintType: 'harmonic' | 'upper bound' | 'lower bound',
         readonly distanceThreshold: number,
@@ -52,7 +53,7 @@ namespace CrossLinkRestraints {
         readonly sigma2: number,
     }
 
-    export function getPairKey(indexA: number, unitA: Unit, indexB: number, unitB: Unit) {
+    export function getPairKey(indexA: StructureElement.UnitIndex, unitA: Unit, indexB: StructureElement.UnitIndex, unitB: Unit) {
         return `${indexA}|${unitA.id}|${indexB}|${unitB.id}`
     }
 }

+ 7 - 6
src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts

@@ -8,6 +8,7 @@ import Unit from '../../unit';
 import Structure from '../../structure';
 import { IHMCrossLinkRestraint } from '../../../model/formats/mmcif/pair-restraint';
 import { CrossLinkRestraints } from './data';
+import { StructureElement } from '../../../structure';
 
 function _addRestraints(map: Map<number, number>, unit: Unit, restraints: IHMCrossLinkRestraint) {
     const { elements } = unit;
@@ -27,8 +28,8 @@ function extractInter(pairs: CrossLinkRestraints.Pair[], unitA: Unit, unitB: Uni
     const restraints = IHMCrossLinkRestraint.fromModel(unitA.model)
     if (!restraints) return
 
-    const rA = new Map<number, number>();
-    const rB = new Map<number, number>();
+    const rA = new Map<number, StructureElement.UnitIndex>();
+    const rB = new Map<number, StructureElement.UnitIndex>();
     _addRestraints(rA, unitA, restraints)
     _addRestraints(rB, unitB, restraints)
 
@@ -53,14 +54,14 @@ function extractIntra(pairs: CrossLinkRestraints.Pair[], unit: Unit) {
     const elementCount = elements.length;
     const kind = unit.kind
 
-    const r = new Map<number, number[]>();
+    const r = new Map<number, StructureElement.UnitIndex[]>();
 
     for (let i = 0; i < elementCount; i++) {
         const e = elements[i];
         restraints.getIndicesByElement(e, kind).forEach(ri => {
             const il = r.get(ri)
-            if (il) il.push(i)
-            else r.set(ri, [i])
+            if (il) il.push(i as StructureElement.UnitIndex)
+            else r.set(ri, [i as StructureElement.UnitIndex])
         })
     }
 
@@ -74,7 +75,7 @@ function extractIntra(pairs: CrossLinkRestraints.Pair[], unit: Unit) {
     })
 }
 
-function createCrossLinkRestraint(unitA: Unit, indexA: number, unitB: Unit, indexB: number, restraints: IHMCrossLinkRestraint, row: number): CrossLinkRestraints.Pair {
+function createCrossLinkRestraint(unitA: Unit, indexA: StructureElement.UnitIndex, unitB: Unit, indexB: StructureElement.UnitIndex, restraints: IHMCrossLinkRestraint, row: number): CrossLinkRestraints.Pair {
     return {
         unitA, indexA, unitB, indexB,
 

+ 6 - 6
src/mol-view/stage.ts

@@ -17,14 +17,14 @@ import { DistanceRestraintProps } from 'mol-geo/representation/structure/represe
 import { BackboneProps } from 'mol-geo/representation/structure/representation/backbone';
 // import { Queries as Q, StructureProperties as SP, Query, Selection } from 'mol-model/structure';
 
-const spacefillProps: SpacefillProps = {
+const spacefillProps: Partial<SpacefillProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     quality: 'auto',
     useFog: false
 }
 
-const ballAndStickProps: BallAndStickProps = {
+const ballAndStickProps: Partial<BallAndStickProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     sizeTheme: { name: 'uniform', value: 0.15 },
@@ -33,7 +33,7 @@ const ballAndStickProps: BallAndStickProps = {
     useFog: false
 }
 
-const distanceRestraintProps: DistanceRestraintProps = {
+const distanceRestraintProps: Partial<DistanceRestraintProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     linkRadius: 0.5,
@@ -41,7 +41,7 @@ const distanceRestraintProps: DistanceRestraintProps = {
     useFog: false
 }
 
-const backboneProps: BackboneProps = {
+const backboneProps: Partial<BackboneProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     // colorTheme: { name: 'uniform', value: 0xFF0000 },
@@ -50,7 +50,7 @@ const backboneProps: BackboneProps = {
     alpha: 0.5
 }
 
-const cartoonProps: CartoonProps = {
+const cartoonProps: Partial<CartoonProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
     // colorTheme: { name: 'uniform', value: 0x2200CC },
@@ -58,7 +58,7 @@ const cartoonProps: CartoonProps = {
     useFog: false
 }
 
-const carbohydrateProps: CartoonProps = {
+const carbohydrateProps: Partial<CartoonProps> = {
     doubleSided: true,
     colorTheme: { name: 'carbohydrate-symbol' },
     // colorTheme: { name: 'uniform', value: 0x2200CC },

+ 6 - 6
src/mol-view/state/entity.ts

@@ -122,41 +122,41 @@ export namespace StructureEntity {
 export type SpacefillEntity = StateEntity<StructureRepresentation<SpacefillProps>, 'spacefill'>
 export namespace SpacefillEntity {
     export function ofRepr(ctx: StateContext, repr: StructureRepresentation<SpacefillProps>): SpacefillEntity {
-        return StateEntity.create(ctx, 'spacefill', repr )
+        return StateEntity.create(ctx, 'spacefill', repr)
     }
 }
 
 export type BallAndStickEntity = StateEntity<StructureRepresentation<BallAndStickProps>, 'ballandstick'>
 export namespace BallAndStickEntity {
     export function ofRepr(ctx: StateContext, repr: StructureRepresentation<BallAndStickProps>): BallAndStickEntity {
-        return StateEntity.create(ctx, 'ballandstick', repr )
+        return StateEntity.create(ctx, 'ballandstick', repr)
     }
 }
 
 export type DistanceRestraintEntity = StateEntity<StructureRepresentation<DistanceRestraintProps>, 'distancerestraint'>
 export namespace DistanceRestraintEntity {
     export function ofRepr(ctx: StateContext, repr: StructureRepresentation<DistanceRestraintProps>): DistanceRestraintEntity {
-        return StateEntity.create(ctx, 'distancerestraint', repr )
+        return StateEntity.create(ctx, 'distancerestraint', repr)
     }
 }
 
 export type BackboneEntity = StateEntity<StructureRepresentation<BackboneProps>, 'backbone'>
 export namespace BackboneEntity {
     export function ofRepr(ctx: StateContext, repr: StructureRepresentation<BackboneProps>): BackboneEntity {
-        return StateEntity.create(ctx, 'backbone', repr )
+        return StateEntity.create(ctx, 'backbone', repr)
     }
 }
 
 export type CartoonEntity = StateEntity<StructureRepresentation<CartoonProps>, 'cartoon'>
 export namespace CartoonEntity {
     export function ofRepr(ctx: StateContext, repr: StructureRepresentation<CartoonProps>): CartoonEntity {
-        return StateEntity.create(ctx, 'cartoon', repr )
+        return StateEntity.create(ctx, 'cartoon', repr)
     }
 }
 
 export type CarbohydrateEntity = StateEntity<StructureRepresentation<CarbohydrateProps>, 'carbohydrate'>
 export namespace CarbohydrateEntity {
     export function ofRepr(ctx: StateContext, repr: StructureRepresentation<CarbohydrateProps>): CarbohydrateEntity {
-        return StateEntity.create(ctx, 'carbohydrate', repr )
+        return StateEntity.create(ctx, 'carbohydrate', repr)
     }
 }

+ 30 - 30
src/mol-view/state/transform.ts

@@ -95,9 +95,9 @@ export const StructureCenter: StructureCenter = StateTransform.create('structure
         return NullEntity
     })
 
-export type StructureToSpacefill = StateTransform<StructureEntity, SpacefillEntity, SpacefillProps>
+export type StructureToSpacefill = StateTransform<StructureEntity, SpacefillEntity, Partial<SpacefillProps>>
 export const StructureToSpacefill: StructureToSpacefill = StateTransform.create('structure', 'spacefill', 'structure-to-spacefill',
-    async function (ctx: StateContext, structureEntity: StructureEntity, props: SpacefillProps = {}) {
+    async function (ctx: StateContext, structureEntity: StructureEntity, props: Partial<SpacefillProps> = {}) {
         const spacefillRepr = SpacefillRepresentation()
         await spacefillRepr.create(structureEntity.value, props).run(ctx.log)
         ctx.viewer.add(spacefillRepr)
@@ -106,9 +106,9 @@ export const StructureToSpacefill: StructureToSpacefill = StateTransform.create(
         return SpacefillEntity.ofRepr(ctx, spacefillRepr)
     })
 
-export type StructureToBallAndStick = StateTransform<StructureEntity, BallAndStickEntity, BallAndStickProps>
+export type StructureToBallAndStick = StateTransform<StructureEntity, BallAndStickEntity, Partial<BallAndStickProps>>
 export const StructureToBallAndStick: StructureToBallAndStick = StateTransform.create('structure', 'ballandstick', 'structure-to-ballandstick',
-    async function (ctx: StateContext, structureEntity: StructureEntity, props: BallAndStickProps = {}) {
+    async function (ctx: StateContext, structureEntity: StructureEntity, props: Partial<BallAndStickProps> = {}) {
         const ballAndStickRepr = BallAndStickRepresentation()
         await ballAndStickRepr.create(structureEntity.value, props).run(ctx.log)
         ctx.viewer.add(ballAndStickRepr)
@@ -117,9 +117,9 @@ export const StructureToBallAndStick: StructureToBallAndStick = StateTransform.c
         return BallAndStickEntity.ofRepr(ctx, ballAndStickRepr)
     })
 
-export type StructureToDistanceRestraint = StateTransform<StructureEntity, DistanceRestraintEntity, DistanceRestraintProps>
+export type StructureToDistanceRestraint = StateTransform<StructureEntity, DistanceRestraintEntity, Partial<DistanceRestraintProps>>
 export const StructureToDistanceRestraint: StructureToDistanceRestraint = StateTransform.create('structure', 'distancerestraint', 'structure-to-distancerestraint',
-    async function (ctx: StateContext, structureEntity: StructureEntity, props: DistanceRestraintProps = {}) {
+    async function (ctx: StateContext, structureEntity: StructureEntity, props: Partial<DistanceRestraintProps> = {}) {
         const distanceRestraintRepr = DistanceRestraintRepresentation()
         await distanceRestraintRepr.create(structureEntity.value, props).run(ctx.log)
         ctx.viewer.add(distanceRestraintRepr)
@@ -128,9 +128,9 @@ export const StructureToDistanceRestraint: StructureToDistanceRestraint = StateT
         return DistanceRestraintEntity.ofRepr(ctx, distanceRestraintRepr)
     })
 
-export type StructureToBackbone = StateTransform<StructureEntity, BackboneEntity, BackboneProps>
+export type StructureToBackbone = StateTransform<StructureEntity, BackboneEntity, Partial<BackboneProps>>
 export const StructureToBackbone: StructureToBackbone = StateTransform.create('structure', 'backbone', 'structure-to-backbone',
-    async function (ctx: StateContext, structureEntity: StructureEntity, props: BackboneProps = {}) {
+    async function (ctx: StateContext, structureEntity: StructureEntity, props: Partial<BackboneProps> = {}) {
         const backboneRepr = BackboneRepresentation()
         await backboneRepr.create(structureEntity.value, props).run(ctx.log)
         ctx.viewer.add(backboneRepr)
@@ -139,9 +139,9 @@ export const StructureToBackbone: StructureToBackbone = StateTransform.create('s
         return BackboneEntity.ofRepr(ctx, backboneRepr)
     })
 
-export type StructureToCartoon = StateTransform<StructureEntity, CartoonEntity, CartoonProps>
+export type StructureToCartoon = StateTransform<StructureEntity, CartoonEntity, Partial<CartoonProps>>
 export const StructureToCartoon: StructureToCartoon = StateTransform.create('structure', 'cartoon', 'structure-to-cartoon',
-    async function (ctx: StateContext, structureEntity: StructureEntity, props: CartoonProps = {}) {
+    async function (ctx: StateContext, structureEntity: StructureEntity, props: Partial<CartoonProps> = {}) {
         const cartoonRepr = CartoonRepresentation()
         await cartoonRepr.create(structureEntity.value, props).run(ctx.log)
         ctx.viewer.add(cartoonRepr)
@@ -150,9 +150,9 @@ export const StructureToCartoon: StructureToCartoon = StateTransform.create('str
         return CartoonEntity.ofRepr(ctx, cartoonRepr)
     })
 
-export type StructureToCarbohydrate = StateTransform<StructureEntity, CarbohydrateEntity, CarbohydrateProps>
+export type StructureToCarbohydrate = StateTransform<StructureEntity, CarbohydrateEntity, Partial<CarbohydrateProps>>
 export const StructureToCarbohydrate: StructureToCarbohydrate = StateTransform.create('structure', 'carbohydrate', 'structure-to-cartoon',
-    async function (ctx: StateContext, structureEntity: StructureEntity, props: CarbohydrateProps = {}) {
+    async function (ctx: StateContext, structureEntity: StructureEntity, props: Partial<CarbohydrateProps> = {}) {
         const carbohydrateRepr = CarbohydrateRepresentation()
         await carbohydrateRepr.create(structureEntity.value, props).run(ctx.log)
         ctx.viewer.add(carbohydrateRepr)
@@ -161,9 +161,9 @@ export const StructureToCarbohydrate: StructureToCarbohydrate = StateTransform.c
         return CarbohydrateEntity.ofRepr(ctx, carbohydrateRepr)
     })
 
-export type SpacefillUpdate = StateTransform<SpacefillEntity, NullEntity, SpacefillProps>
+export type SpacefillUpdate = StateTransform<SpacefillEntity, NullEntity, Partial<SpacefillProps>>
 export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill', 'null', 'spacefill-update',
-    async function (ctx: StateContext, spacefillEntity: SpacefillEntity, props: SpacefillProps = {}) {
+    async function (ctx: StateContext, spacefillEntity: SpacefillEntity, props: Partial<SpacefillProps> = {}) {
         const spacefillRepr = spacefillEntity.value
         await spacefillRepr.update(props).run(ctx.log)
         ctx.viewer.add(spacefillRepr)
@@ -172,9 +172,9 @@ export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill
         return NullEntity
     })
 
-export type BallAndStickUpdate = StateTransform<BallAndStickEntity, NullEntity, BallAndStickProps>
+export type BallAndStickUpdate = StateTransform<BallAndStickEntity, NullEntity, Partial<BallAndStickProps>>
 export const BallAndStickUpdate: BallAndStickUpdate = StateTransform.create('ballandstick', 'null', 'ballandstick-update',
-    async function (ctx: StateContext, ballAndStickEntity: BallAndStickEntity, props: BallAndStickProps = {}) {
+    async function (ctx: StateContext, ballAndStickEntity: BallAndStickEntity, props: Partial<BallAndStickProps> = {}) {
         const ballAndStickRepr = ballAndStickEntity.value
         await ballAndStickRepr.update(props).run(ctx.log)
         ctx.viewer.add(ballAndStickRepr)
@@ -183,9 +183,9 @@ export const BallAndStickUpdate: BallAndStickUpdate = StateTransform.create('bal
         return NullEntity
     })
 
-export type DistanceRestraintUpdate = StateTransform<DistanceRestraintEntity, NullEntity, DistanceRestraintProps>
+export type DistanceRestraintUpdate = StateTransform<DistanceRestraintEntity, NullEntity, Partial<DistanceRestraintProps>>
 export const DistanceRestraintUpdate: DistanceRestraintUpdate = StateTransform.create('distancerestraint', 'null', 'distancerestraint-update',
-    async function (ctx: StateContext, distanceRestraintEntity: DistanceRestraintEntity, props: DistanceRestraintProps = {}) {
+    async function (ctx: StateContext, distanceRestraintEntity: DistanceRestraintEntity, props: Partial<DistanceRestraintProps> = {}) {
         const distanceRestraintRepr = distanceRestraintEntity.value
         await distanceRestraintRepr.update(props).run(ctx.log)
         ctx.viewer.add(distanceRestraintRepr)
@@ -194,9 +194,9 @@ export const DistanceRestraintUpdate: DistanceRestraintUpdate = StateTransform.c
         return NullEntity
     })
 
-export type BackboneUpdate = StateTransform<BackboneEntity, NullEntity, BackboneProps>
+export type BackboneUpdate = StateTransform<BackboneEntity, NullEntity, Partial<BackboneProps>>
 export const BackboneUpdate: BackboneUpdate = StateTransform.create('backbone', 'null', 'backbone-update',
-    async function (ctx: StateContext, backboneEntity: BackboneEntity, props: BackboneProps = {}) {
+    async function (ctx: StateContext, backboneEntity: BackboneEntity, props: Partial<BackboneProps> = {}) {
         const backboneRepr = backboneEntity.value
         await backboneRepr.update(props).run(ctx.log)
         ctx.viewer.add(backboneRepr)
@@ -205,9 +205,9 @@ export const BackboneUpdate: BackboneUpdate = StateTransform.create('backbone',
         return NullEntity
     })
 
-export type CartoonUpdate = StateTransform<CartoonEntity, NullEntity, CartoonProps>
+export type CartoonUpdate = StateTransform<CartoonEntity, NullEntity, Partial<CartoonProps>>
 export const CartoonUpdate: CartoonUpdate = StateTransform.create('cartoon', 'null', 'cartoon-update',
-    async function (ctx: StateContext, cartoonEntity: CartoonEntity, props: CartoonProps = {}) {
+    async function (ctx: StateContext, cartoonEntity: CartoonEntity, props: Partial<CartoonProps> = {}) {
         const cartoonRepr = cartoonEntity.value
         await cartoonRepr.update(props).run(ctx.log)
         ctx.viewer.add(cartoonRepr)
@@ -216,9 +216,9 @@ export const CartoonUpdate: CartoonUpdate = StateTransform.create('cartoon', 'nu
         return NullEntity
     })
 
-export type CarbohydrateUpdate = StateTransform<CarbohydrateEntity, NullEntity, CarbohydrateProps>
+export type CarbohydrateUpdate = StateTransform<CarbohydrateEntity, NullEntity, Partial<CarbohydrateProps>>
 export const CarbohydrateUpdate: CarbohydrateUpdate = StateTransform.create('carbohydrate', 'null', 'carbohydrate-update',
-    async function (ctx: StateContext, carbohydrateEntity: CarbohydrateEntity, props: CarbohydrateProps = {}) {
+    async function (ctx: StateContext, carbohydrateEntity: CarbohydrateEntity, props: Partial<CarbohydrateProps> = {}) {
         const carbohydrateRepr = carbohydrateEntity.value
         await carbohydrateRepr.update(props).run(ctx.log)
         ctx.viewer.add(carbohydrateRepr)
@@ -251,24 +251,24 @@ export const DataToModel: DataToModel = StateTransform.create('data', 'model', '
         return MmcifToModel.apply(ctx, mmcifEntity)
     })
 
-export type ModelToSpacefill = StateTransform<ModelEntity, SpacefillEntity, SpacefillProps>
+export type ModelToSpacefill = StateTransform<ModelEntity, SpacefillEntity, Partial<SpacefillProps>>
 export const ModelToSpacefill: ModelToSpacefill = StateTransform.create('model', 'spacefill', 'model-to-spacefill',
-    async function (ctx: StateContext, modelEntity: ModelEntity, props: SpacefillProps = {}) {
+    async function (ctx: StateContext, modelEntity: ModelEntity, props: Partial<SpacefillProps> = {}) {
         const structureEntity = await ModelToStructure.apply(ctx, modelEntity)
         // StructureToBond.apply(ctx, structureEntity, props)
         return StructureToSpacefill.apply(ctx, structureEntity, props)
     })
 
-export type MmcifUrlToSpacefill = StateTransform<UrlEntity, SpacefillEntity, SpacefillProps>
+export type MmcifUrlToSpacefill = StateTransform<UrlEntity, SpacefillEntity, Partial<SpacefillProps>>
 export const MmcifUrlToSpacefill: MmcifUrlToSpacefill = StateTransform.create('url', 'spacefill', 'url-to-spacefill',
-    async function (ctx: StateContext, urlEntity: UrlEntity, props: SpacefillProps = {}) {
+    async function (ctx: StateContext, urlEntity: UrlEntity, props: Partial<SpacefillProps> = {}) {
         const modelEntity = await MmcifUrlToModel.apply(ctx, urlEntity)
         return ModelToSpacefill.apply(ctx, modelEntity, props)
     })
 
-export type MmcifFileToSpacefill = StateTransform<FileEntity, SpacefillEntity, SpacefillProps>
+export type MmcifFileToSpacefill = StateTransform<FileEntity, SpacefillEntity, Partial<SpacefillProps>>
 export const MmcifFileToSpacefill: MmcifFileToSpacefill = StateTransform.create('file', 'spacefill', 'file-to-spacefill',
-    async function (ctx: StateContext, fileEntity: FileEntity, props: SpacefillProps = {}) {
+    async function (ctx: StateContext, fileEntity: FileEntity, props: Partial<SpacefillProps> = {}) {
         const modelEntity = await MmcifFileToModel.apply(ctx, fileEntity)
         return ModelToSpacefill.apply(ctx, modelEntity, props)
     })