Browse Source

wip, reorganized representation files

Alexander Rose 6 years ago
parent
commit
aa16741242

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

@@ -11,10 +11,10 @@ import { TransformListController } from '../../controller/transform/list';
 import { FileEntity } from 'mol-view/state/entity';
 import { MmcifFileToModel, ModelToStructure, StructureToBallAndStick, StructureToSpacefill, StructureToDistanceRestraint, StructureToBackbone } from 'mol-view/state/transform';
 import { StateContext } from 'mol-view/state/context';
-import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
-import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-stick';
-import { DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint';
-import { BackboneProps } from 'mol-geo/representation/structure/backbone';
+import { SpacefillProps } from 'mol-geo/representation/structure/representation/spacefill';
+import { BallAndStickProps } from 'mol-geo/representation/structure/representation/ball-and-stick';
+import { DistanceRestraintProps } from 'mol-geo/representation/structure/representation/distance-restraint';
+import { BackboneProps } from 'mol-geo/representation/structure/representation/backbone';
 
 const spacefillProps: SpacefillProps = {
     doubleSided: true,

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

@@ -10,11 +10,11 @@ import { TransformListController } from '../../controller/transform/list';
 import { UrlEntity } from 'mol-view/state/entity';
 import { ModelToStructure, StructureToBallAndStick, StructureToSpacefill, StructureToDistanceRestraint, StructureToBackbone, MmcifUrlToModel, StructureToCartoon } from 'mol-view/state/transform';
 import { StateContext } from 'mol-view/state/context';
-import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
-import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-stick';
-import { DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint';
-import { BackboneProps } from 'mol-geo/representation/structure/backbone';
-import { CartoonProps } from 'mol-geo/representation/structure/cartoon';
+import { SpacefillProps } from 'mol-geo/representation/structure/representation/spacefill';
+import { BallAndStickProps } from 'mol-geo/representation/structure/representation/ball-and-stick';
+import { DistanceRestraintProps } from 'mol-geo/representation/structure/representation/distance-restraint';
+import { BackboneProps } from 'mol-geo/representation/structure/representation/backbone';
+import { CartoonProps } from 'mol-geo/representation/structure/representation/cartoon';
 
 const spacefillProps: SpacefillProps = {
     doubleSided: true,

+ 79 - 0
src/mol-geo/representation/structure/complex-representation.ts

@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Structure } from 'mol-model/structure';
+import { Task } from 'mol-task'
+import { PickingId } from '../../util/picking';
+import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
+import { MarkerAction } from '../../util/marker-data';
+import { getQualityProps } from '../util';
+import { StructureProps, DefaultStructureProps, StructureRepresentation } from '.';
+import { ComplexVisual } from './complex-visual';
+
+export function ComplexRepresentation<P extends StructureProps>(visualCtor: () => ComplexVisual<P>): StructureRepresentation<P> {
+    let visual: ComplexVisual<P>
+
+    let _props: Required<P>
+    let _structure: Structure
+
+    function create(structure: Structure, props: P = {} as P) {
+        _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, structure))
+        _props.colorTheme!.structure = structure
+
+        return Task.create('Creating StructureRepresentation', async ctx => {
+            if (!_structure) {
+                visual = visualCtor()
+                await visual.create(ctx, structure, _props)
+            } else {
+                if (_structure.hashCode === structure.hashCode) {
+                    await update(_props)
+                } else {
+                    if (!await visual.update(ctx, _props)) {
+                        await visual.create(ctx, _structure, _props)
+                    }
+                }
+            }
+            _structure = structure
+        });
+    }
+
+    function update(props: P) {
+        return Task.create('Updating StructureRepresentation', async ctx => {
+            _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, _structure))
+            _props.colorTheme!.structure = _structure
+
+            if (!await visual.update(ctx, _props)) {
+                await visual.create(ctx, _structure, _props)
+            }
+        })
+    }
+
+    function getLoci(pickingId: PickingId) {
+        let loci: Loci = EmptyLoci
+        const _loci = visual.getLoci(pickingId)
+        if (!isEmptyLoci(_loci)) loci = _loci
+        return loci
+    }
+
+    function mark(loci: Loci, action: MarkerAction) {
+        visual.mark(loci, action)
+    }
+
+    function destroy() {
+        visual.destroy()
+    }
+
+    return {
+        get renderObjects() { return [ visual.renderObject ] },
+        get props() { return _props },
+        create,
+        update,
+        getLoci,
+        mark,
+        destroy
+    }
+}

+ 11 - 0
src/mol-geo/representation/structure/complex-visual.ts

