Copy link to clipboard
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:
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
Copy link to clipboard
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
Copy link to clipboard
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?
Copy link to clipboard
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;
alert(resultString);
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();