Copy link to clipboard
Copied
Hi there!
I was wondering if any of you have got a script that would be able to add an anchor point to the top-most/bottom-most point of a selected path (ex: an arc)?
I know it's a long shot, and I'm not even sure if it's doable. But it's a problem I've run into quite often, so I was wondering if someone has been able to fix it in the past. Thank you!
Hi @Eduard Buta, I've made a script that I think will do what you want. I've made a github page for it and you can download the latest release .zip file. The way you use it is to select *just the path segment(s) you want* and run the script "Add Path Point At Extrema.js". So if you just want a top anchor point, just select the topmost path segment (use direct selection tool and click on the path no on the anchor points) and then run script. If you select the whole path, it will add extreme point
...Copy link to clipboard
Copied
Eduard,
if it is ā for some reason ā required to run the actions via scripts, you can download them here:
It contains four simple scripts that will run the actions in the Point Maker 2 action set.
Instruction:
- Download and unzip the file
- Important: In the Actions palette, first make sure that you've imported the action set point_maker_2.aia
- Select a curved path and run one of the scripts, for example point_maker_top.jsx
That should work pretty well.
Copy link to clipboard
Copied
@Kurt Gold, I'm going to have a try at this one too, purely with scripting solution (see my comment to @femkeblanco). This is just for fun, and not because you haven't already solved it.
- Mark
Copy link to clipboard
Copied
I won't be able to do anything over the weekend, so I thought I would post my progress. Step one, finding the extrema of the segments, is done. The math was not as hard as I thought. (Step two would be redrawing the path with one or more of these points.)
// select path
var doc = app.activeDocument;
var Ps = doc.selection[0].pathPoints;
// iterate segments
for (var i = 0; i < Ps.length - 1; i++) {
var Px = [Ps[i].anchor[0], Ps[i].rightDirection[0], Ps[i + 1].leftDirection[0], Ps[i + 1].anchor[0]];
var Py = [Ps[i].anchor[1], Ps[i].rightDirection[1], Ps[i + 1].leftDirection[1], Ps[i + 1].anchor[1]];
getNDrawPoint();
}
function getNDrawPoint() {
// get cubic Bezier curve extrema
// for further info: https://pomax.github.io/bezierinfo/#extremities
var a = 3 * Py[3] - 9 * Py[2] + 9 * Py[1] - 3 * Py[0];
var b = 6 * Py[0] - 12 * Py[1] + 6 * Py[2];
var c = 3 * Py[1] - 3 * Py[0];
var t1 = (- b + Math.sqrt(b * b - 4 * a * c)) / (2 * a);
if (t1 > 0 && t1 < 1) var p1 = getPoint(Px, Py, t1);
var t2 = (- b - Math.sqrt(b * b - 4 * a * c)) / (2 * a);
if (t2 > 0 && t2 < 1) var p2 = getPoint(Px, Py, t2);
// draw point(s)
if(p1) drawPoint(p1[0], p1[1])
if(p2) drawPoint(p2[0], p2[1])
}
function getPoint(Px, Py, t) {
var x = Px[0] * (1 - t) * (1 - t) * (1 - t) + 3 * Px[1] * t * (1 - t) * (1 - t) + 3 * Px[2] * t * t * (1 - t) + Px[3] * t * t * t;
var y = Py[0] * (1 - t) * (1 - t) * (1 - t) + 3 * Py[1] * t * (1 - t) * (1 - t) + 3 * Py[2] * t * t * (1 - t) + Py[3] * t * t * t;
return [x, y];
}
function drawPoint(x, y) {
var path1 = doc.pathItems.add();
path1.setEntirePath([[x - 0.5, y + 0.5], [x + 0.5, y - 0.5]]);
var path2 = doc.pathItems.add();
path2.setEntirePath([[x + 0.5, y + 0.5], [x - 0.5, y - 0.5]]);
path2.strokeWidth = path1.strokeWidth = 5;
path2.strokeColor = path1.strokeColor = doc.swatches["CMYK red"].color;
var group1 = doc.groupItems.add();
path1.moveToEnd(group1);
path2.moveToEnd(group1);
}
Copy link to clipboard
Copied
I was naive in thinking that it's just a matter of redrawing the path with added anchor points. In reality, all handles will need to be adjusted. It's back to the drawing board. (I'm hoping to solve this independently from @m1b's solution.)
Copy link to clipboard
Copied
An improved action set is availabe here:
It contains two additional actions that create points at all outer peaks of single or multiple selected curved paths.
point_maker_all_single_selection is supposed to work with single selected paths only.
point_maker_all_multiple_selection can handle multiple selected (ungrouped and non-overlapping) paths. It's a bit long-winded and it could be simplified. But this simplification would require some interaction by the user, so I didn't implement that option, accepting that it is just another bumpy ride.
Copy link to clipboard
Copied
Hi @Kurt Gold, I tried point_maker_3 and once again you have performed some incredible action wizardry! Do you record your actions in the normal way, or create/tweak them using the underlying action markup?
- Mark
Copy link to clipboard
Copied
Good Morning, Mark (late evening over here),
usually I try to go the normal (sometimes insane) way without any dirty tricks. Sometimes, however, I tweak them by editing the action code if it is required. Manipulating them this way unfortunately may be a bit risky and can destroy the entire action.
The Point Maker actions just use common commands. At least so far, but it may vary in case it is necessary.
Copy link to clipboard
Copied
Haha yes insane-ly amazing! š
Copy link to clipboard
Copied
Thank you very much Kurt for your implication here. I'm sure these will be super helpful to those that prefer using actions as opposed to scripts. I appreciate the help here! And please excuse my rather late answer.
Copy link to clipboard
Copied
Hi @Eduard Buta, I've made a script that I think will do what you want. I've made a github page for it and you can download the latest release .zip file. The way you use it is to select *just the path segment(s) you want* and run the script "Add Path Point At Extrema.js". So if you just want a top anchor point, just select the topmost path segment (use direct selection tool and click on the path no on the anchor points) and then run script. If you select the whole path, it will add extreme points to every segment.
- Mark
Copy link to clipboard
Copied
That's a very good approach, Mark.
In particular I like that selecting the entire path will add extreme points to every segment, not only to the outermost regions of the whole path.
I have another action that can do that as well, but currently it only works with filled, but unstroked paths. Perhaps I will add it to Point Maker 4 as soon as I find a way that works with stroked paths as well.
Copy link to clipboard
Copied
This is gold, Mark. Thank you very much for your implication. I've even managed to merge the two together since I am using an extension to run these scripts and I was getting an error otherwise. Thank you very much!
Copy link to clipboard
Copied
Nice one! I should have said you can just copy and paste Bez.js in front of the script in the same file and it will work. You can also remove any methods of Bez.js that you don't need for your purposes. It's easiest for me to keep Bez.js separated because I use it for different scripts. There are ways in a CEP extension that you can keep it separate and it will still load properly, but it sounds like it's easy enough how you've done it.
- Mark
P.S. Eduard, your usage of the word "implication" is unusual in English. I think you intend "contribution". Anyway you are welcome. I'm happy to help.
Copy link to clipboard
Copied
Bonjour m1b,
On ne peut ĆŖtre qu'Ć©merveillĆ© devant un tel script, qui remplit parfaitement sa mission dans un temps record.
Avec une modification, il fonctionne sur des versions plus anciennes comme CS, CS2, CS5, CS6, (avec var items = doc.selection;). Vu la rapiditĆ©, j'ai mĆŖme ajoutĆ© un avertissement indiquant que le traitement est terminĆ© (nombre de points crĆ©Ć©s). TestĆ© sur un tracĆ© complexe copiĆ© plusieurs fois avec des inclinaisons diffĆ©rentes, votre script a crĆ©Ć© 408 points presque instantanĆ©ment (en moyenne 12 points par tracĆ©).
Pour ma part, je n'ai jamais pu assimiler toutes le subtilitĆ©s de la programmation objet et ne connaissant pas la fonction getExtremaOfCurve() de Nishio Hirokazu (grand mathĆ©maticien, je prĆ©sume), jāavais imaginĆ© le scĆ©nario suivant:
1 Connaissant le sens du tracƩ, sƩlectionner un point.
- Ce point doit prƩcƩder le sommet convoitƩ.
2 Lancer le script.
- Ici, vous devrez renseigner le choix du mode.
0 pour gauche/droite, Left/Right et 1 pour haut/bas, Top/Bottom.
Le script doit:
Par itĆ©ration, Ć partir de ce point sur un copie du tracĆ©, crĆ©er un nouveaux point jusqu'Ć ce que la tangente Ć la courbe en ce point change dāinclinaison.
Afficher le rƩsultat, si rƩussite: par exemple "ration = 0.89" sinon "ration = undefined "
function test() {
// INIT ----------------------------
var M = 1; // Modes = 1 sommets top-bottom, Modes = 0 left-right
var steep = 0.001; // steep ratio
// ---------------------------------
var regMod, obj, rep, ratio;
regMod = new RegExp("^[01]{0,1}$","g"); // number
obj = app.activeDocument.selection[0];
if (app.selection.length == 0) {return;}
if (obj.typename != "PathItem") {return;}
do {
var rep = prompt("Mode 0 Left/Right 1 Top /Bottom ?",M,"");
}
while (!saisie(rep,regMod) || !rep == null);
if (rep == null || rep == "") {return;}
M = rep*1;
for (var k = 0, r = 0; k < obj.pathPoints.length; k++) {
if (isSelected(obj.pathPoints[k])) {
break;
}
}
alert("Ratio = "+addpT(obj,k,M));
// --------
function addpT (obj,i,M) {
var pnts, Sg, p, pt, tg, q,ecart;
pnts = [];
Sg = 0; // signe ecart 0 pour - 1 pour +
p = obj.pathPoints;
for(var ii = 0; ii < p.length; ii++){
pnts.push(getDat(p[ii]));
}
//alert(pnts.join("\r"))
j = parseIdx(p,i+1);
if (j < 0) return;
for(var t = steep; t <= 1; t += steep) {
tg = obj.duplicate();
p = tg.pathPoints;
q = [p[i].anchor, p[i].rightDirection,
p[j].leftDirection, p[j].anchor];
p[i].rightDirection = linearSprit(q[0],q[1],t);
p[j].leftDirection = linearSprit(q[2],q[3],t);
p.add();
if (j) {
p[j+1].leftDirection = linearSprit(q[2],q[3],t);
p[j+1].anchor = q[3];
p[j+1].rightDirection = p[j].rightDirection;
}
else {
p[j].leftDirection = linearSprit(q[2],q[3],t);
}
p[i+1].anchor = bezier(q,t);
p[i+1].leftDirection = nwDirection (q[0],q[1],q[2],t);
p[i+1].rightDirection = nwDirection (q[1],q[2],q[3],t);
ecart = p[i+1].rightDirection[M]-p[i+1].leftDirection[M];
if (t == steep) {
if (ecart > 0) {
Sg = 1;
}
}
if ((ecart <= 0 && Sg) || (ecart >= 0 && !Sg)) {
p[i+1].leftDirection[M] = p[i+1].rightDirection[M] = p[i+1].anchor[M];
for(var k = i+3; k <= pnts.length; k++){
p[k].anchor = pnts[k-1][0];
p[k].rightDirection = pnts[k-1][1];
p[k].leftDirection = pnts[k-1][2];
p[k].pointType = pnts[k-1][3];
}
obj.remove();
return t;
}
//redraw();
tg.remove();
}
}
// --------
// returns an array for properties of a pathpoint
function getDat(p){ // pathPoint
with(p) return [anchor, rightDirection, leftDirection, pointType];
}
// --------
function linearSprit (p0,p1,t){ //linear bezier
var u = 1-t;
return [p1[0]*t+p0[0]*u,p1[1]*t+p0[1]*u];
}
// --------
function nwDirection(p0,p1,p2,t){ //quadratic bezier
var u = 1-t;
return [u*u*p0[0]+2*u*t*p1[0]+t*t*p2[0],u*u*p0[1]+2*u*t*p1[1]+t*t*p2[1]];
}
// --------
function bezier(q,t) { // identique Ć nwAnchor
var u = 1-t;
return [u*u*u*q[0][0]+3*u*t*(u*q[1][0]+t*q[2][0])+t*t*t*q[3][0],
u*u*u*q[0][1]+3*u*t*(u*q[1][1]+t*q[2][1])+t*t*t*q[3][1]];
}
// --------
function parseIdx(p, n){ // PathPoints, number for index
var len = p.length;
if(p.parent.closed){
return n >= 0 ? n%len : len-Math.abs(n%len);
} else {
return (n < 0 || n > len-1) ? -1 : n;
}
}
// --------
function isSelected(p){ // PathPoint
return p.selected == PathPointSelection.ANCHORPOINT;
}
// --------
function saisie(chaine,reg) // test saisie number
{
if (chaine == null || chaine == "") return true;
if (reg.test(chaine)) return true;
return false;
}
}
// -------
if (app.documents.length) test();
// --------
Sans prƩtension RenƩ
Copy link to clipboard
Copied
Thanks @renĆ©l80416020. You have arrived at an impressive way of solving this problem! I know you are an accomplished problem solver when I saw you have a offset path script. š
I am sad to say, I myself am like a child playing with the toys of adults, putting them together often with little understanding of their workings! Nonetheless, it is a rewarding and fun activity, with it's own trials and challenges to overcome.
I use the objects (Bez and BezPoint, etc) because it helps me compartmentalise the functionality. I can say to myself well it's a BezPoint, so it should be able to do a thing to itself, or calculate something about itself, and that helps me organise everything. I don't always use this structure well but I'm learning.
- Mark