@@ -0,0 +1,11 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Structure } from 'mol-model/structure';
+import { RepresentationProps, Visual } from '..';
+
+export interface  ComplexVisual<P extends RepresentationProps = {}> extends Visual<Structure, P> { }

+ 7 - 182
src/mol-geo/representation/structure/index.ts

@@ -5,18 +5,10 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Structure, Unit } from 'mol-model/structure';
-import { Task } from 'mol-task'
-import { RenderObject } from 'mol-gl/render-object';
-import { Representation, RepresentationProps, Visual } from '..';
+import { Structure } from 'mol-model/structure';
+import { Representation, RepresentationProps } from '..';
 import { ColorTheme, SizeTheme } from '../../theme';
-import { PickingId } from '../../util/picking';
-import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
-import { MarkerAction } from '../../util/marker-data';
-import { getQualityProps, DefaultBaseProps } from '../util';
-
-export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<Unit.SymmetryGroup, P> { }
-export interface  StructureVisual<P extends RepresentationProps = {}> extends Visual<Structure, P> { }
+import { DefaultBaseProps } from '../util';
 
 export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { }
 
@@ -27,174 +19,7 @@ export const DefaultStructureProps = {
 }
 export type StructureProps = Partial<typeof DefaultStructureProps>
 
-export function StructureRepresentation<P extends StructureProps>(visualCtor: () => StructureVisual<P>): StructureRepresentation<P> {
-    let visual: StructureVisual<P>
-
-    let _props: Required<P>
-    let _structure: Structure
-
-    function create(structure: Structure, props: P = {} as P) {
-        _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, structure))
-        _props.colorTheme!.structure = structure
-
-        return Task.create('Creating StructureRepresentation', async ctx => {
-            if (!_structure) {
-                visual = visualCtor()
-                await visual.create(ctx, structure, _props)
-            } else {
-                if (_structure.hashCode === structure.hashCode) {
-                    await update(_props)
-                } else {
-                    if (!await visual.update(ctx, _props)) {
-                        await visual.create(ctx, _structure, _props)
-                    }
-                }
-            }
-            _structure = structure
-        });
-    }
-
-    function update(props: P) {
-        return Task.create('Updating StructureRepresentation', async ctx => {
-            _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, _structure))
-            _props.colorTheme!.structure = _structure
-
-            if (!await visual.update(ctx, _props)) {
-                await visual.create(ctx, _structure, _props)
-            }
-        })
-    }
-
-    function getLoci(pickingId: PickingId) {
-        let loci: Loci = EmptyLoci
-        const _loci = visual.getLoci(pickingId)
-        if (!isEmptyLoci(_loci)) loci = _loci
-        return loci
-    }
-
-    function mark(loci: Loci, action: MarkerAction) {
-        visual.mark(loci, action)
-    }
-
-    function destroy() {
-        visual.destroy()
-    }
-
-    return {
-        get renderObjects() { return [ visual.renderObject ] },
-        get props() { return _props },
-        create,
-        update,
-        getLoci,
-        mark,
-        destroy
-    }
-}
-
-export function StructureUnitsRepresentation<P extends StructureProps>(visualCtor: () => UnitsVisual<P>): StructureRepresentation<P> {
-    let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>()
-
-    let _props: Required<P>
-    let _structure: Structure
-    let _groups: ReadonlyArray<Unit.SymmetryGroup>
-
-    function create(structure: Structure, props: P = {} as P) {
-        _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, structure))
-        _props.colorTheme!.structure = structure
-
-        return Task.create('Creating StructureRepresentation', async ctx => {
-            if (!_structure) {
-                _groups = structure.unitSymmetryGroups;
-                for (let i = 0; i < _groups.length; i++) {
-                    const group = _groups[i];
-                    const visual = visualCtor()
-                    await visual.create(ctx, group, _props)
-                    visuals.set(group.hashCode, { visual, group })
-                }
-            } else {
-                if (_structure.hashCode === structure.hashCode) {
-                    await update(_props)
-                } else {
-                    _groups = structure.unitSymmetryGroups;
-                    const newGroups: Unit.SymmetryGroup[] = []
-                    const oldUnitsVisuals = visuals
-                    visuals = new Map()
-                    for (let i = 0; i < _groups.length; i++) {
-                        const group = _groups[i];
-                        const visualGroup = oldUnitsVisuals.get(group.hashCode)
-                        if (visualGroup) {
-                            const { visual, group } = visualGroup
-                            if (!await visual.update(ctx, _props)) {
-                                await visual.create(ctx, group, _props)
-                            }
-                            oldUnitsVisuals.delete(group.hashCode)
-                        } else {
-                            newGroups.push(group)
-                            const visual = visualCtor()
-                            await visual.create(ctx, group, _props)
-                            visuals.set(group.hashCode, { visual, group })
-                        }
-                    }
-
-                    // for new groups, reuse leftover visuals
-                    const unusedVisuals: UnitsVisual<P>[] = []
-                    oldUnitsVisuals.forEach(({ visual }) => unusedVisuals.push(visual))
-                    newGroups.forEach(async group => {
-                        const visual = unusedVisuals.pop() || visualCtor()
-                        await visual.create(ctx, group, _props)
-                        visuals.set(group.hashCode, { visual, group })
-                    })
-                    unusedVisuals.forEach(visual => visual.destroy())
-                }
-            }
-            _structure = structure
-        });
-    }
-
-    function update(props: P) {
-        return Task.create('Updating StructureRepresentation', async ctx => {
-            _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, _structure))
-            _props.colorTheme!.structure = _structure
-
-            visuals.forEach(async ({ visual, group }) => {
-                if (!await visual.update(ctx, _props)) {
-                    await visual.create(ctx, group, _props)
-                }
-            })
-        })
-    }
-
-    function getLoci(pickingId: PickingId) {
-        let loci: Loci = EmptyLoci
-        visuals.forEach(({ visual }) => {
-            const _loci = visual.getLoci(pickingId)
-            if (!isEmptyLoci(_loci)) loci = _loci
-        })
-        return loci
-    }
-
-    function mark(loci: Loci, action: MarkerAction) {
-        visuals.forEach(({ visual }) => visual.mark(loci, action))
-    }
-
-    function destroy() {
-        visuals.forEach(({ visual }) => visual.destroy())
-        visuals.clear()
-    }
-
-    return {
-        get renderObjects() {
-            const renderObjects: RenderObject[] = []
-            visuals.forEach(({ visual }) => renderObjects.push(visual.renderObject))
-            return renderObjects
-        },
-        get props() {
-            return _props
-        },
-        create,
-        update,
-        getLoci,
-        mark,
-        destroy
-    }
-}
+export { ComplexRepresentation } from './complex-representation'
+export { UnitsRepresentation } from './units-representation'
+export { ComplexVisual } from './complex-visual'
+export { UnitsVisual } from './units-visual'

