Adobe Illustrator CC Merge touching Anchor points

I have a vector file containing scribbles, where the lines don’t consist of several anchor points but every segment is an own line by itself.

the blue line in the center of the image is an own element by its own

I need to connect these separate little lines to form single lines containing multiple anchor points as it usually is. The problem is, that there are far too many lines in the file to connect them manually and the “join” function always also connects lines which should not be connected.

Is there a script which only connects lines/anchor points that are at the exact same place in Illustrator?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

JOIN PATHS WITH OVERLAPPING POINTS

This script has been updated Feb 07, 2017 with some new features.

Features:

  1. Stray points are removed.
  2. Split points within paths are merged.
  3. Open paths with overlapping points are joined.

Only selected paths are affected.
Groups are supported, compound paths are not.
For overlapping paths an overlap tolerance applies.

How to use:

  • Copy the code below to a text editor and save it as a .js file (for example join_paths_with_overlapping_points.js).
  • In illustrator, select the paths (or groups) you want to clean up.
  • Select File/Scripts/Other Script, and open the script.
  • Follow the intructions.

// JOIN PATHS WITH OVERLAPPING POINTS
// Copyright (c) 2017 Mads Wolff
// This script is distributed under the MIT License.

#target illustrator

alert((function () {

  alert(
    "JOIN PATHS WITH OVERLAPPING POINTSn" +
    "Copyright (c) 2017 Mads Wolffn" +
    "This script is distributed under the MIT License.n" +
    "Use at your own risk.nnn" +
    "FEATURES:nn" +
    "1. Stray points are removed.n" +
    "2. Split points within paths are merged.n" +
    "3. Open paths with overlapping points are joined.nn" +
    "Only selected paths are affected.n" +
    "Groups are supported, compound paths are not.n" +
    "For overlapping paths an overlap tolerance applies.nnn" +
    "HOW TO USE:nn" +
    "Press OK, select an overlap tolerance (in points),n" +
    "press OK again (or CANCEL).n"
  );

  // ---

  var doc = app.activeDocument;

  // ---

  if (doc.selection.length === 0) return "JOIN PATHS WITH OVERLAPPING POINTSnnABORT: There is no selection.";
  var tolerance = prompt("JOIN PATHS WITH OVERLAPPING POINTSnnEnter overlap tolerance (in points):", 0);
  if (tolerance === null) return "JOIN PATHS WITH OVERLAPPING POINTSnnABORT: Script execution cancelled.";
  if (!(tolerance >= 0)) return "JOIN PATHS WITH OVERLAPPING POINTSnnABORT: The overlap tolerance must be a number of points equal to or larger than 0.";

  // ---

  var numberOfPoints = 0;
  var numberOfStrayPoints = 0;
  var numberOfOpenPaths = 0;
  var numberOfClosedPaths = 0;
  var numberOfSplitPointsMerged = 0;
  var numberOfOverlappingPathsJoined = 0;
  var numberOfResultingPaths = 0;

  // ---

  function execute(itemList) {

    itemList = Array.prototype.slice.call(itemList);

    var items = [];
    var overlapClusters = [];

    for (var i = 0; i < itemList.length; i++) {
      var item = itemList[i];
      if (item.typename === "GroupItem") {
        execute(item.pageItems);
      } else if (item.typename === "PathItem") {
        if (item.pathPoints.length > 1) {
          var points = Array.prototype.slice.call(item.pathPoints);
          for (var j = 0; j < points.length; j++) {
            var point = points[j];
            var nextPoint = points[j + ((!item.closed || (j + 1 < points.length)) ? 1 : 1 - points.length)];
            if (nextPoint) {
              if (
                point.anchor[0] === nextPoint.anchor[0] &&
                point.anchor[1] === nextPoint.anchor[1] &&
                point.rightDirection[0] === point.anchor[0] &&
                point.rightDirection[1] === point.anchor[1] &&
                nextPoint.leftDirection[0] === point.anchor[0] &&
                nextPoint.leftDirection[1] === point.anchor[1]
              ) {
                nextPoint.leftDirection = point.leftDirection;
                point.remove();
                numberOfSplitPointsMerged++;
              }
            }
            numberOfPoints++;
          }
        }
        if (item.pathPoints.length === 1) {
          item.remove();
          numberOfStrayPoints++;
          numberOfOpenPaths++;
        } else if (!item.closed) {
          items.push(item);
          numberOfOpenPaths++;
        } else {
          numberOfClosedPaths++;
        }
      }
    }

    numberOfOpenPaths += items.length;

    for (var i = 0; i < items.length; i++) {
      var itemA = items[i];
      var pointsA = itemA.pathPoints;
      for (var j = 0; j < pointsA.length; j++) {
        var pointA = pointsA[j];
        for (var k = i + 1; k < items.length; k++) {
          var itemB = items[k];
          var pointsB = itemB.pathPoints;
          for (var l = 0; l < pointsB.length; l++) {
            var pointB = pointsB[l];
            var overlap = tolerance === 0 ?
              (pointA.anchor[0] === pointB.anchor[0] && pointA.anchor[1] === pointB.anchor[1]) :
              (tolerance >= Math.sqrt(Math.pow(pointA.anchor[0] - pointB.anchor[0], 2) + Math.pow(pointA.anchor[1] - pointB.anchor[1], 2)));
            if (overlap) {
              if (tolerance > 0) {
                var d0 = (pointA.anchor[0] - pointB.anchor[0]) / 2;
                var d1 = (pointA.anchor[1] - pointB.anchor[1]) / 2;
                pointA.anchor = [pointA.anchor[0] - d0, pointA.anchor[1] - d1];
                pointA.leftDirection = [pointA.leftDirection[0] - d0, pointA.leftDirection[1] - d1];
                pointA.rightDirection = [pointA.rightDirection[0] - d0, pointA.rightDirection[1] - d1];
                pointB.anchor = [pointB.anchor[0] + d0, pointB.anchor[1] + d1];
                pointB.leftDirection = [pointB.leftDirection[0] + d0, pointB.leftDirection[1] + d1];
                pointB.rightDirection = [pointB.rightDirection[0] + d0, pointB.rightDirection[1] + d1];
              }
              if (itemA.overlapCluster === undefined) {
                if (itemB.overlapCluster === undefined) {
                  itemA.overlapCluster = [];
                  itemB.overlapCluster = itemA.overlapCluster;
                  itemA.overlapCluster.push(itemA);
                  itemA.overlapCluster.push(itemB);
                  overlapClusters.push(itemA.overlapCluster);
                } else {
                  itemA.overlapCluster = itemB.overlapCluster;
                  itemA.overlapCluster.push(itemA);
                }
              } else {
                itemB.overlapCluster = itemA.overlapCluster;
                itemA.overlapCluster.push(itemB);
              }
            }
          }
        }
      }
    }

    for (var i = 0; i < overlapClusters.length; i++) {
      var overlapCluster = overlapClusters[i];
      doc.selection = overlapCluster;
      numberOfOverlappingPathsJoined += doc.selection.length;
      numberOfResultingPaths++;
      app.executeMenuCommand("join");
      var joinedItem = doc.selection[0];
      delete joinedItem.overlapCluster;
    }

  }

  // ---

  execute(doc.selection);

  // ---

  doc.selection = [];

  // ---

  return "JOIN PATHS WITH OVERLAPPING POINTSnn" +
    numberOfPoints + " points in " + (numberOfOpenPaths + numberOfClosedPaths) + " paths ("+ numberOfOpenPaths +" open and " + numberOfClosedPaths + " closed) were processed.nn" +
    numberOfStrayPoints + " stray points were removed.n" +
    numberOfSplitPointsMerged + " split points were merged.n" +
    numberOfOverlappingPathsJoined + " paths with overlapping points were joined to " + numberOfResultingPaths + " paths.";

})());

