Exit
  • Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
  • 한국 커뮤니티
0

Best clean way to get number of selected layers within script.

Participant ,
May 15, 2020 May 15, 2020

So,

I'm looking for the best way to count the number of selected layers in a PS document.

 

I've been looking around and stumbled on this piece of code : https://graphicdesign.stackexchange.com/questions/104786/is-it-possible-to-count-the-number-of-layer...

 

It does work, but it works by creating a new group before counting the layers, then undoing it all.  This fires a "Mk " event.  And since the script has an EventListenner looking out for "Mk " events, well... I get stuck in an endless loop...

 

Isn't there a simple and effective way to count the selected layers ?  Actually... I just need to know if there's more than one selected...

 

Many thanks.

 

J.

TOPICS
Actions and scripting , SDK
3.8K
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Mentor , May 15, 2020 May 15, 2020
s2t = stringIDToTypeID;
(r = new ActionReference).putProperty(s2t('property'), p = s2t('targetLayers'));
r.putEnumerated (s2t ('document'), s2t ('ordinal'), s2t ('targetEnum'));
n =  executeActionGet (r).hasKey(p) ?  executeActionGet (r).getList(p).count : 0;
alert (n)
Translate
Adobe
Mentor ,
May 15, 2020 May 15, 2020
s2t = stringIDToTypeID;
(r = new ActionReference).putProperty(s2t('property'), p = s2t('targetLayers'));
r.putEnumerated (s2t ('document'), s2t ('ordinal'), s2t ('targetEnum'));
n =  executeActionGet (r).hasKey(p) ?  executeActionGet (r).getList(p).count : 0;
alert (n)
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
May 15, 2020 May 15, 2020

Hi, This is a nice solution. Could you please explain me (just want to learn), how exactly it is working.

Highly appreciated!

 

Thanks

Best regards
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Mentor ,
May 15, 2020 May 15, 2020

This is the code for the action manager, the Photoshop subsystem. Action manager objects are stored in ActionDescriptors. The names of properties and methods are encoded by numeric keys, which we can receive and convert using functions like stringIDToTypeID.
In that case, we get access to the ActionDescriptor of the current document using the ActionReference (this is something like a link to the desired object). Using the ActionReference, we describe the property that we need. We need to get a list of selected layers - it is stored as an ActionList named 'targetLayers'. To do that we create a new ActionReference, first put the name of the property we need into it (so as not to request the entire document object):
(r = new ActionReference) .putProperty (s2t ('property'), p = s2t ('targetLayers'));
then we indicate that this property should be taken from the active document:
r.putEnumerated (s2t ('document'), s2t ('ordinal'), s2t ('targetEnum'));
Since layers may or may not be selected, then using executeActionGet and the .hasKey () function, we check if we have the property we need, if so, we write the number of elements in the list of selected layers to a variable
executeActionGet (r) .getList (p) .count , if not, then write to the variable 0.

At the same time, it is not difficult to obtain the index of each layer from the resulting list and with its help obtain all the necessary properties of each selected layer.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Jul 19, 2023 Jul 19, 2023

I've been wanting to understand this markup a bit better, as the recorded markup is sometimes verbose and redundant. This is a start. Thanks!

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Jul 19, 2023 Jul 19, 2023

@Deleted User 
You mentioned it wouldn't be difficult to get the list of IDs from this, but I'm not sure I understand how?
I tried an alert/dump on all parts of your script, but at most it seems to have count associated with it.
I thought it was most likely here:
executeActionGet(r).getList(p)
But again it's just the count. Any tips?


Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 20, 2023 Jul 20, 2023
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") ); 
var docDesc = executeActionGet(ref);
var targetLayersIDs = docDesc.getList(stringIDToTypeID("targetLayersIDs"));
var theIDs = new Array;
for (var m = 0; m < targetLayersIDs.count; m++) {
	theIDs.push(targetLayersIDs.getReference(m).getIdentifier())
};
alert ("the ids are\n"+theIDs.join("\n"));
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Jul 20, 2023 Jul 20, 2023

