Skip to main content
DBarranca
Legend
July 26, 2015
Question

Build Layers tree

  • July 26, 2015
  • 1 reply
  • 883 views

Hi,

I've been asked to retrieve the Layers' bounds in a document - which is somehow easy also because there already are few examples in the forums.

The fastest approach uses ActionManager (AM) to get the "flat" list of all layers, then again via AM it gets the bounds or whatever you need.

In my own case the layers are *highly* nested in layerSets, and the requirement is to build a URL-like list of them:

DAY/touring

TopLeft (198 px, 23 px); BottomRight (282 px, 40 px); Width (84 px); Height (17 px); Center (240 px, 31.5 px);

DAY/WARNING e SERVICE/alert generico/alert generico

TopLeft (102 px, 144 px); BottomRight (124 px, 164 px); Width (22 px); Height (20 px); Center (113 px, 154 px);

DAY/WARNING e SERVICE/alert generico/Rettangolo arrotondato

TopLeft (99 px, 140 px); BottomRight (127 px, 168 px); Width (28 px); Height (28 px); Center (113 px, 154 px);

where the name after the last "/" is the layerName, everything before is nested layerSet names.

I've been able to get there using a recursive function:

function printData(lay, layPath) {

    logFile.writeln(    layPath + lay.name + "\n" +

                "TopLeft (" + lay.bounds[0] + ", " + lay.bounds[1] + "); BottomRight (" + lay.bounds[2] + ", " + lay.bounds[3] + "); " +

                "Width (" + (lay.bounds[2] - lay.bounds[0]) + "); " + "Height (" + (lay.bounds[3] - lay.bounds[1]) + "); " +

                "Center (" + (lay.bounds[0] + (lay.bounds[2] - lay.bounds[0]) / 2) + ", " + (lay.bounds[1] + (lay.bounds[3] - lay.bounds[1]) / 2) + ");\n\n"

    );

}

function traverseLayers(layerset, layPath, ftn) {

    for (var i = 0; i < layerset.layers.length; i++) {

        app.activeDocument.activeLayer = layerset.layers;

        if (app.activeDocument.activeLayer.typename == "LayerSet") {

            traverseLayers(app.activeDocument.activeLayer, layPath + app.activeDocument.activeLayer.name + "/", ftn);

        } else {

            ftn(app.activeDocument.activeLayer, layPath);

        }

    }

};

// Main

var oldPref = app.preferences.rulerUnits;

app.preferences.rulerUnits = Units.PIXELS;

var logFileName = app.activeDocument.name.replace(/\.[^\.]+$/, ''); 

var logFile = new File(Folder.desktop + "/" + logFileName + ".txt"); 

logFile.open("w", "TEXT", "????"); 

$.os.match(/windows/gi) ? logFile.lineFeed = 'windows'  : logFile.lineFeed = 'macintosh';

var doc = app.activeDocument;

doc.activeLayer = doc.layers[0]

for (var i = 0, count = doc.layers.length; i < count; i++) {

    app.activeDocument.activeLayer = doc.layers

    if (doc.layers.typename == "LayerSet") {

        traverseLayers(doc.layers, doc.layers.name + "/", printData);

    } else {

        printData(doc.layers, "/");

    }

}

logFile.close();

app.preferences.rulerUnits = oldPref;

"EOF";

but it is, as expected, pretty darn slow - for a 400 variously nested layers document it takes several minutes and my old Mac fans spin like crazy.

So I wondered about refactoring it using the "flat-list" (i.e. a list of layers detached from their parent layerSets) ActionManager approach, but how to store full paths (with parent layers)?

I thought about getting the layers via AM, then using a recursive function only to get the layer.parent property, but "parent" is not in the layer's descriptor so I would end up using DOM code again recursively (slow).

Suggestions?

Thank you in advance!

Davide Barranca

---

www.davidebarranca.com

www.cs-extensions.com

This topic has been closed for replies.

1 reply

c.pfaffenbichler
Community Expert
Community Expert
July 26, 2015

Would it help to get a list of all Layers and their indices with their parental Layers and their indices?

// 2015, use it at your own risk;

#target "photoshop-70.032"

if (app.documents.length > 0) {

var myDocument = app.activeDocument;

var aaa = getLayersIncexAndID ();

alert (aaa.join("\n"))

};

////////////////////////////////////

function getLayersIncexAndID () {

var ref = new ActionReference();

ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );

var applicationDesc = executeActionGet(ref);

var theNumber = applicationDesc.getInteger(stringIDToTypeID("numberOfLayers"));

// anumber is intended to keep track of layerset depth;

var aNumber = 0;

var theArray = new Array;

var theGroups = new Array;

////// work through layers //////

