Copy link to clipboard
Copied
Greetings, I'm trying to generate a specific curved closed shape (it's not a perfect circle) based on the shape data that I copied and pasted into a text file. Ideally I would like to subtract this shape from a rectangle/square as shown in the screenshot 2, but I can't chatGPT to generate the correct code just to draw the shape below shown in Screenshot 1.
Any help on this problem is much appreciated 🙂
I just get this error in illustrator and ChatGPT and keeps generating the same error after it has given me a "revised" version that should work.
Error 24: pathArray[i].substr().split().map is not a function
Line: 38
var values = pathArray[i].substr(1).split(/[ ,]+/).map(parseFloat);
when running the full ChatGPT code below
Screenshot 1 from illustrator showing shaped to be subtracted
Shape data from right clicking on the shape/object above copying and then pasting into a text file:
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 446.92 416.26"><defs><style>.cls-1{fill:#ed160a;stroke-width:0px;}</style></defs><path class="cls-1" d="M27.55,240.35c4.53-13.12,9.69-26.03,15.52-38.64,5.59-12.1,11.81-23.91,18.69-35.32,6.59-10.91,13.79-21.44,21.6-31.52,7.46-9.62,15.48-18.82,24.11-27.41,8.79-8.74,18.21-16.85,28.13-24.29,10.57-7.94,21.69-15.12,33.19-21.65,12.26-6.95,24.94-13.15,37.9-18.68,13.77-5.87,27.85-10.99,42.13-15.46,15.07-4.71,30.37-8.71,45.8-12.07,16.14-3.51,32.42-6.34,48.79-8.57,34.29-4.68,68.92-6.74,103.53-6.74H0v416.26c0-30.43,2.14-60.86,6.79-90.94,4.46-28.86,11.23-57.38,20.76-84.98Z"/></svg>
Screenshot 2 from illustrator showing the intened composite shape result
ChatGPT generated code for only making the curved shape from screenshot 1
// Create a new document with a 24" x 24" artboard
var doc = app.documents.add(DocumentColorSpace.CMYK);
var artboard = doc.artboards[0];
artboard.artboardRect = [0, 0, 24 * 72, -24 * 72]; // 24 inches converted to points
// Create a new layer
var layer = doc.layers.add();
layer.name = "CurvedCorners";
// Add the curved shape at position x, y = 0, 0
var curvedPathData = "M27.55,240.35c4.53-13.12,9.69-26.03,15.52-38.64,5.59-12.10,11.81-23.91,18.69-35.32,6.59-10.91,13.79-21.44,21.60-31.52,7.46-9.62,15.48-18.82,24.11-27.41,8.79-8.74,18.21-16.85,28.13-24.29,10.57-7.94,21.69-15.12,33.19-21.65,12.26-6.95,24.94-13.15,37.90-18.68,13.77-5.87,27.85-10.99,42.13-15.46,15.07-4.71,30.37-8.71,45.80-12.07,16.14-3.51,32.42-6.34,48.79-8.57,34.29-4.68,68.92-6.74,103.53-6.74H0v416.26c0-30.43,2.14-60.86,6.79-90.94,4.46-28.86,11.23-57.38,20.76-84.98Z";
var curvedShape = layer.pathItems.add();
curvedShape.setEntirePath(createPathFromData(curvedPathData));
curvedShape.stroked = false;
curvedShape.filled = true;
curvedShape.fillColor = spotColor; // Assuming you have spotColor defined
// Center the curved shape on the artboard
var centerX = (artboard.artboardRect[2] + artboard.artboardRect[0]) / 2;
var centerY = (artboard.artboardRect[1] + artboard.artboardRect[3]) / 2;
var xOffset = centerX - (curvedShape.width / 2);
var yOffset = centerY - (curvedShape.height / 2);
curvedShape.position = [xOffset, yOffset];
/**
* Function to create a path from SVG path data
* {string} pathData - SVG path data
* {Array} - Path points array
*/
function createPathFromData(pathData) {
var pathPoints = [];
var pathArray = pathData.match(/[A-Za-z][^A-Za-z]*/g);
for (var i = 0; i < pathArray.length; i++) {
var command = pathArray[i].charAt(0);
var values = pathArray[i].substr(1).split(/[ ,]+/).map(parseFloat);
if (command === "M") {
pathPoints.push({ anchor: [values[0], values[1]], type: PathPointType.CORNER });
} else if (command === "C") {
pathPoints.push({ anchor: [values[4], values[5]], leftDirection: [values[0], values[1]], rightDirection: [values[2], values[3]], type: PathPointType.SMOOTH });
}
}
return pathPoints;
}
Copy link to clipboard
Copied
Hi @TestriteVisual, just to be clear, can you confirm that you are trying to write a SVG path data parser? For example to convert this:
<path
d="M0,0c73.488,0,100,29.651,100,100l-89.4-9.5-.6,59.5c30.083,0,24.367,30,70,30"
style="fill:#fff; stroke:#1d1d1b; stroke-miterlimit:10;" />
into an Illustrator pathItem?
If that is not correct please clarify what you are doing.
Some notes:
1. Your error occurs because the script uses Javascript ES6 .map method of Array that Extendscript doesn't have. Convert this to a "for" loop. You can try asking ChatGPT to do this for you.
2. parsing a svg path data string is significantly more involved than your sample script attempts to handle, for example "l" denotes *one or more* straight lines segments and that coordinates are relative to the previous point (Illustrator pathPoint values are relative to artboard or document origin.) Also you have to handle "v" (vertical line) and all the other symbols that svg uses.
3. The script attempts to use PathItem.setEntirePath() giving an array of fake (but fully specified) PathPoints, but setEntirePath method only creates pathItems with straight line segments and only accepts an array of points [x, y]. The only way I know to make the pathItem is something like this:
var myPathItem = doc.pathItems.add();
var p = myPathItem.pathPoints.add();
p.anchor = [0, 0];
p.leftDirection = [10, 10];
p.rightDirection = [10, -10];
p.pointType = PointType.SMOOTH;
Sorry it isn't a simple fix, but I hope that will get you pointed in a better direction.
- Mark
Copy link to clipboard
Copied
I realise that you may not be interested in an actual SVG parser (I still don't know exactly why you need to input SVG string rather than just drawing the shape you want using Illustrator-style path data) but, for my own interest, I had a quick look at what's involved in parsing an SVG path data string and it seems that we can get a long way without too much effort—meaning that a very incomplete script can still handle many real-world examples.
So I've written this script that takes the svg path data string and creates a path item:
/**
* Starting point for a simple SVG path data string parser
* and converter to Illustrator path item.
* eg. <path d="M200,0c57.3,0,100-.883,100,100s-56.589,104.282-75,100" />
* @author m1b
* @discussion https://community.adobe.com/t5/illustrator-discussions/how-to-generate-a-curved-line-or-closed-shape-via-javascript/m-p/14394430
*/
(function () {
var doc = app.activeDocument;
var testData1 = "M27.55,240.35c4.53-13.12,9.69-26.03,15.52-38.64,5.59-12.10,11.81-23.91,18.69-35.32,6.59-10.91,13.79-21.44,21.60-31.52,7.46-9.62,15.48-18.82,24.11-27.41,8.79-8.74,18.21-16.85,28.13-24.29,10.57-7.94,21.69-15.12,33.19-21.65,12.26-6.95,24.94-13.15,37.90-18.68,13.77-5.87,27.85-10.99,42.13-15.46,15.07-4.71,30.37-8.71,45.80-12.07,16.14-3.51,32.42-6.34,48.79-8.57,34.29-4.68,68.92-6.74,103.53-6.74H0v416.26c0-30.43,2.14-60.86,6.79-90.94,4.46-28.86,11.23-57.38,20.76-84.98Z";
var testData2 = "M0,0c73.488,0,100,29.651,100,100l-89.4-9.5-.6,59.5c30.083,0,24.367,30,70,30";
var testData3 = "M200,0c57.3,0,100-.883,100,100s-56.589,104.282-75,100c-215-50-29.536-110.551,0-110.551v46.035h117.828l13.587-24.81-28.355-20.674,28.355-20.086-17.131-40.76";
var item1 = pathItemFromSVGData(doc, testData1);
var item2 = pathItemFromSVGData(doc, testData2);
var item3 = pathItemFromSVGData(doc, testData3);
})();
/**
* Draws and returns an Illustrator PathItem created
* using an SVG path element's data string.
* NOTE: this function is a stub, and does not handle
* many possibilities, eg. C and S symbols do not currently
* calculate the `mirror` direction point, but it must.
* @author m1b
* @version 2024-02-08
* @param {Document|Layer|GroupItem} container - the container for the new path item, a DOM object.
* @param {String} svgPathDataString - an SVG path element's data string, eg. "M200,0c57.3,0,100-.883,100,100s-56.589,104.282-75,100".
* @returns {PathItem}
*/
function pathItemFromSVGData(container, svgPathDataString) {
// create the path item
var item = container.pathItems.add();
// parse the svg string
var data = parseSVGPathDataString(svgPathDataString);
var d, // the data for one svg command
val, // the values for the command
c, // sub-value(s)
pos, // the most-recent position [x, y]
p, // the most-recently created point (we don't need this but might want it for something)
mirror; // the mirrored counterpart of a direction point
while (d = data.shift()) {
val = d.values;
switch (d.symbol) {
case 'M': // absolute move to
pos = val[0];
break;
case 'm': // relative move to
pos = rel(val[0]);
break;
case 'L': // absolute line to
while (c = val.shift()) {
pos = c;
p = addPoint(item, pos);
}
break;
case 'l': // relative line to
while (c = val.shift()) {
pos = rel(c);
p = addPoint(item, pos);
}
break;
case 'C': // absolute bezier curve
while (val.length && (c = val.splice(0, 3))) {
if (p) {
p.rightDirection = c[0];
p.pointType = PointType.SMOOTH;
}
else {
addPoint(item, pos, undefined, c[0], PointType.CORNER);
}
p = addPoint(item, c[2], c[1], undefined, PointType.CORNER);
pos = c[2];
}
break;
case 'c': // relative bezier curve
while (val.length && (c = val.splice(0, 3))) {
if (p) {
p.rightDirection = rel(c[0]);
p.pointType = PointType.SMOOTH;
}
else {
addPoint(item, pos, undefined, rel(c[0]), PointType.CORNER);
}
p = addPoint(item, rel(c[2]), rel(c[1]), undefined, PointType.CORNER);
pos = rel(c[2]);
mirror = rel([-c[1][1], -c[1][0]]);
}
break;
case 'S': // absolute smooth bezier curve
while (val.length && (c = val.splice(0, 2))) {
if (p) {
p.rightDirection = mirror;
p.pointType = PointType.SMOOTH;
}
else {
addPoint(item, pos, undefined, mirror, PointType.SMOOTH);
}
p = addPoint(item, c[1], c[0], undefined, PointType.SMOOTH);
pos = c[1];
mirror = [-c[0][1], -c[0][0]];
}
break;
case 's': // relative smooth bezier curve
while (val.length && (c = val.splice(0, 2))) {
if (p) {
p.rightDirection = mirror;
p.pointType = PointType.SMOOTH;
}
else {
addPoint(item, pos, undefined, mirror, PointType.SMOOTH);
}
p = addPoint(item, rel(c[1]), rel(c[0]), undefined, PointType.SMOOTH);
pos = rel(c[1]);
mirror = rel([-c[0][1], -c[0][0]]);
}
break;
case 'H': // absolute horizontal line
while (undefined !== (c = val.shift())) {
pos[0] = c;
p = addPoint(item, pos);
}
break;
case 'h': // relative horizontal line
while (undefined !== (c = val.shift())) {
pos[0] += c;
p = addPoint(item, pos);
}
break;
case 'V': // absolute vertical line
while (undefined !== (c = val.shift())) {
pos[1] = c;
p = addPoint(item, pos);
}
break;
case 'v': // relative vertical line
while (undefined !== (c = val.shift())) {
pos[1] += c;
p = addPoint(item, pos);
}
break;
case 'Q': // absolute quadratic Bézier curve
break;
case 'q': // relative quadratic Bézier curve
break;
case 'T': // absolute smooth quadratic Bézier curve
break;
case 't': // relative smooth quadratic Bézier curve
break;
case 'A': // absolute elliptical arc
break;
case 'a': // relative elliptical arc
break;
case 'Z': // close path
item.closed = true;
break;
default:
throw Error('pathItemFromSVGData: unknown symbol "' + d.symbol + '"');
}
}
return item;
/**
* Return `point` offset by `pos`.
* @param {point} point - [x,y]
* @returns {point}
*/
function rel(point) { return [pos[0] + point[0], pos[1] + point[1]]; };
};
/**
* Adds a PathPoint to a PathItem.
* @author m1b
* @version 2024-02-02
* @param {PathItem} item - the pathItem to add the point to.
* @param {point} anchor - a point [x, y].
* @param {point} [leftDirection] - a point [x, y] (default: copy of anchor point).
* @param {point} [rightDirection] - a point [x, y] (default: copy of anchor point).
* @param {PointType} [pointType] - an Illustrator PointType (default: PointType.CORNER).
* @returns {PathPoint}
*/
function addPoint(item, anchor, leftDirection, rightDirection, pointType) {
var p = item.pathPoints.add();
p.anchor = anchor;
p.leftDirection = leftDirection || anchor;
p.rightDirection = rightDirection || anchor;
p.pointType = pointType || PointType.CORNER;
return p;
};
/**
* Returns an array of data for each command
* parsed from the given `svgPathDataString`.
*
* For example:
* "M200,0c57.3,0,100-.883,100,100Z"
* Returns:
* [
* { symbol: "M", values: [[200, 0]] },
* { symbol: "c", values: [[57.3, 0], [100, 0.883], [100, -100]] },
* { symbol: "Z" }
* ]
*
* NOTE: because Illustrator, we flip the sign on Y axis coordinates.
* @author m1b
* @version 2024-02-02
* @param {String} svgPathDataString - an SVG path element's data string, eg. "M200,0c57.3,0,100-.883,100,100s-56.589,104.282-75,100".
* @returns {Array<Object>} - array of {symbol: values: }.
*/
function parseSVGPathDataString(svgPathDataString) {
var commands = [],
symbolMatcher = /([MmLlHhVvCcSsQqTtAaZ])([^MmLlHhVvCcSsQqTtAaZ]*)/g,
minus = /(.)-/g,
command,
values,
symbol,
len,
match;
while (match = symbolMatcher.exec(svgPathDataString)) {
symbol = match[1];
command = { symbol: symbol };
command.values = [];
commands.push(command);
if (0 === match[2].length)
continue;
// parse the coordinate values
values = match[2].replace(minus, '$1,-').split(',');
// has single numbers, not arrays
var hasSingleNumbers = (-1 !== 'vhVH'.search(symbol));
// add the values
while (len = values.length) {
if (hasSingleNumbers) {
if ('v' === symbol.toLowerCase())
command.values.push(-Number(values.shift()));
else
command.values.push(Number(values.shift()));
}
else
command.values.push([Number(values.shift()), -Number(values.shift())]);
}
}
return commands;
};
Edit 2024-02-08: fixed bug mentioned by @renél80416020 where I was adding extra points on curves.
My test data strings create these: (testData1 is yours @TestriteVisual).
- Mark
Copy link to clipboard
Copied
Hi thanks for the reply, I'm not a programmer so just cutting/pasting snippets that i can get to work and generating code using ChatGPT are my only methods, so I'm completely open to whatever may be the easiest/simplest way to generate this exact curve using a script. I ran a shorter version of the the code you provided just to generate the curve shape, in a new open document and it ran fine on my end. Just want to confirm, do I need this entire code to be able to generate this exact curve? If possible, would you be able to let me know how I can have the script mirror the curve shape, so that I could end up with a composite shape like shown below? Thanks again
This is the shape at the bottom I'm trying to create by subtracting to identical left/fright curves from a rectangle
Shorter version for just the curve shape
/**
* Starting point for a simple SVG path data string parser
* and converter to Illustrator path item.
* eg. <path d="M200,0c57.3,0,100-.883,100,100s-56.589,104.282-75,100" />
* m1b
* @discussion https://community.adobe.com/t5/illustrator-discussions/how-to-generate-a-curved-line-or-closed-shape-via-javascript/m-p/14394430
*/
(function () {
var doc = app.activeDocument;
var testData1 = "M27.55,240.35c4.53-13.12,9.69-26.03,15.52-38.64,5.59-12.10,11.81-23.91,18.69-35.32,6.59-10.91,13.79-21.44,21.60-31.52,7.46-9.62,15.48-18.82,24.11-27.41,8.79-8.74,18.21-16.85,28.13-24.29,10.57-7.94,21.69-15.12,33.19-21.65,12.26-6.95,24.94-13.15,37.90-18.68,13.77-5.87,27.85-10.99,42.13-15.46,15.07-4.71,30.37-8.71,45.80-12.07,16.14-3.51,32.42-6.34,48.79-8.57,34.29-4.68,68.92-6.74,103.53-6.74H0v416.26c0-30.43,2.14-60.86,6.79-90.94,4.46-28.86,11.23-57.38,20.76-84.98Z";
var item1 = pathItemFromSVGData(doc, testData1);
})();
/**
* Draws and returns an Illustrator PathItem created
* using an SVG path element's data string.
* NOTE: this function is a stub, and does not handle
* many possibilities, eg. C and S symbols do not currently
* calculate the `mirror` direction point, but it must.
* m1b
* 2024-02-02
* {Document|Layer|GroupItem} container - the container for the new path item, a DOM object.
* {String} svgPathDataString - an SVG path element's data string, eg. "M200,0c57.3,0,100-.883,100,100s-56.589,104.282-75,100".
* {PathItem}
*/
function pathItemFromSVGData(container, svgPathDataString) {
// create the path item
var item = container.pathItems.add();
// parse the svg string
var data = parseSVGPathDataString(svgPathDataString);
var d, // the data for one svg command
val, // the values for the command
c, // sub-value(s)
pos, // the most-recent position [x, y]
p, // the most-recently created point (we don't need this but might want it for something)
mirror; // the mirrored counterpart of a direction point
while (d = data.shift()) {
val = d.values;
switch (d.symbol) {
case 'M': // absolute move to
pos = val[0];
break;
case 'm': // relative move to
pos = rel(val[0]);
break;
case 'L': // absolute line to
while (c = val.shift()) {
pos = c;
p = addPoint(item, pos);
}
break;
case 'l': // relative line to
while (c = val.shift()) {
pos = rel(c);
p = addPoint(item, pos);
}
break;
case 'C': // absolute bezier curve
while (val.length && (c = val.splice(0, 3))) {
addPoint(item, pos, undefined, c[0], PointType.CORNER);
p = addPoint(item, c[2], c[1], undefined, PointType.CORNER);
pos = c[2];
}
break;
case 'c': // relative bezier curve
while (val.length && (c = val.splice(0, 3))) {
addPoint(item, pos, undefined, rel(c[0]), PointType.CORNER);
p = addPoint(item, rel(c[2]), rel(c[1]), undefined, PointType.CORNER);
pos = rel(c[2]);
mirror = rel([-c[1][1], -c[1][0]]);
}
break;
case 'S': // absolute smooth bezier curve
while (val.length && (c = val.splice(0, 2))) {
addPoint(item, pos, undefined, mirror, PointType.SMOOTH);
p = addPoint(item, rel(c[1]), rel(c[0]), undefined, PointType.SMOOTH);
pos = c[1];
}
break;
case 's': // relative smooth bezier curve
while (val.length && (c = val.splice(0, 2))) {
addPoint(item, pos, undefined, mirror, PointType.SMOOTH);
p = addPoint(item, rel(c[1]), rel(c[0]), undefined, PointType.SMOOTH);
pos = rel(c[1]);
mirror = rel([-c[0][1], -c[0][0]]);
}
break;
case 'H': // absolute horizontal line
while (undefined !== (c = val.shift())) {
pos[0] = c;
p = addPoint(item, pos);
}
break;
case 'h': // relative horizontal line
while (undefined !== (c = val.shift())) {
pos[0] += c;
p = addPoint(item, pos);
}
break;
case 'V': // absolute vertical line
while (undefined !== (c = val.shift())) {
pos[1] = c;
p = addPoint(item, pos);
}
break;
case 'v': // relative vertical line
while (undefined !== (c = val.shift())) {
pos[1] += c;
p = addPoint(item, pos);
}
break;
case 'Q': // absolute quadratic Bézier curve
break;
case 'q': // relative quadratic Bézier curve
break;
case 'T': // absolute smooth quadratic Bézier curve
break;
case 't': // relative smooth quadratic Bézier curve
break;
case 'A': // absolute elliptical arc
break;
case 'a': // relative elliptical arc
break;
case 'Z': // close path
item.closed = true;
break;
default:
throw Error('pathItemFromSVGData: unknown symbol "' + d.symbol + '"');
}
}
return item;
/**
* Return `point` offset by `pos`.
* {point} point - [x,y]
* {point}
*/
function rel(point) { return [pos[0] + point[0], pos[1] + point[1]]; };
};
/**
* Adds a PathPoint to a PathItem.
* m1b
* 2024-02-02
* {PathItem} item - the pathItem to add the point to.
* {point} anchor - a point [x, y].
* {point} [leftDirection] - a point [x, y] (default: copy of anchor point).
* {point} [rightDirection] - a point [x, y] (default: copy of anchor point).
* {PointType} [pointType] - an Illustrator PointType (default: PointType.CORNER).
* {PathPoint}
*/
function addPoint(item, anchor, leftDirection, rightDirection, pointType) {
var p = item.pathPoints.add();
p.anchor = anchor;
p.leftDirection = leftDirection || anchor;
p.rightDirection = rightDirection || anchor;
p.pointType = pointType || PointType.CORNER;
return p;
};
/**
* Returns an array of data for each command
* parsed from the given `svgPathDataString`.
*
* For example:
* "M200,0c57.3,0,100-.883,100,100Z"
* Returns:
* [
* { symbol: "M", values: [[200, 0]] },
* { symbol: "c", values: [[57.3, 0], [100, 0.883], [100, -100]] },
* { symbol: "Z" }
* ]
*
* NOTE: because Illustrator, we flip the sign on Y axis coordinates.
* m1b
* 2024-02-02
* {String} svgPathDataString - an SVG path element's data string, eg. "M200,0c57.3,0,100-.883,100,100s-56.589,104.282-75,100".
* {Array<Object>} - array of {symbol: values: }.
*/
function parseSVGPathDataString(svgPathDataString) {
var commands = [],
symbolMatcher = /([MmLlHhVvCcSsQqTtAaZ])([^MmLlHhVvCcSsQqTtAaZ]*)/g,
minus = /(.)-/g,
command,
values,
symbol,
len,
match;
while (match = symbolMatcher.exec(svgPathDataString)) {
symbol = match[1];
command = { symbol: symbol };
command.values = [];
commands.push(command);
if (0 === match[2].length)
continue;
// parse the coordinate values
values = match[2].replace(minus, '$1,-').split(',');
// has single numbers, not arrays
var hasSingleNumbers = (-1 !== 'vhVH'.search(symbol));
// add the values
while (len = values.length) {
if (hasSingleNumbers) {
if ('v' === symbol.toLowerCase())
command.values.push(-Number(values.shift()));
else
command.values.push(Number(values.shift()));
}
else
command.values.push([Number(values.shift()), -Number(values.shift())]);
}
}
return commands;
};
Copy link to clipboard
Copied
Bonjour, pouquoi voulez vous créer cette courbe par script, alors qu'on peut ouvrir le SVG et utiliser Pathfinder soustraction?
Copy link to clipboard
Copied
Hi @TestriteVisual, yes you need all the functions to make this work. Also see updated script where I fixed a bug that René noticed.
But, like @renél80416020, I'm confused about what you are actually doing. If you want to go deeper here, please explain in detail your starting conditions and what you are trying to do. Why do you talk about path finder? Why not just draw the path(s) you want? And what does SVG have to do with it? Why not draw paths normally using pathItems.add and pathPoints.add? It is hard to give sensible answers when we have no idea of what you are doing and why you choose a difficult way.
- Mark
Copy link to clipboard
Copied
Bonjour Mark
Juste une remarque, sur la courbe les points sont doubles...
René
Copy link to clipboard
Copied
@renél80416020 You are right, René! I've fixed the bug and edited the script above. - Mark