diff --git a/src/mol-model-props/rcsb/assembly-symmetry.ts b/src/mol-model-props/rcsb/assembly-symmetry.ts index 94ef48049231a5cc13bf370f557ea7f8c55f8ff8..b9ef9a2448c5f169152a72cfb916158e51750c1d 100644 --- a/src/mol-model-props/rcsb/assembly-symmetry.ts +++ b/src/mol-model-props/rcsb/assembly-symmetry.ts @@ -8,7 +8,7 @@ import { AssemblySymmetryQuery, AssemblySymmetryQueryVariables } from './graphql import query from './graphql/symmetry.gql'; import { ParamDefinition as PD } from '../../mol-util/param-definition' -import { CustomPropertyDescriptor, Structure } from '../../mol-model/structure'; +import { CustomPropertyDescriptor, Structure, Model } from '../../mol-model/structure'; import { Database as _Database, Column } from '../../mol-data/db' import { GraphQLClient } from '../../mol-util/graphql-client'; import { CustomProperty } from '../common/custom-property'; @@ -26,6 +26,18 @@ const BiologicalAssemblyNames = new Set([ 'software_defined_assembly' ]) +export function isBiologicalAssembly(structure: Structure): boolean { + if (!MmcifFormat.is(structure.models[0].sourceData)) return false + const mmcif = structure.models[0].sourceData.data.db + if (!mmcif.pdbx_struct_assembly.details.isDefined) return false + const id = structure.units[0].conformation.operator.assembly?.id || '' + if (id === '' || id === 'deposited') return true + const indices = Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id) + if (indices.length !== 1) return false + const details = mmcif.pdbx_struct_assembly.details.value(indices[0]) + return BiologicalAssemblyNames.has(details) +} + export namespace AssemblySymmetry { export enum Tag { Cluster = 'rcsb-assembly-symmetry-cluster', @@ -35,19 +47,11 @@ export namespace AssemblySymmetry { export const DefaultServerUrl = 'https://data-beta.rcsb.org/graphql' export function isApplicable(structure?: Structure): boolean { - // check if structure is from pdb entry - if (!structure || structure.models.length !== 1 || !MmcifFormat.is(structure.models[0].sourceData) || (!structure.models[0].sourceData.data.db.database_2.database_id.isDefined && - structure.models[0].entryId.length !== 4)) return false - - // check if assembly is 'biological' - const mmcif = structure.models[0].sourceData.data.db - if (!mmcif.pdbx_struct_assembly.details.isDefined) return false - const id = structure.units[0].conformation.operator.assembly?.id || '' - if (id === '' || id === 'deposited') return true - const indices = Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id) - if (indices.length !== 1) return false - const details = mmcif.pdbx_struct_assembly.details.value(indices[0]) - return BiologicalAssemblyNames.has(details) + return ( + !!structure && structure.models.length === 1 && + Model.isFromPdbArchive(structure.models[0]) && + isBiologicalAssembly(structure) + ) } export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<AssemblySymmetryDataValue> { 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 50b6292dab50b15e0bc47611434c77ab61e8d4d3..8c04d08a79d6683bdbb3e7e2492ddd521eecf4df 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 @@ -155,6 +155,9 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({ name: 'Assembly Symmetry', group: 'Annotation', description: 'Shows Assembly Symmetry axes and cage; colors structure according to assembly symmetry cluster membership. Data calculated with BioJava, obtained via RCSB PDB.' }, + isApplicable(a) { + return AssemblySymmetry.isApplicable(a.data) + }, params: () => AssemblySymmetryPresetParams, async apply(ref, params, plugin) { const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref); diff --git a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts index af50e65b45871fbc1c86bfd5a298c9898a720193..96f7e410c8e5d2959d7d4a59d1c9aa60e59d0bed 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts @@ -20,6 +20,7 @@ import { MolScriptBuilder as MS } from '../../../../../mol-script/language/build import { Task } from '../../../../../mol-task'; import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../../../mol-plugin-state/builder/structure/representation-preset'; import { StateObjectRef } from '../../../../../mol-state'; +import { Model } from '../../../../../mol-model/structure'; export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({ name: 'rcsb-validation-report-prop', @@ -53,7 +54,10 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean, this.ctx.representation.structure.registry.add(ClashesRepresentationProvider) this.ctx.query.structure.registry.add(hasClash) - this.ctx.builders.structure.representation.registerPreset(ValidationReportPreset) + + this.ctx.builders.structure.representation.registerPreset(ValidationReportGeometryQualityPreset) + this.ctx.builders.structure.representation.registerPreset(ValidationReportDensityFitPreset) + this.ctx.builders.structure.representation.registerPreset(ValidationReportRandomCoilIndexPreset) } update(p: { autoAttach: boolean, showTooltip: boolean }) { @@ -78,7 +82,10 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean, this.ctx.representation.structure.registry.remove(ClashesRepresentationProvider) this.ctx.query.structure.registry.remove(hasClash) - this.ctx.builders.structure.representation.unregisterPreset(ValidationReportPreset) + + this.ctx.builders.structure.representation.unregisterPreset(ValidationReportGeometryQualityPreset) + this.ctx.builders.structure.representation.unregisterPreset(ValidationReportDensityFitPreset) + this.ctx.builders.structure.representation.unregisterPreset(ValidationReportRandomCoilIndexPreset) } }, params: () => ({ @@ -99,7 +106,7 @@ function geometryQualityLabel(loci: Loci): string | undefined { const validationReport = ValidationReportProvider.get(unit.model).value if (!validationReport) return - if (unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) return + if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) return const { bondOutliers, angleOutliers } = validationReport const eI = unit.elements[OrderedSet.start(indices)] @@ -127,6 +134,7 @@ function geometryQualityLabel(loci: Loci): string | undefined { for (const { indices, unit } of loci.elements) { const validationReport = ValidationReportProvider.get(unit.model).value if (!validationReport) continue + if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue hasValidationReport = true const { geometryIssues } = validationReport @@ -180,7 +188,7 @@ function densityFitLabel(loci: Loci): string | undefined { for (const { indices, unit } of loci.elements) { const validationReport = ValidationReportProvider.get(unit.model).value if (!validationReport) continue - if (unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue + if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue const { rsrz, rscc } = validationReport const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index @@ -237,7 +245,7 @@ function randomCoilIndexLabel(loci: Loci): string | undefined { for (const { indices, unit } of loci.elements) { const validationReport = ValidationReportProvider.get(unit.model).value if (!validationReport) continue - if (unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue + if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue const { rci } = validationReport const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index @@ -288,24 +296,26 @@ const hasClash = StructureSelectionQuery('Residues with Clashes', MS.struct.modi // -export const ValidationReportPreset = StructureRepresentationPresetProvider({ - id: 'preset-structure-representation-rcsb-validation-report', +export const ValidationReportGeometryQualityPreset = StructureRepresentationPresetProvider({ + id: 'preset-structure-representation-rcsb-validation-report-geometry-uality', display: { - name: 'Validation Report', group: 'Annotation', + name: 'Validation Report (Geometry Quality)', group: 'Annotation', description: 'Color structure based on geometry quality; show geometry clashes. Data from wwPDB Validation Report, obtained via RCSB PDB.' }, + isApplicable(a) { + return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]) + }, params: () => StructureRepresentationPresetProvider.CommonParams, async apply(ref, params, plugin) { const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref); const model = structureCell?.obj?.data.model if (!structureCell || !model) return {}; - const colorTheme = GeometryQualityColorThemeProvider.name as any - await plugin.runTask(Task.create('Validation Report', async runtime => { await ValidationReportProvider.attach({ fetch: plugin.fetch, runtime }, model) })) + const colorTheme = GeometryQualityColorThemeProvider.name as any const { components, representations } = await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin) const clashes = await plugin.builders.structure.tryCreateComponentFromExpression(structureCell, hasClash.expression, 'clashes', { label: 'Clashes' }) @@ -320,4 +330,52 @@ export const ValidationReportPreset = StructureRepresentationPresetProvider({ await plugin.updateDataState(update, { revertOnError: false }); return { components: { ...components, clashes }, representations: { ...representations, clashesBallAndStick, clashesSnfg3d } }; } +}); + +export const ValidationReportDensityFitPreset = StructureRepresentationPresetProvider({ + id: 'preset-structure-representation-rcsb-validation-report-density-fit', + display: { + name: 'Validation Report (Density Fit)', group: 'Annotation', + description: 'Color structure based on density fit. Data from wwPDB Validation Report, obtained via RCSB PDB.' + }, + isApplicable(a) { + return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]) && Model.hasXrayMap(a.data.models[0]) + }, + params: () => StructureRepresentationPresetProvider.CommonParams, + async apply(ref, params, plugin) { + const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref); + const model = structureCell?.obj?.data.model + if (!structureCell || !model) return {}; + + await plugin.runTask(Task.create('Validation Report', async runtime => { + await ValidationReportProvider.attach({ fetch: plugin.fetch, runtime }, model) + })) + + const colorTheme = DensityFitColorThemeProvider.name as any + return await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin) + } +}); + +export const ValidationReportRandomCoilIndexPreset = StructureRepresentationPresetProvider({ + id: 'preset-structure-representation-rcsb-validation-report-random-coil-index', + display: { + name: 'Validation Report (Random Coil Index)', group: 'Annotation', + description: 'Color structure based on Random Coil Index. Data from wwPDB Validation Report, obtained via RCSB PDB.' + }, + isApplicable(a) { + return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]) && Model.isFromNmr(a.data.models[0]) + }, + params: () => StructureRepresentationPresetProvider.CommonParams, + async apply(ref, params, plugin) { + const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref); + const model = structureCell?.obj?.data.model + if (!structureCell || !model) return {}; + + await plugin.runTask(Task.create('Validation Report', async runtime => { + await ValidationReportProvider.attach({ fetch: plugin.fetch, runtime }, model) + })) + + const colorTheme = RandomCoilIndexColorThemeProvider.name as any + return await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin) + } }); \ No newline at end of file