From 8f94e77172febf59ca63da7bb9fb3d1bd0b3c773 Mon Sep 17 00:00:00 2001 From: Roman Rodionov Date: Fri, 30 Aug 2024 15:31:34 +0200 Subject: [PATCH] #4557 - Support of ambiguous monomers in small molecules mode (#5399) - fixed ambiguous monomers in molecules mode - fixed rna builder for ambiguous monomers - fixed sequence mode for ambiguous monomers --- .../application/editor/modes/SequenceMode.ts | 3 +- .../operations/monomer/monomerFactory.ts | 13 +++-- .../src/application/editor/tools/Monomer.ts | 2 +- .../renderers/AmbiguousMonomerRenderer.ts | 2 +- .../render/renderers/RenderersManager.ts | 7 +-- .../sequence/RNASequenceItemRenderer.ts | 40 ++++++++++++--- .../domain/entities/DrawingEntitiesManager.ts | 50 ++++++++++++------- .../src/domain/entities/Nucleoside.ts | 6 ++- .../src/domain/entities/Nucleotide.ts | 3 +- .../ket/fromKet/monomerToDrawingEntity.ts | 2 +- .../domain/serializers/ket/ketSerializer.ts | 2 +- .../RnaBuilder/RnaAccordion/RnaAccordion.tsx | 10 +++- .../src/helpers/getPreset.ts | 17 ++++--- .../rna-builder/rnaBuilderSlice.helper.ts | 11 +++- .../src/state/rna-builder/rnaBuilderSlice.ts | 7 +-- 15 files changed, 120 insertions(+), 55 deletions(-) diff --git a/packages/ketcher-core/src/application/editor/modes/SequenceMode.ts b/packages/ketcher-core/src/application/editor/modes/SequenceMode.ts index 1cb32404fc..daed21cb89 100644 --- a/packages/ketcher-core/src/application/editor/modes/SequenceMode.ts +++ b/packages/ketcher-core/src/application/editor/modes/SequenceMode.ts @@ -11,6 +11,7 @@ import { import { AttachmentPointName, MonomerItemType } from 'domain/types'; import { Command } from 'domain/entities/Command'; import { + AmbiguousMonomer, BaseMonomer, LinkerSequenceNode, Phosphate, @@ -1438,7 +1439,7 @@ export class SequenceMode extends BaseMode { position, ) as Sugar; - let rnaBaseMonomer: RNABase | null = null; + let rnaBaseMonomer: RNABase | AmbiguousMonomer | null = null; if (rnaBase) { rnaBaseMonomer = editor.drawingEntitiesManager.createMonomer( rnaBase, diff --git a/packages/ketcher-core/src/application/editor/operations/monomer/monomerFactory.ts b/packages/ketcher-core/src/application/editor/operations/monomer/monomerFactory.ts index 699d857552..a49c94cdf0 100644 --- a/packages/ketcher-core/src/application/editor/operations/monomer/monomerFactory.ts +++ b/packages/ketcher-core/src/application/editor/operations/monomer/monomerFactory.ts @@ -7,8 +7,9 @@ import { PhosphateRenderer, UnresolvedMonomerRenderer, UnsplitNucleotideRenderer, + AmbiguousMonomerRenderer, } from 'application/render/renderers'; -import { MonomerItemType } from 'domain/types'; +import { MonomerOrAmbiguousType } from 'domain/types'; import { Peptide, Chem, @@ -17,8 +18,10 @@ import { RNABase, UnresolvedMonomer, UnsplitNucleotide, + AmbiguousMonomer, } from 'domain/entities'; import { KetMonomerClass } from 'application/formatters/types/ket'; +import { isAmbiguousMonomerLibraryItem } from 'domain/helpers/monomers'; type DerivedClass = new (...args: unknown[]) => T; export const MONOMER_CONST = { @@ -43,7 +46,7 @@ type Monomer = | typeof Phosphate; export const monomerFactory = ( - monomer: MonomerItemType, + monomer: MonomerOrAmbiguousType, ): [ Monomer: Monomer, MonomerRenderer: DerivedClass, @@ -53,7 +56,11 @@ export const monomerFactory = ( let MonomerRenderer; let ketMonomerClass: KetMonomerClass; - if (monomer.props.unresolved) { + if (isAmbiguousMonomerLibraryItem(monomer)) { + Monomer = AmbiguousMonomer; + MonomerRenderer = AmbiguousMonomerRenderer; + ketMonomerClass = AmbiguousMonomer.getMonomerClass(monomer.monomers); + } else if (monomer.props.unresolved) { Monomer = UnresolvedMonomer; MonomerRenderer = UnresolvedMonomerRenderer; ketMonomerClass = KetMonomerClass.CHEM; diff --git a/packages/ketcher-core/src/application/editor/tools/Monomer.ts b/packages/ketcher-core/src/application/editor/tools/Monomer.ts index f914275ff5..730b0c5cd8 100644 --- a/packages/ketcher-core/src/application/editor/tools/Monomer.ts +++ b/packages/ketcher-core/src/application/editor/tools/Monomer.ts @@ -57,7 +57,7 @@ class MonomerTool implements BaseTool { ), ); if (isAmbiguousMonomerLibraryItem(this.monomer)) { - modelChanges = this.editor.drawingEntitiesManager.addVariantMonomer( + modelChanges = this.editor.drawingEntitiesManager.addAmbiguousMonomer( this.monomer, position, ); diff --git a/packages/ketcher-core/src/application/render/renderers/AmbiguousMonomerRenderer.ts b/packages/ketcher-core/src/application/render/renderers/AmbiguousMonomerRenderer.ts index bd724aabcd..5703fd0ea6 100644 --- a/packages/ketcher-core/src/application/render/renderers/AmbiguousMonomerRenderer.ts +++ b/packages/ketcher-core/src/application/render/renderers/AmbiguousMonomerRenderer.ts @@ -109,8 +109,8 @@ export class AmbiguousMonomerRenderer extends BaseMonomerRenderer { public show(theme) { super.show(theme); this.appendNumberOfMonomers(); + this.appendEnumeration(); if (this.CHAIN_BEGINNING) { - this.appendEnumeration(); this.appendChainBeginning(); } } diff --git a/packages/ketcher-core/src/application/render/renderers/RenderersManager.ts b/packages/ketcher-core/src/application/render/renderers/RenderersManager.ts index ad1da702f4..8eb9cef798 100644 --- a/packages/ketcher-core/src/application/render/renderers/RenderersManager.ts +++ b/packages/ketcher-core/src/application/render/renderers/RenderersManager.ts @@ -187,7 +187,7 @@ export class RenderersManager { const nextMonomer = getNextMonomerInChain(monomerRenderer.monomer); if ( - !(nextMonomer instanceof Peptide) || + !isPeptideOrAmbiguousPeptide(nextMonomer) || nextMonomer === peptideRenderer.monomer ) { return; @@ -311,10 +311,7 @@ export class RenderersManager { ]); this.monomers.forEach((monomerRenderer) => { - if ( - monomerRenderer instanceof PeptideRenderer || - monomerRenderer instanceof AmbiguousMonomerRenderer - ) { + if (isPeptideOrAmbiguousPeptide(monomerRenderer.monomer)) { this.recalculatePeptideEnumeration( monomerRenderer as PeptideRenderer, firstMonomersInCyclicChains, diff --git a/packages/ketcher-core/src/application/render/renderers/sequence/RNASequenceItemRenderer.ts b/packages/ketcher-core/src/application/render/renderers/sequence/RNASequenceItemRenderer.ts index 805cd282d6..41d6c19ec7 100644 --- a/packages/ketcher-core/src/application/render/renderers/sequence/RNASequenceItemRenderer.ts +++ b/packages/ketcher-core/src/application/render/renderers/sequence/RNASequenceItemRenderer.ts @@ -1,15 +1,43 @@ import { BaseSequenceItemRenderer } from 'application/render/renderers/sequence/BaseSequenceItemRenderer'; -import { Nucleoside, Nucleotide } from 'domain/entities'; +import { + AmbiguousMonomer, + Nucleoside, + Nucleotide, + Vec2, +} from 'domain/entities'; +import { BaseSubChain } from 'domain/entities/monomer-chains/BaseSubChain'; export abstract class RNASequenceItemRenderer extends BaseSequenceItemRenderer { - get symbolToDisplay(): string { - return ( - this.node.monomer.attachmentPointsToBonds.R3?.getAnotherMonomer( - this.node.monomer, - )?.monomerItem?.props.MonomerNaturalAnalogCode || '@' + constructor( + public node: Nucleoside | Nucleotide, + _firstNodeInChainPosition: Vec2, + _monomerIndexInChain: number, + _isLastMonomerInChain: boolean, + _subChain: BaseSubChain, + _isEditingSymbol: boolean, + public monomerSize: { width: number; height: number }, + public scaledMonomerPosition: Vec2, + ) { + super( + node, + _firstNodeInChainPosition, + _monomerIndexInChain, + _isLastMonomerInChain, + _subChain, + _isEditingSymbol, + monomerSize, + scaledMonomerPosition, ); } + get symbolToDisplay(): string { + return this.node.rnaBase instanceof AmbiguousMonomer + ? this.node.monomer.label + : this.node.monomer.attachmentPointsToBonds.R3?.getAnotherMonomer( + this.node.monomer, + )?.monomerItem?.props.MonomerNaturalAnalogCode || '@'; + } + protected drawCommonModification(node: Nucleoside | Nucleotide) { if (node.rnaBase.isModification) { this.backgroundElement?.attr( diff --git a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts index dd43572e96..ffba910e99 100644 --- a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts +++ b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts @@ -51,6 +51,7 @@ import { getNextMonomerInChain, getPhosphateFromSugar, isAmbiguousMonomerLibraryItem, + isRnaBaseOrAmbiguousRnaBase, isValidNucleoside, isValidNucleotide, } from 'domain/helpers/monomers'; @@ -85,7 +86,7 @@ type RnaPresetAdditionParams = { type NucleotideOrNucleoside = { sugar: Sugar; phosphate?: Phosphate; - rnaBase: RNABase; + rnaBase: RNABase | AmbiguousMonomer; baseMonomer: Sugar | Phosphate; }; @@ -938,27 +939,38 @@ export class DrawingEntitiesManager { let previousMonomer: BaseMonomer | undefined; const sugarMonomer = node.monomers.find( (monomer) => monomer instanceof Sugar, - ) as Sugar; + ) as Sugar | AmbiguousMonomer; const phosphateMonomer = node.monomers.find( (monomer) => monomer instanceof Phosphate, - ) as Phosphate; - const rnaBaseMonomer = node.monomers.find( - (monomer) => monomer instanceof RNABase, - ) as RNABase; + ) as Phosphate | AmbiguousMonomer; + const rnaBaseMonomer = node.monomers.find((monomer) => + isRnaBaseOrAmbiguousRnaBase(monomer), + ) as RNABase | AmbiguousMonomer; const monomers = [rnaBaseMonomer, sugarMonomer, phosphateMonomer].filter( (monomer) => monomer !== undefined, ) as BaseMonomer[]; monomers.forEach((monomer) => { - const monomerAddOperation = new MonomerAddOperation( - this.addMonomerChangeModel.bind( - this, - monomer.monomerItem, - monomer.position, - monomer, - ), - this.deleteMonomerChangeModel.bind(this), - ); + const monomerAddOperation = + monomer instanceof AmbiguousMonomer + ? new MonomerAddOperation( + this.addAmbiguousMonomerChangeModel.bind( + this, + monomer.variantMonomerItem, + monomer.position, + monomer, + ), + this.deleteMonomerChangeModel.bind(this), + ) + : new MonomerAddOperation( + this.addMonomerChangeModel.bind( + this, + monomer.monomerItem, + monomer.position, + monomer, + ), + this.deleteMonomerChangeModel.bind(this), + ); command.addOperation(monomerAddOperation); if (previousMonomer) { @@ -1513,7 +1525,7 @@ export class DrawingEntitiesManager { this.monomers.forEach((monomer) => { const monomerAddCommand = monomer instanceof AmbiguousMonomer - ? targetDrawingEntitiesManager.addVariantMonomer( + ? targetDrawingEntitiesManager.addAmbiguousMonomer( { ...monomer.variantMonomerItem, }, @@ -1848,7 +1860,7 @@ export class DrawingEntitiesManager { return command; } - private addVariantMonomerChangeModel( + private addAmbiguousMonomerChangeModel( variantMonomerItem: AmbiguousMonomerType, position: Vec2, _monomer?: BaseMonomer, @@ -1866,13 +1878,13 @@ export class DrawingEntitiesManager { return monomer; } - public addVariantMonomer( + public addAmbiguousMonomer( variantMonomerItem: AmbiguousMonomerType, position: Vec2, ) { const command = new Command(); const operation = new MonomerAddOperation( - this.addVariantMonomerChangeModel.bind( + this.addAmbiguousMonomerChangeModel.bind( this, variantMonomerItem, position, diff --git a/packages/ketcher-core/src/domain/entities/Nucleoside.ts b/packages/ketcher-core/src/domain/entities/Nucleoside.ts index 8b7ed8ee51..998493b9d9 100644 --- a/packages/ketcher-core/src/domain/entities/Nucleoside.ts +++ b/packages/ketcher-core/src/domain/entities/Nucleoside.ts @@ -17,9 +17,13 @@ import { getSugarBySequenceType, } from 'domain/helpers/rna'; import { BaseMonomer } from 'domain/entities/BaseMonomer'; +import { AmbiguousMonomer } from 'domain/entities/AmbiguousMonomer'; export class Nucleoside { - constructor(public sugar: Sugar, public rnaBase: RNABase) {} + constructor( + public sugar: Sugar, + public rnaBase: RNABase | AmbiguousMonomer, + ) {} static fromSugar(sugar: Sugar, needValidation = true) { if (needValidation) { diff --git a/packages/ketcher-core/src/domain/entities/Nucleotide.ts b/packages/ketcher-core/src/domain/entities/Nucleotide.ts index a02521ef65..859c2cfeb4 100644 --- a/packages/ketcher-core/src/domain/entities/Nucleotide.ts +++ b/packages/ketcher-core/src/domain/entities/Nucleotide.ts @@ -17,11 +17,12 @@ import { } from 'domain/helpers/rna'; import { RNA_DNA_NON_MODIFIED_PART } from 'domain/constants/monomers'; import { BaseMonomer } from 'domain/entities/BaseMonomer'; +import { AmbiguousMonomer } from 'domain/entities/AmbiguousMonomer'; export class Nucleotide { constructor( public sugar: Sugar, - public rnaBase: RNABase, + public rnaBase: RNABase | AmbiguousMonomer, public phosphate: Phosphate, ) {} diff --git a/packages/ketcher-core/src/domain/serializers/ket/fromKet/monomerToDrawingEntity.ts b/packages/ketcher-core/src/domain/serializers/ket/fromKet/monomerToDrawingEntity.ts index 2cf2fd891c..352e9db3c4 100644 --- a/packages/ketcher-core/src/domain/serializers/ket/fromKet/monomerToDrawingEntity.ts +++ b/packages/ketcher-core/src/domain/serializers/ket/fromKet/monomerToDrawingEntity.ts @@ -88,7 +88,7 @@ export function variantMonomerToDrawingEntity( const monomers = createMonomersForVariantMonomer(template, parsedFileContent); - return drawingEntitiesManager.addVariantMonomer( + return drawingEntitiesManager.addAmbiguousMonomer( { monomers, id: template.id, diff --git a/packages/ketcher-core/src/domain/serializers/ket/ketSerializer.ts b/packages/ketcher-core/src/domain/serializers/ket/ketSerializer.ts index f650aae2d5..5cdb6f9a52 100644 --- a/packages/ketcher-core/src/domain/serializers/ket/ketSerializer.ts +++ b/packages/ketcher-core/src/domain/serializers/ket/ketSerializer.ts @@ -644,7 +644,7 @@ export class KetSerializer implements Serializer { } fileContent[templateNameWithPrefix] = { - type: 'variantMonomerTemplate', + type: 'ambiguousMonomerTemplate', id: templateId, alias: variantMonomer.label, idtAliases: variantMonomer.variantMonomerItem.idtAliases, diff --git a/packages/ketcher-macromolecules/src/components/monomerLibrary/RnaBuilder/RnaAccordion/RnaAccordion.tsx b/packages/ketcher-macromolecules/src/components/monomerLibrary/RnaBuilder/RnaAccordion/RnaAccordion.tsx index 19b3d3e9bf..c2b7c93779 100644 --- a/packages/ketcher-macromolecules/src/components/monomerLibrary/RnaBuilder/RnaAccordion/RnaAccordion.tsx +++ b/packages/ketcher-macromolecules/src/components/monomerLibrary/RnaBuilder/RnaAccordion/RnaAccordion.tsx @@ -58,7 +58,11 @@ import { } from 'state/rna-builder'; import { useDispatch } from 'react-redux'; import { IRnaPreset } from '../types'; -import { KetMonomerClass, MonomerItemType } from 'ketcher-core'; +import { + isAmbiguousMonomerLibraryItem, + KetMonomerClass, + MonomerItemType, +} from 'ketcher-core'; import { selectEditor, selectIsSequenceEditInRNABuilderMode, @@ -173,7 +177,9 @@ export const RnaAccordion = ({ libraryName, duplicatePreset, editPreset }) => { return; } - const monomerClass = monomer.props.MonomerClass.toLowerCase(); + const monomerClass = isAmbiguousMonomerLibraryItem(monomer) + ? monomer.monomers[0].monomerItem.props.MonomerClass?.toLowerCase() + : monomer.props.MonomerClass.toLowerCase(); const currentPreset = { ...newPreset, [monomerClass]: monomer, diff --git a/packages/ketcher-macromolecules/src/helpers/getPreset.ts b/packages/ketcher-macromolecules/src/helpers/getPreset.ts index 63db150d13..1eb29f2211 100644 --- a/packages/ketcher-macromolecules/src/helpers/getPreset.ts +++ b/packages/ketcher-macromolecules/src/helpers/getPreset.ts @@ -6,6 +6,8 @@ import { setMonomerTemplatePrefix, KetMonomerClass, IRnaLabeledPreset, + isAmbiguousMonomerLibraryItem, + setAmbiguousMonomerTemplatePrefix, } from 'ketcher-core'; import { getMonomerUniqueKey } from 'state/library'; @@ -20,7 +22,9 @@ export const getPresets = ( ): IRnaPreset[] => { const monomerLibraryItemByMonomerIDMap = new Map( monomers.map((monomer) => { - const monomerID = setMonomerTemplatePrefix(getMonomerUniqueKey(monomer)); + const monomerID = isAmbiguousMonomerLibraryItem(monomer) + ? setAmbiguousMonomerTemplatePrefix(monomer.id) + : setMonomerTemplatePrefix(getMonomerUniqueKey(monomer)); return [monomerID, monomer]; }), ); @@ -37,6 +41,7 @@ export const getPresets = ( rnaPartsMonomerTemplateRef.$ref, ) as MonomerItemType; const [, , monomerClass] = monomerFactory(monomer); + return [monomerClass, monomer]; }), ); @@ -53,16 +58,12 @@ export const getPresets = ( ) as MonomerItemType; const result: IRnaPreset = { - base: rnaBase - ? { ...rnaBase, label: rnaBase.props.MonomerName } - : undefined, + base: rnaBase ? { ...rnaBase, label: rnaBase.label } : undefined, name: rnaPresetsTemplate.name, phosphate: phosphate - ? { ...phosphate, label: phosphate.props.MonomerName } - : undefined, - sugar: ribose - ? { ...ribose, label: ribose.props.MonomerName } + ? { ...phosphate, label: phosphate.label } : undefined, + sugar: ribose ? { ...ribose, label: ribose.label } : undefined, favorite: rnaPresetsTemplate.favorite, default: isDefault || rnaPresetsTemplate.default, }; diff --git a/packages/ketcher-macromolecules/src/state/rna-builder/rnaBuilderSlice.helper.ts b/packages/ketcher-macromolecules/src/state/rna-builder/rnaBuilderSlice.helper.ts index 65468b1914..7725cb7d9a 100644 --- a/packages/ketcher-macromolecules/src/state/rna-builder/rnaBuilderSlice.helper.ts +++ b/packages/ketcher-macromolecules/src/state/rna-builder/rnaBuilderSlice.helper.ts @@ -2,6 +2,7 @@ import omit from 'lodash/omit'; import { IRnaLabeledPreset, IRnaPreset, + setAmbiguousMonomerTemplatePrefix, setMonomerTemplatePrefix, } from 'ketcher-core'; @@ -15,9 +16,15 @@ export const transformRnaPresetToRnaLabeledPreset = (rnaPreset: IRnaPreset) => { rnaLabeledPreset.templates = []; for (const monomerName of fieldsToLabel) { - if (!rnaPreset[monomerName]?.props?.id) continue; + const monomerLibraryItem = rnaPreset[monomerName]; + const templateId = monomerLibraryItem?.props?.id || monomerLibraryItem?.id; + + if (!templateId) continue; + rnaLabeledPreset.templates.push({ - $ref: setMonomerTemplatePrefix(rnaPreset[monomerName].props.id), + $ref: monomerLibraryItem.isAmbiguous + ? setAmbiguousMonomerTemplatePrefix(templateId) + : setMonomerTemplatePrefix(templateId), }); } diff --git a/packages/ketcher-macromolecules/src/state/rna-builder/rnaBuilderSlice.ts b/packages/ketcher-macromolecules/src/state/rna-builder/rnaBuilderSlice.ts index 4fcf1620c9..eb97709c79 100644 --- a/packages/ketcher-macromolecules/src/state/rna-builder/rnaBuilderSlice.ts +++ b/packages/ketcher-macromolecules/src/state/rna-builder/rnaBuilderSlice.ts @@ -351,9 +351,10 @@ export const selectIsEditMode = (state: RootState): boolean => { export const selectPresetFullName = (preset: IRnaPreset): string => { if (!preset) return ''; - const sugar = preset.sugar?.props.MonomerName || ''; - const base = preset.base?.props.MonomerName || ''; - const phosphate = preset.phosphate?.props.MonomerName || ''; + const sugar = preset.sugar?.label || preset.sugar?.props.MonomerName || ''; + const base = preset.base?.label || preset.base?.props.MonomerName || ''; + const phosphate = + preset.phosphate?.label || preset.phosphate?.props.MonomerName || ''; let fullName = sugar; if (sugar && phosphate) {