Copy link to clipboard
Copied
I'm working on a script which among other things removes all guides in an image file. It's meant to run as a batch script, so for speed I don't want to save an image if I didn't actually removed anything.
I'm checking the saved property of the active document, but it doesn't seem to change when removing guides (or adding them). Doing this manually in Photoshop triggers a change in the value, but not when I script it. I'm also removing color samplers via the same script, and that changes the value of the saved property as expected.
The only option right now seems to be to save all images regardless of any change, but that will slow things down a lot. Does anyone know why manipulation of guides doesn't change the saved state?
app.open( new File( 'path/to/image' ) );
var theImg = app.activeDocument;
theImg.guides.removeAll();
if ( ! theImg.saved ) {
theImg.save();
}
theImg.close();
Copy link to clipboard
Copied
Does anyone know why manipulation of guides doesn't change the saved state?
I at least don’t.
But instead of relying of the saved-property you could use an if-clause to first check if a file has guides.
if (activeDocument.guides.length > 0) {/*do one thing*/}
else {/*do another thing*/}
Copy link to clipboard
Copied
Yes, I think this is the way I'm going to have to do it. Thanks!
Copy link to clipboard
Copied
If a File has saved guide and you remove the files need to be resaved. Photoshop internally has a Dirty bit for documents and if you try to close a document with its dirty bit set Photoshop will let you know the documnebt has unsaved changes with a prompt to save or not. However I do not know if there is a way to test this bit in a script.
If I open a psd the does not have saved canvas guides. If I look in menu View the item Clear Canvas Guides will be grayed out unavailable. Most of the time in Action or Script if is has coded something like that and it is currently unavailable the script or action will through an error. However not always some commands Photoshop scripts and actions ignore. Like arrange layer to the front or back when they are the front or back and the function is not available the command is just ignored in actions and scripts. So it must be the same for View Clear Canvas guides your scripts are not throwing an error. So you can not tell if the was available or not. If it were the guides would be removed and the dirty bit set.
I do not know if Photoshop Scripting exposes this dirty bit to the script writer. That is what your script would need to test to see if a save is requited or not.
A document opened from a file would for sure have a backing file a saved file it is what you opened. The document opened in Photoshop may be dirty or not and would need to be saved again to save the current changes.
Copy link to clipboard
Copied
That's the problem. The dirty state doesn't change when removing or adding guides via the script. I expect it to work just like you describe, but it doesn't.
Copy link to clipboard
Copied
How does one test the dirty state?
Copy link to clipboard
Copied
The saved property should indicate if anything has been changed since the last save-operation.
From the Object Model Viewer:
Document.saved (Read Only)
Data Type: Boolean
Adobe Photoshop CC 2015.5 Object Library
If true, the document been saved since the last change.
Copy link to clipboard
Copied
Thank you I knew it existed I just could not find it I expected it to be call dirty and search did not find dirty. Now I read it does not work correctly via scripting which make me wonder about actions. I know if I clear a guides that have been saved into a file via menu view>Clear canvas guides. If I close out the document Photoshop will prompt ask if I want to save the document first. So it works via Photoshop UI.
Copy link to clipboard
Copied
As that’s a DOM property it might be worth checking out if it behaves the same in AM code.
Copy link to clipboard
Copied
Does app.refresh() help?
I remember once in the past I had to add it to make a history status change stick.
Copy link to clipboard
Copied
I would try "suspendHistory" so you can force photoshop to create new history step. Even if nothing happens.
app.refresh() can be slow 😉
Copy link to clipboard
Copied
app.refresh is one of those all-purpose medicaments 🙂 True, it's slow.
In my case, I already had a complex routine within a suspendHistory, but I had to store a history status in a variable, and in some cases, it didn't stick unless I app.refresh. Go figure.
Copy link to clipboard
Copied
That is possible. Anyway I would try first this. This should create history step everytime. So we probably want add condition if there was one or more guides and in this case create history step. Which means we don't need history step. We just need save document if there was one or more guides.
- app.open( new File( 'path/to/image' ) );
- var theImg = app.activeDocument;
- theImg.guides.removeAll();
- app.activeDocument.suspendHistory("Make it dirty",function(){});
- if ( ! theImg.saved ) {
- theImg.save();
- }
- theImg.close();
Copy link to clipboard
Copied
I never used activeDocument.saved yet. For documents with changes it prints true, for unchanged documents false. Problem is that just created (not saved yet) document with no changes also is seen as saved (just for information)
Ps. recently I had very similar situation that only there came to me I can do was to create empty suspendHistory function.
Copy link to clipboard
Copied
use such function instead of guides.removeAll()
function clear_guides()
{
try {
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated( charIDToTypeID( "Gd " ), charIDToTypeID( "Ordn" ), charIDToTypeID( "Al " ) );
d.putReference( charIDToTypeID( "null" ), r );
executeAction( charIDToTypeID( "Dlt " ), d, DialogModes.NO );
d = null;
r = null;
}
catch (e) { alert(e); throw(e); }
}
Copy link to clipboard
Copied
Although Jarda Bereza suspendHistory() solution is functional I think correct would be to use r-bin Action Manager code. The question is why activeDocument.guides.removeAll() doesn't work for Photoshop same like clear_guides() function if we know DOM is based on AM code? My approach to problem (using AM code though) but with checking guides length:
if ((aD = activeDocument).guides.length) {
function sTT(v) {return stringIDToTypeID(v)}
(ref1 = new ActionReference()).putEnumerated
(sTT('good'), sTT('ordinal'), sTT('allEnum'));
(dsc1 = new ActionDescriptor()).putReference(sTT('null'), ref1)
executeAction(sTT('delete'), dsc1, DialogModes.NO), aD.save()
}
aD.close()
Copy link to clipboard
Copied
Because in AM there is no code to delete one guide.
And the removeAl()l method for guides, as for many other collections, deletes the guides one by one.
Run the script. Then uncomment the clear_guides() and comment removeAll(). Compare the speed.
for (var i = 0; i < 100; i++)
activeDocument.guides.add(Direction.HORIZONTAL, UnitValue(i, "px"))
activeDocument.guides.removeAll()
//clear_guides()
P.S. alert(get_prop_value("document", null, "numberOfGuides"))
Copy link to clipboard
Copied
Okey. I understand if Action Manager deletes all guides at once (I saw it running your fantastic snippet) then .removeAll() method (that deletes singly) is not based on AM code. So conclusion is, not everything what we have in DOM comes from AM, right? Well how is that .removeAll() methos recogines single lines while AM doesn't?
Whatever is answer it should be easy to delete certain line (one of many) by rewritting binaries. I don't have time to do it now, but maybe one day when someone needs...
btw, how to use that: alert(get_prop_value("document", null, "numberOfGuides"))
Copy link to clipboard
Copied
I think RemoveAll() is implemented approximately like this
while(activeDocument.guides.length) activeDocument.guides[0].remove();
By the way, I was mistaken about the fact that you can not delete one guide through AM. It is possible so
var r = new ActionReference();
r.putIndex( charIDToTypeID( "Gd " ), 1 );
var d = new ActionDescriptor();
d.putReference( charIDToTypeID( "null" ), r );
executeAction( charIDToTypeID( "Dlt " ), d, DialogModes.NO );
Strangely, but this action does not cause History State to appear and is not recorded. A bug?
P.S. I gave you the code for get_prop_value() Modifying Levels Adjustment Layer? Or not?
Copy link to clipboard
Copied
r-bin: Finaly I am able to derive AM code for geting guide ActionDescriptor
var r = new ActionReference();
r.putIndex( charIDToTypeID( "Gd " ), 1 );
var desc = executeActionGet( r );
Works also for artboard guides
Copy link to clipboard
Copied
Strange is that many times when I wanted to select other than selected item (like for ex. some layer) I didn't use:
(ref1 = new ActionReference()).putEnumerated(sTT('good'), sTT('ordinal'), sTT('allEnum'))
but now for some reason I simply forget to try the same with guides:
(ref1 = new ActionReference()).putIndex(sTT('good'), 1)
Fortunately you noticed so obvious thing
I don't think it's a bug. Guides are extra stuff, not part of your artLayer like channels. But pathItems are extra stuff too and they are recorded. Anyway Guides are more like accessory stuff.
Yes I'm sorry I had no time to focus on that other topic yet enough, so I simply forgot
Copy link to clipboard
Copied
And I think this is a bug, since it is impossible to perform undo.
The same situation with the annotations is working fine.
var r = new ActionReference();
r.putIndex( stringIDToTypeID( "annotation" ), 1 );
var d = new ActionDescriptor();
d.putReference( charIDToTypeID( "null" ), r );
executeAction( charIDToTypeID( "Dlt " ), d, DialogModes.NO );
P.S. Do not you bother with sTT ('good')?
Why not use sTT ('guide')?
Copy link to clipboard
Copied
Yes, then you are right. Annotations are possibly same kind of accessory like guides are. They should work the same way.
That there is used 'good', not 'guide' is because it was converted so by my 'AM Replacement' script (Clean SL) that changes charIDToTypeID to stringIDToTypeID.
I noticed it changes 'Gd ' to first String it finds (alphabetically) so 'good'. 'u' in 'guide' is greater than 'o' in 'good', so it stops at 'good', however for some reason it still works 😕
That's not single case it happens. I find once for a while other uncorrect convertions that works as well.
I could ask you the same question why did you use 'paint' instead of 'point' in your code:
var d2 = new ActionDescriptor()
var anchor = new ActionDescriptor()
anchor.putUnitDouble(stringIDToTypeID("horizontal"), charIDToTypeID("#Rlt"), pp.anchor[0])
anchor.putUnitDouble(stringIDToTypeID("vertical"), charIDToTypeID("#Rlt"), pp.anchor[1])
d2.putObject(stringIDToTypeID("anchor"), stringIDToTypeID("paint"), anchor)
So like you see there is 'paint' (that works), but correctly should be 'point' (what works too) Check that in this topic: Add subpath to existing path
Here's my code I created (before I found yours - so that is not based on code from that topic), where my converter used 'paint' instead of 'point' too (as 'a' ASCII character is smaller than 'o'):
function subPaths() {function lar(v) {function pos(v1, v2) {
(dsc5 = new ActionDescriptor()).putUnitDouble(sTT('horizontal'),
sTT('pixelsUnit' ), v1[0]), dsc5.putUnitDouble(sTT('vertical'),
sTT('pixelsUnit'), v1[1]), dsc4.putObject(sTT(v2), sTT('paint'), dsc5)
}
dsc4 = new ActionDescriptor(), pos(v[0], 'backward'), pos(v[1], 'anchor')
pos(v[2], 'forward'), lst3.putObject(sTT('null'), dsc4)
}
lst3 = new ActionList(); for(i = 0; i < baf.length; i++) lar(baf);
(dsc3 = new ActionDescriptor()).putBoolean(sTT('closedSubpath'), true)
dsc3.putList(sTT('points'), lst3); (lst2 = new ActionList()).putObject(sTT('null'), dsc3);
(dsc2 = new ActionDescriptor()).putList(sTT('subpathListKey'), lst2); return dsc2
}
a=[t = 300, t], b=[f = 400, t], c=[f, f], d=[t, f]
function sTT(v) {return stringIDToTypeID(v)}
baf = [[a, a, a], [b, b, b], [c, c, c], [d, d, d]];
(ref1 = new ActionReference()).putProperty(sTT('path'), sTT('workPath'));
(dsc1 = new ActionDescriptor()).putReference(sTT('null'), ref1);
(lst1 = new ActionList()).putObject(sTT('pathComponent'), subPaths())
dsc1.putList(sTT('to'), lst1), executeAction(sTT('set'), dsc1, DialogModes.NO)
End of 4th line:
sTT('pixelsUnit'), v1[1]), dsc4.putObject(sTT(v2), sTT('paint'), dsc5) // sTT('point')
How that happened you used 'paint', not 'point' (that is equivalent to 4-char ID 'Pnt ')? Do you use some wrongly working converter too like me? (however I have noticed you prefer to use - working faster? - Chars instead of Strings in your scripts)
Copy link to clipboard
Copied
It was just a typo, and I simply copypasted it ))
Here, if interesting or do not know the list of all the duplicates I found
var dups =
[
["shapingCurve", "shapeCurveType", 1399353411, "ShpC"],
["IEEE32BitFloatingPoint", "sMFloat", 1936289383, "sing"],
["IEEE32BitFloatingPoint", "shortFloat", 1936289383, "sing"],
["IEEE64BitFloatingPoint", "floatType", 1685026146, "doub"],
["IEEE64BitFloatingPoint", "longFloat", 1685026146, "doub"],
["JPEGFormat", "JPEG", 1246774599, "JPEG"],
["NTSCColors", "NTSC", 1314149187, "NTSC"],
["RGBSetupSource", "RGBSetup", 1380401747, "RGBS"],
["TIFFFormat", "TIFF", 1414088262, "TIFF"],
["align", "alignment", 1097623406, "Algn"],
["antiAlias", "antiAliasedPICTAcquire", 1097757761, "AntA"],
["backgroundLayer", "backgroundLevel", 1113811788, "BckL"],
["blackGenerationCurve", "blackGenerationType", 1114399559, "BlcG"],
["blackLevel", "blackLimit", 1114399564, "BlcL"],
["blacks", "blocks", 1114401651, "Blks"],
["blurMethod", "blurMore", 1114403405, "BlrM"],
["brightnessContrast", "brightnessEvent", 1114793795, "BrgC"],
["brushDetail", "brushesDefine", 1114796868, "BrsD"],
["brush", "brushes", 1114796904, "Brsh"],
["calculation", "calculations", 1131176812, "Clcl"],
["centerCropMarks", "conteCrayon", 1131312195, "CntC"],
["center", "contrast", 1131312242, "Cntr"],
["char", "textType", 1413830740, "TEXT"],
["colorPalette", "coloredPencil", 1131180624, "ClrP"],
["comp", "sInt64", 1668246896, "comp"],
["constant", "constrain", 1131311988, "Cnst"],
["createDroplet", "createDuplicate", 1131574340, "CrtD"],
["customPalette", "customPhosphors", 1131639888, "CstP"],
["customPattern", "custom", 1131639917, "Cstm"],
["darken", "darkness", 1148349294, "Drkn"],
["desaturate", "destWhiteMax", 1148417140, "Dstt"],
["distort", "distortion", 1148417138, "Dstr"],
["distort", "distribute", 1148417138, "Dstr"],
["distort", "distribution", 1148417138, "Dstr"],
["distortion", "distribute", 1148417138, "Dstr"],
["distortion", "distribution", 1148417138, "Dstr"],
["distribute", "distribution", 1148417138, "Dstr"],
["fileInfo", "fillInverse", 1181501806, "FlIn"],
["floatType", "longFloat", 1685026146, "doub"],
["generalPreferences", "generalPrefs", 1198420560, "GnrP"],
["generalPreferences", "preferencesClass", 1198420560, "GnrP"],
["generalPrefs", "preferencesClass", 1198420560, "GnrP"],
["good", "guide", 1197744160, "Gd "],
["gradientClassEvent", "gridMinor", 1198679150, "Grdn"],
["grainStippled", "graySetup", 1198674804, "GrSt"],
["grain", "green", 1198681632, "Grn "],
["graininess", "greens", 1198681715, "Grns"],
["historyPreferences", "historyPrefs", 1215525968, "HstP"],
["historyStateSourceType", "historyState", 1215525971, "HstS"],
["imageCachePreferences", "imagePoint", 1231906640, "ImgP"],
["in", "stampIn", 1231953952, "In "],
["inherits", "pInherits", 1665147742, "c@#^"],
["integer", "longInteger", 1819242087, "long"],
["integer", "sInt32", 1819242087, "long"],
["interfaceIconFrameDimmed", "interlace", 1231975538, "Intr"],
["interfaceIconFrameDimmed", "interpolation", 1231975538, "Intr"],
["interfaceIconFrameDimmed", "intersect", 1231975538, "Intr"],
["interfaceWhite", "intersectWith", 1231975511, "IntW"],
["interlace", "interpolation", 1231975538, "Intr"],
["interlace", "intersect", 1231975538, "Intr"],
["interpolation", "intersect", 1231975538, "Intr"],
["lens", "lines", 1282306848, "Lns "],
["lightDirection", "lightDirectional", 1281845316, "LghD"],
["lightOmni", "lightenOnly", 1281845327, "LghO"],
["lightSource", "lightSpot", 1281845331, "LghS"],
["longInteger", "sInt32", 1819242087, "long"],
["magenta", "magentas", 1298624116, "Mgnt"],
["magnitude", "uInt32", 1835100014, "magn"],
["maximumQuality", "maximum", 1299737888, "Mxm "],
["paint", "point", 1349415968, "Pnt "],
["null", "target", 1853189228, "null"],
["numberOfLayers", "numberOfLevels", 1315791436, "NmbL"],
["pencilEraser", "pencilWidth", 1349411692, "Pncl"],
["perspectiveIndex", "perspective", 1349677936, "Prsp"],
["pluginPicker", "pluginPrefs", 1349281616, "PlgP"],
["posterization", "posterize", 1349743730, "Pstr"],
["previewMacThumbnail", "previewMagenta", 1349678669, "PrvM"],
["radius", "reds", 1382314784, "Rds "],
["sInt16", "sMInt", 1936224114, "shor"],
["sInt16", "shortInteger", 1936224114, "shor"],
["sMFloat", "shortFloat", 1936289383, "sing"],
["sMInt", "shortInteger", 1936224114, "shor"],
["saturation", "start", 1400140404, "Strt"],
["scratchDisks", "screenDot", 1399026244, "ScrD"],
["separationSetup", "sprayedStrokes", 1399878227, "SprS"],
["shadingIntensity", "shadowIntensity", 1399350345, "ShdI"],
["sharpenEdges", "shearEd", 1399353925, "ShrE"],
["sharpen", "sharpness", 1399353968, "Shrp"],
["splitChannels", "supplementalCategories", 1399876675, "SplC"],
["spotColor", "spot", 1399877492, "Spot"],
["strokeLength", "strokeLocation", 1400140364, "StrL"],
["textClickPoint", "textureCoverage", 1417180227, "TxtC"],
["textureFile", "textureFill", 1417180230, "TxtF"],
["toggleOptionsPalette", "toggleOthers", 1416064079, "TglO"],
["transferSpec", "transparencyShape", 1416785491, "TrnS"],
["transferSpec", "transparencyStop", 1416785491, "TrnS"],
["transparencyGamutPreferences", "transparencyGridSize", 1416785479, "TrnG"],
["transparencyGamutPreferences", "transparencyGrid", 1416785479, "TrnG"],
["transparencyGridSize", "transparencyGrid", 1416785479, "TrnG"],
["transparencyShape", "transparencyStop", 1416785491, "TrnS"],
["transparency", "transparent", 1416785523, "Trns"],
["userMaskEnabled", "userMaskOptions", 1433629261, "UsrM"]
];
Copy link to clipboard
Copied
Did you use some brute force method to find it all - how you did you could find duplicated strings - I was able only first
["grain", "green", 1198681632, "Grn "] / does 'green' work finally in new release? Did not in previous Ps's only 'grain' work?