Method 2

Try this script, it works by joining paths from end point to start point, but not with paths which have the same starting and ending points. However, because joining paths changes the path count because it merges two paths into one, the start/end points are subject to change during this “connect-the-dots” algorithm.

#target illustrator
function test(){
  function overlapPointExists(){
    var doc = app.activeDocument, thisPath, firstPt, lastPt, nextPath, firstPt_2, lastPt_2;
    for(var i=0; i < doc.pathItems.length; i++){
      thisPath = doc.pathItems[i];
      if(!thisPath.closed){
        firstPt = thisPath.pathPoints[0];
        lastPt = thisPath.pathPoints[thisPath.pathPoints.length - 1];
        for(var j=0; j < doc.pathItems.length; j++){
          if(i != j){
            nextPath = doc.pathItems[j];
            firstPt_2 = nextPath.pathPoints[0];
            lastPt_2 = nextPath.pathPoints[nextPath.pathPoints.length - 1];
            if(lastPt.anchor[0] == firstPt_2.anchor[0] && lastPt.anchor[1] == firstPt_2.anchor[1]){
              lastPt.selected = PathPointSelection.ANCHORPOINT;
              firstPt_2.selected = PathPointSelection.ANCHORPOINT;
              return true;
            }
          }
        };
      }
    };
    doc.selection = null;
    return false;
  };
  while(overlapPointExists()){
    app.executeMenuCommand("join");
    app.activeDocument.selection = null;
  }
  app.activeDocument.selection = null;
};
test();

