From ba9f7c5cd79433c912dda75f0e6c5ac9388a1fe4 Mon Sep 17 00:00:00 2001 From: Roman Rodionov Date: Tue, 22 Oct 2024 17:35:51 +0200 Subject: [PATCH] #5403 - Introduce hydrogen bonds in macromolecules mode --- .../entities/drawingEntitiesManager.test.ts | 4 +++ .../src/application/editor/Editor.ts | 7 +++--- .../src/application/editor/tools/Bond.ts | 8 ++++-- .../SnakeModePolymerBondRenderer.ts | 12 ++++----- .../domain/entities/DrawingEntitiesManager.ts | 25 ++++++++++++++----- .../ketcher-core/src/domain/entities/index.ts | 1 + .../src/EditorEvents.tsx | 6 ++++- .../LeftMenuComponent/LeftMenuComponent.tsx | 2 +- .../src/components/menu/subMenu/styles.ts | 2 +- 9 files changed, 47 insertions(+), 20 deletions(-) diff --git a/packages/ketcher-core/__tests__/domain/entities/drawingEntitiesManager.test.ts b/packages/ketcher-core/__tests__/domain/entities/drawingEntitiesManager.test.ts index 9529982779..f99ddb482c 100644 --- a/packages/ketcher-core/__tests__/domain/entities/drawingEntitiesManager.test.ts +++ b/packages/ketcher-core/__tests__/domain/entities/drawingEntitiesManager.test.ts @@ -22,6 +22,7 @@ import { RenderersManager } from 'application/render/renderers/RenderersManager' import { createPolymerEditorCanvas } from '../../helpers/dom'; import { CoreEditor } from 'application/editor'; import { FlexMode } from 'application/editor/modes/FlexMode'; +import { MACROMOLECULES_BOND_TYPES } from 'application/editor/tools/Bond'; describe('Drawing Entities Manager', () => { it('should create monomer', () => { @@ -42,6 +43,7 @@ describe('Drawing Entities Manager', () => { new Peptide(peptideMonomerItem), new Vec2(0, 0), new Vec2(10, 10), + MACROMOLECULES_BOND_TYPES.SINGLE, ); expect(command.operations.length).toEqual(1); expect(command.operations[0]).toBeInstanceOf(PolymerBondAddOperation); @@ -64,6 +66,7 @@ describe('Drawing Entities Manager', () => { firstPeptide, new Vec2(0, 0), new Vec2(10, 10), + MACROMOLECULES_BOND_TYPES.SINGLE, ); const resultingOperations = @@ -118,6 +121,7 @@ describe('Drawing Entities Manager', () => { new Peptide(peptideMonomerItem), new Vec2(0, 0), new Vec2(10, 10), + MACROMOLECULES_BOND_TYPES.SINGLE, ); expect( Array.from(drawingEntitiesManager.polymerBonds)[0][1], diff --git a/packages/ketcher-core/src/application/editor/Editor.ts b/packages/ketcher-core/src/application/editor/Editor.ts index f9cf6d9e77..66b01f6003 100644 --- a/packages/ketcher-core/src/application/editor/Editor.ts +++ b/packages/ketcher-core/src/application/editor/Editor.ts @@ -52,6 +52,7 @@ import { Coordinates } from './shared/coordinates'; import ZoomTool from './tools/Zoom'; import { ViewModel } from 'application/render/view-model/ViewModel'; import { HandTool } from 'application/editor/tools/Hand'; +import { HydrogenBond } from 'domain/entities/HydrogenBond'; interface ICoreEditorConstructorParams { theme; @@ -223,7 +224,8 @@ export class CoreEditor { ); } else if ( eventData instanceof FlexModePolymerBondRenderer || - eventData instanceof SnakeModePolymerBondRenderer + (eventData instanceof SnakeModePolymerBondRenderer && + !(eventData.polymerBond instanceof HydrogenBond)) ) { this.events.rightClickPolymerBond.dispatch(event, eventData); } else if (isClickOnCanvas) { @@ -317,7 +319,7 @@ export class CoreEditor { } } - public onSelectTool(tool: ToolName, options?: {}) { + public onSelectTool(tool: ToolName, options?: object) { this.selectTool(tool, options); } @@ -433,7 +435,6 @@ export class CoreEditor { } public selectTool(name: ToolName, options?) { - console.log(options); const ToolConstructor: ToolConstructorInterface = toolsMap[name]; const oldTool = this.tool; diff --git a/packages/ketcher-core/src/application/editor/tools/Bond.ts b/packages/ketcher-core/src/application/editor/tools/Bond.ts index fe9599ce9f..528ed404c7 100644 --- a/packages/ketcher-core/src/application/editor/tools/Bond.ts +++ b/packages/ketcher-core/src/application/editor/tools/Bond.ts @@ -49,7 +49,7 @@ class PolymerBond implements BaseTool { private bondRenderer?: FlexModeOrSnakeModePolymerBondRenderer; private isBondConnectionModalOpen = false; private history: EditorHistory; - private bondType: string; + private bondType: MACROMOLECULES_BOND_TYPES; constructor(private editor: CoreEditor, options: { toolName: string }) { this.editor = editor; @@ -110,6 +110,7 @@ class PolymerBond implements BaseTool { selectedRenderer.monomer, selectedRenderer.monomer.position, Coordinates.canvasToModel(this.editor.lastCursorPositionOfCanvas), + this.bondType, ); this.editor.renderersContainer.update(modelChanges); @@ -438,7 +439,10 @@ class PolymerBond implements BaseTool { } public mouseUpAtom(event) { - if (!this.bondRenderer) { + if ( + !this.bondRenderer || + this.bondType === MACROMOLECULES_BOND_TYPES.HYDROGEN + ) { return; } diff --git a/packages/ketcher-core/src/application/render/renderers/PolymerBondRenderer/SnakeModePolymerBondRenderer.ts b/packages/ketcher-core/src/application/render/renderers/PolymerBondRenderer/SnakeModePolymerBondRenderer.ts index 7cc2e2eaff..a634f312e4 100644 --- a/packages/ketcher-core/src/application/render/renderers/PolymerBondRenderer/SnakeModePolymerBondRenderer.ts +++ b/packages/ketcher-core/src/application/render/renderers/PolymerBondRenderer/SnakeModePolymerBondRenderer.ts @@ -20,6 +20,7 @@ import { PolymerBond } from 'domain/entities/PolymerBond'; import { getSugarFromRnaBase } from 'domain/helpers/monomers'; import { BaseRenderer } from '../BaseRenderer'; import { HydrogenBond } from 'domain/entities/HydrogenBond'; +import { SnakeMode } from 'application/editor'; enum LineDirection { Horizontal = 'Horizontal', @@ -125,7 +126,8 @@ export class SnakeModePolymerBondRenderer extends BaseRenderer { const cells = matrix?.polymerBondToCells.get(this.polymerBond); if ( - (this.isHydrogenBond || this.polymerBond.isSideChainConnection) && + this.polymerBond.isSideChainConnection && + (!this.isHydrogenBond || editor.mode instanceof SnakeMode) && cells ) { this.appendSideConnectionBond(rootElement, cells); @@ -755,7 +757,7 @@ export class SnakeModePolymerBondRenderer extends BaseRenderer { this.selectionElement?.remove(); if ( (this.isSnakeBond && !this.isMonomersOnSameHorizontalLine()) || - this.polymerBond.isSideChainConnection + (this.polymerBond.isSideChainConnection && this.path) ) { this.selectionElement = this.rootElement ?.insert('path', ':first-child') @@ -865,7 +867,7 @@ export class SnakeModePolymerBondRenderer extends BaseRenderer { protected appendHoverAreaElement(): void { if ( (this.isSnakeBond && !this.isMonomersOnSameHorizontalLine()) || - this.polymerBond.isSideChainConnection + (this.polymerBond.isSideChainConnection && this.path) ) { ( | undefined>( this.hoverAreaElement @@ -921,9 +923,7 @@ export class SnakeModePolymerBondRenderer extends BaseRenderer { ); } - this.bodyElement - .attr('stroke', this.isHydrogenBond ? '#333333' : '#0097A8') - .attr('pointer-events', 'none'); + this.bodyElement.attr('stroke', '#0097A8').attr('pointer-events', 'none'); if (this.polymerBond.selected && this.selectionElement) { this.selectionElement.attr('stroke', '#CCFFDD'); diff --git a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts index b67e595f6e..5811c60f21 100644 --- a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts +++ b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts @@ -537,7 +537,7 @@ export class DrawingEntitiesManager { // This check helps to avoid operations duplication if (bond.selected) return; - if (bond instanceof PolymerBond) { + if (bond instanceof PolymerBond || bond instanceof HydrogenBond) { // We need to remove connected bond when doing a group selection even if it is not selected // and mark it as selected to avoid operations duplication bond.turnOnSelection(); @@ -631,6 +631,7 @@ export class DrawingEntitiesManager { firstMonomer, startPosition, endPosition, + bondType = MACROMOLECULES_BOND_TYPES.SINGLE, _polymerBond?: PolymerBond, ) { if (_polymerBond) { @@ -638,7 +639,10 @@ export class DrawingEntitiesManager { return _polymerBond; } - const polymerBond = new PolymerBond(firstMonomer); + const polymerBond = + bondType === MACROMOLECULES_BOND_TYPES.HYDROGEN + ? new HydrogenBond(firstMonomer) + : new PolymerBond(firstMonomer); this.polymerBonds.set(polymerBond.id, polymerBond); // If we started from a specific AP, we need to 'attach' the bond to the first monomer if (firstMonomer.chosenFirstAttachmentPointForBond) { @@ -651,7 +655,12 @@ export class DrawingEntitiesManager { return polymerBond; } - public startPolymerBondCreation(firstMonomer, startPosition, endPosition) { + public startPolymerBondCreation( + firstMonomer: BaseMonomer, + startPosition: Vec2, + endPosition: Vec2, + bondType: MACROMOLECULES_BOND_TYPES, + ) { const command = new Command(); const operation = new PolymerBondAddOperation( @@ -660,6 +669,7 @@ export class DrawingEntitiesManager { firstMonomer, startPosition, endPosition, + bondType, ), this.deletePolymerBondChangeModel.bind(this), ); @@ -692,7 +702,7 @@ export class DrawingEntitiesManager { } } - public deletePolymerBond(polymerBond: PolymerBond) { + public deletePolymerBond(polymerBond: PolymerBond | HydrogenBond) { const command = new Command(); const operation = new PolymerBondDeleteOperation( @@ -1020,12 +1030,15 @@ export class DrawingEntitiesManager { polymerBond.turnOnHover(); polymerBond.firstMonomer.turnOnHover(); - polymerBond.firstMonomer.turnOnAttachmentPointsVisibility(); assert(polymerBond.secondMonomer); polymerBond.secondMonomer.turnOnHover(); - polymerBond.secondMonomer.turnOnAttachmentPointsVisibility(); + + if (!(polymerBond instanceof HydrogenBond)) { + polymerBond.firstMonomer.turnOnAttachmentPointsVisibility(); + polymerBond.secondMonomer.turnOnAttachmentPointsVisibility(); + } const operation = new PolymerBondShowInfoOperation(polymerBond); diff --git a/packages/ketcher-core/src/domain/entities/index.ts b/packages/ketcher-core/src/domain/entities/index.ts index e97692bb57..d3165f33b9 100644 --- a/packages/ketcher-core/src/domain/entities/index.ts +++ b/packages/ketcher-core/src/domain/entities/index.ts @@ -58,3 +58,4 @@ export * from './UnsplitNucleotide'; export * from './PolymerBond'; export * from './AmbiguousMonomer'; export * from './MonomerToAtomBond'; +export * from './HydrogenBond'; diff --git a/packages/ketcher-macromolecules/src/EditorEvents.tsx b/packages/ketcher-macromolecules/src/EditorEvents.tsx index 67eda724d1..b8211192b6 100644 --- a/packages/ketcher-macromolecules/src/EditorEvents.tsx +++ b/packages/ketcher-macromolecules/src/EditorEvents.tsx @@ -33,6 +33,7 @@ import { Nucleoside, Nucleotide, PolymerBond, + HydrogenBond, } from 'ketcher-core'; import { selectAllPresets } from 'state/rna-builder'; import { @@ -164,7 +165,10 @@ export const EditorEvents = () => { (e) => { const polymerBond = e.target.__data__?.polymerBond; - if (polymerBond && !polymerBond.finished) { + if ( + (polymerBond && !polymerBond.finished) || + polymerBond instanceof HydrogenBond + ) { return; } diff --git a/packages/ketcher-macromolecules/src/components/LeftMenuComponent/LeftMenuComponent.tsx b/packages/ketcher-macromolecules/src/components/LeftMenuComponent/LeftMenuComponent.tsx index 60faf83894..c3c58bf5dd 100644 --- a/packages/ketcher-macromolecules/src/components/LeftMenuComponent/LeftMenuComponent.tsx +++ b/packages/ketcher-macromolecules/src/components/LeftMenuComponent/LeftMenuComponent.tsx @@ -60,7 +60,7 @@ export function LeftMenuComponent() { ` display: flex; position: absolute; left: ${({ isVertical }) => (isVertical ? '-34px' : '5px')}; - top: ${({ isVertical }) => (isVertical ? '38px' : '0')}; + top: ${({ isVertical }) => (isVertical ? '38px' : '-19px')}; border-radius: 4px; flex-direction: ${({ isVertical }) => (isVertical ? 'column' : 'row')}; z-index: 1;