function fromTemplateOnBond()

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];
}