public applySnakeLayout()

in packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts [1615:1806]


  public applySnakeLayout(
    canvasWidth: number,
    isSnakeMode: boolean,
    needRedrawBonds = true,
    needRepositionMonomers = true,
  ) {
    if (this.monomers.size === 0) {
      return new Command();
    }

    const previousSnakeLayoutMatrix = this.snakeLayoutMatrix;
    const command = new Command();
    let chainsCollection: ChainsCollection;

    command.merge(this.recalculateAntisenseChains());

    if (isSnakeMode) {
      const rearrangedMonomersSet: Set<number> = new Set();
      let lastPosition = new Vec2({
        x: MONOMER_START_X_POSITION,
        y: MONOMER_START_Y_POSITION,
      });
      let maxVerticalDistance = 0;
      chainsCollection = ChainsCollection.fromMonomers([
        ...this.monomers.values(),
      ]);
      chainsCollection.rearrange();

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

        const complimentaryChainsWithData =
          chainsCollection.getComplimentaryChainsWithData(chain);
        const antisenseChainsWithData = complimentaryChainsWithData.filter(
          (complimentaryChainWithData) =>
            complimentaryChainWithData.complimentaryChain.firstMonomer
              ?.monomerItem.isAntisense,
        );
        const antisenseChainsStartIndexes = antisenseChainsWithData.map(
          (antisenseChainWithData) => {
            const firstConnectedAntisenseNodeIndex =
              antisenseChainWithData.complimentaryChain.nodes.findIndex(
                (node) => {
                  return (
                    node ===
                    antisenseChainWithData.firstConnectedComplimentaryNode
                  );
                },
              );
            const senseNodeIndex = chain.nodes.indexOf(
              antisenseChainWithData.firstConnectedNode,
            );

            if (!isNumber(senseNodeIndex)) {
              return -1;
            }

            return senseNodeIndex - firstConnectedAntisenseNodeIndex;
          },
        );
        const antisenseChainsStartIndexesMap = new Map(
          antisenseChainsStartIndexes.map(
            (antisenseChainsStartIndex, index) => [
              antisenseChainsStartIndex,
              antisenseChainsWithData[index],
            ],
          ),
        );

        let restOfRowsWithAntisense = 0;
        let isPreviousChainWithAntisense = false;

        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 antisenseChainWithData =
            antisenseChainsStartIndexesMap.get(nodeIndex);

          if (antisenseChainWithData) {
            const { rowsUsedByAntisense, command: rearrangedAntisenseCommand } =
              this.rearrangeAntisenseChain(
                antisenseChainWithData.complimentaryChain,
                lastPosition,
                canvasWidth,
                rearrangedMonomersSet,
                maxVerticalDistance,
                needRepositionMonomers,
              );

            restOfRowsWithAntisense = rowsUsedByAntisense;
            command.merge(rearrangedAntisenseCommand);
            isPreviousChainWithAntisense = true;
          }

          if (!node) {
            continue;
          }

          const r2PolymerBond =
            node.lastMonomerInNode.attachmentPointsToBonds[
              AttachmentPointName.R2
            ];

          if (r2PolymerBond instanceof PolymerBond) {
            r2PolymerBond.restOfRowsWithAntisense = restOfRowsWithAntisense;
          }

          if (node instanceof Nucleoside || node instanceof Nucleotide) {
            const rearrangeResult = this.reArrangeRnaChain(
              node,
              lastPosition,
              canvasWidth,
              rearrangedMonomersSet,
              maxVerticalDistance,
              restOfRowsWithAntisense,
              false,
              needRepositionMonomers,
            );

            if (
              rearrangeResult.lastPosition.y > lastPosition.y &&
              lastPosition
            ) {
              restOfRowsWithAntisense--;
            }

            lastPosition = rearrangeResult.lastPosition;
            maxVerticalDistance = rearrangeResult.maxVerticalDistance;
            command.merge(rearrangeResult.command);
          } else {
            node.monomers.forEach((monomer) => {
              const rearrangeResult = this.reArrangeChain(
                monomer,
                lastPosition,
                canvasWidth,
                rearrangedMonomersSet,
                maxVerticalDistance,
                restOfRowsWithAntisense,
                needRepositionMonomers,
              );

              if (rearrangeResult.lastPosition.y > lastPosition.y) {
                restOfRowsWithAntisense--;
              }

              lastPosition = rearrangeResult.lastPosition;
              maxVerticalDistance = rearrangeResult.maxVerticalDistance;
              command.merge(rearrangeResult.command);
            });
          }
        }

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

        if (isPreviousChainWithAntisense) {
          lastPosition = lastPosition.add(
            new Vec2(0, SNAKE_LAYOUT_Y_OFFSET_BETWEEN_CHAINS),
          );
          isPreviousChainWithAntisense = false;
        }
      });

      const snakeLayoutMatrix =
        this.calculateSnakeLayoutMatrix(chainsCollection);

      this.snakeLayoutMatrix = snakeLayoutMatrix;

      command.merge(
        this.recalculateCanvasMatrix(
          chainsCollection,
          previousSnakeLayoutMatrix,
        ),
      );
    }

    if (needRedrawBonds) {
      command.merge(this.redrawBonds());
    }

    return command;
  }