Stacking All Open Documents As Layers & Renaming Layers

Anyone have pointers for achieving this?

Stacking all open documents as layers & renaming layers in order i.e. 001 - 100

Coming from CEP, I have the perfect plugin built for stacking all my open documents as layers and then renaming them. I’d just like to update and not have to fight with my CEP plugin.

I’ve got a basic UXP plugin working, there are some specifics in code that I haven’t figured out, because I’m in unfamiliar territory :smiley:

I’d do it like

  • loop open documents by index
  • (optionally merge all layers)
  • duplicate layer (new document as target destination)
  • rename layers

I think renaming can also be done at the duplicate event already. All these events can be recorded with Alchemist.

2 Likes

Hey Simon,

I’m not 100% that would work, more in line with how my past script worked … I tried using Alchemist to record copying one layer, closing that document, pasting into the next document … this does not work. Also I would run into the issue of not knowing how to code a loop. Alchemist is going to want layer ids and document names and that isn’t going to work.

I’m currently looking into the using the ExtendScript BatchPlay logger. I am just in the process of installing a virtual machine to hopefully run the old software.

I really appreciate the help.

Our workflow has been:

Open Camera Raw Files, Post in Adobe Camera Raw, Open as smart objects, This will give us a number of open documents. I had an extendscript for stacking all open documents which essentially collapsed all open documents into the first opened document, then I had to rename all the layers with some reverse layer scripting to ensure our layer order was setup correctly. Layers renamed i.e. Layer 001 - 100

I’ll circle back once I try out the ExtendScript BatchPlay logger.

Thanks for your time!

I can’t seem to figure out how to use ExtendScript BatchPlay logger. Anyone know of a tutorial for this?

This is my stack script I’m trying to rewrite for UXP:

