API Extension - batchPlay and artboardEnabled

Hiya!
I’m trying to get my head around batchPlay as someone who used ExtendScript extensively (lol), but didn’t use ActionDescriptors much at all.
I’ve written the below as a starting point for a “get artboards” API extension. I’m currently targeting a single layer, but my end goal is an array of artboard objects.
When run, the Promise is rejected with an error code of 2000, and the value “Error: NAPI API failure: String expected”.
I’ll be completely honest, I haven’t the foggiest of how to proceed from here!
I’ve done the obvious and appended the batchPlay function with .toString(), but all I get there is “[object Promise]” as my returned value. I guess I’m not actually getting the artboardEnabled boolean value and am in fact getting some kind of object back from isArtboard()?
Is this even the best way of doing this?

const app = window.require("photoshop").app;
const batchPlay = require("photoshop").action.batchPlay;

require('photoshop').app.Layer.prototype.isArtboard = async () => {
return await batchPlay (
    [{
        "_obj": "get",
        "_target": [{
                "_property": "artboardEnabled"
            },
            {
                "_enum": "ordinal",
                "_value": "targetEnum"
            }
        ],
        "_options": {
            "dialogOptions": "dontDisplay"
        }
    }], {
        "synchronousExecution": false,
        "modalBehavior": "fail"
    });
}

require('photoshop').app.activeDocument.layers[1].isArtboard();

Hey! You might want to try this

function getArtboardEnabled() {
    const batchPlay = require("photoshop").action.batchPlay;

    return batchPlay(
        [{
            "_obj": "get",
            "_target": [{
                    "_property": "artboardEnabled"
                },
                {
                    "_ref": "layer",
                    "_enum": "ordinal",
                    "_value": "targetEnum"
                },
                {
                    "_ref": "document",
                    "_enum": "ordinal",
                    "_value": "targetEnum"
                }
            ]
        }], {
            "synchronousExecution": true
        })[0].artboardEnabled
}
1 Like

Hi Pierre,
thanks, this is perfect!
I understand it except for the penultimate line; what’s the [0].artboardEnabled for after the batchPlay JSON? Like, I get what it is, an array index and an object, that I guess is delivering the “payload” defined in the JSON? I’m also guessing that the [0] index is a reference to the activeDocument? Does batchPlay return an array, which in turn is returned by the function?

OK , with further testing I can see now that batchPlay returns [object object] and the [0].artboardEnabled is returning the value, in theory.
I’m getting inconsistent results - it will return the same result for all layers regardless of their actual artboardEnabled value. I get true for one document and false for another, I can’t work out what’s defining that value and why it isn’t changing:

const app = window.require("photoshop").app;
const batchPlay = require("photoshop").action.batchPlay;
const doc = app.activeDocument;

function getArtboardEnabled() {
    const batchPlay = require("photoshop").action.batchPlay;

    return batchPlay(
        [{
            "_obj": "get",
            "_target": [{
                    "_property": "artboardEnabled"
                },
                {
                    "_ref": "layer",
                    "_enum": "ordinal",
                    "_value": "targetEnum"
                },
                {
                    "_ref": "document",
                    "_enum": "ordinal",
                    "_value": "targetEnum"
                }
            ]
        }], {
            "synchronousExecution": true
        })[0].artboardEnabled
}


for(i=0;i<doc.layers.length;i++){
    console.log(doc.layers[i].name + " " + getArtboardEnabled(doc.layers[i]));
}

See in front of batchPlay, there’s the return instruction. It’s equivalent to:

function getArtboardEnabled() {
    const batchPlay = require("photoshop").action.batchPlay;

    var foo = batchPlay(
        [{
            "_obj": "get",
            "_target": [{
                    "_property": "artboardEnabled"
                },
                {
                    "_ref": "layer",
                    "_enum": "ordinal",
                    "_value": "targetEnum"
                },
                {
                    "_ref": "document",
                    "_enum": "ordinal",
                    "_value": "targetEnum"
                }
            ]
        }], {
            "synchronousExecution": true
        });

    var bar = foo[0].artboardEnabled
    console.log(bar)
}

By default, batchPlay returns Promise<Object[]>, unless you add synchronousExecution:true to the options, then it’s just an Object array.
The index isn’t related to the activeDocument, the function just returns multiple descriptors (objects) since you can also feed it multiple ones.

In case of a getter, this doesn’t make too much sense, but if you’d do something like batchPlay([*makeLayer*, *moveLayer*],{}), it would return a promise including both of the “processed” descriptors.

For example the makeLayer descriptor might look like

  {
      "_obj": "make",
      "_target": [
         {
            "_ref": "layer"
         }
      ]
   }