enter image description here

EDIT:
The script was way too slow when there weren’t just a few paths in the document.
So here is one which will work based on a selection of paths. It works by you having your separate and ungrouped paths selected at first, then it groups them into a temporary group (stacking order may change if the items aren’t in consecutive order), and it uses that group as an isolation container to act only on paths therein. After it runs through, the group gets ungrouped and an alert box tells you how long it all took. Comment out or remove this alert when it gets annoying.

#target illustrator
function test(){
  function overlapPointExists(pathCollection){
    var doc = app.activeDocument, thisPath, firstPt, lastPt, nextPath, firstPt_2, lastPt_2;
    for(var i=0; i < pathCollection.pathItems.length; i++){
      thisPath = pathCollection.pathItems[i];
      if(!thisPath.closed){
        firstPt = thisPath.pathPoints[0];
        lastPt = thisPath.pathPoints[thisPath.pathPoints.length - 1];
        for(var j=0; j < pathCollection.pathItems.length; j++){
          if(i != j){
            nextPath = pathCollection.pathItems[j];
            firstPt_2 = nextPath.pathPoints[0];
            lastPt_2 = nextPath.pathPoints[nextPath.pathPoints.length - 1];
            if(lastPt.anchor[0] == firstPt_2.anchor[0] && lastPt.anchor[1] == firstPt_2.anchor[1]){
              lastPt.selected = PathPointSelection.ANCHORPOINT;
              firstPt_2.selected = PathPointSelection.ANCHORPOINT;
              return true;
            }
          }
        };
      } else {
        return false;
      }
    };
    doc.selection = null;
    return false;
  };
  if(app.activeDocument.selection == null || app.activeDocument.selection.length == 0){
    alert("Please select something first.");
    return;
  }
  var doc = app.activeDocument;
  var start = (new Date().getTime());
  var sel = doc.selection;
  app.executeMenuCommand("group");
  var pathGroup = sel[0].parent;
  pathGroup.selected = false;

  while(overlapPointExists(pathGroup)){
    app.redraw();
    app.executeMenuCommand("join");
    doc.selection = null;
  }
  pathGroup.selected = true;
  app.executeMenuCommand("ungroup");
  doc.selection = null;
  var end = (new Date().getTime());
  alert("Process took " + (end - start) / 1000 + " seconds.");
};
test();


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x