Parcourir la source

moved carbohydrate visuals to carbohydrate repr

Alexander Rose il y a 6 ans
Parent
commit
c9360b0f9b

+ 0 - 10
src/mol-app/ui/transform/ball-and-stick.tsx

@@ -21,16 +21,6 @@ import { Slider } from '../controls/slider';
 import { VisualQuality } from 'mol-geo/representation/util';
 import { VisualQuality } from 'mol-geo/representation/util';
 import { Unit } from 'mol-model/structure';
 import { Unit } from 'mol-model/structure';
 
 
-export const ColorThemeInfo = {
-    'atom-index': {},
-    'carbohydrate-symbol': {},
-    'chain-id': {},
-    'element-symbol': {},
-    'instance-index': {},
-    'uniform': {}
-}
-export type ColorThemeInfo = keyof typeof ColorThemeInfo
-
 interface BallAndStickState {
 interface BallAndStickState {
     doubleSided: boolean
     doubleSided: boolean
     flipSided: boolean
     flipSided: boolean

+ 235 - 0
src/mol-app/ui/transform/carbohydrate.tsx

@@ -0,0 +1,235 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from LiteMol
+ * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import * as React from 'react'
+
+import { View } from '../view';
+import { Controller } from '../../controller/controller';
+import { Toggle } from '../controls/common';
+import { CarbohydrateEntity } from 'mol-view/state/entity';
+import { CarbohydrateUpdate } from 'mol-view/state/transform'
+import { StateContext } from 'mol-view/state/context';
+import { ColorTheme, SizeTheme, ColorThemeName, ColorThemeNames } from 'mol-geo/theme';
+import { Color, ColorNames } from 'mol-util/color';
+import { Slider } from '../controls/slider';
+import { VisualQuality } from 'mol-geo/representation/util';
+import { Unit } from 'mol-model/structure';
+
+interface CarbohydrateState {
+    doubleSided: boolean
+    flipSided: boolean
+    flatShaded: boolean
+    detail: number
+    colorTheme: ColorTheme
+    colorValue: Color
+    sizeTheme: SizeTheme
+    visible: boolean
+    alpha: number
+    depthMask: boolean
+    useFog: boolean
+    quality: VisualQuality
+    unitKinds: Unit.Kind[]
+    linkScale: number
+    linkSpacing: number
+    linkRadius: number
+    radialSegments: number
+}
+
+export class Carbohydrate extends View<Controller<any>, CarbohydrateState, { transform: CarbohydrateUpdate, entity: CarbohydrateEntity, ctx: StateContext }> {
+    state = {
+        doubleSided: true,
+        flipSided: false,
+        flatShaded: false,
+        detail: 2,
+        colorTheme: { name: 'element-symbol' } as ColorTheme,
+        colorValue: 0x000000,
+        sizeTheme: { name: 'uniform' } as SizeTheme,
+        visible: true,
+        alpha: 1,
+        depthMask: true,
+        useFog: true,
+        quality: 'auto' as VisualQuality,
+        unitKinds: [] as Unit.Kind[],
+        linkScale: 0.4,
+        linkSpacing: 1,
+        linkRadius: 0.25,
+        radialSegments: 16
+    }
+
+    componentWillMount() {
+        this.setState({ ...this.state, ...this.props.entity.value.props })
+    }
+
+    update(state?: Partial<CarbohydrateState>) {
+        console.log(state)
+        const { transform, entity, ctx } = this.props
+        const newState = { ...this.state, ...state }
+        this.setState(newState)
+        transform.apply(ctx, entity, newState)
+    }
+
+    render() {
+        const { transform } = this.props
+
+        const qualityOptions = ['auto', 'custom', 'highest', 'high', 'medium', 'low', 'lowest'].map((name, idx) => {
+            return <option key={name} value={name}>{name}</option>
+        })
+
+        const sphereDetailOptions = [0, 1, 2, 3].map((value, idx) => {
+            return <option key={value} value={value}>{value.toString()}</option>
+        })
+
+        const colorThemeOptions = ColorThemeNames.map((name, idx) => {
+            return <option key={name} value={name}>{name}</option>
+        })
+
+        const colorValueOptions = Object.keys(ColorNames).map((name, idx) => {
+            return <option key={name} value={(ColorNames as any)[name]}>{name}</option>
+        })
+
+        return <div className='molstar-transformer-wrapper'>
+            <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'>
+                <div className='molstar-panel-header'>
+                    <button
+                        className='molstar-btn molstar-btn-link molstar-panel-expander'
+                        onClick={() => this.update()}
+                    >
+                        <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span>
+                    </button>
+                </div>
+                <div className='molstar-panel-body'>
+                    <div>
+                    <div className='molstar-control-row molstar-options-group'>
+                        <span>Quality</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.quality}
+                                    onChange={(e) => this.update({ quality: e.target.value as VisualQuality })}
+                                >
+                                    {qualityOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Sphere detail</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.detail}
+                                    onChange={(e) => this.update({ detail: parseInt(e.target.value) })}
+                                >
+                                    {sphereDetailOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Color theme</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.colorTheme.name}
+                                    onChange={(e) => {
+                                        this.update({
+                                            colorTheme: {
+                                                name: e.target.value as ColorThemeName,
+                                                value: this.state.colorValue
+                                            }
+                                        })
+                                    }}
+                                >
+                                    {colorThemeOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <span>Color value</span>
+                            <div>
+                                <select
+                                    className='molstar-form-control'
+                                    value={this.state.colorValue}
+                                    onChange={(e) => {
+                                        const colorValue = parseInt(e.target.value)
+                                        this.update({
+                                            colorTheme: {
+                                                name: 'uniform',
+                                                value: colorValue
+                                            },
+                                            colorValue
+                                        })
+                                    }}
+                                >
+                                    {colorValueOptions}
+                                </select>
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.visible}
+                                    label='Visibility'
+                                    onChange={value => this.update({ visible: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.depthMask}
+                                    label='Depth write'
+                                    onChange={value => this.update({ depthMask: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.doubleSided}
+                                    label='Double sided'
+                                    onChange={value => this.update({ doubleSided: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.flipSided}
+                                    label='Flip sided'
+                                    onChange={value => this.update({ flipSided: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Toggle
+                                    value={this.state.flatShaded}
+                                    label='Flat shaded'
+                                    onChange={value => this.update({ flatShaded: value })}
+                                />
+                            </div>
+                        </div>
+                        <div className='molstar-control-row molstar-options-group'>
+                            <div>
+                                <Slider
+                                    value={this.state.alpha}
+                                    label='Opacity'
+                                    min={0}
+                                    max={1}
+                                    step={0.01}
+                                    callOnChangeWhileSliding={true}
+                                    onChange={value => this.update({ alpha: value })}
+                                />
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>;
+    }
+}

+ 0 - 10
src/mol-app/ui/transform/cartoon.tsx

@@ -21,16 +21,6 @@ import { Slider } from '../controls/slider';
 import { VisualQuality } from 'mol-geo/representation/util';
 import { VisualQuality } from 'mol-geo/representation/util';
 import { Unit } from 'mol-model/structure';
 import { Unit } from 'mol-model/structure';
 
 
-export const ColorThemeInfo = {
-    'atom-index': {},
-    'carbohydrate-symbol': {},
-    'chain-id': {},
-    'element-symbol': {},
-    'instance-index': {},
-    'uniform': {}
-}
-export type ColorThemeInfo = keyof typeof ColorThemeInfo
-
 interface CartoonState {
 interface CartoonState {
     doubleSided: boolean
     doubleSided: boolean
     flipSided: boolean
     flipSided: boolean

+ 0 - 10
src/mol-app/ui/transform/distance-restraint.tsx

@@ -21,16 +21,6 @@ import { Slider } from '../controls/slider';
 import { VisualQuality } from 'mol-geo/representation/util';
 import { VisualQuality } from 'mol-geo/representation/util';
 import { Unit } from 'mol-model/structure';
 import { Unit } from 'mol-model/structure';
 
 
-export const ColorThemeInfo = {
-    'atom-index': {},
-    'carbohydrate-symbol': {},
-    'chain-id': {},
-    'element-symbol': {},
-    'instance-index': {},
-    'uniform': {}
-}
-export type ColorThemeInfo = keyof typeof ColorThemeInfo
-
 interface DistanceRestraintState {
 interface DistanceRestraintState {
     doubleSided: boolean
     doubleSided: boolean
     flipSided: boolean
     flipSided: boolean

+ 0 - 10
src/mol-app/ui/transform/spacefill.tsx

@@ -21,16 +21,6 @@ import { Slider } from '../controls/slider';
 import { VisualQuality } from 'mol-geo/representation/util';
 import { VisualQuality } from 'mol-geo/representation/util';
 import { Unit } from 'mol-model/structure';
 import { Unit } from 'mol-model/structure';
 
 
-export const ColorThemeInfo = {
-    'atom-index': {},
-    'carbohydrate-symbol': {},
-    'chain-id': {},
-    'element-symbol': {},
-    'instance-index': {},
-    'uniform': {}
-}
-export type ColorThemeInfo = keyof typeof ColorThemeInfo
-
 interface SpacefillState {
 interface SpacefillState {
     doubleSided: boolean
     doubleSided: boolean
     flipSided: boolean
     flipSided: boolean

+ 62 - 0
src/mol-geo/representation/structure/carbohydrate.ts

@@ -0,0 +1,62 @@
+/**
+ * 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 { 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 { CarbohydrateSymbolVisual, DefaultCarbohydrateSymbolProps } from './visual/carbohydrate-symbol-mesh';
+import { CarbohydrateLinkVisual, DefaultCarbohydrateLinkProps } from './visual/carbohydrate-link-cylinder';
+
+export const DefaultCartoonProps = {
+    ...DefaultCarbohydrateSymbolProps,
+    ...DefaultCarbohydrateLinkProps
+}
+export type CarbohydrateProps = Partial<typeof DefaultCartoonProps>
+
+export function CarbohydrateRepresentation(): StructureRepresentation<CarbohydrateProps> {
+    const carbohydrateSymbolRepr = StructureRepresentation(CarbohydrateSymbolVisual)
+    const carbohydrateLinkRepr = StructureRepresentation(CarbohydrateLinkVisual)
+
+    return {
+        get renderObjects() {
+            return [ ...carbohydrateSymbolRepr.renderObjects, ...carbohydrateLinkRepr.renderObjects ]
+        },
+        get props() {
+            return { ...carbohydrateSymbolRepr.props, ...carbohydrateLinkRepr.props }
+        },
+        create: (structure: Structure, props: CarbohydrateProps = {} as CarbohydrateProps) => {
+            const p = Object.assign({}, DefaultCartoonProps, props)
+            return Task.create('Creating CarbohydrateRepresentation', async ctx => {
+                await carbohydrateSymbolRepr.create(structure, p).runInContext(ctx)
+                await carbohydrateLinkRepr.create(structure, p).runInContext(ctx)
+            })
+        },
+        update: (props: CarbohydrateProps) => {
+            const p = Object.assign({}, props)
+            return Task.create('Updating CarbohydrateRepresentation', async ctx => {
+                await carbohydrateSymbolRepr.update(p).runInContext(ctx)
+                await carbohydrateLinkRepr.update(p).runInContext(ctx)
+            })
+        },
+        getLoci: (pickingId: PickingId) => {
+            const carbohydrateSymbolLoci = carbohydrateSymbolRepr.getLoci(pickingId)
+            const carbohydrateLinkLoci = carbohydrateLinkRepr.getLoci(pickingId)
+            return !isEmptyLoci(carbohydrateSymbolLoci) ? carbohydrateSymbolLoci
+                : carbohydrateLinkLoci
+        },
+        mark: (loci: Loci, action: MarkerAction) => {
+            carbohydrateSymbolRepr.mark(loci, action)
+            carbohydrateLinkRepr.mark(loci, action)
+        },
+        destroy() {
+            carbohydrateSymbolRepr.destroy()
+            carbohydrateLinkRepr.destroy()
+        }
+    }
+}

+ 1 - 1
src/mol-geo/representation/structure/cartoon.ts

@@ -47,7 +47,7 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
         },
         },
         create: (structure: Structure, props: CartoonProps = {} as CartoonProps) => {
         create: (structure: Structure, props: CartoonProps = {} as CartoonProps) => {
             const p = Object.assign({}, DefaultCartoonProps, props)
             const p = Object.assign({}, DefaultCartoonProps, props)
-            return Task.create('CartoonRepresentation', async ctx => {
+            return Task.create('Creating CartoonRepresentation', async ctx => {
                 await traceRepr.create(structure, p).runInContext(ctx)
                 await traceRepr.create(structure, p).runInContext(ctx)
                 await gapRepr.create(structure, p).runInContext(ctx)
                 await gapRepr.create(structure, p).runInContext(ctx)
                 await blockRepr.create(structure, p).runInContext(ctx)
                 await blockRepr.create(structure, p).runInContext(ctx)

+ 10 - 1
src/mol-view/stage.ts

@@ -7,7 +7,7 @@
 import Viewer from './viewer'
 import Viewer from './viewer'
 import { StateContext } from './state/context';
 import { StateContext } from './state/context';
 import { Progress } from 'mol-task';
 import { Progress } from 'mol-task';
-import { MmcifUrlToModel, ModelToStructure, StructureToSpacefill, StructureToBallAndStick, StructureToDistanceRestraint, StructureToCartoon, StructureToBackbone, StructureCenter } from './state/transform';
+import { MmcifUrlToModel, ModelToStructure, StructureToSpacefill, StructureToBallAndStick, StructureToDistanceRestraint, StructureToCartoon, StructureToBackbone, StructureCenter, StructureToCarbohydrate } from './state/transform';
 import { UrlEntity } from './state/entity';
 import { UrlEntity } from './state/entity';
 import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
 import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
 import { Context } from 'mol-app/context/context';
 import { Context } from 'mol-app/context/context';
@@ -58,6 +58,14 @@ const cartoonProps: CartoonProps = {
     useFog: false
     useFog: false
 }
 }
 
 
+const carbohydrateProps: CartoonProps = {
+    doubleSided: true,
+    colorTheme: { name: 'carbohydrate-symbol' },
+    // colorTheme: { name: 'uniform', value: 0x2200CC },
+    quality: 'auto',
+    useFog: false
+}
+
 export class Stage {
 export class Stage {
     viewer: Viewer
     viewer: Viewer
     ctx = new StateContext(Progress.format)
     ctx = new StateContext(Progress.format)
@@ -139,6 +147,7 @@ export class Stage {
         StructureToDistanceRestraint.apply(this.ctx, structureEntity, { ...distanceRestraintProps, visible: false })
         StructureToDistanceRestraint.apply(this.ctx, structureEntity, { ...distanceRestraintProps, visible: false })
         StructureToBackbone.apply(this.ctx, structureEntity, { ...backboneProps, visible: false })
         StructureToBackbone.apply(this.ctx, structureEntity, { ...backboneProps, visible: false })
         StructureToCartoon.apply(this.ctx, structureEntity, { ...cartoonProps, visible: true })
         StructureToCartoon.apply(this.ctx, structureEntity, { ...cartoonProps, visible: true })
+        StructureToCarbohydrate.apply(this.ctx, structureEntity, { ...carbohydrateProps, visible: true })
         StructureCenter.apply(this.ctx, structureEntity)
         StructureCenter.apply(this.ctx, structureEntity)
 
 
         this.globalContext.components.sequenceView.setState({ structure: structureEntity.value });
         this.globalContext.components.sequenceView.setState({ structure: structureEntity.value });

+ 8 - 0
src/mol-view/state/entity.ts

@@ -17,6 +17,7 @@ import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-sti
 import { DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint';
 import { DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint';
 import { CartoonProps } from 'mol-geo/representation/structure/cartoon';
 import { CartoonProps } from 'mol-geo/representation/structure/cartoon';
 import { BackboneProps } from 'mol-geo/representation/structure/backbone';
 import { BackboneProps } from 'mol-geo/representation/structure/backbone';
+import { CarbohydrateProps } from 'mol-geo/representation/structure/carbohydrate';
 
 
 const getNextId = idFactory(1)
 const getNextId = idFactory(1)
 
 
@@ -151,4 +152,11 @@ export namespace CartoonEntity {
     export function ofRepr(ctx: StateContext, repr: StructureRepresentation<CartoonProps>): CartoonEntity {
     export function ofRepr(ctx: StateContext, repr: StructureRepresentation<CartoonProps>): CartoonEntity {
         return StateEntity.create(ctx, 'cartoon', repr )
         return StateEntity.create(ctx, 'cartoon', repr )
     }
     }
+}
+
+export type CarbohydrateEntity = StateEntity<StructureRepresentation<CarbohydrateProps>, 'carbohydrate'>
+export namespace CarbohydrateEntity {
+    export function ofRepr(ctx: StateContext, repr: StructureRepresentation<CarbohydrateProps>): CarbohydrateEntity {
+        return StateEntity.create(ctx, 'carbohydrate', repr )
+    }
 }
 }

+ 57 - 33
src/mol-view/state/transform.ts

@@ -5,7 +5,7 @@
  */
  */
 
 
 import CIF from 'mol-io/reader/cif'
 import CIF from 'mol-io/reader/cif'
-import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity, NullEntity, BallAndStickEntity, DistanceRestraintEntity, CartoonEntity, BackboneEntity } from './entity';
+import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity, NullEntity, BallAndStickEntity, DistanceRestraintEntity, CartoonEntity, BackboneEntity, CarbohydrateEntity } from './entity';
 import { Model, Structure, Format } from 'mol-model/structure';
 import { Model, Structure, Format } from 'mol-model/structure';
 
 
 import { StateContext } from './context';
 import { StateContext } from './context';
@@ -15,6 +15,7 @@ import { BallAndStickProps, BallAndStickRepresentation } from 'mol-geo/represent
 import { DistanceRestraintRepresentation, DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint';
 import { DistanceRestraintRepresentation, DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint';
 import { CartoonRepresentation, CartoonProps } from 'mol-geo/representation/structure/cartoon';
 import { CartoonRepresentation, CartoonProps } from 'mol-geo/representation/structure/cartoon';
 import { BackboneProps, BackboneRepresentation } from 'mol-geo/representation/structure/backbone';
 import { BackboneProps, BackboneRepresentation } from 'mol-geo/representation/structure/backbone';
+import { CarbohydrateProps, CarbohydrateRepresentation } from 'mol-geo/representation/structure/carbohydrate';
 
 
 type transformer<I extends AnyEntity, O extends AnyEntity, P extends {}> = (ctx: StateContext, inputEntity: I, props?: P) => Promise<O>
 type transformer<I extends AnyEntity, O extends AnyEntity, P extends {}> = (ctx: StateContext, inputEntity: I, props?: P) => Promise<O>
 
 
@@ -126,27 +127,39 @@ export const StructureToDistanceRestraint: StructureToDistanceRestraint = StateT
         console.log('stats', ctx.viewer.stats)
         console.log('stats', ctx.viewer.stats)
         return DistanceRestraintEntity.ofRepr(ctx, distanceRestraintRepr)
         return DistanceRestraintEntity.ofRepr(ctx, distanceRestraintRepr)
     })
     })
+
 export type StructureToBackbone = StateTransform<StructureEntity, BackboneEntity, BackboneProps>
 export type StructureToBackbone = StateTransform<StructureEntity, BackboneEntity, BackboneProps>
 export const StructureToBackbone: StructureToBackbone = StateTransform.create('structure', 'backbone', 'structure-to-backbone',
 export const StructureToBackbone: StructureToBackbone = StateTransform.create('structure', 'backbone', 'structure-to-backbone',
-            async function (ctx: StateContext, structureEntity: StructureEntity, props: BackboneProps = {}) {
-                const backboneRepr = BackboneRepresentation()
-                await backboneRepr.create(structureEntity.value, props).run(ctx.log)
-                ctx.viewer.add(backboneRepr)
-                ctx.viewer.requestDraw()
-                console.log('stats', ctx.viewer.stats)
-                return BackboneEntity.ofRepr(ctx, backboneRepr)
-            })
+    async function (ctx: StateContext, structureEntity: StructureEntity, props: BackboneProps = {}) {
+        const backboneRepr = BackboneRepresentation()
+        await backboneRepr.create(structureEntity.value, props).run(ctx.log)
+        ctx.viewer.add(backboneRepr)
+        ctx.viewer.requestDraw()
+        console.log('stats', ctx.viewer.stats)
+        return BackboneEntity.ofRepr(ctx, backboneRepr)
+    })
 
 
 export type StructureToCartoon = StateTransform<StructureEntity, CartoonEntity, CartoonProps>
 export type StructureToCartoon = StateTransform<StructureEntity, CartoonEntity, CartoonProps>
 export const StructureToCartoon: StructureToCartoon = StateTransform.create('structure', 'cartoon', 'structure-to-cartoon',
 export const StructureToCartoon: StructureToCartoon = StateTransform.create('structure', 'cartoon', 'structure-to-cartoon',
-        async function (ctx: StateContext, structureEntity: StructureEntity, props: CartoonProps = {}) {
-            const cartoonRepr = CartoonRepresentation()
-            await cartoonRepr.create(structureEntity.value, props).run(ctx.log)
-            ctx.viewer.add(cartoonRepr)
-            ctx.viewer.requestDraw()
-            console.log('stats', ctx.viewer.stats)
-            return CartoonEntity.ofRepr(ctx, cartoonRepr)
-        })
+    async function (ctx: StateContext, structureEntity: StructureEntity, props: CartoonProps = {}) {
+        const cartoonRepr = CartoonRepresentation()
+        await cartoonRepr.create(structureEntity.value, props).run(ctx.log)
+        ctx.viewer.add(cartoonRepr)
+        ctx.viewer.requestDraw()
+        console.log('stats', ctx.viewer.stats)
+        return CartoonEntity.ofRepr(ctx, cartoonRepr)
+    })
+
+export type StructureToCarbohydrate = StateTransform<StructureEntity, CarbohydrateEntity, CarbohydrateProps>
+export const StructureToCarbohydrate: StructureToCarbohydrate = StateTransform.create('structure', 'carbohydrate', 'structure-to-cartoon',
+    async function (ctx: StateContext, structureEntity: StructureEntity, props: CarbohydrateProps = {}) {
+        const carbohydrateRepr = CarbohydrateRepresentation()
+        await carbohydrateRepr.create(structureEntity.value, props).run(ctx.log)
+        ctx.viewer.add(carbohydrateRepr)
+        ctx.viewer.requestDraw()
+        console.log('stats', ctx.viewer.stats)
+        return CarbohydrateEntity.ofRepr(ctx, carbohydrateRepr)
+    })
 
 
 export type SpacefillUpdate = StateTransform<SpacefillEntity, NullEntity, SpacefillProps>
 export type SpacefillUpdate = StateTransform<SpacefillEntity, NullEntity, SpacefillProps>
 export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill', 'null', 'spacefill-update',
 export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill', 'null', 'spacefill-update',
@@ -183,25 +196,36 @@ export const DistanceRestraintUpdate: DistanceRestraintUpdate = StateTransform.c
 
 
 export type BackboneUpdate = StateTransform<BackboneEntity, NullEntity, BackboneProps>
 export type BackboneUpdate = StateTransform<BackboneEntity, NullEntity, BackboneProps>
 export const BackboneUpdate: BackboneUpdate = StateTransform.create('backbone', 'null', 'backbone-update',
 export const BackboneUpdate: BackboneUpdate = StateTransform.create('backbone', 'null', 'backbone-update',
-            async function (ctx: StateContext, backboneEntity: BackboneEntity, props: BackboneProps = {}) {
-                const backboneRepr = backboneEntity.value
-                await backboneRepr.update(props).run(ctx.log)
-                ctx.viewer.add(backboneRepr)
-                ctx.viewer.requestDraw()
-                console.log('stats', ctx.viewer.stats)
-                return NullEntity
-            })
+    async function (ctx: StateContext, backboneEntity: BackboneEntity, props: BackboneProps = {}) {
+        const backboneRepr = backboneEntity.value
+        await backboneRepr.update(props).run(ctx.log)
+        ctx.viewer.add(backboneRepr)
+        ctx.viewer.requestDraw()
+        console.log('stats', ctx.viewer.stats)
+        return NullEntity
+    })
 
 
 export type CartoonUpdate = StateTransform<CartoonEntity, NullEntity, CartoonProps>
 export type CartoonUpdate = StateTransform<CartoonEntity, NullEntity, CartoonProps>
 export const CartoonUpdate: CartoonUpdate = StateTransform.create('cartoon', 'null', 'cartoon-update',
 export const CartoonUpdate: CartoonUpdate = StateTransform.create('cartoon', 'null', 'cartoon-update',
-        async function (ctx: StateContext, cartoonEntity: CartoonEntity, props: CartoonProps = {}) {
-            const cartoonRepr = cartoonEntity.value
-            await cartoonRepr.update(props).run(ctx.log)
-            ctx.viewer.add(cartoonRepr)
-            ctx.viewer.requestDraw()
-            console.log('stats', ctx.viewer.stats)
-            return NullEntity
-        })
+    async function (ctx: StateContext, cartoonEntity: CartoonEntity, props: CartoonProps = {}) {
+        const cartoonRepr = cartoonEntity.value
+        await cartoonRepr.update(props).run(ctx.log)
+        ctx.viewer.add(cartoonRepr)
+        ctx.viewer.requestDraw()
+        console.log('stats', ctx.viewer.stats)
+        return NullEntity
+    })
+
+export type CarbohydrateUpdate = StateTransform<CarbohydrateEntity, NullEntity, CarbohydrateProps>
+export const CarbohydrateUpdate: CarbohydrateUpdate = StateTransform.create('carbohydrate', 'null', 'carbohydrate-update',
+    async function (ctx: StateContext, carbohydrateEntity: CarbohydrateEntity, props: CarbohydrateProps = {}) {
+        const carbohydrateRepr = carbohydrateEntity.value
+        await carbohydrateRepr.update(props).run(ctx.log)
+        ctx.viewer.add(carbohydrateRepr)
+        ctx.viewer.requestDraw()
+        console.log('stats', ctx.viewer.stats)
+        return NullEntity
+    })
 
 
 // composed
 // composed