diff --git a/packages/ketcher-core/src/application/editor/Editor.ts b/packages/ketcher-core/src/application/editor/Editor.ts index f5c545c9eb..39a27c1498 100644 --- a/packages/ketcher-core/src/application/editor/Editor.ts +++ b/packages/ketcher-core/src/application/editor/Editor.ts @@ -73,10 +73,6 @@ interface ICoreEditorConstructorParams { monomersLibraryUpdate?: string | JSON; } -function isMouseMainButtonPressed(event: MouseEvent) { - return event.button === 0; -} - let persistentMonomersLibrary: MonomerItemType[] = []; let persistentMonomersLibraryParsedJson: IKetMacromoleculesContent | null = null; @@ -92,6 +88,7 @@ export class CoreEditor { public viewModel: ViewModel; public lastCursorPosition: Vec2 = new Vec2(0, 0); public lastCursorPositionOfCanvas: Vec2 = new Vec2(0, 0); + public isMouseMainButtonPressed = false; private _monomersLibraryParsedJson: IKetMacromoleculesContent | null = null; private _monomersLibrary: MonomerItemType[] = []; public canvas: SVGSVGElement; @@ -678,10 +675,11 @@ export class CoreEditor { subs.add((event) => { this.updateLastCursorPosition(event); + this.isMouseMainButtonPressed = event.button === 0; if ( ['mouseup', 'mousedown', 'click', 'dbclick'].includes(event.type) && - !isMouseMainButtonPressed(event) + !this.isMouseMainButtonPressed ) { return true; } diff --git a/packages/ketcher-core/src/application/editor/tools/Bond.ts b/packages/ketcher-core/src/application/editor/tools/Bond.ts index 7c476d6f45..055e321b6f 100644 --- a/packages/ketcher-core/src/application/editor/tools/Bond.ts +++ b/packages/ketcher-core/src/application/editor/tools/Bond.ts @@ -320,6 +320,11 @@ class PolymerBond implements BaseTool { } const modelChanges = this.finishBondCreation(renderer.monomer); this.history.update(modelChanges); + if (modelChanges.operations[0]?.polymerBond) { + this.editor.drawingEntitiesManager.detectBondsOverlappedByMonomers([ + modelChanges.operations[0].polymerBond, + ]); + } this.editor.renderersContainer.update(modelChanges); this.editor.renderersContainer.deletePolymerBond( this.bondRenderer.polymerBond, @@ -377,6 +382,7 @@ class PolymerBond implements BaseTool { 'You have connected monomers with attachment points of the same group', ); } + return this.editor.drawingEntitiesManager.finishPolymerBondCreation( this.bondRenderer.polymerBond, secondMonomer, @@ -437,6 +443,11 @@ class PolymerBond implements BaseTool { // This logic so far is only for no-modal connections. Maybe then we can chain it after modal invoke const modelChanges = this.finishBondCreation(renderer.monomer); + if (modelChanges.operations[0]?.polymerBond) { + this.editor.drawingEntitiesManager.detectBondsOverlappedByMonomers([ + modelChanges.operations[0].polymerBond, + ]); + } this.editor.renderersContainer.update(modelChanges); this.editor.renderersContainer.deletePolymerBond( this.bondRenderer.polymerBond, diff --git a/packages/ketcher-core/src/application/editor/tools/SelectRectangle.ts b/packages/ketcher-core/src/application/editor/tools/SelectRectangle.ts index d4d26d59b8..d47220585e 100644 --- a/packages/ketcher-core/src/application/editor/tools/SelectRectangle.ts +++ b/packages/ketcher-core/src/application/editor/tools/SelectRectangle.ts @@ -284,6 +284,10 @@ class SelectRectangle implements BaseTool { } public mouseOverPolymerBond(event) { + if (this.editor.isMouseMainButtonPressed) { + return; + } + const renderer: DeprecatedFlexModeOrSnakeModePolymerBondRenderer = event.target.__data__; diff --git a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts index 863707f342..218436b96d 100644 --- a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts +++ b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts @@ -119,10 +119,10 @@ type RnaPresetAdditionParams = { export class DrawingEntitiesManager { public monomers: Map = new Map(); public polymerBonds: Map = new Map(); + private bondsMonomersOverlaps: Map = new Map(); public atoms: Map = new Map(); public bonds: Map = new Map(); public monomerToAtomBonds: Map = new Map(); - public cycles: Chain[] = []; public micromoleculesHiddenEntities: Struct = new Struct(); public canvasMatrix?: CanvasMatrix; @@ -809,8 +809,6 @@ export class DrawingEntitiesManager { this.polymerBonds.set(_polymerBond.id, _polymerBond); firstMonomer.setBond(firstMonomerAttachmentPoint, _polymerBond); secondMonomer.setBond(secondMonomerAttachmentPoint, _polymerBond); - _polymerBond.isOverlappedByMonomer = - this.checkBondForOverlapsByMonomers(_polymerBond); return _polymerBond; } @@ -826,8 +824,6 @@ export class DrawingEntitiesManager { secondMonomerAttachmentPoint, polymerBond, ); - polymerBond.isOverlappedByMonomer = - this.checkBondForOverlapsByMonomers(polymerBond); polymerBond.firstMonomer.removePotentialBonds(true); polymerBond.secondMonomer.removePotentialBonds(true); @@ -2168,6 +2164,8 @@ export class DrawingEntitiesManager { command.merge(this.redrawBonds()); } + this.detectBondsOverlappedByMonomers(); + this.monomers.forEach((monomer) => { editor.renderersContainer.deleteMonomer(monomer); editor.renderersContainer.addMonomer(monomer); @@ -2189,6 +2187,10 @@ export class DrawingEntitiesManager { public rerenderBondsOverlappedByMonomers() { const editor = CoreEditor.provideEditorInstance(); + if (editor.mode instanceof SequenceMode) { + return; + } + const monomersToCheck = this.selectedEntities .filter(([, entity]) => entity instanceof BaseMonomer) .map(([, entity]) => entity as BaseMonomer); @@ -3070,6 +3072,11 @@ export class DrawingEntitiesManager { polymerBond: PolymerBond, monomers?: BaseMonomer[], ) { + const editor = CoreEditor.provideEditorInstance(); + if (editor.mode instanceof SequenceMode) { + return false; + } + const secondMonomer = polymerBond.secondMonomer; if (!secondMonomer) { return false; @@ -3080,21 +3087,41 @@ export class DrawingEntitiesManager { } const monomersToUse = monomers ?? this.monomersArray; - return monomersToUse.some((monomer) => { - if ( - monomer.id === polymerBond.firstMonomer.id || - monomer.id === secondMonomer.id - ) { - return false; - } + // Skip processing for large structures for now as in worst case its has O(n^2) complexity and may freeze the app + // Further optimization might be needed to allow that + if (monomersToUse.length > 500) { + return false; + } - const distanceFromMonomerToLine = monomer.center.calculateDistanceToLine([ - polymerBond.firstMonomer.center, - secondMonomer.center, - ]); + const previousOverlap = this.bondsMonomersOverlaps.get(polymerBond.id); + const monomersToUseWithPreviousOverlap = previousOverlap + ? [previousOverlap, ...monomersToUse] + : monomersToUse; - return distanceFromMonomerToLine < 0.375; - }); + const overlappingMonomer = monomersToUseWithPreviousOverlap.find( + (monomer) => { + if ( + monomer.id === polymerBond.firstMonomer.id || + monomer.id === secondMonomer.id + ) { + return false; + } + + const distanceFromMonomerToLine = + monomer.center.calculateDistanceToLine([ + polymerBond.firstMonomer.center, + secondMonomer.center, + ]); + + return distanceFromMonomerToLine < 0.375; + }, + ); + + if (overlappingMonomer) { + this.bondsMonomersOverlaps.set(polymerBond.id, overlappingMonomer); + } + + return Boolean(overlappingMonomer); } public detectBondsOverlappedByMonomers( diff --git a/packages/ketcher-macromolecules/src/components/modal/save/Save.test.tsx b/packages/ketcher-macromolecules/src/components/modal/save/Save.test.tsx index e330622416..2c539a6703 100644 --- a/packages/ketcher-macromolecules/src/components/modal/save/Save.test.tsx +++ b/packages/ketcher-macromolecules/src/components/modal/save/Save.test.tsx @@ -38,7 +38,7 @@ describe('Save modal', () => { mergeInto: jest.fn(), }, setMicromoleculesHiddenEntities: jest.fn(), - detectCycles: jest.fn(), + detectBondsOverlappedByMonomers: jest.fn(), monomers: [], polymerBonds: [], bonds: [],