Skip to content

Commit

Permalink
#4530 - Add ability to define attachment points for molecules
Browse files Browse the repository at this point in the history
- added conversion of molecules to superatoms without label after adding superatom attachment points
- added connections between molecules and monomers
  • Loading branch information
rrodionov91 committed May 27, 2024
1 parent 36d1161 commit 12e7e11
Show file tree
Hide file tree
Showing 19 changed files with 279 additions and 86 deletions.
12 changes: 11 additions & 1 deletion packages/ketcher-core/src/application/editor/actions/bond.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { Action } from './action';
import { ReSGroup, ReStruct } from '../../render';
import { StereoValidator } from 'domain/helpers';
import utils from '../shared/utils';
import { fromSgroupAttachmentPointRemove } from 'application/editor';

export function fromBondAddition(
reStruct: ReStruct,
Expand Down Expand Up @@ -481,10 +482,19 @@ export function removeAttachmentPointFromSuperatom(
sgroup: ReSGroup,
beginAtomId: number | undefined,
endAtomId: number | undefined,
action: Action,
restruct: ReStruct,
) {
(sgroup.item?.atoms as number[]).forEach((atomId) => {
if (beginAtomId === atomId || endAtomId === atomId) {
sgroup.item?.removeAttachmentPoint(atomId);
action.mergeWith(
fromSgroupAttachmentPointRemove(
restruct,
sgroup.item?.id as number,
atomId as number,
false,
),
);
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ function fromBondDeletion(
bid: number,
skipAtoms: Array<any> = [],
) {
let action = new Action();

if (restruct.sgroups && restruct.sgroups.size > 0) {
restruct.sgroups.forEach((sgroup) => {
if (sgroup.item?.type && sgroup.item?.type === 'SUP') {
Expand All @@ -57,12 +59,13 @@ function fromBondDeletion(
sgroup,
beginAtomConnectedToBond,
endAtomConnectedToBond,
action,
restruct,
);
}
});
}

let action = new Action();
const bond: any = restruct.molecule.bonds.get(bid);
const atomsToRemove: Array<any> = [];

Expand Down
47 changes: 40 additions & 7 deletions packages/ketcher-core/src/application/editor/actions/sgroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export function sGroupAttributeAction(id, attrs) {
return action;
}

export function fromSgroupDeletion(restruct, id) {
export function fromSgroupDeletion(restruct, id, needPerform = true) {
let action = new Action();
const struct = restruct.molecule;

Expand Down Expand Up @@ -174,7 +174,9 @@ export function fromSgroupDeletion(restruct, id) {

action.addOp(new SGroupDelete(id));

action = action.perform(restruct);
if (needPerform) {
action = action.perform(restruct);
}

action.mergeWith(sGroupAttributeAction(id, attrs));

Expand Down Expand Up @@ -457,21 +459,21 @@ export function removeAtomFromSgroupIfNeeded(action, restruct, id) {
}

// Add action operations to remove whole s-group if needed
export function removeSgroupIfNeeded(action, restruct, atoms) {
export function removeSgroupIfNeeded(action, restruct: Restruct, atoms) {
const struct = restruct.molecule;
const sgCounts = new Map();

atoms.forEach((id) => {
const sgroups = atomGetSGroups(restruct, id);
atoms.forEach((atomId) => {
const sgroups = atomGetSGroups(restruct, atomId);

sgroups.forEach((sid) => {
sgCounts.set(sid, sgCounts.has(sid) ? sgCounts.get(sid) + 1 : 1);
});
});

sgCounts.forEach((count, sid) => {
const sG = restruct.sgroups.get(sid).item;
const sgAtoms = SGroup.getAtoms(restruct.molecule, sG);
const sGroup = restruct.sgroups.get(sid)?.item;
const sgAtoms = SGroup.getAtoms(restruct.molecule, sGroup);

if (sgAtoms.length === count) {
// delete whole s-group
Expand All @@ -483,6 +485,13 @@ export function removeSgroupIfNeeded(action, restruct, atoms) {
});
action.addOp(new SGroupDelete(sid));
}

if (
sGroup?.isSuperatomWithoutLabel &&
sGroup.getAttachmentPoints().length === 0
) {
action.mergeWith(fromSgroupDeletion(restruct, sid, false));
}
});
}

Expand Down Expand Up @@ -516,3 +525,27 @@ export function fromSgroupAttachmentPointAddition(

return action;
}

export function fromSgroupAttachmentPointRemove(
restruct: Restruct,
sgroupId: number,
atomId: number,
needPerform = true,
) {
let action = new Action();
const struct = restruct.molecule;
const sgroup = struct.sgroups.get(sgroupId);
const attachmentPoint = sgroup
?.getAttachmentPoints()
.find((attachmentPoint) => attachmentPoint.atomId === atomId);

if (sgroup && attachmentPoint) {
action.addOp(new SGroupAttachmentPointRemove(sgroupId, attachmentPoint));
}

if (needPerform) {
action = action.perform(restruct);
}

return action;
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ class SGroupAtomAdd extends BaseOperation {
const sgroup = struct.sgroups.get(sgid)!;

if (sgroup.atoms.indexOf(aid) >= 0) {
throw new Error(
'The same atom cannot be added to an S-group more than once',
);
return;
}

if (!atom) {
Expand Down
12 changes: 10 additions & 2 deletions packages/ketcher-core/src/application/formatters/types/ket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,20 @@ export interface IKetGroupNode {

export type KetNode = IKetMonomerNode | IKetGroupNode;

export interface IKetConnectionEndPoint {
monomerId?: string;
export interface IKetConnectionMonomerEndPoint {
monomerId: string;
attachmentPointId?: string;
groupId?: string;
}

export interface IKetConnectionMoleculeEndPoint {
moleculeId: string;
atomId: number;
}

export type IKetConnectionEndPoint = IKetConnectionMonomerEndPoint &
IKetConnectionMoleculeEndPoint;

export enum KetConnectionType {
SINGLE = 'single',
HYDROGEN = 'hydrogen',
Expand Down
6 changes: 1 addition & 5 deletions packages/ketcher-core/src/domain/entities/BaseMonomer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import { AttachmentPointName, MonomerItemType } from 'domain/types';
import { PolymerBond } from 'domain/entities/PolymerBond';
import { BaseMonomerRenderer } from 'application/render/renderers/BaseMonomerRenderer';
import { BaseRenderer } from 'application/render/renderers/BaseRenderer';
import {
getAttachmentPointLabel,
getAttachmentPointLabelWithBinaryShift,
} from 'domain/helpers/attachmentPointCalculations';
import { getAttachmentPointLabel } from 'domain/helpers/attachmentPointCalculations';
import assert from 'assert';
import {
IKetAttachmentPoint,
Expand Down Expand Up @@ -315,7 +312,6 @@ export abstract class BaseMonomer extends DrawingEntity {
private getAttachmentPointDict(): Partial<
Record<AttachmentPointName, PolymerBond | null>
> {
console.log(this.monomerItem.attachmentPoints);
if (this.monomerItem.attachmentPoints) {
const { attachmentPointDictionary } =
BaseMonomer.getAttachmentPointDictFromMonomerDefinition(
Expand Down
30 changes: 28 additions & 2 deletions packages/ketcher-core/src/domain/entities/atom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,16 @@ export class Atom extends BaseMicromoleculeEntity {
return rad + conn + Math.abs(charge);
}

public static getSuperAtomAttachmentPointByAttachmentAtom(
struct: Struct,
atomId: number,
) {
const sgroup = struct.getGroupFromAtomId(atomId);
return sgroup
?.getAttachmentPoints()
.find((attachmentPoint) => attachmentPoint.atomId === atomId);
}

public static getSuperAtomAttachmentPointByLeavingGroup(
struct: Struct,
atomId: number,
Expand All @@ -729,8 +739,24 @@ export class Atom extends BaseMicromoleculeEntity {
.find((attachmentPoint) => attachmentPoint.leaveAtomId === atomId);
}

public static isSuperatomLeavingGroupAtom(struct: Struct, atomId: number) {
return Atom.getSuperAtomAttachmentPointByLeavingGroup(struct, atomId);
public static isSuperatomLeavingGroupAtom(struct: Struct, atomId?: number) {
if (atomId === undefined) {
return false;
}

return Boolean(
Atom.getSuperAtomAttachmentPointByLeavingGroup(struct, atomId),
);
}

public static isSuperatomAttachmentAtom(struct: Struct, atomId?: number) {
if (atomId === undefined) {
return false;
}

return Boolean(
Atom.getSuperAtomAttachmentPointByAttachmentAtom(struct, atomId),
);
}

public static isAttachmentAtomHasExternalConnections(
Expand Down
7 changes: 5 additions & 2 deletions packages/ketcher-core/src/domain/entities/functionalGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ export class FunctionalGroup {
isFunctionalGroupReturned?: boolean,
): number | FunctionalGroup | null {
for (const fg of functionalGroups.values()) {
if (fg.relatedSGroup.atoms.includes(atomId))
if (
!fg.relatedSGroup.isSuperatomWithoutLabel &&
fg.relatedSGroup.atoms.includes(atomId)
)
return isFunctionalGroupReturned ? fg : fg.relatedSGroupId;
}
return null;
Expand All @@ -145,7 +148,7 @@ export class FunctionalGroup {
): FunctionalGroup | number | null {
for (const fg of functionalGroups.values()) {
const bonds = SGroup.getBonds(molecule, fg.relatedSGroup);
if (bonds.includes(bondId)) {
if (!fg.relatedSGroup.isSuperatomWithoutLabel && bonds.includes(bondId)) {
return isFunctionalGroupReturned ? fg : fg.relatedSGroupId;
}
}
Expand Down
6 changes: 6 additions & 0 deletions packages/ketcher-core/src/domain/entities/struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,12 @@ export class Struct {
return null;
}

getGroupFromBondId(atomId: number | undefined): SGroup | undefined {
const sgroupId = this.getGroupIdFromBondId(atomId as number);

return this.sgroups?.get(sgroupId as number);
}

getGroupsIdsFromBondId(bondId: number): number[] {
const bond = this.bonds.get(bondId);
if (!bond) return [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,25 @@ export function polymerBondToDrawingEntity(
secondMonomer,
connection.endpoint1.attachmentPointId ||
getAttachmentPointLabel(
firstMonomer.monomerItem.struct.sgroups
(firstMonomer.monomerItem.struct.sgroups
.get(0)
.getAttachmentPoints()
?.getAttachmentPoints()
.findIndex(
(attachmentPoint) =>
attachmentPoint.atomId ===
atomIdMap.get(connection.endpoint1.atomId),
) + 1,
) as number) + 1,
),
connection.endpoint2.attachmentPointId ||
getAttachmentPointLabel(
secondMonomer.monomerItem.struct.sgroups
(secondMonomer.monomerItem.struct.sgroups
.get(0)
.getAttachmentPoints()
?.getAttachmentPoints()
.findIndex(
(attachmentPoint) =>
attachmentPoint.atomId ===
atomIdMap.get(connection.endpoint2.atomId),
) + 1,
) as number) + 1,
),
),
);
Expand Down
26 changes: 16 additions & 10 deletions packages/ketcher-core/src/domain/serializers/ket/ketSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import { textToKet } from './toKet/textToKet';
import { textToStruct } from './fromKet/textToStruct';
import {
IKetConnection,
IKetConnectionEndPoint,
IKetConnectionMoleculeEndPoint,
IKetConnectionMonomerEndPoint,
IKetMacromoleculesContent,
IKetMacromoleculesContentRootProperty,
IKetMonomerNode,
Expand Down Expand Up @@ -469,7 +472,10 @@ export class KetSerializer implements Serializer<Struct> {
return this.deserializeToStruct(fileContent);
}

getConnectionMonomerEndpoint(monomer: BaseMonomer, polymerBond: PolymerBond) {
getConnectionMonomerEndpoint(
monomer: BaseMonomer,
polymerBond: PolymerBond,
): IKetConnectionMonomerEndPoint {
return {
monomerId: setMonomerPrefix(monomer.id),
attachmentPointId: monomer.getAttachmentPointByBond(polymerBond),
Expand All @@ -481,7 +487,7 @@ export class KetSerializer implements Serializer<Struct> {
polymerBond: PolymerBond,
monomerToAtomIdMap: Map<BaseMonomer, Map<number, number>>,
struct: Struct,
) {
): IKetConnectionMoleculeEndPoint {
const atomId = MacromoleculesConverter.findAttachmentPointAtom(
polymerBond,
monomer,
Expand Down Expand Up @@ -572,28 +578,28 @@ export class KetSerializer implements Serializer<Struct> {
connectionType: KetConnectionType.SINGLE,
endpoint1: polymerBond.firstMonomer.monomerItem.props
.isMicromoleculeFragment
? this.getConnectionMoleculeEndpoint(
? (this.getConnectionMoleculeEndpoint(
polymerBond.firstMonomer,
polymerBond,
monomerToAtomIdMap,
struct,
)
: this.getConnectionMonomerEndpoint(
) as IKetConnectionEndPoint)
: (this.getConnectionMonomerEndpoint(
polymerBond.firstMonomer,
polymerBond,
),
) as IKetConnectionEndPoint),
endpoint2: polymerBond.secondMonomer.monomerItem.props
.isMicromoleculeFragment
? this.getConnectionMoleculeEndpoint(
? (this.getConnectionMoleculeEndpoint(
polymerBond.secondMonomer,
polymerBond,
monomerToAtomIdMap,
struct,
)
: this.getConnectionMonomerEndpoint(
) as IKetConnectionEndPoint)
: (this.getConnectionMonomerEndpoint(
polymerBond.secondMonomer,
polymerBond,
),
) as IKetConnectionEndPoint),
});
});

Expand Down
Loading

0 comments on commit 12e7e11

Please sign in to comment.