• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

Practice script to convert art to "permanent outlines" drawing anchors and handles, what's wrong?

Enthusiast ,
Nov 23, 2019 Nov 23, 2019

Copy link to clipboard

Copied

Just for practice I wrote a basic script that would loop through all document.pageItems then replace it's appearance with a stroke while attempting to draw each of it's pathPoint anchors/handles manually. I'm getting unexpected results, though.

 

On a simple shape, there are no problems. Left is the script result, right is the actual path:

 

ice_screenshot_20191123-194249.png

Looks great. But once I try a more complex file like a character, I get strange results:

 

ice_screenshot_20191123-194526.png

The left script results produces a bundled group of anchors where there should be none (between the pencil and the clipboard, beneath the left eye). It looks as though it stops executing at this point, because it doesn't continue to the leaf, shadow, eyes, mouth, etc. When I comment out the section that draws handles, all is fine, it strips the appearance just as expected:

 

ice_screenshot_20191123-194755.png

Can I get some help here? What am I doing wrong? How can this be done better overall?

 

//
// Barebones script to convert all paths in current document to permanent Outlines, including handles and anchors.
// This action can be undone with a single Edit > Undo command.
//
var anchorWidth = 1; // number in pixels, width of stroke
var anchorSize = 5; // number in pixels, height/width of rectangle
var handleSize = 4; // number in pixels, size of ellipse/orb where handle is grabbed
var anchorColor = newRGB(50, 50, 200); // RGB values, currently blue
//
var outlineWidth = 1; // number in pixels, width of stroke
var outlineColor = newRGB(0, 0, 0); // The RGB value of color ([0,0,0] = black)
//
var forceOpacity = true; // Boolean (false or true) if true, force all paths to have full opacity

function convertToOutlines() {
  for (var i = app.activeDocument.pageItems.length - 1; i >= 0; i--) {
    var item = app.activeDocument.pageItems[i];
    if (/path/i.test(item.typename)) {
      if (item.stroked || item.filled) {
        replaceAppearance(item);
        // If I comment out the code below, all objects convert appearance exactly as expected.
        // But certain parts (leaf, shadow, eyes/mouth) aren't affected by this?
        if (item.pathPoints.length)
          for (var p = 0; p < item.pathPoints.length; p++) {
            var point = item.pathPoints[p];
            drawAnchor(point);
            if (point.leftDirection) drawHandle(point, "left");
            if (point.rightDirection) drawHandle(point, "right");
          }
        // ---------------
        item.opacity = forceOpacity ? 100.0 : item.opacity;
      }
    }
  }
}
convertToOutlines();

function drawAnchor(point) {
  var childpos = point.anchor;
  var anchor = app.activeDocument.pathItems.rectangle(
    point.anchor[1] + anchorSize / 2,
    point.anchor[0] - anchorSize / 2,
    anchorSize,
    anchorSize
  );
  setAnchorAppearance(anchor);
}
function drawHandle(point, direction) {
  var handle = app.activeDocument.pathItems.add();
  handle.setEntirePath([point.anchor, point[direction + "Direction"]]);
  setAnchorAppearance(handle);
  var handleBar = app.activeDocument.pathItems.ellipse(
    point[direction + "Direction"][1] + handleSize / 2,
    point[direction + "Direction"][0] - handleSize / 2,
    handleSize,
    handleSize
  );
  handleBar.stroked = false;
  handleBar.filled = true;
  handleBar.fillColor = anchorColor;
}

function setAnchorAppearance(item) {
  item.filled = false;
  item.stroked = true;
  item.strokeWidth = anchorWidth;
  item.strokeColor = anchorColor;
}

function replaceAppearance(item) {
  item.filled = false;
  item.stroked = true;
  item.strokeWidth = outlineWidth;
  item.strokeColor = outlineColor;
}

function newRGB(r, g, b) {
  var color = new RGBColor();
  color.red = r;
  color.green = g;
  color.blue = b;
  return color;
}

 

TOPICS
Scripting

Views

1.9K

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Enthusiast , Nov 23, 2019 Nov 23, 2019

Figured it out:

 

ice_screenshot_20191123-205620.png

 

