Copy link to clipboard
Copied
Hi Folks,
So i searched quite a bit and could not land on any meaningful documentation around this aspect of PS scripting. I am clear that this provides us access to low level functionality not exposed on the DOM but beyond this i am lost. Some questions that might be trivial are listed below
I see lots of code snippets shared, but none gives an insight as to how it was written, how did you get to know the values used as arguments. Any pointers to a proper documentation would be helpful
Thanks
-Manan
I started writing scripts a little over a year ago when I did not find a programmer to solve a specific problem, so maybe my experience will come in handy (I apologize in advance - I use a translator to write all this, so some wording may be incorrect).
For most tasks, three things are enough to write Action Manager code: to know the names of all top-level objects, to have at hand a set of functions for each type of objects from the Photoshop JavaScript Reference Guide ADOBE PHOTOSHOP SCRIPTING,
...Copy link to clipboard
Copied
Examine samples in Photoshop Software Development Kit and Presets / Scripts of your Ps folder.
Copy link to clipboard
Copied
Copy link to clipboard
Copied
I started writing scripts a little over a year ago when I did not find a programmer to solve a specific problem, so maybe my experience will come in handy (I apologize in advance - I use a translator to write all this, so some wording may be incorrect).
For most tasks, three things are enough to write Action Manager code: to know the names of all top-level objects, to have at hand a set of functions for each type of objects from the Photoshop JavaScript Reference Guide ADOBE PHOTOSHOP SCRIPTING, to have an installed script listener plugin to see examples of performing operations on objects.
Names of top-level objects: application, document, layer, channel, path, historyState (snapshotClass), action, actionSet
For all these objects (except for the actionSet), we can get the descriptor of the active object using ActionReference (this is something like the path to the desired object) in the same way (only the class name in the first argument changes):
var r = new ActionReference ();
r.putEnumerated (stringIDToTypeID ("document"), stringIDToTypeID ("ordinal"), stringIDToTypeID ("targetEnum"));
var d = executeActionGet (r);
In this case, the descriptor d stores all the properties of the object. Open the Photoshop JavaScript Reference Guide and see what we can do with this object (without knowing anything about it).
For example, we can get property keys in a simple loop:
var str = '';
for (var i = 0; i < d.count; i++)
{ str += typeIDToStringID(d.getKey(i)) + '\n' }
alert(str)
(we can, of course, use ready-made scripts to get the properties of objects (or write yourself), but for a step-by-step example it will be more clear)
We can find the type of each property (and, accordingly, what function we need to use to get this property from the descriptor):
var str = ''
for (var i = 0; i < d.count; i++)
{ str += typeIDToStringID(d.getKey(i)) + ':' + d.getType(d.getKey(i)) + '\n' }
alert(str)
Further, knowing the name and type of the property, we can get its value using one of the functions from the Photoshop JavaScript Reference Guide.
For example, we need to get the number and sizes of artboards. We do not know exactly where they are stored, so we open the document, create the artboard, get the document descriptor and look for something related to artboard there:
Nothing of the kind. We get the descriptor of the active layer:
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
var d = executeActionGet(r);
var str = '';
for (var i = 0; i < d.count; i++)
{ str += typeIDToStringID(d.getKey(i)) + ':' + d.getType(d.getKey(i)) + '\n' }
alert(str)
We see the artboard property, which is of type OBJECTTYPE. We find the function to get the object from the descriptor:
So, we change our function to get this object:
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
var d = executeActionGet(r).getObjectValue(stringIDToTypeID("artboard"));
Since it will return an object to us (another ActionDescriptor), we can again get its properties in the same way:
var str = '';
for (var i = 0; i < d.count; i++)
{ str += typeIDToStringID(d.getKey(i)) + ':' + d.getType(d.getKey(i)) + '\n' }
alert(str)
We see artboardRect which again has the OBJECTTYPE type, we again change our function:
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
var d = executeActionGet(r).getObjectValue(stringIDToTypeID("artboard")).getObjectValue(stringIDToTypeID("artboardRect"));
and get the properties of the object:
Here it is! Since there are only 4 values, we can not bother with receiving each by name, but by index:
var top = d.getDouble(d.getKey(0)),
left = d.getDouble(d.getKey(1)),
bottom = d.getDouble(d.getKey(2)),
right = d.getDouble(d.getKey(3));
Now we need to think about how to get all artboards from the document. We return to a couple of levels above to obtain the properties of the layer and see what can be found useful. We remember, all that we see, switch to the usual layer and get the properties again. We see that the set of properties is different. We assume that you can use the "arboard" property which the normal layer does not have in order to understand - we have before us a regular layer or arboard. This can be done using the .hasKey function, that is, if:
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
var d = executeActionGet(r).hasKey(stringIDToTypeID("artboard"));
will return true, then this is artboard, if not, then the usual layer
We try on artboard - we get true, we try on an ordinary layer - also true. Error? No. Please note that this property appears in all layers of the document as soon as at least one artboard has been created 😞 This method does not suit us.
We get other properties and try to understand how to distinguish an artboard from an ordinary layer. Notice the potentially interesting layerKind property of type INTEGERTYPE.
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
var d = executeActionGet(r).getInteger(stringIDToTypeID('layerKind'));
we get 7. We switch to a regular layer - we get 1. Create a text layer and everything that can come in
head and regretfully understand that layerKind == 7 can be not only on artboard, but also on layerSet (that is, artboard is a kind of layer group). Not that again.
We go through other properties and notice that BOOLEANTYPE artboardEnabled is false for all layers except artboard itself. I.e:
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
var d = executeActionGet(r).getBoolean(stringIDToTypeID('artboardEnabled'));
this is what we need.
Now we are trying to iterate over all layers of the document to find out how much artboard we have. We will do this again through ActionReference, but not through .putEnumerated, but through one of the three functions (the meaning can be understood from their names) -
.putIdentifier - getting the path to the layer by ID, putIndex - by the numeric index, putName - by name. Since we don’t know anything about the document and cannot get IDs and names, we basically have only one option - putIndex.
That is, to get not an active layer, but just an n-layer (for example, the first)), instead
r.putEnumerated (stringIDToTypeID ("layer"), stringIDToTypeID ("ordinal"), stringIDToTypeID ("targetEnum"));
write
r.putIndex (stringIDToTypeID ("layer"), 1)
We return to the document properties and look for some useful property there that will allow us not to iterate over everything in an indefinite loop. We see INTEGERTYPE numberOfLayers - the number of layers (including hidden) in the document.
Here it is necessary to note the features of indexing layers - the index starts with 1 except for those cases when the document has a background layer. If it is, then indexing starts from 0.
We are writing the beginning of our function, in which we find out whether there is a background layer in the document (fortunately, the document property has a BOOLEANTYPE hasBackgroundLayer), if so, then we start enumerating the indices from zero, if not, then from one
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
var from = executeActionGet(r).getBoolean(stringIDToTypeID('hasBackgroundLayer')) ? 0 : 1;
Please note that obtaining the value takes a noticeable time - this is due to the fact that we first get the entire handle of the object, and then select one property from it. This can be slightly optimized if, on the way to the object, you immediately indicate which one we need:
var r = new ActionReference();
r.putProperty(stringIDToTypeID("property"), stringIDToTypeID('hasBackgroundLayer'));
r.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
var from = executeActionGet(r).getBoolean(stringIDToTypeID('hasBackgroundLayer')) ? 0 : 1;
Now the code works almost instantly.
Further:
var r = new ActionReference();
r.putProperty(stringIDToTypeID("property"), stringIDToTypeID('numberOfLayers'))
r.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"))
var to = executeActionGet(r).getInteger(stringIDToTypeID('numberOfLayers'));
Now we write a loop (here I will allow myself some compact code)
var r = new ActionReference();
r.putProperty(stringIDToTypeID("property"), stringIDToTypeID('hasBackgroundLayer'));
r.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
var from = executeActionGet(r).getBoolean(stringIDToTypeID('hasBackgroundLayer')) ? 0 : 1;
var r = new ActionReference();
r.putProperty(stringIDToTypeID("property"), stringIDToTypeID('numberOfLayers'))
r.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"))
var to = executeActionGet(r).getInteger(stringIDToTypeID('numberOfLayers'));
s2t = stringIDToTypeID;
var artboards = [];
for (var i = from; i <= to; i++) {
(r = new ActionReference()).putProperty(s2t("property"), p = s2t('artboardEnabled'));
r.putIndex(s2t("layer"), i);
var artboardEnabled = executeActionGet(r).getBoolean(p);
if (artboardEnabled) {
(r = new ActionReference()).putProperty(s2t("property"), p = s2t('artboard'));
r.putIndex(s2t("layer"), i);
var artboard = executeActionGet(r).getObjectValue(p),
artboardRect = artboard.getObjectValue(s2t("artboardRect")),
bounds = {
top: artboardRect.getDouble(s2t('top')),
left: artboardRect.getDouble(s2t('left')),
bottom: artboardRect.getDouble(s2t('bottom')),
};
artboards.push({ layerIndex: i, bounds: bounds })
}
}
alert(artboards.toSource());
Of course, a more complete understanding of Action Manager code comes gradually after reading forums, viewing scripts from other authors, and communicating with more advanced users (I am infinitely grateful to all the people who respond to requests for help and post interesting code examples).
Copy link to clipboard
Copied
Thanks, this is right what i wanted, really appreciate your writing a detailed answer with supporting code snippets. Now i feel like i can poke around the sdk.
P.S. :- Some of the code snippets are broken, i suppose maybe due to translation software. Things like increment in the for loop, + is absent while appending strings and introduction of extra space like \n is converted to \ n.
-Manan
Copy link to clipboard
Copied
Yes, I wrote in the translator, including the code. Tried to fix it.
While writing, I forgot what the task was - it is clear that width and height are easy to get, knowing the boundaries of the artboard, i.e. height = bottom is top, and width = right is left. Since artboard is essentially an ordinary group, then get the initial state (like it was 0 degrees, and then turned 90, then it means 90 degrees). You can only evaluate orientation by the ratio of height and width (i.e. portrait or landscape).
Copy link to clipboard
Copied
This is absolutely gold mine! Thank you so much for ur post & info!
Also photoshop 2021, appear to no longer have getObjectValue! :- ( Any idea what they replaced it with?
Also... (Im still going over basics with ps 2020 as it still works) if I want to access details on adjustment layer like... contrast, or color lookup, how would I cast the "layer" type in to correct type I need in order to retrieve its details/options?
TIA!
Copy link to clipboard
Copied
Also photoshop 2021, appear to no longer have getObjectValue! :- ( Any idea what they replaced it with?
By @Dariusz1989
Why do you think so? Example?
Copy link to clipboard
Copied
Because 2020 works with this > and 2021 crashes :- )
Copy link to clipboard
Copied
Make sure you created arboard:
sTT = stringIDToTypeID; (ref = new ActionReference())
.putProperty(sTT('property'), artbrd = sTT('artboard'))
ref.putEnumerated(sTT('layer'), sTT('ordinal'), sTT('targetEnum'));
(dsc=executeActionGet(ref)).hasKey(artbrd)&&alert(dsc.getObjectValue(artbrd))
Copy link to clipboard
Copied
Hi jazz-y,
You explain in such a way that it is so easy to understand. 🙂
Highly appreciated.
Copy link to clipboard
Copied
Thank you jazz-y, this is some very valuable information.
In your examples, you used a lot of code specific to the active document of layer, using putEnumerated.
What if I want to get information of a specific layer by the kind of ID I get when using activeLayer.id?
I tried r.putIdentifier(stringIDToTypeID ("layer"),mylayer.id), but it doesn't work.
Any ideas? Thanks.
Copy link to clipboard
Copied
Everything should work, maybe the problem is somewhere else. Post a code snippet.
Copy link to clipboard
Copied
What if I want to get information of a specific layer by the kind of ID I get when using activeLayer.id?
I tried r.putIdentifier(stringIDToTypeID ("layer"),mylayer.id), but it doesn't work.
Any ideas?
By @27shutterclicks
Will not work in CS6 or earlier. What version of Photoshop do you have?
Copy link to clipboard
Copied
@jazz-y Indeed, that code did work and the problem was somewhere else. Thanks for replying.
Copy link to clipboard
Copied
Hi @jazz-y ,
Could you please tell me how to obtain the LISTTYPE and ENUMERATEDTYPE descriptor values?
Thanks.
Copy link to clipboard
Copied
You can use the getList method for Listtype. See an example at the following link
For Enumerator you use the method getEnumerationType and getEnumerationValue method
-Manan
Copy link to clipboard
Copied
Thank you sooo much...
Copy link to clipboard
Copied
Btw I need 2 more help...
One is how to get the Reference type ..
Another one is I'm Trying to get the list of application objects according to @jazz-y .
but in the console, I saw a value which one has no key.
This is the Screenshot and I'm talking about the no.7 object key
Copy link to clipboard
Copied
For some types there is no StringID, but only CharID. If typeIDtoStringID returns an empty string, use typeIDtoCharID.
To determine the ReferenceFormType for a reference, use ref.getForm();
Copy link to clipboard
Copied
Copy link to clipboard
Copied
My vague newb understanding is that charID is for special 4 character ID codes. For example:
// fit on screen ( same as ctrl-0 )
runMenuItem(app.charIDToTypeID("FtOn"));
Some of these codes require a space "Zm " to pad-out to four characters...
I believe that string ID is used for anything that is not a special four-character code. These values will often be found by inserting a menu item into an action and looking at the AM code recorded by the SL plug-in, or just by looking at the raw AM code captured when working.
// actual pixels menu command
runMenuItem(stringIDToTypeID('actualPixels'));
Photoshop_Constants_Rosetta_Stone
I am happy to be corrected if any of the above is incorrect, I'm learning too...
Copy link to clipboard
Copied
I forgot to mention that the previous example of running a menu command only appears to work on menu items that are "simple" and do not require further input, so it is not all 4 character charID codes that can be used exactly as above (this is just my gut feeling from limited exploration).
Copy link to clipboard
Copied
As I understand it, at first only charIDs were used, then (when new commands became inconvenient to describe with 4 characters) stringID was added to them.
So, almost any charID code can be converted to stringID:
alert (typeIDToStringID (charIDToTypeID('FtOn')) == stringIDToTypeID ('fitOnScreen'))
and far from all stringIDs, you can get the corresponding charID:
alert (typeIDToCharID(stringIDToTypeID('placedLayerEditContents')))
That is, the use of stringID is preferable in most situations (it is corny easier to read the code).
However, there are some situations where it is preferable to use only charID (for example, when specifying the name of the event in notifiers). There are also rare charIDs that do not have a stringID, for example charIDToTypeID ("LqFy")
Sometimes it is tempting not to use a charID or stringID, but to immediately indicate a numerical value of a type, however this is an erroneous path - type values may differ not only from version to version of Photoshop, but simply during the execution of the script.
Copy link to clipboard
Copied
Thank you for the info, I built the following for practice:
//community.adobe.com/t5/photoshop/action-manager-scripting/td-p/11160326
//So, almost any charID code can be converted to stringID
var input = prompt("Enter the 4 character charID code:", 'FtOn');
var converter = (typeIDToStringID(charIDToTypeID(input)));
alert("stringID = " + converter + "\r" + "(stringIDToTypeID ('" + converter + "'))");
//community.adobe.com/t5/photoshop/action-manager-scripting/td-p/11160326
//from all stringIDs, you can get the corresponding charID:
var input = prompt("Enter the stringID code:", 'fitOnScreen');
var converter = (typeIDToCharID(stringIDToTypeID(input)));
alert("charID = '" + converter + "'" + "\r" + "(charIDToTypeID ('" + converter + "'))");