function stackOpenDocuments(){  
#target photoshop
while(app.documents.length>1){
app.activeDocument = app.documents[1];
var layerName = decodeURI(activeDocument.name).replace(/\....$/,'');
activeDocument.activeLayer.duplicate(documents[0]);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
activeDocument.activeLayer.name = layerName; }

In theory, the API can do all this without batchPlay.
I haven’t tested this myself, but all of it is in the API documentation in more detail:
Layers and Document.
If the code is janky, this is also a viable framework for building a batchPlay version.

Edit: the code was janky - see my later response here for working version

The janky code example I wrote at 1am
const {app, core} = require("photoshop");

// the doc to stack into
const docZero = app.documents[0];
// the other open docs
const docsToStack = app.documents.filter((doc) => doc.id !== docZero.id);
// a counter to use for naming layers
const docCounter = 0;

// execute as modal is required for functions that change the state of Photoshop or documents
// also running asynchronously because we want to await flatten() and duplicate() completion
await core.executeAsModal(async () => {
  // flatten docZero  
  await docZero.flatten();
  // rename layer with counter
  docZero.layers[0].name = `Layer ${docCounter}`;
  // increment counter
  docCounter++;

  // loop through other open docs
  for (const doc of docsToStack) {
    // flatten
    await doc.flatten();
    // get layer to duplicate
    const layerToDuplicate = doc.layers[0];
    // duplicate it to docZero
    await layerToDuplicate.duplicate(docZero);
    // close doc
    await doc.closeWithoutSaving();
    // rename new layer in docZero with counter
    docZero.layers[docCounter].name = `Layer ${docCounter}`;
    // increment counter
    docCounter++;
  }
});
1 Like

@Timothy_Bennett, thank you for this. I certainly feel like this is exactly what I need, but I don’t know how to go about using it, grrr … I didn’t want to leave your response hanging, but I feel like I’m going to have to take a deep breath and start over. I’m confused because I started another plugin with index.html and js using BatchPlay as Alchemist makes it really easy to plug things in, but then now I don’t know how to switch back and use UXP at the same time. Is it possible?

If anyone wants to make some money helping me put this together I am not going to treat this community as a place for hand me outs. I value your abilities - I’m short on time and need to get something working again. I can certainly run processes through Alchemist and make buttons function, but there are a few issues like this I’m having and it’s due to my lack of understanding and I’m somewhere in the middle trying to find out where to start but lacking the time to start over. Maybe I just need to see it done and then I’d face palming ???

@RyanBlack no worries, if it weren’t for the kindness of random strangers on internet forums then I wouldn’t be here to help you now!
I think you are probably heading for a facepalm moment if you can already take Alchemist code and wire it up to a button, so hopefully the below will make things a bit clearer.

batchPlay and UXP are not independent of each other, batchPlay is part of UXP and is just one way of writing functions that directly control Photoshop; using the API is the other.
You can include batchPlay functions in your JS files alongside API calls, or just use API calls (provided they exist for the Photoshop actions you want to perform), or just batchPlay the whole thing. There’s no real advantage to either approach.

I’ve had a chance to test/tweak the code I posted last night (it was janky, in my defense, it was very late when I wrote it!), but I’ve got a working version for you. Let’s have a look at it and see if it helps you understand UXP a bit better…

Make yourself a new plugin with the UXP Developer Tool or grab one you already have and in your index.html add:
<sp-button id="btnStack">Stack</sp-button>
I’m assuming you can figure that one out!

Next, bob over to index.js and at the very top of the file you’re going to want to handle your imports.
For this we’re going to need app and core from the photoshop module:
const { app, core } = require("photoshop");
This is going to load a whole load of JS from UXP into our index.js file at compilation time that’ll give us access to the API functions associated with app and core as if we’d coded them into our file ourselves.
You’ll have done similar for batchPlay.

Next we’ll take a modified version of what I posted last night and wrap it in a function called stackLayers():


//running asynchronously because we want to await executeAsModal
const stackLayers = async () => {

  // the doc to stack into
  const docZero = app.documents[0];

  // the other open docs
  // this uses ES6 array.filter to return an array of the open documents that DO NOT share the same id as the first open document
  const docsToStack = app.documents.filter((doc) => doc._id !== docZero._id);

  // a counter to use for naming layers - declaring outside the scope of our for loop so as not reset on each iteration
  let docCounter = 0;

  // execute as modal is required for functions that change the state of Photoshop or documents
  // think of it as a function that 'wraps' yours and tells Photoshop to go into a modal state and not allow anything to interrupt it from doing whatever is contained in the executeAsModal
  // we also call it with the await keyword to tell JS that we want to wait for it to complete before moving on to later code (in this case there isn't any though)
  await core.executeAsModal(() => {

    // flatten docZero
    docZero.flatten();

    // rename layer with counter
    docZero.layers[0].name = `Layer ${docCounter}`;

    // increment counter
    docCounter++;

    // loop through other open docs
    for (const doc of docsToStack) {

      // flatten
      doc.flatten();

      // rename layer with counter
      doc.layers[0].name = `Layer ${docCounter}`;

      // increment counter
      docCounter++;

      // duplicate layer to docZero
      doc.layers[0].duplicate(docZero);

      // close doc
      doc.closeWithoutSaving();

    }
  });
};

Finally, we need to add an eventListener to index.js for the button that will call our function, we’ll put it right after the stackLayers() function we wrote:

document.getElementById("btnStack").addEventListener("click", stackLayers);

Bob’s your uncle, one pure API UXP plugin!

For completion’s sake here’s a rough example of including some batchPlay in our stackLayers() function:

const { app, core } = require("photoshop");
const batchPlay = require("photoshop").action.batchPlay;


// a batchPlay function to flatten active document
const flattenBatchPlay = () => {
  return batchPlay(
    [
       {
          "_obj": "flattenImage",
          "_isCommand": true
       }
    ],{});
  }

// our old stackLayers() function 
const stackLayers = async () => {
  await core.executeAsModal(() => {
      // call batchPlay function
      flattenBatchPlay()
  
      // some vanillla JS
      docZero.layers[0].name = `Layer ${docCounter}`;

      // do some stuff - batchPlay or API

      // close doc with API 
      doc.closeWithoutSaving();

    });
};

Hopefully that’s illuminated things a bit for you - otherwise feel free to DM me!

1 Like

@Timothy_Bennett … you sir are a fantastic individual.

I now have a perfectly functional stack button AND it renames my layers like a champ. Super simple. I modified so that flatten is disabled, this way my final document maintains all layers as smart objects, this helps for making RAW adjustments to a layer if I happened to miss something in ACR post. It speeds the process of the action to as it doesn’t have to flatten prior to the next step between each document.

So, I am working my way through the course you suggested in my introduction post. Looking forward to the more capable future me :smile: I remember having very similar feelings about photography when I realized that I wanted to be a photographer, couldn’t stop thinking about it, all the tutorials yada yada. It was a blast… I have that same vibe going on about making a plugin to help our team.

More noob: When I’m reading the API it still seems hazy and hard to follow. For example when I look at your code it makes complete sense, but as soon as I run over to the API and try to put things together for something else I easily get lost. I haven’t yet found a how to / tutorial on how to understand the layout - how to use the API pages :\ I’m sure I just need to give it more time.

My next process will be to mask all layers in a document with feather properties. I feel like after seeing your code for stacking documents as layers, it should be a very similar code. Thinking this through I would need to use an array like you did for

const docsToStack = app.documents.filter((doc) => doc._id !== docZero._id);

1 Like

Well done navigating the rough seas of UXP as a relative newcomer. Given your familiarity with CEP, I was going to suggest looking at the bundled script “Load Files into Stack.jsx” found in the Presets/Scripts folder – bearing in mind that the script is fairly ancient. Since you have already received some excellent help, you’re probably better off having worked through it.

Still, there are interesting bits in the bundled scripts even though they are still ExtendScript.

1 Like

Hurrah, I’m glad that’s all worked out for you!

I hear you, making sense of the documentation can be a challenge!

In regards to your second question about masks - as yet the API doesn’t provide masking functionality and so you’re in batchPlay land with that.
Fear not! You basically want to follow the rough batchPlay example I gave in my previous answer:

You’re going to want to get a handle on the active document’s layers:
const layers = app.activeDocument.layers;
and then use that to build a for loop to iterate over each layer, and call your batchPlay functions on each iteration.
I imagine that you’ll probably need to pass some parameters into the batchPlay functions for things like layer id’s and what not, but that’s pretty straightforward from within your loop.

Let us know how you get on!

1 Like