7

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?

1
  • I was going to ask the same question... Cannot find the answer online. Commented Feb 2, 2017 at 3:46

2 Answers 2

5
+50

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 POINTS\n" +
    "Copyright (c) 2017 Mads Wolff\n" +
    "This script is distributed under the MIT License.\n" +
    "Use at your own risk.\n\n\n" +
    "FEATURES:\n\n" +
    "1. Stray points are removed.\n" +
    "2. Split points within paths are merged.\n" +
    "3. Open paths with overlapping points are joined.\n\n" +
    "Only selected paths are affected.\n" +
    "Groups are supported, compound paths are not.\n" +
    "For overlapping paths an overlap tolerance applies.\n\n\n" +
    "HOW TO USE:\n\n" +
    "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 POINTS\n\nABORT: There is no selection.";
  var tolerance = prompt("JOIN PATHS WITH OVERLAPPING POINTS\n\nEnter overlap tolerance (in points):", 0);
  if (tolerance === null) return "JOIN PATHS WITH OVERLAPPING POINTS\n\nABORT: Script execution cancelled.";
  if (!(tolerance >= 0)) return "JOIN PATHS WITH OVERLAPPING POINTS\n\nABORT: 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 POINTS\n\n" +
    numberOfPoints + " points in " + (numberOfOpenPaths + numberOfClosedPaths) + " paths ("+ numberOfOpenPaths +" open and " + numberOfClosedPaths + " closed) were processed.\n\n" +
    numberOfStrayPoints + " stray points were removed.\n" +
    numberOfSplitPointsMerged + " split points were merged.\n" +
    numberOfOverlappingPathsJoined + " paths with overlapping points were joined to " + numberOfResultingPaths + " paths.";

})());
11
  • How do I use this in Illustrator? Do I have to copy and paste it or save it as a file? Commented Feb 4, 2017 at 19:58
  • 1
    @MicroMachine. Actually you have to copy it to a text editor and save it as a .js file. Then you can select your paths and run the script in File/Scripts/Other script (ctrl+f12).
    – Wolff
    Commented Feb 4, 2017 at 21:03
  • 1
    @MicroMachine. I didn't implement that feature because it wasn't part of the question. Martin asked for a script that could join individual lines to connected paths - not remove duplicate points. Doesn't my solution or Silly-V's solution answer the question?
    – Wolff
    Commented Feb 6, 2017 at 15:53
  • 1
    @MicroMachine. I'll see if I can easily implement the "merge overlapped anchors" feature. Maybe also change it so points that are almost overlapping are moved to the mid-point between them instead of just connecting them (as join does it now).
    – Wolff
    Commented Feb 6, 2017 at 17:40
  • 1
    @MicroMachine. Check out the new version. Does this do the trick?
    – Wolff
    Commented Feb 7, 2017 at 1:37
4

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();
1
  • Although it may take a while - on my Win7 machine with only 82 paths, it went "not responding" for 20 seconds or so before coming back to life with all paths joined.
    – Silly-V
    Commented Feb 2, 2017 at 22:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.