Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backmerge: #6068 - Same chain configuration imported by different HELM layouted differently (anyway - both are wrong) #6111

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import {
pasteFromClipboardAndAddToCanvas,
selectTopPanelButton,
TopPanelButton,
moveMouseAway,
} from '@utils';
import { clickOnFileFormatDropdown, getCml } from '@utils/formats';

@@ -76,6 +77,7 @@ test.describe('Reagents CML format', () => {
page,
);
await saveFileAsCmlFormat(page);
await moveMouseAway(page);
await takeEditorScreenshot(page);
});

6 changes: 6 additions & 0 deletions packages/ketcher-core/src/application/editor/tools/Erase.ts
Original file line number Diff line number Diff line change
@@ -30,6 +30,9 @@ class EraserTool implements BaseTool {
) {
const modelChanges =
this.editor.drawingEntitiesManager.deleteSelectedEntities();
modelChanges.merge(
this.editor.drawingEntitiesManager.recalculateAntisenseChains(),
);
this.history.update(modelChanges);
this.editor.renderersContainer.update(modelChanges);
}
@@ -47,6 +50,9 @@ class EraserTool implements BaseTool {
this.editor.drawingEntitiesManager.deleteDrawingEntity(
selectedItemRenderer.drawingEntity,
);
modelChanges.merge(
this.editor.drawingEntitiesManager.recalculateAntisenseChains(),
);
this.history.update(modelChanges);
this.editor.renderersContainer.update(modelChanges);
}
Original file line number Diff line number Diff line change
@@ -225,7 +225,17 @@ export class SnakeModePolymerBondRenderer extends BaseRenderer {
) as Connection;
const isVerticalConnection = firstCellConnection.isVertical;
const isStraightVerticalConnection =
cells.length === 2 && isVerticalConnection;
(cells.length === 2 ||
cells.reduce(
(isStraight: boolean, cell: Cell, index: number): boolean => {
if (!isStraight || index === 0 || index === cells.length - 1) {
return isStraight;
}
return cell.x === firstCell.x && !cell.monomer;
},
true,
)) &&
isVerticalConnection;
const isFirstMonomerOfBondInFirstCell = firstCell.node?.monomers.includes(
this.polymerBond.firstMonomer,
);
116 changes: 86 additions & 30 deletions packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts
Original file line number Diff line number Diff line change
@@ -50,6 +50,7 @@ import {
import { Coordinates, CoreEditor } from 'application/editor/internal';
import {
getNextMonomerInChain,
getSugarFromRnaBase,
isAmbiguousMonomerLibraryItem,
isRnaBaseOrAmbiguousRnaBase,
isValidNucleoside,
@@ -864,6 +865,8 @@ export class DrawingEntitiesManager {
command.merge(this.recalculateCanvasMatrix());
}

command.merge(this.recalculateAntisenseChains());

return command;
}

@@ -1546,6 +1549,7 @@ export class DrawingEntitiesManager {
private calculateSnakeLayoutMatrix(chainsCollection: ChainsCollection) {
const snakeLayoutMatrix = new Matrix<Cell>();
const monomersGroupedByY = new Map<number, Map<number, BaseMonomer>>();
const monomerToNode = chainsCollection.monomerToNode;

this.monomers.forEach((monomer) => {
const x = Number(monomer.position.x.toFixed());
@@ -1596,7 +1600,7 @@ export class DrawingEntitiesManager {
Number(indexY),
Number(monomerXToIndexInMatrix[x]),
new Cell(
chainsCollection.monomerToNode.get(monomer),
monomerToNode.get(monomer),
[],
Number(indexY),
Number(monomerXToIndexInMatrix[x]),
@@ -1642,26 +1646,27 @@ export class DrawingEntitiesManager {
return;
}

const antisenseChains = [
...chainsCollection.getComplementaryChains(chain),
];
const antisenseChainsStartIndexes = antisenseChains.map(
(antisenseChain) => {
const complimentaryChainsWithData =
chainsCollection.getComplimentaryChainsWithData(chain);
const antisenseChainsWithData = complimentaryChainsWithData.filter(
(complimentaryChainWithData) =>
complimentaryChainWithData.complimentaryChain.firstMonomer
?.monomerItem.isAntisense,
);
const antisenseChainsStartIndexes = antisenseChainsWithData.map(
(antisenseChainWithData) => {
const firstConnectedAntisenseNodeIndex =
antisenseChain.nodes.findIndex((node) => {
return (
(node instanceof Nucleoside || node instanceof Nucleotide) &&
node.getAntisenseRnaBase()
);
});
const firstConnectedAntisenseNode = antisenseChain.nodes[
firstConnectedAntisenseNodeIndex
] as Nucleoside | Nucleotide;
const senseRnaBase =
firstConnectedAntisenseNode.getAntisenseRnaBase();
const senseNode =
senseRnaBase && chainsCollection.monomerToNode.get(senseRnaBase);
const senseNodeIndex = senseNode && chain.nodes.indexOf(senseNode);
antisenseChainWithData.complimentaryChain.nodes.findIndex(
(node) => {
return (
node ===
antisenseChainWithData.firstConnectedComplimentaryNode
);
},
);
const senseNodeIndex = chain.nodes.indexOf(
antisenseChainWithData.firstConnectedNode,
);

if (!isNumber(senseNodeIndex)) {
return -1;
@@ -1674,25 +1679,32 @@ export class DrawingEntitiesManager {
antisenseChainsStartIndexes.map(
(antisenseChainsStartIndex, index) => [
antisenseChainsStartIndex,
antisenseChains[index],
antisenseChainsWithData[index],
],
),
);

let restOfRowsWithAntisense = 0;
let isPreviousChainWithAntisense = false;

chain.forEachNode(({ node, nodeIndex }) => {
if (rearrangedMonomersSet.has(node.monomer.id)) {
for (
let nodeIndex = Math.min(0, ...antisenseChainsStartIndexes);
nodeIndex < chain.length;
nodeIndex++
) {
const node = chain.nodes[nodeIndex];

if (node && rearrangedMonomersSet.has(node.monomer.id)) {
return;
}

const antisenseChain = antisenseChainsStartIndexesMap.get(nodeIndex);
const antisenseChainWithData =
antisenseChainsStartIndexesMap.get(nodeIndex);

if (antisenseChain) {
if (antisenseChainWithData) {
const { rowsUsedByAntisense, command: rearrangedAntisenseCommand } =
this.rearrangeAntisenseChain(
antisenseChain,
antisenseChainWithData.complimentaryChain,
lastPosition,
canvasWidth,
rearrangedMonomersSet,
@@ -1705,6 +1717,10 @@ export class DrawingEntitiesManager {
isPreviousChainWithAntisense = true;
}

if (!node) {
continue;
}

const r2PolymerBond =
node.lastMonomerInNode.attachmentPointsToBonds[
AttachmentPointName.R2
@@ -1757,7 +1773,8 @@ export class DrawingEntitiesManager {
command.merge(rearrangeResult.command);
});
}
});
}

lastPosition = getFirstPosition(maxVerticalDistance, lastPosition);
maxVerticalDistance = 0;

@@ -2641,15 +2658,54 @@ export class DrawingEntitiesManager {
const senseToAntisenseChains = new Map<Chain, Chain[]>();
const handledChains = new Set<Chain>();

this.monomers.forEach((monomer) => {
command.merge(
this.modifyMonomerItem(monomer, {
...monomer.monomerItem,
isAntisense: false,
isSense: false,
}),
);
});

chainsCollection.chains.forEach((chain) => {
if (handledChains.has(chain)) {
return;
}

let senseChain: Chain;
const complementaryChains =
chainsCollection.getComplementaryChains(chain);
const chainsToCheck = new Set(complementaryChains).add(chain);
const complimentaryChainsWithData =
chainsCollection.getComplimentaryChainsWithData(chain);
const chainsToCheck = new Set<Chain>();

complimentaryChainsWithData.forEach((complimentaryChainWithData) => {
const hasHydrogenBondWithRnaBase =
complimentaryChainWithData.complimentaryChain.monomers.some(
(monomer) => {
return (
(monomer instanceof RNABase &&
Boolean(getSugarFromRnaBase(monomer)) &&
monomer.hydrogenBonds.length > 0) ||
monomer.hydrogenBonds.some((hydrogenBond) => {
const anotherMonomer =
hydrogenBond.getAnotherMonomer(monomer);

return (
anotherMonomer instanceof RNABase &&
Boolean(getSugarFromRnaBase(anotherMonomer))
);
})
);
},
);

if (hasHydrogenBondWithRnaBase) {
chainsToCheck.add(complimentaryChainWithData.complimentaryChain);
}
});

chainsToCheck.add(chain);

const chainToMonomers = new Map<Chain, BaseMonomer[]>();

chainsToCheck.forEach((chainToCheck) => {
19 changes: 0 additions & 19 deletions packages/ketcher-core/src/domain/entities/Nucleoside.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ import assert from 'assert';
import {
getNextMonomerInChain,
getRnaBaseFromSugar,
getSugarFromRnaBase,
isValidNucleoside,
isValidNucleotide,
} from 'domain/helpers/monomers';
@@ -126,22 +125,4 @@ export class Nucleoside {
this.rnaBase.isModification || this.sugar.isModification || isNotLastNode
);
}

public getAntisenseRnaBase() {
const hydrogenBondToAntisenseNode = this.rnaBase.hydrogenBonds.find(
(hydrogenBond) => {
const anotherMonomer = hydrogenBond.getAnotherMonomer(this.rnaBase);

return (
anotherMonomer instanceof RNABase &&
getSugarFromRnaBase(anotherMonomer)
);
},
);
const antisenseRnaBase = hydrogenBondToAntisenseNode?.getAnotherMonomer(
this.rnaBase,
);

return antisenseRnaBase;
}
}
5 changes: 0 additions & 5 deletions packages/ketcher-core/src/domain/entities/Nucleotide.ts
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@ import { AmbiguousMonomer } from 'domain/entities/AmbiguousMonomer';
import { RNA_MONOMER_DISTANCE } from 'application/editor/tools/RnaPreset';
import { SugarRenderer } from 'application/render';
import { SNAKE_LAYOUT_CELL_WIDTH } from 'domain/entities/DrawingEntitiesManager';
import { Nucleoside } from 'domain/entities/Nucleoside';

export class Nucleotide {
constructor(
@@ -124,8 +123,4 @@ export class Nucleotide {
this.phosphate.isModification
);
}

public getAntisenseRnaBase() {
return Nucleoside.prototype.getAntisenseRnaBase.call(this);
}
}
Original file line number Diff line number Diff line change
@@ -4,8 +4,6 @@ import {
BaseMonomer,
Chem,
IsChainCycled,
Nucleoside,
Nucleotide,
Peptide,
Phosphate,
RNABase,
@@ -24,6 +22,13 @@ import { BaseSubChain } from 'domain/entities/monomer-chains/BaseSubChain';
import { MonomerToAtomBond } from 'domain/entities/MonomerToAtomBond';
import { isMonomerSgroupWithAttachmentPoints } from '../../../utilities/monomers';

export interface ComplimentaryChainsWithData {
complimentaryChain: Chain;
chain: Chain;
firstConnectedNode: SubChainNode;
firstConnectedComplimentaryNode: SubChainNode;
}

export class ChainsCollection {
public chains: Chain[] = [];

@@ -305,26 +310,48 @@ export class ChainsCollection {
});
}

public getComplementaryChains(chain: Chain) {
const complementaryChains: Set<Chain> = new Set();
const monomerToChain = this.monomerToChain;
private getFirstAntisenseMonomerInNode(node: SubChainNode) {
for (let i = 0; i < node.monomers.length; i++) {
const monomer = node.monomers[i];
const hydrogenBond = monomer.hydrogenBonds[0];

chain.forEachNode(({ node }) => {
if (!(node instanceof Nucleotide || node instanceof Nucleoside)) {
return;
if (hydrogenBond) {
return hydrogenBond.getAnotherMonomer(monomer);
}
}

return undefined;
}

public getComplimentaryChainsWithData(chain: Chain) {
const complimentaryChainsWithData: ComplimentaryChainsWithData[] = [];
const handledChains = new Set<Chain>();
const monomerToChain = this.monomerToChain;

const complementaryRnaBase = node.getAntisenseRnaBase();
const complementaryChain =
complementaryRnaBase && monomerToChain.get(complementaryRnaBase);
chain.forEachNode(({ node }) => {
const complimentaryMonomer = this.getFirstAntisenseMonomerInNode(node);
const complimentaryNode =
complimentaryMonomer && this.monomerToNode.get(complimentaryMonomer);
const complimentaryChain =
complimentaryMonomer && monomerToChain.get(complimentaryMonomer);

if (!complementaryChain) {
if (
!complimentaryNode ||
!complimentaryChain ||
handledChains.has(complimentaryChain)
) {
return;
}

complementaryChains.add(complementaryChain);
handledChains.add(complimentaryChain);
complimentaryChainsWithData.push({
complimentaryChain,
chain,
firstConnectedNode: node,
firstConnectedComplimentaryNode: complimentaryNode,
});
});

return complementaryChains;
return complimentaryChainsWithData;
}
}