Changing Arrow Parameter on Line Tool
Photoshop 2021 Line Tool is different than previous versions. In previous versions the width of the line/arrow could be set. Now it's set with a stroke. In both the old and new versions, it was difficult to change the arrowhead shapes. You basically had to redraw them. This script will allow you to change an existing arrow and save the preferences so that you can change multiple arrows.
Use at your own risk.
//copyright 2020 Chuck Uebele, all rights reserved
//Use at your own risk.
var xmlFile = new File(Folder.desktop+'/ArrowPrefs2.xml');
var xmlPref
if(xmlFile.exists){
xmlPref = readXMLFile (xmlFile)
}
else{xmlPref = new XML('<root><prefs><arrows/></prefs>')};
var doc = activeDocument
var oldPrefs = app.preferences.rulerUnits
var oldRes = doc.resolution
changeRes (72)
app.preferences.rulerUnits = Units.POINTS;
var dlg
var lineAngTan1;//from first point to oppsite side of line
var lineAngTan2;//From oppsite side of line to first point
var lineLengthAng;
var lineLength;
var arrowWidth;
var arrowHeadMid;
var arrowHeadBase;
var arrowLength;
var concaveLength;
var arrowConcave;
var arrowConcaveFactor;
var newPointArray = [];
var dropLst = new Array()
var prefsDrop
var run = true;
try{
var sLay = doc.activeLayer
var sLayName = sLay.name + ' Shape Path'
var cPath = doc.pathItems.getByName(sLayName)
var ptArray = new Array();
var ptNewArray = new Array();
var idPath;
recordAnchors (cPath);
var numAnchors = ptArray.length;
}
catch(e){
run = false;
var numAnchors=0
}
if(run){main ()}
function main(){
switch (numAnchors){
case 0:
alert('An arrow Layer is not selected');
break;
case 6:
if(Math.round (ptArray[0][0])==Math.round (ptArray[4][0])&&Math.round (ptArray[0][1])==Math.round (ptArray[4][1])){
lineLength = lineL (ptArray[2][0], ptArray[2][1], ptArray[5][0], ptArray[5][1]);
arrowWidth = lineL (ptArray[1][0], ptArray[1][1], ptArray[3][0], ptArray[3][1]);
lineLengthAng = getAng (ptArray[2][0], ptArray[2][1], ptArray[5][0], ptArray[5][1]);
lineAngTan1 = getAng (ptArray[1][0], ptArray[1][1], ptArray[3][0], ptArray[3][1]);
lineAngTan2 = getAng (ptArray[3][0], ptArray[3][1], ptArray[1][0], ptArray[1][1]);
arrowHeadMid = bisect (ptArray[1][0], ptArray[1][1], ptArray[3][0], ptArray[3][1]);
arrowHeadBase = (ptArray[0][0], ptArray[0][1]);
arrowLength = lineL (ptArray[2][0], ptArray[2][1], arrowHeadMid[0], arrowHeadMid[1]);
concaveLength = lineL (ptArray[2][0], ptArray[2][1], ptArray[0][0], ptArray[0][1]);
arrowConcaveFactor = concaveLength/arrowLength;
ui ();
//Get new points
newPointArray[2] = [ptArray[2][0],ptArray[2][1]]; //tip
newPointArray[4] = newPointArray[0] = newPt (ptArray[2][0], ptArray[2][1], -(arrowLength*arrowConcaveFactor), lineLengthAng); // arrow base
var tempPt = newPt (ptArray[2][0], ptArray[2][1], -(arrowLength), lineLengthAng);//temp midpoint for arrow ends
newPointArray[1] = newPt (tempPt[0],tempPt[1], -(arrowWidth)/2, lineAngTan2); //first arrow end
newPointArray[3] = newPt (tempPt[0],tempPt[1], -(arrowWidth)/2, lineAngTan1); //second arrow end
newPointArray[5] = newPt (ptArray[2][0], ptArray[2][1], -(lineLength), lineLengthAng); //arrow end
}
else{
lineLength = lineL (ptArray[0][0], ptArray[0][1], ptArray[3][0], ptArray[3][1]);
arrowWidth = lineL (ptArray[2][0], ptArray[2][1], ptArray[4][0], ptArray[4][1]);
lineLengthAng = getAng (ptArray[0][0], ptArray[0][1], ptArray[3][0], ptArray[3][1]);
lineAngTan1 = getAng (ptArray[2][0], ptArray[2][1], ptArray[4][0], ptArray[4][1]);
lineAngTan2 = getAng (ptArray[4][0], ptArray[4][1], ptArray[2][0], ptArray[2][1]);
arrowHeadMid = bisect (ptArray[2][0], ptArray[2][1], ptArray[4][0], ptArray[4][1]);
arrowHeadBase = (ptArray[3][0], ptArray[3][1]);
arrowLength = lineL (ptArray[3][0], ptArray[3][1], arrowHeadMid[0], arrowHeadMid[1]);
concaveLength = lineL (ptArray[3][0], ptArray[3][1], ptArray[1][0], ptArray[1][1]);
arrowConcaveFactor = concaveLength/arrowLength;
ui ();
//Get new points
newPointArray[3] = [ptArray[3][0],ptArray[3][1]]; //tip
newPointArray[5] = newPointArray[1] = newPt (ptArray[3][0], ptArray[3][1], (arrowLength*arrowConcaveFactor), lineLengthAng); // arrow base
var tempPt = newPt (ptArray[3][0], ptArray[3][1], (arrowLength), lineLengthAng);//temp midpoint for arrow ends
newPointArray[2] = newPt (tempPt[0],tempPt[1], -(arrowWidth)/2, lineAngTan2); //first arrow end
newPointArray[4] = newPt (tempPt[0],tempPt[1], -(arrowWidth)/2, lineAngTan1); //second arrow end
newPointArray[0] = newPt (ptArray[3][0], ptArray[3][1], (lineLength), lineLengthAng); //arrow end
}
break;
case 10:
lineLength = lineL (ptArray[2][0], ptArray[2][1], ptArray[7][0], ptArray[7][1]);
arrowWidth = lineL (ptArray[1][0], ptArray[1][1], ptArray[3][0], ptArray[3][1]);
lineLengthAng = getAng (ptArray[2][0], ptArray[2][1], ptArray[7][0], ptArray[7][1]);
lineAngTan1 = getAng (ptArray[1][0], ptArray[1][1], ptArray[3][0], ptArray[3][1]);
lineAngTan2 = getAng (ptArray[3][0], ptArray[3][1], ptArray[1][0], ptArray[1][1]);
arrowHeadMid = bisect (ptArray[1][0], ptArray[1][1], ptArray[3][0], ptArray[3][1]);
arrowHeadBase = (ptArray[0][0], ptArray[0][1]);
arrowLength = lineL (ptArray[2][0], ptArray[2][1], arrowHeadMid[0], arrowHeadMid[1]);
concaveLength = lineL (ptArray[2][0], ptArray[2][1], ptArray[0][0], ptArray[0][1]);
arrowConcaveFactor = concaveLength/arrowLength;
ui ();
//Get new points
newPointArray[2] = [ptArray[2][0],ptArray[2][1]]; //tip1
newPointArray[7] = [ptArray[7][0],ptArray[7][1]]; //tip2
newPointArray[4] = newPointArray[0] = newPt (ptArray[2][0], ptArray[2][1], -(arrowLength*arrowConcaveFactor), lineLengthAng); // arrow base 1
newPointArray[5] = newPointArray[9] = newPt (ptArray[7][0], ptArray[7][1], (arrowLength*arrowConcaveFactor), lineLengthAng); // arrow base 2
var tempPt1 = newPt (ptArray[2][0], ptArray[2][1], -(arrowLength), lineLengthAng);//temp midpoint for arrow ends 1
var tempPt2 = newPt (ptArray[7][0], ptArray[7][1], (arrowLength), lineLengthAng);//temp midpoint for arrow ends 2
newPointArray[1] = newPt (tempPt1[0],tempPt1[1], -(arrowWidth)/2, lineAngTan2); //first arrow end 1
newPointArray[6] = newPt (tempPt2[0],tempPt2[1], -(arrowWidth)/2, lineAngTan2); //first arrow end 2
newPointArray[3] = newPt (tempPt1[0],tempPt1[1], -(arrowWidth)/2, lineAngTan1); //second arrow end 1
newPointArray[8] = newPt (tempPt2[0],tempPt2[1], -(arrowWidth)/2, lineAngTan1); //second arrow end 2
//newPointArray[5] = newPt (ptArray[2][0], ptArray[2][1], -(lineLength), lineLengthAng); //arrow end
break;
};
var myPathInfo = mkNewPathInfo ();
deltPath ();
var tempPath = app.activeDocument.pathItems.add( 'temp', myPathInfo);
selPath ();
doc.activeLayer = sLay;
mkPath ()
tempPath.remove();
app.preferences.rulerUnits = oldPrefs;
changeRes (oldRes)
}//end function run
function ui(){
dlg = new Window('dialog','Edit Arrow');
dlg.alignChildren = ['left','top'];
dlg.aLgp = dlg.add('group');
dlg.aLgp.alignChildren = ['left','top'];
dlg.aLgp.sTxts = dlg.aLgp.add('statictext',undefined,'Arrow Length');
dlg.aLgp.ALen = dlg.aLgp.add('edittext',undefined,arrowLength.toFixed (2));
dlg.aLgp.ALen.size = [50,12];
dlg.aLgp.sTxte = dlg.aLgp.add('statictext',undefined,'px');
for(var i=0;i<xmlPref.prefs.arrows.children().length();i++){
dropLst[i]= xmlPref.prefs.arrows.children()[i].@preset
}
dlg.aWgp = dlg.add('group');
dlg.aWgp.alignChildren = ['left','top'];
dlg.aWgp.sTxts = dlg.aWgp.add('statictext',undefined,'Arrow Width');
dlg.aWgp.AWid = dlg.aWgp.add('edittext',undefined,arrowWidth.toFixed (2));
dlg.aWgp.AWid.size = [50,12];
dlg.aWgp.sTxte = dlg.aWgp.add('statictext',undefined,'px');
dlg.aConGp = dlg.add('group');
dlg.aConGp.alignChildren = ['left','top'];
dlg.aConGp.sTxts = dlg.aConGp.add('statictext',undefined,'Arrow Concave');
dlg.aConGp.ACon = dlg.aConGp.add('edittext',undefined,((1-arrowConcaveFactor)*100).toFixed (2));
dlg.aConGp.ACon.size = [50,12];
dlg.aConGp.sTxte = dlg.aConGp.add('statictext',undefined,'%');
dlg.btnGp = dlg.add('group');
dlg.btnGp.okay = dlg.btnGp.add('button',undefined,'Okay');
dlg.btnGp.cancel = dlg.btnGp.add('button',undefined,'Cancel');
dlg.prefsPn = dlg.add('panel',undefined,'Preferences');
dlg.prefsPn.alignChildren = ['left','top'];
dlg.prefsPn.save = dlg.prefsPn.add('button',undefined,'Save Preference');
dlg.prefsPn.del = dlg.prefsPn.add('button',undefined,'Delete Preference');
prefsDrop = dlg.prefsPn.add('dropdownlist',undefined,dropLst);
prefsDrop.title = 'Load Preferences';
prefsDrop.size = [300,25];
prefsDrop.onChange = function(){
dlg.aLgp.ALen.text = xmlPref.prefs.arrows.children()[parseInt(prefsDrop.selection)].leng
dlg.aWgp.AWid.text = xmlPref.prefs.arrows.children()[parseInt(prefsDrop.selection)].aWidth
dlg.aConGp.ACon.text = xmlPref.prefs.arrows.children()[parseInt(prefsDrop.selection)].concave
};
dlg.aLgp.ALen.onChange = function(){
textToNum (1, 10000, dlg.aLgp.ALen, arrowLength);
}
dlg.aWgp.AWid.onChange = function(){
textToNum (1, 50000, dlg.aWgp.AWid, arrowWidth);
}
dlg.aConGp.ACon.onChange = function(){
textToNum (-50, 50, dlg.aConGp.ACon, arrowConcaveFactor);
}
dlg.prefsPn.save.onClick = function(){
savePrefs ();
}
dlg.prefsPn.del.onClick = function(){
if(prefsDrop.selection != null){
var selc = parseInt(prefsDrop.selection);
prefsDrop.remove(selc);
delete xmlPref.prefs.arrows.children()[selc];
writeXMLFile (xmlFile, xmlPref)
}
}
dlg.btnGp.okay.onClick = function(){
dlg.close();
arrowLength = parseInt (dlg.aLgp.ALen.text);
arrowWidth = parseInt (dlg.aWgp.AWid.text);
arrowConcaveFactor = (100 - parseInt (dlg.aConGp.ACon.text))*.01;
}
dlg.btnGp.cancel.onClick = function(){
run = false;
dlg.close()
}
dlg.show();
}
function textToNum(sMin,sMax,e,def){
def=Math.round (def)
var sHold = def;
if(isNaN(parseInt(e.text))){
alert('"' + e.text + '" is not a number\nEnter a value between ' + sMin + '-' + sMax );
e.text = def};
else{
sHold = parseInt(e.text)
if(sHold < sMin){
rangeAlert();
sHold = sMin;
};
if(sHold > sMax){
rangeAlert();
sHold = sMax;
};
e.text = sHold;
}
function rangeAlert(){alert('Number range must be between ' + sMin + '-' + sMax)};
};//end function textToNum
function recordAnchors(pathObj){
ptArray = new Array();
for(var i=0;i<pathObj.subPathItems[0].pathPoints.length;i++){
ptNewArray [i] = ptArray[i] = pathObj.subPathItems[0].pathPoints[i].anchor;
}//end loop
}//end function
function lineL (ax,ay,bx,by){
var x = Math.abs (ax-bx);
var y = Math.abs (ay-by);
var len = Math.sqrt (Math.pow (x, 2) + Math.pow (y, 2));
return len
}
function getAng(ax,ay,bx,by){
var dx = ax-bx;
var dy = ay-by;
var theta = Math.atan2 (dy, dx);
//theta *= 180/Math.PI
return theta;
}
function newPt (x1,y1,rad,ang){
var newEnd = new Array();
newEnd[0] = x1 + rad * Math.cos (ang);
newEnd[1] = y1 + rad * Math.sin (ang);
return newEnd;
}
function bisect(x1,y1,x2,y2){
var midPoint = new Array();
midPoint[0] = (x1+x2)/2;
midPoint[1] = (y1+y2)/2;
return midPoint;
}
/////////////////////
function extractSubPathInfo(pathObj,oldPoint,newPoint){
var pathArray = new Array();
var pl = pathObj.subPathItems.length;
for(var s=0;s<pl;s++){
var pArray = new Array();
for(var i=0;i<pathObj.subPathItems[s].pathPoints.length;i++){
pArray[i] = new PathPointInfo;
pArray[i].kind = pathObj.subPathItems[s].pathPoints[i].kind;
if(pathObj.subPathItems[s].pathPoints[i].anchor[0]==oldPoint[0] &&
pathObj.subPathItems[s].pathPoints[i].anchor[1]==oldPoint[1]){
pArray[i].anchor = newPoint;
pArray[i].leftDirection = newPoint;
pArray[i].rightDirection = newPoint;
}else{
pArray[i].anchor = pathObj.subPathItems[s].pathPoints[i].anchor;
pArray[i].leftDirection = pathObj.subPathItems[s].pathPoints[i].leftDirection;
pArray[i].rightDirection = pathObj.subPathItems[s].pathPoints[i].rightDirection;
}
};
pathArray[pathArray.length] = new Array();
pathArray[pathArray.length - 1] = new SubPathInfo();
pathArray[pathArray.length - 1].operation = pathObj.subPathItems[s].operation;
pathArray[pathArray.length - 1].closed = pathObj.subPathItems[s].closed;
pathArray[pathArray.length - 1].entireSubPath = pArray;
};
return pathArray;
};
function deltPath(){
var idDlt = charIDToTypeID( "Dlt " );
var desc2 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref1 = new ActionReference();
idPath = charIDToTypeID( "Path" );
var idvectorMask = stringIDToTypeID( "vectorMask" );
ref1.putEnumerated( idPath, idPath, idvectorMask );
var idLyr = charIDToTypeID( "Lyr " );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
ref1.putEnumerated( idLyr, idOrdn, idTrgt );
desc2.putReference( idnull, ref1 );
executeAction( idDlt, desc2, DialogModes.NO );
}
function selPath(){
var idslct = charIDToTypeID( "slct" );
var desc2 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref1 = new ActionReference();
var idPath = charIDToTypeID( "Path" );
ref1.putName( idPath, "temp" );
desc2.putReference( idnull, ref1 );
executeAction( idslct, desc2, DialogModes.NO );
}
function mkPath(){
var idMk = charIDToTypeID( "Mk " );
var desc10 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref3 = new ActionReference();
var idPath = charIDToTypeID( "Path" );
ref3.putClass( idPath );
desc10.putReference( idnull, ref3 );
var idAt = charIDToTypeID( "At " );
var ref4 = new ActionReference();
var idPath = charIDToTypeID( "Path" );
var idPath = charIDToTypeID( "Path" );
var idvectorMask = stringIDToTypeID( "vectorMask" );
ref4.putEnumerated( idPath, idPath, idvectorMask );
desc10.putReference( idAt, ref4 );
var idUsng = charIDToTypeID( "Usng" );
var ref5 = new ActionReference();
var idPath = charIDToTypeID( "Path" );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
ref5.putEnumerated( idPath, idOrdn, idTrgt );
desc10.putReference( idUsng, ref5 );
executeAction( idMk, desc10, DialogModes.NO );
}
function mkNewPathInfo(){
var pathArray = new Array();
var piArray = new Array();
for(var i=0;i<newPointArray.length;i++){
piArray[i] = new PathPointInfo;
piArray[i].kind = PointKind.CORNERPOINT;
piArray[i].anchor = newPointArray[i];
piArray[i].leftDirection = newPointArray[i];
piArray[i].rightDirection = newPointArray[i];
}//end loop
pathArray[0] = new SubPathInfo();
pathArray[0].operation = ShapeOperation.SHAPEXOR;
pathArray[0].closed = true;
pathArray[0].entireSubPath = piArray;
return pathArray;
};//end function
function changeRes(res){
var idImgS = charIDToTypeID( "ImgS" );
var desc15 = new ActionDescriptor();
var idRslt = charIDToTypeID( "Rslt" );
var idRsl = charIDToTypeID( "#Rsl" );
desc15.putUnitDouble( idRslt, idRsl, res );
executeAction( idImgS, desc15, DialogModes.NO );
}
function savePrefs(){
var newName = prompt ('Enter New Profile Name', '', 'Save Preferences');
var prefGood = true;
for(var i=0;i<prefsDrop.items.length;i++){
if(prefsDrop.items[i].text == newName){
newName = prompt ('That preset exists\nPlease choose a unique name','', 'Save Preferences' );
i=0
}
}
prefsDrop.add('item',newName)
xmlPref.prefs.arrows.appendChild (XML('<arrow preset="'+ newName+ '"><leng>'+dlg.aLgp.ALen.text+'</leng><aWidth>'+dlg.aWgp.AWid.text+'</aWidth><concave>'+dlg.aConGp.ACon.text+'</concave></arrow>'));
writeXMLFile (xmlFile, xmlPref)
}
//===============READ/WRITE functions========================================
//=========================================================================
function readXMLFile(file) {
if (!file.exists) {
alert( "Cannot find file: " + deodeURI(file.absoluteURI));
}
else{
file.encoding = "UTF8";
file.lineFeed = "unix";
file.open("r", "TEXT", "????");
var str = file.read();
file.close();
return new XML(str);
};
};
function writeXMLFile(file, xml) {
if (!(xml instanceof XML)) {
alert( "Bad XML parameter");
};
else{
file.encoding = "UTF8";
file.open("w", "TEXT", "????");
//unicode signature, this is UTF16 but will convert to UTF8 "EF BB BF"
file.write("\uFEFF");
file.lineFeed = "unix";
file.write(xml.toXMLString());
file.close();
};
};
//=========================================================================