+ 5 - 5
src/mol-geo/representation/structure/backbone.ts → src/mol-geo/representation/structure/representation/backbone.ts

@@ -4,13 +4,13 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { StructureRepresentation, StructureUnitsRepresentation } from '.';
-import { PickingId } from '../../util/picking';
+import { StructureRepresentation, UnitsRepresentation } from '..';
+import { PickingId } from '../../../util/picking';
 import { Structure } from 'mol-model/structure';
 import { Task } from 'mol-task';
 import { Loci } from 'mol-model/loci';
-import { MarkerAction } from '../../util/marker-data';
-import { PolymerBackboneVisual, DefaultPolymerBackboneProps } from './visual/polymer-backbone-cylinder';
+import { MarkerAction } from '../../../util/marker-data';
+import { PolymerBackboneVisual, DefaultPolymerBackboneProps } from '../visual/polymer-backbone-cylinder';
 
 export const DefaultBackboneProps = {
     ...DefaultPolymerBackboneProps
@@ -18,7 +18,7 @@ export const DefaultBackboneProps = {
 export type BackboneProps = Partial<typeof DefaultBackboneProps>
 
 export function BackboneRepresentation(): StructureRepresentation<BackboneProps> {
-    const traceRepr = StructureUnitsRepresentation(PolymerBackboneVisual)
+    const traceRepr = UnitsRepresentation(PolymerBackboneVisual)
 
     return {
         get renderObjects() {

+ 10 - 10
src/mol-geo/representation/structure/ball-and-stick.ts → src/mol-geo/representation/structure/representation/ball-and-stick.ts

@@ -4,16 +4,16 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { StructureRepresentation, StructureUnitsRepresentation } from '.';
-import { ElementSphereVisual, DefaultElementSphereProps } from './visual/element-sphere';
-import { IntraUnitLinkVisual, DefaultIntraUnitLinkProps } from './visual/intra-unit-link-cylinder';
-import { PickingId } from '../../util/picking';
+import { ComplexRepresentation, StructureRepresentation, UnitsRepresentation } from '..';
+import { ElementSphereVisual, DefaultElementSphereProps } from '../visual/element-sphere';
+import { IntraUnitLinkVisual, DefaultIntraUnitLinkProps } from '../visual/intra-unit-link-cylinder';
+import { PickingId } from '../../../util/picking';
 import { Structure, Unit } from 'mol-model/structure';
 import { Task } from 'mol-task';
 import { Loci, isEmptyLoci } from 'mol-model/loci';
-import { MarkerAction } from '../../util/marker-data';
-import { SizeTheme } from '../../theme';
-import { InterUnitLinkVisual } from './visual/inter-unit-link-cylinder';
+import { MarkerAction } from '../../../util/marker-data';
+import { SizeTheme } from '../../../theme';
+import { InterUnitLinkVisual } from '../visual/inter-unit-link-cylinder';
 
 export const DefaultBallAndStickProps = {
     ...DefaultElementSphereProps,
@@ -25,9 +25,9 @@ export const DefaultBallAndStickProps = {
 export type BallAndStickProps = Partial<typeof DefaultBallAndStickProps>
 
 export function BallAndStickRepresentation(): StructureRepresentation<BallAndStickProps> {
-    const elmementRepr = StructureUnitsRepresentation(ElementSphereVisual)
-    const intraLinkRepr = StructureUnitsRepresentation(IntraUnitLinkVisual)
-    const interLinkRepr = StructureRepresentation(InterUnitLinkVisual)
+    const elmementRepr = UnitsRepresentation(ElementSphereVisual)
+    const intraLinkRepr = UnitsRepresentation(IntraUnitLinkVisual)
+    const interLinkRepr = ComplexRepresentation(InterUnitLinkVisual)
 
     return {
         get renderObjects() {

+ 7 - 7
src/mol-geo/representation/structure/carbohydrate.ts → src/mol-geo/representation/structure/representation/carbohydrate.ts

@@ -4,14 +4,14 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { StructureRepresentation } from '.';
-import { PickingId } from '../../util/picking';
+import { ComplexRepresentation, 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';
+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,
@@ -20,8 +20,8 @@ export const DefaultCartoonProps = {
 export type CarbohydrateProps = Partial<typeof DefaultCartoonProps>
 
 export function CarbohydrateRepresentation(): StructureRepresentation<CarbohydrateProps> {
-    const carbohydrateSymbolRepr = StructureRepresentation(CarbohydrateSymbolVisual)
-    const carbohydrateLinkRepr = StructureRepresentation(CarbohydrateLinkVisual)
+    const carbohydrateSymbolRepr = ComplexRepresentation(CarbohydrateSymbolVisual)
+    const carbohydrateLinkRepr = ComplexRepresentation(CarbohydrateLinkVisual)
 
     return {
         get renderObjects() {

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

@@ -4,18 +4,16 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { StructureRepresentation, StructureUnitsRepresentation } from '.';
-import { PickingId } from '../../util/picking';
+import { StructureRepresentation, UnitsRepresentation } 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 { PolymerTraceVisual, DefaultPolymerTraceProps } from './visual/polymer-trace-mesh';
-import { PolymerGapVisual, DefaultPolymerGapProps } from './visual/polymer-gap-cylinder';
-import { NucleotideBlockVisual, DefaultNucleotideBlockProps } from './visual/nucleotide-block-mesh';
-import { PolymerDirectionVisual, DefaultPolymerDirectionProps } from './visual/polymer-direction-wedge';
-import { CarbohydrateSymbolVisual } from './visual/carbohydrate-symbol-mesh';
-import { CarbohydrateLinkVisual } from './visual/carbohydrate-link-cylinder';
+import { MarkerAction } from '../../../util/marker-data';
+import { PolymerTraceVisual, DefaultPolymerTraceProps } from '../visual/polymer-trace-mesh';
+import { PolymerGapVisual, DefaultPolymerGapProps } from '../visual/polymer-gap-cylinder';
+import { NucleotideBlockVisual, DefaultNucleotideBlockProps } from '../visual/nucleotide-block-mesh';
+import { PolymerDirectionVisual, DefaultPolymerDirectionProps } from '../visual/polymer-direction-wedge';
 
 export const DefaultCartoonProps = {
     ...DefaultPolymerTraceProps,
@@ -26,24 +24,18 @@ export const DefaultCartoonProps = {
 export type CartoonProps = Partial<typeof DefaultCartoonProps>
 
 export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
-    const traceRepr = StructureUnitsRepresentation(PolymerTraceVisual)
-    const gapRepr = StructureUnitsRepresentation(PolymerGapVisual)
-    const blockRepr = StructureUnitsRepresentation(NucleotideBlockVisual)
-    const directionRepr = StructureUnitsRepresentation(PolymerDirectionVisual)
-
-    // TODO move to own repr
-    const carbohydrateSymbolRepr = StructureRepresentation(CarbohydrateSymbolVisual)
-    const carbohydrateLinkRepr = StructureRepresentation(CarbohydrateLinkVisual)
+    const traceRepr = UnitsRepresentation(PolymerTraceVisual)
+    const gapRepr = UnitsRepresentation(PolymerGapVisual)
+    const blockRepr = UnitsRepresentation(NucleotideBlockVisual)
+    const directionRepr = UnitsRepresentation(PolymerDirectionVisual)
 
     return {
         get renderObjects() {
             return [ ...traceRepr.renderObjects, ...gapRepr.renderObjects,
-                ...blockRepr.renderObjects, ...directionRepr.renderObjects,
-                ...carbohydrateSymbolRepr.renderObjects, ...carbohydrateLinkRepr.renderObjects ]
+                ...blockRepr.renderObjects, ...directionRepr.renderObjects ]
         },
         get props() {
-            return { ...traceRepr.props, ...gapRepr.props, ...blockRepr.props,
-                ...carbohydrateSymbolRepr.props, ...carbohydrateLinkRepr.props }
+            return { ...traceRepr.props, ...gapRepr.props, ...blockRepr.props }
         },
         create: (structure: Structure, props: CartoonProps = {} as CartoonProps) => {
             const p = Object.assign({}, DefaultCartoonProps, props)
@@ -52,8 +44,6 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
                 await gapRepr.create(structure, p).runInContext(ctx)
                 await blockRepr.create(structure, p).runInContext(ctx)
                 await directionRepr.create(structure, p).runInContext(ctx)
-                await carbohydrateSymbolRepr.create(structure, p).runInContext(ctx)
-                await carbohydrateLinkRepr.create(structure, p).runInContext(ctx)
             })
         },
         update: (props: CartoonProps) => {
@@ -63,8 +53,6 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
                 await gapRepr.update(p).runInContext(ctx)
                 await blockRepr.update(p).runInContext(ctx)
                 await directionRepr.update(p).runInContext(ctx)
-                await carbohydrateSymbolRepr.update(p).runInContext(ctx)
-                await carbohydrateLinkRepr.update(p).runInContext(ctx)
             })
         },
         getLoci: (pickingId: PickingId) => {
@@ -72,30 +60,22 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
             const gapLoci = gapRepr.getLoci(pickingId)
             const blockLoci = blockRepr.getLoci(pickingId)
             const directionLoci = directionRepr.getLoci(pickingId)
-            const carbohydrateSymbolLoci = carbohydrateSymbolRepr.getLoci(pickingId)
-            const carbohydrateLinkLoci = carbohydrateLinkRepr.getLoci(pickingId)
             return !isEmptyLoci(traceLoci) ? traceLoci
                 : !isEmptyLoci(gapLoci) ? gapLoci
                 : !isEmptyLoci(blockLoci) ? blockLoci
-                : !isEmptyLoci(directionLoci) ? directionLoci
-                : !isEmptyLoci(carbohydrateSymbolLoci) ? carbohydrateSymbolLoci
-                : carbohydrateLinkLoci
+                : directionLoci
         },
         mark: (loci: Loci, action: MarkerAction) => {
             traceRepr.mark(loci, action)
             gapRepr.mark(loci, action)
             blockRepr.mark(loci, action)
             directionRepr.mark(loci, action)
-            carbohydrateSymbolRepr.mark(loci, action)
-            carbohydrateLinkRepr.mark(loci, action)
         },
         destroy() {
             traceRepr.destroy()
             gapRepr.destroy()
             blockRepr.destroy()
             directionRepr.destroy()
-            carbohydrateSymbolRepr.destroy()
-            carbohydrateLinkRepr.destroy()
         }
     }
 }

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

@@ -4,14 +4,14 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { StructureRepresentation } from '.';
-import { PickingId } from '../../util/picking';
+import { ComplexRepresentation, StructureRepresentation } from '..';
+import { PickingId } from '../../../util/picking';
 import { Structure } from 'mol-model/structure';
 import { Task } from 'mol-task';
 import { Loci } from 'mol-model/loci';
-import { MarkerAction } from '../../util/marker-data';
-import { SizeTheme } from '../../theme';
-import { CrossLinkRestraintVisual, DefaultCrossLinkRestraintProps } from './visual/cross-link-restraint-cylinder';
+import { MarkerAction } from '../../../util/marker-data';
+import { SizeTheme } from '../../../theme';
+import { CrossLinkRestraintVisual, DefaultCrossLinkRestraintProps } from '../visual/cross-link-restraint-cylinder';
 
 export const DefaultDistanceRestraintProps = {
     ...DefaultCrossLinkRestraintProps,
@@ -21,7 +21,7 @@ export const DefaultDistanceRestraintProps = {
 export type DistanceRestraintProps = Partial<typeof DefaultDistanceRestraintProps>
 
 export function DistanceRestraintRepresentation(): StructureRepresentation<DistanceRestraintProps> {
-    const crossLinkRepr = StructureRepresentation(CrossLinkRestraintVisual)
+    const crossLinkRepr = ComplexRepresentation(CrossLinkRestraintVisual)
 
     return {
         get renderObjects() {

+ 3 - 3
src/mol-geo/representation/structure/spacefill.ts → src/mol-geo/representation/structure/representation/spacefill.ts

@@ -4,8 +4,8 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { StructureUnitsRepresentation } from '.';
-import { ElementSphereVisual, DefaultElementSphereProps } from './visual/element-sphere';
+import { UnitsRepresentation } from '..';
+import { ElementSphereVisual, DefaultElementSphereProps } from '../visual/element-sphere';
 
 export const DefaultSpacefillProps = {
     ...DefaultElementSphereProps,
@@ -13,5 +13,5 @@ export const DefaultSpacefillProps = {
 export type SpacefillProps = Partial<typeof DefaultSpacefillProps>
 
 export function SpacefillRepresentation() {
-    return StructureUnitsRepresentation(ElementSphereVisual)
+    return UnitsRepresentation(ElementSphereVisual)
 }

+ 129 - 0
src/mol-geo/representation/structure/units-representation.ts

@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Structure, Unit } from 'mol-model/structure';
+import { Task } from 'mol-task'
+import { RenderObject } from 'mol-gl/render-object';
+import { Representation, RepresentationProps, Visual } from '..';
+import { PickingId } from '../../util/picking';
+import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
+import { MarkerAction } from '../../util/marker-data';
+import { getQualityProps } from '../util';
+import { DefaultStructureProps, StructureProps } from '.';
+
+export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<Unit.SymmetryGroup, P> { }
+export interface  StructureVisual<P extends RepresentationProps = {}> extends Visual<Structure, P> { }
+
+export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { }
+
+export function UnitsRepresentation<P extends StructureProps>(visualCtor: () => UnitsVisual<P>): StructureRepresentation<P> {
+    let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>()
+
+    let _props: Required<P>
+    let _structure: Structure
+    let _groups: ReadonlyArray<Unit.SymmetryGroup>
+
+    function create(structure: Structure, props: P = {} as P) {
+        _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, structure))
+        _props.colorTheme!.structure = structure
+
+        return Task.create('Creating StructureRepresentation', async ctx => {
+            if (!_structure) {
+                _groups = structure.unitSymmetryGroups;
+                for (let i = 0; i < _groups.length; i++) {
+                    const group = _groups[i];
+                    const visual = visualCtor()
+                    await visual.create(ctx, group, _props)
+                    visuals.set(group.hashCode, { visual, group })
+                }
+            } else {
+                if (_structure.hashCode === structure.hashCode) {
+                    await update(_props)
+                } else {
+                    _groups = structure.unitSymmetryGroups;
+                    const newGroups: Unit.SymmetryGroup[] = []
+                    const oldUnitsVisuals = visuals
+                    visuals = new Map()
+                    for (let i = 0; i < _groups.length; i++) {
+                        const group = _groups[i];
+                        const visualGroup = oldUnitsVisuals.get(group.hashCode)
+                        if (visualGroup) {
+                            const { visual, group } = visualGroup
+                            if (!await visual.update(ctx, _props)) {
+                                await visual.create(ctx, group, _props)
+                            }
+                            oldUnitsVisuals.delete(group.hashCode)
+                        } else {
+                            newGroups.push(group)
+                            const visual = visualCtor()
+                            await visual.create(ctx, group, _props)
+                            visuals.set(group.hashCode, { visual, group })
+                        }
+                    }
+
+                    // for new groups, reuse leftover visuals
+                    const unusedVisuals: UnitsVisual<P>[] = []
+                    oldUnitsVisuals.forEach(({ visual }) => unusedVisuals.push(visual))
+                    newGroups.forEach(async group => {
+                        const visual = unusedVisuals.pop() || visualCtor()
+                        await visual.create(ctx, group, _props)
+                        visuals.set(group.hashCode, { visual, group })
+                    })
+                    unusedVisuals.forEach(visual => visual.destroy())
+                }
+            }
+            _structure = structure
+        });
+    }
+
+    function update(props: P) {
+        return Task.create('Updating StructureRepresentation', async ctx => {
+            _props = Object.assign({}, DefaultStructureProps, _props, props, getQualityProps(props, _structure))
+            _props.colorTheme!.structure = _structure
+
+            visuals.forEach(async ({ visual, group }) => {
+                if (!await visual.update(ctx, _props)) {
+                    await visual.create(ctx, group, _props)
+                }
+            })
+        })
+    }
+
+    function getLoci(pickingId: PickingId) {
+        let loci: Loci = EmptyLoci
+        visuals.forEach(({ visual }) => {
+            const _loci = visual.getLoci(pickingId)
+            if (!isEmptyLoci(_loci)) loci = _loci
+        })
+        return loci
+    }
+
+    function mark(loci: Loci, action: MarkerAction) {
+        visuals.forEach(({ visual }) => visual.mark(loci, action))
+    }
+
+    function destroy() {
+        visuals.forEach(({ visual }) => visual.destroy())
+        visuals.clear()
+    }
+
+    return {
+        get renderObjects() {
+            const renderObjects: RenderObject[] = []
+            visuals.forEach(({ visual }) => renderObjects.push(visual.renderObject))
+            return renderObjects
+        },
+        get props() {
+            return _props
+        },
+        create,
+        update,
+        getLoci,
+        mark,
+        destroy
+    }
+}

+ 11 - 0
src/mol-geo/representation/structure/units-visual.ts

@@ -0,0 +1,11 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { RepresentationProps, Visual } from '..';
+import { Unit } from 'mol-model/structure';
+
+export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<Unit.SymmetryGroup, P> { }

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

@@ -8,7 +8,7 @@ import { ValueCell } from 'mol-util/value-cell'
 
 import { MeshRenderObject } from 'mol-gl/render-object'
 import { Unit, Structure, Link, StructureElement } from 'mol-model/structure';
-import { DefaultStructureProps, StructureVisual } from '..';
+import { DefaultStructureProps, ComplexVisual } from '..';
 import { RuntimeContext } from 'mol-task'
 import { createColors, createStructureMeshRenderObject } from './util/common';
 import { Mesh } from '../../../shape/mesh';
@@ -63,7 +63,7 @@ export const DefaultCarbohydrateLinkProps = {
 }
 export type CarbohydrateLinkProps = Partial<typeof DefaultCarbohydrateLinkProps>
 
-export function CarbohydrateLinkVisual(): StructureVisual<CarbohydrateLinkProps> {
+export function CarbohydrateLinkVisual(): ComplexVisual<CarbohydrateLinkProps> {
     let renderObject: MeshRenderObject
     let currentProps: typeof DefaultCarbohydrateLinkProps
     let mesh: Mesh

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

@@ -8,7 +8,7 @@ import { ValueCell } from 'mol-util/value-cell'
 
 import { MeshRenderObject } from 'mol-gl/render-object'
 import { Unit, Structure, StructureElement } from 'mol-model/structure';
-import { DefaultStructureProps, StructureVisual } from '..';
+import { DefaultStructureProps, ComplexVisual } from '..';
 import { RuntimeContext } from 'mol-task'
 import { createColors, createStructureMeshRenderObject } from './util/common';
 import { Mesh } from '../../../shape/mesh';
@@ -126,7 +126,7 @@ export const DefaultCarbohydrateSymbolProps = {
 }
 export type CarbohydrateSymbolProps = Partial<typeof DefaultCarbohydrateSymbolProps>
 
-export function CarbohydrateSymbolVisual(): StructureVisual<CarbohydrateSymbolProps> {
+export function CarbohydrateSymbolVisual(): ComplexVisual<CarbohydrateSymbolProps> {
     let renderObject: MeshRenderObject
     let currentProps: typeof DefaultCarbohydrateSymbolProps
     let mesh: Mesh

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

@@ -8,7 +8,7 @@ import { ValueCell } from 'mol-util/value-cell'
 
 import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
 import { Link, Structure, StructureElement } from 'mol-model/structure';
-import { DefaultStructureProps, StructureVisual } from '..';
+import { DefaultStructureProps, ComplexVisual } from '..';
 import { RuntimeContext } from 'mol-task'
 import { LinkCylinderProps, DefaultLinkCylinderProps, createLinkCylinderMesh } from './util/link';
 import { MeshValues } from 'mol-gl/renderable';
@@ -56,7 +56,7 @@ export const DefaultCrossLinkRestraintProps = {
 }
 export type CrossLinkRestraintProps = Partial<typeof DefaultCrossLinkRestraintProps>
 
-export function CrossLinkRestraintVisual(): StructureVisual<CrossLinkRestraintProps> {
+export function CrossLinkRestraintVisual(): ComplexVisual<CrossLinkRestraintProps> {
     let renderObject: MeshRenderObject
     let currentProps: typeof DefaultCrossLinkRestraintProps
     let mesh: Mesh

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

@@ -8,7 +8,7 @@ import { ValueCell } from 'mol-util/value-cell'
 
 import { MeshRenderObject } from 'mol-gl/render-object'
 import { Link, Structure, StructureElement } from 'mol-model/structure';
-import { DefaultStructureProps, StructureVisual } from '..';
+import { DefaultStructureProps, ComplexVisual } from '..';
 import { RuntimeContext } from 'mol-task'
 import { LinkCylinderProps, DefaultLinkCylinderProps, createLinkCylinderMesh } from './util/link';
 import { Mesh } from '../../../shape/mesh';
@@ -51,7 +51,7 @@ export const DefaultInterUnitLinkProps = {
 }
 export type InterUnitLinkProps = Partial<typeof DefaultInterUnitLinkProps>
 
-export function InterUnitLinkVisual(): StructureVisual<InterUnitLinkProps> {
+export function InterUnitLinkVisual(): ComplexVisual<InterUnitLinkProps> {
     let renderObject: MeshRenderObject
     let currentProps: typeof DefaultInterUnitLinkProps
     let mesh: Mesh

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

@@ -9,12 +9,12 @@ import { StateContext } from './state/context';
 import { Progress } from 'mol-task';
 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 { SpacefillProps } from 'mol-geo/representation/structure/representation/spacefill';
 import { Context } from 'mol-app/context/context';
-import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-stick';
-import { CartoonProps } from 'mol-geo/representation/structure/cartoon';
-import { DistanceRestraintProps } from 'mol-geo/representation/structure/distance-restraint';
-import { BackboneProps } from 'mol-geo/representation/structure/backbone';
+import { BallAndStickProps } from 'mol-geo/representation/structure/representation/ball-and-stick';
+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 { Queries as Q, StructureProperties as SP, Query, Selection } from 'mol-model/structure';
 
 const spacefillProps: SpacefillProps = {

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

@@ -12,12 +12,12 @@ import { CifFile, CifFrame } from 'mol-io/reader/cif';
 import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
 import { Model, Structure } from 'mol-model/structure';
 import { StructureRepresentation } from 'mol-geo/representation/structure';
-import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
-import { BallAndStickProps } from 'mol-geo/representation/structure/ball-and-stick';
-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';
+import { SpacefillProps } from 'mol-geo/representation/structure/representation/spacefill';
+import { BallAndStickProps } from 'mol-geo/representation/structure/representation/ball-and-stick';
+import { DistanceRestraintProps } from 'mol-geo/representation/structure/representation/distance-restraint';
+import { CartoonProps } from 'mol-geo/representation/structure/representation/cartoon';
+import { BackboneProps } from 'mol-geo/representation/structure/representation/backbone';
+import { CarbohydrateProps } from 'mol-geo/representation/structure/representation/carbohydrate';
 
 const getNextId = idFactory(1)
 

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

@@ -10,12 +10,12 @@ import { Model, Structure, Format } from 'mol-model/structure';
 
 import { StateContext } from './context';
 import StructureSymmetry from 'mol-model/structure/structure/symmetry';
-import { SpacefillProps, SpacefillRepresentation } from 'mol-geo/representation/structure/spacefill';
-import { BallAndStickProps, BallAndStickRepresentation } from 'mol-geo/representation/structure/ball-and-stick';
-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';
+import { SpacefillProps, SpacefillRepresentation } from 'mol-geo/representation/structure/representation/spacefill';
+import { BallAndStickProps, BallAndStickRepresentation } from 'mol-geo/representation/structure/representation/ball-and-stick';
+import { DistanceRestraintRepresentation, DistanceRestraintProps } from 'mol-geo/representation/structure/representation/distance-restraint';
+import { CartoonRepresentation, CartoonProps } from 'mol-geo/representation/structure/representation/cartoon';
+import { BackboneProps, BackboneRepresentation } from 'mol-geo/representation/structure/representation/backbone';
+import { CarbohydrateProps, CarbohydrateRepresentation } from 'mol-geo/representation/structure/representation/carbohydrate';
 
 type transformer<I extends AnyEntity, O extends AnyEntity, P extends {}> = (ctx: StateContext, inputEntity: I, props?: P) => Promise<O>