Browse Source

wip, size theme

Alexander Rose 6 years ago
parent
commit
32b6b00164

+ 4 - 1
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, StructureToBallAndStick, DistanceRestraintUpdate, CartoonUpdate, BallAndStickUpdate, BackboneUpdate, MmcifUrlToSpacefill } from 'mol-view/state/transform';
+import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill, StructureCenter, StructureToBallAndStick, DistanceRestraintUpdate, CartoonUpdate, BallAndStickUpdate, BackboneUpdate, MmcifUrlToSpacefill, CarbohydrateUpdate } from 'mol-view/state/transform';
 
 function getTransforms(entity: AnyEntity): AnyTransform[] {
     const transforms: AnyTransform[] = []
@@ -57,6 +57,9 @@ function getTransforms(entity: AnyEntity): AnyTransform[] {
         case 'cartoon':
             transforms.push(CartoonUpdate)
             break;
+        case 'carbohydrate':
+            transforms.push(CarbohydrateUpdate)
+            break;
     }
     return transforms
 }

+ 16 - 1
src/mol-app/ui/transform/backbone.tsx

@@ -46,7 +46,7 @@ export class Backbone extends View<Controller<any>, BackboneState, { transform:
         detail: 2,
         colorTheme: { name: 'element-symbol' } as ColorThemeProps,
         colorValue: 0x000000,
-        sizeTheme: { name: 'uniform' } as SizeThemeProps,
+        sizeTheme: { name: 'uniform', factor: 1 } as SizeThemeProps,
         visible: true,
         alpha: 1,
         depthMask: true,
@@ -219,6 +219,21 @@ export class Backbone extends View<Controller<any>, BackboneState, { transform:
                                 />
                             </div>
                         </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Slider
+                                    value={this.state.sizeTheme.factor || 1}
+                                    label='Size factor'
+                                    min={0.1}
+                                    max={3}
+                                    step={0.01}
+                                    callOnChangeWhileSliding={true}
+                                    onChange={value => this.update({
+                                        sizeTheme: { ...this.state.sizeTheme, factor: value }
+                                    })}
+                                />
+                            </div>
+                        </div>
                     </div>
                 </div>
             </div>

+ 16 - 1
src/mol-app/ui/transform/ball-and-stick.tsx

@@ -49,7 +49,7 @@ export class BallAndStick extends View<Controller<any>, BallAndStickState, { tra
         flatShaded: false,
         colorTheme: { name: 'element-symbol' } as ColorThemeProps,
         colorValue: 0x000000,
-        sizeTheme: { name: 'uniform' } as SizeThemeProps,
+        sizeTheme: { name: 'uniform', value: 0.15 } as SizeThemeProps,
         visible: true,
         alpha: 1,
         depthMask: true,
@@ -211,6 +211,21 @@ export class BallAndStick extends View<Controller<any>, BallAndStickState, { tra
                                 />
                             </div>
                         </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Slider
+                                    value={this.state.sizeTheme.factor || 1}
+                                    label='Size factor'
+                                    min={0.1}
+                                    max={3}
+                                    step={0.01}
+                                    callOnChangeWhileSliding={true}
+                                    onChange={value => this.update({
+                                        sizeTheme: { ...this.state.sizeTheme, factor: value }
+                                    })}
+                                />
+                            </div>
+                        </div>
                     </div>
                 </div>
             </div>

+ 16 - 1
src/mol-app/ui/transform/carbohydrate.tsx

@@ -50,7 +50,7 @@ export class Carbohydrate extends View<Controller<any>, CarbohydrateState, { tra
         detail: 2,
         colorTheme: { name: 'element-symbol' } as ColorThemeProps,
         colorValue: 0x000000,
-        sizeTheme: { name: 'uniform' } as SizeThemeProps,
+        sizeTheme: { name: 'uniform', factor: 1 } as SizeThemeProps,
         visible: true,
         alpha: 1,
         depthMask: true,
@@ -227,6 +227,21 @@ export class Carbohydrate extends View<Controller<any>, CarbohydrateState, { tra
                                 />
                             </div>
                         </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Slider
+                                    value={this.state.sizeTheme.factor || 1}
+                                    label='Size factor'
+                                    min={0.1}
+                                    max={3}
+                                    step={0.01}
+                                    callOnChangeWhileSliding={true}
+                                    onChange={value => this.update({
+                                        sizeTheme: { ...this.state.sizeTheme, factor: value }
+                                    })}
+                                />
+                            </div>
+                        </div>
                     </div>
                 </div>
             </div>

+ 53 - 2
src/mol-app/ui/transform/cartoon.tsx

@@ -36,6 +36,10 @@ interface CartoonState {
     useFog: boolean
     quality: VisualQuality
     unitKinds: Unit.Kind[]
+    linearSegments: number
+    radialSegments: number
+    aspectRatio: number
+    arrowFactor: number
 }
 
 export class Cartoon extends View<Controller<any>, CartoonState, { transform: CartoonUpdate, entity: CartoonEntity, ctx: StateContext }> {
@@ -46,13 +50,17 @@ export class Cartoon extends View<Controller<any>, CartoonState, { transform: Ca
         detail: 2,
         colorTheme: { name: 'element-symbol' } as ColorThemeProps,
         colorValue: 0x000000,
-        sizeTheme: { name: 'uniform' } as SizeThemeProps,
+        sizeTheme: { name: 'uniform', value: 0.13, factor: 1 } as SizeThemeProps,
         visible: true,
         alpha: 1,
         depthMask: true,
         useFog: true,
         quality: 'auto' as VisualQuality,
-        unitKinds: [] as Unit.Kind[]
+        unitKinds: [] as Unit.Kind[],
+        linearSegments: 8,
+        radialSegments: 12,
+        aspectRatio: 8,
+        arrowFactor: 1.5
     }
 
     componentWillMount() {
@@ -219,6 +227,49 @@ export class Cartoon extends View<Controller<any>, CartoonState, { transform: Ca
                                 />
                             </div>
                         </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Slider
+                                    value={this.state.aspectRatio || 1}
+                                    label='Aspect ratio'
+                                    min={0.1}
+                                    max={10}
+                                    step={0.1}
+                                    callOnChangeWhileSliding={true}
+                                    onChange={value => this.update({ aspectRatio: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Slider
+                                    value={this.state.sizeTheme.value || 0.1}
+                                    label='Size value'
+                                    min={0.01}
+                                    max={0.3}
+                                    step={0.01}
+                                    callOnChangeWhileSliding={true}
+                                    onChange={value => this.update({
+                                        sizeTheme: { ...this.state.sizeTheme, value: value }
+                                    })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Slider
+                                    value={this.state.sizeTheme.factor || 1}
+                                    label='Size factor'
+                                    min={0.1}
+                                    max={3}
+                                    step={0.01}
+                                    callOnChangeWhileSliding={true}
+                                    onChange={value => this.update({
+                                        sizeTheme: { ...this.state.sizeTheme, factor: value }
+                                    })}
+                                />
+                            </div>
+                        </div>
                     </div>
                 </div>
             </div>

+ 3 - 0
src/mol-app/ui/transform/list.tsx

@@ -23,6 +23,7 @@ import { Cartoon } from './cartoon';
 import { DistanceRestraint } from './distance-restraint';
 import { Backbone } from './backbone';
 import { UrlLoader } from './url-loader';
+import { Carbohydrate } from './carbohydrate';
 
 function getTransformComponent(controller: TransformListController, entity: AnyEntity, transform: AnyTransform) {
     switch (transform.kind) {
@@ -44,6 +45,8 @@ function getTransformComponent(controller: TransformListController, entity: AnyE
             return <Backbone controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Backbone>
         case 'cartoon-update':
             return <Cartoon controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Cartoon>
+        case 'carbohydrate-update':
+            return <Carbohydrate controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Carbohydrate>
     }
     return <Transform controller={controller} entity={entity} transform={transform}></Transform>
 }

+ 14 - 2
src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts

@@ -16,7 +16,9 @@ import { LocationIterator } from './util/location-iterator';
 import { createLinkCylinderMesh, DefaultLinkCylinderProps, LinkCylinderProps } from './util/link';
 import { OrderedSet, Interval } from 'mol-data/int';
 import { ComplexMeshVisual } from '../complex-visual';
-import { SizeThemeProps } from 'mol-view/theme/size';
+import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
+import { LinkType } from 'mol-model/structure/model/types';
+import { BitFlags } from 'mol-util';
 
 // TODO create seperate visual
 // for (let i = 0, il = carbohydrates.terminalLinks.length; i < il; ++i) {
@@ -30,8 +32,12 @@ import { SizeThemeProps } from 'mol-view/theme/size';
 //     }
 // }
 
+const radiusFactor = 0.3
+
 async function createCarbohydrateLinkCylinderMesh(ctx: RuntimeContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) {
     const { links, elements } = structure.carbohydrates
+    const sizeTheme = SizeTheme(props.sizeTheme)
+    const location = StructureElement.create()
 
     const builderProps = {
         linkCount: links.length,
@@ -42,7 +48,13 @@ async function createCarbohydrateLinkCylinderMesh(ctx: RuntimeContext, structure
             Vec3.copy(posB, elements[l.carbohydrateIndexB].geometry.center)
         },
         order: (edgeIndex: number) => 1,
-        flags: (edgeIndex: number) => 0
+        flags: (edgeIndex: number) => BitFlags.create(LinkType.Flag.None),
+        radius: (edgeIndex: number) => {
+            const l = links[edgeIndex]
+            location.unit = elements[l.carbohydrateIndexA].unit
+            location.element = elements[l.carbohydrateIndexA].anomericCarbon
+            return sizeTheme.size(location) * radiusFactor
+        }
     }
 
     return createLinkCylinderMesh(ctx, builderProps, props, mesh)

+ 16 - 6
src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts

@@ -16,24 +16,34 @@ import { getSaccharideShape, SaccharideShapes } from 'mol-model/structure/struct
 import { LocationIterator } from './util/location-iterator';
 import { OrderedSet, Interval } from 'mol-data/int';
 import { ComplexMeshVisual, DefaultComplexMeshProps } from '../complex-visual';
-import { SizeThemeProps } from 'mol-view/theme/size';
+import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
 
 const t = Mat4.identity()
 const sVec = Vec3.zero()
 const pd = Vec3.zero()
 
+const sideFactor = 1.75 * 2 * 0.806; // 0.806 == Math.cos(Math.PI / 4)
+const radiusFactor = 1.75
+
 async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Structure, props: CarbohydrateSymbolProps, mesh?: Mesh) {
     const builder = MeshBuilder.create(256, 128, mesh)
 
-    const carbohydrates = structure.carbohydrates
+    const sizeTheme = SizeTheme(props.sizeTheme)
+    const { detail } = props
 
-    const side = 1.75 * 2 * 0.806; // 0.806 == Math.cos(Math.PI / 4)
-    const radius = 1.75
+    const carbohydrates = structure.carbohydrates
+    const l = StructureElement.create()
 
     for (let i = 0, il = carbohydrates.elements.length; i < il; ++i) {
         const c = carbohydrates.elements[i];
         const shapeType = getSaccharideShape(c.component.type)
 
+        l.unit = c.unit
+        l.element = c.unit.elements[c.anomericCarbon]
+        const size = sizeTheme.size(l)
+        const radius = size * radiusFactor
+        const side = size * sideFactor
+
         const { center, normal, direction } = c.geometry
         Vec3.add(pd, center, direction)
         Mat4.targetTo(t, center, pd, normal)
@@ -43,7 +53,7 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru
 
         switch (shapeType) {
             case SaccharideShapes.FilledSphere:
-                builder.addSphere(center, radius, 2)
+                builder.addSphere(center, radius, detail)
                 break;
             case SaccharideShapes.FilledCube:
                 Mat4.scaleUniformly(t, t, side)
@@ -113,7 +123,7 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru
 
 export const DefaultCarbohydrateSymbolProps = {
     ...DefaultComplexMeshProps,
-    sizeTheme: { name: 'physical', factor: 1 } as SizeThemeProps,
+    sizeTheme: { name: 'uniform', value: 1, factor: 1 } as SizeThemeProps,
     detail: 0,
     unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
 }

+ 13 - 4
src/mol-geo/representation/structure/visual/cross-link-restraint-cylinder.ts

@@ -15,26 +15,35 @@ import { Loci, EmptyLoci } from 'mol-model/loci';
 import { ComplexMeshVisual, DefaultComplexMeshProps } from '../complex-visual';
 import { LocationIterator } from './util/location-iterator';
 import { Interval } from 'mol-data/int';
-import { SizeThemeProps } from 'mol-view/theme/size';
+import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
+import { BitFlags } from 'mol-util';
+import { LinkType } from 'mol-model/structure/model/types';
 
 async function createCrossLinkRestraintCylinderMesh(ctx: RuntimeContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) {
 
     const crossLinks = structure.crossLinkRestraints
     if (!crossLinks.count) return Mesh.createEmpty(mesh)
 
+    const sizeTheme = SizeTheme(props.sizeTheme)
+    const location = StructureElement.create()
+
     const builderProps = {
         linkCount: crossLinks.count,
         referencePosition: (edgeIndex: number) => null,
         position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
             const b = crossLinks.pairs[edgeIndex]
-            // console.log(b)
             const uA = b.unitA, uB = b.unitB
             uA.conformation.position(uA.elements[b.indexA], posA)
             uB.conformation.position(uB.elements[b.indexB], posB)
-            // console.log(posA, posB)
         },
         order: (edgeIndex: number) => 1,
-        flags: (edgeIndex: number) => 0
+        flags: (edgeIndex: number) => BitFlags.create(LinkType.Flag.None),
+        radius: (edgeIndex: number) => {
+            const b = crossLinks.pairs[edgeIndex]
+            location.unit = b.unitA
+            location.element = b.unitA.elements[b.indexA]
+            return sizeTheme.size(location)
+        }
     }
 
     return createLinkCylinderMesh(ctx, builderProps, props, mesh)

+ 12 - 2
src/mol-geo/representation/structure/visual/inter-unit-link-cylinder.ts

@@ -15,7 +15,8 @@ import { Loci, EmptyLoci } from 'mol-model/loci';
 import { LinkIterator } from './util/location-iterator';
 import { ComplexMeshVisual, DefaultComplexMeshProps } from '../complex-visual';
 import { Interval } from 'mol-data/int';
-import { SizeThemeProps } from 'mol-view/theme/size';
+import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
+import { BitFlags } from 'mol-util';
 
 async function createInterUnitLinkCylinderMesh(ctx: RuntimeContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) {
     const links = structure.links
@@ -23,6 +24,9 @@ async function createInterUnitLinkCylinderMesh(ctx: RuntimeContext, structure: S
 
     if (!bondCount) return Mesh.createEmpty(mesh)
 
+    const sizeTheme = SizeTheme(props.sizeTheme)
+    const location = StructureElement.create()
+
     const builderProps = {
         linkCount: bondCount,
         referencePosition: (edgeIndex: number) => null, // TODO
@@ -33,7 +37,13 @@ async function createInterUnitLinkCylinderMesh(ctx: RuntimeContext, structure: S
             uB.conformation.position(uB.elements[b.indexB], posB)
         },
         order: (edgeIndex: number) => bonds[edgeIndex].order,
-        flags: (edgeIndex: number) => bonds[edgeIndex].flag
+        flags: (edgeIndex: number) => BitFlags.create(bonds[edgeIndex].flag),
+        radius: (edgeIndex: number) => {
+            const b = bonds[edgeIndex]
+            location.unit = b.unitA
+            location.element = b.unitA.elements[b.indexA]
+            return sizeTheme.size(location)
+        }
     }
 
     return createLinkCylinderMesh(ctx, builderProps, props, mesh)

+ 10 - 2
src/mol-geo/representation/structure/visual/intra-unit-link-cylinder.ts

@@ -16,11 +16,15 @@ import { Loci, EmptyLoci } from 'mol-model/loci';
 import { LinkIterator } from './util/location-iterator';
 import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual';
 import { Interval } from 'mol-data/int';
-import { SizeThemeProps } from 'mol-view/theme/size';
+import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
+import { BitFlags } from 'mol-util';
 
 async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit, props: LinkCylinderProps, mesh?: Mesh) {
     if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
 
+    const sizeTheme = SizeTheme(props.sizeTheme)
+    const location = StructureElement.create(unit)
+
     const elements = unit.elements;
     const links = unit.links
     const { edgeCount, a, b, edgeProps, offset } = links
@@ -49,7 +53,11 @@ async function createIntraUnitLinkCylinderMesh(ctx: RuntimeContext, unit: Unit,
             pos(elements[b[edgeIndex]], posB)
         },
         order: (edgeIndex: number) => _order[edgeIndex],
-        flags: (edgeIndex: number) => _flags[edgeIndex]
+        flags: (edgeIndex: number) => BitFlags.create(_flags[edgeIndex]),
+        radius: (edgeIndex: number) => {
+            location.element = elements[a[edgeIndex]]
+            return sizeTheme.size(location)
+        }
     }
 
     return createLinkCylinderMesh(ctx, builderProps, props, mesh)

+ 30 - 11
src/mol-geo/representation/structure/visual/polymer-backbone-cylinder.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit } from 'mol-model/structure';
+import { Unit, StructureElement } from 'mol-model/structure';
 import { UnitsVisual } from '..';
 import { RuntimeContext } from 'mol-task'
 import { Mesh } from '../../../shape/mesh';
@@ -14,31 +14,49 @@ 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';
+import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
+import { CylinderProps } from '../../../primitive/cylinder';
 
-async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) {
+export interface PolymerBackboneCylinderProps {
+    sizeTheme: SizeThemeProps
+    radialSegments: number
+}
+
+async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit, props: PolymerBackboneCylinderProps, mesh?: Mesh) {
     const polymerElementCount = getPolymerElementCount(unit)
     if (!polymerElementCount) return Mesh.createEmpty(mesh)
-    console.log('polymerElementCount backbone', polymerElementCount)
 
-    // TODO better vertex count estimates
-    const builder = MeshBuilder.create(polymerElementCount * 30, polymerElementCount * 30 / 2, mesh)
+    const sizeTheme = SizeTheme(props.sizeTheme)
+    const { radialSegments } = props
+
+    const vertexCountEstimate = radialSegments * 2 * polymerElementCount * 2
+    const builder = MeshBuilder.create(vertexCountEstimate, vertexCountEstimate / 10, mesh)
 
     const { elements } = unit
     const pos = unit.conformation.invariantPosition
     const pA = Vec3.zero()
     const pB = Vec3.zero()
+    const l = StructureElement.create(unit)
+    const cylinderProps: CylinderProps = { radiusTop: 1, radiusBottom: 1, radialSegments }
 
     let i = 0
     const polymerBackboneIt = PolymerBackboneIterator(unit)
     while (polymerBackboneIt.hasNext) {
-        // TODO size theme
         const { centerA, centerB } = polymerBackboneIt.move()
-        pos(elements[centerA.element], pA)
-        pos(elements[centerB.element], pB)
+        const elmA = elements[centerA.element]
+        const elmB = elements[centerB.element]
+        pos(elmA, pA)
+        pos(elmB, pB)
+
+        l.element = elmA
+        cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(l)
         builder.setId(centerA.element)
-        builder.addCylinder(pA, pB, 0.5, { radiusTop: 0.2, radiusBottom: 0.2 })
+        builder.addCylinder(pA, pB, 0.5, cylinderProps)
+
+        l.element = elmB
+        cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(l)
         builder.setId(centerB.element)
-        builder.addCylinder(pB, pA, 0.5, { radiusTop: 0.2, radiusBottom: 0.2 })
+        builder.addCylinder(pB, pA, 0.5, cylinderProps)
 
         if (i % 10000 === 0 && ctx.shouldUpdate) {
             await ctx.update({ message: 'Backbone mesh', current: i, max: polymerElementCount });
@@ -50,7 +68,8 @@ async function createPolymerBackboneCylinderMesh(ctx: RuntimeContext, unit: Unit
 }
 
 export const DefaultPolymerBackboneProps = {
-    ...DefaultUnitsMeshProps
+    ...DefaultUnitsMeshProps,
+    radialSegments: 16
 }
 export type PolymerBackboneProps = typeof DefaultPolymerBackboneProps
 

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

@@ -15,6 +15,7 @@ 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';
+import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
 
 const t = Mat4.identity()
 const sVec = Vec3.zero()
@@ -22,13 +23,22 @@ const n0 = Vec3.zero()
 const n1 = Vec3.zero()
 const upVec = Vec3.zero()
 
-async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) {
+const depthFactor = 4
+const widthFactor = 4
+const heightFactor = 6
+
+export interface PolymerDirectionWedgeProps {
+    sizeTheme: SizeThemeProps
+}
+
+async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit, props: PolymerDirectionWedgeProps, mesh?: Mesh) {
     const polymerElementCount = getPolymerElementCount(unit)
-    console.log('polymerElementCount direction', polymerElementCount)
     if (!polymerElementCount) return Mesh.createEmpty(mesh)
 
-    // TODO better vertex count estimates
-    const builder = MeshBuilder.create(polymerElementCount * 30, polymerElementCount * 30 / 2, mesh)
+    const sizeTheme = SizeTheme(props.sizeTheme)
+
+    const vertexCount = polymerElementCount * 24
+    const builder = MeshBuilder.create(vertexCount, vertexCount / 10, mesh)
     const linearSegments = 1
 
     const state = createCurveSegmentState(linearSegments)
@@ -47,18 +57,15 @@ async function createPolymerDirectionWedgeMesh(ctx: RuntimeContext, unit: Unit,
         interpolateCurveSegment(state, v, tension)
 
         if ((isSheet && !v.secStrucChange) || !isSheet) {
+            const size = sizeTheme.size(v.center)
+            const depth = depthFactor * size
+            const width = widthFactor * size
+            const height = heightFactor * size
 
-            let width = 0.5, height = 1.2, depth = 0.6
-            if (isNucleic) {
-                Vec3.fromArray(n0, binormalVectors, 0)
-                Vec3.fromArray(n1, binormalVectors, 3)
-                Vec3.normalize(upVec, Vec3.add(upVec, n0, n1))
-                depth = 0.9
-            } else {
-                Vec3.fromArray(n0, normalVectors, 0)
-                Vec3.fromArray(n1, normalVectors, 3)
-                Vec3.normalize(upVec, Vec3.add(upVec, n0, n1))
-            }
+            const vectors = isNucleic ? binormalVectors : normalVectors
+            Vec3.fromArray(n0, vectors, 0)
+            Vec3.fromArray(n1, vectors, 3)
+            Vec3.normalize(upVec, Vec3.add(upVec, n0, n1))
 
             Mat4.targetTo(t, v.p3, v.p1, upVec)
             Mat4.mul(t, t, Mat4.rotY90Z180)

+ 34 - 11
src/mol-geo/representation/structure/visual/polymer-gap-cylinder.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit } from 'mol-model/structure';
+import { Unit, StructureElement } from 'mol-model/structure';
 import { UnitsVisual } from '..';
 import { RuntimeContext } from 'mol-task'
 import { Mesh } from '../../../shape/mesh';
@@ -14,36 +14,58 @@ 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';
+import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
+import { CylinderProps } from '../../../primitive/cylinder';
 
-async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) {
+const segmentCount = 10
+
+export interface PolymerGapCylinderProps {
+    sizeTheme: SizeThemeProps
+    radialSegments: number
+}
+
+async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, props: PolymerGapCylinderProps, mesh?: Mesh) {
     const polymerGapCount = getPolymerGapCount(unit)
     if (!polymerGapCount) return Mesh.createEmpty(mesh)
-    console.log('polymerGapCount', polymerGapCount)
 
-    // TODO better vertex count estimates
-    const builder = MeshBuilder.create(polymerGapCount * 30, polymerGapCount * 30 / 2, mesh)
+    const sizeTheme = SizeTheme(props.sizeTheme)
+    const { radialSegments } = props
+
+    const vertexCountEstimate = segmentCount * radialSegments * 2 * polymerGapCount * 2
+    const builder = MeshBuilder.create(vertexCountEstimate, vertexCountEstimate / 10, mesh)
 
     const { elements } = unit
     const pos = unit.conformation.invariantPosition
     const pA = Vec3.zero()
     const pB = Vec3.zero()
+    const l = StructureElement.create(unit)
+    const cylinderProps: CylinderProps = {
+        radiusTop: 1, radiusBottom: 1, topCap: true, bottomCap: true, radialSegments
+    }
 
     let i = 0
     const polymerGapIt = PolymerGapIterator(unit)
     while (polymerGapIt.hasNext) {
-        // TODO size theme
         const { centerA, centerB } = polymerGapIt.move()
         if (centerA.element === centerB.element) {
             builder.setId(centerA.element)
             pos(elements[centerA.element], pA)
             builder.addSphere(pA, 0.6, 0)
         } else {
-            pos(elements[centerA.element], pA)
-            pos(elements[centerB.element], pB)
+            const elmA = elements[centerA.element]
+            const elmB = elements[centerB.element]
+            pos(elmA, pA)
+            pos(elmB, pB)
+
+            l.element = elmA
+            cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(l)
             builder.setId(centerA.element)
-            builder.addFixedCountDashedCylinder(pA, pB, 0.5, 10, { radiusTop: 0.2, radiusBottom: 0.2 })
+            builder.addFixedCountDashedCylinder(pA, pB, 0.5, segmentCount, cylinderProps)
+
+            l.element = elmB
+            cylinderProps.radiusTop = cylinderProps.radiusBottom = sizeTheme.size(l)
             builder.setId(centerB.element)
-            builder.addFixedCountDashedCylinder(pB, pA, 0.5, 10, { radiusTop: 0.2, radiusBottom: 0.2 })
+            builder.addFixedCountDashedCylinder(pB, pA, 0.5, segmentCount, cylinderProps)
         }
 
         if (i % 10000 === 0 && ctx.shouldUpdate) {
@@ -56,7 +78,8 @@ async function createPolymerGapCylinderMesh(ctx: RuntimeContext, unit: Unit, pro
 }
 
 export const DefaultPolymerGapProps = {
-    ...DefaultUnitsMeshProps
+    ...DefaultUnitsMeshProps,
+    radialSegments: 16
 }
 export type PolymerGapProps = typeof DefaultPolymerGapProps
 

+ 38 - 16
src/mol-geo/representation/structure/visual/polymer-trace-mesh.ts

@@ -5,7 +5,7 @@
  */
 
 import { Unit } from 'mol-model/structure';
-import { UnitsVisual } from '..';
+import { UnitsVisual, MeshUpdateState } from '..';
 import { RuntimeContext } from 'mol-task'
 import { markElement, getElementLoci } from './util/element';
 import { Mesh } from '../../../shape/mesh';
@@ -14,18 +14,27 @@ import { getPolymerElementCount, PolymerTraceIterator, createCurveSegmentState,
 import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/types';
 import { StructureElementIterator } from './util/location-iterator';
 import { UnitsMeshVisual, DefaultUnitsMeshProps } from '../units-visual';
+import { SizeThemeProps, SizeTheme } from 'mol-view/theme/size';
+
+export interface PolymerTraceMeshProps {
+    sizeTheme: SizeThemeProps
+    linearSegments: number
+    radialSegments: number
+    aspectRatio: number
+    arrowFactor: number
+}
 
 // TODO handle polymer ends properly
 
-async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, props: {}, mesh?: Mesh) {
+async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, props: PolymerTraceMeshProps, mesh?: Mesh) {
     const polymerElementCount = getPolymerElementCount(unit)
-    console.log('polymerElementCount trace', polymerElementCount)
     if (!polymerElementCount) return Mesh.createEmpty(mesh)
 
-    // TODO better vertex count estimates
-    const builder = MeshBuilder.create(polymerElementCount * 30, polymerElementCount * 30 / 2, mesh)
-    const linearSegments = 8
-    const radialSegments = 12
+    const sizeTheme = SizeTheme(props.sizeTheme)
+    const { linearSegments, radialSegments, aspectRatio, arrowFactor } = props
+
+    const vertexCount = linearSegments * radialSegments * polymerElementCount + (radialSegments + 1) * polymerElementCount * 2
+    const builder = MeshBuilder.create(vertexCount, vertexCount / 10, mesh)
 
     const state = createCurveSegmentState(linearSegments)
     const { curvePoints, normalVectors, binormalVectors } = state
@@ -41,21 +50,23 @@ async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, props: {}
         const isHelix = SecondaryStructureType.is(v.secStrucType, SecondaryStructureType.Flag.Helix)
         const tension = (isNucleic || isSheet) ? 0.5 : 0.9
 
-        // console.log('ELEMENT', i)
         interpolateCurveSegment(state, v, tension)
 
-        let width = 0.2, height = 0.2
+        let width = sizeTheme.size(v.center)
 
-        // TODO size theme
         if (isSheet) {
-            width = 0.15; height = 1.0
-            const arrowHeight = v.secStrucChange ? 1.7 : 0
+            const height = width * aspectRatio
+            const arrowHeight = v.secStrucChange ? height * arrowFactor : 0
             builder.addSheet(curvePoints, normalVectors, binormalVectors, linearSegments, width, height, arrowHeight, true, true)
         } else {
+            let height: number
             if (isHelix) {
-                width = 0.2; height = 1.0
+                height = width * aspectRatio
             } else if (isNucleic) {
-                width = 1.5; height = 0.3
+                height = width * aspectRatio;
+                [width, height] = [height, width]
+            } else {
+                height = width
             }
             builder.addTube(curvePoints, normalVectors, binormalVectors, linearSegments, radialSegments, width, height, 1, true, true)
         }
@@ -70,7 +81,11 @@ async function createPolymerTraceMesh(ctx: RuntimeContext, unit: Unit, props: {}
 }
 
 export const DefaultPolymerTraceProps = {
-    ...DefaultUnitsMeshProps
+    ...DefaultUnitsMeshProps,
+    linearSegments: 8,
+    radialSegments: 12,
+    aspectRatio: 5,
+    arrowFactor: 1.5
 }
 export type PolymerTraceProps = typeof DefaultPolymerTraceProps
 
@@ -81,6 +96,13 @@ export function PolymerTraceVisual(): UnitsVisual<PolymerTraceProps> {
         createLocationIterator: StructureElementIterator.fromGroup,
         getLoci: getElementLoci,
         mark: markElement,
-        setUpdateState: () => {}
+        setUpdateState: (state: MeshUpdateState, newProps: PolymerTraceProps, currentProps: PolymerTraceProps) => {
+            state.createMesh = (
+                newProps.linearSegments !== currentProps.linearSegments ||
+                newProps.radialSegments !== currentProps.radialSegments ||
+                newProps.aspectRatio !== currentProps.aspectRatio ||
+                newProps.arrowFactor !== currentProps.arrowFactor
+            )
+        }
     })
 }

+ 20 - 23
src/mol-geo/representation/structure/visual/util/link.ts

@@ -10,9 +10,12 @@ import { Mesh } from '../../../../shape/mesh';
 import { MeshBuilder } from '../../../../shape/mesh-builder';
 import { LinkType } from 'mol-model/structure/model/types';
 import { DefaultMeshProps } from '../../../util';
+import { SizeThemeProps } from 'mol-view/theme/size';
+import { CylinderProps } from '../../../../primitive/cylinder';
 
 export const DefaultLinkCylinderProps = {
     ...DefaultMeshProps,
+    sizeTheme: { name: 'uniform', value: 0.15 } as SizeThemeProps,
     linkScale: 0.4,
     linkSpacing: 1,
     linkRadius: 0.25,
@@ -55,7 +58,8 @@ export interface LinkCylinderMeshBuilderProps {
     referencePosition(edgeIndex: number): Vec3 | null
     position(posA: Vec3, posB: Vec3, edgeIndex: number): void
     order(edgeIndex: number): number
-    flags(edgeIndex: number): LinkType.Flag
+    flags(edgeIndex: number): LinkType
+    radius(edgeIndex: number): number
 }
 
 /**
@@ -63,39 +67,32 @@ export interface LinkCylinderMeshBuilderProps {
  * the half closer to the first vertex, i.e. vertex a.
  */
 export async function createLinkCylinderMesh(ctx: RuntimeContext, linkBuilder: LinkCylinderMeshBuilderProps, props: LinkCylinderProps, mesh?: Mesh) {
-    const { linkCount, referencePosition, position, order, flags } = linkBuilder
+    const { linkCount, referencePosition, position, order, flags, radius } = linkBuilder
 
     if (!linkCount) return Mesh.createEmpty(mesh)
 
-    // approximate vertextCount (* 2), exact calculation would need to take
-    // multiple cylinders for bond orders and metall coordinations into account
-    const vertexCount = props.radialSegments * 2 * linkCount * 2
-    const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh)
+    const { linkScale, linkSpacing, radialSegments } = props
+
+    const vertexCountEstimate = radialSegments * 2 * linkCount * 2
+    const meshBuilder = MeshBuilder.create(vertexCountEstimate, vertexCountEstimate / 4, mesh)
 
     const va = Vec3.zero()
     const vb = Vec3.zero()
     const vShift = Vec3.zero()
-
-    const { linkScale, linkSpacing, linkRadius, radialSegments } = props
-
-    const cylinderParams = {
-        height: 1,
-        radiusTop: linkRadius,
-        radiusBottom: linkRadius,
-        radialSegments
-    }
+    const cylinderProps: CylinderProps = { radiusTop: 1, radiusBottom: 1, radialSegments }
 
     for (let edgeIndex = 0, _eI = linkCount; edgeIndex < _eI; ++edgeIndex) {
         position(va, vb, edgeIndex)
 
+        const linkRadius = radius(edgeIndex)
         const o = order(edgeIndex)
-        const f = flags(edgeIndex) as any as LinkType // TODO
+        const f = flags(edgeIndex)
         meshBuilder.setId(edgeIndex)
 
         if (LinkType.is(f, LinkType.Flag.MetallicCoordination)) {
             // show metall coordinations with dashed cylinders
-            cylinderParams.radiusTop = cylinderParams.radiusBottom = linkRadius / 3
-            meshBuilder.addFixedCountDashedCylinder(va, vb, 0.5, 7, cylinderParams)
+            cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius / 3
+            meshBuilder.addFixedCountDashedCylinder(va, vb, 0.5, 7, cylinderProps)
         } else if (o === 2 || o === 3) {
             // show bonds with order 2 or 3 using 2 or 3 parallel cylinders
             const multiRadius = linkRadius * (linkScale / (0.5 * o))
@@ -104,13 +101,13 @@ export async function createLinkCylinderMesh(ctx: RuntimeContext, linkBuilder: L
             calculateShiftDir(vShift, va, vb, referencePosition(edgeIndex))
             Vec3.setMagnitude(vShift, vShift, absOffset)
 
-            cylinderParams.radiusTop = cylinderParams.radiusBottom = multiRadius
+            cylinderProps.radiusTop = cylinderProps.radiusBottom = multiRadius
 
-            if (o === 3) meshBuilder.addCylinder(va, vb, 0.5, cylinderParams)
-            meshBuilder.addDoubleCylinder(va, vb, 0.5, vShift, cylinderParams)
+            if (o === 3) meshBuilder.addCylinder(va, vb, 0.5, cylinderProps)
+            meshBuilder.addDoubleCylinder(va, vb, 0.5, vShift, cylinderProps)
         } else {
-            cylinderParams.radiusTop = cylinderParams.radiusBottom = linkRadius
-            meshBuilder.addCylinder(va, vb, 0.5, cylinderParams)
+            cylinderProps.radiusTop = cylinderProps.radiusBottom = linkRadius
+            meshBuilder.addCylinder(va, vb, 0.5, cylinderProps)
         }
 
         if (edgeIndex % 10000 === 0 && ctx.shouldUpdate) {

+ 9 - 7
src/mol-view/stage.ts

@@ -15,6 +15,7 @@ import { BallAndStickProps } from 'mol-geo/representation/structure/representati
 import { CartoonProps } from 'mol-geo/representation/structure/representation/cartoon';
 import { DistanceRestraintProps } from 'mol-geo/representation/structure/representation/distance-restraint';
 import { BackboneProps } from 'mol-geo/representation/structure/representation/backbone';
+import { CarbohydrateProps } from 'mol-geo/representation/structure/representation/carbohydrate';
 // import { Queries as Q, StructureProperties as SP, Query, Selection } from 'mol-model/structure';
 
 const spacefillProps: Partial<SpacefillProps> = {
@@ -44,7 +45,7 @@ const distanceRestraintProps: Partial<DistanceRestraintProps> = {
 const backboneProps: Partial<BackboneProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
-    // colorTheme: { name: 'uniform', value: 0xFF0000 },
+    sizeTheme: { name: 'uniform', value: 0.3 },
     quality: 'auto',
     useFog: false,
     alpha: 0.5
@@ -53,16 +54,17 @@ const backboneProps: Partial<BackboneProps> = {
 const cartoonProps: Partial<CartoonProps> = {
     doubleSided: true,
     colorTheme: { name: 'chain-id' },
-    // colorTheme: { name: 'uniform', value: 0x2200CC },
+    sizeTheme: { name: 'uniform', value: 0.13, factor: 1 },
+    aspectRatio: 8,
     quality: 'auto',
     useFog: false
 }
 
-const carbohydrateProps: Partial<CartoonProps> = {
+const carbohydrateProps: Partial<CarbohydrateProps> = {
     doubleSided: true,
     colorTheme: { name: 'carbohydrate-symbol' },
-    // colorTheme: { name: 'uniform', value: 0x2200CC },
-    quality: 'auto',
+    sizeTheme: { name: 'uniform', value: 1, factor: 1 },
+    quality: 'highest',
     useFog: false
 }
 
@@ -86,7 +88,7 @@ export class Stage {
         // this.loadPdbid('1hrv') // viral assembly
         // this.loadPdbid('1rb8') // virus
         // this.loadPdbid('1blu') // metal coordination
-        // this.loadPdbid('3pqr') // inter unit bonds, two polymer chains, ligands, water, carbohydrates linked to protein
+        this.loadPdbid('3pqr') // inter unit bonds, two polymer chains, ligands, water, carbohydrates linked to protein
         // this.loadPdbid('4v5a') // ribosome
         // this.loadPdbid('3j3q') // ...
         // this.loadPdbid('2np2') // dna
@@ -114,7 +116,7 @@ export class Stage {
         // this.loadPdbid('4zs9') // contains raffinose
         // this.loadPdbid('2yft') // contains kestose
         // this.loadPdbid('2b5t') // contains large carbohydrate polymer
-        this.loadPdbid('1b5f') // contains carbohydrate with alternate locations
+        // this.loadPdbid('1b5f') // contains carbohydrate with alternate locations
         // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`)
         // this.loadMmcifUrl(`../../examples/1cbs_updated.cif`)
         // this.loadMmcifUrl(`../../examples/1crn.cif`)