Sfoglia il codice sorgente

assembly symmetry: cage support and improved labels

Alexander Rose 5 anni fa
parent
commit
8330068434

+ 3 - 1
src/mol-math/linear-algebra/3d/mat4.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -1038,6 +1038,8 @@ namespace Mat4 {
     export const rotY90: ReadonlyMat4 = Mat4.fromRotation(Mat4(), degToRad(90), yAxis)
     /** Rotation matrix for 180deg around y-axis */
     export const rotY180: ReadonlyMat4 = Mat4.fromRotation(Mat4(), degToRad(180), yAxis)
+    /** Rotation matrix for 270deg around y-axis */
+    export const rotY270: ReadonlyMat4 = Mat4.fromRotation(Mat4(), degToRad(270), yAxis)
     /** Rotation matrix for 90deg around z-axis */
     export const rotZ90: ReadonlyMat4 = Mat4.fromRotation(Mat4(), degToRad(90), zAxis)
     /** Rotation matrix for 180deg around z-axis */

+ 7 - 1
src/mol-math/linear-algebra/3d/vec3.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -22,6 +22,8 @@ import { Quat, Mat3, EPSILON } from '../3d';
 import { spline as _spline, quadraticBezier as _quadraticBezier, clamp } from '../../interpolate'
 import { NumberArray } from '../../../mol-util/type-helpers';
 
+export { ReadonlyVec3 }
+
 interface Vec3 extends Array<number> { [d: number]: number, '@type': 'vec3', length: 3 }
 interface ReadonlyVec3 extends Array<number> { readonly [d: number]: number, '@type': 'vec3', length: 3 }
 
@@ -546,6 +548,10 @@ namespace Vec3 {
 
     export const unit: ReadonlyVec3 = Vec3.create(1, 1, 1)
     export const negUnit: ReadonlyVec3 = Vec3.create(-1, -1, -1)
+
+    export const unitX: ReadonlyVec3 = Vec3.create(1, 0, 0)
+    export const unitY: ReadonlyVec3 = Vec3.create(0, 1, 0)
+    export const unitZ: ReadonlyVec3 = Vec3.create(0, 0, 1)
 }
 
 export default Vec3

+ 0 - 146
src/mol-model-props/rcsb/representations/assembly-symmetry-axes.ts

@@ -1,146 +0,0 @@
-/**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { Structure } from '../../../mol-model/structure';
-import { AssemblySymmetryProvider, AssemblySymmetryValue, getSymmetrySelectParam } from '../assembly-symmetry';
-import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
-import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
-import { addCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
-import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
-import { RuntimeContext } from '../../../mol-task';
-import { Shape } from '../../../mol-model/shape';
-import { ColorNames } from '../../../mol-util/color/names';
-import { ShapeRepresentation } from '../../../mol-repr/shape/representation';
-import { MarkerActions } from '../../../mol-util/marker-action';
-import { Prism } from '../../../mol-geo/primitive/prism';
-import { Wedge } from '../../../mol-geo/primitive/wedge';
-import { Primitive, transformPrimitive } from '../../../mol-geo/primitive/primitive';
-import { memoize1 } from '../../../mol-util/memoize';
-import { polygon } from '../../../mol-geo/primitive/polygon';
-import { ColorMap, Color } from '../../../mol-util/color';
-import { TableLegend } from '../../../mol-util/legend';
-
-const OrderColors = ColorMap({
-    '2': ColorNames.deepskyblue,
-    '3': ColorNames.lime,
-    'N': ColorNames.red,
-})
-const OrderColorsLegend = TableLegend(Object.keys(OrderColors).map(name => {
-    return [name, (OrderColors as any)[name] as Color] as [string, Color]
-}))
-
-function axesColorHelp(value: { name: string, params: {} }) {
-    return value.name === 'byOrder'
-        ? { description: 'Color axes by their order', legend: OrderColorsLegend }
-        : {}
-}
-
-export const AssemblySymmetryAxesParams = {
-    ...Mesh.Params,
-    axesColor: PD.MappedStatic('byOrder', {
-        byOrder: PD.EmptyGroup(),
-        uniform: PD.Group({
-            colorValue: PD.Color(ColorNames.orange),
-        }, { isFlat: true })
-    }, { help: axesColorHelp }),
-    axesScale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
-    symmetryIndex: getSymmetrySelectParam(),
-}
-export type AssemblySymmetryAxesParams = typeof AssemblySymmetryAxesParams
-export type AssemblySymmetryAxesProps = PD.Values<AssemblySymmetryAxesParams>
-
-const t = Mat4.identity()
-const upY = Vec3.create(0, 1, 0)
-const upX = Vec3.create(1, 0, 0)
-const tmpV = Vec3()
-const center = Vec3()
-const scale = Vec3()
-
-const getPrimitive = memoize1((order: number): Primitive | undefined => {
-    if (order < 2) {
-        return Prism(polygon(48, false))
-    } else if (order === 2) {
-        const lens = Prism(polygon(48, false))
-        Mat4.setIdentity(t)
-        Mat4.scale(t, t, Vec3.set(scale, 1, 0.35, 1))
-        transformPrimitive(lens, t)
-        return lens
-    } else if (order === 3) {
-        return Wedge()
-    } else {
-        return Prism(polygon(order, false))
-    }
-})
-
-function getAxesMesh(data: AssemblySymmetryValue, props: PD.Values<AssemblySymmetryAxesParams>, mesh?: Mesh) {
-
-    const { symmetryIndex, axesScale } = props
-
-    const rotation_axes = data?.[symmetryIndex]?.rotation_axes
-    if (!rotation_axes) return Mesh.createEmpty(mesh)
-
-    const axis = rotation_axes[0]!
-    const start = axis.start as Vec3
-    const end = axis.end as Vec3
-    const radius = (Vec3.distance(start, end) / 500) * axesScale
-
-    const cylinderProps = { radiusTop: radius, radiusBottom: radius }
-    const builderState = MeshBuilder.createState(256, 128, mesh)
-
-    for (let i = 0, il = rotation_axes.length; i < il; ++i) {
-        const axis = rotation_axes[i]!
-        const start = axis.start as Vec3
-        const end = axis.end as Vec3
-        builderState.currentGroup = i
-        addCylinder(builderState, start, end, 1, cylinderProps)
-
-        const primitive = getPrimitive(axis.order!)
-        if (primitive) {
-            Vec3.scale(center, Vec3.add(center, start, end), 0.5)
-            if (Vec3.dot(upY, Vec3.sub(tmpV, start, center)) === 0) {
-                Mat4.targetTo(t, start, center, upY)
-            } else {
-                Mat4.targetTo(t, start, center, upX)
-            }
-            Mat4.scale(t, t, Vec3.set(scale, radius * 7, radius * 7, radius * 0.4))
-
-            Mat4.setTranslation(t, start)
-            MeshBuilder.addPrimitive(builderState, t, primitive)
-            Mat4.setTranslation(t, end)
-            MeshBuilder.addPrimitive(builderState, t, primitive)
-        }
-    }
-    return MeshBuilder.getMesh(builderState)
-}
-
-export async function getAssemblySymmetryAxesRepresentation(ctx: RuntimeContext, structure: Structure, params: AssemblySymmetryAxesProps, prev?: ShapeRepresentation<AssemblySymmetryValue, Mesh, Mesh.Params>) {
-    const repr = prev || ShapeRepresentation(getAxesShape, Mesh.Utils);
-    const data = AssemblySymmetryProvider.get(structure).value
-    await repr.createOrUpdate(params, data).runInContext(ctx);
-    repr.setState({ markerActions: MarkerActions.Highlighting })
-    return repr;
-}
-
-function getAxesShape(ctx: RuntimeContext, data: AssemblySymmetryValue, props: AssemblySymmetryAxesProps, shape?: Shape<Mesh>) {
-    const geo = getAxesMesh(data, props, shape && shape.geometry);
-    const getColor = (groupId: number) => {
-        if (props.axesColor.name === 'byOrder') {
-            const { rotation_axes } = data[props.symmetryIndex]
-            const order = rotation_axes![groupId]?.order
-            if (order === 2) return OrderColors[2]
-            else if (order === 3) return OrderColors[3]
-            else return OrderColors.N
-        } else {
-            return props.axesColor.params.colorValue
-        }
-    }
-    const getLabel = (groupId: number) => {
-        const { symbol, kind, rotation_axes } = data[props.symmetryIndex]
-        return `Axes ${groupId + 1} of ${symbol} ${kind} with Order ${rotation_axes![groupId]?.order}`
-    }
-    return Shape.create('Unitcell', data, geo, getColor, () => 1, getLabel)
-}

+ 333 - 0
src/mol-model-props/rcsb/representations/assembly-symmetry.ts

@@ -0,0 +1,333 @@
+/**
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ParamDefinition as PD } from '../../../mol-util/param-definition';
+import { AssemblySymmetryValue, getSymmetrySelectParam, AssemblySymmetryProvider } from '../assembly-symmetry';
+import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
+import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
+import { addCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
+import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
+import { RuntimeContext } from '../../../mol-task';
+import { Shape } from '../../../mol-model/shape';
+import { ColorNames } from '../../../mol-util/color/names';
+import { ShapeRepresentation } from '../../../mol-repr/shape/representation';
+import { MarkerActions } from '../../../mol-util/marker-action';
+import { Prism, PrismCage } from '../../../mol-geo/primitive/prism';
+import { Wedge, WedgeCage } from '../../../mol-geo/primitive/wedge';
+import { Primitive, transformPrimitive } from '../../../mol-geo/primitive/primitive';
+import { memoize1 } from '../../../mol-util/memoize';
+import { polygon } from '../../../mol-geo/primitive/polygon';
+import { ColorMap, Color } from '../../../mol-util/color';
+import { TableLegend } from '../../../mol-util/legend';
+import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
+import { Cage, transformCage, cloneCage } from '../../../mol-geo/primitive/cage';
+import { OctahedronCage } from '../../../mol-geo/primitive/octahedron';
+import { TetrahedronCage } from '../../../mol-geo/primitive/tetrahedron';
+import { IcosahedronCage } from '../../../mol-geo/primitive/icosahedron';
+import { degToRad, radToDeg } from '../../../mol-math/misc';
+import { Mutable } from '../../../mol-util/type-helpers';
+import { ReadonlyVec3 } from '../../../mol-math/linear-algebra/3d/vec3';
+import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
+import { Structure } from '../../../mol-model/structure';
+import { isInteger } from '../../../mol-util/number';
+
+const OrderColors = ColorMap({
+    '2': ColorNames.deepskyblue,
+    '3': ColorNames.lime,
+    'N': ColorNames.red,
+})
+const OrderColorsLegend = TableLegend(Object.keys(OrderColors).map(name => {
+    return [name, (OrderColors as any)[name] as Color] as [string, Color]
+}))
+
+function axesColorHelp(value: { name: string, params: {} }) {
+    return value.name === 'byOrder'
+        ? { description: 'Color axes by their order', legend: OrderColorsLegend }
+        : {}
+}
+
+const SharedParams = {
+    ...Mesh.Params,
+    scale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
+    symmetryIndex: getSymmetrySelectParam(),
+}
+
+const AxesParams = {
+    ...SharedParams,
+    axesColor: PD.MappedStatic('byOrder', {
+        byOrder: PD.EmptyGroup(),
+        uniform: PD.Group({
+            colorValue: PD.Color(ColorNames.orange),
+        }, { isFlat: true })
+    }, { help: axesColorHelp }),
+}
+type AxesParams = typeof AxesParams
+
+const CageParams = {
+    ...SharedParams,
+    cageColor: PD.Color(ColorNames.orange),
+}
+type CageParams = typeof CageParams
+
+const AssemblySymmetryVisuals = {
+    'axes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, AxesParams>) => ShapeRepresentation(getAxesShape, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
+    'cage': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, CageParams>) => ShapeRepresentation(getCageShape, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
+}
+
+export const AssemblySymmetryParams = {
+    ...AxesParams,
+    ...CageParams,
+    visuals: PD.MultiSelect(['axes', 'cage'], PD.objectToOptions(AssemblySymmetryVisuals)),
+}
+export type AssemblySymmetryParams = typeof AssemblySymmetryParams
+export type AssemblySymmetryProps = PD.Values<AssemblySymmetryParams>
+
+//
+
+type RotationAxes = ReadonlyArray<{ order: number, start: ReadonlyVec3, end: ReadonlyVec3 }>
+function isRotationAxes(x: AssemblySymmetryValue[0]['rotation_axes']): x is RotationAxes {
+    return !!x && x.length > 0
+}
+
+function getAssemblyName(s: Structure) {
+    const { id } = s.units[0].conformation.operator.assembly
+    return isInteger(id) ? `Assembly ${id}` : id
+}
+
+const t = Mat4.identity()
+const tmpV = Vec3()
+const tmpCenter = Vec3()
+const tmpScale = Vec3()
+
+const getOrderPrimitive = memoize1((order: number): Primitive | undefined => {
+    if (order < 2) {
+        return Prism(polygon(48, false))
+    } else if (order === 2) {
+        const lens = Prism(polygon(48, false))
+        const m = Mat4.identity()
+        Mat4.scale(m, m, Vec3.create(1, 0.35, 1))
+        transformPrimitive(lens, m)
+        return lens
+    } else if (order === 3) {
+        return Wedge()
+    } else {
+        return Prism(polygon(order, false))
+    }
+})
+
+function getAxesMesh(data: AssemblySymmetryValue, props: PD.Values<AxesParams>, mesh?: Mesh) {
+    const { symmetryIndex, scale } = props
+
+    const { rotation_axes } = data[symmetryIndex]
+    if (!isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
+
+    const { start, end } = rotation_axes[0]
+    const radius = (Vec3.distance(start, end) / 500) * scale
+
+    Vec3.set(tmpScale, radius * 7, radius * 7, radius * 0.4)
+
+    const cylinderProps = { radiusTop: radius, radiusBottom: radius }
+    const builderState = MeshBuilder.createState(256, 128, mesh)
+
+    builderState.currentGroup = 0
+    Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5)
+
+    for (let i = 0, il = rotation_axes.length; i < il; ++i) {
+        const { order, start, end } = rotation_axes[i]
+        builderState.currentGroup = i
+        addCylinder(builderState, start, end, 1, cylinderProps)
+
+        const primitive = getOrderPrimitive(order)
+        if (primitive) {
+            Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5)
+            if (Vec3.dot(Vec3.unitY, Vec3.sub(tmpV, start, tmpCenter)) === 0) {
+                Mat4.targetTo(t, start, tmpCenter, Vec3.unitY)
+            } else {
+                Mat4.targetTo(t, start, tmpCenter, Vec3.unitX)
+            }
+            Mat4.scale(t, t, tmpScale)
+
+            Mat4.setTranslation(t, start)
+            MeshBuilder.addPrimitive(builderState, t, primitive)
+            Mat4.setTranslation(t, end)
+            MeshBuilder.addPrimitive(builderState, t, primitive)
+        }
+    }
+    return MeshBuilder.getMesh(builderState)
+}
+
+function getAxesShape(ctx: RuntimeContext, data: Structure, props: AssemblySymmetryProps, shape?: Shape<Mesh>) {
+    const assemblySymmetry = AssemblySymmetryProvider.get(data).value!
+    const geo = getAxesMesh(assemblySymmetry, props, shape && shape.geometry);
+    const getColor = (groupId: number) => {
+        if (props.axesColor.name === 'byOrder') {
+            const { rotation_axes } = assemblySymmetry[props.symmetryIndex]
+            const order = rotation_axes![groupId]?.order
+            if (order === 2) return OrderColors[2]
+            else if (order === 3) return OrderColors[3]
+            else return OrderColors.N
+        } else {
+            return props.axesColor.params.colorValue
+        }
+    }
+    const getLabel = (groupId: number) => {
+        const { type, symbol, kind, rotation_axes } = assemblySymmetry[props.symmetryIndex]
+        const order = rotation_axes![groupId]?.order
+        return [
+            `<small>${data.model.entryId}</small>`,
+            `<small>${getAssemblyName(data)}</small>`,
+            `Axis ${groupId + 1} with Order ${order} of ${type} ${kind} (${symbol})`
+        ].join(' | ')
+    }
+    return Shape.create('Axes', data, geo, getColor, () => 1, getLabel)
+}
+
+//
+
+const getSymbolCage = memoize1((symbol: string): Cage | undefined => {
+    if (symbol.startsWith('D') || symbol.startsWith('C')) {
+        // z axis is prism axis, x/y axes cut through edge midpoints
+        const fold = parseInt(symbol.substr(1))
+        if (fold === 2) {
+            return PrismCage(polygon(4, false))
+        } else if (fold === 3) {
+            return WedgeCage()
+        } else if (fold > 3) {
+            return PrismCage(polygon(fold, false))
+        }
+    } else if (symbol === 'O') {
+        // x/y/z axes cut through order 4 vertices
+        return OctahedronCage()
+    } else if (symbol === 'I') {
+        // z axis cut through order 5 vertex
+        // x axis cut through edge midpoint
+        const cage = IcosahedronCage()
+        const m = Mat4.identity()
+        Mat4.rotate(m, m, degToRad(31.7), Vec3.unitX)
+        return transformCage(cloneCage(cage), m)
+    } else if (symbol === 'T') {
+        // x/y/z axes cut through edge midpoints
+        return TetrahedronCage()
+    }
+})
+
+function getSymbolScale(symbol: string) {
+    if (symbol.startsWith('D') || symbol.startsWith('C')) {
+        return 0.75
+    } else if (symbol === 'O') {
+        return 1.2
+    } else if (symbol === 'I') {
+        return 0.25
+    } else if (symbol === 'T') {
+        return 0.8
+    }
+    return 1
+}
+
+function setSymbolTransform(t: Mat4, symbol: string, axes: RotationAxes, size: number, structure: Structure) {
+    const eye = Vec3()
+    const target = Vec3()
+    const up = Vec3()
+    let pair: Mutable<RotationAxes> | undefined = undefined
+
+    if (symbol.startsWith('C')) {
+        pair = [axes[0]]
+    } else if (symbol.startsWith('D')) {
+        const fold = parseInt(symbol.substr(1))
+        if (fold === 2) {
+            pair = axes.filter(a => a.order === 2)
+        } else if (fold >= 3) {
+            const aN = axes.filter(a => a.order === fold)[0]
+            const a2 = axes.filter(a => a.order === 2)[0]
+            pair = [aN, a2]
+        }
+    } else if (symbol === 'O') {
+        pair = axes.filter(a => a.order === 4)
+    } else if (symbol === 'I') {
+        const a5 = axes.filter(a => a.order === 5)[0]
+        const a5dir = Vec3.sub(Vec3(), a5.end, a5.start)
+        pair = [a5]
+        for (const a of axes.filter(a => a.order === 3)) {
+            let d = radToDeg(Vec3.angle(Vec3.sub(up, a.end, a.start), a5dir))
+            if (equalEps(d, 100.81, 0.1)) {
+                pair[1] = a
+                break
+            }
+        }
+    } else if (symbol === 'T') {
+        pair = axes.filter(a => a.order === 2)
+    }
+
+    Mat4.setIdentity(t)
+    if (pair) {
+        const [aA, aB] = pair
+        Vec3.scale(eye, Vec3.add(eye, aA.end, aA.start), 0.5)
+        Vec3.copy(target, aA.end)
+        if (aB) {
+            Vec3.sub(up, aB.end, aB.start)
+            Mat4.targetTo(t, eye, target, up)
+            Mat4.scaleUniformly(t, t, size * getSymbolScale(symbol))
+        } else {
+            if (Vec3.dot(Vec3.unitY, Vec3.sub(tmpV, aA.end, aA.start)) === 0) {
+                Vec3.copy(up, Vec3.unitY)
+            } else {
+                Vec3.copy(up, Vec3.unitX)
+            }
+            const sizeXY = (structure.lookup3d.boundary.sphere.radius * 2) * 0.8
+            Mat4.targetTo(t, eye, target, up)
+            Mat4.scale(t, t, Vec3.create(sizeXY, sizeXY, size))
+        }
+    }
+}
+
+function getCageMesh(data: Structure, props: PD.Values<CageParams>, mesh?: Mesh) {
+    const assemblySymmetry = AssemblySymmetryProvider.get(data).value!
+    const { symmetryIndex, scale } = props
+
+    const { rotation_axes, symbol } = assemblySymmetry[symmetryIndex]
+    if (!isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
+
+    const cage = getSymbolCage(symbol)
+    if (!cage) return Mesh.createEmpty(mesh)
+
+    const { start, end } = rotation_axes[0]
+    const size = Vec3.distance(start, end)
+    const radius = (size / 500) * scale
+
+    const builderState = MeshBuilder.createState(256, 128, mesh)
+    builderState.currentGroup = 0
+    setSymbolTransform(t, symbol, rotation_axes, size, data)
+    Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5)
+    Mat4.setTranslation(t, tmpCenter)
+    MeshBuilder.addCage(builderState, t, cage, radius, 1, 8)
+
+    return MeshBuilder.getMesh(builderState)
+}
+
+function getCageShape(ctx: RuntimeContext, data: Structure, props: AssemblySymmetryProps, shape?: Shape<Mesh>) {
+    const assemblySymmetry = AssemblySymmetryProvider.get(data).value!
+    const geo = getCageMesh(data, props, shape && shape.geometry);
+    const getColor = (groupId: number) => {
+        return props.cageColor
+    }
+    const getLabel = (groupId: number) => {
+        const { type, symbol, kind } = assemblySymmetry[props.symmetryIndex]
+        data.model.entryId
+        return [
+            `<small>${data.model.entryId}</small>`,
+            `<small>${getAssemblyName(data)}</small>`,
+            `Cage of ${type} ${kind} (${symbol})`
+        ].join(' | ')
+    }
+    return Shape.create('Cage', data, geo, getColor, () => 1, getLabel)
+}
+
+//
+
+export type AssemblySymmetryRepresentation = Representation<Structure, AssemblySymmetryParams>
+export function AssemblySymmetryRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, AssemblySymmetryParams>): AssemblySymmetryRepresentation {
+    return Representation.createMulti('Symmetry', ctx, getParams, Representation.StateBuilder, AssemblySymmetryVisuals as unknown as Representation.Def<Structure, AssemblySymmetryParams>)
+}

+ 21 - 16
src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts

@@ -7,7 +7,7 @@
 import { ParamDefinition as PD } from '../../../../../mol-util/param-definition'
 import { AssemblySymmetryProvider, AssemblySymmetry, getSymmetrySelectParam } from '../../../../../mol-model-props/rcsb/assembly-symmetry';
 import { PluginBehavior } from '../../../behavior';
-import { getAssemblySymmetryAxesRepresentation, AssemblySymmetryAxesParams } from '../../../../../mol-model-props/rcsb/representations/assembly-symmetry-axes';
+import { AssemblySymmetryParams, AssemblySymmetryRepresentation } from '../../../../../mol-model-props/rcsb/representations/assembly-symmetry';
 import { AssemblySymmetryClusterColorThemeProvider } from '../../../../../mol-model-props/rcsb/themes/assembly-symmetry-cluster';
 import { PluginStateTransform, PluginStateObject } from '../../../../state/objects';
 import { Task } from '../../../../../mol-task';
@@ -22,7 +22,7 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
         private provider = AssemblySymmetryProvider
 
         register(): void {
-            this.ctx.state.dataState.actions.add(AssemblySymmetryAxes3D)
+            this.ctx.state.dataState.actions.add(AssemblySymmetry3D)
             this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach);
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add('rcsb-assembly-symmetry-cluster', AssemblySymmetryClusterColorThemeProvider)
         }
@@ -35,7 +35,7 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
         }
 
         unregister() {
-            this.ctx.state.dataState.actions.remove(AssemblySymmetryAxes3D)
+            this.ctx.state.dataState.actions.remove(AssemblySymmetry3D)
             this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove('rcsb-assembly-symmetry-cluster')
         }
@@ -46,15 +46,15 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
     })
 });
 
-type AssemblySymmetryAxes3D = typeof AssemblySymmetryAxes3D
-const AssemblySymmetryAxes3D = PluginStateTransform.BuiltIn({
-    name: 'rcsb-assembly-symmetry-axes-3d',
-    display: 'RCSB Assembly Symmetry Axes',
+type AssemblySymmetry3D = typeof AssemblySymmetry3D
+const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
+    name: 'rcsb-assembly-symmetry-3d',
+    display: 'RCSB Assembly Symmetry',
     from: PluginStateObject.Molecule.Structure,
     to: PluginStateObject.Shape.Representation3D,
     params: (a, ctx: PluginContext) => {
         return {
-            ...AssemblySymmetryAxesParams,
+            ...AssemblySymmetryParams,
             symmetryIndex: getSymmetrySelectParam(a?.data),
         }
     }
@@ -63,19 +63,24 @@ const AssemblySymmetryAxes3D = PluginStateTransform.BuiltIn({
         return true;
     },
     apply({ a, params }, plugin: PluginContext) {
-        return Task.create('RCSB Assembly Symmetry Axes', async ctx => {
+        return Task.create('RCSB Assembly Symmetry', async ctx => {
             await AssemblySymmetryProvider.attach({ runtime: ctx, fetch: plugin.fetch }, a.data)
-            const repr = await getAssemblySymmetryAxesRepresentation(ctx, a.data, params)
-            const { symbol, kind } = AssemblySymmetryProvider.get(a.data).value![params.symmetryIndex]
-            return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: `Axes`, description: `${symbol} ${kind}` });
+            const assemblySymmetry = AssemblySymmetryProvider.get(a.data).value
+            const repr = AssemblySymmetryRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.structureRepresentation.themeCtx }, () => AssemblySymmetryParams)
+            await repr.createOrUpdate(params, a.data).runInContext(ctx);
+            const { type, kind, symbol } = assemblySymmetry![params.symmetryIndex]
+            return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: kind, description: `${type} (${symbol})` });
         });
     },
     update({ a, b, newParams }, plugin: PluginContext) {
-        return Task.create('RCSB Assembly Symmetry Axes', async ctx => {
+        return Task.create('RCSB Assembly Symmetry', async ctx => {
             await AssemblySymmetryProvider.attach({ runtime: ctx, fetch: plugin.fetch }, a.data)
-            await getAssemblySymmetryAxesRepresentation(ctx, a.data, newParams, b.data.repr);
-            const { symbol, kind } = AssemblySymmetryProvider.get(a.data).value![newParams.symmetryIndex]
-            b.description = `${symbol} ${kind}`
+            const assemblySymmetry = AssemblySymmetryProvider.get(a.data).value
+            const props = { ...b.data.repr.props, ...newParams }
+            await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
+            const { type, kind, symbol } = assemblySymmetry![newParams.symmetryIndex]
+            b.label = kind
+            b.description = `${type} (${symbol})`
             return StateTransformer.UpdateResult.Updated;
         });
     },