Thanks! In testing it out, I've also realized that the id and itemIndex don't let you manipulate the layer directly: for example app.activeDocument.layers[858] will produce an error for id and app.activeDocument.layers[858] will produce an error for id and app.activeDocument.layers[57] will produce an error for itemIndex specifically. I did find addSelectedLayer() from Flatten All Layers.jsx, which seems to bridge the gap. The script I'm currently working on, sizes the active layer based on the layer below. Ideally I'd be able to reference the second layer without selecting it, but I'm not sure that's necessary?

 

    ///////////////////////////////////////////////////////////////////////////////
    // From: Flatten All Layer Effects.jsx
    // Function: getSelectedLayers
    // Usage: creates and array of the currently selected layers
    // Input: <none> Must have an open document
    // Return: Array selectedLayers
    ///////////////////////////////////////////////////////////////////////////////
function addSelectedLayer( layerIndexOrName ) {
	try {
		var id243 = charIDToTypeID( "slct" );
		var desc46 = new ActionDescriptor();
		var id244 = charIDToTypeID( "null" );
		var ref44 = new ActionReference();
		var id245 = charIDToTypeID( "Lyr " );
		if ( typeof layerIndexOrName == "number" ) {
			ref44.putIndex( id245, layerIndexOrName );
		} else {
			ref44.putName( id245, layerIndexOrName );
		}
		desc46.putReference( id244, ref44 );
		var id246 = stringIDToTypeID( "selectionModifier" );
		var id247 = stringIDToTypeID( "selectionModifierType" );
		var id248 = stringIDToTypeID( "addToSelection" );
		desc46.putEnumerated( id246, id247, id248 );
		var id249 = charIDToTypeID( "MkVs" );
		desc46.putBoolean( id249, false );
		executeAction( id243, desc46, DialogModes.NO );
	}

 

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 20, 2023 Jul 20, 2023

DOM code is »problematic« and doesn’t use the identifier anway. 

And its indexing may, if I remember correctly, be messed up by Groups. (Edit: »messed up« in relation to the AM indices, the indices retrieved via DOM-code work for DOM-code itself fine.)

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 20, 2023 Jul 20, 2023

Also what do you mean by »manipulate« exactly? 

Certain operations can be performed with AM-code by addressing a Layer via index or identifier, for some operations one has to select it first even with AM-code. 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Jul 20, 2023 Jul 20, 2023

Thanks for the reply! What I mean by manipulate, is to refer to, extract data from, or modify the layers directly based on the indices mentioned in AM or DOM references. I don't know how one would get information indirectly(without making a layer active) via AM, were via DOM, I had been attempting to get information via app.activeDocument.layers[id or indexItem]. Even selecting the active layer based on id or indexItem, seems to require AM, correct?

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 20, 2023 Jul 20, 2023

You beem to be pretty vague on this. 

What data do you want to extract from the Layers? 

Which modifications do you want to perform on the Layers? 

 

Getting the seleted Layers’ bounds, blend mode, color, opacity, … can be done without selecting the Layers individually. 

An example for bounds: 

// 2023, use it at your own risk;
#target photoshop
if (app.documents.length > 0) {
    alert (collectSelectedLayersBounds ())
};
////// collect bounds of selected layers //////
function collectSelectedLayersBounds () {
// set to pixels;
    var originalRulerUnits = app.preferences.rulerUnits;
    app.preferences.rulerUnits = Units.PIXELS;
// get selected layers;
    var selectedLayers = new Array;
    var ref = new ActionReference();
    ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
    var desc = executeActionGet(ref);
    if (desc.getBoolean(stringIDToTypeID("hasBackgroundLayer")) == true) {var theAdd =0}
    else {var theAdd = 1};
    if( desc.hasKey( stringIDToTypeID( 'targetLayers' ) ) ){
    desc = desc.getList( stringIDToTypeID( 'targetLayers' ));
    var c = desc.count;
    var selectedLayers = new Array();
    // run through selected layers;
    for(var i=0;i<c;i++){
    var theIndex = desc.getReference( i ).getIndex()+theAdd;
    // get id for solid color layers;
    try {
    var ref = new ActionReference();
    ref.putIndex( charIDToTypeID("Lyr "), theIndex ); 
    var layerDesc = executeActionGet(ref);
    var theName = layerDesc.getString(stringIDToTypeID('name'));
    var theIdentifier = layerDesc.getInteger(stringIDToTypeID ("layerID"));
    var theBounds = layerDesc.getObjectValue(stringIDToTypeID("bounds"));
    var theseBounds = [theBounds.getUnitDoubleValue(stringIDToTypeID("left")), theBounds.getUnitDoubleValue(stringIDToTypeID("top")), theBounds.getUnitDoubleValue(stringIDToTypeID("right")), theBounds.getUnitDoubleValue(stringIDToTypeID("bottom"))];
    selectedLayers.push([theName, theIdentifier, theseBounds]);
    } catch (e) {};
    };
    // if only one:
    }else{
    var ref = new ActionReference();
    ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
    var layerDesc = executeActionGet(ref);
    try {
    var theName = layerDesc.getString(stringIDToTypeID('name'));
    var theIdentifier = layerDesc.getInteger(stringIDToTypeID ("layerID"));
    var theBounds = layerDesc.getObjectValue(stringIDToTypeID("bounds"));
    var theseBounds = [theBounds.getUnitDoubleValue(stringIDToTypeID("left")), theBounds.getUnitDoubleValue(stringIDToTypeID("top")), theBounds.getUnitDoubleValue(stringIDToTypeID("right")), theBounds.getUnitDoubleValue(stringIDToTypeID("bottom"))];
    selectedLayers = [[theName, theIdentifier, theseBounds]]
    } catch (e) {};
    };
// reset;
    app.preferences.rulerUnits = originalRulerUnits;
    return selectedLayers;
};

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Jul 20, 2023 Jul 20, 2023

Sorry, not trying to be vague. I'm trying to understand if there's a simple way to reference layer 2 based on active layer 1. Originally I had hard coded 0 and 1, which limits placement within the document. The file I was working on, I wanted to get properties from itemIndex 57(selected) and 58, which seems as though you'd need AM to do that.

function psDims(theLayer) {
    /* Left, Top, Right and Bottom
    + - 1 - +
    |       |
    0       2
    |       |
    + - 3 - +
    */
    var bounds = theLayer.bounds;
    left = bounds[0];
    top = bounds[1];
    right = bounds[2];
    bottom = bounds[3];

    width = right - left;
    height = bottom - top;

    props = {
        lyr: theLayer,
        left: left,
        top: top,
        right: right,
        bottom: bottom,
        width: width,
        height: height
    };
    return props;
}
    aD = app.activeDocument;
    layer1 = psDims(aD.artLayers[0]);
    layer2 = psDims(aD.artLayers[1]);

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 20, 2023 Jul 20, 2023

If I understand correctly this would get the bounds-information; otherwise please post a description including meaningful screenshots including the pertinent Panels: 

 

edited; sorry, I had mixed up index and identifier

 

 

var theID = getSelectedLayerIndex();
var bounds1 = getLayerBoundsByIndex(theID);
var bounds2 = getLayerBoundsByIndex(theID+1);
alert ("\nbounds selected layer\n"+bounds1.join("\n")+"\n\nbounds higher layer\n"+bounds2.join("\n"))
// by mike hale;
// http://ps-scripts.com/bb/viewtopic.php?f=9&t=3217&p=14468&hilit=selectLayerByIndex&sid=95f9e770da5eb2014c336c0c67802704#p14468
function getSelectedLayerIndex() { 
try{
activeDocument.backgroundLayer;
var mod = 1;
}catch(e){
var mod = 0
}
var ref = new ActionReference(); 
ref.putProperty( charIDToTypeID("Prpr") , charIDToTypeID( "ItmI" )); 
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") ); 
return desc = executeActionGet(ref).getInteger(charIDToTypeID( "ItmI" ))-mod;
};
function getLayerBoundsByIndex( idx ) { 
var ref = new ActionReference(); 
ref.putProperty( charIDToTypeID("Prpr") , stringIDToTypeID( "bounds" )); 
ref.putIndex( charIDToTypeID( "Lyr " ), idx );
var desc = executeActionGet(ref).getObjectValue(stringIDToTypeID( "bounds" ));
var bounds = [];
bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('left')));
bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('top')));
bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('right')));
bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('bottom'))); 
return bounds;
};

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Jul 20, 2023 Jul 20, 2023

