From 52b8242a5984478cd9648915a8ba5a3df247a2bd Mon Sep 17 00:00:00 2001
From: Alexander Rose <alexander.rose@weirdbyte.de>
Date: Sun, 17 Jun 2018 17:41:34 +0200
Subject: [PATCH] wip, build structure representation from visuals

---
 src/mol-app/ui/entity/tree.tsx                |   8 +-
 .../{bond.tsx => ball-and-stick.tsx}          |  10 +-
 src/mol-app/ui/transform/list.tsx             |   6 +-
 .../structure/ball-and-stick.ts               |  64 ++++++++
 src/mol-geo/representation/structure/index.ts |   3 +-
 .../representation/structure/spacefill.ts     | 155 +-----------------
 src/mol-geo/representation/structure/utils.ts |  29 +++-
 .../{point.ts => visual/element-point.ts}     |  14 +-
 .../structure/visual/element-sphere.ts        | 153 +++++++++++++++++
 .../intra-unit-link-cylinder.ts}              | 137 ++++++++--------
 src/mol-geo/theme/index.ts                    |   3 +-
 src/mol-geo/theme/structure/size/index.ts     |   2 +-
 .../size/{element.ts => physical.ts}          |  26 ++-
 src/mol-view/stage.ts                         |   6 +-
 src/mol-view/state/entity.ts                  |  10 +-
 src/mol-view/state/transform.ts               |  35 ++--
 16 files changed, 382 insertions(+), 279 deletions(-)
 rename src/mol-app/ui/transform/{bond.tsx => ball-and-stick.tsx} (95%)
 create mode 100644 src/mol-geo/representation/structure/ball-and-stick.ts
 rename src/mol-geo/representation/structure/{point.ts => visual/element-point.ts} (94%)
 create mode 100644 src/mol-geo/representation/structure/visual/element-sphere.ts
 rename src/mol-geo/representation/structure/{bond.ts => visual/intra-unit-link-cylinder.ts} (62%)
 rename src/mol-geo/theme/structure/size/{element.ts => physical.ts} (59%)

diff --git a/src/mol-app/ui/entity/tree.tsx b/src/mol-app/ui/entity/tree.tsx
index 66eba7892..755b2c100 100644
--- a/src/mol-app/ui/entity/tree.tsx
+++ b/src/mol-app/ui/entity/tree.tsx
@@ -13,7 +13,7 @@ import { View } from '../view';
 import { EntityTreeController } from '../../controller/entity/tree';
 import { Controller } from '../../controller/controller';
 import { AnyEntity, RootEntity } from 'mol-view/state/entity';
