in packages/ketcher-core/src/application/editor/actions/template.ts [271:430]
function fromTemplateOnBond(restruct, template, bid, flip, isPreview = false) {
// TODO: refactor function !!
const action = new Action();
const tmpl = template.molecule;
const struct = restruct.molecule;
const bond = struct.bonds.get(bid);
const tmplBond = tmpl.bonds.get(template.bid);
const tmplBegin = tmpl.atoms.get(flip ? tmplBond.end : tmplBond.begin);
const atomsMap = new Map([
[tmplBond.begin, flip ? bond.end : bond.begin],
[tmplBond.end, flip ? bond.begin : bond.end],
]);
// calc angle
const bondAtoms = {
begin: flip ? tmplBond.end : tmplBond.begin,
end: flip ? tmplBond.begin : tmplBond.end,
};
const { angle, scale } = utils.mergeBondsParams(
struct,
bond,
tmpl,
bondAtoms,
);
const frid = struct.getBondFragment(bid);
/* For merge */
const pasteItems: any = {
// only atoms and bonds now
atoms: [],
bonds: [],
};
/* ----- */
tmpl.atoms.forEach((atom, id) => {
const attrs: any = Atom.getAttrHash(atom);
attrs.fragment = frid;
if (id === tmplBond.begin || id === tmplBond.end) {
action.mergeWith(fromAtomsAttrs(restruct, atomsMap.get(id), attrs, true));
return;
}
const v = Vec2.diff(atom.pp, tmplBegin.pp)
.rotate(angle)
.scaled(scale)
.add(struct.atoms.get(bond.begin).pp);
const mergeA = closest.atom(restruct, v, null, 0.1);
if (mergeA === null) {
const operation = new AtomAdd(attrs, v).perform(restruct) as AtomAdd;
action.addOp(operation);
atomsMap.set(id, operation.data.aid);
pasteItems.atoms.push(operation.data.aid);
} else {
atomsMap.set(id, mergeA.id);
action.mergeWith(fromAtomsAttrs(restruct, atomsMap.get(id), attrs, true));
// TODO [RB] need to merge fragments?
}
});
mergeSgroups(action, restruct, pasteItems.atoms, bond.begin);
// When a template of "Benzene" molecule is attached it
// uses specific fusing rules when attaching to a bond
// that is connected exactly to one bond on each side.
// For more info please refer to: https://github.com/epam/ketcher/issues/1855
const fusingBondType = getConnectingBond(tmpl, struct, bid, bond);
const isFusingBenzeneBySpecialRules = fusingBondType !== null;
tmpl.bonds.forEach((tBond, tBondIndex) => {
const existId = struct.findBondId(
atomsMap.get(tBond.begin),
atomsMap.get(tBond.end),
);
let previewBondId = null;
if (existId === null) {
const operation = new BondAdd(
atomsMap.get(tBond.begin),
atomsMap.get(tBond.end),
tBond,
).perform(restruct) as BondAdd;
action.addOp(operation);
const newBondId = operation.data.bid;
previewBondId = newBondId;
if (isFusingBenzeneBySpecialRules) {
const isBenzeneTemplate = tmpl.name === benzeneMoleculeName;
const isCyclopentadieneTemplate =
tmpl.name === cyclopentadieneMoleculeName;
if (isBenzeneTemplate) {
const newBondType = benzeneDoubleBondIndexes.includes(tBondIndex)
? Bond.PATTERN.TYPE.DOUBLE
: Bond.PATTERN.TYPE.SINGLE;
action.addOp(
new BondAttr(newBondId, 'type', newBondType).perform(restruct),
);
}
if (isCyclopentadieneTemplate) {
const { beginBondIds, endBondIds } = Bond.getBondNeighbourIds(
struct,
bid,
);
const bondBegin = struct.bonds.get(beginBondIds[0])!;
const bondEnd = struct.bonds.get(endBondIds[0])!;
const newBondType = Bond.getCyclopentadieneDoubleBondIndexes(
bond,
bondBegin,
bondEnd,
).includes(tBondIndex)
? Bond.PATTERN.TYPE.DOUBLE
: Bond.PATTERN.TYPE.SINGLE;
action.addOp(
new BondAttr(newBondId, 'type', newBondType).perform(restruct),
);
}
}
pasteItems.bonds.push(newBondId);
} else {
const commonBond = bond.type > tmplBond.type ? bond : tmplBond;
action.mergeWith(fromBondsAttrs(restruct, existId, commonBond, true));
if (isFusingBenzeneBySpecialRules && fusingBondType) {
action.addOp(
new BondAttr(bid, 'type', fusingBondType).perform(restruct),
);
}
previewBondId = bid;
}
action.addOp(
new BondAttr(previewBondId, 'isPreview', isPreview).perform(restruct),
);
});
if (pasteItems.atoms.length) {
action.addOp(
new CalcImplicitH([bond.begin, bond.end, ...pasteItems.atoms]).perform(
restruct,
),
);
}
if (pasteItems.bonds.length) {
action.mergeWith(
fromBondStereoUpdate(
restruct,
restruct.molecule.bonds.get(pasteItems.bonds[0]),
),
);
}
action.operations.reverse();
return [action, pasteItems];
}