Why doesnt this batchplay call work?

I am trying to learn batchplay by building single step functions, such as duplicate layer, or create a new fill layer. But one of them doesn’t work and I can’t figure out why.

I have gotten duplicate layer to work after grokking some out of date documentation, but Create Fill Layer still fails with no error or debug messages. In addition, there appears to be a spelling error in the command, which makes me even more lost.

Process:

  1. use Plugin> Development > Record Action Command
  2. press the new adjustment layer button in the Layers panel.
  3. select new fill layer.
  4. pick a color.
  5. layer is created, I end the Action Recording.

I then plug this into a function, the same way I did with duplicate layer.
Please make note that the “green” variable was spelled “grain” by Photoshop itself. I have tried both green and grain

function createFillLayer(r=255.0, g=255.0, b=255.0){
    async function _createFillLayer(r, g, b){
        result = await batchPlay(
            [
                {"_obj":"make","_target":[{"_ref":"contentLayer"}],"using":{"_obj":"contentLayer","type":{"_obj":"solidColorLayer","color":{"_obj":"RGBColor","blue":b,"grain":g,"red":r}}}}
                ], {}
        )
        //console.warn(result)
    }
    utils.executeAsModalHelper(_createFillLayer(r, g, b), "fill layer")
    return
}

in Utils.js:

async function executeAsModalHelper (
  targetFunction,
  commandName="myCommand",
  suspendHistory = true
) {
  /*handles errors in execute as modal for you.

    targetFunction : the func to excecute. It must have the following signature:
                    async function targetFunction(executionContext, descriptor)

    options :   1. commandName (optional): A string describing the command.     
                This string is shown in the progress bar UI.

                2. suspendHistory (optional): Should the entire targetFunction be 1 history call? This needs to be re

                3. interactive (optional): A boolean to toggle interactive mode.
    */
  try {
    await executeAsModal(targetFunction, { commandName: commandName })
  } catch (e) {
    if (e.number == 9) {
      showAlert(
        'executeAsModal was rejected (some other plugin is currently inside the modal scope'
      )
      console.error(
        'executeAsModal was rejected (some other plugin is currently inside the modal scope'
      )
    } else {
      // This case is hit if the targetFunction throws an exception
      console.error(str(targetFunction) + ' function failed!')
    }
  }
}


However, this function works fine, and I cant figure what the difference is.

function duplicateLayer(layerToCopy="Color Fill 2", layerIDToCopy=1079, newLayerID=1080){
    async function _duplicateLayer(layerToCopy="Color Fill 2", layerIDToCopy=1079, newLayerID=1080){
    result = await batchPlay(
        [
            {"_obj":"select","_target":[{"_name":"Color Fill 2","_ref":"layer"}],"layerID":[1079],"makeVisible":false},
            {"ID":[1080],"_obj":"duplicate","_target":[{"_enum":"ordinal","_ref":"layer"}],"version":5}
            ], {}
                )
    }

    utils.executeAsModalHelper(_duplicateLayer, "DuplicateLayer")

}

What result do you get from BP?

P. S. grain instead of green is not an error, but some legacy from old days. There’s info on forum.

I think this returns Promise<void>. Instead, you should return the function to execute.

BTW, in your code you never show what _createFillLayer(r, g, b) returns. If it’s not a function, it just can’t work. You should change to

utils.executeAsModalHelper(() => _createFillLayer(r, g, b), "fill layer")

Thanks for the responses and ideas, you guys!
I got this to work. I think it was partially that I was passing in _createFillLayer() using parenthesis, and so it was calling _createFillLayer (which has no return value) when createFillLayer was defined, turning into type “null” instead of a function reference.

So first, I stepped back and redid my executeAsModalHelper to take an array of args and then unpack them using the spread operator “…”. I unpack them in an arrow function so that it doesn’t fire the function during startup. This works pretty well, including for a few other actions I’ve recorded. Here is the new version:


async function executeAsModalHelper (
  targetFunction,
  targetFunctionArgs=[],
  commandName="myCommand"
) {

  try {
    await executeAsModal(()=>{targetFunction(...targetFunctionArgs)}, { commandName: commandName })
  } catch (e) {
    if (e.number == 9) {
      showAlert(
        'executeAsModal was rejected (some other plugin is currently inside the modal scope'
      )
      console.error(
        'executeAsModal was rejected (some other plugin is currently inside the modal scope'
      )
    } else {
      // This case is hit if the targetFunction throws an exception
      console.error(str(targetFunction) + ' function failed!')
      console.warn(e)
    }
  }
}

And now functions which use executeAsModal that are called via buttons follow this template:

function buttonCallback(){
  async function _buttonCallback(arg1, arg2, arg3="blah"){
    await batchPlay( 
    //action Descriptor where arg1, arg2, etc are inserted into. 
    //Dont forget the {} as the second arg of batchPlay!!
    )
  }
  let args = []
  //Code to get args from current document, such as foreground color, active layer, whatever
  executeAsModalHelper(_buttonCallback, args)
}

You can also probably pass arguments into the buttonCallback using either .bind() or an arrow function, in case you want some hard-coded inputs per-button. But once you pass at least 1 argument, the first argument slot is filled with I think an event object that is passed from the button itself, to give info about the bubbling phase and other stuff I think? So yeah, first arg slot is auto-filled by that!

Not sure if this is a smart way to do things, but it at least has some organization and batchplay now works!