From c9360b0f9b4f2945b35c9fd84cc4d3ee3f3c89d3 Mon Sep 17 00:00:00 2001 From: Alexander Rose <alex.rose@rcsb.org> Date: Fri, 10 Aug 2018 10:41:03 -0700 Subject: [PATCH] moved carbohydrate visuals to carbohydrate repr --- src/mol-app/ui/transform/ball-and-stick.tsx | 10 - src/mol-app/ui/transform/carbohydrate.tsx | 235 ++++++++++++++++++ src/mol-app/ui/transform/cartoon.tsx | 10 - .../ui/transform/distance-restraint.tsx | 10 - src/mol-app/ui/transform/spacefill.tsx | 10 - .../representation/structure/carbohydrate.ts | 62 +++++ .../representation/structure/cartoon.ts | 2 +- src/mol-view/stage.ts | 11 +- src/mol-view/state/entity.ts | 8 + src/mol-view/state/transform.ts | 90 ++++--- 10 files changed, 373 insertions(+), 75 deletions(-) create mode 100644 src/mol-app/ui/transform/carbohydrate.tsx create mode 100644 src/mol-geo/representation/structure/carbohydrate.ts diff --git a/src/mol-app/ui/transform/ball-and-stick.tsx b/src/mol-app/ui/transform/ball-and-stick.tsx index 9ccf4be39..0b62b3d2a 100644 --- a/src/mol-app/ui/transform/ball-and-stick.tsx +++ b/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 { 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 { doubleSided: boolean flipSided: boolean diff --git a/src/mol-app/ui/transform/carbohydrate.tsx b/src/mol-app/ui/transform/carbohydrate.tsx new file mode 100644 index 000000000..d5115bf81 --- /dev/null +++ b/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>; + } +} \ No newline at end of file diff --git a/src/mol-app/ui/transform/cartoon.tsx b/src/mol-app/ui/transform/cartoon.tsx index eb8f356b5..41bfcad26 100644 --- a/src/mol-app/ui/transform/cartoon.tsx +++ b/src/mol-app/ui/transform/cartoon.tsx @@ -21,16 +21,6 @@ import { Slider } from '../controls/slider'; import { VisualQuality } from 'mol-geo/representation/util'; 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 { doubleSided: boolean flipSided: boolean diff --git a/src/mol-app/ui/transform/distance-restraint.tsx b/src/mol-app/ui/transform/distance-restraint.tsx index a032eeaf2..d8453d71d 100644 --- a/src/mol-app/ui/transform/distance-restraint.tsx +++ b/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 { 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 { doubleSided: boolean flipSided: boolean diff --git a/src/mol-app/ui/transform/spacefill.tsx b/src/mol-app/ui/transform/spacefill.tsx index 42719bd63..05d2ae6aa 100644 --- a/src/mol-app/ui/transform/spacefill.tsx +++ b/src/mol-app/ui/transform/spacefill.tsx @@ -21,16 +21,6 @@ import { Slider } from '../controls/slider'; import { VisualQuality } from 'mol-geo/representation/util'; 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 { doubleSided: boolean flipSided: boolean diff --git a/src/mol-geo/representation/structure/carbohydrate.ts b/src/mol-geo/representation/structure/carbohydrate.ts new file mode 100644 index 000000000..a78c780b3 --- /dev/null +++ b/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() + } + } +} \ No newline at end of file diff --git a/src/mol-geo/representation/structure/cartoon.ts b/src/mol-geo/representation/structure/cartoon.ts index 37c540cd6..c374e27a8 100644 --- a/src/mol-geo/representation/structure/cartoon.ts +++ b/src/mol-geo/representation/structure/cartoon.ts @@ -47,7 +47,7 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> { }, create: (structure: Structure, props: CartoonProps = {} as CartoonProps) => { 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 gapRepr.create(structure, p).runInContext(ctx) await blockRepr.create(structure, p).runInContext(ctx) diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index 228cfd166..64a897cda 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -7,7 +7,7 @@ import Viewer from './viewer' import { StateContext } from './state/context'; 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 { SpacefillProps } from 'mol-geo/representation/structure/spacefill'; import { Context } from 'mol-app/context/context'; @@ -58,6 +58,14 @@ const cartoonProps: CartoonProps = { useFog: false } +const carbohydrateProps: CartoonProps = { + doubleSided: true, + colorTheme: { name: 'carbohydrate-symbol' }, + // colorTheme: { name: 'uniform', value: 0x2200CC }, + quality: 'auto', + useFog: false +} + export class Stage { viewer: Viewer ctx = new StateContext(Progress.format) @@ -139,6 +147,7 @@ export class Stage { StructureToDistanceRestraint.apply(this.ctx, structureEntity, { ...distanceRestraintProps, visible: false }) StructureToBackbone.apply(this.ctx, structureEntity, { ...backboneProps, visible: false }) StructureToCartoon.apply(this.ctx, structureEntity, { ...cartoonProps, visible: true }) + StructureToCarbohydrate.apply(this.ctx, structureEntity, { ...carbohydrateProps, visible: true }) StructureCenter.apply(this.ctx, structureEntity) 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 75643ee09..96e96d8ff 100644 --- a/src/mol-view/state/entity.ts +++ b/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 { CartoonProps } from 'mol-geo/representation/structure/cartoon'; import { BackboneProps } from 'mol-geo/representation/structure/backbone'; +import { CarbohydrateProps } from 'mol-geo/representation/structure/carbohydrate'; const getNextId = idFactory(1) @@ -151,4 +152,11 @@ export namespace CartoonEntity { export function ofRepr(ctx: StateContext, repr: StructureRepresentation<CartoonProps>): CartoonEntity { 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 ) + } } \ No newline at end of file diff --git a/src/mol-view/state/transform.ts b/src/mol-view/state/transform.ts index 23d96b188..32157001c 100644 --- a/src/mol-view/state/transform.ts +++ b/src/mol-view/state/transform.ts @@ -5,7 +5,7 @@ */ 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 { 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 { CartoonRepresentation, CartoonProps } from 'mol-geo/representation/structure/cartoon'; 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> @@ -126,27 +127,39 @@ export const StructureToDistanceRestraint: StructureToDistanceRestraint = StateT console.log('stats', ctx.viewer.stats) return DistanceRestraintEntity.ofRepr(ctx, distanceRestraintRepr) }) + export type StructureToBackbone = StateTransform<StructureEntity, BackboneEntity, BackboneProps> 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 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 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 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 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 -- GitLab