From d2f5aa613b369b708d21c494299e6ebe984ad041 Mon Sep 17 00:00:00 2001 From: Roman Rodionov Date: Tue, 12 Nov 2024 15:55:42 +0100 Subject: [PATCH] #5935 - Unable to establish hydrogen bond connection if monomer has no free attachment points (#5955) (#5958) #5940 - Copy/paste operation works wrong (copy only two hydrogen bonds and drops others) #5941 - Hydrogen bonds remain in place on monomer delete #5933 - Error message is wrong if user tries to establish hydrogen bond if it is already exist --- .../src/application/editor/modes/BaseMode.ts | 16 +++-- .../src/application/editor/tools/Bond.ts | 22 ++++++- .../src/domain/entities/BaseMonomer.ts | 63 +++++++++++++++---- .../domain/entities/DrawingEntitiesManager.ts | 14 +++-- .../src/domain/entities/HydrogenBond.ts | 8 ++- .../src/domain/entities/PolymerBond.ts | 2 +- 6 files changed, 100 insertions(+), 25 deletions(-) diff --git a/packages/ketcher-core/src/application/editor/modes/BaseMode.ts b/packages/ketcher-core/src/application/editor/modes/BaseMode.ts index a08e0fc562..2ad5fd90d8 100644 --- a/packages/ketcher-core/src/application/editor/modes/BaseMode.ts +++ b/packages/ketcher-core/src/application/editor/modes/BaseMode.ts @@ -26,6 +26,8 @@ import { PolymerBond } from 'domain/entities/PolymerBond'; import { ketcherProvider } from 'application/utils'; import { DrawingEntitiesManager } from 'domain/entities/DrawingEntitiesManager'; import { HydrogenBond } from 'domain/entities/HydrogenBond'; +import { AttachmentPointName } from 'domain/types'; +import { MACROMOLECULES_BOND_TYPES } from 'application/editor/tools/Bond'; export abstract class BaseMode { private _pasteIsInProgress = false; @@ -115,10 +117,7 @@ export abstract class BaseMode { entity.position, entity, ); - } else if ( - (entity instanceof PolymerBond || entity instanceof HydrogenBond) && - entity.secondMonomer - ) { + } else if (entity instanceof PolymerBond && entity.secondMonomer) { const firstAttachmentPoint = entity.firstMonomer.getAttachmentPointByBond(entity); const secondAttachmentPoint = @@ -138,6 +137,15 @@ export abstract class BaseMode { entity, ); } + } else if (entity instanceof HydrogenBond && entity.secondMonomer) { + drawingEntitiesManager.finishPolymerBondCreationModelChange( + entity.firstMonomer, + entity.secondMonomer, + AttachmentPointName.HYDROGEN, + AttachmentPointName.HYDROGEN, + MACROMOLECULES_BOND_TYPES.HYDROGEN, + entity, + ); } }); const ketSerializer = new KetSerializer(); diff --git a/packages/ketcher-core/src/application/editor/tools/Bond.ts b/packages/ketcher-core/src/application/editor/tools/Bond.ts index 3f9eb8102c..3016d7bb10 100644 --- a/packages/ketcher-core/src/application/editor/tools/Bond.ts +++ b/packages/ketcher-core/src/application/editor/tools/Bond.ts @@ -104,7 +104,7 @@ class PolymerBond implements BaseTool { const startAttachmentPoint = selectedRenderer.monomer.startBondAttachmentPoint; - if (!startAttachmentPoint) { + if (!startAttachmentPoint && !this.isHydrogenBond) { this.editor.events.error.dispatch( "Selected monomer doesn't have any free attachment points", ); @@ -339,14 +339,32 @@ class PolymerBond implements BaseTool { private finishBondCreation(secondMonomer: BaseMonomer) { assert(this.bondRenderer); - if (!secondMonomer.hasFreeAttachmentPoint) { + + if (!this.isHydrogenBond && !secondMonomer.hasFreeAttachmentPoint) { this.editor.events.error.dispatch( "Monomers don't have any connection point available", ); + return this.editor.drawingEntitiesManager.cancelPolymerBondCreation( this.bondRenderer.polymerBond, ); } + + if ( + this.isHydrogenBond && + secondMonomer.hasHydrogenBondWithMonomer( + this.bondRenderer?.polymerBond.firstMonomer, + ) + ) { + this.editor.events.error.dispatch( + 'Unable to establish multiple hydrogen bonds between two monomers', + ); + + return this.editor.drawingEntitiesManager.cancelPolymerBondCreation( + this.bondRenderer.polymerBond, + ); + } + const firstMonomerAttachmentPoint = this.isHydrogenBond ? AttachmentPointName.HYDROGEN : this.bondRenderer.polymerBond.firstMonomer.getPotentialAttachmentPointByBond( diff --git a/packages/ketcher-core/src/domain/entities/BaseMonomer.ts b/packages/ketcher-core/src/domain/entities/BaseMonomer.ts index 59d4335a8f..bb99ddd44a 100644 --- a/packages/ketcher-core/src/domain/entities/BaseMonomer.ts +++ b/packages/ketcher-core/src/domain/entities/BaseMonomer.ts @@ -40,6 +40,7 @@ export abstract class BaseMonomer extends DrawingEntity { public attachmentPointsVisible = false; public monomerItem: MonomerItemType; public isMonomerInRnaChainRow = false; + public hydrogenBonds: HydrogenBond[] = []; constructor( monomerItem: MonomerItemType, @@ -108,16 +109,28 @@ export abstract class BaseMonomer extends DrawingEntity { public setPotentialBond( attachmentPoint: string | undefined, - potentialBond?: PolymerBond | null, + potentialBond?: PolymerBond | HydrogenBond | null, ) { + if (potentialBond instanceof HydrogenBond) { + this.hydrogenBonds.push(potentialBond); + + return; + } + if (attachmentPoint !== undefined) { this.potentialAttachmentPointsToBonds[attachmentPoint] = potentialBond; } } public getAttachmentPointByBond( - bond: PolymerBond | MonomerToAtomBond, + bond: PolymerBond | MonomerToAtomBond | HydrogenBond, ): AttachmentPointName | undefined { + if (bond instanceof HydrogenBond) { + return this.hydrogenBonds.find((hydrogenBond) => hydrogenBond === bond) + ? AttachmentPointName.HYDROGEN + : undefined; + } + for (const attachmentPointName in this.attachmentPointsToBonds) { if (this.attachmentPointsToBonds[attachmentPointName] === bond) { return attachmentPointName as AttachmentPointName; @@ -206,7 +219,7 @@ export abstract class BaseMonomer extends DrawingEntity { public forEachBond( callback: ( - polymerBond: PolymerBond | MonomerToAtomBond, + polymerBond: PolymerBond | MonomerToAtomBond | HydrogenBond, attachmentPointName: AttachmentPointName, ) => void, ) { @@ -218,21 +231,42 @@ export abstract class BaseMonomer extends DrawingEntity { ); } } + + this.hydrogenBonds.forEach((hydrogenBond) => { + callback(hydrogenBond, AttachmentPointName.HYDROGEN); + }); } public setBond( attachmentPointName: AttachmentPointName, bond: PolymerBond | MonomerToAtomBond | HydrogenBond, ) { - this.attachmentPointsToBonds[ - bond instanceof HydrogenBond - ? AttachmentPointName.HYDROGEN - : attachmentPointName - ] = bond; + if (!(bond instanceof HydrogenBond)) { + this.attachmentPointsToBonds[attachmentPointName] = bond; + + return; + } + + if (!this.hydrogenBonds.includes(bond)) { + this.hydrogenBonds.push(bond); + } } - public unsetBond(attachmentPointName: AttachmentPointName) { - this.attachmentPointsToBonds[attachmentPointName] = null; + public unsetBond( + attachmentPointName?: AttachmentPointName, + bondToDelete?: HydrogenBond | PolymerBond, + ) { + if (bondToDelete instanceof HydrogenBond) { + this.hydrogenBonds = this.hydrogenBonds.filter( + (bond) => bond !== bondToDelete, + ); + + return; + } + + if (attachmentPointName) { + this.attachmentPointsToBonds[attachmentPointName] = null; + } } public get hasBonds() { @@ -242,7 +276,14 @@ export abstract class BaseMonomer extends DrawingEntity { hasBonds = true; } } - return hasBonds; + + return hasBonds || this.hydrogenBonds.length > 0; + } + + public hasHydrogenBondWithMonomer(monomer: BaseMonomer) { + return this.hydrogenBonds.find( + (bond) => bond.firstMonomer === monomer || bond.secondMonomer === monomer, + ); } public hasPotentialBonds() { diff --git a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts index 25af74d182..83418cbaf1 100644 --- a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts +++ b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts @@ -698,11 +698,17 @@ export class DrawingEntitiesManager { polymerBond.secondMonomer?.removePotentialBonds(); polymerBond.firstMonomer.turnOffSelection(); polymerBond.secondMonomer?.turnOffSelection(); - if (firstMonomerAttachmentPoint) { - polymerBond.firstMonomer.unsetBond(firstMonomerAttachmentPoint); + if (firstMonomerAttachmentPoint || polymerBond instanceof HydrogenBond) { + polymerBond.firstMonomer.unsetBond( + firstMonomerAttachmentPoint, + polymerBond, + ); } - if (secondMonomerAttachmentPoint) { - polymerBond.secondMonomer?.unsetBond(secondMonomerAttachmentPoint); + if (secondMonomerAttachmentPoint || polymerBond instanceof HydrogenBond) { + polymerBond.secondMonomer?.unsetBond( + secondMonomerAttachmentPoint, + polymerBond, + ); } } diff --git a/packages/ketcher-core/src/domain/entities/HydrogenBond.ts b/packages/ketcher-core/src/domain/entities/HydrogenBond.ts index dc522f61e1..4ce14bc63e 100644 --- a/packages/ketcher-core/src/domain/entities/HydrogenBond.ts +++ b/packages/ketcher-core/src/domain/entities/HydrogenBond.ts @@ -1,11 +1,11 @@ import { BaseRenderer } from 'application/render/renderers/BaseRenderer'; -import { SnakeModePolymerBondRenderer } from 'application/render/renderers/PolymerBondRenderer/SnakeModePolymerBondRenderer'; import { BaseMonomer } from './BaseMonomer'; import { BaseBond } from './BaseBond'; +import { FlexOrSequenceOrSnakeModePolymerBondRenderer } from 'domain/entities/PolymerBond'; export class HydrogenBond extends BaseBond { public secondMonomer?: BaseMonomer; - public renderer?: SnakeModePolymerBondRenderer = undefined; + public renderer?: FlexOrSequenceOrSnakeModePolymerBondRenderer = undefined; constructor(public firstMonomer: BaseMonomer, secondMonomer?: BaseMonomer) { super(); @@ -21,7 +21,9 @@ export class HydrogenBond extends BaseBond { this.secondMonomer = monomer; } - public setRenderer(renderer: SnakeModePolymerBondRenderer): void { + public setRenderer( + renderer: FlexOrSequenceOrSnakeModePolymerBondRenderer, + ): void { super.setBaseRenderer(renderer as BaseRenderer); this.renderer = renderer; } diff --git a/packages/ketcher-core/src/domain/entities/PolymerBond.ts b/packages/ketcher-core/src/domain/entities/PolymerBond.ts index 96c83b2e08..15438c2f7a 100644 --- a/packages/ketcher-core/src/domain/entities/PolymerBond.ts +++ b/packages/ketcher-core/src/domain/entities/PolymerBond.ts @@ -12,7 +12,7 @@ import { AttachmentPointName } from 'domain/types'; import { BaseMonomer } from './BaseMonomer'; import { BaseBond } from 'domain/entities/BaseBond'; -type FlexOrSequenceOrSnakeModePolymerBondRenderer = +export type FlexOrSequenceOrSnakeModePolymerBondRenderer = | BackBoneBondSequenceRenderer | FlexModePolymerBondRenderer | PolymerBondSequenceRenderer