diff --git a/src/mol-model-props/rcsb/assembly-symmetry.ts b/src/mol-model-props/rcsb/assembly-symmetry.ts index 29209bd0ace506d634e07250398b2be6c74e2215..47d2849d74b1ec7cd9482a26785c794d63956573 100644 --- a/src/mol-model-props/rcsb/assembly-symmetry.ts +++ b/src/mol-model-props/rcsb/assembly-symmetry.ts @@ -154,6 +154,7 @@ const _Descriptor: ModelPropertyDescriptor = { } export interface AssemblySymmetry { + '@type': 'rcsb_assembly_symmetry', db: AssemblySymmetry.Database getSymmetries(assemblyId: string): Table<AssemblySymmetry.Schema['rcsb_assembly_symmetry']> getClusters(symmetryId: number): Table<AssemblySymmetry.Schema['rcsb_assembly_symmetry_cluster']> @@ -168,6 +169,7 @@ export function AssemblySymmetry(db: AssemblySymmetry.Database): AssemblySymmetr const a = db.rcsb_assembly_symmetry_axis return { + '@type': 'rcsb_assembly_symmetry', db, getSymmetries: (assemblyId: string) => Table.pick(f, f._schema, i => f.assembly_id.value(i) === assemblyId), getClusters: (symmetryId: number) => Table.pick(c, c._schema, i => c.symmetry_id.value(i) === symmetryId), @@ -179,6 +181,9 @@ export function AssemblySymmetry(db: AssemblySymmetry.Database): AssemblySymmetr const Client = new GraphQLClient(AssemblySymmetry.GraphQLEndpointURL, (url: string, type: 'string' | 'binary', body?: string) => ajaxGet({ url, type, body }) ) export namespace AssemblySymmetry { + export function is(x: any): x is AssemblySymmetry { + return x['@type'] === 'rcsb_assembly_symmetry' + } export const GraphQLEndpointURL = 'http://rest-experimental.rcsb.org/graphql' export const Schema = { rcsb_assembly_symmetry_info: { diff --git a/src/mol-model-props/rcsb/representations/assembly-symmetry-axes.ts b/src/mol-model-props/rcsb/representations/assembly-symmetry-axes.ts index e6f46fa74dcdf464380791f3f80b5bc0b4121e0f..dce096fb50cc0df3c91952baefd27dd6336844df 100644 --- a/src/mol-model-props/rcsb/representations/assembly-symmetry-axes.ts +++ b/src/mol-model-props/rcsb/representations/assembly-symmetry-axes.ts @@ -18,9 +18,11 @@ import { addCylinder } from 'mol-geo/geometry/mesh/builder/cylinder'; import { VisualUpdateState } from 'mol-repr/util'; import { ComplexMeshVisual, ComplexMeshParams } from 'mol-repr/structure/complex-visual'; import { Mesh } from 'mol-geo/geometry/mesh/mesh'; -import { EmptyLoci } from 'mol-model/loci'; +import { EmptyLoci, createDataLoci, Loci, isDataLoci } from 'mol-model/loci'; import { LocationIterator } from 'mol-geo/util/location-iterator'; import { NullLocation } from 'mol-model/location'; +import { PickingId } from 'mol-geo/geometry/picking'; +import { OrderedSet, Interval } from 'mol-data/int'; export const AssemblySymmetryAxesParams = { ...ComplexMeshParams, @@ -75,8 +77,8 @@ export function AssemblySymmetryAxesVisual(): ComplexVisual<AssemblySymmetryAxes defaultProps: PD.getDefaultValues(AssemblySymmetryAxesParams), createGeometry: createAssemblySymmetryAxesMesh, createLocationIterator, - getLoci: () => EmptyLoci, - mark: () => false, + getLoci, + mark, setUpdateState: (state: VisualUpdateState, newProps: PD.Values<AssemblySymmetryAxesParams>, currentProps: PD.Values<AssemblySymmetryAxesParams>) => { state.createGeometry = ( newProps.sizeFactor !== currentProps.sizeFactor || @@ -88,15 +90,29 @@ export function AssemblySymmetryAxesVisual(): ComplexVisual<AssemblySymmetryAxes } function createLocationIterator(structure: Structure) { - let groupCount = 0 - const assemblySymmetry = AssemblySymmetry.get(structure.models[0]) - if (assemblySymmetry) { - const axis = assemblySymmetry.db.rcsb_assembly_symmetry_axis - groupCount = axis._rowCount + const groupCount = assemblySymmetry ? assemblySymmetry.db.rcsb_assembly_symmetry_axis._rowCount : 0 + return LocationIterator(groupCount, 1, () => NullLocation) +} + +function getLoci(pickingId: PickingId, structure: Structure, id: number) { + const { objectId, groupId } = pickingId + if (id === objectId) { + const assemblySymmetry = AssemblySymmetry.get(structure.models[0]) + if (assemblySymmetry) { + return createDataLoci(assemblySymmetry, 'axes', OrderedSet.ofSingleton(groupId)) + } } + return EmptyLoci +} - return LocationIterator(groupCount, 1, () => NullLocation) +function mark(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) { + let changed = false + if (!isDataLoci(loci) || loci.tag !== 'axes') return false + const assemblySymmetry = AssemblySymmetry.get(structure.models[0]) + if (!assemblySymmetry || loci.data !== assemblySymmetry) return false + OrderedSet.forEach(loci.indices, v => { if (apply(Interval.ofSingleton(v))) changed = true }) + return changed } export function createAssemblySymmetryAxesMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<AssemblySymmetryAxesParams>, mesh?: Mesh) { @@ -113,16 +129,18 @@ export function createAssemblySymmetryAxesMesh(ctx: VisualContext, structure: St // symmetry.assembly_id not available for structure.assemblyName if (symmetry.assembly_id !== structure.assemblyName) return Mesh.createEmpty(mesh) - const axes = assemblySymmetry.getAxes(symmetryId) - if (!axes._rowCount) return Mesh.createEmpty(mesh) - + const axes = assemblySymmetry.db.rcsb_assembly_symmetry_axis const vectorSpace = AssemblySymmetry.Schema.rcsb_assembly_symmetry_axis.start.space; // const colors: Color[] = [] // const labels: string[] = [] + const radius = 1 * sizeFactor const cylinderProps = { radiusTop: radius, radiusBottom: radius } const builderState = MeshBuilder.createState(256, 128, mesh) + for (let i = 0, il = axes._rowCount; i < il; ++i) { + if (axes.symmetry_id.value(i) !== symmetryId) continue + const start = Tensor.toVec3(vectorSpace, axes.start.value(i)) const end = Tensor.toVec3(vectorSpace, axes.end.value(i)) builderState.currentGroup = i diff --git a/src/mol-model/loci.ts b/src/mol-model/loci.ts index abdd4824ddfb8b200375bd82d2bb762da8bf5955..a27e2d98f79d8fc53abc99dacfdf0be92e09f98f 100644 --- a/src/mol-model/loci.ts +++ b/src/mol-model/loci.ts @@ -27,9 +27,28 @@ export function isEmptyLoci(x: any): x is EmptyLoci { return !!x && x.kind === 'empty-loci'; } +export interface DataLoci { + readonly kind: 'data-loci', + readonly data: any, + readonly tag: string + readonly indices: OrderedSet<number> +} +export function isDataLoci(x: any): x is DataLoci { + return !!x && x.kind === 'data-loci'; +} +export function areDataLociEqual(a: DataLoci, b: DataLoci) { + return a.data === b.data && a.tag === b.tag && OrderedSet.areEqual(a.indices, b.indices) +} +export function createDataLoci(data: any, tag: string, indices: OrderedSet<number>): DataLoci { + return { kind: 'data-loci', data, tag, indices } +} + export function areLociEqual(lociA: Loci, lociB: Loci) { if (isEveryLoci(lociA) && isEveryLoci(lociB)) return true if (isEmptyLoci(lociA) && isEmptyLoci(lociB)) return true + if (isDataLoci(lociA) && isDataLoci(lociB)) { + return areDataLociEqual(lociA, lociB) + } if (Structure.isLoci(lociA) && Structure.isLoci(lociB)) { return Structure.areLociEqual(lociA, lociB) } @@ -48,7 +67,7 @@ export function areLociEqual(lociA: Loci, lociB: Loci) { export { Loci } -type Loci = StructureElement.Loci | Structure.Loci | Link.Loci | EveryLoci | EmptyLoci | Shape.Loci +type Loci = StructureElement.Loci | Structure.Loci | Link.Loci | EveryLoci | EmptyLoci | DataLoci | Shape.Loci namespace Loci { 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 fba6dbf620a7db014cd11dabbd7fa51ae9a0119a..e7c7ce4f56bcb76775d47642e46b3c4a50a9eb60 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 @@ -10,6 +10,9 @@ import { AssemblySymmetry } from 'mol-model-props/rcsb/assembly-symmetry'; import { CustomPropertyRegistry } from 'mol-plugin/util/custom-prop-registry'; import { AssemblySymmetryClusterColorThemeProvider } from 'mol-model-props/rcsb/themes/assembly-symmetry-cluster'; import { AssemblySymmetryAxesRepresentationProvider } from 'mol-model-props/rcsb/representations/assembly-symmetry-axes'; +import { Loci, isDataLoci } from 'mol-model/loci'; +import { OrderedSet } from 'mol-data/int'; +import { Table } from 'mol-data/db'; export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean }>({ name: 'rcsb-assembly-symmetry-prop', @@ -27,6 +30,7 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean register(): void { this.ctx.customModelProperties.register(this.provider); + this.ctx.lociLabels.addProvider(labelAssemblySymmetryAxes); // TODO: support filtering of themes and representations based on the input structure // in this case, it would check structure.models[0].customProperties.has(AssemblySymmetry.Descriptor) @@ -43,6 +47,7 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean unregister() { this.ctx.customModelProperties.unregister(AssemblySymmetry.Descriptor.name); + this.ctx.lociLabels.removeProvider(labelAssemblySymmetryAxes); this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove('rcsb-assembly-symmetry-cluster') this.ctx.structureRepresentation.registry.remove('rcsb-assembly-symmetry-axes') } @@ -50,4 +55,21 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean params: () => ({ autoAttach: PD.Boolean(false) }) -}); \ No newline at end of file +}); + +function labelAssemblySymmetryAxes(loci: Loci): string | undefined { + if (isDataLoci(loci) && AssemblySymmetry.is(loci.data) && loci.tag === 'axes') { + const { rcsb_assembly_symmetry_axis: axis, rcsb_assembly_symmetry: sym } = loci.data.db + const labels: string[] = [] + OrderedSet.forEach(loci.indices, v => { + const symmetryId = axis.symmetry_id.value(v) + const symmetry = Table.pickRow(sym, i => sym.id.value(i) === symmetryId) + if (symmetry) { + labels.push(`Axis of order ${axis.order.value(v)} for ${symmetry.kind} ${symmetry.type.toLowerCase()} symmetry`) + } + }) + // labels.push(`Axis ${i + 1} for ${symmetry.kind} ${symmetry.type.toLowerCase()} symmetry`) + return labels.length ? labels.join(', ') : undefined + } + return undefined +} \ No newline at end of file diff --git a/src/mol-theme/label.ts b/src/mol-theme/label.ts index 0e961fca8c17953d53ebed5f0a74fa00261176f0..befa5cea8789581c5171ce2878c13aef97cddfba 100644 --- a/src/mol-theme/label.ts +++ b/src/mol-theme/label.ts @@ -44,6 +44,8 @@ export function labelFirst(loci: Loci): string { return 'Everything' case 'empty-loci': return 'Nothing' + case 'data-loci': + return '' } }