-import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill, StructureCenter, StructureToBond, BondUpdate } from 'mol-view/state/transform';
+import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill, StructureCenter, StructureToBallAndStick, BallAndStickUpdate } from 'mol-view/state/transform';
 
 function getTransforms(entity: AnyEntity): AnyTransform[] {
     const transforms: AnyTransform[] = []
@@ -40,13 +40,13 @@ function getTransforms(entity: AnyEntity): AnyTransform[] {
             transforms.push(ModelToStructure)
             break;
         case 'structure':
-            transforms.push(StructureToSpacefill, StructureToBond, StructureCenter)
+            transforms.push(StructureToSpacefill, StructureToBallAndStick, StructureCenter)
             break;
         case 'spacefill':
             transforms.push(SpacefillUpdate)
             break;
-        case 'bond':
-            transforms.push(BondUpdate)
+        case 'ballandstick':
+            transforms.push(BallAndStickUpdate)
             break;
     }
     return transforms
diff --git a/src/mol-app/ui/transform/bond.tsx b/src/mol-app/ui/transform/ball-and-stick.tsx
similarity index 95%
rename from src/mol-app/ui/transform/bond.tsx
rename to src/mol-app/ui/transform/ball-and-stick.tsx
index cc87a6dc8..9356ca559 100644
--- a/src/mol-app/ui/transform/bond.tsx
+++ b/src/mol-app/ui/transform/ball-and-stick.tsx
@@ -12,8 +12,8 @@ import * as React from 'react'
 import { View } from '../view';
 import { Controller } from '../../controller/controller';
 import { Toggle } from '../controls/common';
-import { BondEntity } from 'mol-view/state/entity';
-import { BondUpdate } from 'mol-view/state/transform'
+import { BallAndStickEntity } from 'mol-view/state/entity';
+import { BallAndStickUpdate } from 'mol-view/state/transform'
 import { StateContext } from 'mol-view/state/context';
 import { ColorTheme } from 'mol-geo/theme';
 import { Color, ColorNames } from 'mol-util/color';
@@ -28,7 +28,7 @@ export const ColorThemeInfo = {
 }
 export type ColorThemeInfo = keyof typeof ColorThemeInfo
 
-interface BondState {
+interface BallAndStickState {
     doubleSided: boolean
     flipSided: boolean
     flatShaded: boolean
@@ -39,7 +39,7 @@ interface BondState {
     depthMask: boolean
 }
 
-export class Bond extends View<Controller<any>, BondState, { transform: BondUpdate, entity: BondEntity, ctx: StateContext }> {
+export class BallAndStick extends View<Controller<any>, BallAndStickState, { transform: BallAndStickUpdate, entity: BallAndStickEntity, ctx: StateContext }> {
     state = {
         doubleSided: true,
         flipSided: false,
@@ -51,7 +51,7 @@ export class Bond extends View<Controller<any>, BondState, { transform: BondUpda
         depthMask: true
     }
 
-    update(state?: Partial<BondState>) {
+    update(state?: Partial<BallAndStickState>) {
         const { transform, entity, ctx } = this.props
         const newState = { ...this.state, ...state }
         this.setState(newState)
diff --git a/src/mol-app/ui/transform/list.tsx b/src/mol-app/ui/transform/list.tsx
index 8fa592ee0..57d5fcaa7 100644
--- a/src/mol-app/ui/transform/list.tsx
+++ b/src/mol-app/ui/transform/list.tsx
@@ -14,7 +14,7 @@ import { Controller } from '../../controller/controller';
 import { TransformListController } from '../../controller/transform/list';
 import { AnyTransform } from 'mol-view/state/transform';
 import { Spacefill } from './spacefill';
-import { Bond } from './bond';
+import { BallAndStick } from './ball-and-stick';
 import { AnyEntity } from 'mol-view/state/entity';
 import { FileLoader } from './file-loader';
 import { ModelToStructure } from './model';
@@ -30,8 +30,8 @@ function getTransformComponent(controller: TransformListController, entity: AnyE
             return <StructureCenter controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></StructureCenter>
         case 'spacefill-update':
             return <Spacefill controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Spacefill>
-        case 'bond-update':
-            return <Bond controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Bond>
+        case 'ballandstick-update':
+            return <BallAndStick controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></BallAndStick>
     }
     return <Transform controller={controller} entity={entity} transform={transform}></Transform>
 }
diff --git a/src/mol-geo/representation/structure/ball-and-stick.ts b/src/mol-geo/representation/structure/ball-and-stick.ts
new file mode 100644
index 000000000..dfbc17747
--- /dev/null
+++ b/src/mol-geo/representation/structure/ball-and-stick.ts
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { StructureRepresentation } from '.';
+import { ElementSphereVisual, DefaultElementSphereProps } from './visual/element-sphere';
+import { IntraUnitLinkVisual, DefaultIntraUnitLinkProps } from './visual/intra-unit-link-cylinder';
+import { PickingId } from '../../util/picking';
+import { Structure } from 'mol-model/structure';
+import { Task } from 'mol-task';
+import { Loci, isEmptyLoci } from 'mol-model/loci';
+import { MarkerAction } from '../../util/marker-data';
+import { SizeTheme } from '../../theme';
+
+export const DefaultBallAndStickProps = {
+    ...DefaultElementSphereProps,
+    ...DefaultIntraUnitLinkProps,
+
+    sizeTheme: { name: 'physical', factor: 0.2 } as SizeTheme,
+}
+export type BallAndStickProps = Partial<typeof DefaultBallAndStickProps>
+
+export function BallAndStickRepresentation(): StructureRepresentation<BallAndStickProps> {
+    const sphereRepr = StructureRepresentation(ElementSphereVisual)
+    const intraLinkRepr = StructureRepresentation(IntraUnitLinkVisual)
+
+    return {
+        get renderObjects() {
+            return [ ...sphereRepr.renderObjects, ...intraLinkRepr.renderObjects ]
+        },
+        create: (structure: Structure, props: BallAndStickProps = {} as BallAndStickProps) => {
+            const p = Object.assign({}, props, DefaultBallAndStickProps)
+            return Task.create('Creating BallAndStickRepresentation', async ctx => {
+                await sphereRepr.create(structure, p).runInContext(ctx)
+                await intraLinkRepr.create(structure, p).runInContext(ctx)
+            })
+        },
+        update: (props: BallAndStickProps) => {
+            return Task.create('Updating BallAndStickRepresentation', async ctx => {
+                await sphereRepr.update(props).runInContext(ctx)
+                await intraLinkRepr.update(props).runInContext(ctx)
+            })
+        },
+        getLoci: (pickingId: PickingId) => {
+            const sphereLoci = sphereRepr.getLoci(pickingId)
+            const intraLinkLoci = intraLinkRepr.getLoci(pickingId)
+            if (isEmptyLoci(sphereLoci)) {
+                return intraLinkLoci
+            } else {
+                return sphereLoci 
+            }
+        },
+        mark: (loci: Loci, action: MarkerAction) => {
+            sphereRepr.mark(loci, action)
+            intraLinkRepr.mark(loci, action)
+        },
+        destroy() {
+            sphereRepr.destroy()
+            intraLinkRepr.destroy()
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts
index 288135c4d..ecb466cea 100644
--- a/src/mol-geo/representation/structure/index.ts
+++ b/src/mol-geo/representation/structure/index.ts
@@ -9,7 +9,7 @@ import { Structure, StructureSymmetry, Unit } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/render-object';
 import { Representation, RepresentationProps, Visual } from '..';
-import { ColorTheme } from '../../theme';
+import { ColorTheme, SizeTheme } from '../../theme';
 import { PickingId } from '../../util/picking';
 import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
 import { MarkerAction } from '../../util/marker-data';
@@ -21,6 +21,7 @@ export interface StructureRepresentation<P extends RepresentationProps = {}> ext
 
 export const DefaultStructureProps = {
     colorTheme: { name: 'instance-index' } as ColorTheme,
+    sizeTheme: { name: 'physical' } as SizeTheme,
     alpha: 1,
     visible: true,
     doubleSided: false,
diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts
index d99c82521..7847fd9f6 100644
--- a/src/mol-geo/representation/structure/spacefill.ts
+++ b/src/mol-geo/representation/structure/spacefill.ts
@@ -2,159 +2,16 @@
  * 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 { ValueCell } from 'mol-util/value-cell'
-
-import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
-import { Unit, Element, Queries } from 'mol-model/structure';
-import { DefaultStructureProps, UnitsVisual } from './index';
-import { RuntimeContext } from 'mol-task'
-import { createTransforms, createColors, createSphereMesh, markElement } from './utils';
-import VertexMap from '../../shape/vertex-map';
-import { deepEqual, defaults } from 'mol-util';
-import { fillSerial } from 'mol-gl/renderable/util';
-import { RenderableState, MeshValues } from 'mol-gl/renderable';
-import { getMeshData } from '../../util/mesh-data';
-import { Mesh } from '../../shape/mesh';
-import { PickingId } from '../../util/picking';
-import { OrderedSet } from 'mol-data/int';
-import { createMarkers, MarkerAction } from '../../util/marker-data';
-import { Loci, EmptyLoci } from 'mol-model/loci';
-
-async function createSpacefillMesh(ctx: RuntimeContext, unit: Unit, detail: number, mesh?: Mesh) {
-    let radius: Element.Property<number>
-    if (Unit.isAtomic(unit)) {
-        radius = Queries.props.atom.vdw_radius
-    } else if (Unit.isSpheres(unit)) {
-        radius = Queries.props.coarse.sphere_radius
-    } else {
-        console.warn('Unsupported unit type')
-        return Mesh.createEmpty(mesh)
-    }
-    return await createSphereMesh(ctx, unit, (l) => radius(l) * 0.3, detail, mesh)
-}
+import { StructureRepresentation } from '.';
+import { ElementSphereVisual, DefaultElementSphereProps } from './visual/element-sphere';
 
 export const DefaultSpacefillProps = {
-    ...DefaultStructureProps,
-    flipSided: false,
-    flatShaded: false,
-    detail: 0,
+    ...DefaultElementSphereProps,
 }
 export type SpacefillProps = Partial<typeof DefaultSpacefillProps>
 
-export default function SpacefillVisual(): UnitsVisual<SpacefillProps> {
-    const renderObjects: RenderObject[] = []
-    let spheres: MeshRenderObject
-    let currentProps: typeof DefaultSpacefillProps
-    let mesh: Mesh
-    let currentGroup: Unit.SymmetryGroup
-    let vertexMap: VertexMap
-
-    return {
-        renderObjects,
-        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: SpacefillProps = {}) {
-            currentProps = Object.assign({}, DefaultSpacefillProps, props)
-
-            renderObjects.length = 0 // clear
-            currentGroup = group
-
-            const { detail, colorTheme } = { ...DefaultSpacefillProps, ...props }
-            const instanceCount = group.units.length
-            const elementCount = group.elements.length
-
-            mesh = await createSpacefillMesh(ctx, group.units[0], detail)
-            // console.log(mesh)
-            vertexMap = VertexMap.fromMesh(mesh)
-
-            if (ctx.shouldUpdate) await ctx.update('Computing spacefill transforms');
-            const transforms = createTransforms(group)
-
-            if (ctx.shouldUpdate) await ctx.update('Computing spacefill colors');
-            const color = createColors(group, vertexMap, colorTheme)
-
-            if (ctx.shouldUpdate) await ctx.update('Computing spacefill marks');
-            const marker = createMarkers(instanceCount * elementCount)
-
-            const values: MeshValues = {
-                ...getMeshData(mesh),
-                aTransform: transforms,
-                aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
-                ...color,
-                ...marker,
-
-                uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
-                uInstanceCount: ValueCell.create(instanceCount),
-                uElementCount: ValueCell.create(elementCount),
-
-                elements: mesh.indexBuffer,
-
-                drawCount: ValueCell.create(mesh.triangleCount * 3),
-                instanceCount: ValueCell.create(instanceCount),
-
-                dDoubleSided: ValueCell.create(defaults(props.doubleSided, true)),
-                dFlatShaded: ValueCell.create(defaults(props.flatShaded, false)),
-                dFlipSided: ValueCell.create(defaults(props.flipSided, false)),
-                dUseFog: ValueCell.create(defaults(props.useFog, true)),
-            }
-            const state: RenderableState = {
-                depthMask: defaults(props.depthMask, true),
-                visible: defaults(props.visible, true)
-            }
-
-            spheres = createMeshRenderObject(values, state)
-            renderObjects.push(spheres)
-        },
-        async update(ctx: RuntimeContext, props: SpacefillProps) {
-            const newProps = Object.assign({}, currentProps, props)
-
-            if (!spheres) return false
-
-            let updateColor = false
-
-            if (newProps.detail !== currentProps.detail) {
-                mesh = await createSpacefillMesh(ctx, currentGroup.units[0], newProps.detail, mesh)
-                ValueCell.update(spheres.values.drawCount, mesh.triangleCount * 3)
-                // TODO update in-place
-                vertexMap = VertexMap.fromMesh(mesh)
-                updateColor = true
-            }
-
-            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
-                updateColor = true
-            }
-
-            if (updateColor) {
-                if (ctx.shouldUpdate) await ctx.update('Computing spacefill colors');
-                createColors(currentGroup, vertexMap, newProps.colorTheme, spheres.values)
-            }
-
-            ValueCell.updateIfChanged(spheres.values.uAlpha, newProps.alpha)
-            ValueCell.updateIfChanged(spheres.values.dDoubleSided, newProps.doubleSided)
-            ValueCell.updateIfChanged(spheres.values.dFlipSided, newProps.flipSided)
-            ValueCell.updateIfChanged(spheres.values.dFlatShaded, newProps.flatShaded)
-
-            spheres.state.visible = newProps.visible
-            spheres.state.depthMask = newProps.depthMask
-
-            currentProps = newProps
-            return true
-        },
-        getLoci(pickingId: PickingId) {
-            const { objectId, instanceId, elementId } = pickingId
-            if (spheres.id === objectId) {
-                const unit = currentGroup.units[instanceId]
-                const indices = OrderedSet.ofSingleton(elementId as Element.Index);
-                return Element.Loci([{ unit, indices }])
-            }
-            return EmptyLoci
-        },
-        mark(loci: Loci, action: MarkerAction) {
-            markElement(spheres.values.tMarker, currentGroup, loci, action)
-        },
-        destroy() {
-            // TODO
-        }
-    }
-}
+export function SpacefillRepresentation() {
+    return StructureRepresentation(ElementSphereVisual)
+}
\ No newline at end of file
diff --git a/src/mol-geo/representation/structure/utils.ts b/src/mol-geo/representation/structure/utils.ts
index ada7c4309..f49793aa3 100644
--- a/src/mol-geo/representation/structure/utils.ts
+++ b/src/mol-geo/representation/structure/utils.ts
@@ -9,12 +9,12 @@ import { Unit, Element } from 'mol-model/structure';
 import { Mat4, Vec3 } from 'mol-math/linear-algebra'
 
 import { createUniformColor, ColorData } from '../../util/color-data';
-import { createUniformSize } from '../../util/size-data';
-import { elementSizeData } from '../../theme/structure/size/element';
+import { createUniformSize, SizeData } from '../../util/size-data';
+import { physicalSizeData, getPhysicalRadius } from '../../theme/structure/size/physical';
 import VertexMap from '../../shape/vertex-map';
 import { ColorTheme, SizeTheme } from '../../theme';
 import { elementIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../theme/structure/color';
-import { ValueCell } from 'mol-util';
+import { ValueCell, defaults } from 'mol-util';
 import { Mesh } from '../../shape/mesh';
 import { RuntimeContext } from 'mol-task';
 import { icosahedronVertexCount } from '../../primitive/icosahedron';
@@ -49,16 +49,27 @@ export function createColors(group: Unit.SymmetryGroup, vertexMap: VertexMap, pr
     }
 }
 
-export function createSizes(group: Unit.SymmetryGroup, vertexMap: VertexMap, props: SizeTheme) {
+export function createSizes(group: Unit.SymmetryGroup, vertexMap: VertexMap, props: SizeTheme): SizeData {
     switch (props.name) {
         case 'uniform':
             return createUniformSize(props)
-        case 'vdw':
-            return elementSizeData({ group, vertexMap })
+        case 'physical':
+            return physicalSizeData(defaults(props.factor, 1), { group, vertexMap })
     }
 }
 
-export async function createSphereMesh(ctx: RuntimeContext, unit: Unit, radius: Element.Property<number>, detail: number, mesh?: Mesh) {
+export function getElementRadius(unit: Unit, props: SizeTheme): Element.Property<number> {
+    switch (props.name) {
+        case 'uniform':
+            return () => props.value
+        case 'physical':
+            const radius = getPhysicalRadius(unit)
+            const factor = defaults(props.factor, 1)
+            return (l) => radius(l) * factor
+    }
+}
+
+export async function createElementSphereMesh(ctx: RuntimeContext, unit: Unit, radius: Element.Property<number>, detail: number, mesh?: Mesh) {
     const { elements } = unit;
     const elementCount = elements.length;
     const vertexCount = elementCount * icosahedronVertexCount(detail)
@@ -67,13 +78,13 @@ export async function createSphereMesh(ctx: RuntimeContext, unit: Unit, radius:
     const v = Vec3.zero()
     const m = Mat4.identity()
 
-    const { x, y, z } = unit.model.atomicConformation
+    const pos = unit.conformation.invariantPosition
     const l = Element.Location()
     l.unit = unit
 
     for (let i = 0; i < elementCount; i++) {
         l.element = elements[i]
-        v[0] = x[l.element]; v[1] = y[l.element]; v[2] = z[l.element]
+        pos(elements[i], v)
         Mat4.setTranslation(m, v)
 
         meshBuilder.setId(i)
diff --git a/src/mol-geo/representation/structure/point.ts b/src/mol-geo/representation/structure/visual/element-point.ts
similarity index 94%
rename from src/mol-geo/representation/structure/point.ts
rename to src/mol-geo/representation/structure/visual/element-point.ts
index 363cc3363..bca8c3e37 100644
--- a/src/mol-geo/representation/structure/point.ts
+++ b/src/mol-geo/representation/structure/visual/element-point.ts
@@ -11,21 +11,21 @@ import { Unit, Element } from 'mol-model/structure';
 import { RuntimeContext } from 'mol-task'
 import { fillSerial } from 'mol-gl/renderable/util';
 
-import { UnitsVisual, DefaultStructureProps } from './index';
-import VertexMap from '../../shape/vertex-map';
-import { SizeTheme } from '../../theme';
-import { createTransforms, createColors, createSizes, markElement } from './utils';
+import { UnitsVisual, DefaultStructureProps } from '../index';
+import VertexMap from '../../../shape/vertex-map';
+import { SizeTheme } from '../../../theme';
+import { createTransforms, createColors, createSizes, markElement } from '../utils';
 import { deepEqual, defaults } from 'mol-util';
 import { SortedArray, OrderedSet } from 'mol-data/int';
 import { RenderableState, PointValues } from 'mol-gl/renderable';
-import { PickingId } from '../../util/picking';
+import { PickingId } from '../../../util/picking';
 import { Loci, EmptyLoci } from 'mol-model/loci';
-import { MarkerAction, createMarkers } from '../../util/marker-data';
+import { MarkerAction, createMarkers } from '../../../util/marker-data';
 import { Vec3 } from 'mol-math/linear-algebra';
 
 export const DefaultPointProps = {
     ...DefaultStructureProps,
-    sizeTheme: { name: 'vdw' } as SizeTheme
+    sizeTheme: { name: 'physical' } as SizeTheme
 }
 export type PointProps = Partial<typeof DefaultPointProps>
 
diff --git a/src/mol-geo/representation/structure/visual/element-sphere.ts b/src/mol-geo/representation/structure/visual/element-sphere.ts
new file mode 100644
index 000000000..78ced5e32
--- /dev/null
+++ b/src/mol-geo/representation/structure/visual/element-sphere.ts
@@ -0,0 +1,153 @@
+/**
+ * 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 { ValueCell } from 'mol-util/value-cell'
+
+import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Unit, Element } from 'mol-model/structure';
+import { DefaultStructureProps, UnitsVisual } from '../index';
+import { RuntimeContext } from 'mol-task'
+import { createTransforms, createColors, createElementSphereMesh, markElement, getElementRadius } from '../utils';
+import VertexMap from '../../../shape/vertex-map';
+import { deepEqual, defaults } from 'mol-util';
+import { fillSerial } from 'mol-gl/renderable/util';
+import { RenderableState, MeshValues } from 'mol-gl/renderable';
+import { getMeshData } from '../../../util/mesh-data';
+import { Mesh } from '../../../shape/mesh';
+import { PickingId } from '../../../util/picking';
+import { OrderedSet } from 'mol-data/int';
+import { createMarkers, MarkerAction } from '../../../util/marker-data';
+import { Loci, EmptyLoci } from 'mol-model/loci';
+import { SizeTheme } from '../../../theme';
+
+export const DefaultElementSphereProps = {
+    ...DefaultStructureProps,
+    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
+    flipSided: false,
+    flatShaded: false,
+    detail: 0,
+}
+export type ElementSphereProps = Partial<typeof DefaultElementSphereProps>
+
+export function ElementSphereVisual(): UnitsVisual<ElementSphereProps> {
+    const renderObjects: RenderObject[] = []
+    let spheres: MeshRenderObject
+    let currentProps: typeof DefaultElementSphereProps
+    let mesh: Mesh
+    let currentGroup: Unit.SymmetryGroup
+    let vertexMap: VertexMap
+
+    return {
+        renderObjects,
+        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: ElementSphereProps = {}) {
+            currentProps = Object.assign({}, DefaultElementSphereProps, props)
+
+            renderObjects.length = 0 // clear
+            currentGroup = group
+
+            const { detail, colorTheme, sizeTheme } = { ...DefaultElementSphereProps, ...props }
+            const instanceCount = group.units.length
+            const elementCount = group.elements.length
+            const unit = group.units[0]
+
+            const radius = getElementRadius(unit, sizeTheme)
+            mesh = await createElementSphereMesh(ctx, unit, radius, detail, mesh)
+            // console.log(mesh)
+            vertexMap = VertexMap.fromMesh(mesh)
+
+            if (ctx.shouldUpdate) await ctx.update('Computing spacefill transforms');
+            const transforms = createTransforms(group)
+
+            if (ctx.shouldUpdate) await ctx.update('Computing spacefill colors');
+            const color = createColors(group, vertexMap, colorTheme)
+
+            if (ctx.shouldUpdate) await ctx.update('Computing spacefill marks');
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                aTransform: transforms,
+                aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
+                ...color,
+                ...marker,
+
+                uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
+                uInstanceCount: ValueCell.create(instanceCount),
+                uElementCount: ValueCell.create(elementCount),
+
+                elements: mesh.indexBuffer,
+
+                drawCount: ValueCell.create(mesh.triangleCount * 3),
+                instanceCount: ValueCell.create(instanceCount),
+
+                dDoubleSided: ValueCell.create(defaults(props.doubleSided, true)),
+                dFlatShaded: ValueCell.create(defaults(props.flatShaded, false)),
+                dFlipSided: ValueCell.create(defaults(props.flipSided, false)),
+                dUseFog: ValueCell.create(defaults(props.useFog, true)),
+            }
+            const state: RenderableState = {
+                depthMask: defaults(props.depthMask, true),
+                visible: defaults(props.visible, true)
+            }
+
+            spheres = createMeshRenderObject(values, state)
+            renderObjects.push(spheres)
+        },
+        async update(ctx: RuntimeContext, props: ElementSphereProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!spheres) 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(spheres.values.drawCount, mesh.triangleCount * 3)
+                // TODO update in-place
+                vertexMap = VertexMap.fromMesh(mesh)
+                updateColor = true
+            }
+
+            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
+                updateColor = true
+            }
+
+            if (updateColor) {
+                if (ctx.shouldUpdate) await ctx.update('Computing spacefill colors');
+                createColors(currentGroup, vertexMap, newProps.colorTheme, spheres.values)
+            }
+
+            ValueCell.updateIfChanged(spheres.values.uAlpha, newProps.alpha)
+            ValueCell.updateIfChanged(spheres.values.dDoubleSided, newProps.doubleSided)
+            ValueCell.updateIfChanged(spheres.values.dFlipSided, newProps.flipSided)
+            ValueCell.updateIfChanged(spheres.values.dFlatShaded, newProps.flatShaded)
+
+            spheres.state.visible = newProps.visible
+            spheres.state.depthMask = newProps.depthMask
+
+            currentProps = newProps
+            return true
+        },
+        getLoci(pickingId: PickingId) {
+            const { objectId, instanceId, elementId } = pickingId
+            if (spheres.id === objectId) {
+                const unit = currentGroup.units[instanceId]
+                const indices = OrderedSet.ofSingleton(elementId as Element.Index);
+                return Element.Loci([{ unit, indices }])
+            }
+            return EmptyLoci
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            markElement(spheres.values.tMarker, currentGroup, loci, action)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}
diff --git a/src/mol-geo/representation/structure/bond.ts b/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts
similarity index 62%
rename from src/mol-geo/representation/structure/bond.ts
rename to src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts
index 7f5841e15..983840d79 100644
--- a/src/mol-geo/representation/structure/bond.ts
+++ b/src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts
@@ -11,27 +11,28 @@ import { ValueCell } from 'mol-util/value-cell'
 
 import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
 import { Unit, Link } from 'mol-model/structure';
-import { UnitsVisual, DefaultStructureProps } from './index';
+import { UnitsVisual, DefaultStructureProps } from '../index';
 import { RuntimeContext } from 'mol-task'
-import { createTransforms } from './utils';
+import { createTransforms } from '../utils';
 import { fillSerial } from 'mol-gl/renderable/util';
 import { RenderableState, MeshValues } from 'mol-gl/renderable';
-import { getMeshData } from '../../util/mesh-data';
-import { Mesh } from '../../shape/mesh';
-import { PickingId } from '../../util/picking';
-import { MeshBuilder } from '../../shape/mesh-builder';
+import { getMeshData } from '../../../util/mesh-data';
+import { Mesh } from '../../../shape/mesh';
+import { PickingId } from '../../../util/picking';
+import { MeshBuilder } from '../../../shape/mesh-builder';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
-import { createUniformColor } from '../../util/color-data';
+import { createUniformColor } from '../../../util/color-data';
 import { defaults } from 'mol-util';
 import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
-import { MarkerAction, applyMarkerAction, createMarkers } from '../../util/marker-data';
+import { MarkerAction, applyMarkerAction, createMarkers, MarkerData } from '../../../util/marker-data';
+import { SizeTheme } from '../../../theme';
 
-async function createBondMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
+async function createLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
 
     const elements = unit.elements;
-    const bonds = unit.links
-    const { edgeCount, a, b } = bonds
+    const links = unit.links
+    const { edgeCount, a, b } = links
 
     if (!edgeCount) return Mesh.createEmpty(mesh)
 
@@ -71,25 +72,26 @@ async function createBondMesh(ctx: RuntimeContext, unit: Unit, mesh?: Mesh) {
     return meshBuilder.getMesh()
 }
 
-export const DefaultBondProps = {
+export const DefaultIntraUnitLinkProps = {
     ...DefaultStructureProps,
+    sizeTheme: { name: 'physical', factor: 0.3 } as SizeTheme,
     flipSided: false,
     flatShaded: false,
 }
-export type BondProps = Partial<typeof DefaultBondProps>
+export type IntraUnitLinkProps = Partial<typeof DefaultIntraUnitLinkProps>
 
-export default function IntraUnitBondVisual(): UnitsVisual<BondProps> {
+export function IntraUnitLinkVisual(): UnitsVisual<IntraUnitLinkProps> {
     const renderObjects: RenderObject[] = []
     let cylinders: MeshRenderObject
-    let currentProps: typeof DefaultBondProps
+    let currentProps: typeof DefaultIntraUnitLinkProps
     let mesh: Mesh
     let currentGroup: Unit.SymmetryGroup
     // let vertexMap: VertexMap
 
     return {
         renderObjects,
-        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: BondProps = {}) {
-            currentProps = Object.assign({}, DefaultBondProps, props)
+        async create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: IntraUnitLinkProps = {}) {
+            currentProps = Object.assign({}, DefaultIntraUnitLinkProps, props)
 
             renderObjects.length = 0 // clear
             currentGroup = group
@@ -98,18 +100,18 @@ export default function IntraUnitBondVisual(): UnitsVisual<BondProps> {
             const elementCount = Unit.isAtomic(unit) ? unit.links.edgeCount * 2 : 0
             const instanceCount = group.units.length
 
-            mesh = await createBondMesh(ctx, unit)
+            mesh = await createLinkCylinderMesh(ctx, unit)
 
             // console.log(mesh)
             // vertexMap = VertexMap.fromMesh(mesh)
 
-            if (ctx.shouldUpdate) await ctx.update('Computing bond transforms');
+            if (ctx.shouldUpdate) await ctx.update('Computing link transforms');
             const transforms = createTransforms(group)
 
-            if (ctx.shouldUpdate) await ctx.update('Computing bond colors');
+            if (ctx.shouldUpdate) await ctx.update('Computing link colors');
             const color = createUniformColor({ value: 0xFF0000 })
 
-            if (ctx.shouldUpdate) await ctx.update('Computing bond marks');
+            if (ctx.shouldUpdate) await ctx.update('Computing link marks');
             const marker = createMarkers(instanceCount * elementCount)
 
             const values: MeshValues = {
@@ -141,7 +143,7 @@ export default function IntraUnitBondVisual(): UnitsVisual<BondProps> {
             cylinders = createMeshRenderObject(values, state)
             renderObjects.push(cylinders)
         },
-        async update(ctx: RuntimeContext, props: BondProps) {
+        async update(ctx: RuntimeContext, props: IntraUnitLinkProps) {
             const newProps = Object.assign({}, currentProps, props)
 
             if (!cylinders) return false
@@ -158,54 +160,61 @@ export default function IntraUnitBondVisual(): UnitsVisual<BondProps> {
             return true
         },
         getLoci(pickingId: PickingId) {
-            const { objectId, instanceId, elementId } = pickingId
-            const unit = currentGroup.units[instanceId]
-            if (cylinders.id === objectId && Unit.isAtomic(unit)) {
-                return Link.Loci([{
-                    aUnit: unit,
-                    aIndex: unit.links.a[elementId],
-                    bUnit: unit,
-                    bIndex: unit.links.b[elementId]
-                }])
-            }
-            return EmptyLoci
+            return getLinkLoci(pickingId, currentGroup, cylinders.id)
         },
         mark(loci: Loci, action: MarkerAction) {
-            const group = currentGroup
-            const tMarker = cylinders.values.tMarker
-            const unit = group.units[0]
-            if (!Unit.isAtomic(unit)) return
+            markLink(loci, action, currentGroup, cylinders.values)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}
 
-            const elementCount = unit.links.edgeCount * 2
-            const instanceCount = group.units.length
+function getLinkLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number) {
+    const { objectId, instanceId, elementId } = pickingId
+    const unit = group.units[instanceId]
+    if (id === objectId && Unit.isAtomic(unit)) {
+        return Link.Loci([{
+            aUnit: unit,
+            aIndex: unit.links.a[elementId],
+            bUnit: unit,
+            bIndex: unit.links.b[elementId]
+        }])
+    }
+    return EmptyLoci
+}
 
-            let changed = false
-            const array = tMarker.ref.value.array
-            if (isEveryLoci(loci)) {
-                applyMarkerAction(array, 0, elementCount * instanceCount, action)
-                changed = true
-            } else if (Link.isLoci(loci)) {
-                for (const b of loci.links) {
-                    const unitIdx = Unit.findUnitById(b.aUnit.id, group.units)
-                    if (unitIdx !== -1) {
-                        const _idx = unit.links.getEdgeIndex(b.aIndex, b.bIndex)
-                        if (_idx !== -1) {
-                            const idx = _idx
-                            if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
-                                changed = true
-                            }
-                        }
+function markLink(loci: Loci, action: MarkerAction, group: Unit.SymmetryGroup, values: MarkerData) {
+    const tMarker = values.tMarker
+    const unit = group.units[0]
+    if (!Unit.isAtomic(unit)) return
+
+    const elementCount = unit.links.edgeCount * 2
+    const instanceCount = group.units.length
+
+    let changed = false
+    const array = tMarker.ref.value.array
+    if (isEveryLoci(loci)) {
+        applyMarkerAction(array, 0, elementCount * instanceCount, action)
+        changed = true
+    } else if (Link.isLoci(loci)) {
+        for (const b of loci.links) {
+            const unitIdx = Unit.findUnitById(b.aUnit.id, group.units)
+            if (unitIdx !== -1) {
+                const _idx = unit.links.getEdgeIndex(b.aIndex, b.bIndex)
+                if (_idx !== -1) {
+                    const idx = _idx
+                    if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
+                        changed = true
                     }
                 }
-            } else {
-                return
             }
-            if (changed) {
-                ValueCell.update(tMarker, tMarker.ref.value)
-            }
-        },
-        destroy() {
-            // TODO
         }
+    } else {
+        return
     }
-}
+    if (changed) {
+        ValueCell.update(tMarker, tMarker.ref.value)
+    }
+}
\ No newline at end of file
diff --git a/src/mol-geo/theme/index.ts b/src/mol-geo/theme/index.ts
index 452ff76ed..92b1bd849 100644
--- a/src/mol-geo/theme/index.ts
+++ b/src/mol-geo/theme/index.ts
@@ -24,7 +24,8 @@ export interface UniformSizeTheme {
 }
 
 export interface ScaleSizeTheme {
-    name: 'vdw'
+    name: 'physical' // van-der-Waals for atoms, given radius for coarse spheres
+    factor?: number // scaling factor
 }
 
 export type SizeTheme = UniformSizeTheme | ScaleSizeTheme
\ No newline at end of file
diff --git a/src/mol-geo/theme/structure/size/index.ts b/src/mol-geo/theme/structure/size/index.ts
index 0c18ca93e..5878055d3 100644
--- a/src/mol-geo/theme/structure/size/index.ts
+++ b/src/mol-geo/theme/structure/size/index.ts
@@ -12,4 +12,4 @@ export interface StructureSizeDataProps {
     vertexMap: VertexMap
 }
 
-export { elementSizeData } from './element'
\ No newline at end of file
+export { physicalSizeData } from './physical'
\ No newline at end of file
diff --git a/src/mol-geo/theme/structure/size/element.ts b/src/mol-geo/theme/structure/size/physical.ts
similarity index 59%
rename from src/mol-geo/theme/structure/size/element.ts
rename to src/mol-geo/theme/structure/size/physical.ts
index 741ba136f..84be4d339 100644
--- a/src/mol-geo/theme/structure/size/element.ts
+++ b/src/mol-geo/theme/structure/size/physical.ts
@@ -8,23 +8,31 @@ import { Element, Unit, Queries } from 'mol-model/structure';
 import { StructureSizeDataProps } from '.';
 import { createAttributeSize } from '../../../util/size-data';
 
-/** Create attribute data with the size of an element, i.e. vdw for atoms and radius for coarse spheres */
-export function elementSizeData(props: StructureSizeDataProps) {
-    const { group, vertexMap } = props
-    const unit = group.units[0]
-    const elements = group.elements;
-    let radius: Element.Property<number>
+export function getPhysicalRadius(unit: Unit): Element.Property<number> {
     if (Unit.isAtomic(unit)) {
-        radius = Queries.props.atom.vdw_radius
+        return Queries.props.atom.vdw_radius
     } else if (Unit.isSpheres(unit)) {
-        radius = Queries.props.coarse.sphere_radius
+        return Queries.props.coarse.sphere_radius
+    } else {
+        return () => 0
     }
+}
+
+/**
+ * Create attribute data with the physical size of an element,
+ * i.e. vdw for atoms and radius for coarse spheres
+ */
+export function physicalSizeData(factor: number, props: StructureSizeDataProps) {
+    const { group, vertexMap } = props
+    const unit = group.units[0]
+    const elements = group.elements;
+    const radius = getPhysicalRadius(unit)
     const l = Element.Location()
     l.unit = unit
     return createAttributeSize({
         sizeFn: (elementIdx: number) => {
             l.element = elements[elementIdx]
-            return radius(l)
+            return radius(l) * factor
         },
         vertexMap
     })
diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts
index 1f46148e8..cade31f83 100644
--- a/src/mol-view/stage.ts
+++ b/src/mol-view/stage.ts
@@ -7,7 +7,7 @@
 import Viewer from 'mol-view/viewer'
 import { StateContext } from './state/context';
 import { Progress } from 'mol-task';
-import { MmcifUrlToModel, ModelToStructure, StructureToSpacefill, StructureToBond } from './state/transform';
+import { MmcifUrlToModel, ModelToStructure, StructureToSpacefill, StructureToBallAndStick } from './state/transform';
 import { UrlEntity } from './state/entity';
 import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
 import { Context } from 'mol-app/context/context';
@@ -47,8 +47,8 @@ export class Stage {
         const urlEntity = UrlEntity.ofUrl(this.ctx, url)
         const modelEntity = await MmcifUrlToModel.apply(this.ctx, urlEntity)
         const structureEntity = await ModelToStructure.apply(this.ctx, modelEntity)
-        StructureToSpacefill.apply(this.ctx, structureEntity, spacefillProps)
-        StructureToBond.apply(this.ctx, structureEntity, spacefillProps) // TODO props
+        StructureToSpacefill.apply(this.ctx, structureEntity, { ...spacefillProps, visible: false })
+        StructureToBallAndStick.apply(this.ctx, structureEntity, spacefillProps) // TODO props
 
         this.globalContext.components.sequenceView.setState({ structure: structureEntity.value });
     }
diff --git a/src/mol-view/state/entity.ts b/src/mol-view/state/entity.ts
index f987c4ada..2b089ed13 100644
--- a/src/mol-view/state/entity.ts
+++ b/src/mol-view/state/entity.ts
@@ -13,7 +13,7 @@ import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
 import { Model, Structure } from 'mol-model/structure';
 import { StructureRepresentation } from 'mol-geo/representation/structure';
 import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
-import { BondProps } from 'mol-geo/representation/structure/bond';
+import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-stick';
 
 const getNextId = idFactory(1)
 
@@ -122,9 +122,9 @@ export namespace SpacefillEntity {
     }
 }
 
-export type BondEntity = StateEntity<StructureRepresentation<BondProps>, 'bond'>
-export namespace BondEntity {
-    export function ofRepr(ctx: StateContext, repr: StructureRepresentation<BondProps>): BondEntity {
-        return StateEntity.create(ctx, 'bond', 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 )
     }
 }
\ No newline at end of file
diff --git a/src/mol-view/state/transform.ts b/src/mol-view/state/transform.ts
index a618bdcca..499f564a8 100644
--- a/src/mol-view/state/transform.ts
+++ b/src/mol-view/state/transform.ts
@@ -5,14 +5,13 @@
  */
 
 import CIF from 'mol-io/reader/cif'
-import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity, NullEntity, BondEntity } from './entity';
+import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity, NullEntity, BallAndStickEntity } from './entity';
 import { Model, Structure, Format } from 'mol-model/structure';
 
 import { StateContext } from './context';
-import Spacefill, { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
-import { StructureRepresentation } from 'mol-geo/representation/structure';
 import StructureSymmetry from 'mol-model/structure/structure/symmetry';
-import Bond, { BondProps } from 'mol-geo/representation/structure/bond';
+import { SpacefillProps, SpacefillRepresentation } from 'mol-geo/representation/structure/spacefill';
+import { BallAndStickProps, BallAndStickRepresentation } from 'mol-geo/representation/structure/ball-and-stick';
 
 type transformer<I extends AnyEntity, O extends AnyEntity, P extends {}> = (ctx: StateContext, inputEntity: I, props?: P) => Promise<O>
 
@@ -95,7 +94,7 @@ export type StructureCenter = StateTransform<StructureEntity, NullEntity, {}>
 export type StructureToSpacefill = StateTransform<StructureEntity, SpacefillEntity, SpacefillProps>
 export const StructureToSpacefill: StructureToSpacefill = StateTransform.create('structure', 'spacefill', 'structure-to-spacefill',
     async function (ctx: StateContext, structureEntity: StructureEntity, props: SpacefillProps = {}) {
-        const spacefillRepr = StructureRepresentation(Spacefill)
+        const spacefillRepr = SpacefillRepresentation()
         await spacefillRepr.create(structureEntity.value, props).run(ctx.log)
         ctx.viewer.add(spacefillRepr)
         ctx.viewer.requestDraw()
@@ -103,15 +102,15 @@ export const StructureToSpacefill: StructureToSpacefill = StateTransform.create(
         return SpacefillEntity.ofRepr(ctx, spacefillRepr)
     })
 
-export type StructureToBond = StateTransform<StructureEntity, BondEntity, BondProps>
-    export const StructureToBond: StructureToBond = StateTransform.create('structure', 'bond', 'structure-to-bond',
-        async function (ctx: StateContext, structureEntity: StructureEntity, props: BondProps = {}) {
-            const bondRepr = StructureRepresentation(Bond)
-            await bondRepr.create(structureEntity.value, props).run(ctx.log)
-            ctx.viewer.add(bondRepr)
+export type StructureToBallAndStick = StateTransform<StructureEntity, BallAndStickEntity, BallAndStickProps>
+    export const StructureToBallAndStick: StructureToBallAndStick = StateTransform.create('structure', 'ballandstick', 'structure-to-ballandstick',
+        async function (ctx: StateContext, structureEntity: StructureEntity, props: BallAndStickProps = {}) {
+            const ballAndStickRepr = BallAndStickRepresentation()
+            await ballAndStickRepr.create(structureEntity.value, props).run(ctx.log)
+            ctx.viewer.add(ballAndStickRepr)
             ctx.viewer.requestDraw()
             console.log('stats', ctx.viewer.stats)
-            return BondEntity.ofRepr(ctx, bondRepr)
+            return BallAndStickEntity.ofRepr(ctx, ballAndStickRepr)
         })
 
 export type SpacefillUpdate = StateTransform<SpacefillEntity, NullEntity, SpacefillProps>
@@ -125,12 +124,12 @@ export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill
         return NullEntity
     })
 
-export type BondUpdate = StateTransform<BondEntity, NullEntity, BondProps>
-    export const BondUpdate: BondUpdate = StateTransform.create('bond', 'null', 'bond-update',
-        async function (ctx: StateContext, bondEntity: BondEntity, props: BondProps = {}) {
-            const bondRepr = bondEntity.value
-            await bondRepr.update(props).run(ctx.log)
-            ctx.viewer.add(bondRepr)
+export type BallAndStickUpdate = StateTransform<BallAndStickEntity, NullEntity, BallAndStickProps>
+    export const BallAndStickUpdate: BallAndStickUpdate = StateTransform.create('ballandstick', 'null', 'ballandstick-update',
+        async function (ctx: StateContext, ballAndStickEntity: BallAndStickEntity, props: BallAndStickProps = {}) {
+            const ballAndStickRepr = ballAndStickEntity.value
+            await ballAndStickRepr.update(props).run(ctx.log)
+            ctx.viewer.add(ballAndStickRepr)
             ctx.viewer.requestDraw()
             console.log('stats', ctx.viewer.stats)
             return NullEntity
-- 
GitLab