for (var m = theNumber; m >= 0; m--) {

try {

var ref = new ActionReference();

ref.putIndex( charIDToTypeID( "Lyr " ), m);

var layerDesc = executeActionGet(ref);

var layerSet = typeIDToStringID(layerDesc.getEnumerationValue(stringIDToTypeID("layerSection")));

var isBackground = layerDesc.getBoolean(stringIDToTypeID("background"));

var theName = layerDesc.getString(stringIDToTypeID('name'));

var theID = layerDesc.getInteger(stringIDToTypeID('layerID'));

////////////////////////////////////

// if group start:

if (layerSet == "layerSectionStart" && isBackground != true) {

if (aNumber == 0) {var setArray = [[theName, theID]]}

else {

// include groups in array;

  var thisArray = [[theName, theID]];

  for (var o = setArray.length - 1; o >= 0; o--) {thisArray.push(setArray)};

  theArray.push(thisArray)

// set array;

  setArray.push([theName, theID])

  };

theGroups.push([theName, theID]);

// add to mark group;

aNumber++

};

// if group end;

if (layerSet == "layerSectionEnd" && isBackground != true) {

// subtract to mark end of group;

aNumber--;

if (aNumber == 0) {var setArray = []}

else {setArray.pop()}

};

// if neither group start or end;

if (layerSet != "layerSectionEnd" && layerSet != "layerSectionStart" /*&& isBackground != true*/) {

// if anumber is 0 layer is top level;

if (aNumber == 0) {theArray.push([[theName, theID]])}

else {

  var thisArray = [[theName, theID]];

  for (var o = setArray.length - 1; o >= 0; o--) {thisArray.push(setArray)};

  theArray.push(thisArray)

  };

};

////////////////////////////////////

}

catch (e) {};

};

// the results;

return theArray

};

DBarranca
DBarrancaAuthor
Legend
July 26, 2015

Hi Christoph,

sure it helps, thank you! Fascinating stuff, but I'm having a bit of a headache trying to understand the flow.

I've set up a simple file such as:

To understand better, I've used indexes rather than IDs. Looping through them gives:

name: A - index: 8

    name: B - index: 7

        name: B_one - index: 6

    name: </Layer group> - index: 5

    name: A_one - index: 4

name: </Layer group> - index: 3

name: One - index: 2

name: Layer 0 - index: 1

So as expected LayerSets have a layerSectionEnd that occupies an extra index.

Doing extra log in your script returns:

setArray  = [ ["A", 8] ]

theGroups = [ ["A", 8] ]

aNumber   = 1

thisArray = [ ["B", 7] ]

thisArray = [ ["B", 7], ["A", 8] ]

theArray  = [ [ ["B", 7], ["A", 8] ] ]

setArray  = [ ["A", 8], ["B", 7] ]

theGroups = [ ["A", 8], ["B", 7] ]

aNumber   = 2

thisArray = [ ["B_one", 6] ]

thisArray = [ ["B_one", 6], ["B", 7] ]

thisArray = [ ["B_one", 6], ["B", 7], ["A", 8] ]

theArray  = [ [ ["B", 7], ["A", 8] ], [ ["B_one", 6], ["B", 7], ["A", 8] ] ]

thisArray = [ ["A_one", 4] ]

thisArray = [ ["A_one", 4], ["A", 8] ]

theArray  = [ [ ["B", 7], ["A", 8] ], [ ["B_one", 6], ["B", 7], ["A", 8] ], [ ["A_one", 4], ["A", 8] ] ]

theArray  = [ [ ["B", 7], ["A", 8] ], [ ["B_one", 6], ["B", 7], ["A", 8] ], [ ["A_one", 4], ["A", 8] ], [ ["One", 2] ] ]

theArray  = [ [ ["B", 7], ["A", 8] ], [ ["B_one", 6], ["B", 7], ["A", 8] ], [ ["A_one", 4], ["A", 8] ], [ ["One", 2] ], [ ["Layer 0", 1] ] ]

---

B,7,A,8

B_one,6,B,7,A,8

A_one,4,A,8

One,2

Layer 0,1

What I'm not getting properly is how you use theArray, thisArray and theGroups (well... everything :-) )

Besides comments in code, could you please add some extra information?

Thank you very much for your help,

-Davide

matias.kiviniemi
Legend
July 26, 2015

A mixed collection of thoughts in no particular relevance, pick the ones that make sense:

  • In your DOM-code, optimize the layers.length in the for-loop. It's not a real property like a javascript array but value gets counted every time (don't ask how).
  • In your DOM-code, do you really need to set .activeLayer? It's quite slow in my experience
  • Truly faster way is to use Generator, you get full doc JSON in less than a second for normal size docs, probably "~few secs" in most cases. JSON event format · adobe-photoshop/generator-core Wiki · GitHub