Thanks for this. Is the below snippet to avoid errors because Photoshop doesn't index the background layers?

try{
activeDocument.backgroundLayer;
var mod = 1;
}catch(e){
var mod = 0
}

 The most helpful piece for me is.

function getLayerBoundsByIndex(idx) {
    var ref = new ActionReference();
    ref.putProperty(charIDToTypeID("Prpr"), stringIDToTypeID("bounds"));
    ref.putIndex(charIDToTypeID("Lyr "), idx);
    var desc = executeActionGet(ref).getObjectValue(stringIDToTypeID("bounds"));
    var bounds = [];
    bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('left')));
    bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('top')));
    bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('right')));
    bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('bottom')));
    return bounds;
};
//Added some alerts for testing
aL = app.activeDocument.activeLayer;
aLID = aL.itemIndex;
alert(getLayerBoundsByIndex(aLID));
alert(getLayerBoundsByIndex(aLID - 1));


With bounds as an example, I think what I was trying to do/ask, is if there is a way to read a layer from AM into a layer object that can be accessed through DOM. The only way I could find was to select a layer, and then access its attributes/properties via activeLayer. I don't really have a great reason for going this route other than it's easier for me to understand.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 20, 2023 Jul 20, 2023
LATEST

As far as I know you are correct and a Layer identified with AM-code can only be realiably addressed for DOM-code by selecting it. 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
May 16, 2020 May 16, 2020

This works perfectly.  Many thanks.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
May 15, 2020 May 15, 2020

Hi,

This is another version of the script that you have shared above.

 

var doc = app.activeDocument;
var layers = doc.layers;

function getSelectedLayers() {
    var resultLayers = new Array();
    try {
        var idGrp = stringIDToTypeID("groupLayersEvent");
        var descGrp = new ActionDescriptor();
        var refGrp = new ActionReference();
        refGrp.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
        descGrp.putReference(charIDToTypeID("null"), refGrp);
        executeAction(idGrp, descGrp, DialogModes.NO);
        for (var i = 0; i < app.activeDocument.activeLayer.layers.length; i++) {
            resultLayers.push(app.activeDocument.activeLayer.layers[i])
        }
        var id8 = charIDToTypeID("slct");
        var desc5 = new ActionDescriptor();
        var id9 = charIDToTypeID("null");
        var ref2 = new ActionReference();
        var id10 = charIDToTypeID("HstS");
        var id11 = charIDToTypeID("Ordn");
        var id12 = charIDToTypeID("Prvs");
        ref2.putEnumerated(id10, id11, id12);
        desc5.putReference(id9, ref2);
        executeAction(id8, desc5, DialogModes.NO);
        return resultLayers;
    } catch (e) {
        alert("No Layer selected or locked layer selected");
        return resultLayers;
    }
}
var selectedLayers = getSelectedLayers();
$.writeln(selectedLayers.length);

 

This will give you an array of selected layers. I hope this helps

 

Thanks

Best regards
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Nov 07, 2020 Nov 07, 2020

This helped me a lot, thanks

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines