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
Show file tree
Hide file tree
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
Expand Up @@ -10,6 +10,7 @@ import {
pasteFromClipboardAndAddToCanvas,
selectTopPanelButton,
TopPanelButton,
moveMouseAway,
} from '@utils';
import { clickOnFileFormatDropdown, getCml } from '@utils/formats';

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

Expand Down
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
Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
Expand Down
116 changes: 86 additions & 30 deletions packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
import { Coordinates, CoreEditor } from 'application/editor/internal';
import {
getNextMonomerInChain,
getSugarFromRnaBase,
isAmbiguousMonomerLibraryItem,
isRnaBaseOrAmbiguousRnaBase,
isValidNucleoside,
Expand Down Expand Up @@ -864,6 +865,8 @@ export class DrawingEntitiesManager {
command.merge(this.recalculateCanvasMatrix());
}

command.merge(this.recalculateAntisenseChains());

return command;
}

Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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]),
Expand Down Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -1705,6 +1717,10 @@ export class DrawingEntitiesManager {
isPreviousChainWithAntisense = true;
}

if (!node) {
continue;
}

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

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

Expand Down Expand Up @@ -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) => {
Expand Down
19 changes: 0 additions & 19 deletions packages/ketcher-core/src/domain/entities/Nucleoside.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import assert from 'assert';
import {
getNextMonomerInChain,
getRnaBaseFromSugar,
getSugarFromRnaBase,
isValidNucleoside,
isValidNucleotide,
} from 'domain/helpers/monomers';
Expand Down Expand Up @@ -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
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Up @@ -4,8 +4,6 @@ import {
BaseMonomer,
Chem,
IsChainCycled,
Nucleoside,
Nucleotide,
Peptide,
Phosphate,
RNABase,
Expand All @@ -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[] = [];

Expand Down Expand Up @@ -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;
}
}
Loading