BatchPlay: Select layer doesn't work when multiple layers have the same name

Hello,

We’re rebuilding our previous CEP panels to UXP plugins and face some issues. The most recent issue we found is caused by the inability to select layers when a document has duplicate layer names.

Example:

When using Alchemist you can see that in the event only the id changes. Unfortunately the bottom layer is always select. I’m using the code below.

const batchPlay = require("photoshop").action.batchPlay;
const result = batchPlay(
[
    {
        "_obj": "select",
        "_target": [{ "_ref": "layer", "_name": "dupname" }],
        "makeVisible": true,
        "layerID": [3],
        "_isCommand": false,
        "_options": { "dialogOptions": "dontDisplay" }
    }
], {
"synchronousExecution": false,
"modalBehavior": "fail"
});

The code is copy pasted from Alchemist. Has anyone face the same issue and found a possible work around for this?

Found a solution. It seems to be possible to also pass the ID in the target object. The solution was to refer to the id property instead of the name property:

const batchPlay = require("photoshop").action.batchPlay;
const result = batchPlay(
[
   {
      "_obj": "select",
      "_target": [{ "_ref": "layer", "_id": 3}],
      "makeVisible": false,
      "layerID": [3],
      "_isCommand": false,
      "_options": {"dialogOptions": "dontDisplay"}
   }
],{ "synchronousExecution": false, "modalBehavior": "fail"});

how do you obtain the layerID without using Alchemist?
Are you able to obtain the LayerID programmatically via JavaScript?

I believe the above solution only works if you remember the layerID for that specific layer in that specific Photoshop document correct?

For each different Photoshop document then the layerID will always be different correct? or is there a system such as looping through an array of all layers in the photoshop document without having to manually select all layers? if so, how do you do that?

Im having a hard time finding a way to loop through layers, selecting them or setting them as the activeLayer and doing a merge on layer groups… This use to be very simple in CEP… it seems like i have to use the Alchemist plugin for all these simple commands that use to be available as methods in CEP Photoshop API classes/libraries/objects but are increasingly complex in UXP Photoshop API.

If someone could help me select a layer that’s running in a for loop and perform actions on it such as “merge” without having to do some kind of manual maneuver such as using alchemist to get the layerID for every layer for every different Photoshop document/file then it would be heaven sent.

thank you so much

How do you obtain the layerID without using Alchemist?
You can get the layerID via the _id property. I found this by using console.log and watching the debug panel in the UXP developer tool.

I believe the above solution only works if you remember the layerID for that specific layer in that specific Photoshop document correct?
Yes, but there are easy ways to obtain that layer ID

For each different Photoshop document then the layerID will always be different correct?
Correct.

or is there a system such as looping through an array of all layers in the photoshop document without having to manually select all layers? if so, how do you do that?
Yes you can. You can use the layerTree from a document. The code below will collect all layerIds of the top level layers in your document.

let app = window.require('photoshop').app;

let layerIds = [];
for (let i = 0; i < app.activeDocument.layerTree.length; i++) {
    const layer = app.activeDocument.layerTree[i];
    layerIds.push(layer._id);
}

Hopefully this will help you make the next steps!

im not sure what im doing wrong but i keep getting popups from photoshop saying that the select & merge command is currently not available:

The command “Select” is not currently available.
The command “Merge Layers” is not currently available.

im not sure if its absolutely neccessary to push the layerID into an array but from what i see i am properly outputting the correct layer name and correct layer id. The only issue i can see perhaps is that every layer name is the same in my script but the layer ids are all different so it should not matter. Also i tried the code up above and i get the same issue. not sure whats going on.

Here is my code:
https://pastebin.com/iLB82stb

Perhaps my ES6 syntax and Async/Await programming is wrong

also i have an error in my pastebin. Line 51 should have double quotes inside the tidle. below is my intention :

 "_name": `"${layerName}"`

This should be:

"_name": layerName

I also tried just layerName and it did not work for me… I got the same error messages I mentioned.

how do you obtain the layerID without using Alchemist?
Are you able to obtain the LayerID programmatically via JavaScript?

Hey, if that can be of any help, try this simple approach:

const app = require('photoshop').app;
const currentDocument = app.activeDocument;
let allLayers = currentDocument.layers;
let allLayerID = allLayers.map(layer => layer._id);
let allLayerNames = allLayers.map(layer => layer.name);
console.log("allLayerID", allLayerID);
console.log("allLayerNames", allLayerNames);
1 Like

Hey @Pierre_G

i am able to get the layer id using the prop ._id but when i put the layer ID it will not select the layer and it will not merge for each layer ID… Oddly, it only selects and merges the last layer ID in the collection.
Below i have 2 methods, selectLayer() and mergeLayers()… it should select and merge one at a time but it only merges the last layer id in the list.