I had a few problems:

 

  1. I thought that decrementing from app.activeDocument.pageItems would mean I'd only draw anchors for items existing at the beginning of the loop because I assumed that pageItems was a chronological list. Turns out the bundled drawn anchors were a result of anchors drawing on anchors and recursing themselves, and I fixed this by saving all pageItems to an array at the beginning, then looping through this array to convert.
  2. Though the above technically solved all
...

Votes

Translate

Translate
Adobe
Enthusiast ,
Nov 23, 2019 Nov 23, 2019

Copy link to clipboard

Copied

I'm including the file (couldn't attach it, said the contents didn't match the file type?) in case any one willing to help wants to verify or test against it. Thanks guys!

 

Mango

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Nov 23, 2019 Nov 23, 2019

Copy link to clipboard

Copied

Figured it out:

 

ice_screenshot_20191123-205620.png

 

I had a few problems:

 

  1. I thought that decrementing from app.activeDocument.pageItems would mean I'd only draw anchors for items existing at the beginning of the loop because I assumed that pageItems was a chronological list. Turns out the bundled drawn anchors were a result of anchors drawing on anchors and recursing themselves, and I fixed this by saving all pageItems to an array at the beginning, then looping through this array to convert.
  2. Though the above technically solved all issues in this test file, I might have run into issues with CompoundPathItems, since I'd used /path/i.test(item.typename) and this would pass true for both PathItem (intended) and CompoundPathItem (unintented).
  3. The function which iterates through items for conversion itself shouldn't have a hardcoded list, like prior with app.activeDocuments.pageItems. Instead, it's better to pass an array as a parameter to this, and iterate through this array -- reason being that #2 can be solved for instantly by the function calling itself like "convertListToOutlines(items.pageItems)" if it comes across something that isn't a pathItem.

 

For posterity I'm including the final script below. I'd still be interested in hearing any ways that it could be made better!

 

// https://github.com/Inventsable
// contact: tom@inventsable.cc
//
// Barebones script to convert all paths in current document to permanent Outlines, including handles and anchors.
// This action can be undone with a single Edit > Undo command.
//
// You can edit the below settings:
var anchorWidth = 1; // number in pixels, width of stroke
var anchorSize = 5; // number in pixels, height/width of rectangle
var handleSize = 4; // number in pixels, size of ellipse/orb where handle is grabbed
var anchorColor = newRGB(50, 50, 200); // RGB values, currently blue
var anchorIsFilled = true; // Boolean (true or false) if true, anchors are filled, otherwise have only stroke
//
var outlineWidth = 1; // number in pixels, width of stroke
var outlineColor = newRGB(0, 0, 0); // The RGB value of color ([0,0,0] = black)
//
var forceOpacity = true; // Boolean (true or false) if true, force all paths to have full opacity
// --------------------------------------------------- //
//
// Do not edit below unless you know what you're doing!
//

convertAllToOutlines();

function convertAllToOutlines() {
  // Need to collect all current pageItems otherwise recurses into it's own drawn anchors
  // app.activeDocument.pageItems is not chronological! Adding to it, even if decrementing, causes recursion
  var targets = scanCurrentPageItems();
  convertListToOutlines(targets);
}

function scanCurrentPageItems() {
  var list = [];
  for (var i = app.activeDocument.pageItems.length - 1; i >= 0; i--) {
    list.push(app.activeDocument.pageItems[i]);
  }
  return list;
}

function convertListToOutlines(list) {
  for (var i = list.length - 1; i >= 0; i--) {
    var item = list[i];
    if (/compound|group/i.test(item.typename)) {
      if (item.pageItems && item.pageItems.length) {
        convertListToOutlines(item.pageItems);
      }
    } else if (/path/i.test(item.typename)) {
      if (item.stroked || item.filled) {
        replaceAppearance(item);
        if (item.pathPoints && item.pathPoints.length) {
          for (var p = 0; p < item.pathPoints.length; p++) {
            var point = item.pathPoints[p];
            drawAnchor(point);
            if (point.leftDirection) drawHandle(point, "left");
            if (point.rightDirection) drawHandle(point, "right");
          }
          item.opacity = forceOpacity ? 100.0 : item.opacity;
        } else {
          alert("Problem with " + item.name + ": " + item.typename);
        }
      }
    } else {
      alert(item.name + " " + item.typename);
    }
  }
}