and will be returned as

  {
      "_obj": "make",
      "_target": [
         {
            "_ref": "layer"
         }
      ],
      "layerID": 3,
   }

including additional information such as the ID that got assigned by PS.

So if you’re specifically targeting a property via a getter, you always have to call [0].property on the result.

1 Like

Please note that this code will work on the current layer of the current document!

If you need to taget a specific layer, then you have to replace

{
    "_ref": "layer",
    "_enum": "ordinal",
    "_value": "targetEnum"
}

by

{
    "_ref": "layer",
    "_id": myLayer_ID // number
}
1 Like

ah, ok!
So what do I do in the instance of this:

for(i=0;i<doc.layers.length;i++){
    if(getArtboardEnabled(doc.layers[i])) {
        arr.push(doc.layers[i])
    }
}

Do I need to get the layer ID and pass it as a variable in the JSON? Or leave the JSON as ordinal/targetEnum and instead set doc.activeLayer = doc.layers[i] in my loop?
Forgive me if I’m asking stupid questions, I’m finding this batchPlay/promises/async stuff quite a steep learning curve!

You don’t need to, but in my opinion that’s the best approach. As you’ve said, you can also set the active layer and then get the desired property, but that’s just one extra Photoshop operation, and those are usually slower so I’d try to minimize them.

1 Like

Banging, I’m not so dumb after all :grinning:
Thanks Simon & Pierre!
Here’s where I ended up should anyone else want to return an array of document artboards:

const app = window.require("photoshop").app;
const doc = app.activeDocument;

var arr = [];

for(i=0;i<doc.layers.length;i++){
    if(getArtboardEnabled(doc.layers[i])) {
        arr.push(doc.layers[i])
    }
}

function getArtboardEnabled(obj) {
    const batchPlay = require("photoshop").action.batchPlay;    
    return batchPlay(
        [{
            "_obj": "get",
            "_target": [{
                    "_property": "artboardEnabled"
                },
                {
                    "_ref": "layer",
                    "_id": obj._id
                }
            ]
        }], {
            "synchronousExecution": true
        })[0].artboardEnabled
}

I sat down and worked on this for a while, and I started to feel like it had all clicked and I was really getting it. What hubris!
The code below all works up until the isLinked() function. I get the following error:
Script snippet %2312:99 Uncaught TypeError: Cannot read property 'smartObject' of undefined
Now, I know that the Smart Object is being passed to isLinked() successfully, so I’m guessing that it’s a pending promise that’s causing it to be undefined?

const app = window.require("photoshop").app;
const batchPlay = require("photoshop").action.batchPlay;
const doc = app.activeDocument;
const artboards = [];
const linkedSmartObjects = [];
const embeddedSmartObjects = [];

getArtboards();

for (i = 0; i < artboards.length; i++) {
  getSmartObjects(artboards[i]);
}

function getArtboards() {
  for (i = 0; i < doc.layers.length; i++) {
    if (getArtboardEnabled(doc.layers[i])) {
      artboards.push(doc.layers[i])
    }
  }

  function getArtboardEnabled(artboard) {
    return batchPlay(
      [{
        "_obj": "get",
        "_target": [{
            "_property": "artboardEnabled"
          },
          {
            "_ref": "layer",
            "_id": artboard._id
          }
        ]
      }], {
        "synchronousExecution": true
      })[0].artboardEnabled
  }
};

function getSmartObjects(artboard) {
  for (i = 0; i < artboard.children.length; i++) {
    if (getLayerKind(artboard.children[i]) === 5) {
      if (isLinked(artboard.children[i])) {
        linkedSmartObjects.push(artboard.children[i])
      } else {
        embeddedSmartObjects.push(artboard.children[i])
      }
    }
  }

  function getLayerKind(layer) {
    return batchPlay(
      [{
        "_obj": "get",
        "_target": [{
            "_property": "layerKind"
          },
          {
            "_ref": "layer",
            "_id": layer._id
          }
        ],
        "_options": {
          "dialogOptions": "dontDisplay"
        }
      }], {
        "synchronousExecution": true,
        "modalBehavior": "fail"
      })[0].layerKind;
  }
  function isLinked(smartObject) {
    return batchPlay(
      [
         {
            "_obj": "get",
            "_target": [
               {
                  "_property": "smartObject"
               },
               {
                  "_ref": "layer",
                  "_id": smartObject._id
               }
            ],
            "_options": {
               "dialogOptions": "dontDisplay"
            }
         }
      ],{
         "synchronousExecution": false,
         "modalBehavior": "fail"
      })[0].smartObject.linked;
  };
};

makes it async, so you’re trying to access the 0-index on an unresolved promise and not an array, which results in undefined.

1 Like

Oops! Move along, nothing to see here :sweat_smile:

Thanks again, I feel like I’m sort of getting it!