async function cleanUpDocument() {
    const frameInfoRegEx = /^.*frame.*info\s*$/i;
    const imageCopyRegEx = /^\s*image\s*$|^\s*image.*copy\s*$/i;

    console.log("Cleaning up documents.  Please wait....");

    for await (const document of app.documents) {

        const rootLayers = document.layerTree.filter(layer => !layer.name.match(frameInfoRegEx));

        console.log(`# of Layers in root: ${rootLayers.length}`);

        if (rootLayers.length > 0) {

            for await (const rootLayer of rootLayers) {

                // console.log(rootLayer.name);

                for await (const child of rootLayer.children) {

                    // console.log(`\t${child.name}`);

                    if (child.isGroupLayer && imageCopyRegEx.test(child.name)) {

                        await selectLayer(child.name, child._id);

                        await mergeLayers();

                        console.log(`child name: ${child.name}, child parent: ${child.parent.name}, LayerID: ${child._id}`);

                        console.log(`Group Merge! ${child.kind} ${child.isGroupLayer}`);

                    } else {

                        if (child.length <= 0) {
                            console.log(`this layer group is empty: ${child.name}, ${child.length}`);
                        }

                    }

                }
            }
        }
    }
}

async function selectLayer(layerName, layerID) {
     return await batchPlay(
        [
            {
                "_obj": "select",
                "_target": [
                    {
                        "_ref": "layer",
                        "_name": layerName
                    }
                ],
                "makeVisible": false,
                "layerID": [
                    layerID
                ],
                "_isCommand": true,
                "_options": {
                    "dialogOptions": "dontDisplay"
                }
            }
        ],{
            "synchronousExecution": false,
            "modalBehavior": "fail"
        });
}

async function mergeLayers() {
    return await batchPlay(
        [
            {
                "_obj": "mergeLayersNew",
                "_isCommand": true,
                "_options": {
                    "dialogOptions": "dontDisplay"
                }
            }
        ], {
            "synchronousExecution": false,
            "modalBehavior": "fail"
        })
}

document.getElementById("cleanDocsBtn").addEventListener("click", cleanUpDocument);

If you want to reference a layer by its ID, you have to put it into the target object like

{
    "_obj": "select",
    "_target": [
        {
            "_ref": "layer",
            "_id": layerID
        }
    ],
    ...
}

also, you can omit the "_isCommand": true, it doesn’t have any effect.

1 Like

Ok ill try that but could you give me the whole code there… I see you have some continuation on your code by the tripe dots … that would be great
Thank you very much!

I was just looking at your example, you can take the rest from there and try it.

wow after all this time, that worked! if only there was a .mergeLayerGroup() method!!! Then i would not have to suffer with batchPlay. Thank you kind sir!!!

1 Like

It is only working partially for me.
I have an array with all the required layers IDs with a loop running and selecting the layers by ID but it only selects 1 layer, not all the layers in teh array.
It only selects the last layer in teh array of IDs.
It probably selects all but every time it selects a layer it deselects the previous one.

My batch play for selection looks like this:

async function selectLayerById(layerId) {
   const { app, core, action } = require("photoshop");
   await require("photoshop").core.executeAsModal(async (executionControl, descriptor) => {
      //const result = await batchPlay(...
      const result = await batchPlay(
         [
            {
               _obj: "select",
               _target: [
                  {
                     _ref: "layer",
                     _id: layerId
                  }
               ],
               makeVisible: false,
               layerID: [
                  layerId
               ],
               _options: {
                  dialogOptions: "dontDisplay"
               }
            }
         ],{
            synchronousExecution: false,
            modalBehavior: "execute"
         });
      //
   }, {
      "commandName": "selectLayerByName"
   });
}

@karpiyon, this is not really related to original topic and IMO should be as a separate one, but here’s how you should do it… You should select first layer as usual and then each consecutive descriptor must have:

"selectionModifier": {"_enum": "selectionModifierType", "_value": "addToSelection"}

Full function to get descriptors:

const getSelectIdsActions = function (ids) {
    return [
        {
            "_obj": "select",
            "_target": [{"_ref": "layer", "_id": parseInt(ids[0])}],
            "makeVisible": false
        },
        ...ids.slice(1).map(id => (
            {
                "_obj": "select",
                "_target": [{"_ref": "layer", "_id": parseInt(id)}],
                "makeVisible": false,
                "selectionModifier": {"_enum": "selectionModifierType", "_value": "addToSelection"}
            }
        ))
    ];
};
2 Likes