function drawAnchor(point) {
  var anchor = app.activeDocument.pathItems.rectangle(
    point.anchor[1] + anchorSize / 2,
    point.anchor[0] - anchorSize / 2,
    anchorSize,
    anchorSize
  );
  setAnchorAppearance(anchor, false);
}
function drawHandle(point, direction) {
  var handle = app.activeDocument.pathItems.add();
  handle.setEntirePath([point.anchor, point[direction + "Direction"]]);
  setAnchorAppearance(handle, true);
  var handleBar = app.activeDocument.pathItems.ellipse(
    point[direction + "Direction"][1] + handleSize / 2,
    point[direction + "Direction"][0] - handleSize / 2,
    handleSize,
    handleSize
  );
  handleBar.stroked = false;
  handleBar.filled = true;
  handleBar.fillColor = anchorColor;
}

function setAnchorAppearance(item, isHandle) {
  if (!isHandle) {
    item.filled = anchorIsFilled;
    item.stroked = !anchorIsFilled;
    if (!anchorIsFilled) {
      item.strokeWidth = anchorWidth;
      item.strokeColor = anchorColor;
    } else {
      item.fillColor = anchorColor;
    }
  } else {
    item.filled = false;
    item.stroked = true;
    item.strokeWidth = anchorWidth;
    item.strokeColor = anchorColor;
  }
}

function replaceAppearance(item) {
  item.filled = false;
  item.stroked = true;
  item.strokeWidth = outlineWidth;
  item.strokeColor = outlineColor;
}

function newRGB(r, g, b) {
  var color = new RGBColor();
  color.red = r;
  color.green = g;
  color.blue = b;
  return color;
}

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Nov 25, 2019 Nov 25, 2019

Copy link to clipboard

Copied

I think your script is as good as it gets.

Thakns for sharing!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Nov 25, 2019 Nov 25, 2019

Copy link to clipboard

Copied

Thanks Carlos! Could always be better though 😋

 

It probably makes more sense to use PathItems instead of PageItems, since then I wouldn't have to compare for groups/compounds, and right now the script still produces handles even when they aren't extended since leftDirection and rightDirection never seem to be null. Initially I tried comparing the anchor position to the handle data directly and only drawing if they aren't equal -- this didn't work (at least in comparing both array entries directly) so I dropped it but didn't look too far into why. Might do a bit of sprucing up and fixes regardless.

 

Someone had asked if this was possible on reddit and I recommended scripting, but they didn't think that could draw the handles/anchors. But we all know better than that

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Valorous Hero ,
Nov 27, 2019 Nov 27, 2019

Copy link to clipboard

Copied

I second Carlos, and I'll just say that leftDirection and rightDirection are going to be the same or within a very small tolerance of your anchor point - which is one way of filtering whether the direction handles are fully retracted and need to be rendered or not.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Nov 26, 2019 Nov 26, 2019

Copy link to clipboard

Copied

oh I see what you mean, in that case then yes, maybe it could get even better. 

That's right, we know better, we have magic wands 🙂

 

Jongware wrote a similar script years ago, check his script. The link to the forums is dead but Wundes has also posted a link to the direct download

http://js4ai.blogspot.com/2012/03/draw-control-handles-as-geometry.html

 

here's another version by jooja

https://graphicdesign.stackexchange.com/questions/50559/illustrator-how-to-draw-circles-at-control-p...

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Nov 28, 2019 Nov 28, 2019

Copy link to clipboard

Copied

Updated this quite a bit, source code on GitHub:

 

  • Handles are only drawn when needed (needed to wrap Number() around them while comparing)
  • Refactored basic recursion
  • Merges all Clipping Masks for visible results only
  • Places anchors/handles in same layer as target
  • Optionally reconstructs every PathItem from scratch to override complex appearances (stacked fills/strokes)
  • Optionally inherits layer label color to paint anchors/handles
  • Optionally nests grouped handles within collection group for entire anchor
  • Smart sorting of Layer contents after render places outlines just above their target
  • Smart renaming appends Layer index, pathPoint index, and variable suffix per item entry for After Effects expression compatibility

 

Before:

Before, with clipping masks and unnamed pathsBefore, with clipping masks and unnamed paths

 

After:

Merge all masks, inherit layer label color, rename all targets, deeply organize them by handle and anchor and moreMerge all masks, inherit layer label color, rename all targets, deeply organize them by handle and anchor and more

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Jun 08, 2023 Jun 08, 2023

Copy link to clipboard

Copied

LATEST
 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines