## Any easy way to detect if a PathItem is a given shape?

Jun 18, 2020

Copied

I'd like to iterate through PathItems and detect if one is a parametric shape like a rectangle, ellipse, polygon, etc., specifically having been made from the corresponding tool (rectangle tool, and so on). I figure this can be done by iterating through PathPoints of enclosed PathItems:

• Rectangle has 4 vertices, and two pairs of equal paths
• You could tell if something is an ellipse by looking at the distance of the Bezier handles or anchors. If these are all equal length, it's a perfect circle. It it has an equal amount of two matching pairs, it's oblong.
• Polygons should have equal distance between all pathPoints, and would have 3 or more sides

So I figure I could do all this logic, but first was curious if there's a much easier way to detect since I'm essentially just looking to return a value of "Rectangle", "Ellipse", "Polygon". Is there any easier way to do this?

not easily as with most everything but there's hope

if you save your document with "Use Compression" unchecked, and open the ai document with a Text Editor, you'll find a lot of useful information about the file.

search for "BeginLayer" to get to the good stuff. It turns out Illustrator adds tags to Shapes

this snippet shows a 6 side Polygone named "myHexagone". Expanded Shapes just list a bunch of Anchor coordinates, no shape tags

TOPICS
Scripting

Views

115

Likes

Report

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

## Any easy way to detect if a PathItem is a given shape?

Jun 18, 2020

Copied

I'd like to iterate through PathItems and detect if one is a parametric shape like a rectangle, ellipse, polygon, etc., specifically having been made from the corresponding tool (rectangle tool, and so on). I figure this can be done by iterating through PathPoints of enclosed PathItems:

• Rectangle has 4 vertices, and two pairs of equal paths
• You could tell if something is an ellipse by looking at the distance of the Bezier handles or anchors. If these are all equal length, it's a perfect circle. It it has an equal amount of two matching pairs, it's oblong.
• Polygons should have equal distance between all pathPoints, and would have 3 or more sides

So I figure I could do all this logic, but first was curious if there's a much easier way to detect since I'm essentially just looking to return a value of "Rectangle", "Ellipse", "Polygon". Is there any easier way to do this?

not easily as with most everything but there's hope

if you save your document with "Use Compression" unchecked, and open the ai document with a Text Editor, you'll find a lot of useful information about the file.

search for "BeginLayer" to get to the good stuff. It turns out Illustrator adds tags to Shapes

this snippet shows a 6 side Polygone named "myHexagone". Expanded Shapes just list a bunch of Anchor coordinates, no shape tags

TOPICS
Scripting

Views

116

Likes

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Jun 18, 2020 1
3 Replies 3
Jun 19, 2020

Copied

not easily as with most everything but there's hope

if you save your document with "Use Compression" unchecked, and open the ai document with a Text Editor, you'll find a lot of useful information about the file.

search for "BeginLayer" to get to the good stuff. It turns out Illustrator adds tags to Shapes

this snippet shows a 6 side Polygone named "myHexagone". Expanded Shapes just list a bunch of Anchor coordinates, no shape tags

Likes

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Jun 19, 2020 1
Nov 15, 2020

Copied

Hey Carlos, I wanted to come back to this due to that renaming script from earlier this week.

Is the Use Compression option accessible via scripting in any way? Is it possible to programmatically save an uncompressed file to later parse, or expand a compress file to get the same result as Use Compression = false?

Likes

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Nov 15, 2020 0
LATEST
Nov 15, 2020

Copied

For reference if any one sees this in the future and was wondering how to do what I initially suggested with detecting (pure) parametric shapes, this is pretty simple but it doesn't handle the fact that Illustrator will name "<Rectangle>" for something generated from the Rectangle tool and this doesn't change even after modifying the points, or how using the Polygon tool to create a 4 sided polygon will result in "<Polgyon>".

Example code:

``````// The layer to check items of:
var targetLayer = app.activeDocument.layers[1];
// To prevent small rounding errors, use Math.round() on anchors, handles, and lengths
var settings = {
roundIntegers: true,
};
//
// Adding a few helpers and polyfills here which need to be ran prior to invoking main function
//
// Polyfill for Math.hypot, to later find the distance between two points
if (!Math.hypot)
Math.hypot = function () {
var max = 0;
var s = 0;
var containsInfinity = false;
for (var i = 0; i < arguments.length; ++i) {
var arg = Math.abs(Number(arguments[i]));
if (arg === Infinity) containsInfinity = true;
if (arg > max) {
s *= (max / arg) * (max / arg);
max = arg;
}
s += arg === 0 && max === 0 ? 0 : (arg / max) * (arg / max);
}
return containsInfinity
? Infinity
: max === 1 / 0
? 1 / 0
: max * Math.sqrt(s);
};
//
// Polyfills for Array methods to make iterating over and working with data much easier
Array.prototype.forEach = function (callback) {
for (var i = 0; i < this.length; i++) callback(this[i], i, this);
};
Array.prototype.includes = function (item) {
for (var i = 0; i < this.length; i++) if (this[i] == item) return true;
return false;
};
Array.prototype.filter = function (callback) {
var filtered = [];
for (var i = 0; i < this.length; i++)
if (callback(this[i], i, this)) filtered.push(this[i]);
return filtered;
};
Array.prototype.map = function (callback) {
var mappedParam = [];
for (var i = 0; i < this.length; i++)
mappedParam.push(callback(this[i], i, this));
return mappedParam;
};
// Helper for converting DOM collections to native Arrays
function get(type, parent, deep) {
if (arguments.length == 1 || !parent) {
parent = app.activeDocument;
deep = true;
}
var result = [];
if (!parent[type]) return [];
for (var i = 0; i < parent[type].length; i++) {
result.push(parent[type][i]);
if (parent[type][i][type] && deep)
result = [].concat(result, get(type, parent[type][i]));
}
return result || [];
}
//
//
// The real code:
//
function checkDocItems() {
// Get either selection or a specified Layer
var list = get(
app.selection.length ? app.selection : "pathItems",
targetLayer,
false
);
// Make sure this includes only PathItems and not CompoundPaths
list = list.filter(function (item) {
return !/compound/i.test(item.typename) && /path/i.test(item.typename);
});
// Pass to a utility function and display the result
list.forEach(function (item) {
var isParametric = isParametricShape(item, settings);
alert(item.name + " == " + isParametric);
});
}

function isParametricShape(item, options) {
var result = false;
var pointsList = get("pathPoints", item);
// If open shape, no points, or less than 3 points it can't be parametric:
if (!item.closed || !pointsList.length || pointsList.length < 3) return false;
// Otherwise collect data about every point:
var pointsDetails = pointsList.map(function (point, index, list) {
var prevIndex = index < 1 ? list.length - 1 : index - 1;
var nextIndex = (index + 1) % list.length;
var nextPoint = list[nextIndex];
var chain = [list[prevIndex].anchor, point.anchor, list[nextIndex].anchor];
var handles = {
leftLength: Math.hypot(
point.leftDirection[0] - point.anchor[0],
point.leftDirection[1] - point.anchor[1]
),
rightLength: Math.hypot(
point.rightDirection[0] - point.anchor[0],
point.rightDirection[1] - point.anchor[1]
),
totalLength: 0,
exists: false,
};
handles.totalLength = settings.roundIntegers
? Math.round(handles.rightLength + handles.leftLength)
: handles.rightLength + handles.leftLength;
if (handles.leftLength || handles.rightLength) handles.exists = true;
return {
index: index,
anchor: point.anchor,
angle: getAngleFromChain(chain),
length: Math.hypot(
nextPoint.anchor[0] - point.anchor[0],
nextPoint.anchor[1] - point.anchor[1]
),
handles: handles,
};
});

var totalAngles = [],
totalLengths = [],
totalHandles = [],
totalPoints = [];
pointsDetails.forEach(function (detail) {
// Round integers when needed and add new instances to a specific Array per detail:
if (settings.roundIntegers)
(detail.angle = Math.round(detail.angle)),
(detail.length = Math.round(detail.length));
if (!totalAngles.includes(detail.angle)) totalAngles.push(detail.angle);
if (!totalLengths.includes(detail.length)) totalLengths.push(detail.length);
if (
!totalHandles.includes(detail.handles.totalLength) &&
detail.handles.totalLength !== 0
)
totalHandles.push(detail.handles.totalLength);

totalPoints.push(detail.anchor);
});
var deduction = getDeduction(
totalPoints,
totalAngles,
totalLengths,
totalHandles
);
var resultString =
item.name +
" isParametric: " +
deduction.isParametric +
"\r\n" +
deduction.type +
" == " +
deduction.name +
"\r\nTotal angles: " +
totalAngles.length +
"\r\nTotal lengths: " +
totalLengths.length;
return deduction.isParametric;
}

function getDeduction(points, angles, lengths, handles) {
var result = {
isParametric: false,
type: "Path",
name: "Unknown",
};
if (
points.length == 4 &&
angles.length == 1 &&
lengths.length == 1 &&
handles.length
) {
result.isParametric = true;
result.name = "Circle";
result.type = "Ellipse";
} else if (
points.length == 4 &&
angles.length == 1 &&
lengths.length == 1 &&
!handles.length
) {
result.isParametric = true;
result.name = "Square";
result.type = "Rectangle";
} else if (
points.length == 4 &&
angles.length == 1 &&
lengths.length == 2 &&
!handles.length
) {
result.isParametric = true;
result.name = "Rectangle";
result.type = "Rectangle";
} else if (
points.length >= 3 &&
angles.length == 1 &&
lengths.length == 1 &&
!handles.length
) {
result.isParametric = true;
result.type = "Polygon";
result.name = getPolygonName(points.length);
}
return result;
}

function getPolygonName(length) {
var names = [
null,
null,
null,
"Triangle",
"Rectangle",
"Pentagon",
"Hexagon",
"Heptagon",
"Octagon",
"Nonagon",
"Decagon",
];
return names[length];
}

// Return the number of degrees from 3 connected points:
function getAngleFromChain(chain) {
var A = chain[0];
var B = chain[1];
var C = chain[2];
var AB = Math.sqrt(Math.pow(B[0] - A[0], 2) + Math.pow(B[1] - A[1], 2));
var BC = Math.sqrt(Math.pow(B[0] - C[0], 2) + Math.pow(B[1] - C[1], 2));
var AC = Math.sqrt(Math.pow(C[0] - A[0], 2) + Math.pow(C[1] - A[1], 2));
return (
Math.acos((BC * BC + AB * AB - AC * AC) / (2 * BC * AB)) * (180 / Math.PI)
);
}

// Initiate the main function
checkDocItems();``````

Likes

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Nov 15, 2020 0
Resources
Learning Resources for Illustrator
What's new and fixed in Illustrator
Fonts and Typography in Illustrator