diff --git a/src/mol-math/linear-algebra/3d/mat4.ts b/src/mol-math/linear-algebra/3d/mat4.ts index 49b48f3b347c922efb621bd98546ecc66599547c..5f7a7c6249b5f51444e9a2d54dc527f686c61ccf 100644 --- a/src/mol-math/linear-algebra/3d/mat4.ts +++ b/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 */ diff --git a/src/mol-math/linear-algebra/3d/vec3.ts b/src/mol-math/linear-algebra/3d/vec3.ts index d57e24e13474ae7fdf56f8cd0f1fa5bb0e9de827..d9710fc162f18d6c7b48417aa0de0bde88afc6c9 100644 --- a/src/mol-math/linear-algebra/3d/vec3.ts +++ b/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 \ No newline at end of file diff --git a/src/mol-model-props/rcsb/representations/assembly-symmetry-axes.ts b/src/mol-model-props/rcsb/representations/assembly-symmetry-axes.ts deleted file mode 100644 index 9ee117f515d40d3de4f727d7e29c1dc6243dc87b..0000000000000000000000000000000000000000 --- a/src/mol-model-props/rcsb/representations/assembly-symmetry-axes.ts +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/src/mol-model-props/rcsb/representations/assembly-symmetry.ts b/src/mol-model-props/rcsb/representations/assembly-symmetry.ts new file mode 100644 index 0000000000000000000000000000000000000000..b2c2d994f281bc48e7d52175fdc73f0e2e427969 --- /dev/null +++ b/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>) +} \ No newline at end of file diff --git a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts index 1816260ecfb519ae9d2a433bf1e2fc23fae4ec12..3a42be2426f4cf5d938be4f73a8dde3682404fd0 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts +++ b/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; }); },